diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ac7915f22..2015604e64 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,21 +3,21 @@ accounts/usbwallet @karalabe accounts/scwallet @gballet -accounts/abi @gballet +accounts/abi @gballet @MariusVanDerWijden cmd/clef @holiman cmd/puppeth @karalabe consensus @karalabe core/ @karalabe @holiman @rjl493456442 -dashboard/ @kurkomisi eth/ @karalabe @holiman @rjl493456442 +eth/catalyst/ @gballet graphql/ @gballet les/ @zsfelfoldi @rjl493456442 light/ @zsfelfoldi @rjl493456442 mobile/ @karalabe @ligi +node/ @fjl @renaynay p2p/ @fjl @zsfelfoldi rpc/ @fjl @holiman -p2p/simulations @zelig @janos @justelad -p2p/protocols @zelig @janos @justelad -p2p/testing @zelig @janos @justelad +p2p/simulations @fjl +p2p/protocols @fjl +p2p/testing @fjl signer/ @holiman -whisper/ @gballet @gluk256 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f87996cdcb..a08542df25 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,11 +30,11 @@ Please make sure your contributions adhere to our coding guidelines: Before you submit a feature request, please check and make sure that it isn't possible through some other means. The JavaScript-enabled console is a powerful feature in the right hands. Please check our -[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info +[Geth documentation page](https://geth.ethereum.org/docs/) for more info and help. ## Configuration, dependencies, and tests -Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide) for more details on configuring your environment, managing project dependencies and testing procedures. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug.md similarity index 50% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug.md index 59285e456d..2aa2c48a60 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,8 +1,10 @@ -Hi there, - -Please note that this is an issue tracker reserved for bug reports and feature requests. - -For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. +--- +name: Report a bug +about: Something with go-ethereum is not working as expected +title: '' +labels: 'type:bug' +assignees: '' +--- #### System information @@ -24,3 +26,5 @@ Commit hash : (if `develop`) ```` [backtrace] ```` + +When submitting logs: please submit them as text and not screenshots. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000000..aacd885f9e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,17 @@ +--- +name: Request a feature +about: Report a missing feature - e.g. as a step before submitting a PR +title: '' +labels: 'type:feature' +assignees: '' +--- + +# Rationale + +Why should this feature exist? +What are the use-cases? + +# Implementation + +Do you have ideas regarding the implementation of this feature? +Are you willing to implement this feature? \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..8f460ab558 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,9 @@ +--- +name: Ask a question +about: Something is unclear +title: '' +labels: 'type:docs' +assignees: '' +--- + +This should only be used in very rare cases e.g. if you are not 100% sure if something is a bug or asking a question that leads to improving the documentation. For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. diff --git a/.golangci.yml b/.golangci.yml index 24d00da6ec..18b325e206 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,7 @@ # This file configures github.com/golangci/golangci-lint. run: - timeout: 2m + timeout: 3m tests: true # default is true. Enables skipping of directories: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ diff --git a/.travis.yml b/.travis.yml index 6f4483c4b0..7406f31fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ jobs: allow_failures: - stage: build os: osx - go: 1.14.x + go: 1.15.x env: - azure-osx - azure-ios @@ -15,8 +15,8 @@ jobs: # This builder only tests code linters on latest version of Go - stage: lint os: linux - dist: xenial - go: 1.14.x + dist: bionic + go: 1.16.x env: - lint git: @@ -24,85 +24,12 @@ jobs: script: - go run build/ci.go lint - - stage: build - os: linux - dist: xenial - go: 1.11.x - env: - - GO111MODULE=on - script: - - go run build/ci.go install - - go run build/ci.go test -coverage $TEST_PACKAGES - - - stage: build - os: linux - dist: xenial - go: 1.12.x - env: - - GO111MODULE=on - script: - - go run build/ci.go install - - go run build/ci.go test -coverage $TEST_PACKAGES - - - 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 - stage: build if: type = push os: linux - dist: xenial - go: 1.14.x + dist: bionic + go: 1.16.x env: - ubuntu-ppa - GO111MODULE=on @@ -119,15 +46,15 @@ jobs: - python-paramiko script: - echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts - - go run build/ci.go debsrc -goversion 1.14.2 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " + - go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " # This builder does the Linux Azure uploads - stage: build if: type = push os: linux - dist: xenial + dist: bionic sudo: required - go: 1.14.x + go: 1.16.x env: - azure-linux - GO111MODULE=on @@ -139,32 +66,32 @@ jobs: - gcc-multilib script: # Build for the primary platforms that Trusty can manage - - go run build/ci.go install - - 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 archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go install -dlgo + - go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go install -dlgo -arch 386 + - go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # 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 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 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 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 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 archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - 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 -signify SIGNIFY_KEY -upload gethstore/builds + - 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 -signify SIGNIFY_KEY -upload gethstore/builds + - 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 -signify SIGNIFY_KEY -upload gethstore/builds + - 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 -signify SIGNIFY_KEY -upload gethstore/builds # This builder does the Linux Azure MIPS xgo uploads - stage: build if: type = push os: linux - dist: xenial + dist: bionic services: - docker - go: 1.14.x + go: 1.16.x env: - azure-linux-mips - GO111MODULE=on @@ -173,38 +100,29 @@ jobs: script: - go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done - - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done - - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done - - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY signify SIGNIFY_KEY -upload gethstore/builds - go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v - for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done - - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # This builder does the Android Maven and Azure uploads - stage: build if: type = push os: linux - dist: xenial + dist: bionic addons: apt: packages: - - oracle-java8-installer - - oracle-java8-set-default - language: android - android: - components: - - platform-tools - - tools - - android-15 - - android-19 - - android-24 + - openjdk-8-jdk env: - azure-android - maven-android @@ -212,25 +130,33 @@ jobs: git: submodules: false # avoid cloning ethereum/tests before_install: - - curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -xz + # Install Android and it's dependencies manually, Travis is stale + - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 + - curl https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip -o android.zip + - unzip -q android.zip -d $HOME/sdk && rm android.zip + - mv $HOME/sdk/cmdline-tools $HOME/sdk/latest && mkdir $HOME/sdk/cmdline-tools && mv $HOME/sdk/latest $HOME/sdk/cmdline-tools + - export PATH=$PATH:$HOME/sdk/cmdline-tools/latest/bin + - export ANDROID_HOME=$HOME/sdk + + - yes | sdkmanager --licenses >/dev/null + - sdkmanager "platform-tools" "platforms;android-15" "platforms;android-19" "platforms;android-24" "ndk-bundle" + + # Install Go to allow building with + - curl https://dl.google.com/go/go1.16.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go script: # Build the Android archive and upload it to Maven Central and Azure - - curl https://dl.google.com/android/repository/android-ndk-r19b-linux-x86_64.zip -o android-ndk-r19b.zip - - unzip -q android-ndk-r19b.zip && rm android-ndk-r19b.zip - - mv android-ndk-r19b $ANDROID_HOME/ndk-bundle - - mkdir -p $GOPATH/src/github.com/ethereum - ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum - - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds + - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -signify SIGNIFY_KEY -deploy https://oss.sonatype.org -upload gethstore/builds # This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads - stage: build if: type = push os: osx - go: 1.14.x + go: 1.16.x env: - azure-osx - azure-ios @@ -239,8 +165,8 @@ jobs: git: submodules: false # avoid cloning ethereum/tests script: - - go run build/ci.go install - - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds + - go run build/ci.go install -dlgo + - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # Build the iOS framework and upload it to CocoaPods and Azure - gem uninstall cocoapods -a -x @@ -255,14 +181,45 @@ jobs: # Workaround for https://github.com/golang/go/issues/23749 - 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 -signify SIGNIFY_KEY -deploy trunk -upload gethstore/builds + + # These builders run the tests + - stage: build + os: linux + arch: amd64 + dist: bionic + go: 1.16.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: bionic + go: 1.16.x + env: + - GO111MODULE=on + script: + - go run build/ci.go test -coverage $TEST_PACKAGES + + - stage: build + os: linux + dist: bionic + go: 1.15.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 - stage: build if: type = cron os: linux - dist: xenial - go: 1.14.x + dist: bionic + go: 1.16.x env: - azure-purge - GO111MODULE=on diff --git a/CHANGELOG.md b/CHANGELOG.md index 52d871ee46..df8ff340de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog + +## v1.1.0 +*[\#152](https://github.com/binance-chain/bsc/pull/152) upgrade to go-ethereum 1.10.3 + ## v1.0.7-hf.2 BUGFIX * [\#194](https://github.com/binance-chain/bsc/pull/194) bump btcd to v0.20.1-beta diff --git a/COPYING b/COPYING index 8d66e87723..f288702d2f 100644 --- a/COPYING +++ b/COPYING @@ -1,7 +1,7 @@ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - Copyright (C) 2014 The go-ethereum Authors. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -616,4 +616,59 @@ above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. \ No newline at end of file +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Dockerfile b/Dockerfile index 84e10f90ea..437fb2737a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,8 @@ -FROM golang:1.15-alpine as builder +# Build Geth in a stock Go builder container +FROM golang:1.16-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git bash -# Temporarily pull a custom Go bundle -ADD https://golang.org/dl/go1.15.5.src.tar.gz /tmp/go.tar.gz -RUN (cd /tmp && tar -xf go.tar.gz) -RUN (cd /tmp/go/src && ./make.bash) -ENV PATH="/tmp/go/bin:${PATH}" - ADD . /go-ethereum RUN cd /go-ethereum && make geth diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 9c28979a1e..483afad8c3 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.14-alpine as builder +FROM golang:1.16-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git @@ -12,4 +12,4 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ -EXPOSE 8545 8546 8547 30303 30303/udp +EXPOSE 8545 8546 30303 30303/udp diff --git a/Makefile b/Makefile index 67095f4d00..cb5a87dad0 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,8 @@ android: $(GORUN) build/ci.go aar --local @echo "Done building." @echo "Import \"$(GOBIN)/geth.aar\" to use the library." + @echo "Import \"$(GOBIN)/geth-sources.jar\" to add javadocs" + @echo "For more info see https://stackoverflow.com/questions/20994336/android-studio-how-to-attach-javadoc" ios: $(GORUN) build/ci.go xcode --local @@ -44,12 +46,11 @@ clean: # You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'. devtools: - env GOBIN= go get -u golang.org/x/tools/cmd/stringer - env GOBIN= go get -u github.com/kevinburke/go-bindata/go-bindata - env GOBIN= go get -u github.com/fjl/gencodec - env GOBIN= go get -u github.com/golang/protobuf/protoc-gen-go + env GOBIN= go install golang.org/x/tools/cmd/stringer@latest + env GOBIN= go install github.com/kevinburke/go-bindata/go-bindata@latest + env GOBIN= go install github.com/fjl/gencodec@latest + env GOBIN= go install github.com/golang/protobuf/protoc-gen-go@latest env GOBIN= go install ./cmd/abigen - @type "npm" 2> /dev/null || echo 'Please install node.js and npm' @type "solc" 2> /dev/null || echo 'Please install solc' @type "protoc" 2> /dev/null || echo 'Please install protoc' diff --git a/README.md b/README.md index 3948fcc669..a5968a22a2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ The goal of Binance Smart Chain is to bring programmability and interoperability Binance Smart Chain starts its development based on go-ethereum fork. So you may see many toolings, binaries and also docs are based on Ethereum ones, such as the name “geth”. +[![API Reference]( +https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667 +)](https://pkg.go.dev/github.com/ethereum/go-ethereum?tab=doc) +[![Discord](https://img.shields.io/badge/discord-join%20chat-blue.svg)](https://discord.gg/T3Ka4V4N) + But from that baseline of EVM compatible, Binance Smart Chain introduces a system of 21 validators with Proof of Staked Authority (PoSA) consensus that can support short block time and lower fees. The most bonded validator candidates of staking will become validators and produce blocks. The double-sign detection and other slashing logic guarantee security, stability, and chain finality. Cross-chain transfer and other communication are possible due to native support of interoperability. Relayers and on-chain contracts are developed to support that. Binance DEX remains a liquid venue of the exchange of assets on both chains. This dual-chain architecture will be ideal for users to take advantage of the fast trading on one side and build their decentralized apps on the other side. **The Binance Smart Chain** will be: @@ -54,9 +59,9 @@ BNB will be used to: Many of the below are the same as or similar to go-ethereum. -For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. +For prerequisites and detailed build instructions please read the [Installation Instructions](https://geth.ethereum.org/docs/install-and-build/installing-geth). -Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install +Building `geth` requires both a Go (version 1.14 or later) and a C compiler. You can install them using your favourite package manager. Once the dependencies are installed, run ```shell @@ -76,22 +81,44 @@ directory. | Command | Description | | :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`geth`** | Main Binance Smart Chain client binary. It is the entry point into the BSC network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It has the same and more RPC and other interface as go-ethereum and can be used by other processes as a gateway into the BSC network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. | -| `abigen`,`bootnode`,`evm`, `gethrpctest`,`rlpdump`,`puppeth` | **These binaries are exactly the same as the binaries built in go-ethereum repo.**| +| **`geth`** | Main Binance Smart Chain client binary. It is the entry point into the BSC network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It has the same and more RPC and other interface as go-ethereum and can be used by other processes as a gateway into the BSC network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI page](https://geth.ethereum.org/docs/interface/command-line-options) for command line options. | +| `clef` | Stand-alone signing tool, which can be used as a backend signer for `geth`. | +| `devp2p` | Utilities to interact with nodes on the networking layer, without running a full blockchain. | +| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://docs.soliditylang.org/en/develop/abi-spec.html) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://geth.ethereum.org/docs/dapp/native-bindings) page for details. | +| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. | +| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug run`). | +| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://eth.wiki/en/fundamentals/rlp)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | ## Running `geth` Going through all the possible command line flags is out of scope here (please consult our -[CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)). +[CLI Wiki page](https://geth.ethereum.org/docs/interface/command-line-options)), +but we've enumerated a few common parameter combos to get you up to speed quickly +on how you can run your own `geth` instance. ### Hardware Requirements The hardware must meet certain requirements to run a full node. - VPS running recent versions of Mac OS X or Linux. -- 500 GB of free disk space -- 8 cores of CPU and 16 gigabytes of memory (RAM) for mainnet. +- 1T of SSD storage for mainnet, 500G of SSD storage for testnet. +- 8 cores of CPU and 32 gigabytes of memory (RAM) for mainnet. - 4 cores of CPU and 8 gigabytes of memory (RAM) for testnet. -- A broadband Internet connection with upload/download speeds of at least 1 megabyte per second +- A broadband Internet connection with upload/download speeds of at least 10 megabyte per second + +```shell +$ geth console +``` + +This command will: + * Start `geth` in fast sync mode (default, can be changed with the `--syncmode` flag), + causing it to download more data in exchange for avoiding processing the entire history + of the Binance Smart Chain network, which is very CPU intensive. + * Start up `geth`'s built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console), + (via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://web3js.readthedocs.io/en/) + (note: the `web3` version bundled within `geth` is very old, and not up to date with official docs), + as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/rpc/server). + This tool is optional and if you leave it out you can always attach to an already running + `geth` instance with `geth attach`. ### A Full node on the Rialto test network @@ -112,12 +139,28 @@ use separate accounts for play-money and real-money. Unless you manually move accounts, `geth` will by default correctly separate the two networks and will not make any accounts available between them.* +### Configuration + +As an alternative to passing the numerous flags to the `geth` binary, you can also pass a +configuration file via: + +```shell +$ geth --config /path/to/your_config.toml +``` + +To get an idea how the file should look like you can use the `dumpconfig` subcommand to +export your existing configuration: + +```shell +$ geth --your-favourite-flags dumpconfig +``` + ### Programmatically interfacing `geth` nodes As a developer, sooner rather than later you'll want to start interacting with `geth` and the Binance Smart Chain network via your own programs and not manually through the console. To aid -this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) -and [`geth` specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). +this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://eth.wiki/json-rpc/API) +and [`geth` specific APIs](https://geth.ethereum.org/docs/rpc/server)). These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based platforms, and named pipes on Windows). @@ -128,16 +171,16 @@ you'd expect. HTTP based JSON-RPC API options: - * `--rpc` Enable the HTTP-RPC server - * `--rpcaddr` HTTP-RPC server listening interface (default: `localhost`) - * `--rpcport` HTTP-RPC server listening port (default: `8545`) - * `--rpcapi` API's offered over the HTTP-RPC interface (default: `eth,net,web3`) - * `--rpccorsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced) + * `--http` Enable the HTTP-RPC server + * `--http.addr` HTTP-RPC server listening interface (default: `localhost`) + * `--http.port` HTTP-RPC server listening port (default: `8545`) + * `--http.api` API's offered over the HTTP-RPC interface (default: `eth,net,web3`) + * `--http.corsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced) * `--ws` Enable the WS-RPC server - * `--wsaddr` WS-RPC server listening interface (default: `localhost`) - * `--wsport` WS-RPC server listening port (default: `8546`) - * `--wsapi` API's offered over the WS-RPC interface (default: `eth,net,web3`) - * `--wsorigins` Origins from which to accept websockets requests + * `--ws.addr` WS-RPC server listening interface (default: `localhost`) + * `--ws.port` WS-RPC server listening port (default: `8546`) + * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`) + * `--ws.origins` Origins from which to accept websockets requests * `--ipcdisable` Disable the IPC-RPC server * `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,shh,txpool,web3`) * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it) @@ -153,6 +196,32 @@ BSC nodes with exposed APIs! Further, all browser tabs can access locally running web servers, so malicious web pages could try to subvert locally available APIs!** +## Contribution + +Thank you for considering to help out with the source code! We welcome contributions +from anyone on the internet, and are grateful for even the smallest of fixes! + +If you'd like to contribute to bsc, please fork, fix, commit and send a pull request +for the maintainers to review and merge into the main code base. If you wish to submit +more complex changes though, please check up with the core devs first on [our discord channel](https://discord.com/invite/T3Ka4V4N) +to ensure those changes are in line with the general philosophy of the project and/or get +some early feedback which can make both your efforts much lighter as well as our review +and merge procedures quick and simple. + +Please make sure your contributions adhere to our coding guidelines: + + * Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) + guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). + * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) + guidelines. + * Pull requests need to be based on and opened against the `master` branch. + * Commit messages should be prefixed with the package(s) they modify. + * E.g. "eth, rpc: make trace configs optional" + +Please see the [Developers' Guide](https://geth.ethereum.org/docs/developers/devguide) +for more details on configuring your environment, managing project dependencies, and +testing procedures. + ## License The bsc library (i.e. all code outside of the `cmd` directory) is licensed under the diff --git a/SECURITY.md b/SECURITY.md index bc54ede42f..d6c2197ef3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,119 +2,21 @@ ## Supported Versions -Please see Releases. We recommend to use the most recent released version. +Please see [Releases](https://github.com/binance-chain/bsc/releases). We recommend using the [most recently released version](https://github.com/binance-chain/bsc/releases/latest). ## Audit reports -Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits - +Audit reports are published in the `docs` folder: https://github.com/binance-chain/bsc/tree/master/docs/audits | Scope | Date | Report Link | | ------- | ------- | ----------- | | `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) | | `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) | - - ## Reporting a Vulnerability **Please do not file a public ticket** mentioning the vulnerability. -To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. - -The following key may be used to communicate sensitive information to developers. - -Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A` - - -``` ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 +To find out how to disclose a vulnerability in Ethereum visit [https://bugcrowd.com/binance](https://bugcrowd.com/binance) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/binance-chain/bsc/security/advisories) for more information about publicly disclosed security vulnerabilities. -mQINBFgl3tgBEAC8A1tUBkD9YV+eLrOmtgy+/JS/H9RoZvkg3K1WZ8IYfj6iIRaY -neAk3Bp182GUPVz/zhKr2g0tMXIScDR3EnaDsY+Qg+JqQl8NOG+Cikr1nnkG2on9 -L8c8yiqry1ZTCmYMqCa2acTFqnyuXJ482aZNtB4QG2BpzfhW4k8YThpegk/EoRUi -m+y7buJDtoNf7YILlhDQXN8qlHB02DWOVUihph9tUIFsPK6BvTr9SIr/eG6j6k0b -fUo9pexOn7LS4SojoJmsm/5dp6AoKlac48cZU5zwR9AYcq/nvkrfmf2WkObg/xRd -EvKZzn05jRopmAIwmoC3CiLmqCHPmT5a29vEob/yPFE335k+ujjZCPOu7OwjzDk7 -M0zMSfnNfDq8bXh16nn+ueBxJ0NzgD1oC6c2PhM+XRQCXChoyI8vbfp4dGvCvYqv -QAE1bWjqnumZ/7vUPgZN6gDfiAzG2mUxC2SeFBhacgzDvtQls+uuvm+FnQOUgg2H -h8x2zgoZ7kqV29wjaUPFREuew7e+Th5BxielnzOfVycVXeSuvvIn6cd3g/s8mX1c -2kLSXJR7+KdWDrIrR5Az0kwAqFZt6B6QTlDrPswu3mxsm5TzMbny0PsbL/HBM+GZ -EZCjMXxB8bqV2eSaktjnSlUNX1VXxyOxXA+ZG2jwpr51egi57riVRXokrQARAQAB -tDlFdGhlcmV1bSBGb3VuZGF0aW9uIFNlY3VyaXR5IFRlYW0gPHNlY3VyaXR5QGV0 -aGVyZXVtLm9yZz6JAj4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA -BQJaCWH6BQkFo2BYAAoJEOiNMzT6X2oK+DEP/3H6dxkm0hvHZKoHLVuuxcu3EHYo -k5sd3MMWPrZSN8qzZnY7ayEDMxnarWOizc+2jfOxfJlzX/g8lR1/fsHdWPFPhPoV -Qk8ygrHn1H8U8+rpw/U03BqmqHpYCDzJ+CIis9UWROniqXw1nuqu/FtWOsdWxNKh -jUo6k/0EsaXsxRPzgJv7fEUcVcQ7as/C3x9sy3muc2gvgA4/BKoGPb1/U0GuA8lV -fDIDshAggmnSUAg+TuYSAAdoFQ1sKwFMPigcLJF2eyKuK3iUyixJrec/c4LSf3wA -cGghbeuqI8INP0Y2zvXDQN2cByxsFAuoZG+m0cyKGaDH2MVUvOKKYqn/03qvrf15 -AWAsW0l0yQwOTCo3FbsNzemClm5Bj/xH0E4XuwXwChcMCMOWJrFoxyvCEI+keoQc -c08/a8/MtS7vBAABXwOziSmm6CNqmzpWrh/fDrjlJlba9U3MxzvqU3IFlTdMratv -6V+SgX+L25lCzW4NxxUavoB8fAlvo8lxpHKo24FP+RcLQ8XqkU3RiUsgRjQRFOqQ -TaJcsp8mimmiYyf24mNu6b48pi+a5c/eQR9w59emeEUZqsJU+nqv8BWIIp7o4Agh -NYnKjkhPlY5e1fLVfAHIADZFynWwRPkPMJSrBiP5EtcOFxQGHGjRxU/KjXkvE0hV -xYb1PB8pWMTu/beeiQI+BBMBAgAoBQJYJd7YAhsDBQkB4TOABgsJCAcDAgYVCAIJ -CgsEFgIDAQIeAQIXgAAKCRDojTM0+l9qCplDD/9IZ2i+m1cnqQKtiyHbyFGx32oL -fzqPylX2bOG5DPsSTorSUdJMGVfT04oVxXc4S/2DVnNvi7RAbSiLapCWSplgtBOj -j1xlblOoXxT3m7s1XHGCX5tENxI9fVSSPVKJn+fQaWpPB2MhBA+1lUI6GJ+11T7K -J8LrP/fiw1/nOb7rW61HW44Gtyox23sA/d1+DsFVaF8hxJlNj5coPKr8xWzQ8pQl -juzdjHDukjevuw4rRmRq9vozvj9keEU9XJ5dldyEVXFmdDk7KT0p0Rla9nxYhzf/ -r/Bv8Bzy0HCWRb2D31BjXXGG05oVnYmNGxGFxYja4MwgrMmne3ilEVjfUJsapsqi -w41BAyQgIdfREulYN7ahsF5PrjVAqBd9IGtE8ULelF2SQxEBQBngEkP0ahP6tRAL -i7/CBjPKOyKijtqVny7qrGOnU2ygcA88/WDibexDhrjz0Gx8WmErU7rIWZiZ5u4Y -vJYVRo0+6rBCXRPeSJfiP5h1p17Anr2l42boAYslfcrzquB8MHtrNcyn650OLtHG -nbxgIdniKrpuzGN6Opw+O2id2JhD1/1p4SOemwAmthplr1MIyOHNP3q93rEj2J7h -5zPS/AJuKkMDFUpslPNLQjCOwPXtdzL7/kUZGBSyez1T3TaW1uY6l9XaJJRaSn+v -1zPgfp4GJ3lPs4AlAbQ0RXRoZXJldW0gRm91bmRhdGlvbiBCdWcgQm91bnR5IDxi -b3VudHlAZXRoZXJldW0ub3JnPokCPgQTAQIAKAIbAwYLCQgHAwIGFQgCCQoLBBYC -AwECHgECF4AFAloJYfoFCQWjYFgACgkQ6I0zNPpfagoENg/+LnSaVeMxiGVtcjWl -b7Xd73yrEy4uxiESS1AalW9mMf7oZzfI05f7QIQlaLAkNac74vZDJbPKjtb7tpMO -RFhRZMCveq6CPKU6pd1SI8IUVUKwpEe6AJP3lHdVP57dquieFE2HlYKm6uHbCGWU -0cjyTA+uu2KbgCHGmofsPY/xOcZLGEHTHqa5w60JJAQm+BSDKnw8wTyrxGvA3EK/ -ePSvOZMYa+iw6vYuZeBIMbdiXR/A2keBi3GuvqB8tDMj7P22TrH5mVDm3zNqGYD6 -amDPeiWp4cztY3aZyLcgYotqXPpDceZzDn+HopBPzAb/llCdE7bVswKRhphVMw4b -bhL0R/TQY7Sf6TK2LKSBrjv0DWOSijikE71SJcBnJvHU7EpKrQQ0lMGclm3ynyji -Nf0YTPXQt4I+fwTmOew2GFeK3UytNWbWI7oXX7Nm4bj9bhf3IJ0kmZb/Gs73+xII -e7Rz52Mby436tWyQIQiF9ITYNGvNf53TwBBZMn0pKPiTyr3Ur7FHEotkEOFNh1// -4zQY10XxuBdLrYGyZ4V8xHJM+oKre8Eg2R9qHXVbjvErHE+7CvgnV7YUip0criPr -BlKRvuoJaSliH2JFhSjWVrkPmFGrWN0BAx10yIqMnEplfKeHf4P9Elek3oInS8WP -G1zJG6s/t5+hQK0X37+TB+6rd3GJAj4EEwECACgFAlgl4TsCGwMFCQHhM4AGCwkI -BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEOiNMzT6X2oKzf8P/iIKd77WHTbp4pMN -8h52HyZJtDJmjA1DPZrbGl1TesW/Z9uTd12txlgqZnbG2GfN9+LSP6EOPzR6v2xC -OVhR+RdWhZDJJuQCVS7lJIqQrZgmeTZG0TyQPZdLjVFBOrrhVwYX+HXbu429IzHr -URf5InyR1QgqOXyElDYS6e28HFqvaoA0DWTWDDqOLPVl+U5fuceIE2XXdv3AGLeP -Yf8J5MPobjPiZtBqI6S6iENY2Yn35qLX+axeC/iYSCHVtFuCCIdb/QYR1ZZV8Ps/ -aI9DwC7LU+YfPw7iqCIoqxSeA3o1PORkdSigEg3jtfRv5UqVo9a0oBb9jdoADsat -F/gW0E7mto3XGOiaR0eB9SSdsM3x7Bz4A0HIGNaxpZo1RWqlO91leP4c13Px7ISv -5OGXfLg+M8qb+qxbGd1HpitGi9s1y1aVfEj1kOtZ0tN8eu+Upg5WKwPNBDX3ar7J -9NCULgVSL+E79FG+zXw62gxiQrLfKzm4wU/9L5wVkwQnm29hLJ0tokrSBZFnc/1l -7OC+GM63tYicKkY4rqmoWUeYx7IwFH9mtDtvR1RxO85RbQhZizwpZpdpRkH0DqZu -ZJRmRa5r7rPqmfa7d+VIFhz2Xs8pJMLVqxTsLKcLglmjw7aOrYG0SWeH7YraXWGD -N3SlvSBiVwcK7QUKzLLvpadLwxfsuQINBFgl3tgBEACbgq6HTN5gEBi0lkD/MafI -nmNi+59U5gRGYqk46WlfRjhHudXjDpgD0lolGb4hYontkMaKRlCg2Rvgjvk3Zve0 -PKWjKw7gr8YBa9fMFY8BhAXI32OdyI9rFhxEZFfWAfwKVmT19BdeAQRFvcfd+8w8 -f1XVc+zddULMJFBTr+xKDlIRWwTkdLPQeWbjo0eHl/g4tuLiLrTxVbnj26bf+2+1 -DbM/w5VavzPrkviHqvKe/QP/gay4QDViWvFgLb90idfAHIdsPgflp0VDS5rVHFL6 -D73rSRdIRo3I8c8mYoNjSR4XDuvgOkAKW9LR3pvouFHHjp6Fr0GesRbrbb2EG66i -PsR99MQ7FqIL9VMHPm2mtR+XvbnKkH2rYyEqaMbSdk29jGapkAWle4sIhSKk749A -4tGkHl08KZ2N9o6GrfUehP/V2eJLaph2DioFL1HxRryrKy80QQKLMJRekxigq8gr -eW8xB4zuf9Mkuou+RHNmo8PebHjFstLigiD6/zP2e+4tUmrT0/JTGOShoGMl8Rt0 -VRxdPImKun+4LOXbfOxArOSkY6i35+gsgkkSy1gTJE0BY3S9auT6+YrglY/TWPQ9 -IJxWVOKlT+3WIp5wJu2bBKQ420VLqDYzkoWytel/bM1ACUtipMiIVeUs2uFiRjpz -A1Wy0QHKPTdSuGlJPRrfcQARAQABiQIlBBgBAgAPAhsMBQJaCWIIBQkFo2BYAAoJ -EOiNMzT6X2oKgSwQAKKs7BGF8TyZeIEO2EUK7R2bdQDCdSGZY06tqLFg3IHMGxDM -b/7FVoa2AEsFgv6xpoebxBB5zkhUk7lslgxvKiSLYjxfNjTBltfiFJ+eQnf+OTs8 -KeR51lLa66rvIH2qUzkNDCCTF45H4wIDpV05AXhBjKYkrDCrtey1rQyFp5fxI+0I -Q1UKKXvzZK4GdxhxDbOUSd38MYy93nqcmclGSGK/gF8XiyuVjeifDCM6+T1NQTX0 -K9lneidcqtBDvlggJTLJtQPO33o5EHzXSiud+dKth1uUhZOFEaYRZoye1YE3yB0T -NOOE8fXlvu8iuIAMBSDL9ep6sEIaXYwoD60I2gHdWD0lkP0DOjGQpi4ouXM3Edsd -5MTi0MDRNTij431kn8T/D0LCgmoUmYYMBgbwFhXr67axPZlKjrqR0z3F/Elv0ZPP -cVg1tNznsALYQ9Ovl6b5M3cJ5GapbbvNWC7yEE1qScl9HiMxjt/H6aPastH63/7w -cN0TslW+zRBy05VNJvpWGStQXcngsSUeJtI1Gd992YNjUJq4/Lih6Z1TlwcFVap+ -cTcDptoUvXYGg/9mRNNPZwErSfIJ0Ibnx9wPVuRN6NiCLOt2mtKp2F1pM6AOQPpZ -85vEh6I8i6OaO0w/Z0UHBwvpY6jDUliaROsWUQsqz78Z34CVj4cy6vPW2EF4 -=r6KK ------END PGP PUBLIC KEY BLOCK----- -``` +Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number. diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 4b88a52cef..5ca7c241db 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -24,6 +24,7 @@ import ( "io" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) // The ABI holds information about a contract's context and available @@ -76,42 +77,62 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { return nil, err } // Pack up the method ID too if not a constructor and return - return append(method.ID(), arguments...), nil + return append(method.ID, arguments...), nil } -// Unpack output in v according to the abi specification -func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) { +func (abi ABI) getArguments(name string, data []byte) (Arguments, error) { // since there can't be naming collisions with contracts and events, // we need to decide whether we're calling a method or an event + var args Arguments if method, ok := abi.Methods[name]; ok { if len(data)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) + return nil, fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) } - return method.Outputs.Unpack(v, data) + args = method.Outputs } if event, ok := abi.Events[name]; ok { - return event.Inputs.Unpack(v, data) + args = event.Inputs } - return fmt.Errorf("abi: could not locate named method or event") + if args == nil { + return nil, errors.New("abi: could not locate named method or event") + } + return args, nil } -// UnpackIntoMap unpacks a log into the provided map[string]interface{} -func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { - // since there can't be naming collisions with contracts and events, - // we need to decide whether we're calling a method or an event - if method, ok := abi.Methods[name]; ok { - if len(data)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output") - } - return method.Outputs.UnpackIntoMap(v, data) +// Unpack unpacks the output according to the abi specification. +func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error) { + args, err := abi.getArguments(name, data) + if err != nil { + return nil, err } - if event, ok := abi.Events[name]; ok { - return event.Inputs.UnpackIntoMap(v, data) + return args.Unpack(data) +} + +// UnpackIntoInterface unpacks the output in v according to the abi specification. +// It performs an additional copy. Please only use, if you want to unpack into a +// structure that does not strictly conform to the abi structure (e.g. has additional arguments) +func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error { + args, err := abi.getArguments(name, data) + if err != nil { + return err } - return fmt.Errorf("abi: could not locate named method or event") + unpacked, err := args.Unpack(data) + if err != nil { + return err + } + return args.Copy(v, unpacked) } -// UnmarshalJSON implements json.Unmarshaler interface +// UnpackIntoMap unpacks a log into the provided map[string]interface{}. +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) { + args, err := abi.getArguments(name, data) + if err != nil { + return err + } + return args.UnpackIntoMap(v, data) +} + +// UnmarshalJSON implements json.Unmarshaler interface. func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { Type string @@ -139,59 +160,17 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { for _, field := range fields { switch field.Type { case "constructor": - abi.Constructor = Method{ - Inputs: field.Inputs, - - // Note for constructor the `StateMutability` can only - // be payable or nonpayable according to the output of - // compiler. So constant is always false. - StateMutability: field.StateMutability, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + abi.Constructor = NewMethod("", "", Constructor, field.StateMutability, field.Constant, field.Payable, field.Inputs, nil) case "function": - name := field.Name - _, ok := abi.Methods[name] - for idx := 0; ok; idx++ { - name = fmt.Sprintf("%s%d", field.Name, idx) - _, ok = abi.Methods[name] - } - abi.Methods[name] = Method{ - Name: name, - RawName: field.Name, - StateMutability: field.StateMutability, - Inputs: field.Inputs, - Outputs: field.Outputs, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + name := abi.overloadedMethodName(field.Name) + abi.Methods[name] = NewMethod(name, field.Name, Function, field.StateMutability, field.Constant, field.Payable, field.Inputs, field.Outputs) case "fallback": // New introduced function type in v0.6.0, check more detail // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function if abi.HasFallback() { return errors.New("only single fallback is allowed") } - abi.Fallback = Method{ - Name: "", - RawName: "", - - // The `StateMutability` can only be payable or nonpayable, - // so the constant is always false. - StateMutability: field.StateMutability, - IsFallback: true, - - // Fallback doesn't have any input or output - Inputs: nil, - Outputs: nil, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + abi.Fallback = NewMethod("", "", Fallback, field.StateMutability, field.Constant, field.Payable, nil, nil) case "receive": // New introduced function type in v0.6.0, check more detail // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function @@ -201,49 +180,55 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { if field.StateMutability != "payable" { return errors.New("the statemutability of receive can only be payable") } - abi.Receive = Method{ - Name: "", - RawName: "", - - // The `StateMutability` can only be payable, so constant - // is always true while payable is always false. - StateMutability: field.StateMutability, - IsReceive: true, - - // Receive doesn't have any input or output - Inputs: nil, - Outputs: nil, - - // Legacy fields, keep them for backward compatibility - Constant: field.Constant, - Payable: field.Payable, - } + abi.Receive = NewMethod("", "", Receive, field.StateMutability, field.Constant, field.Payable, nil, nil) case "event": - name := field.Name - _, ok := abi.Events[name] - for idx := 0; ok; idx++ { - name = fmt.Sprintf("%s%d", field.Name, idx) - _, ok = abi.Events[name] - } - abi.Events[name] = Event{ - Name: name, - RawName: field.Name, - Anonymous: field.Anonymous, - Inputs: field.Inputs, - } + name := abi.overloadedEventName(field.Name) + abi.Events[name] = NewEvent(name, field.Name, field.Anonymous, field.Inputs) + default: + return fmt.Errorf("abi: could not recognize type %v of field %v", field.Type, field.Name) } } return nil } -// MethodById looks up a method by the 4-byte id -// returns nil if none found +// overloadedMethodName returns the next available name for a given function. +// Needed since solidity allows for function overload. +// +// e.g. if the abi contains Methods send, send1 +// overloadedMethodName would return send2 for input send. +func (abi *ABI) overloadedMethodName(rawName string) string { + name := rawName + _, ok := abi.Methods[name] + for idx := 0; ok; idx++ { + name = fmt.Sprintf("%s%d", rawName, idx) + _, ok = abi.Methods[name] + } + return name +} + +// overloadedEventName returns the next available name for a given event. +// Needed since solidity allows for event overload. +// +// e.g. if the abi contains events received, received1 +// overloadedEventName would return received2 for input received. +func (abi *ABI) overloadedEventName(rawName string) string { + name := rawName + _, ok := abi.Events[name] + for idx := 0; ok; idx++ { + name = fmt.Sprintf("%s%d", rawName, idx) + _, ok = abi.Events[name] + } + return name +} + +// MethodById looks up a method by the 4-byte id, +// returns nil if none found. func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { if len(sigdata) < 4 { return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata)) } for _, method := range abi.Methods { - if bytes.Equal(method.ID(), sigdata[:4]) { + if bytes.Equal(method.ID, sigdata[:4]) { return &method, nil } } @@ -254,7 +239,7 @@ func (abi *ABI) MethodById(sigdata []byte) (*Method, error) { // ABI and returns nil if none found. func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { for _, event := range abi.Events { - if bytes.Equal(event.ID().Bytes(), topic.Bytes()) { + if bytes.Equal(event.ID.Bytes(), topic.Bytes()) { return &event, nil } } @@ -263,10 +248,32 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { // HasFallback returns an indicator whether a fallback function is included. func (abi *ABI) HasFallback() bool { - return abi.Fallback.IsFallback + return abi.Fallback.Type == Fallback } // HasReceive returns an indicator whether a receive function is included. func (abi *ABI) HasReceive() bool { - return abi.Receive.IsReceive + return abi.Receive.Type == Receive +} + +// revertSelector is a special function selector for revert reason unpacking. +var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + +// UnpackRevert resolves the abi-encoded revert reason. According to the solidity +// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, +// the provided revert reason is abi-encoded as if it were a call to a function +// `Error(string)`. So it's a special tool for it. +func UnpackRevert(data []byte) (string, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], revertSelector) { + return "", errors.New("invalid data for unpacking") + } + typ, _ := NewType("string", "", nil) + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { + return "", err + } + return unpacked[0].(string), nil } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 352006cf5f..a022ec5f9d 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -19,6 +19,7 @@ package abi import ( "bytes" "encoding/hex" + "errors" "fmt" "math/big" "reflect" @@ -26,17 +27,13 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" ) const jsondata = ` [ - { "type" : "function", "name" : "balance", "stateMutability" : "view" }, - { "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } -]` - -const jsondata2 = ` -[ + { "type" : "function", "name" : ""}, { "type" : "function", "name" : "balance", "stateMutability" : "view" }, { "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, { "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, @@ -45,6 +42,8 @@ const jsondata2 = ` { "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, { "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, { "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "int8", "inputs" : [ { "name" : "inputs", "type" : "int8" } ] }, + { "type" : "function", "name" : "bytes32", "inputs" : [ { "name" : "inputs", "type" : "bytes32" } ] }, { "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, { "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, { "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, @@ -53,30 +52,83 @@ const jsondata2 = ` { "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }, { "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] }, { "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] }, - { "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] } + { "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }, + { "type" : "function", "name" : "receive", "inputs" : [ { "name" : "memo", "type" : "bytes" }], "outputs" : [], "payable" : true, "stateMutability" : "payable" }, + { "type" : "function", "name" : "fixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "fixedArrBytes", "stateMutability" : "view", "inputs" : [ { "name" : "bytes", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "mixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" } ] }, + { "type" : "function", "name" : "doubleFixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }, + { "type" : "function", "name" : "multipleMixedArrStr", "stateMutability" : "view", "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type" : "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }, + { "type" : "function", "name" : "overloadedNames", "stateMutability" : "view", "inputs": [ { "components": [ { "internalType": "uint256", "name": "_f", "type": "uint256" }, { "internalType": "uint256", "name": "__f", "type": "uint256"}, { "internalType": "uint256", "name": "f", "type": "uint256"}],"internalType": "struct Overloader.F", "name": "f","type": "tuple"}]} ]` +var ( + Uint256, _ = NewType("uint256", "", nil) + Uint32, _ = NewType("uint32", "", nil) + Uint16, _ = NewType("uint16", "", nil) + String, _ = NewType("string", "", nil) + Bool, _ = NewType("bool", "", nil) + Bytes, _ = NewType("bytes", "", nil) + Bytes32, _ = NewType("bytes32", "", nil) + Address, _ = NewType("address", "", nil) + Uint64Arr, _ = NewType("uint64[]", "", nil) + AddressArr, _ = NewType("address[]", "", nil) + Int8, _ = NewType("int8", "", nil) + // Special types for testing + Uint32Arr2, _ = NewType("uint32[2]", "", nil) + Uint64Arr2, _ = NewType("uint64[2]", "", nil) + Uint256Arr, _ = NewType("uint256[]", "", nil) + Uint256Arr2, _ = NewType("uint256[2]", "", nil) + Uint256Arr3, _ = NewType("uint256[3]", "", nil) + Uint256ArrNested, _ = NewType("uint256[2][2]", "", nil) + Uint8ArrNested, _ = NewType("uint8[][2]", "", nil) + Uint8SliceNested, _ = NewType("uint8[][]", "", nil) + TupleF, _ = NewType("tuple", "struct Overloader.F", []ArgumentMarshaling{ + {Name: "_f", Type: "uint256"}, + {Name: "__f", Type: "uint256"}, + {Name: "f", Type: "uint256"}}) +) + +var methods = map[string]Method{ + "": NewMethod("", "", Function, "", false, false, nil, nil), + "balance": NewMethod("balance", "balance", Function, "view", false, false, nil, nil), + "send": NewMethod("send", "send", Function, "", false, false, []Argument{{"amount", Uint256, false}}, nil), + "test": NewMethod("test", "test", Function, "", false, false, []Argument{{"number", Uint32, false}}, nil), + "string": NewMethod("string", "string", Function, "", false, false, []Argument{{"inputs", String, false}}, nil), + "bool": NewMethod("bool", "bool", Function, "", false, false, []Argument{{"inputs", Bool, false}}, nil), + "address": NewMethod("address", "address", Function, "", false, false, []Argument{{"inputs", Address, false}}, nil), + "uint64[]": NewMethod("uint64[]", "uint64[]", Function, "", false, false, []Argument{{"inputs", Uint64Arr, false}}, nil), + "uint64[2]": NewMethod("uint64[2]", "uint64[2]", Function, "", false, false, []Argument{{"inputs", Uint64Arr2, false}}, nil), + "int8": NewMethod("int8", "int8", Function, "", false, false, []Argument{{"inputs", Int8, false}}, nil), + "bytes32": NewMethod("bytes32", "bytes32", Function, "", false, false, []Argument{{"inputs", Bytes32, false}}, nil), + "foo": NewMethod("foo", "foo", Function, "", false, false, []Argument{{"inputs", Uint32, false}}, nil), + "bar": NewMethod("bar", "bar", Function, "", false, false, []Argument{{"inputs", Uint32, false}, {"string", Uint16, false}}, nil), + "slice": NewMethod("slice", "slice", Function, "", false, false, []Argument{{"inputs", Uint32Arr2, false}}, nil), + "slice256": NewMethod("slice256", "slice256", Function, "", false, false, []Argument{{"inputs", Uint256Arr2, false}}, nil), + "sliceAddress": NewMethod("sliceAddress", "sliceAddress", Function, "", false, false, []Argument{{"inputs", AddressArr, false}}, nil), + "sliceMultiAddress": NewMethod("sliceMultiAddress", "sliceMultiAddress", Function, "", false, false, []Argument{{"a", AddressArr, false}, {"b", AddressArr, false}}, nil), + "nestedArray": NewMethod("nestedArray", "nestedArray", Function, "", false, false, []Argument{{"a", Uint256ArrNested, false}, {"b", AddressArr, false}}, nil), + "nestedArray2": NewMethod("nestedArray2", "nestedArray2", Function, "", false, false, []Argument{{"a", Uint8ArrNested, false}}, nil), + "nestedSlice": NewMethod("nestedSlice", "nestedSlice", Function, "", false, false, []Argument{{"a", Uint8SliceNested, false}}, nil), + "receive": NewMethod("receive", "receive", Function, "payable", false, true, []Argument{{"memo", Bytes, false}}, []Argument{}), + "fixedArrStr": NewMethod("fixedArrStr", "fixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}}, nil), + "fixedArrBytes": NewMethod("fixedArrBytes", "fixedArrBytes", Function, "view", false, false, []Argument{{"bytes", Bytes, false}, {"fixedArr", Uint256Arr2, false}}, nil), + "mixedArrStr": NewMethod("mixedArrStr", "mixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}}, nil), + "doubleFixedArrStr": NewMethod("doubleFixedArrStr", "doubleFixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"fixedArr2", Uint256Arr3, false}}, nil), + "multipleMixedArrStr": NewMethod("multipleMixedArrStr", "multipleMixedArrStr", Function, "view", false, false, []Argument{{"str", String, false}, {"fixedArr1", Uint256Arr2, false}, {"dynArr", Uint256Arr, false}, {"fixedArr2", Uint256Arr3, false}}, nil), + "overloadedNames": NewMethod("overloadedNames", "overloadedNames", Function, "view", false, false, []Argument{{"f", TupleF, false}}, nil), +} + func TestReader(t *testing.T) { - Uint256, _ := NewType("uint256", "", nil) - exp := ABI{ - Methods: map[string]Method{ - "balance": { - "balance", "balance", "view", false, false, false, false, nil, nil, - }, - "send": { - "send", "send", "", false, false, false, false, []Argument{ - {"amount", Uint256, false}, - }, nil, - }, - }, + abi := ABI{ + Methods: methods, } - abi, err := JSON(strings.NewReader(jsondata)) + exp, err := JSON(strings.NewReader(jsondata)) if err != nil { - t.Error(err) + t.Fatal(err) } - // deep equal fails for some reason for name, expM := range exp.Methods { gotM, exist := abi.Methods[name] if !exist { @@ -98,8 +150,55 @@ func TestReader(t *testing.T) { } } +func TestInvalidABI(t *testing.T) { + json := `[{ "type" : "function", "name" : "", "constant" : fals }]` + _, err := JSON(strings.NewReader(json)) + if err == nil { + t.Fatal("invalid json should produce error") + } + json2 := `[{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "typ" : "uint256" } ] }]` + _, err = JSON(strings.NewReader(json2)) + if err == nil { + t.Fatal("invalid json should produce error") + } +} + +// TestConstructor tests a constructor function. +// The test is based on the following contract: +// contract TestConstructor { +// constructor(uint256 a, uint256 b) public{} +// } +func TestConstructor(t *testing.T) { + json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]` + method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil) + // Test from JSON + abi, err := JSON(strings.NewReader(json)) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(abi.Constructor, method) { + t.Error("Missing expected constructor") + } + // Test pack/unpack + packed, err := abi.Pack("", big.NewInt(1), big.NewInt(2)) + if err != nil { + t.Error(err) + } + unpacked, err := abi.Constructor.Inputs.Unpack(packed) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(unpacked[0], big.NewInt(1)) { + t.Error("Unable to pack/unpack from constructor") + } + if !reflect.DeepEqual(unpacked[1], big.NewInt(2)) { + t.Error("Unable to pack/unpack from constructor") + } +} + func TestTestNumbers(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) + abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) } @@ -135,60 +234,22 @@ func TestTestNumbers(t *testing.T) { } } -func TestTestString(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) - if err != nil { - t.Fatal(err) - } - - if _, err := abi.Pack("string", "hello world"); err != nil { - t.Error(err) - } -} - -func TestTestBool(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) - if err != nil { - t.Fatal(err) - } - - if _, err := abi.Pack("bool", true); err != nil { - t.Error(err) - } -} - -func TestTestSlice(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) - if err != nil { - t.Fatal(err) - } - slice := make([]uint64, 2) - if _, err := abi.Pack("uint64[2]", slice); err != nil { - t.Error(err) - } - if _, err := abi.Pack("uint64[]", slice); err != nil { - t.Error(err) - } -} - func TestMethodSignature(t *testing.T) { - String, _ := NewType("string", "", nil) - m := Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} + m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil) exp := "foo(string,string)" - if m.Sig() != exp { - t.Error("signature mismatch", exp, "!=", m.Sig()) + if m.Sig != exp { + t.Error("signature mismatch", exp, "!=", m.Sig) } idexp := crypto.Keccak256([]byte(exp))[:4] - if !bytes.Equal(m.ID(), idexp) { - t.Errorf("expected ids to match %x != %x", m.ID(), idexp) + if !bytes.Equal(m.ID, idexp) { + t.Errorf("expected ids to match %x != %x", m.ID, idexp) } - uintt, _ := NewType("uint256", "", nil) - m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", uintt, false}}, nil} + m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", Uint256, false}}, nil) exp = "foo(uint256)" - if m.Sig() != exp { - t.Error("signature mismatch", exp, "!=", m.Sig()) + if m.Sig != exp { + t.Error("signature mismatch", exp, "!=", m.Sig) } // Method with tuple arguments @@ -204,10 +265,10 @@ func TestMethodSignature(t *testing.T) { {Name: "y", Type: "int256"}, }}, }) - m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil} + m = NewMethod("foo", "foo", Function, "", false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil) exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" - if m.Sig() != exp { - t.Error("signature mismatch", exp, "!=", m.Sig()) + if m.Sig != exp { + t.Error("signature mismatch", exp, "!=", m.Sig) } } @@ -219,12 +280,12 @@ func TestOverloadedMethodSignature(t *testing.T) { } check := func(name string, expect string, method bool) { if method { - if abi.Methods[name].Sig() != expect { - t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig()) + if abi.Methods[name].Sig != expect { + t.Fatalf("The signature of overloaded method mismatch, want %s, have %s", expect, abi.Methods[name].Sig) } } else { - if abi.Events[name].Sig() != expect { - t.Fatalf("The signature of overloaded event mismatch, want %s, have %s", expect, abi.Events[name].Sig()) + if abi.Events[name].Sig != expect { + t.Fatalf("The signature of overloaded event mismatch, want %s, have %s", expect, abi.Events[name].Sig) } } } @@ -235,7 +296,7 @@ func TestOverloadedMethodSignature(t *testing.T) { } func TestMultiPack(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) + abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) } @@ -400,15 +461,7 @@ func TestInputVariableInputLength(t *testing.T) { } func TestInputFixedArrayAndVariableInputLength(t *testing.T) { - const definition = `[ - { "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, - { "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] }, - { "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] }, - { "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] }, - { "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] } - ]` - - abi, err := JSON(strings.NewReader(definition)) + abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Error(err) } @@ -555,7 +608,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { strvalue = common.RightPadBytes([]byte(strin), 32) fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32) fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32) - dynarroffset = U256(big.NewInt(int64(256 + ((len(strin)/32)+1)*32))) + dynarroffset = math.U256Bytes(big.NewInt(int64(256 + ((len(strin)/32)+1)*32))) dynarrlength = make([]byte, 32) dynarrlength[31] = byte(len(dynarrin)) dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32) @@ -602,8 +655,6 @@ func TestBareEvents(t *testing.T) { { "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] } ]` - arg0, _ := NewType("uint256", "", nil) - arg1, _ := NewType("address", "", nil) tuple, _ := NewType("tuple", "", []ArgumentMarshaling{{Name: "a", Type: "uint256"}}) expectedEvents := map[string]struct { @@ -613,12 +664,12 @@ func TestBareEvents(t *testing.T) { "balance": {false, nil}, "anon": {true, nil}, "args": {false, []Argument{ - {Name: "arg0", Type: arg0, Indexed: false}, - {Name: "arg1", Type: arg1, Indexed: true}, + {Name: "arg0", Type: Uint256, Indexed: false}, + {Name: "arg1", Type: Address, Indexed: true}, }}, "tuple": {false, []Argument{ {Name: "t", Type: tuple, Indexed: false}, - {Name: "arg1", Type: arg1, Indexed: true}, + {Name: "arg1", Type: Address, Indexed: true}, }}, } @@ -692,7 +743,7 @@ func TestUnpackEvent(t *testing.T) { } var ev ReceivedEvent - err = abi.Unpack(&ev, "received", data) + err = abi.UnpackIntoInterface(&ev, "received", data) if err != nil { t.Error(err) } @@ -701,7 +752,7 @@ func TestUnpackEvent(t *testing.T) { Sender common.Address } var receivedAddrEv ReceivedAddrEvent - err = abi.Unpack(&receivedAddrEv, "receivedAddr", data) + err = abi.UnpackIntoInterface(&receivedAddrEv, "receivedAddr", data) if err != nil { t.Error(err) } @@ -891,45 +942,25 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) { } func TestABI_MethodById(t *testing.T) { - const abiJSON = `[ - {"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"}, - {"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]}, - {"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]}, - {"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]}, - {"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]}, - {"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]}, - {"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]}, - {"type":"function","name":"balance","constant":true}, - {"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]}, - {"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]}, - {"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]}, - {"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]}, - {"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]}, - {"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]}, - {"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]}, - {"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]}, - {"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]}, - {"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]}, - {"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]}, - {"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]}, - {"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]} - ] -` - abi, err := JSON(strings.NewReader(abiJSON)) + abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) } for name, m := range abi.Methods { a := fmt.Sprintf("%v", m) - m2, err := abi.MethodById(m.ID()) + m2, err := abi.MethodById(m.ID) if err != nil { t.Fatalf("Failed to look up ABI method: %v", err) } b := fmt.Sprintf("%v", m2) if a != b { - t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID()) + t.Errorf("Method %v (id %x) not 'findable' by id in ABI", name, m.ID) } } + // test unsuccessful lookups + if _, err = abi.MethodById(crypto.Keccak256()); err == nil { + t.Error("Expected error: no method with this id") + } // Also test empty if _, err := abi.MethodById([]byte{0x00}); err == nil { t.Errorf("Expected error, too short to decode data") @@ -995,8 +1026,8 @@ func TestABI_EventById(t *testing.T) { t.Errorf("We should find a event for topic %s, test #%d", topicID.Hex(), testnum) } - if event.ID() != topicID { - t.Errorf("Event id %s does not match topic %s, test #%d", event.ID().Hex(), topicID.Hex(), testnum) + if event.ID != topicID { + t.Errorf("Event id %s does not match topic %s, test #%d", event.ID.Hex(), topicID.Hex(), testnum) } unknowntopicID := crypto.Keccak256Hash([]byte("unknownEvent")) @@ -1010,8 +1041,10 @@ func TestABI_EventById(t *testing.T) { } } -func TestDuplicateMethodNames(t *testing.T) { - abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` +// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name +// conflict and that the second transfer method will be renamed transfer1. +func TestDoubleDuplicateMethodNames(t *testing.T) { + abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) @@ -1030,24 +1063,86 @@ func TestDuplicateMethodNames(t *testing.T) { } } -// TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name -// conflict and that the second transfer method will be renamed transfer1. -func TestDoubleDuplicateMethodNames(t *testing.T) { - abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` +// TestDoubleDuplicateEventNames checks that if send0 already exists, there won't be a name +// conflict and that the second send event will be renamed send1. +// The test runs the abi of the following contract. +// contract DuplicateEvent { +// event send(uint256 a); +// event send0(); +// event send(); +// } +func TestDoubleDuplicateEventNames(t *testing.T) { + abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]` contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { t.Fatal(err) } - if _, ok := contractAbi.Methods["transfer"]; !ok { - t.Fatalf("Could not find original method") + if _, ok := contractAbi.Events["send"]; !ok { + t.Fatalf("Could not find original event") } - if _, ok := contractAbi.Methods["transfer0"]; !ok { - t.Fatalf("Could not find duplicate method") + if _, ok := contractAbi.Events["send0"]; !ok { + t.Fatalf("Could not find duplicate event") } - if _, ok := contractAbi.Methods["transfer1"]; !ok { - t.Fatalf("Could not find duplicate method") + if _, ok := contractAbi.Events["send1"]; !ok { + t.Fatalf("Could not find duplicate event") } - if _, ok := contractAbi.Methods["transfer2"]; ok { - t.Fatalf("Should not have found extra method") + if _, ok := contractAbi.Events["send2"]; ok { + t.Fatalf("Should not have found extra event") + } +} + +// TestUnnamedEventParam checks that an event with unnamed parameters is +// correctly handled. +// The test runs the abi of the following contract. +// contract TestEvent { +// event send(uint256, uint256); +// } +func TestUnnamedEventParam(t *testing.T) { + abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]` + contractAbi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + + event, ok := contractAbi.Events["send"] + if !ok { + t.Fatalf("Could not find event") + } + if event.Inputs[0].Name != "arg0" { + t.Fatalf("Could not find input") + } + if event.Inputs[1].Name != "arg1" { + t.Fatalf("Could not find input") + } +} + +func TestUnpackRevert(t *testing.T) { + t.Parallel() + + var cases = []struct { + input string + expect string + expectErr error + }{ + {"", "", errors.New("invalid data for unpacking")}, + {"08c379a1", "", errors.New("invalid data for unpacking")}, + {"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil}, + } + for index, c := range cases { + t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { + got, err := UnpackRevert(common.Hex2Bytes(c.input)) + if c.expectErr != nil { + if err == nil { + t.Fatalf("Expected non-nil error") + } + if err.Error() != c.expectErr.Error() { + t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err) + } + return + } + if c.expect != got { + t.Fatalf("Output mismatch, want %v, got %v", c.expect, got) + } + }) } } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 7f7f505865..e6d5245596 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -41,7 +41,7 @@ type ArgumentMarshaling struct { Indexed bool } -// UnmarshalJSON implements json.Unmarshaler interface +// UnmarshalJSON implements json.Unmarshaler interface. func (argument *Argument) UnmarshalJSON(data []byte) error { var arg ArgumentMarshaling err := json.Unmarshal(data, &arg) @@ -59,19 +59,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error { return nil } -// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events -// can ever have 'indexed' arguments, it should always be false on arguments for method input/output -func (arguments Arguments) LengthNonIndexed() int { - out := 0 - for _, arg := range arguments { - if !arg.Indexed { - out++ - } - } - return out -} - -// NonIndexed returns the arguments with indexed arguments filtered out +// NonIndexed returns the arguments with indexed arguments filtered out. func (arguments Arguments) NonIndexed() Arguments { var ret []Argument for _, arg := range arguments { @@ -82,217 +70,128 @@ func (arguments Arguments) NonIndexed() Arguments { return ret } -// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[] +// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]. func (arguments Arguments) isTuple() bool { return len(arguments) > 1 } -// Unpack performs the operation hexdata -> Go format -func (arguments Arguments) Unpack(v interface{}, data []byte) error { +// Unpack performs the operation hexdata -> Go format. +func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { if len(data) == 0 { if len(arguments) != 0 { - return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") - } else { - return nil // Nothing to unmarshal, return + return nil, fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") } + // Nothing to unmarshal, return default variables + nonIndexedArgs := arguments.NonIndexed() + defaultVars := make([]interface{}, len(nonIndexedArgs)) + for index, arg := range nonIndexedArgs { + defaultVars[index] = reflect.New(arg.Type.GetType()) + } + return defaultVars, nil } - // make sure the passed value is arguments pointer - if reflect.Ptr != reflect.ValueOf(v).Kind() { - return fmt.Errorf("abi: Unpack(non-pointer %T)", v) - } - marshalledValues, err := arguments.UnpackValues(data) - if err != nil { - return err - } - if arguments.isTuple() { - return arguments.unpackTuple(v, marshalledValues) - } - return arguments.unpackAtomic(v, marshalledValues[0]) + return arguments.UnpackValues(data) } -// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value +// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value. func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + // Make sure map is not nil + if v == nil { + return fmt.Errorf("abi: cannot unpack into a nil map") + } if len(data) == 0 { if len(arguments) != 0 { return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected") - } else { - return nil // Nothing to unmarshal, return } + return nil // Nothing to unmarshal, return } marshalledValues, err := arguments.UnpackValues(data) if err != nil { return err } - return arguments.unpackIntoMap(v, marshalledValues) -} - -// unpack sets the unmarshalled value to go format. -// Note the dst here must be settable. -func unpack(t *Type, dst interface{}, src interface{}) error { - var ( - dstVal = reflect.ValueOf(dst).Elem() - srcVal = reflect.ValueOf(src) - ) - tuple, typ := false, t - for { - if typ.T == SliceTy || typ.T == ArrayTy { - typ = typ.Elem - continue - } - tuple = typ.T == TupleTy - break - } - if !tuple { - return set(dstVal, srcVal) - } - - // Dereferences interface or pointer wrapper - dstVal = indirectInterfaceOrPtr(dstVal) - - switch t.T { - case TupleTy: - if dstVal.Kind() != reflect.Struct { - return fmt.Errorf("abi: invalid dst value for unpack, want struct, got %s", dstVal.Kind()) - } - fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, dstVal) - if err != nil { - return err - } - for i, elem := range t.TupleElems { - fname := fieldmap[t.TupleRawNames[i]] - field := dstVal.FieldByName(fname) - if !field.IsValid() { - return fmt.Errorf("abi: field %s can't found in the given value", t.TupleRawNames[i]) - } - if err := unpack(elem, field.Addr().Interface(), srcVal.Field(i).Interface()); err != nil { - return err - } - } - return nil - case SliceTy: - if dstVal.Kind() != reflect.Slice { - return fmt.Errorf("abi: invalid dst value for unpack, want slice, got %s", dstVal.Kind()) - } - slice := reflect.MakeSlice(dstVal.Type(), srcVal.Len(), srcVal.Len()) - for i := 0; i < slice.Len(); i++ { - if err := unpack(t.Elem, slice.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil { - return err - } - } - dstVal.Set(slice) - case ArrayTy: - if dstVal.Kind() != reflect.Array { - return fmt.Errorf("abi: invalid dst value for unpack, want array, got %s", dstVal.Kind()) - } - array := reflect.New(dstVal.Type()).Elem() - for i := 0; i < array.Len(); i++ { - if err := unpack(t.Elem, array.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil { - return err - } - } - dstVal.Set(array) + for i, arg := range arguments.NonIndexed() { + v[arg.Name] = marshalledValues[i] } return nil } -// unpackIntoMap unpacks marshalledValues into the provided map[string]interface{} -func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error { - // Make sure map is not nil - if v == nil { - return fmt.Errorf("abi: cannot unpack into a nil map") +// Copy performs the operation go format -> provided struct. +func (arguments Arguments) Copy(v interface{}, values []interface{}) error { + // make sure the passed value is arguments pointer + if reflect.Ptr != reflect.ValueOf(v).Kind() { + return fmt.Errorf("abi: Unpack(non-pointer %T)", v) } - - for i, arg := range arguments.NonIndexed() { - v[arg.Name] = marshalledValues[i] + if len(values) == 0 { + if len(arguments) != 0 { + return fmt.Errorf("abi: attempting to copy no values while %d arguments are expected", len(arguments)) + } + return nil // Nothing to copy, return } - return nil + if arguments.isTuple() { + return arguments.copyTuple(v, values) + } + return arguments.copyAtomic(v, values[0]) } // unpackAtomic unpacks ( hexdata -> go ) a single value -func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { - if arguments.LengthNonIndexed() == 0 { - return nil - } - argument := arguments.NonIndexed()[0] - elem := reflect.ValueOf(v).Elem() +func (arguments Arguments) copyAtomic(v interface{}, marshalledValues interface{}) error { + dst := reflect.ValueOf(v).Elem() + src := reflect.ValueOf(marshalledValues) - if elem.Kind() == reflect.Struct && argument.Type.T != TupleTy { - fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem) - if err != nil { - return err - } - field := elem.FieldByName(fieldmap[argument.Name]) - if !field.IsValid() { - return fmt.Errorf("abi: field %s can't be found in the given value", argument.Name) - } - return unpack(&argument.Type, field.Addr().Interface(), marshalledValues) + if dst.Kind() == reflect.Struct && src.Kind() != reflect.Struct { + return set(dst.Field(0), src) } - return unpack(&argument.Type, elem.Addr().Interface(), marshalledValues) + return set(dst, src) } -// unpackTuple unpacks ( hexdata -> go ) a batch of values. -func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { - var ( - value = reflect.ValueOf(v).Elem() - typ = value.Type() - kind = value.Kind() - ) - if err := requireUnpackKind(value, typ, kind, arguments); err != nil { - return err - } +// copyTuple copies a batch of values from marshalledValues to v. +func (arguments Arguments) copyTuple(v interface{}, marshalledValues []interface{}) error { + value := reflect.ValueOf(v).Elem() + nonIndexedArgs := arguments.NonIndexed() - // If the interface is a struct, get of abi->struct_field mapping - var abi2struct map[string]string - if kind == reflect.Struct { - var ( - argNames []string - err error - ) - for _, arg := range arguments.NonIndexed() { - argNames = append(argNames, arg.Name) + switch value.Kind() { + case reflect.Struct: + argNames := make([]string, len(nonIndexedArgs)) + for i, arg := range nonIndexedArgs { + argNames[i] = arg.Name } - abi2struct, err = mapArgNamesToStructFields(argNames, value) + var err error + abi2struct, err := mapArgNamesToStructFields(argNames, value) if err != nil { return err } - } - for i, arg := range arguments.NonIndexed() { - switch kind { - case reflect.Struct: + for i, arg := range nonIndexedArgs { field := value.FieldByName(abi2struct[arg.Name]) if !field.IsValid() { return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name) } - if err := unpack(&arg.Type, field.Addr().Interface(), marshalledValues[i]); err != nil { + if err := set(field, reflect.ValueOf(marshalledValues[i])); err != nil { return err } - case reflect.Slice, reflect.Array: - if value.Len() < i { - return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) - } - v := value.Index(i) - if err := requireAssignable(v, reflect.ValueOf(marshalledValues[i])); err != nil { - return err - } - if err := unpack(&arg.Type, v.Addr().Interface(), marshalledValues[i]); err != nil { + } + case reflect.Slice, reflect.Array: + if value.Len() < len(marshalledValues) { + return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len()) + } + for i := range nonIndexedArgs { + if err := set(value.Index(i), reflect.ValueOf(marshalledValues[i])); err != nil { return err } - default: - return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ) } + default: + return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", value.Type()) } return nil - } // UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification, // without supplying a struct to unpack into. Instead, this method returns a list containing the // values. An atomic argument will be a list with one element. func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { - retval := make([]interface{}, 0, arguments.LengthNonIndexed()) + nonIndexedArgs := arguments.NonIndexed() + retval := make([]interface{}, 0, len(nonIndexedArgs)) virtualArgs := 0 - for index, arg := range arguments.NonIndexed() { - marshalledValue, err := ToGoType((index+virtualArgs)*32, arg.Type, data) + for index, arg := range nonIndexedArgs { + marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -318,18 +217,18 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { return retval, nil } -// PackValues performs the operation Go format -> Hexdata -// It is the semantic opposite of UnpackValues +// PackValues performs the operation Go format -> Hexdata. +// It is the semantic opposite of UnpackValues. func (arguments Arguments) PackValues(args []interface{}) ([]byte, error) { return arguments.Pack(args...) } -// Pack performs the operation Go format -> Hexdata +// Pack performs the operation Go format -> Hexdata. func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) { // Make sure arguments match up and pack them abiArgs := arguments if len(args) != len(abiArgs) { - return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs)) + return nil, fmt.Errorf("argument count mismatch: got %d for %d", len(args), len(abiArgs)) } // variable input is the output appended at the end of packed // output. This is used for strings and bytes types input. diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index e51f0bd8ea..b8065e8488 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -21,6 +21,7 @@ import ( "errors" "io" "io/ioutil" + "math/big" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" @@ -28,11 +29,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" ) +// ErrNoChainID is returned whenever the user failed to specify a chain id. +var ErrNoChainID = errors.New("no chain id specified") + +// ErrNotAuthorized is returned when an account is not properly unlocked. +var ErrNotAuthorized = errors.New("not authorized to sign this account") + // NewTransactor is a utility method to easily create a transaction signer from // an encrypted json key stream and the associated passphrase. +// +// Deprecated: Use NewTransactorWithChainID instead. func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { + log.Warn("WARNING: NewTransactor has been deprecated in favour of NewTransactorWithChainID") json, err := ioutil.ReadAll(keyin) if err != nil { return nil, err @@ -45,13 +56,17 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { } // NewKeyStoreTransactor is a utility method to easily create a transaction signer from -// an decrypted key from a keystore +// an decrypted key from a keystore. +// +// Deprecated: Use NewKeyStoreTransactorWithChainID instead. func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { + log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID") + signer := types.HomesteadSigner{} return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) if err != nil { @@ -64,13 +79,17 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account // NewKeyedTransactor is a utility method to easily create a transaction signer // from a single private key. +// +// Deprecated: Use NewKeyedTransactorWithChainID instead. func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID") keyAddr := crypto.PubkeyToAddress(key.PublicKey) + signer := types.HomesteadSigner{} return &TransactOpts{ From: keyAddr, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { if address != keyAddr { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) if err != nil { @@ -81,14 +100,73 @@ func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { } } +// NewTransactorWithChainID is a utility method to easily create a transaction signer from +// an encrypted json key stream and the associated passphrase. +func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.Int) (*TransactOpts, error) { + json, err := ioutil.ReadAll(keyin) + if err != nil { + return nil, err + } + key, err := keystore.DecryptKey(json, passphrase) + if err != nil { + return nil, err + } + return NewKeyedTransactorWithChainID(key.PrivateKey, chainID) +} + +// NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from +// an decrypted key from a keystore. +func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) { + if chainID == nil { + return nil, ErrNoChainID + } + signer := types.LatestSignerForChainID(chainID) + return &TransactOpts{ + From: account.Address, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != account.Address { + return nil, ErrNotAuthorized + } + signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes()) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + +// NewKeyedTransactorWithChainID is a utility method to easily create a transaction signer +// from a single private key. +func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) { + keyAddr := crypto.PubkeyToAddress(key.PublicKey) + if chainID == nil { + return nil, ErrNoChainID + } + signer := types.LatestSignerForChainID(chainID) + return &TransactOpts{ + From: keyAddr, + Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if address != keyAddr { + return nil, ErrNotAuthorized + } + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, signature) + }, + }, nil +} + // NewClefTransactor is a utility method to easily create a transaction signer // with a clef backend. func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts { return &TransactOpts{ From: account.Address, - Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) { + Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { if address != account.Address { - return nil, errors.New("not authorized to sign this account") + return nil, ErrNotAuthorized } return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id }, diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index ca60cc1b43..eed6a44bbc 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -41,7 +41,7 @@ var ( ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") ) -// ContractCaller defines the methods needed to allow operating with contract on a read +// ContractCaller defines the methods needed to allow operating with a contract on a read // only basis. type ContractCaller interface { // CodeAt returns the code of the given account. This is needed to differentiate @@ -62,8 +62,8 @@ type PendingContractCaller interface { PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) } -// ContractTransactor defines the methods needed to allow operating with contract -// on a write only basis. Beside the transacting method, the remainder are helpers +// ContractTransactor defines the methods needed to allow operating with a contract +// on a write only basis. Besides the transacting method, the remainder are helpers // used when the user does not provide some needed values, but rather leaves it up // to the transactor to decide. type ContractTransactor interface { diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 6734684675..a7db3195fc 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -25,8 +25,10 @@ import ( "time" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" @@ -38,22 +40,22 @@ import ( "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) -// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend. +// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend. var _ bind.ContractBackend = (*SimulatedBackend)(nil) var ( errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") errBlockDoesNotExist = errors.New("block does not exist in blockchain") errTransactionDoesNotExist = errors.New("transaction does not exist") - errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction") ) // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in -// the background. Its main purpose is to allow easily testing contract bindings. +// the background. Its main purpose is to allow for easy testing of contract bindings. // Simulated backend implements the following interfaces: // ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor, // DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender @@ -63,7 +65,7 @@ type SimulatedBackend struct { mu sync.Mutex pendingBlock *types.Block // Currently pending block that will be imported on request - pendingState *state.StateDB // Currently pending state that will be the active on on request + pendingState *state.StateDB // Currently pending state that will be the active on request events *filters.EventSystem // Event system for filtering log events live @@ -72,10 +74,11 @@ type SimulatedBackend struct { // NewSimulatedBackendWithDatabase creates a new binding backend based on the given database // and uses a simulated blockchain for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc} genesis.MustCommit(database) - blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil) + blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil, nil) backend := &SimulatedBackend{ database: database, @@ -89,6 +92,7 @@ func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.Genesis // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. +// A simulated backend always uses chainID 1337. func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit) } @@ -121,10 +125,9 @@ func (b *SimulatedBackend) Rollback() { func (b *SimulatedBackend) rollback() { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) - statedb, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil) } // stateByBlockNumber retrieves a state by a given blocknumber. @@ -132,11 +135,11 @@ func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber * if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) == 0 { return b.blockchain.State() } - block, err := b.BlockByNumber(ctx, blockNumber) + block, err := b.blockByNumberNoLock(ctx, blockNumber) if err != nil { return nil, err } - return b.blockchain.StateAt(block.Hash()) + return b.blockchain.StateAt(block.Root()) } // CodeAt returns the code associated with a certain account in the blockchain. @@ -144,12 +147,12 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return nil, err } - return statedb.GetCode(contract), nil + return stateDB.GetCode(contract), nil } // BalanceAt returns the wei balance of a certain account in the blockchain. @@ -157,12 +160,12 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return nil, err } - return statedb.GetBalance(contract), nil + return stateDB.GetBalance(contract), nil } // NonceAt returns the nonce of a certain account in the blockchain. @@ -170,12 +173,12 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return 0, err } - return statedb.GetNonce(contract), nil + return stateDB.GetNonce(contract), nil } // StorageAt returns the value of key in the storage of an account in the blockchain. @@ -183,12 +186,12 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres b.mu.Lock() defer b.mu.Unlock() - statedb, err := b.stateByBlockNumber(ctx, blockNumber) + stateDB, err := b.stateByBlockNumber(ctx, blockNumber) if err != nil { return nil, err } - val := statedb.GetState(contract, key) + val := stateDB.GetState(contract, key) return val[:], nil } @@ -220,7 +223,7 @@ func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common. return nil, false, ethereum.NotFound } -// BlockByHash retrieves a block based on the block hash +// BlockByHash retrieves a block based on the block hash. func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { b.mu.Lock() defer b.mu.Unlock() @@ -243,6 +246,12 @@ func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) ( b.mu.Lock() defer b.mu.Unlock() + return b.blockByNumberNoLock(ctx, number) +} + +// blockByNumberNoLock retrieves a block from the database by number, caching it +// (associated with its hash) if found without Lock. +func (b *SimulatedBackend) blockByNumberNoLock(ctx context.Context, number *big.Int) (*types.Block, error) { if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { return b.blockchain.CurrentBlock(), nil } @@ -285,7 +294,7 @@ func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) ( return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil } -// TransactionCount returns the number of transactions in a given block +// TransactionCount returns the number of transactions in a given block. func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { b.mu.Lock() defer b.mu.Unlock() @@ -302,7 +311,7 @@ func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash commo return uint(block.Transactions().Len()), nil } -// TransactionInBlock returns the transaction for a specific block at a specific index +// TransactionInBlock returns the transaction for a specific block at a specific index. func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { b.mu.Lock() defer b.mu.Unlock() @@ -337,6 +346,36 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad return b.pendingState.GetCode(contract), nil } +func newRevertError(result *core.ExecutionResult) *revertError { + reason, errUnpack := abi.UnpackRevert(result.Revert()) + err := errors.New("execution reverted") + if errUnpack == nil { + err = fmt.Errorf("execution reverted: %v", reason) + } + return &revertError{ + error: err, + reason: hexutil.Encode(result.Revert()), + } +} + +// revertError is an API error that encompasses an EVM revert with JSON error +// code and a binary data blob. +type revertError struct { + error + reason string // revert reason hex encoded +} + +// ErrorCode returns the JSON error code for a revert. +// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal +func (e *revertError) ErrorCode() int { + return 3 +} + +// ErrorData returns the hex encoded revert reason. +func (e *revertError) ErrorData() interface{} { + return e.reason +} + // CallContract executes a contract call. func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { b.mu.Lock() @@ -345,12 +384,19 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 { return nil, errBlockNumberUnsupported } - state, err := b.blockchain.State() + stateDB, err := b.blockchain.State() if err != nil { return nil, err } - rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) - return rval, err + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err } // PendingCallContract executes a contract call on the pending state. @@ -359,8 +405,15 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu defer b.mu.Unlock() defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) - return rval, err + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + if err != nil { + return nil, err + } + // If the result contains a revert reason, try to unpack and return it. + if len(res.Revert()) > 0 { + return nil, newRevertError(res) + } + return res.Return(), res.Err } // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving @@ -395,25 +448,57 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } else { hi = b.pendingBlock.GasLimit() } + // Recap the highest gas allowance with account's balance. + if call.GasPrice != nil && call.GasPrice.BitLen() != 0 { + balance := b.pendingState.GetBalance(call.From) // from can't be nil + available := new(big.Int).Set(balance) + if call.Value != nil { + if call.Value.Cmp(available) >= 0 { + return 0, errors.New("insufficient funds for transfer") + } + available.Sub(available, call.Value) + } + allowance := new(big.Int).Div(available, call.GasPrice) + if allowance.IsUint64() && hi > allowance.Uint64() { + transfer := call.Value + if transfer == nil { + transfer = new(big.Int) + } + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", transfer, "gasprice", call.GasPrice, "fundable", allowance) + hi = allowance.Uint64() + } + } cap = hi // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) bool { + executable := func(gas uint64) (bool, *core.ExecutionResult, error) { call.Gas = gas snapshot := b.pendingState.Snapshot() - _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) b.pendingState.RevertToSnapshot(snapshot) - if err != nil || failed { - return false + if err != nil { + if errors.Is(err, core.ErrIntrinsicGas) { + return true, nil, nil // Special case, raise gas limit + } + return true, nil, err // Bail out } - return true + return res.Failed(), res, nil } // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { mid := (hi + lo) / 2 - if !executable(mid) { + failed, _, err := executable(mid) + + // If the error is not nil(consensus error), it means the provided message + // call or transaction will never be accepted no matter how much gas it is + // assigned. Return the error directly, don't struggle any more + if err != nil { + return 0, err + } + if failed { lo = mid } else { hi = mid @@ -421,8 +506,19 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - if !executable(hi) { - return 0, errGasEstimationFailed + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfGas { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) + } + return 0, result.Err + } + // Otherwise, the specified gas cap is too low + return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) } } return hi, nil @@ -430,7 +526,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs // callContract implements common code between normal and pending contract calls. // state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { +func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) { // Ensure message is initialized properly. if call.GasPrice == nil { call.GasPrice = big.NewInt(1) @@ -442,18 +538,19 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM call.Value = new(big.Int) } // Set infinite balance to the fake caller account. - from := statedb.GetOrNewStateObject(call.From) + from := stateDB.GetOrNewStateObject(call.From) from.SetBalance(math.MaxBig256) // Execute the call. - msg := callmsg{call} + msg := callMsg{call} - evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) + txContext := core.NewEVMTxContext(msg) + evmContext := core.NewEVMBlockContext(block.Header(), b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{}) - gaspool := new(core.GasPool).AddGas(math.MaxUint64) + vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{}) + gasPool := new(core.GasPool).AddGas(math.MaxUint64) - return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() + return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() } // SendTransaction updates the pending block to include the given transaction. @@ -462,7 +559,10 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa b.mu.Lock() defer b.mu.Unlock() - sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx) + // Check transaction validity. + block := b.blockchain.CurrentBlock() + signer := types.MakeSigner(b.blockchain.Config(), block.Number()) + sender, err := types.Sender(signer, tx) if err != nil { panic(fmt.Errorf("invalid transaction: %v", err)) } @@ -471,16 +571,17 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) } - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + // Include tx in chain. + blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } block.AddTxWithChain(b.blockchain, tx) }) - statedb, _ := b.blockchain.State() + stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) return nil } @@ -494,7 +595,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter // Block filter requested, construct a single-shot filter filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics) } else { - // Initialize unset filter boundaried to run from genesis to chain head + // Initialize unset filter boundaries to run from genesis to chain head from := int64(0) if query.FromBlock != nil { from = query.FromBlock.Int64() @@ -512,8 +613,8 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter return nil, err } res := make([]types.Log, len(logs)) - for i, log := range logs { - res[i] = *log + for i, nLog := range logs { + res[i] = *nLog } return res, nil } @@ -534,9 +635,9 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere for { select { case logs := <-sink: - for _, log := range logs { + for _, nlog := range logs { select { - case ch <- *log: + case ch <- *nlog: case err := <-sub.Err(): return err case <-quit: @@ -552,7 +653,7 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere }), nil } -// SubscribeNewHead returns an event subscription for a new header +// SubscribeNewHead returns an event subscription for a new header. func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { // subscribe to a new head sink := make(chan *types.Header) @@ -580,20 +681,22 @@ func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *type } // AdjustTime adds a time shift to the simulated clock. +// It can only be called on empty blocks. func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { b.mu.Lock() defer b.mu.Unlock() + if len(b.pendingBlock.Transactions()) != 0 { + return errors.New("Could not adjust time on non-empty block") + } + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { - for _, tx := range b.pendingBlock.Transactions() { - block.AddTx(tx) - } block.OffsetTime(int64(adjustment.Seconds())) }) - statedb, _ := b.blockchain.State() + stateDB, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) + b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) return nil } @@ -603,19 +706,20 @@ func (b *SimulatedBackend) Blockchain() *core.BlockChain { return b.blockchain } -// callmsg implements core.Message to allow passing it as a transaction simulator. -type callmsg struct { +// callMsg implements core.Message to allow passing it as a transaction simulator. +type callMsg struct { ethereum.CallMsg } -func (m callmsg) From() common.Address { return m.CallMsg.From } -func (m callmsg) Nonce() uint64 { return 0 } -func (m callmsg) CheckNonce() bool { return false } -func (m callmsg) To() *common.Address { return m.CallMsg.To } -func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } -func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } -func (m callmsg) Value() *big.Int { return m.CallMsg.Value } -func (m callmsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) From() common.Address { return m.CallMsg.From } +func (m callMsg) Nonce() uint64 { return 0 } +func (m callMsg) CheckNonce() bool { return false } +func (m callMsg) To() *common.Address { return m.CallMsg.To } +func (m callMsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } +func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } +func (m callMsg) Value() *big.Int { return m.CallMsg.Value } +func (m callMsg) Data() []byte { return m.CallMsg.Data } +func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 455d89c1e3..64ddf8bb2c 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -19,7 +19,9 @@ package backends import ( "bytes" "context" + "errors" "math/big" + "reflect" "strings" "testing" "time" @@ -37,7 +39,7 @@ import ( func TestSimulatedBackend(t *testing.T) { var gasLimit uint64 = 8000029 key, _ := crypto.GenerateKey() // nolint: gosec - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) genAlloc := make(core.GenesisAlloc) genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} @@ -105,14 +107,18 @@ const deployedCode = `60806040526004361061003b576000357c010000000000000000000000 // expected return value contains "hello world" var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -func TestNewSimulatedBackend(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - expectedBal := big.NewInt(10000000000) - sim := NewSimulatedBackend( +func simTestBackend(testAddr common.Address) *SimulatedBackend { + return NewSimulatedBackend( core.GenesisAlloc{ - testAddr: {Balance: expectedBal}, + testAddr: {Balance: big.NewInt(10000000000)}, }, 10000000, ) +} + +func TestNewSimulatedBackend(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + expectedBal := big.NewInt(10000000000) + sim := simTestBackend(testAddr) defer sim.Close() if sim.config != params.AllEthashProtocolChanges { @@ -123,8 +129,8 @@ func TestNewSimulatedBackend(t *testing.T) { t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config) } - statedb, _ := sim.blockchain.State() - bal := statedb.GetBalance(testAddr) + stateDB, _ := sim.blockchain.State() + bal := stateDB.GetBalance(testAddr) if bal.Cmp(expectedBal) != 0 { t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) } @@ -137,8 +143,7 @@ func TestSimulatedBackend_AdjustTime(t *testing.T) { defer sim.Close() prevTime := sim.pendingBlock.Time() - err := sim.AdjustTime(time.Second) - if err != nil { + if err := sim.AdjustTime(time.Second); err != nil { t.Error(err) } newTime := sim.pendingBlock.Time() @@ -148,14 +153,48 @@ func TestSimulatedBackend_AdjustTime(t *testing.T) { } } +func TestNewSimulatedBackend_AdjustTimeFail(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + // Create tx and send + tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, big.NewInt(1), nil) + signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) + if err != nil { + t.Errorf("could not sign tx: %v", err) + } + sim.SendTransaction(context.Background(), signedTx) + // AdjustTime should fail on non-empty block + if err := sim.AdjustTime(time.Second); err == nil { + t.Error("Expected adjust time to error on non-empty block") + } + sim.Commit() + + prevTime := sim.pendingBlock.Time() + if err := sim.AdjustTime(time.Minute); err != nil { + t.Error(err) + } + newTime := sim.pendingBlock.Time() + if newTime-prevTime != uint64(time.Minute.Seconds()) { + t.Errorf("adjusted time not equal to a minute. prev: %v, new: %v", prevTime, newTime) + } + // Put a transaction after adjusting time + tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, big.NewInt(1), nil) + signedTx2, err := types.SignTx(tx2, types.HomesteadSigner{}, testKey) + if err != nil { + t.Errorf("could not sign tx: %v", err) + } + sim.SendTransaction(context.Background(), signedTx2) + sim.Commit() + newTime = sim.pendingBlock.Time() + if newTime-prevTime >= uint64(time.Minute.Seconds()) { + t.Errorf("time adjusted, but shouldn't be: prev: %v, new: %v", prevTime, newTime) + } +} + func TestSimulatedBackend_BalanceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) expectedBal := big.NewInt(10000000000) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: expectedBal}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -228,11 +267,7 @@ func TestSimulatedBackend_BlockByNumber(t *testing.T) { func TestSimulatedBackend_NonceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -267,16 +302,22 @@ func TestSimulatedBackend_NonceAt(t *testing.T) { if newNonce != nonce+uint64(1) { t.Errorf("received incorrect nonce. expected 1, got %v", nonce) } + // create some more blocks + sim.Commit() + // Check that we can get data for an older block/state + newNonce, err = sim.NonceAt(bgCtx, testAddr, big.NewInt(1)) + if err != nil { + t.Fatalf("could not get nonce for test addr: %v", err) + } + if newNonce != nonce+uint64(1) { + t.Fatalf("received incorrect nonce. expected 1, got %v", nonce) + } } func TestSimulatedBackend_SendTransaction(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -356,36 +397,194 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) { } func TestSimulatedBackend_EstimateGas(t *testing.T) { - sim := NewSimulatedBackend( - core.GenesisAlloc{}, 10000000, - ) + /* + pragma solidity ^0.6.4; + contract GasEstimation { + function PureRevert() public { revert(); } + function Revert() public { revert("revert reason");} + function OOG() public { for (uint i = 0; ; i++) {}} + function Assert() public { assert(false);} + function Valid() public {} + }*/ + const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033" + + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000) defer sim.Close() - bgCtx := context.Background() - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - gas, err := sim.EstimateGas(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &testAddr, - Value: big.NewInt(1000), - Data: []byte{}, - }) - if err != nil { - t.Errorf("could not estimate gas: %v", err) + parsed, _ := abi.JSON(strings.NewReader(contractAbi)) + contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) + sim.Commit() + + var cases = []struct { + name string + message ethereum.CallMsg + expect uint64 + expectError error + expectData interface{} + }{ + {"plain transfer(valid)", ethereum.CallMsg{ + From: addr, + To: &addr, + Gas: 0, + GasPrice: big.NewInt(0), + Value: big.NewInt(1), + Data: nil, + }, params.TxGas, nil, nil}, + + {"plain transfer(invalid)", ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Gas: 0, + GasPrice: big.NewInt(0), + Value: big.NewInt(1), + Data: nil, + }, 0, errors.New("execution reverted"), nil}, + + {"Revert", ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Gas: 0, + GasPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("d8b98391"), + }, 0, errors.New("execution reverted: revert reason"), "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"}, + + {"PureRevert", ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Gas: 0, + GasPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("aa8b1d30"), + }, 0, errors.New("execution reverted"), nil}, + + {"OOG", ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Gas: 100000, + GasPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("50f6fe34"), + }, 0, errors.New("gas required exceeds allowance (100000)"), nil}, + + {"Assert", ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Gas: 100000, + GasPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("b9b046f9"), + }, 0, errors.New("invalid opcode: opcode 0xfe not defined"), nil}, + + {"Valid", ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Gas: 100000, + GasPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("e09fface"), + }, 21275, nil, nil}, + } + for _, c := range cases { + got, err := sim.EstimateGas(context.Background(), c.message) + if c.expectError != nil { + if err == nil { + t.Fatalf("Expect error, got nil") + } + if c.expectError.Error() != err.Error() { + t.Fatalf("Expect error, want %v, got %v", c.expectError, err) + } + if c.expectData != nil { + if err, ok := err.(*revertError); !ok { + t.Fatalf("Expect revert error, got %T", err) + } else if !reflect.DeepEqual(err.ErrorData(), c.expectData) { + t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData()) + } + } + continue + } + if got != c.expect { + t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got) + } } +} + +func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) - if gas != params.TxGas { - t.Errorf("expected 21000 gas cost for a transaction got %v", gas) + sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000) + defer sim.Close() + + recipient := common.HexToAddress("deadbeef") + var cases = []struct { + name string + message ethereum.CallMsg + expect uint64 + expectError error + }{ + {"EstimateWithoutPrice", ethereum.CallMsg{ + From: addr, + To: &recipient, + Gas: 0, + GasPrice: big.NewInt(0), + Value: big.NewInt(1000), + Data: nil, + }, 21000, nil}, + + {"EstimateWithPrice", ethereum.CallMsg{ + From: addr, + To: &recipient, + Gas: 0, + GasPrice: big.NewInt(1000), + Value: big.NewInt(1000), + Data: nil, + }, 21000, nil}, + + {"EstimateWithVeryHighPrice", ethereum.CallMsg{ + From: addr, + To: &recipient, + Gas: 0, + GasPrice: big.NewInt(1e14), // gascost = 2.1ether + Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether + Data: nil, + }, 21000, nil}, + + {"EstimateWithSuperhighPrice", ethereum.CallMsg{ + From: addr, + To: &recipient, + Gas: 0, + GasPrice: big.NewInt(2e14), // gascost = 4.2ether + Value: big.NewInt(1000), + Data: nil, + }, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14) + } + for _, c := range cases { + got, err := sim.EstimateGas(context.Background(), c.message) + if c.expectError != nil { + if err == nil { + t.Fatalf("Expect error, got nil") + } + if c.expectError.Error() != err.Error() { + t.Fatalf("Expect error, want %v, got %v", c.expectError, err) + } + continue + } + if got != c.expect { + t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got) + } } } func TestSimulatedBackend_HeaderByHash(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -406,11 +605,7 @@ func TestSimulatedBackend_HeaderByHash(t *testing.T) { func TestSimulatedBackend_HeaderByNumber(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -457,11 +652,7 @@ func TestSimulatedBackend_HeaderByNumber(t *testing.T) { func TestSimulatedBackend_TransactionCount(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() currentBlock, err := sim.BlockByNumber(bgCtx, nil) @@ -511,11 +702,7 @@ func TestSimulatedBackend_TransactionCount(t *testing.T) { func TestSimulatedBackend_TransactionInBlock(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -578,11 +765,7 @@ func TestSimulatedBackend_TransactionInBlock(t *testing.T) { func TestSimulatedBackend_PendingNonceAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -644,11 +827,7 @@ func TestSimulatedBackend_PendingNonceAt(t *testing.T) { func TestSimulatedBackend_TransactionReceipt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -694,12 +873,7 @@ func TestSimulatedBackend_SuggestGasPrice(t *testing.T) { func TestSimulatedBackend_PendingCodeAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, - 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() code, err := sim.CodeAt(bgCtx, testAddr, nil) @@ -714,7 +888,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -735,12 +909,7 @@ func TestSimulatedBackend_PendingCodeAt(t *testing.T) { func TestSimulatedBackend_CodeAt(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, - 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() code, err := sim.CodeAt(bgCtx, testAddr, nil) @@ -755,7 +924,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - auth := bind.NewKeyedTransactor(testKey) + auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) @@ -779,12 +948,7 @@ func TestSimulatedBackend_CodeAt(t *testing.T) { // receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000)}, - }, - 10000000, - ) + sim := simTestBackend(testAddr) defer sim.Close() bgCtx := context.Background() @@ -792,7 +956,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { if err != nil { t.Errorf("could not get code at test addr: %v", err) } - contractAuth := bind.NewKeyedTransactor(testKey) + contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(abiBin), sim) if err != nil { t.Errorf("could not deploy contract: %v", err) @@ -800,7 +964,7 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { input, err := parsed.Pack("receive", []byte("X")) if err != nil { - t.Errorf("could pack receive function on contract: %v", err) + t.Errorf("could not pack receive function on contract: %v", err) } // make sure you can call the contract in pending state @@ -840,3 +1004,113 @@ func TestSimulatedBackend_PendingAndCallContract(t *testing.T) { t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) } } + +// This test is based on the following contract: +/* +contract Reverter { + function revertString() public pure{ + require(false, "some error"); + } + function revertNoString() public pure { + require(false, ""); + } + function revertASM() public pure { + assembly { + revert(0x0, 0x0) + } + } + function noRevert() public pure { + assembly { + // Assembles something that looks like require(false, "some error") but is not reverted + mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020) + mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a) + mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000) + return(0x0, 0x64) + } + } +}*/ +func TestSimulatedBackend_CallContractRevert(t *testing.T) { + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + bgCtx := context.Background() + + reverterABI := `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]` + reverterBin := "608060405234801561001057600080fd5b506101d3806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033" + + parsed, err := abi.JSON(strings.NewReader(reverterABI)) + if err != nil { + t.Errorf("could not get code at test addr: %v", err) + } + contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) + addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim) + if err != nil { + t.Errorf("could not deploy contract: %v", err) + } + + inputs := make(map[string]interface{}, 3) + inputs["revertASM"] = nil + inputs["revertNoString"] = "" + inputs["revertString"] = "some error" + + call := make([]func([]byte) ([]byte, error), 2) + call[0] = func(input []byte) ([]byte, error) { + return sim.PendingCallContract(bgCtx, ethereum.CallMsg{ + From: testAddr, + To: &addr, + Data: input, + }) + } + call[1] = func(input []byte) ([]byte, error) { + return sim.CallContract(bgCtx, ethereum.CallMsg{ + From: testAddr, + To: &addr, + Data: input, + }, nil) + } + + // Run pending calls then commit + for _, cl := range call { + for key, val := range inputs { + input, err := parsed.Pack(key) + if err != nil { + t.Errorf("could not pack %v function on contract: %v", key, err) + } + + res, err := cl(input) + if err == nil { + t.Errorf("call to %v was not reverted", key) + } + if res != nil { + t.Errorf("result from %v was not nil: %v", key, res) + } + if val != nil { + rerr, ok := err.(*revertError) + if !ok { + t.Errorf("expect revert error") + } + if rerr.Error() != "execution reverted: "+val.(string) { + t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) + } + } else { + // revert(0x0,0x0) + if err.Error() != "execution reverted" { + t.Errorf("error was malformed: got %v want %v", err, "execution reverted") + } + } + } + input, err := parsed.Pack("noRevert") + if err != nil { + t.Errorf("could not pack noRevert function on contract: %v", err) + } + res, err := cl(input) + if err != nil { + t.Error("call to noRevert was reverted") + } + if res == nil { + t.Errorf("result from noRevert was nil") + } + sim.Commit() + } +} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index e69e3afa54..55aca31a1a 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -32,7 +32,7 @@ import ( // SignerFn is a signer function callback when a contract requires a method to // sign the transaction before submission. -type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Transaction, error) +type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { @@ -49,11 +49,13 @@ type TransactOpts struct { Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) Signer SignerFn // Method to use for signing the transaction (mandatory) - Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) + Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds) GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) + + NoSend bool // Do all transact steps but do not send the transaction } // FilterOpts is the collection of options to fine tune filtering for events @@ -117,11 +119,14 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. -func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, params ...interface{}) error { +func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method string, params ...interface{}) error { // Don't crash on a lazy user if opts == nil { opts = new(CallOpts) } + if results == nil { + results = new([]interface{}) + } // Pack the input, call and unpack the results input, err := c.abi.Pack(method, params...) if err != nil { @@ -149,7 +154,10 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, } } else { 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. if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { return err @@ -158,10 +166,14 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, } } } - if err != nil { + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res return err } - return c.abi.Unpack(result, method, output) + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) } // Transact invokes the (paid) contract method with params as input values. @@ -177,7 +189,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in } // RawTransact initiates a transaction with the given raw calldata as the input. -// It's usually used to initiates transaction for invoking **Fallback** function. +// It's usually used to initiate transactions for invoking **Fallback** function. func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { // todo(rjl493456442) check the method is payable or not, // reject invalid transaction at the first place @@ -246,10 +258,13 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if opts.Signer == nil { return nil, errors.New("no signer to authorize the transaction with") } - signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx) + signedTx, err := opts.Signer(opts.From, rawTx) if err != nil { return nil, err } + if opts.NoSend { + return signedTx, nil + } if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { return nil, err } @@ -264,9 +279,9 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int opts = new(FilterOpts) } // Append the event selector to the query parameters and construct the topic set - query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...) + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) - topics, err := makeTopics(query...) + topics, err := abi.MakeTopics(query...) if err != nil { return nil, nil, err } @@ -313,9 +328,9 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter opts = new(WatchOpts) } // Append the event selector to the query parameters and construct the topic set - query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...) + query = append([][]interface{}{{c.abi.Events[name].ID}}, query...) - topics, err := makeTopics(query...) + topics, err := abi.MakeTopics(query...) if err != nil { return nil, nil, err } @@ -339,7 +354,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter // UnpackLog unpacks a retrieved log into the provided output structure. func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error { if len(log.Data) > 0 { - if err := c.abi.Unpack(out, event, log.Data); err != nil { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { return err } } @@ -349,7 +364,7 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) indexed = append(indexed, arg) } } - return parseTopics(out, indexed, log.Topics[1:]) + return abi.ParseTopics(out, indexed, log.Topics[1:]) } // UnpackLogIntoMap unpacks a retrieved log into the provided map. @@ -365,7 +380,7 @@ func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event strin indexed = append(indexed, arg) } } - return parseTopicsIntoMap(out, indexed, log.Topics[1:]) + return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) } // ensureContext is a helper method to ensure a context is not nil, even if the diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 3ae685e00f..c4740f68b7 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -17,9 +17,9 @@ package bind_test import ( - "bytes" "context" "math/big" + "reflect" "strings" "testing" @@ -34,8 +34,10 @@ import ( ) type mockCaller struct { - codeAtBlockNumber *big.Int - callContractBlockNumber *big.Int + codeAtBlockNumber *big.Int + callContractBlockNumber *big.Int + pendingCodeAtCalled bool + pendingCallContractCalled bool } func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { @@ -47,6 +49,16 @@ func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, b mc.callContractBlockNumber = blockNumber return nil, nil } + +func (mc *mockCaller) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + mc.pendingCodeAtCalled = true + return nil, nil +} + +func (mc *mockCaller) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + mc.pendingCallContractCalled = true + return nil, nil +} func TestPassingBlockNumber(t *testing.T) { mc := &mockCaller{} @@ -59,11 +71,10 @@ func TestPassingBlockNumber(t *testing.T) { }, }, }, mc, nil, nil) - var ret string blockNumber := big.NewInt(42) - bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something") + bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, nil, "something") if mc.callContractBlockNumber != blockNumber { t.Fatalf("CallContract() was not passed the block number") @@ -73,7 +84,7 @@ func TestPassingBlockNumber(t *testing.T) { t.Fatalf("CodeAt() was not passed the block number") } - bc.Call(&bind.CallOpts{}, &ret, "something") + bc.Call(&bind.CallOpts{}, nil, "something") if mc.callContractBlockNumber != nil { t.Fatalf("CallContract() was passed a block number when it should not have been") @@ -82,57 +93,39 @@ func TestPassingBlockNumber(t *testing.T) { if mc.codeAtBlockNumber != nil { t.Fatalf("CodeAt() was passed a block number when it should not have been") } + + bc.Call(&bind.CallOpts{BlockNumber: blockNumber, Pending: true}, nil, "something") + + if !mc.pendingCallContractCalled { + t.Fatalf("CallContract() was not passed the block number") + } + + if !mc.pendingCodeAtCalled { + t.Fatalf("CodeAt() was not passed the block number") + } } const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { hash := crypto.Keccak256Hash([]byte("testName")) - mockLog := types.Log{ - Address: common.HexToAddress("0x0"), - Topics: []common.Hash{ - common.HexToHash("0x0"), - hash, - }, - Data: hexutil.MustDecode(hexData), - BlockNumber: uint64(26), - TxHash: common.HexToHash("0x0"), - TxIndex: 111, - BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), - Index: 7, - Removed: false, + topics := []common.Hash{ + common.HexToHash("0x0"), + hash, } + mockLog := newMockLog(topics, common.HexToHash("0x0")) abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) - receivedMap := make(map[string]interface{}) expectedReceivedMap := map[string]interface{}{ "name": hash, "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), "memo": []byte{88}, } - if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { - t.Error(err) - } - - if len(receivedMap) != 4 { - t.Fatal("unpacked map expected to have length 4") - } - if receivedMap["name"] != expectedReceivedMap["name"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { - t.Error("unpacked map does not match expected map") - } - if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { - t.Error("unpacked map does not match expected map") - } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) } func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { @@ -141,51 +134,23 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { t.Fatal(err) } hash := crypto.Keccak256Hash(sliceBytes) - mockLog := types.Log{ - Address: common.HexToAddress("0x0"), - Topics: []common.Hash{ - common.HexToHash("0x0"), - hash, - }, - Data: hexutil.MustDecode(hexData), - BlockNumber: uint64(26), - TxHash: common.HexToHash("0x0"), - TxIndex: 111, - BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), - Index: 7, - Removed: false, + topics := []common.Hash{ + common.HexToHash("0x0"), + hash, } + mockLog := newMockLog(topics, common.HexToHash("0x0")) abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) - receivedMap := make(map[string]interface{}) expectedReceivedMap := map[string]interface{}{ "names": hash, "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), "memo": []byte{88}, } - if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { - t.Error(err) - } - - if len(receivedMap) != 4 { - t.Fatal("unpacked map expected to have length 4") - } - if receivedMap["names"] != expectedReceivedMap["names"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { - t.Error("unpacked map does not match expected map") - } - if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { - t.Error("unpacked map does not match expected map") - } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) } func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { @@ -194,51 +159,23 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { t.Fatal(err) } hash := crypto.Keccak256Hash(arrBytes) - mockLog := types.Log{ - Address: common.HexToAddress("0x0"), - Topics: []common.Hash{ - common.HexToHash("0x0"), - hash, - }, - Data: hexutil.MustDecode(hexData), - BlockNumber: uint64(26), - TxHash: common.HexToHash("0x0"), - TxIndex: 111, - BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), - Index: 7, - Removed: false, + topics := []common.Hash{ + common.HexToHash("0x0"), + hash, } + mockLog := newMockLog(topics, common.HexToHash("0x0")) abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) - receivedMap := make(map[string]interface{}) expectedReceivedMap := map[string]interface{}{ "addresses": hash, "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), "memo": []byte{88}, } - if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { - t.Error(err) - } - - if len(receivedMap) != 4 { - t.Fatal("unpacked map expected to have length 4") - } - if receivedMap["addresses"] != expectedReceivedMap["addresses"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { - t.Error("unpacked map does not match expected map") - } - if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { - t.Error("unpacked map does not match expected map") - } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) } func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { @@ -249,99 +186,72 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { functionTyBytes := append(addrBytes, functionSelector...) var functionTy [24]byte copy(functionTy[:], functionTyBytes[0:24]) - mockLog := types.Log{ - Address: common.HexToAddress("0x0"), - Topics: []common.Hash{ - common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), - common.BytesToHash(functionTyBytes), - }, - Data: hexutil.MustDecode(hexData), - BlockNumber: uint64(26), - TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), - TxIndex: 111, - BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), - Index: 7, - Removed: false, + topics := []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + common.BytesToHash(functionTyBytes), } - + mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42")) abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) - receivedMap := make(map[string]interface{}) expectedReceivedMap := map[string]interface{}{ "function": functionTy, "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), "memo": []byte{88}, } - if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { - t.Error(err) - } - - if len(receivedMap) != 4 { - t.Fatal("unpacked map expected to have length 4") - } - if receivedMap["function"] != expectedReceivedMap["function"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { - t.Error("unpacked map does not match expected map") - } - if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { - t.Error("unpacked map does not match expected map") - } + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) } func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { - byts := []byte{1, 2, 3, 4, 5} - hash := crypto.Keccak256Hash(byts) - mockLog := types.Log{ - Address: common.HexToAddress("0x0"), - Topics: []common.Hash{ - common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), - hash, - }, - Data: hexutil.MustDecode(hexData), - BlockNumber: uint64(26), - TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), - TxIndex: 111, - BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), - Index: 7, - Removed: false, + bytes := []byte{1, 2, 3, 4, 5} + hash := crypto.Keccak256Hash(bytes) + topics := []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + hash, } + mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42")) abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) - receivedMap := make(map[string]interface{}) expectedReceivedMap := map[string]interface{}{ "content": hash, "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), "memo": []byte{88}, } - if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + unpackAndCheck(t, bc, expectedReceivedMap, mockLog) +} + +func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) { + received := make(map[string]interface{}) + if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil { t.Error(err) } - if len(receivedMap) != 4 { - t.Fatal("unpacked map expected to have length 4") - } - if receivedMap["content"] != expectedReceivedMap["content"] { - t.Error("unpacked map does not match expected map") - } - if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") + if len(received) != len(expected) { + t.Fatalf("unpacked map length %v not equal expected length of %v", len(received), len(expected)) } - if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { - t.Error("unpacked map does not match expected map") + for name, elem := range expected { + if !reflect.DeepEqual(elem, received[name]) { + t.Errorf("field %v does not match expected, want %v, got %v", name, elem, received[name]) + } } - if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { - t.Error("unpacked map does not match expected map") +} + +func newMockLog(topics []common.Hash, txHash common.Hash) types.Log { + return types.Log{ + Address: common.HexToAddress("0x0"), + Topics: topics, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: txHash, + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, } } diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index c98f8b4d4c..0e98709b14 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -52,7 +52,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // contracts is the map of each individual contract requested binding contracts = make(map[string]*tmplContract) - // structs is the map of all reclared structs shared by passed contracts. + // structs is the map of all redeclared structs shared by passed contracts. structs = make(map[string]*tmplStruct) // isLib is the map used to flag each encountered library as such @@ -80,10 +80,10 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] fallback *tmplMethod receive *tmplMethod - // identifiers are used to detect duplicated identifier of function - // and event. For all calls, transacts and events, abigen will generate + // identifiers are used to detect duplicated identifiers of functions + // and events. For all calls, transacts and events, abigen will generate // corresponding bindings. However we have to ensure there is no - // identifier coliision in the bindings of these categories. + // identifier collisions in the bindings of these categories. callIdentifiers = make(map[string]bool) transactIdentifiers = make(map[string]bool) eventIdentifiers = make(map[string]bool) @@ -220,8 +220,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] "bindtype": bindType[lang], "bindtopictype": bindTopicType[lang], "namedtype": namedType[lang], - "formatmethod": formatMethod, - "formatevent": formatEvent, "capitalise": capitalise, "decapitalise": decapitalise, } @@ -248,7 +246,7 @@ var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) stri LangJava: bindTypeJava, } -// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go one. +// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. func bindBasicTypeGo(kind abi.Type) string { switch kind.T { case abi.AddressTy: @@ -288,7 +286,7 @@ func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } } -// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java one. +// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java ones. func bindBasicTypeJava(kind abi.Type) string { switch kind.T { case abi.AddressTy: @@ -332,7 +330,7 @@ func bindBasicTypeJava(kind abi.Type) string { } // pluralizeJavaType explicitly converts multidimensional types to predefined -// type in go side. +// types in go side. func pluralizeJavaType(typ string) string { switch typ { case "boolean": @@ -371,7 +369,7 @@ var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) } // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same -// funcionality as for simple types, but dynamic types get converted to hashes. +// functionality as for simple types, but dynamic types get converted to hashes. func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { bound := bindTypeGo(kind, structs) @@ -388,7 +386,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } // bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same -// funcionality as for simple types, but dynamic types get converted to hashes. +// functionality as for simple types, but dynamic types get converted to hashes. func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { bound := bindTypeJava(kind, structs) @@ -396,7 +394,7 @@ func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { // parameters that are not value types i.e. arrays and structs are not // stored directly but instead a keccak256-hash of an encoding is stored. // - // We only convert stringS and bytes to hash, still need to deal with + // We only convert strings and bytes to hash, still need to deal with // array(both fixed-size and dynamic-size) and struct. if bound == "String" || bound == "byte[]" { bound = "Hash" @@ -417,7 +415,7 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: - // We compose raw struct name and canonical parameter expression + // We compose a raw struct name and a canonical parameter expression // together here. The reason is before solidity v0.5.11, kind.TupleRawName // is empty, so we use canonical parameter expression to distinguish // different struct definition. From the consideration of backward @@ -456,7 +454,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: - // We compose raw struct name and canonical parameter expression + // We compose a raw struct name and a canonical parameter expression // together here. The reason is before solidity v0.5.11, kind.TupleRawName // is empty, so we use canonical parameter expression to distinguish // different struct definition. From the consideration of backward @@ -488,7 +486,7 @@ func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { } // namedType is a set of functions that transform language specific types to -// named versions that my be used inside method names. +// named versions that may be used inside method names. var namedType = map[Lang]func(string, abi.Type) string{ LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, LangJava: namedTypeJava, @@ -530,16 +528,14 @@ func alias(aliases map[string]string, n string) string { } // methodNormalizer is a name transformer that modifies Solidity method names to -// conform to target language naming concentions. +// conform to target language naming conventions. var methodNormalizer = map[Lang]func(string) string{ LangGo: abi.ToCamelCase, LangJava: decapitalise, } // capitalise makes a camel-case string which starts with an upper case character. -func capitalise(input string) string { - return abi.ToCamelCase(input) -} +var capitalise = abi.ToCamelCase // decapitalise makes a camel-case string which starts with a lower case character. func decapitalise(input string) string { @@ -588,74 +584,3 @@ func hasStruct(t abi.Type) bool { return false } } - -// resolveArgName converts a raw argument representation into a user friendly format. -func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string { - var ( - prefix string - embedded string - typ = &arg.Type - ) -loop: - for { - switch typ.T { - case abi.SliceTy: - prefix += "[]" - case abi.ArrayTy: - prefix += fmt.Sprintf("[%d]", typ.Size) - default: - embedded = typ.TupleRawName + typ.String() - break loop - } - typ = typ.Elem - } - if s, exist := structs[embedded]; exist { - return prefix + s.Name - } else { - return arg.Type.String() - } -} - -// formatMethod transforms raw method representation into a user friendly one. -func formatMethod(method abi.Method, structs map[string]*tmplStruct) string { - inputs := make([]string, len(method.Inputs)) - for i, input := range method.Inputs { - inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name) - } - outputs := make([]string, len(method.Outputs)) - for i, output := range method.Outputs { - outputs[i] = resolveArgName(output, structs) - if len(output.Name) > 0 { - outputs[i] += fmt.Sprintf(" %v", output.Name) - } - } - // Extract meaningful state mutability of solidity method. - // If it's default value, never print it. - state := method.StateMutability - if state == "nonpayable" { - state = "" - } - if state != "" { - state = state + " " - } - identity := fmt.Sprintf("function %v", method.RawName) - if method.IsFallback { - identity = "fallback" - } else if method.IsReceive { - identity = "receive" - } - return fmt.Sprintf("%s(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", ")) -} - -// formatEvent transforms raw event representation into a user friendly one. -func formatEvent(event abi.Event, structs map[string]*tmplStruct) string { - inputs := make([]string, len(event.Inputs)) - for i, input := range event.Inputs { - if input.Indexed { - inputs[i] = fmt.Sprintf("%v indexed %v", resolveArgName(input, structs), input.Name) - } else { - inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name) - } - } - return fmt.Sprintf("event %v(%v)", event.RawName, strings.Join(inputs, ", ")) -} diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 7add7110a0..d0958cb62f 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -199,7 +199,8 @@ var bindTests = []struct { {"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256","indexed":true}]}, {"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256"}]}, {"type":"event","name":"anonymous","anonymous":true,"inputs":[]}, - {"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]} + {"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]}, + {"type":"event","name":"unnamed","inputs":[{"name":"","type":"uint256","indexed": true},{"name":"","type":"uint256","indexed":true}]} ] `}, ` @@ -249,6 +250,12 @@ var bindTests = []struct { fmt.Println(event.Addr) // Make sure the reconstructed indexed fields are present fmt.Println(res, str, dat, hash, err) + + oit, err := e.FilterUnnamed(nil, []*big.Int{}, []*big.Int{}) + + arg0 := oit.Event.Arg0 // Make sure unnamed arguments are handled correctly + arg1 := oit.Event.Arg1 // Make sure unnamed arguments are handled correctly + fmt.Println(arg0, arg1) } // Run a tiny reflection test to ensure disallowed methods don't appear if _, ok := reflect.TypeOf(&EventChecker{}).MethodByName("FilterAnonymous"); ok { @@ -289,7 +296,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -344,7 +351,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -390,7 +397,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -448,7 +455,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -496,7 +503,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -522,6 +529,70 @@ var bindTests = []struct { nil, nil, }, + // Tests that structs are correctly unpacked + { + + `Structs`, + ` + pragma solidity ^0.6.5; + pragma experimental ABIEncoderV2; + contract Structs { + struct A { + bytes32 B; + } + + function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + uint256[] memory c; + bool[] memory d; + return (a, c, d); + } + + function G() public view returns (A[] memory a) { + A[] memory a = new A[](2); + a[0].B = bytes32(uint256(1234) << 96); + return a; + } + } + `, + []string{`608060405234801561001057600080fd5b50610278806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806328811f591461003b5780636fecb6231461005b575b600080fd5b610043610070565b604051610052939291906101a0565b60405180910390f35b6100636100d6565b6040516100529190610186565b604080516002808252606082810190935282918291829190816020015b610095610131565b81526020019060019003908161008d575050805190915061026960611b9082906000906100be57fe5b60209081029190910101515293606093508392509050565b6040805160028082526060828101909352829190816020015b6100f7610131565b8152602001906001900390816100ef575050805190915061026960611b90829060009061012057fe5b602090810291909101015152905090565b60408051602081019091526000815290565b815260200190565b6000815180845260208085019450808401835b8381101561017b578151518752958201959082019060010161015e565b509495945050505050565b600060208252610199602083018461014b565b9392505050565b6000606082526101b3606083018661014b565b6020838203818501528186516101c98185610239565b91508288019350845b818110156101f3576101e5838651610143565b9484019492506001016101d2565b505084810360408601528551808252908201925081860190845b8181101561022b57825115158552938301939183019160010161020d565b509298975050505050505050565b9081526020019056fea2646970667358221220eb85327e285def14230424c52893aebecec1e387a50bb6b75fc4fdbed647f45f64736f6c63430006050033`}, + []string{`[{"inputs":[],"name":"F","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"},{"internalType":"uint256[]","name":"c","type":"uint256[]"},{"internalType":"bool[]","name":"d","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"structStructs.A[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"}]`}, + ` + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) + defer sim.Close() + + // Deploy a structs method invoker contract and execute its default method + _, _, structs, err := DeployStructs(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy defaulter contract: %v", err) + } + sim.Commit() + opts := bind.CallOpts{} + if _, err := structs.F(&opts); err != nil { + t.Fatalf("Failed to invoke F method: %v", err) + } + if _, err := structs.G(&opts); err != nil { + t.Fatalf("Failed to invoke G method: %v", err) + } + `, + nil, + nil, + nil, + nil, + }, // Tests that non-existent contracts are reported as such (though only simulator test) { `NonExistent`, @@ -562,6 +633,45 @@ var bindTests = []struct { nil, nil, }, + { + `NonExistentStruct`, + ` + contract NonExistentStruct { + function Struct() public view returns(uint256 a, uint256 b) { + return (10, 10); + } + } + `, + []string{`6080604052348015600f57600080fd5b5060888061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063d5f6622514602d575b600080fd5b6033604c565b6040805192835260208301919091528051918290030190f35b600a809156fea264697066735822beefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeefbeef64736f6c6343decafe0033`}, + []string{`[{"inputs":[],"name":"Struct","outputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"}],"stateMutability":"pure","type":"function"}]`}, + ` + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + `, + ` + // Create a simulator and wrap a non-deployed contract + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{}, uint64(10000000000)) + defer sim.Close() + + nonexistent, err := NewNonExistentStruct(common.Address{}, sim) + if err != nil { + t.Fatalf("Failed to access non-existent contract: %v", err) + } + // Ensure that contract calls fail with the appropriate error + if res, err := nonexistent.Struct(nil); err == nil { + t.Fatalf("Call succeeded on non-existent contract: %v", res) + } else if (err != bind.ErrNoCode) { + t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode) + } + `, + nil, + nil, + nil, + nil, + }, // Tests that gas estimation works for contracts with weird gas mechanics too. { `FunkyGasPattern`, @@ -591,7 +701,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -641,7 +751,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -716,7 +826,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -810,7 +920,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1000,7 +1110,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1135,7 +1245,7 @@ var bindTests = []struct { ` key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1277,7 +1387,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1343,7 +1453,7 @@ var bindTests = []struct { ` // Initialize test accounts key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1437,7 +1547,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, _, err := DeployIdentifierCollision(transactOpts, sim) if err != nil { t.Fatalf("failed to deploy contract: %v", err) @@ -1499,7 +1609,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 10000000) defer sim.Close() - transactOpts := bind.NewKeyedTransactor(key) + transactOpts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, c1, err := DeployContractOne(transactOpts, sim) if err != nil { t.Fatal("Failed to deploy contract") @@ -1556,7 +1666,7 @@ var bindTests = []struct { ` // Generate a new random account and a funded simulator key, _ := crypto.GenerateKey() - auth := bind.NewKeyedTransactor(key) + auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}}, 10000000) defer sim.Close() @@ -1594,11 +1704,7 @@ var bindTests = []struct { contract NewFallbacks { event Fallback(bytes data); fallback() external { - bytes memory data; - assembly { - calldatacopy(data, 0, calldatasize()) - } - emit Fallback(data); + emit Fallback(msg.data); } event Received(address addr, uint value); @@ -1607,7 +1713,7 @@ var bindTests = []struct { } } `, - []string{"60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040526004361061000d575b36610081575b7f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a15b005b34801561008e5760006000fd5b505b606036600082377f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f986816040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fa5780820151818401525b6020810190506100de565b50505050905090810190601f1680156101275780820380516001836020036101000a031916815260200191505b509250505060405180910390a1505b00fea26469706673582212205643ca37f40c2b352dc541f42e9e6720de065de756324b7fcc9fb1d67eda4a7d64736f6c63430006040033"}, + []string{"6080604052348015600f57600080fd5b506101078061001f6000396000f3fe608060405236605f577f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a1005b348015606a57600080fd5b507f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f98660003660405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a100fea26469706673582212201f994dcfbc53bf610b19176f9a361eafa77b447fd9c796fa2c615dfd0aaf3b8b64736f6c634300060c0033"}, []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Fallback","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Received","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]`}, ` "bytes" @@ -1625,7 +1731,7 @@ var bindTests = []struct { sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 1000000) defer sim.Close() - opts := bind.NewKeyedTransactor(key) + opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) _, _, c, err := DeployNewFallbacks(opts, sim) if err != nil { t.Fatalf("Failed to deploy contract: %v", err) @@ -1655,6 +1761,7 @@ var bindTests = []struct { } // Test fallback function + gotEvent = false opts.Value = nil calldata := []byte{0x01, 0x02, 0x03} c.Fallback(opts, calldata) @@ -1689,11 +1796,11 @@ func TestGolangBindings(t *testing.T) { t.Skip("go sdk not found for testing") } // Create a temporary workspace for the test suite - ws, err := ioutil.TempDir("", "") + ws, err := ioutil.TempDir("", "binding-test") if err != nil { t.Fatalf("failed to create temporary workspace: %v", err) } - defer os.RemoveAll(ws) + //defer os.RemoveAll(ws) pkg := filepath.Join(ws, "bindtest") if err = os.MkdirAll(pkg, 0700); err != nil { @@ -1739,11 +1846,16 @@ func TestGolangBindings(t *testing.T) { t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) } pwd, _ := os.Getwd() - replacer := exec.Command(gocmd, "mod", "edit", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root + replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ethereum/go-ethereum@v0.0.0", "-replace", "github.com/ethereum/go-ethereum="+filepath.Join(pwd, "..", "..", "..")) // Repo root replacer.Dir = pkg if out, err := replacer.CombinedOutput(); err != nil { t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) } + tidier := exec.Command(gocmd, "mod", "tidy") + tidier.Dir = pkg + if out, err := tidier.CombinedOutput(); err != nil { + t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) + } // Test the entire package and report any failures cmd := exec.Command(gocmd, "test", "-v", "-count", "1") cmd.Dir = pkg diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index c361a49f84..e9bdd3d414 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -30,7 +30,7 @@ type tmplData struct { type tmplContract struct { Type string // Type name of the main contract binding InputABI string // JSON ABI used as the input to generate the binding from - InputBin string // Optional EVM bytecode used to denetare deploy code from + InputBin string // Optional EVM bytecode used to generate deploy code from FuncSigs map[string]string // Optional map: string signature -> 4-byte signature Constructor abi.Method // Contract constructor for deploy parametrization Calls map[string]*tmplMethod // Contract calls that only read state data @@ -50,7 +50,8 @@ type tmplMethod struct { Structured bool // Whether the returns should be accumulated into a struct } -// tmplEvent is a wrapper around an a +// tmplEvent is a wrapper around an abi.Event that contains a few preprocessed +// and cached data fields. type tmplEvent struct { Original abi.Event // Original event as parsed by the abi package Normalized abi.Event // Normalized version of the parsed fields @@ -64,7 +65,7 @@ type tmplField struct { SolKind abi.Type // Raw abi type information } -// tmplStruct is a wrapper around an abi.tuple contains a auto-generated +// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated // struct name. type tmplStruct struct { Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. @@ -78,8 +79,8 @@ var tmplSource = map[Lang]string{ LangJava: tmplSourceJava, } -// tmplSourceGo is the Go source template use to generate the contract binding -// based on. +// tmplSourceGo is the Go source template that the generated Go contract binding +// is based on. const tmplSourceGo = ` // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. @@ -103,7 +104,6 @@ var ( _ = big.NewInt _ = strings.NewReader _ = ethereum.NotFound - _ = abi.U256 _ = bind.Bind _ = common.Big1 _ = types.BloomLookup @@ -261,7 +261,7 @@ var ( // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. - func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + func (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...) } @@ -280,7 +280,7 @@ var ( // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named // returns. - func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + func (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...) } @@ -298,33 +298,40 @@ var ( {{range .Calls}} // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatmethod .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) { - {{if .Structured}}ret := new(struct{ - {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}} - {{end}} - }){{else}}var ( - {{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type $structs}}) - {{end}} - ){{end}} - out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{ - {{range $i, $_ := .Normalized.Outputs}}ret{{$i}}, - {{end}} - }{{end}}{{end}} - err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) - return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err + var out []interface{} + err := _{{$contract.Type}}.contract.Call(opts, &out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) + {{if .Structured}} + outstruct := new(struct{ {{range .Normalized.Outputs}} {{.Name}} {{bindtype .Type $structs}}; {{end}} }) + if err != nil { + return *outstruct, err + } + {{range $i, $t := .Normalized.Outputs}} + outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return *outstruct, err + {{else}} + if err != nil { + return {{range $i, $_ := .Normalized.Outputs}}*new({{bindtype .Type $structs}}), {{end}} err + } + {{range $i, $t := .Normalized.Outputs}} + out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} + + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err + {{end}} } // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatmethod .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) } // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatmethod .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) { return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}}) } @@ -333,21 +340,21 @@ var ( {{range .Transacts}} // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatmethod .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}}) } // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatmethod .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) } // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatmethod .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}}) } @@ -356,21 +363,21 @@ var ( {{if .Fallback}} // Fallback is a paid mutator transaction binding the contract fallback function. // - // Solidity: {{formatmethod .Fallback.Original $structs}} + // Solidity: {{.Fallback.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.contract.RawTransact(opts, calldata) } // Fallback is a paid mutator transaction binding the contract fallback function. // - // Solidity: {{formatmethod .Fallback.Original $structs}} + // Solidity: {{.Fallback.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) } // Fallback is a paid mutator transaction binding the contract fallback function. // - // Solidity: {{formatmethod .Fallback.Original $structs}} + // Solidity: {{.Fallback.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) } @@ -379,21 +386,21 @@ var ( {{if .Receive}} // Receive is a paid mutator transaction binding the contract receive function. // - // Solidity: {{formatmethod .Receive.Original $structs}} + // Solidity: {{.Receive.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function } // Receive is a paid mutator transaction binding the contract receive function. // - // Solidity: {{formatmethod .Receive.Original $structs}} + // Solidity: {{.Receive.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) } // Receive is a paid mutator transaction binding the contract receive function. // - // Solidity: {{formatmethod .Receive.Original $structs}} + // Solidity: {{.Receive.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) } @@ -472,7 +479,7 @@ var ( // Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatevent .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) { {{range .Normalized.Inputs}} {{if .Indexed}}var {{.Name}}Rule []interface{} @@ -489,7 +496,7 @@ var ( // Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatevent .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) { {{range .Normalized.Inputs}} {{if .Indexed}}var {{.Name}}Rule []interface{} @@ -531,12 +538,13 @@ var ( // Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}. // - // Solidity: {{formatevent .Original $structs}} + // Solidity: {{.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) { event := new({{$contract.Type}}{{.Normalized.Name}}) if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil { return nil, err } + event.Raw = log return event, nil } @@ -544,8 +552,8 @@ var ( {{end}} ` -// tmplSourceJava is the Java source template use to generate the contract binding -// based on. +// tmplSourceJava is the Java source template that the generated Java contract binding +// is based on. const tmplSourceJava = ` // This file is an automatically generated Java binding. Do not modify as any // change will likely be lost upon the next re-generation! @@ -625,7 +633,7 @@ import java.util.*; // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}. // // Solidity: {{.Original.String}} - public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { + public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else if eq (len .Normalized.Outputs) 0}}void{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception { Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); {{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}}); {{end}} @@ -663,7 +671,7 @@ import java.util.*; {{if .Fallback}} // Fallback is a paid mutator transaction binding the contract fallback function. // - // Solidity: {{formatmethod .Fallback.Original $structs}} + // Solidity: {{.Fallback.Original.String}} public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { return this.Contract.rawTransact(opts, calldata); } @@ -672,7 +680,7 @@ import java.util.*; {{if .Receive}} // Receive is a paid mutator transaction binding the contract receive function. // - // Solidity: {{formatmethod .Receive.Original $structs}} + // Solidity: {{.Receive.Original.String}} public Transaction Receive(TransactOpts opts) throws Exception { return this.Contract.rawTransact(opts, null); } diff --git a/accounts/abi/bind/util.go b/accounts/abi/bind/util.go index d129993ca1..118abc59a7 100644 --- a/accounts/abi/bind/util.go +++ b/accounts/abi/bind/util.go @@ -18,7 +18,7 @@ package bind import ( "context" - "fmt" + "errors" "time" "github.com/ethereum/go-ethereum/common" @@ -56,14 +56,14 @@ func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*ty // contract address when it is mined. It stops waiting when ctx is canceled. func WaitDeployed(ctx context.Context, b DeployBackend, tx *types.Transaction) (common.Address, error) { if tx.To() != nil { - return common.Address{}, fmt.Errorf("tx is not contract creation") + return common.Address{}, errors.New("tx is not contract creation") } receipt, err := WaitMined(ctx, b, tx) if err != nil { return common.Address{}, err } if receipt.ContractAddress == (common.Address{}) { - return common.Address{}, fmt.Errorf("zero address") + return common.Address{}, errors.New("zero address") } // Check that code has indeed been deployed at the address. // This matters on pre-Homestead chains: OOG in the constructor diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index e0141f46e0..9f9b7a000d 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -18,6 +18,7 @@ package bind_test import ( "context" + "errors" "math/big" "testing" "time" @@ -84,7 +85,7 @@ func TestWaitDeployed(t *testing.T) { select { case <-mined: if err != test.wantErr { - t.Errorf("test %q: error mismatch: got %q, want %q", name, err, test.wantErr) + t.Errorf("test %q: error mismatch: want %q, got %q", name, test.wantErr, err) } if address != test.wantAddress { t.Errorf("test %q: unexpected contract address %s", name, address.Hex()) @@ -94,3 +95,40 @@ func TestWaitDeployed(t *testing.T) { } } } + +func TestWaitDeployedCornerCases(t *testing.T) { + backend := backends.NewSimulatedBackend( + core.GenesisAlloc{ + crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)}, + }, + 10000000, + ) + defer backend.Close() + + // Create a transaction to an account. + code := "6060604052600a8060106000396000f360606040526008565b00" + tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, big.NewInt(1), common.FromHex(code)) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + backend.SendTransaction(ctx, tx) + backend.Commit() + notContentCreation := errors.New("tx is not contract creation") + if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() { + t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err) + } + + // Create a transaction that is not mined. + tx = types.NewContractCreation(1, big.NewInt(0), 3000000, big.NewInt(1), common.FromHex(code)) + tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + + go func() { + contextCanceled := errors.New("context canceled") + if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() { + t.Errorf("error missmatch: want %q, got %q, ", contextCanceled, err) + } + }() + + backend.SendTransaction(ctx, tx) + cancel() +} diff --git a/accounts/abi/error.go b/accounts/abi/error.go index 9d8674ad08..f0f71b6c91 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -39,23 +39,21 @@ func formatSliceString(kind reflect.Kind, sliceSize int) string { // type in t. func sliceTypeCheck(t Type, val reflect.Value) error { if val.Kind() != reflect.Slice && val.Kind() != reflect.Array { - return typeErr(formatSliceString(t.Kind, t.Size), val.Type()) + return typeErr(formatSliceString(t.GetType().Kind(), t.Size), val.Type()) } if t.T == ArrayTy && val.Len() != t.Size { - return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len())) } - if t.Elem.T == SliceTy { + if t.Elem.T == SliceTy || t.Elem.T == ArrayTy { if val.Len() > 0 { return sliceTypeCheck(*t.Elem, val.Index(0)) } - } else if t.Elem.T == ArrayTy { - return sliceTypeCheck(*t.Elem, val.Index(0)) } - if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.Kind { - return typeErr(formatSliceString(t.Elem.Kind, t.Size), val.Type()) + if val.Type().Elem().Kind() != t.Elem.GetType().Kind() { + return typeErr(formatSliceString(t.Elem.GetType().Kind(), t.Size), val.Type()) } return nil } @@ -68,10 +66,10 @@ func typeCheck(t Type, value reflect.Value) error { } // Check base type validity. Element types will be checked later on. - if t.Kind != value.Kind() { - return typeErr(t.Kind, value.Kind()) + if t.GetType().Kind() != value.Kind() { + return typeErr(t.GetType().Kind(), value.Kind()) } else if t.T == FixedBytesTy && t.Size != value.Len() { - return typeErr(t.Type, value.Type()) + return typeErr(t.GetType(), value.Type()) } else { return nil } diff --git a/accounts/abi/event.go b/accounts/abi/event.go index f1474813af..b238a36d7c 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -32,7 +32,7 @@ type Event struct { // the raw name and a suffix will be added in the case of a event overload. // // e.g. - // There are two events have same name: + // These are two events that have the same name: // * foo(int,int) // * foo(uint,uint) // The event name of the first one wll be resolved as foo while the second one @@ -42,36 +42,59 @@ type Event struct { RawName string Anonymous bool Inputs Arguments + str string + // Sig contains the string signature according to the ABI spec. + // e.g. event foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + // ID returns the canonical representation of the event's signature used by the + // abi definition to identify event names and types. + ID common.Hash } -func (e Event) String() string { - inputs := make([]string, len(e.Inputs)) - for i, input := range e.Inputs { - inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name) +// NewEvent creates a new Event. +// It sanitizes the input arguments to remove unnamed arguments. +// It also precomputes the id, signature and string representation +// of the event. +func NewEvent(name, rawName string, anonymous bool, inputs Arguments) Event { + // sanitize inputs to remove inputs without names + // and precompute string and sig representation. + names := make([]string, len(inputs)) + types := make([]string, len(inputs)) + for i, input := range inputs { + if input.Name == "" { + inputs[i] = Argument{ + Name: fmt.Sprintf("arg%d", i), + Indexed: input.Indexed, + Type: input.Type, + } + } else { + inputs[i] = input + } + // string representation + names[i] = fmt.Sprintf("%v %v", input.Type, inputs[i].Name) if input.Indexed { - inputs[i] = fmt.Sprintf("%v indexed %v", input.Type, input.Name) + names[i] = fmt.Sprintf("%v indexed %v", input.Type, inputs[i].Name) } + // sig representation + types[i] = input.Type.String() } - return fmt.Sprintf("event %v(%v)", e.RawName, strings.Join(inputs, ", ")) -} -// Sig returns the event string signature according to the ABI spec. -// -// Example -// -// event foo(uint32 a, int b) = "foo(uint32,int256)" -// -// Please note that "int" is substitute for its canonical representation "int256" -func (e Event) Sig() string { - types := make([]string, len(e.Inputs)) - for i, input := range e.Inputs { - types[i] = input.Type.String() + str := fmt.Sprintf("event %v(%v)", rawName, strings.Join(names, ", ")) + sig := fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id := common.BytesToHash(crypto.Keccak256([]byte(sig))) + + return Event{ + Name: name, + RawName: rawName, + Anonymous: anonymous, + Inputs: inputs, + str: str, + Sig: sig, + ID: id, } - return fmt.Sprintf("%v(%v)", e.RawName, strings.Join(types, ",")) } -// ID returns the canonical representation of the event's signature used by the -// abi definition to identify event names and types. -func (e Event) ID() common.Hash { - return common.BytesToHash(crypto.Keccak256([]byte(e.Sig()))) +func (e Event) String() string { + return e.str } diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 090b9217db..3332f8a072 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -104,8 +104,8 @@ func TestEventId(t *testing.T) { } for name, event := range abi.Events { - if event.ID() != test.expectations[name] { - t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID()) + if event.ID != test.expectations[name] { + t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID) } } } @@ -147,10 +147,6 @@ func TestEventString(t *testing.T) { // TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. func TestEventMultiValueWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` - type testStruct struct { - Value1 [2]uint8 - Value2 uint8 - } abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) var b bytes.Buffer @@ -158,10 +154,10 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) { for ; i <= 3; i++ { b.Write(packNum(reflect.ValueOf(i))) } - var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) - require.Equal(t, [2]uint8{1, 2}, rst.Value1) - require.Equal(t, uint8(3), rst.Value2) + unpacked, err := abi.Unpack("test", b.Bytes()) + require.NoError(t, err) + require.Equal(t, [2]uint8{1, 2}, unpacked[0]) + require.Equal(t, uint8(3), unpacked[1]) } func TestEventTupleUnpack(t *testing.T) { @@ -312,14 +308,14 @@ func TestEventTupleUnpack(t *testing.T) { &[]interface{}{common.Address{}, new(big.Int)}, &[]interface{}{}, jsonEventPledge, - "abi: insufficient number of elements in the list/array for unpack, want 3, got 2", + "abi: insufficient number of arguments for unpack, want 3, got 2", "Can not unpack Pledge event into too short slice", }, { pledgeData1, new(map[string]interface{}), &[]interface{}{}, jsonEventPledge, - "abi: cannot unmarshal tuple into map[string]interface {}", + "abi:[2] cannot unmarshal tuple in to map[string]interface {}", "Can not unpack Pledge event into map", }, { mixedCaseData1, @@ -351,14 +347,14 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass var e Event assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI") a := ABI{Events: map[string]Event{"e": e}} - return a.Unpack(dest, "e", data) + return a.UnpackIntoInterface(dest, "e", data) } // TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. func TestEventUnpackIndexed(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` type testStruct struct { - Value1 uint8 + Value1 uint8 // indexed Value2 uint8 } abi, err := JSON(strings.NewReader(definition)) @@ -366,16 +362,16 @@ func TestEventUnpackIndexed(t *testing.T) { var b bytes.Buffer b.Write(packNum(reflect.ValueOf(uint8(8)))) var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, uint8(0), rst.Value1) require.Equal(t, uint8(8), rst.Value2) } -// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input. +// TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input. func TestEventIndexedWithArrayUnpack(t *testing.T) { definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` type testStruct struct { - Value1 [2]uint8 + Value1 [2]uint8 // indexed Value2 string } abi, err := JSON(strings.NewReader(definition)) @@ -388,7 +384,7 @@ func TestEventIndexedWithArrayUnpack(t *testing.T) { b.Write(common.RightPadBytes([]byte(stringOut), 32)) var rst testStruct - require.NoError(t, abi.Unpack(&rst, "test", b.Bytes())) + require.NoError(t, abi.UnpackIntoInterface(&rst, "test", b.Bytes())) require.Equal(t, [2]uint8{0, 0}, rst.Value1) require.Equal(t, stringOut, rst.Value2) } diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 217c3d2e68..f69e3ee9b5 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -23,11 +23,29 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) +// FunctionType represents different types of functions a contract might have. +type FunctionType int + +const ( + // Constructor represents the constructor of the contract. + // The constructor function is called while deploying a contract. + Constructor FunctionType = iota + // Fallback represents the fallback function. + // This function is executed if no other function matches the given function + // signature and no receive function is specified. + Fallback + // Receive represents the receive function. + // This function is executed on plain Ether transfers. + Receive + // Function represents a normal function. + Function +) + // Method represents a callable given a `Name` and whether the method is a constant. // If the method is `Const` no transaction needs to be created for this // particular Method call. It can easily be simulated using a local VM. // For example a `Balance()` method only needs to retrieve something -// from the storage and therefore requires no Tx to be send to the +// from the storage and therefore requires no Tx to be sent to the // network. A method such as `Transact` does require a Tx and thus will // be flagged `false`. // Input specifies the required input parameters for this gives method. @@ -36,7 +54,7 @@ type Method struct { // the raw name and a suffix will be added in the case of a function overload. // // e.g. - // There are two functions have same name: + // These are two functions that have the same name: // * foo(int,int) // * foo(uint,uint) // The method name of the first one will be resolved as foo while the second one @@ -44,6 +62,10 @@ type Method struct { Name string RawName string // RawName is the raw method name parsed from ABI + // Type indicates whether the method is a + // special fallback introduced in solidity v0.6.0 + Type FunctionType + // StateMutability indicates the mutability state of method, // the default value is nonpayable. It can be empty if the abi // is generated by legacy compiler. @@ -53,69 +75,84 @@ type Method struct { Constant bool Payable bool - // The following two flags indicates whether the method is a - // special fallback introduced in solidity v0.6.0 - IsFallback bool - IsReceive bool - Inputs Arguments Outputs Arguments + str string + // Sig returns the methods string signature according to the ABI spec. + // e.g. function foo(uint32 a, int b) = "foo(uint32,int256)" + // Please note that "int" is substitute for its canonical representation "int256" + Sig string + // ID returns the canonical representation of the method's signature used by the + // abi definition to identify method names and types. + ID []byte } -// Sig returns the methods string signature according to the ABI spec. -// -// Example -// -// function foo(uint32 a, int b) = "foo(uint32,int256)" -// -// Please note that "int" is substitute for its canonical representation "int256" -func (method Method) Sig() string { - // Short circuit if the method is special. Fallback - // and Receive don't have signature at all. - if method.IsFallback || method.IsReceive { - return "" - } - types := make([]string, len(method.Inputs)) - for i, input := range method.Inputs { +// NewMethod creates a new Method. +// A method should always be created using NewMethod. +// It also precomputes the sig representation and the string representation +// of the method. +func NewMethod(name string, rawName string, funType FunctionType, mutability string, isConst, isPayable bool, inputs Arguments, outputs Arguments) Method { + var ( + types = make([]string, len(inputs)) + inputNames = make([]string, len(inputs)) + outputNames = make([]string, len(outputs)) + ) + for i, input := range inputs { + inputNames[i] = fmt.Sprintf("%v %v", input.Type, input.Name) types[i] = input.Type.String() } - return fmt.Sprintf("%v(%v)", method.RawName, strings.Join(types, ",")) -} - -func (method Method) String() string { - inputs := make([]string, len(method.Inputs)) - for i, input := range method.Inputs { - inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name) - } - outputs := make([]string, len(method.Outputs)) - for i, output := range method.Outputs { - outputs[i] = output.Type.String() + for i, output := range outputs { + outputNames[i] = output.Type.String() if len(output.Name) > 0 { - outputs[i] += fmt.Sprintf(" %v", output.Name) + outputNames[i] += fmt.Sprintf(" %v", output.Name) } } + // calculate the signature and method id. Note only function + // has meaningful signature and id. + var ( + sig string + id []byte + ) + if funType == Function { + sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) + id = crypto.Keccak256([]byte(sig))[:4] + } // Extract meaningful state mutability of solidity method. // If it's default value, never print it. - state := method.StateMutability + state := mutability if state == "nonpayable" { state = "" } if state != "" { state = state + " " } - identity := fmt.Sprintf("function %v", method.RawName) - if method.IsFallback { + identity := fmt.Sprintf("function %v", rawName) + if funType == Fallback { identity = "fallback" - } else if method.IsReceive { + } else if funType == Receive { identity = "receive" + } else if funType == Constructor { + identity = "constructor" + } + str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", ")) + + return Method{ + Name: name, + RawName: rawName, + Type: funType, + StateMutability: mutability, + Constant: isConst, + Payable: isPayable, + Inputs: inputs, + Outputs: outputs, + str: str, + Sig: sig, + ID: id, } - return fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", ")) } -// ID returns the canonical representation of the method's signature used by the -// abi definition to identify method names and types. -func (method Method) ID() []byte { - return crypto.Keccak256([]byte(method.Sig()))[:4] +func (method Method) String() string { + return method.str } // IsConstant returns the indicator whether the method is read-only. diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go index ea176bf4e0..395a528965 100644 --- a/accounts/abi/method_test.go +++ b/accounts/abi/method_test.go @@ -137,7 +137,7 @@ func TestMethodSig(t *testing.T) { } for _, test := range cases { - got := abi.Methods[test.method].Sig() + got := abi.Methods[test.method].Sig if got != test.expect { t.Errorf("expected string to be %s, got %s", test.expect, got) } diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go index dd1c9a5df8..0cd91cb4fa 100644 --- a/accounts/abi/pack.go +++ b/accounts/abi/pack.go @@ -17,6 +17,8 @@ package abi import ( + "errors" + "fmt" "math/big" "reflect" @@ -25,7 +27,7 @@ import ( ) // packBytesSlice packs the given bytes as [L, V] as the canonical representation -// bytes slice +// bytes slice. func packBytesSlice(bytes []byte, l int) []byte { len := packNum(reflect.ValueOf(l)) return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...) @@ -33,49 +35,51 @@ func packBytesSlice(bytes []byte, l int) []byte { // packElement packs the given reflect value according to the abi specification in // t. -func packElement(t Type, reflectValue reflect.Value) []byte { +func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { switch t.T { case IntTy, UintTy: - return packNum(reflectValue) + return packNum(reflectValue), nil case StringTy: - return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()) + return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len()), nil case AddressTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } - return common.LeftPadBytes(reflectValue.Bytes(), 32) + return common.LeftPadBytes(reflectValue.Bytes(), 32), nil case BoolTy: if reflectValue.Bool() { - return math.PaddedBigBytes(common.Big1, 32) + return math.PaddedBigBytes(common.Big1, 32), nil } - return math.PaddedBigBytes(common.Big0, 32) + return math.PaddedBigBytes(common.Big0, 32), nil case BytesTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } - return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()) + if reflectValue.Type() != reflect.TypeOf([]byte{}) { + return []byte{}, errors.New("Bytes type is neither slice nor array") + } + return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil case FixedBytesTy, FunctionTy: if reflectValue.Kind() == reflect.Array { reflectValue = mustArrayToByteSlice(reflectValue) } - return common.RightPadBytes(reflectValue.Bytes(), 32) + return common.RightPadBytes(reflectValue.Bytes(), 32), nil default: - panic("abi: fatal error") + return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T) } } -// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation +// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation. func packNum(value reflect.Value) []byte { switch kind := value.Kind(); kind { case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return U256(new(big.Int).SetUint64(value.Uint())) + return math.U256Bytes(new(big.Int).SetUint64(value.Uint())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return U256(big.NewInt(value.Int())) + return math.U256Bytes(big.NewInt(value.Int())) case reflect.Ptr: - return U256(new(big.Int).Set(value.Interface().(*big.Int))) + return math.U256Bytes(new(big.Int).Set(value.Interface().(*big.Int))) default: panic("abi: fatal error") } - } diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index cf649b4807..5c7cb1cc1a 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -18,623 +18,51 @@ package abi import ( "bytes" + "encoding/hex" + "fmt" "math" "math/big" "reflect" + "strconv" "strings" "testing" "github.com/ethereum/go-ethereum/common" ) +// TestPack tests the general pack/unpack tests in packing_test.go func TestPack(t *testing.T) { - for i, test := range []struct { - typ string - components []ArgumentMarshaling - input interface{} - output []byte - }{ - { - "uint8", - nil, - uint8(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint8[]", - nil, - []uint8{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint16", - nil, - uint16(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint16[]", - nil, - []uint16{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint32", - nil, - uint32(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint32[]", - nil, - []uint32{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint64", - nil, - uint64(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint64[]", - nil, - []uint64{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint256", - nil, - big.NewInt(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "uint256[]", - nil, - []*big.Int{big.NewInt(1), big.NewInt(2)}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int8", - nil, - int8(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int8[]", - nil, - []int8{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int16", - nil, - int16(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int16[]", - nil, - []int16{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int32", - nil, - int32(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int32[]", - nil, - []int32{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int64", - nil, - int64(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int64[]", - nil, - []int64{1, 2}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int256", - nil, - big.NewInt(2), - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "int256[]", - nil, - []*big.Int{big.NewInt(1), big.NewInt(2)}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"), - }, - { - "bytes1", - nil, - [1]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes2", - nil, - [2]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes3", - nil, - [3]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes4", - nil, - [4]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes5", - nil, - [5]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes6", - nil, - [6]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes7", - nil, - [7]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes8", - nil, - [8]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes9", - nil, - [9]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes10", - nil, - [10]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes11", - nil, - [11]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes12", - nil, - [12]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes13", - nil, - [13]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes14", - nil, - [14]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes15", - nil, - [15]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes16", - nil, - [16]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes17", - nil, - [17]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes18", - nil, - [18]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes19", - nil, - [19]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes20", - nil, - [20]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes21", - nil, - [21]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes22", - nil, - [22]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes23", - nil, - [23]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes24", - nil, - [24]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes25", - nil, - [25]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes26", - nil, - [26]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes27", - nil, - [27]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes28", - nil, - [28]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes29", - nil, - [29]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes30", - nil, - [30]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes31", - nil, - [31]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "bytes32", - nil, - [32]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "uint32[2][3][4]", - nil, - [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"), - }, - { - "address[]", - nil, - []common.Address{{1}, {2}}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"), - }, - { - "bytes32[]", - nil, - []common.Hash{{1}, {2}}, - common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"), - }, - { - "function", - nil, - [24]byte{1}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, - { - "string", - nil, - "foobar", - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"), - }, - { - "string[]", - nil, - []string{"hello", "foobar"}, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 - "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 - "0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1 - "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 - "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] - "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 - "666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1] - }, - { - "string[2]", - nil, - []string{"hello", "foobar"}, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0 - "0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1 - "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 - "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] - "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 - "666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1] - }, - { - "bytes32[][]", - nil, - [][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}}, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 - "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 - "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 - "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 - "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] - "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] - "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 - "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] - "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] - "0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2] - }, - - { - "bytes32[][2]", - nil, - [][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}}, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 - "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 - "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 - "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] - "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] - "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 - "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] - "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] - "0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2] - }, - - { - "bytes32[3][2]", - nil, - [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}, - common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] - "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] - "0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2] - "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] - "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] - "0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2] - }, - { - // static tuple - "tuple", - []ArgumentMarshaling{ - {Name: "a", Type: "int64"}, - {Name: "b", Type: "int256"}, - {Name: "c", Type: "int256"}, - {Name: "d", Type: "bool"}, - {Name: "e", Type: "bytes32[3][2]"}, - }, - struct { - A int64 - B *big.Int - C *big.Int - D bool - E [][]common.Hash - }{1, big.NewInt(1), big.NewInt(-1), true, [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}}, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001" + // struct[a] - "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c] - "0000000000000000000000000000000000000000000000000000000000000001" + // struct[d] - "0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0] - "0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1] - "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2] - "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0] - "0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1] - "0500000000000000000000000000000000000000000000000000000000000000"), // struct[e] array[1][2] - }, - { - // dynamic tuple - "tuple", - []ArgumentMarshaling{ - {Name: "a", Type: "string"}, - {Name: "b", Type: "int64"}, - {Name: "c", Type: "bytes"}, - {Name: "d", Type: "string[]"}, - {Name: "e", Type: "int256[]"}, - {Name: "f", Type: "address[]"}, - }, - struct { - FieldA string `abi:"a"` // Test whether abi tag works - FieldB int64 `abi:"b"` - C []byte - D []string - E []*big.Int - F []common.Address - }{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}}, - common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset - "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] - "0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset - "0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset - "0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset - "0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset - "0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length - "666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar" - "0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length - "0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1} - "0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length - "0000000000000000000000000000000000000000000000000000000000000040" + // foo offset - "0000000000000000000000000000000000000000000000000000000000000080" + // bar offset - "0000000000000000000000000000000000000000000000000000000000000003" + // foo length - "666f6f0000000000000000000000000000000000000000000000000000000000" + // foo - "0000000000000000000000000000000000000000000000000000000000000003" + // bar offset - "6261720000000000000000000000000000000000000000000000000000000000" + // bar - "0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length - "0000000000000000000000000000000000000000000000000000000000000001" + // 1 - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1 - "0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length - "0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1} - "0000000000000000000000000200000000000000000000000000000000000000"), // common.Address{2} - }, - { - // nested tuple - "tuple", - []ArgumentMarshaling{ - {Name: "a", Type: "tuple", Components: []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256[]"}}}, - {Name: "b", Type: "int256[]"}, - }, - struct { - A struct { - FieldA *big.Int `abi:"a"` - B []*big.Int - } - B []*big.Int - }{ - A: struct { - FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple - B []*big.Int - }{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(0)}}, - B: []*big.Int{big.NewInt(1), big.NewInt(0)}}, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // a offset - "00000000000000000000000000000000000000000000000000000000000000e0" + // b offset - "0000000000000000000000000000000000000000000000000000000000000001" + // a.a value - "0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset - "0000000000000000000000000000000000000000000000000000000000000002" + // a.b length - "0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value - "0000000000000000000000000000000000000000000000000000000000000000" + // a.b[1] value - "0000000000000000000000000000000000000000000000000000000000000002" + // b length - "0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value - "0000000000000000000000000000000000000000000000000000000000000000"), // b[1] value - }, - { - // tuple slice - "tuple[]", - []ArgumentMarshaling{ - {Name: "a", Type: "int256"}, - {Name: "b", Type: "int256[]"}, - }, - []struct { - A *big.Int - B []*big.Int - }{ - {big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(0)}}, - {big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}}, - }, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // tuple length - "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset - "00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A - "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset - "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length - "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value - "0000000000000000000000000000000000000000000000000000000000000000" + // tuple[0].B[1] value - "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A - "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset - "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length - "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].B[1] value - }, - { - // static tuple array - "tuple[2]", - []ArgumentMarshaling{ - {Name: "a", Type: "int256"}, - {Name: "b", Type: "int256"}, - }, - [2]struct { - A *big.Int - B *big.Int - }{ - {big.NewInt(-1), big.NewInt(1)}, - {big.NewInt(1), big.NewInt(-1)}, - }, - common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a - "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b - "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].b - }, - { - // dynamic tuple array - "tuple[2]", - []ArgumentMarshaling{ - {Name: "a", Type: "int256[]"}, - }, - [2]struct { - A []*big.Int - }{ - {[]*big.Int{big.NewInt(-1), big.NewInt(1)}}, - {[]*big.Int{big.NewInt(1), big.NewInt(-1)}}, - }, - common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset - "00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset - "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset - "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0] - "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1] - "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset - "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length - "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0] - "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1] - }, - } { - typ, err := NewType(test.typ, "", test.components) - if err != nil { - t.Fatalf("%v failed. Unexpected parse error: %v", i, err) - } - output, err := typ.pack(reflect.ValueOf(test.input)) - if err != nil { - t.Fatalf("%v failed. Unexpected pack error: %v", i, err) - } - - if !bytes.Equal(output, test.output) { - t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output) - } + for i, test := range packUnpackTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + encb, err := hex.DecodeString(test.packed) + if err != nil { + t.Fatalf("invalid hex %s: %v", test.packed, err) + } + inDef := fmt.Sprintf(`[{ "name" : "method", "type": "function", "inputs": %s}]`, test.def) + inAbi, err := JSON(strings.NewReader(inDef)) + if err != nil { + t.Fatalf("invalid ABI definition %s, %v", inDef, err) + } + var packed []byte + packed, err = inAbi.Pack("method", test.unpacked) + + if err != nil { + t.Fatalf("test %d (%v) failed: %v", i, test.def, err) + } + if !reflect.DeepEqual(packed[4:], encb) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, encb, packed[4:]) + } + }) } } func TestMethodPack(t *testing.T) { - abi, err := JSON(strings.NewReader(jsondata2)) + abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) } - sig := abi.Methods["slice"].ID() + sig := abi.Methods["slice"].ID sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) @@ -648,7 +76,7 @@ func TestMethodPack(t *testing.T) { } var addrA, addrB = common.Address{1}, common.Address{2} - sig = abi.Methods["sliceAddress"].ID() + sig = abi.Methods["sliceAddress"].ID sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) sig = append(sig, common.LeftPadBytes(addrA[:], 32)...) @@ -663,7 +91,7 @@ func TestMethodPack(t *testing.T) { } var addrC, addrD = common.Address{3}, common.Address{4} - sig = abi.Methods["sliceMultiAddress"].ID() + sig = abi.Methods["sliceMultiAddress"].ID sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) @@ -681,7 +109,7 @@ func TestMethodPack(t *testing.T) { t.Errorf("expected %x got %x", sig, packed) } - sig = abi.Methods["slice256"].ID() + sig = abi.Methods["slice256"].ID sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) @@ -695,7 +123,7 @@ func TestMethodPack(t *testing.T) { } a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}} - sig = abi.Methods["nestedArray"].ID() + sig = abi.Methods["nestedArray"].ID sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...) @@ -712,7 +140,7 @@ func TestMethodPack(t *testing.T) { t.Errorf("expected %x got %x", sig, packed) } - sig = abi.Methods["nestedArray2"].ID() + sig = abi.Methods["nestedArray2"].ID sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...) @@ -728,7 +156,7 @@ func TestMethodPack(t *testing.T) { t.Errorf("expected %x got %x", sig, packed) } - sig = abi.Methods["nestedSlice"].ID() + sig = abi.Methods["nestedSlice"].ID sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...) sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...) diff --git a/accounts/abi/packing_test.go b/accounts/abi/packing_test.go new file mode 100644 index 0000000000..eae3b0df20 --- /dev/null +++ b/accounts/abi/packing_test.go @@ -0,0 +1,990 @@ +// 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 . + +package abi + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type packUnpackTest struct { + def string + unpacked interface{} + packed string +} + +var packUnpackTests = []packUnpackTest{ + // Booleans + { + def: `[{ "type": "bool" }]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: true, + }, + { + def: `[{ "type": "bool" }]`, + packed: "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: false, + }, + // Integers + { + def: `[{ "type": "uint8" }]`, + unpacked: uint8(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{ "type": "uint8[]" }]`, + unpacked: []uint8{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{ "type": "uint16" }]`, + unpacked: uint16(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{ "type": "uint16[]" }]`, + unpacked: []uint16{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint17"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: big.NewInt(1), + }, + { + def: `[{"type": "uint32"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: uint32(1), + }, + { + def: `[{"type": "uint32[]"}]`, + unpacked: []uint32{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint64"}]`, + unpacked: uint64(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint64[]"}]`, + unpacked: []uint64{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint256"}]`, + unpacked: big.NewInt(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "uint256[]"}]`, + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int8"}]`, + unpacked: int8(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int8[]"}]`, + unpacked: []int8{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int16"}]`, + unpacked: int16(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int16[]"}]`, + unpacked: []int16{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int17"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: big.NewInt(1), + }, + { + def: `[{"type": "int32"}]`, + unpacked: int32(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int32"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: int32(1), + }, + { + def: `[{"type": "int32[]"}]`, + unpacked: []int32{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int64"}]`, + unpacked: int64(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int64[]"}]`, + unpacked: []int64{1, 2}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int256"}]`, + unpacked: big.NewInt(2), + packed: "0000000000000000000000000000000000000000000000000000000000000002", + }, + { + def: `[{"type": "int256"}]`, + packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + unpacked: big.NewInt(-1), + }, + { + def: `[{"type": "int256[]"}]`, + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + }, + // Address + { + def: `[{"type": "address"}]`, + packed: "0000000000000000000000000100000000000000000000000000000000000000", + unpacked: common.Address{1}, + }, + { + def: `[{"type": "address[]"}]`, + unpacked: []common.Address{{1}, {2}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000100000000000000000000000000000000000000" + + "0000000000000000000000000200000000000000000000000000000000000000", + }, + // Bytes + { + def: `[{"type": "bytes1"}]`, + unpacked: [1]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes2"}]`, + unpacked: [2]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes3"}]`, + unpacked: [3]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes4"}]`, + unpacked: [4]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes5"}]`, + unpacked: [5]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes6"}]`, + unpacked: [6]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes7"}]`, + unpacked: [7]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes8"}]`, + unpacked: [8]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes9"}]`, + unpacked: [9]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes10"}]`, + unpacked: [10]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes11"}]`, + unpacked: [11]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes12"}]`, + unpacked: [12]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes13"}]`, + unpacked: [13]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes14"}]`, + unpacked: [14]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes15"}]`, + unpacked: [15]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes16"}]`, + unpacked: [16]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes17"}]`, + unpacked: [17]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes18"}]`, + unpacked: [18]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes19"}]`, + unpacked: [19]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes20"}]`, + unpacked: [20]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes21"}]`, + unpacked: [21]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes22"}]`, + unpacked: [22]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes23"}]`, + unpacked: [23]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes24"}]`, + unpacked: [24]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes25"}]`, + unpacked: [25]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes26"}]`, + unpacked: [26]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes27"}]`, + unpacked: [27]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes28"}]`, + unpacked: [28]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes29"}]`, + unpacked: [29]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes30"}]`, + unpacked: [30]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes31"}]`, + unpacked: [31]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes32"}]`, + unpacked: [32]byte{1}, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "bytes32"}]`, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + { + def: `[{"type": "bytes"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), + }, + { + def: `[{"type": "bytes32"}]`, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + // Functions + { + def: `[{"type": "function"}]`, + packed: "0100000000000000000000000000000000000000000000000000000000000000", + unpacked: [24]byte{1}, + }, + // Slice and Array + { + def: `[{"type": "uint8[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint8{1, 2}, + }, + { + def: `[{"type": "uint8[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: []uint8{}, + }, + { + def: `[{"type": "uint256[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: []*big.Int{}, + }, + { + def: `[{"type": "uint8[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint8{1, 2}, + }, + { + def: `[{"type": "int8[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int8{1, 2}, + }, + { + def: `[{"type": "int16[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []int16{1, 2}, + }, + { + def: `[{"type": "int16[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int16{1, 2}, + }, + { + def: `[{"type": "int32[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []int32{1, 2}, + }, + { + def: `[{"type": "int32[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int32{1, 2}, + }, + { + def: `[{"type": "int64[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []int64{1, 2}, + }, + { + def: `[{"type": "int64[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]int64{1, 2}, + }, + { + def: `[{"type": "int256[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "int256[3]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + // multi dimensional, if these pass, all types that don't require length prefix should pass + { + def: `[{"type": "uint8[][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: [][]uint8{}, + }, + { + def: `[{"type": "uint8[][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [][]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000a0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + unpacked: [][]uint8{{1, 2}, {1, 2, 3}}, + }, + { + def: `[{"type": "uint8[2][2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint8[][2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000060" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: [2][]uint8{{}, {}}, + }, + { + def: `[{"type": "uint8[][2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: [2][]uint8{{1}, {1}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000000", + unpacked: [][2]uint8{}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [][2]uint8{{1, 2}}, + }, + { + def: `[{"type": "uint8[2][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [][2]uint8{{1, 2}, {1, 2}}, + }, + { + def: `[{"type": "uint16[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint16{1, 2}, + }, + { + def: `[{"type": "uint16[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint16{1, 2}, + }, + { + def: `[{"type": "uint32[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint32{1, 2}, + }, + { + def: `[{"type": "uint32[2][3][4]"}]`, + unpacked: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "0000000000000000000000000000000000000000000000000000000000000004" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "0000000000000000000000000000000000000000000000000000000000000007" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "0000000000000000000000000000000000000000000000000000000000000009" + + "000000000000000000000000000000000000000000000000000000000000000a" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "000000000000000000000000000000000000000000000000000000000000000c" + + "000000000000000000000000000000000000000000000000000000000000000d" + + "000000000000000000000000000000000000000000000000000000000000000e" + + "000000000000000000000000000000000000000000000000000000000000000f" + + "0000000000000000000000000000000000000000000000000000000000000010" + + "0000000000000000000000000000000000000000000000000000000000000011" + + "0000000000000000000000000000000000000000000000000000000000000012" + + "0000000000000000000000000000000000000000000000000000000000000013" + + "0000000000000000000000000000000000000000000000000000000000000014" + + "0000000000000000000000000000000000000000000000000000000000000015" + + "0000000000000000000000000000000000000000000000000000000000000016" + + "0000000000000000000000000000000000000000000000000000000000000017" + + "0000000000000000000000000000000000000000000000000000000000000018", + }, + + { + def: `[{"type": "bytes32[]"}]`, + unpacked: [][32]byte{{1}, {2}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0100000000000000000000000000000000000000000000000000000000000000" + + "0200000000000000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "uint32[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint32{1, 2}, + }, + { + def: `[{"type": "uint64[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []uint64{1, 2}, + }, + { + def: `[{"type": "uint64[2]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: [2]uint64{1, 2}, + }, + { + def: `[{"type": "uint256[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: []*big.Int{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "uint256[3]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000003", + unpacked: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + { + def: `[{"type": "string[4]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "00000000000000000000000000000000000000000000000000000000000000c0" + + "0000000000000000000000000000000000000000000000000000000000000100" + + "0000000000000000000000000000000000000000000000000000000000000140" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "48656c6c6f000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000005" + + "576f726c64000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "476f2d657468657265756d000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "457468657265756d000000000000000000000000000000000000000000000000", + unpacked: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"}, + }, + { + def: `[{"type": "string[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000008" + + "457468657265756d000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000b" + + "676f2d657468657265756d000000000000000000000000000000000000000000", + unpacked: []string{"Ethereum", "go-ethereum"}, + }, + { + def: `[{"type": "bytes[]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "0000000000000000000000000000000000000000000000000000000000000080" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "f0f0f00000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000003" + + "f0f0f00000000000000000000000000000000000000000000000000000000000", + unpacked: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}}, + }, + { + def: `[{"type": "uint256[2][][]"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000040" + + "00000000000000000000000000000000000000000000000000000000000000e0" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000000c8" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000003e8" + + "0000000000000000000000000000000000000000000000000000000000000002" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000000c8" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "00000000000000000000000000000000000000000000000000000000000003e8", + unpacked: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}}, + }, + // struct outputs + { + def: `[{"components": [{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: struct { + Int1 *big.Int + Int2 *big.Int + }{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"components": [{"name":"int_one","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"components": [{"name":"int__one","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"components": [{"name":"int_one_","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001", + unpacked: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"components": [{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}], "type":"tuple"}]`, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002", + unpacked: struct { + IntOne *big.Int + Intone *big.Int + }{big.NewInt(1), big.NewInt(2)}, + }, + { + def: `[{"type": "string"}]`, + unpacked: "foobar", + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000006" + + "666f6f6261720000000000000000000000000000000000000000000000000000", + }, + { + def: `[{"type": "string[]"}]`, + unpacked: []string{"hello", "foobar"}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 + "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] + "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 + "666f6f6261720000000000000000000000000000000000000000000000000000", // str[1] + }, + { + def: `[{"type": "string[2]"}]`, + unpacked: [2]string{"hello", "foobar"}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0 + "0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1 + "0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5 + "68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0] + "0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6 + "666f6f6261720000000000000000000000000000000000000000000000000000", // str[1] + }, + { + def: `[{"type": "bytes32[][]"}]`, + unpacked: [][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2 + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 + "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // array[1][2] + }, + { + def: `[{"type": "bytes32[][2]"}]`, + unpacked: [2][][32]byte{{{1}, {2}}, {{3}, {4}, {5}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0 + "00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1 + "0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2 + "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3 + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // array[1][2] + }, + { + def: `[{"type": "bytes32[3][2]"}]`, + unpacked: [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}}, + packed: "0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1] + "0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2] + "0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // array[1][2] + }, + { + // static tuple + def: `[{"components": [{"name":"a","type":"int64"}, + {"name":"b","type":"int256"}, + {"name":"c","type":"int256"}, + {"name":"d","type":"bool"}, + {"name":"e","type":"bytes32[3][2]"}], "type":"tuple"}]`, + unpacked: struct { + A int64 + B *big.Int + C *big.Int + D bool + E [2][3][32]byte + }{1, big.NewInt(1), big.NewInt(-1), true, [2][3][32]byte{{{1}, {2}, {3}}, {{3}, {4}, {5}}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000001" + // struct[a] + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c] + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[d] + "0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0] + "0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1] + "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2] + "0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0] + "0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1] + "0500000000000000000000000000000000000000000000000000000000000000", // struct[e] array[1][2] + }, + { + def: `[{"components": [{"name":"a","type":"string"}, + {"name":"b","type":"int64"}, + {"name":"c","type":"bytes"}, + {"name":"d","type":"string[]"}, + {"name":"e","type":"int256[]"}, + {"name":"f","type":"address[]"}], "type":"tuple"}]`, + unpacked: struct { + A string + B int64 + C []byte + D []string + E []*big.Int + F []common.Address + }{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[b] + "0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset + "0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset + "0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset + "0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset + "0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length + "666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar" + "0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length + "0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1} + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length + "0000000000000000000000000000000000000000000000000000000000000040" + // foo offset + "0000000000000000000000000000000000000000000000000000000000000080" + // bar offset + "0000000000000000000000000000000000000000000000000000000000000003" + // foo length + "666f6f0000000000000000000000000000000000000000000000000000000000" + // foo + "0000000000000000000000000000000000000000000000000000000000000003" + // bar offset + "6261720000000000000000000000000000000000000000000000000000000000" + // bar + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length + "0000000000000000000000000000000000000000000000000000000000000001" + // 1 + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1 + "0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length + "0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1} + "0000000000000000000000000200000000000000000000000000000000000000", // common.Address{2} + }, + { + def: `[{"components": [{ "type": "tuple","components": [{"name": "a","type": "uint256"}, + {"name": "b","type": "uint256[]"}], + "name": "a","type": "tuple"}, + {"name": "b","type": "uint256[]"}], "type": "tuple"}]`, + unpacked: struct { + A struct { + A *big.Int + B []*big.Int + } + B []*big.Int + }{ + A: struct { + A *big.Int + B []*big.Int + }{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(2)}}, + B: []*big.Int{big.NewInt(1), big.NewInt(2)}}, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + // struct a + "0000000000000000000000000000000000000000000000000000000000000040" + // a offset + "00000000000000000000000000000000000000000000000000000000000000e0" + // b offset + "0000000000000000000000000000000000000000000000000000000000000001" + // a.a value + "0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset + "0000000000000000000000000000000000000000000000000000000000000002" + // a.b length + "0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value + "0000000000000000000000000000000000000000000000000000000000000002" + // a.b[1] value + "0000000000000000000000000000000000000000000000000000000000000002" + // b length + "0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value + "0000000000000000000000000000000000000000000000000000000000000002", // b[1] value + }, + + { + def: `[{"components": [{"name": "a","type": "int256"}, + {"name": "b","type": "int256[]"}], + "name": "a","type": "tuple[]"}]`, + unpacked: []struct { + A *big.Int + B []*big.Int + }{ + {big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(3)}}, + {big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}}, + }, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple length + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset + "00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value + "0000000000000000000000000000000000000000000000000000000000000003" + // tuple[0].B[1] value + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].B[1] value + }, + { + def: `[{"components": [{"name": "a","type": "int256"}, + {"name": "b","type": "int256"}], + "name": "a","type": "tuple[2]"}]`, + unpacked: [2]struct { + A *big.Int + B *big.Int + }{ + {big.NewInt(-1), big.NewInt(1)}, + {big.NewInt(1), big.NewInt(-1)}, + }, + packed: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].b + }, + { + def: `[{"components": [{"name": "a","type": "int256[]"}], + "name": "a","type": "tuple[2]"}]`, + unpacked: [2]struct { + A []*big.Int + }{ + {[]*big.Int{big.NewInt(-1), big.NewInt(1)}}, + {[]*big.Int{big.NewInt(1), big.NewInt(-1)}}, + }, + packed: "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset + "00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset + "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0] + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1] + "0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset + "0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length + "0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0] + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // tuple[1].A[1] + }, +} diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 73ca8fa2bd..11248e0730 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -17,57 +17,74 @@ package abi import ( + "errors" "fmt" + "math/big" "reflect" "strings" ) -// indirect recursively dereferences the value until it either gets the value -// or finds a big.Int -func indirect(v reflect.Value) reflect.Value { - if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT { - return indirect(v.Elem()) +// ConvertType converts an interface of a runtime type into a interface of the +// given type +// e.g. turn +// var fields []reflect.StructField +// fields = append(fields, reflect.StructField{ +// Name: "X", +// Type: reflect.TypeOf(new(big.Int)), +// Tag: reflect.StructTag("json:\"" + "x" + "\""), +// } +// into +// type TupleT struct { X *big.Int } +func ConvertType(in interface{}, proto interface{}) interface{} { + protoType := reflect.TypeOf(proto) + if reflect.TypeOf(in).ConvertibleTo(protoType) { + return reflect.ValueOf(in).Convert(protoType).Interface() } - return v + // Use set as a last ditch effort + if err := set(reflect.ValueOf(proto), reflect.ValueOf(in)); err != nil { + panic(err) + } + return proto } -// indirectInterfaceOrPtr recursively dereferences the value until value is not interface. -func indirectInterfaceOrPtr(v reflect.Value) reflect.Value { - if (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.Elem().IsValid() { +// indirect recursively dereferences the value until it either gets the value +// or finds a big.Int +func indirect(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Ptr && v.Elem().Type() != reflect.TypeOf(big.Int{}) { return indirect(v.Elem()) } return v } -// reflectIntKind returns the reflect using the given size and +// reflectIntType returns the reflect using the given size and // unsignedness. -func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) { +func reflectIntType(unsigned bool, size int) reflect.Type { + if unsigned { + switch size { + case 8: + return reflect.TypeOf(uint8(0)) + case 16: + return reflect.TypeOf(uint16(0)) + case 32: + return reflect.TypeOf(uint32(0)) + case 64: + return reflect.TypeOf(uint64(0)) + } + } switch size { case 8: - if unsigned { - return reflect.Uint8, uint8T - } - return reflect.Int8, int8T + return reflect.TypeOf(int8(0)) case 16: - if unsigned { - return reflect.Uint16, uint16T - } - return reflect.Int16, int16T + return reflect.TypeOf(int16(0)) case 32: - if unsigned { - return reflect.Uint32, uint32T - } - return reflect.Int32, int32T + return reflect.TypeOf(int32(0)) case 64: - if unsigned { - return reflect.Uint64, uint64T - } - return reflect.Int64, int64T + return reflect.TypeOf(int64(0)) } - return reflect.Ptr, bigT + return reflect.TypeOf(&big.Int{}) } -// mustArrayToBytesSlice creates a new byte slice with the exact same size as value +// mustArrayToByteSlice creates a new byte slice with the exact same size as value // and copies the bytes in value to the new slice. func mustArrayToByteSlice(value reflect.Value) reflect.Value { slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) @@ -84,12 +101,16 @@ func set(dst, src reflect.Value) error { switch { case dstType.Kind() == reflect.Interface && dst.Elem().IsValid(): return set(dst.Elem(), src) - case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT: + case dstType.Kind() == reflect.Ptr && dstType.Elem() != reflect.TypeOf(big.Int{}): return set(dst.Elem(), src) case srcType.AssignableTo(dstType) && dst.CanSet(): dst.Set(src) - case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice: + case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice && dst.CanSet(): return setSlice(dst, src) + case dstType.Kind() == reflect.Array: + return setArray(dst, src) + case dstType.Kind() == reflect.Struct: + return setStruct(dst, src) default: return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type()) } @@ -98,38 +119,59 @@ func set(dst, src reflect.Value) error { // setSlice attempts to assign src to dst when slices are not assignable by default // e.g. src: [][]byte -> dst: [][15]byte +// setSlice ignores if we cannot copy all of src' elements. func setSlice(dst, src reflect.Value) error { slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len()) for i := 0; i < src.Len(); i++ { - v := src.Index(i) - reflect.Copy(slice.Index(i), v) + if src.Index(i).Kind() == reflect.Struct { + if err := set(slice.Index(i), src.Index(i)); err != nil { + return err + } + } else { + // e.g. [][32]uint8 to []common.Hash + if err := set(slice.Index(i), src.Index(i)); err != nil { + return err + } + } } - - dst.Set(slice) - return nil + if dst.CanSet() { + dst.Set(slice) + return nil + } + return errors.New("Cannot set slice, destination not settable") } -// requireAssignable assures that `dest` is a pointer and it's not an interface. -func requireAssignable(dst, src reflect.Value) error { - if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface { - return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type()) +func setArray(dst, src reflect.Value) error { + if src.Kind() == reflect.Ptr { + return set(dst, indirect(src)) } - return nil + array := reflect.New(dst.Type()).Elem() + min := src.Len() + if src.Len() > dst.Len() { + min = dst.Len() + } + for i := 0; i < min; i++ { + if err := set(array.Index(i), src.Index(i)); err != nil { + return err + } + } + if dst.CanSet() { + dst.Set(array) + return nil + } + return errors.New("Cannot set array, destination not settable") } -// requireUnpackKind verifies preconditions for unpacking `args` into `kind` -func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind, - args Arguments) error { - - switch k { - case reflect.Struct: - case reflect.Slice, reflect.Array: - if minLen := args.LengthNonIndexed(); v.Len() < minLen { - return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d", - minLen, v.Len()) +func setStruct(dst, src reflect.Value) error { + for i := 0; i < src.NumField(); i++ { + srcField := src.Field(i) + dstField := dst.Field(i) + if !dstField.IsValid() || !srcField.IsValid() { + return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) + } + if err := set(dstField, srcField); err != nil { + return err } - default: - return fmt.Errorf("abi: cannot unmarshal tuple into %v", t) } return nil } @@ -156,9 +198,8 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri continue } // skip fields that have no abi:"" tag. - var ok bool - var tagName string - if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok { + tagName, ok := typ.Field(i).Tag.Lookup("abi") + if !ok { continue } // check if tag is empty. diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index c425e6e54b..cf13a79da8 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -17,6 +17,7 @@ package abi import ( + "math/big" "reflect" "testing" ) @@ -189,3 +190,72 @@ func TestReflectNameToStruct(t *testing.T) { }) } } + +func TestConvertType(t *testing.T) { + // Test Basic Struct + type T struct { + X *big.Int + Y *big.Int + } + // Create on-the-fly structure + var fields []reflect.StructField + fields = append(fields, reflect.StructField{ + Name: "X", + Type: reflect.TypeOf(new(big.Int)), + Tag: "json:\"" + "x" + "\"", + }) + fields = append(fields, reflect.StructField{ + Name: "Y", + Type: reflect.TypeOf(new(big.Int)), + Tag: "json:\"" + "y" + "\"", + }) + val := reflect.New(reflect.StructOf(fields)) + val.Elem().Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val.Elem().Field(1).Set(reflect.ValueOf(big.NewInt(2))) + // ConvertType + out := *ConvertType(val.Interface(), new(T)).(*T) + if out.X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.X, big.NewInt(1)) + } + if out.Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out.Y, big.NewInt(2)) + } + // Slice Type + val2 := reflect.MakeSlice(reflect.SliceOf(reflect.StructOf(fields)), 2, 2) + val2.Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val2.Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val2.Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val2.Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out2 := *ConvertType(val2.Interface(), new([]T)).(*[]T) + if out2[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + if out2[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[0].X, big.NewInt(1)) + } + if out2[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out2[1].Y, big.NewInt(2)) + } + // Array Type + val3 := reflect.New(reflect.ArrayOf(2, reflect.StructOf(fields))) + val3.Elem().Index(0).Field(0).Set(reflect.ValueOf(big.NewInt(1))) + val3.Elem().Index(0).Field(1).Set(reflect.ValueOf(big.NewInt(2))) + val3.Elem().Index(1).Field(0).Set(reflect.ValueOf(big.NewInt(3))) + val3.Elem().Index(1).Field(1).Set(reflect.ValueOf(big.NewInt(4))) + out3 := *ConvertType(val3.Interface(), new([2]T)).(*[2]T) + if out3[0].X.Cmp(big.NewInt(1)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[0].Y.Cmp(big.NewInt(2)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } + if out3[1].X.Cmp(big.NewInt(3)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[0].X, big.NewInt(1)) + } + if out3[1].Y.Cmp(big.NewInt(4)) != 0 { + t.Errorf("ConvertType failed, got %v want %v", out3[1].Y, big.NewInt(2)) + } +} diff --git a/accounts/abi/bind/topics.go b/accounts/abi/topics.go similarity index 83% rename from accounts/abi/bind/topics.go rename to accounts/abi/topics.go index 7b64f03347..360df7d5e8 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/topics.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package bind +package abi import ( "encoding/binary" @@ -23,13 +23,12 @@ import ( "math/big" "reflect" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) -// makeTopics converts a filter query argument list into a filter topic set. -func makeTopics(query ...[]interface{}) ([][]common.Hash, error) { +// MakeTopics converts a filter query argument list into a filter topic set. +func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { topics := make([][]common.Hash, len(query)) for i, filter := range query { for _, rule := range filter { @@ -103,7 +102,7 @@ func genIntType(rule int64, size uint) []byte { var topic [common.HashLength]byte if rule < 0 { // if a rule is negative, we need to put it into two's complement. - // extended to common.Hashlength bytes. + // extended to common.HashLength bytes. topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} } for i := uint(0); i < size; i++ { @@ -112,19 +111,19 @@ func genIntType(rule int64, size uint) []byte { return topic[:] } -// parseTopics converts the indexed topic fields into actual log field values. -func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error { +// ParseTopics converts the indexed topic fields into actual log field values. +func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error { return parseTopicWithSetter(fields, topics, - func(arg abi.Argument, reconstr interface{}) { - field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name)) + func(arg Argument, reconstr interface{}) { + field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(arg.Name)) field.Set(reflect.ValueOf(reconstr)) }) } -// parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs -func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error { +// ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs. +func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error { return parseTopicWithSetter(fields, topics, - func(arg abi.Argument, reconstr interface{}) { + func(arg Argument, reconstr interface{}) { out[arg.Name] = reconstr }) } @@ -134,7 +133,7 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics // // Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 // hashes as the topic value! -func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter func(abi.Argument, interface{})) error { +func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Argument, interface{})) error { // Sanity check that the fields and topics match up if len(fields) != len(topics) { return errors.New("topic/field count mismatch") @@ -146,13 +145,13 @@ func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter fun } var reconstr interface{} switch arg.Type.T { - case abi.TupleTy: + case TupleTy: return errors.New("tuple type in topic reconstruction") - case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy: + case StringTy, BytesTy, SliceTy, ArrayTy: // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash reconstr = topics[i] - case abi.FunctionTy: + case FunctionTy: if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 { return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes()) } @@ -161,7 +160,7 @@ func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter fun reconstr = tmp default: var err error - reconstr, err = abi.ToGoType(0, arg.Type, topics[i].Bytes()) + reconstr, err = toGoType(0, arg.Type, topics[i].Bytes()) if err != nil { return err } diff --git a/accounts/abi/bind/topics_test.go b/accounts/abi/topics_test.go similarity index 67% rename from accounts/abi/bind/topics_test.go rename to accounts/abi/topics_test.go index 627e43316e..4a539a7116 100644 --- a/accounts/abi/bind/topics_test.go +++ b/accounts/abi/topics_test.go @@ -14,14 +14,13 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package bind +package abi import ( "math/big" "reflect" "testing" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -119,7 +118,7 @@ func TestMakeTopics(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := makeTopics(tt.args.query...) + got, err := MakeTopics(tt.args.query...) if (err != nil) != tt.wantErr { t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr) return @@ -135,7 +134,7 @@ type args struct { createObj func() interface{} resultObj func() interface{} resultMap func() map[string]interface{} - fields abi.Arguments + fields Arguments topics []common.Hash } @@ -149,6 +148,14 @@ type int256Struct struct { Int256Value *big.Int } +type hashStruct struct { + HashValue common.Hash +} + +type funcStruct struct { + FuncValue [24]byte +} + type topicTest struct { name string args args @@ -156,10 +163,12 @@ type topicTest struct { } func setupTopicsTests() []topicTest { - bytesType, _ := abi.NewType("bytes5", "", nil) - int8Type, _ := abi.NewType("int8", "", nil) - int256Type, _ := abi.NewType("int256", "", nil) - tupleType, _ := abi.NewType("tuple(int256,int8)", "", nil) + bytesType, _ := NewType("bytes5", "", nil) + int8Type, _ := NewType("int8", "", nil) + int256Type, _ := NewType("int256", "", nil) + tupleType, _ := NewType("tuple(int256,int8)", "", nil) + stringType, _ := NewType("string", "", nil) + funcType, _ := NewType("function", "", nil) tests := []topicTest{ { @@ -170,7 +179,7 @@ func setupTopicsTests() []topicTest { resultMap: func() map[string]interface{} { return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}} }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "staticBytes", Type: bytesType, Indexed: true, @@ -189,7 +198,7 @@ func setupTopicsTests() []topicTest { resultMap: func() map[string]interface{} { return map[string]interface{}{"int8Value": int8(-1)} }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "int8Value", Type: int8Type, Indexed: true, @@ -209,7 +218,7 @@ func setupTopicsTests() []topicTest { resultMap: func() map[string]interface{} { return map[string]interface{}{"int256Value": big.NewInt(-1)} }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "int256Value", Type: int256Type, Indexed: true, @@ -222,12 +231,55 @@ func setupTopicsTests() []topicTest { wantErr: false, }, { - name: "tuple(int256, int8)", + name: "hash type", + args: args{ + createObj: func() interface{} { return &hashStruct{} }, + resultObj: func() interface{} { return &hashStruct{crypto.Keccak256Hash([]byte("stringtopic"))} }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"hashValue": crypto.Keccak256Hash([]byte("stringtopic"))} + }, + fields: Arguments{Argument{ + Name: "hashValue", + Type: stringType, + Indexed: true, + }}, + topics: []common.Hash{ + crypto.Keccak256Hash([]byte("stringtopic")), + }, + }, + wantErr: false, + }, + { + name: "function type", + args: args{ + createObj: func() interface{} { return &funcStruct{} }, + resultObj: func() interface{} { + return &funcStruct{[24]byte{255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}} + }, + resultMap: func() map[string]interface{} { + return map[string]interface{}{"funcValue": [24]byte{255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}} + }, + fields: Arguments{Argument{ + Name: "funcValue", + Type: funcType, + Indexed: true, + }}, + topics: []common.Hash{ + {0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: false, + }, + { + name: "error on topic/field count mismatch", args: args{ createObj: func() interface{} { return nil }, resultObj: func() interface{} { return nil }, resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, - fields: abi.Arguments{abi.Argument{ + fields: Arguments{Argument{ Name: "tupletype", Type: tupleType, Indexed: true, @@ -236,6 +288,59 @@ func setupTopicsTests() []topicTest { }, wantErr: true, }, + { + name: "error on unindexed arguments", + args: args{ + createObj: func() interface{} { return &int256Struct{} }, + resultObj: func() interface{} { return &int256Struct{} }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "int256Value", + Type: int256Type, + Indexed: false, + }}, + topics: []common.Hash{ + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: true, + }, + { + name: "error on tuple in topic reconstruction", + args: args{ + createObj: func() interface{} { return &tupleType }, + resultObj: func() interface{} { return &tupleType }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: Arguments{Argument{ + Name: "tupletype", + Type: tupleType, + Indexed: true, + }}, + topics: []common.Hash{{0}}, + }, + wantErr: true, + }, + { + name: "error on improper encoded function", + args: args{ + createObj: func() interface{} { return &funcStruct{} }, + resultObj: func() interface{} { return &funcStruct{} }, + resultMap: func() map[string]interface{} { + return make(map[string]interface{}) + }, + fields: Arguments{Argument{ + Name: "funcValue", + Type: funcType, + Indexed: true, + }}, + topics: []common.Hash{ + {0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, + }, + }, + wantErr: true, + }, } return tests @@ -247,7 +352,7 @@ func TestParseTopics(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { createObj := tt.args.createObj() - if err := parseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { + if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr) } resultObj := tt.args.resultObj() @@ -264,7 +369,7 @@ func TestParseTopicsIntoMap(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { outMap := make(map[string]interface{}) - if err := parseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { + if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr) } resultMap := tt.args.resultMap() diff --git a/accounts/abi/type.go b/accounts/abi/type.go index baa29dd267..ffa3acafe9 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -23,6 +23,8 @@ import ( "regexp" "strconv" "strings" + + "github.com/ethereum/go-ethereum/common" ) // Type enumerator @@ -42,20 +44,19 @@ const ( FunctionTy ) -// Type is the reflection of the supported argument type +// Type is the reflection of the supported argument type. type Type struct { Elem *Type - Kind reflect.Kind - Type reflect.Type Size int T byte // Our own type checking stringKind string // holds the unparsed string for deriving signatures // Tuple relative fields - TupleRawName string // Raw struct name defined in source code, may be empty. - TupleElems []*Type // Type information of all tuple fields - TupleRawNames []string // Raw field name of all tuple fields + TupleRawName string // Raw struct name defined in source code, may be empty. + TupleElems []*Type // Type information of all tuple fields + TupleRawNames []string // Raw field name of all tuple fields + TupleType reflect.Type // Underlying struct of the tuple } var ( @@ -94,20 +95,16 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty if len(intz) == 0 { // is a slice typ.T = SliceTy - typ.Kind = reflect.Slice typ.Elem = &embeddedType - typ.Type = reflect.SliceOf(embeddedType.Type) typ.stringKind = embeddedType.stringKind + sliced } else if len(intz) == 1 { - // is a array + // is an array typ.T = ArrayTy - typ.Kind = reflect.Array typ.Elem = &embeddedType typ.Size, err = strconv.Atoi(intz[0]) if err != nil { return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) } - typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type) typ.stringKind = embeddedType.stringKind + sliced } else { return Type{}, fmt.Errorf("invalid formatting of array type") @@ -139,36 +136,24 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty // varType is the parsed abi type switch varType := parsedType[1]; varType { case "int": - typ.Kind, typ.Type = reflectIntKindAndType(false, varSize) typ.Size = varSize typ.T = IntTy case "uint": - typ.Kind, typ.Type = reflectIntKindAndType(true, varSize) typ.Size = varSize typ.T = UintTy case "bool": - typ.Kind = reflect.Bool typ.T = BoolTy - typ.Type = reflect.TypeOf(bool(false)) case "address": - typ.Kind = reflect.Array - typ.Type = addressT typ.Size = 20 typ.T = AddressTy case "string": - typ.Kind = reflect.String - typ.Type = reflect.TypeOf("") typ.T = StringTy case "bytes": if varSize == 0 { typ.T = BytesTy - typ.Kind = reflect.Slice - typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0))) } else { typ.T = FixedBytesTy - typ.Kind = reflect.Array typ.Size = varSize - typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0))) } case "tuple": var ( @@ -178,17 +163,20 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty expression string // canonical parameter expression ) expression += "(" + overloadedNames := make(map[string]string) for idx, c := range components { cType, err := NewType(c.Type, c.InternalType, c.Components) if err != nil { return Type{}, err } - if ToCamelCase(c.Name) == "" { - return Type{}, errors.New("abi: purely anonymous or underscored field is not supported") + fieldName, err := overloadedArgName(c.Name, overloadedNames) + if err != nil { + return Type{}, err } + overloadedNames[fieldName] = fieldName fields = append(fields, reflect.StructField{ - Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field. - Type: cType.Type, + Name: fieldName, // reflect.StructOf will panic for any exported field. + Type: cType.GetType(), Tag: reflect.StructTag("json:\"" + c.Name + "\""), }) elems = append(elems, &cType) @@ -199,8 +187,8 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty } } expression += ")" - typ.Kind = reflect.Struct - typ.Type = reflect.StructOf(fields) + + typ.TupleType = reflect.StructOf(fields) typ.TupleElems = elems typ.TupleRawNames = names typ.T = TupleTy @@ -217,10 +205,8 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty } case "function": - typ.Kind = reflect.Array typ.T = FunctionTy typ.Size = 24 - typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0))) default: return Type{}, fmt.Errorf("unsupported arg type: %s", t) } @@ -228,7 +214,57 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty return } -// String implements Stringer +// GetType returns the reflection type of the ABI type. +func (t Type) GetType() reflect.Type { + switch t.T { + case IntTy: + return reflectIntType(false, t.Size) + case UintTy: + return reflectIntType(true, t.Size) + case BoolTy: + return reflect.TypeOf(false) + case StringTy: + return reflect.TypeOf("") + case SliceTy: + return reflect.SliceOf(t.Elem.GetType()) + case ArrayTy: + return reflect.ArrayOf(t.Size, t.Elem.GetType()) + case TupleTy: + return t.TupleType + case AddressTy: + return reflect.TypeOf(common.Address{}) + case FixedBytesTy: + return reflect.ArrayOf(t.Size, reflect.TypeOf(byte(0))) + case BytesTy: + return reflect.SliceOf(reflect.TypeOf(byte(0))) + case HashTy: + // hashtype currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FixedPointTy: + // fixedpoint type currently not used + return reflect.ArrayOf(32, reflect.TypeOf(byte(0))) + case FunctionTy: + return reflect.ArrayOf(24, reflect.TypeOf(byte(0))) + default: + panic("Invalid type") + } +} + +func overloadedArgName(rawName string, names map[string]string) (string, error) { + fieldName := ToCamelCase(rawName) + if fieldName == "" { + return "", errors.New("abi: purely anonymous or underscored field is not supported") + } + // Handle overloaded fieldNames + _, ok := names[fieldName] + for idx := 0; ok; idx++ { + fieldName = fmt.Sprintf("%s%d", ToCamelCase(rawName), idx) + _, ok = names[fieldName] + } + return fieldName, nil +} + +// String implements Stringer. func (t Type) String() (out string) { return t.stringKind } @@ -310,7 +346,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { return append(ret, tail...), nil default: - return packElement(t, v), nil + return packElement(t, v) } } diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index a2c78dc2e0..8c3aedca6a 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -36,58 +36,58 @@ func TestTypeRegexp(t *testing.T) { components []ArgumentMarshaling kind Type }{ - {"bool", nil, Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}}, - {"bool[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}}, - {"bool[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}}, - {"bool[2][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}}, - {"bool[][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}}, - {"bool[][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}}, - {"bool[2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}}, - {"bool[2][][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}}, - {"bool[2][2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}}, - {"bool[][][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}}, - {"bool[][2][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}}, - {"int8", nil, Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}}, - {"int16", nil, Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}}, - {"int32", nil, Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}}, - {"int64", nil, Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}}, - {"int256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}}, - {"int8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, - {"int8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, - {"int16[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, - {"int16[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, - {"int32[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, - {"int32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, - {"int64[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, - {"int64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, - {"int256[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, - {"int256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, - {"uint8", nil, Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}}, - {"uint16", nil, Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}}, - {"uint32", nil, Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}}, - {"uint64", nil, Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}}, - {"uint256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}}, - {"uint8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, - {"uint8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, - {"uint16[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, - {"uint16[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, - {"uint32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, - {"uint32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, - {"uint64[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, - {"uint64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, - {"uint256[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, - {"uint256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, - {"bytes32", nil, Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}}, - {"bytes[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, - {"bytes[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}}, - {"bytes32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, - {"bytes32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, - {"string", nil, Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}}, - {"string[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}}, - {"string[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}}, - {"address", nil, Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}}, - {"address[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, - {"address[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, + {"bool", nil, Type{T: BoolTy, stringKind: "bool"}}, + {"bool[]", nil, Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}}, + {"bool[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}}, + {"bool[2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}}, + {"bool[][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}}, + {"bool[][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}}, + {"bool[2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}}, + {"bool[2][][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}}, + {"bool[2][2][2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}}, + {"bool[][][]", nil, Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}}, + {"bool[][2][]", nil, Type{T: SliceTy, Elem: &Type{T: ArrayTy, Size: 2, Elem: &Type{T: SliceTy, Elem: &Type{T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}}, + {"int8", nil, Type{Size: 8, T: IntTy, stringKind: "int8"}}, + {"int16", nil, Type{Size: 16, T: IntTy, stringKind: "int16"}}, + {"int32", nil, Type{Size: 32, T: IntTy, stringKind: "int32"}}, + {"int64", nil, Type{Size: 64, T: IntTy, stringKind: "int64"}}, + {"int256", nil, Type{Size: 256, T: IntTy, stringKind: "int256"}}, + {"int8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}}, + {"int8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}}, + {"int16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}}, + {"int16[2]", nil, Type{Size: 2, T: ArrayTy, Elem: &Type{Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}}, + {"int32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}}, + {"int32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}}, + {"int64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}}, + {"int64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}}, + {"int256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}}, + {"int256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, + {"uint8", nil, Type{Size: 8, T: UintTy, stringKind: "uint8"}}, + {"uint16", nil, Type{Size: 16, T: UintTy, stringKind: "uint16"}}, + {"uint32", nil, Type{Size: 32, T: UintTy, stringKind: "uint32"}}, + {"uint64", nil, Type{Size: 64, T: UintTy, stringKind: "uint64"}}, + {"uint256", nil, Type{Size: 256, T: UintTy, stringKind: "uint256"}}, + {"uint8[]", nil, Type{T: SliceTy, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}}, + {"uint8[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}}, + {"uint16[]", nil, Type{T: SliceTy, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}}, + {"uint16[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}}, + {"uint32[]", nil, Type{T: SliceTy, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}}, + {"uint32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}}, + {"uint64[]", nil, Type{T: SliceTy, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}}, + {"uint64[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}}, + {"uint256[]", nil, Type{T: SliceTy, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}}, + {"uint256[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}}, + {"bytes32", nil, Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}}, + {"bytes[]", nil, Type{T: SliceTy, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}}, + {"bytes[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}}, + {"bytes32[]", nil, Type{T: SliceTy, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}}, + {"bytes32[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[2]"}}, + {"string", nil, Type{T: StringTy, stringKind: "string"}}, + {"string[]", nil, Type{T: SliceTy, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[]"}}, + {"string[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{T: StringTy, stringKind: "string"}, stringKind: "string[2]"}}, + {"address", nil, Type{Size: 20, T: AddressTy, stringKind: "address"}}, + {"address[]", nil, Type{T: SliceTy, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}}, + {"address[2]", nil, Type{T: ArrayTy, Size: 2, Elem: &Type{Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, // TODO when fixed types are implemented properly // {"fixed", nil, Type{}}, // {"fixed128x128", nil, Type{}}, @@ -95,14 +95,14 @@ func TestTypeRegexp(t *testing.T) { // {"fixed[2]", nil, Type{}}, // {"fixed128x128[]", nil, Type{}}, // {"fixed128x128[2]", nil, Type{}}, - {"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct { + {"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct { A int64 `json:"a"` }{}), stringKind: "(int64)", - TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}}, - {"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct { + TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}}, + {"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{T: TupleTy, TupleType: reflect.TypeOf(struct { ATypicalParamName int64 `json:"aTypicalParamName"` }{}), stringKind: "(int64)", - TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}}, + TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}}, } for _, tt := range tests { @@ -255,7 +255,7 @@ func TestTypeCheck(t *testing.T) { {"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"}, {"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"}, {"string", nil, "hello world", ""}, - {"string", nil, string(""), ""}, + {"string", nil, "", ""}, {"string", nil, []byte{}, "abi: cannot use slice as type string as argument"}, {"bytes32[]", nil, [][32]byte{{}}, ""}, {"function", nil, [24]byte{}, ""}, @@ -306,3 +306,63 @@ func TestTypeCheck(t *testing.T) { } } } + +func TestInternalType(t *testing.T) { + components := []ArgumentMarshaling{{Name: "a", Type: "int64"}} + internalType := "struct a.b[]" + kind := Type{ + T: TupleTy, + TupleType: reflect.TypeOf(struct { + A int64 `json:"a"` + }{}), + stringKind: "(int64)", + TupleRawName: "ab[]", + TupleElems: []*Type{{T: IntTy, Size: 64, stringKind: "int64"}}, + TupleRawNames: []string{"a"}, + } + + blob := "tuple" + typ, err := NewType(blob, internalType, components) + if err != nil { + t.Errorf("type %q: failed to parse type string: %v", blob, err) + } + if !reflect.DeepEqual(typ, kind) { + t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(kind))) + } +} + +func TestGetTypeSize(t *testing.T) { + var testCases = []struct { + typ string + components []ArgumentMarshaling + typSize int + }{ + // simple array + {"uint256[2]", nil, 32 * 2}, + {"address[3]", nil, 32 * 3}, + {"bytes32[4]", nil, 32 * 4}, + // array array + {"uint256[2][3][4]", nil, 32 * (2 * 3 * 4)}, + // array tuple + {"tuple[2]", []ArgumentMarshaling{{Name: "x", Type: "bytes32"}, {Name: "y", Type: "bytes32"}}, (32 * 2) * 2}, + // simple tuple + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "uint256"}, {Name: "y", Type: "uint256"}}, 32 * 2}, + // tuple array + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}}, 32 * 2}, + // tuple tuple + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32"}}}}, 32}, + {"tuple", []ArgumentMarshaling{{Name: "x", Type: "tuple", Components: []ArgumentMarshaling{{Name: "x", Type: "bytes32[2]"}, {Name: "y", Type: "uint256"}}}}, 32 * (2 + 1)}, + } + + for i, data := range testCases { + typ, err := NewType(data.typ, "", data.components) + if err != nil { + t.Errorf("type %q: failed to parse type string: %v", data.typ, err) + } + + result := getTypeSize(typ) + if result != data.typSize { + t.Errorf("case %d type %q: get type size error: actual: %d expected: %d", i, data.typ, result, data.typSize) + } + } +} diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 01aac707f1..ec06984936 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -26,40 +26,44 @@ import ( ) var ( - // MaxUint256 is the maximum value that can be represented by a uint256 + // MaxUint256 is the maximum value that can be represented by a uint256. MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) - // MaxInt256 is the maximum value that can be represented by a int256 + // MaxInt256 is the maximum value that can be represented by a int256. MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1) ) -// ReadInteger reads the integer based on its kind and returns the appropriate value -func ReadInteger(typ byte, kind reflect.Kind, b []byte) interface{} { - switch kind { - case reflect.Uint8: - return b[len(b)-1] - case reflect.Uint16: - return binary.BigEndian.Uint16(b[len(b)-2:]) - case reflect.Uint32: - return binary.BigEndian.Uint32(b[len(b)-4:]) - case reflect.Uint64: - return binary.BigEndian.Uint64(b[len(b)-8:]) - case reflect.Int8: +// ReadInteger reads the integer based on its kind and returns the appropriate value. +func ReadInteger(typ Type, b []byte) interface{} { + if typ.T == UintTy { + switch typ.Size { + case 8: + return b[len(b)-1] + case 16: + return binary.BigEndian.Uint16(b[len(b)-2:]) + case 32: + return binary.BigEndian.Uint32(b[len(b)-4:]) + case 64: + return binary.BigEndian.Uint64(b[len(b)-8:]) + default: + // the only case left for unsigned integer is uint256. + return new(big.Int).SetBytes(b) + } + } + switch typ.Size { + case 8: return int8(b[len(b)-1]) - case reflect.Int16: + case 16: return int16(binary.BigEndian.Uint16(b[len(b)-2:])) - case reflect.Int32: + case 32: return int32(binary.BigEndian.Uint32(b[len(b)-4:])) - case reflect.Int64: + case 64: return int64(binary.BigEndian.Uint64(b[len(b)-8:])) default: - // the only case left for integer is int256/uint256. - ret := new(big.Int).SetBytes(b) - if typ == UintTy { - return ret - } + // the only case left for integer is int256 // big.SetBytes can't tell if a number is negative or positive in itself. // On EVM, if the returned number > max int256, it is negative. // A number is > max int256 if the bit at position 255 is set. + ret := new(big.Int).SetBytes(b) if ret.Bit(255) == 1 { ret.Add(MaxUint256, new(big.Int).Neg(ret)) ret.Add(ret, common.Big1) @@ -69,7 +73,7 @@ func ReadInteger(typ byte, kind reflect.Kind, b []byte) interface{} { } } -// reads a bool +// readBool reads a bool. func readBool(word []byte) (bool, error) { for _, b := range word[:31] { if b != 0 { @@ -87,7 +91,8 @@ func readBool(word []byte) (bool, error) { } // A function type is simply the address with the function selection signature at the end. -// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) +// +// readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { if t.T != FunctionTy { return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") @@ -100,20 +105,20 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { return } -// ReadFixedBytes uses reflection to create a fixed array to be read from +// ReadFixedBytes uses reflection to create a fixed array to be read from. func ReadFixedBytes(t Type, word []byte) (interface{}, error) { if t.T != FixedBytesTy { return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") } // convert - array := reflect.New(t.Type).Elem() + array := reflect.New(t.GetType()).Elem() reflect.Copy(array, reflect.ValueOf(word[0:t.Size])) return array.Interface(), nil } -// iteratively unpack elements +// forEachUnpack iteratively unpack elements. func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) { if size < 0 { return nil, fmt.Errorf("cannot marshal input to array, size is negative (%d)", size) @@ -127,10 +132,10 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) if t.T == SliceTy { // declare our slice - refSlice = reflect.MakeSlice(t.Type, size, size) + refSlice = reflect.MakeSlice(t.GetType(), size, size) } else if t.T == ArrayTy { // declare our array - refSlice = reflect.New(t.Type).Elem() + refSlice = reflect.New(t.GetType()).Elem() } else { return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") } @@ -140,7 +145,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) elemSize := getTypeSize(*t.Elem) for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { - inter, err := ToGoType(i, *t.Elem, output) + inter, err := toGoType(i, *t.Elem, output) if err != nil { return nil, err } @@ -154,10 +159,10 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) } func forTupleUnpack(t Type, output []byte) (interface{}, error) { - retval := reflect.New(t.Type).Elem() + retval := reflect.New(t.GetType()).Elem() virtualArgs := 0 for index, elem := range t.TupleElems { - marshalledValue, err := ToGoType((index+virtualArgs)*32, *elem, output) + marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) if elem.T == ArrayTy && !isDynamicType(*elem) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -183,9 +188,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { return retval.Interface(), nil } -// ToGoType parses the output bytes and recursively assigns the value of these bytes +// toGoType parses the output bytes and recursively assigns the value of these bytes // into a go type with accordance with the ABI spec. -func ToGoType(index int, t Type, output []byte) (interface{}, error) { +func toGoType(index int, t Type, output []byte) (interface{}, error) { if index+32 > len(output) { return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) } @@ -214,21 +219,23 @@ func ToGoType(index int, t Type, output []byte) (interface{}, error) { return nil, err } return forTupleUnpack(t, output[begin:]) - } else { - return forTupleUnpack(t, output[index:]) } + return forTupleUnpack(t, output[index:]) case SliceTy: return forEachUnpack(t, output[begin:], 0, length) case ArrayTy: if isDynamicType(*t.Elem) { - offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:])) + offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) + if offset > uint64(len(output)) { + return nil, fmt.Errorf("abi: toGoType offset greater than output length: offset: %d, len(output): %d", offset, len(output)) + } return forEachUnpack(t, output[offset:], 0, t.Size) } return forEachUnpack(t, output[index:], 0, t.Size) case StringTy: // variable arrays are written at the end of the return bytes return string(output[begin : begin+length]), nil case IntTy, UintTy: - return ReadInteger(t.T, t.Kind, returnOutput), nil + return ReadInteger(t, returnOutput), nil case BoolTy: return readBool(returnOutput) case AddressTy: @@ -246,7 +253,7 @@ func ToGoType(index int, t Type, output []byte) (interface{}, error) { } } -// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type. +// lengthPrefixPointsTo interprets a 32 byte slice as an offset and then determines which indices to look to decode the type. func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) { bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32]) bigOffsetEnd.Add(bigOffsetEnd, common.Big32) diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 0622e0adce..b88f77805b 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -30,6 +30,32 @@ import ( "github.com/stretchr/testify/require" ) +// TestUnpack tests the general pack/unpack tests in packing_test.go +func TestUnpack(t *testing.T) { + for i, test := range packUnpackTests { + t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) { + //Unpack + def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) + abi, err := JSON(strings.NewReader(def)) + if err != nil { + t.Fatalf("invalid ABI definition %s: %v", def, err) + } + encb, err := hex.DecodeString(test.packed) + if err != nil { + t.Fatalf("invalid hex %s: %v", test.packed, err) + } + out, err := abi.Unpack("method", encb) + if err != nil { + t.Errorf("test %d (%v) failed: %v", i, test.def, err) + return + } + if !reflect.DeepEqual(test.unpacked, ConvertType(out[0], test.unpacked)) { + t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.unpacked, out[0]) + } + }) + } +} + type unpackTest struct { def string // ABI definition JSON enc string // evm return data @@ -52,16 +78,6 @@ func (test unpackTest) checkError(err error) error { var unpackTests = []unpackTest{ // Bools - { - def: `[{ "type": "bool" }]`, - enc: "0000000000000000000000000000000000000000000000000000000000000001", - want: true, - }, - { - def: `[{ "type": "bool" }]`, - enc: "0000000000000000000000000000000000000000000000000000000000000000", - want: false, - }, { def: `[{ "type": "bool" }]`, enc: "0000000000000000000000000000000000000000000000000001000000000001", @@ -75,11 +91,6 @@ var unpackTests = []unpackTest{ err: "abi: improperly encoded boolean value", }, // Integers - { - def: `[{"type": "uint32"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000001", - want: uint32(1), - }, { def: `[{"type": "uint32"}]`, enc: "0000000000000000000000000000000000000000000000000000000000000001", @@ -92,16 +103,6 @@ var unpackTests = []unpackTest{ want: uint16(0), err: "abi: cannot unmarshal *big.Int in to uint16", }, - { - def: `[{"type": "uint17"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000001", - want: big.NewInt(1), - }, - { - def: `[{"type": "int32"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000001", - want: int32(1), - }, { def: `[{"type": "int32"}]`, enc: "0000000000000000000000000000000000000000000000000000000000000001", @@ -114,38 +115,10 @@ var unpackTests = []unpackTest{ want: int16(0), err: "abi: cannot unmarshal *big.Int in to int16", }, - { - def: `[{"type": "int17"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000001", - want: big.NewInt(1), - }, - { - def: `[{"type": "int256"}]`, - enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - want: big.NewInt(-1), - }, - // Address - { - def: `[{"type": "address"}]`, - enc: "0000000000000000000000000100000000000000000000000000000000000000", - want: common.Address{1}, - }, - // Bytes - { - def: `[{"type": "bytes32"}]`, - enc: "0100000000000000000000000000000000000000000000000000000000000000", - want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - { - def: `[{"type": "bytes"}]`, - enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", - want: common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"), - }, { def: `[{"type": "bytes"}]`, enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000", - want: [32]byte{}, - err: "abi: cannot unmarshal []uint8 in to [32]uint8", + want: [32]byte{1}, }, { def: `[{"type": "bytes32"}]`, @@ -153,245 +126,13 @@ var unpackTests = []unpackTest{ want: []byte(nil), err: "abi: cannot unmarshal [32]uint8 in to []uint8", }, - { - def: `[{"type": "bytes32"}]`, - enc: "0100000000000000000000000000000000000000000000000000000000000000", - want: [32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - }, - // Functions - { - def: `[{"type": "function"}]`, - enc: "0100000000000000000000000000000000000000000000000000000000000000", - want: [24]byte{1}, - }, - // Slice and Array - { - def: `[{"type": "uint8[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []uint8{1, 2}, - }, - { - def: `[{"type": "uint8[]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - want: []uint8{}, - }, - { - def: `[{"type": "uint256[]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - want: []*big.Int{}, - }, - { - def: `[{"type": "uint8[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]uint8{1, 2}, - }, - // multi dimensional, if these pass, all types that don't require length prefix should pass - { - def: `[{"type": "uint8[][]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - want: [][]uint8{}, - }, - { - def: `[{"type": "uint8[][]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [][]uint8{{1, 2}, {1, 2}}, - }, - { - def: `[{"type": "uint8[][]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", - want: [][]uint8{{1, 2}, {1, 2, 3}}, - }, - { - def: `[{"type": "uint8[2][2]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2][2]uint8{{1, 2}, {1, 2}}, - }, - { - def: `[{"type": "uint8[][2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - want: [2][]uint8{{}, {}}, - }, - { - def: `[{"type": "uint8[][2]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", - want: [2][]uint8{{1}, {1}}, - }, - { - def: `[{"type": "uint8[2][]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", - want: [][2]uint8{}, - }, - { - def: `[{"type": "uint8[2][]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [][2]uint8{{1, 2}}, - }, - { - def: `[{"type": "uint8[2][]"}]`, - enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [][2]uint8{{1, 2}, {1, 2}}, - }, - { - def: `[{"type": "uint16[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []uint16{1, 2}, - }, - { - def: `[{"type": "uint16[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]uint16{1, 2}, - }, - { - def: `[{"type": "uint32[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []uint32{1, 2}, - }, - { - def: `[{"type": "uint32[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]uint32{1, 2}, - }, - { - def: `[{"type": "uint32[2][3][4]"}]`, - enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018", - want: [4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}}, - }, - { - def: `[{"type": "uint64[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []uint64{1, 2}, - }, - { - def: `[{"type": "uint64[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]uint64{1, 2}, - }, - { - def: `[{"type": "uint256[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []*big.Int{big.NewInt(1), big.NewInt(2)}, - }, - { - def: `[{"type": "uint256[3]"}]`, - enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", - want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, - }, - { - def: `[{"type": "string[4]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b476f2d657468657265756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000", - want: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"}, - }, - { - def: `[{"type": "string[]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b676f2d657468657265756d000000000000000000000000000000000000000000", - want: []string{"Ethereum", "go-ethereum"}, - }, - { - def: `[{"type": "bytes[]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003f0f0f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0f0f00000000000000000000000000000000000000000000000000000000000", - want: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}}, - }, - { - def: `[{"type": "uint256[2][][]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8", - want: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}}, - }, - { - def: `[{"type": "int8[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []int8{1, 2}, - }, - { - def: `[{"type": "int8[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]int8{1, 2}, - }, - { - def: `[{"type": "int16[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []int16{1, 2}, - }, - { - def: `[{"type": "int16[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]int16{1, 2}, - }, - { - def: `[{"type": "int32[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []int32{1, 2}, - }, - { - def: `[{"type": "int32[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]int32{1, 2}, - }, - { - def: `[{"type": "int64[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []int64{1, 2}, - }, - { - def: `[{"type": "int64[2]"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: [2]int64{1, 2}, - }, - { - def: `[{"type": "int256[]"}]`, - enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: []*big.Int{big.NewInt(1), big.NewInt(2)}, - }, - { - def: `[{"type": "int256[3]"}]`, - enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", - want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, - }, - // struct outputs - { - def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: struct { - Int1 *big.Int - Int2 *big.Int - }{big.NewInt(1), big.NewInt(2)}, - }, - { - def: `[{"name":"int_one","type":"int256"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: struct { - IntOne *big.Int - }{big.NewInt(1)}, - }, - { - def: `[{"name":"int__one","type":"int256"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: struct { - IntOne *big.Int - }{big.NewInt(1)}, - }, - { - def: `[{"name":"int_one_","type":"int256"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: struct { - IntOne *big.Int - }{big.NewInt(1)}, - }, - { - def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`, - enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", - want: struct { - IntOne *big.Int - Intone *big.Int - }{big.NewInt(1), big.NewInt(2)}, - }, { def: `[{"name":"___","type":"int256"}]`, enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", want: struct { IntOne *big.Int Intone *big.Int - }{}, - err: "abi: purely underscored output cannot unpack to struct", + }{IntOne: big.NewInt(1)}, }, { def: `[{"name":"int_one","type":"int256"},{"name":"IntOne","type":"int256"}]`, @@ -438,11 +179,36 @@ var unpackTests = []unpackTest{ }{}, err: "abi: purely underscored output cannot unpack to struct", }, + // Make sure only the first argument is consumed + { + def: `[{"name":"int_one","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int__one","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, + { + def: `[{"name":"int_one_","type":"int256"}]`, + enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", + want: struct { + IntOne *big.Int + }{big.NewInt(1)}, + }, } -func TestUnpack(t *testing.T) { +// TestLocalUnpackTests runs test specially designed only for unpacking. +// All test cases that can be used to test packing and unpacking should move to packing_test.go +func TestLocalUnpackTests(t *testing.T) { for i, test := range unpackTests { t.Run(strconv.Itoa(i), func(t *testing.T) { + //Unpack def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(def)) if err != nil { @@ -453,7 +219,7 @@ func TestUnpack(t *testing.T) { t.Fatalf("invalid hex %s: %v", test.enc, err) } outptr := reflect.New(reflect.TypeOf(test.want)) - err = abi.Unpack(outptr.Interface(), "method", encb) + err = abi.UnpackIntoInterface(outptr.Interface(), "method", encb) if err := test.checkError(err); err != nil { t.Errorf("test %d (%v) failed: %v", i, test.def, err) return @@ -466,7 +232,7 @@ func TestUnpack(t *testing.T) { } } -func TestUnpackSetDynamicArrayOutput(t *testing.T) { +func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`)) if err != nil { t.Fatal(err) @@ -481,7 +247,7 @@ func TestUnpackSetDynamicArrayOutput(t *testing.T) { ) // test 32 - err = abi.Unpack(&out32, "testDynamicFixedBytes32", marshalledReturn32) + err = abi.UnpackIntoInterface(&out32, "testDynamicFixedBytes32", marshalledReturn32) if err != nil { t.Fatal(err) } @@ -498,7 +264,7 @@ func TestUnpackSetDynamicArrayOutput(t *testing.T) { } // test 15 - err = abi.Unpack(&out15, "testDynamicFixedBytes32", marshalledReturn15) + err = abi.UnpackIntoInterface(&out15, "testDynamicFixedBytes32", marshalledReturn15) if err != nil { t.Fatal(err) } @@ -592,14 +358,14 @@ func TestMethodMultiReturn(t *testing.T) { }, { &[]interface{}{new(int)}, &[]interface{}{}, - "abi: insufficient number of elements in the list/array for unpack, want 2, got 1", + "abi: insufficient number of arguments for unpack, want 2, got 1", "Can not unpack into a slice with wrong types", }} for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { require := require.New(t) - err := abi.Unpack(tc.dest, "multi", data) + err := abi.UnpackIntoInterface(tc.dest, "multi", data) if tc.error == "" { require.Nil(err, "Should be able to unpack method outputs.") require.Equal(tc.expected, tc.dest) @@ -622,7 +388,7 @@ func TestMultiReturnWithArray(t *testing.T) { ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9} ret2, ret2Exp := new(uint64), uint64(8) - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -646,7 +412,7 @@ func TestMultiReturnWithStringArray(t *testing.T) { ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f") ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"} ret4, ret4Exp := new(bool), false - if err := abi.Unpack(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -684,7 +450,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"} ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)} - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -724,7 +490,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { {{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}}, } ret2, ret2Exp := new(uint64), uint64(0x9876) - if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { + if err := abi.UnpackIntoInterface(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil { t.Fatal(err) } if !reflect.DeepEqual(*ret1, ret1Exp) { @@ -763,7 +529,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a")) buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000")) - err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&mixedBytes, "mixedBytes", buff.Bytes()) if err != nil { t.Error(err) } else { @@ -778,7 +544,7 @@ func TestUnmarshal(t *testing.T) { // marshal int var Int *big.Int - err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + err = abi.UnpackIntoInterface(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) } @@ -789,7 +555,7 @@ func TestUnmarshal(t *testing.T) { // marshal bool var Bool bool - err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) + err = abi.UnpackIntoInterface(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) if err != nil { t.Error(err) } @@ -806,7 +572,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(bytesOut) var Bytes []byte - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -822,7 +588,7 @@ func TestUnmarshal(t *testing.T) { bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -838,7 +604,7 @@ func TestUnmarshal(t *testing.T) { bytesOut = common.RightPadBytes([]byte("hello"), 64) buff.Write(bytesOut) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -848,7 +614,7 @@ func TestUnmarshal(t *testing.T) { } // marshal dynamic bytes output empty - err = abi.Unpack(&Bytes, "bytes", nil) + err = abi.UnpackIntoInterface(&Bytes, "bytes", nil) if err == nil { t.Error("expected error") } @@ -859,7 +625,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005")) buff.Write(common.RightPadBytes([]byte("hello"), 32)) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err != nil { t.Error(err) } @@ -873,7 +639,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.RightPadBytes([]byte("hello"), 32)) var hash common.Hash - err = abi.Unpack(&hash, "fixed", buff.Bytes()) + err = abi.UnpackIntoInterface(&hash, "fixed", buff.Bytes()) if err != nil { t.Error(err) } @@ -886,12 +652,12 @@ func TestUnmarshal(t *testing.T) { // marshal error buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) - err = abi.Unpack(&Bytes, "bytes", buff.Bytes()) + err = abi.UnpackIntoInterface(&Bytes, "bytes", buff.Bytes()) if err == nil { t.Error("expected error") } - err = abi.Unpack(&Bytes, "multi", make([]byte, 64)) + err = abi.UnpackIntoInterface(&Bytes, "multi", make([]byte, 64)) if err == nil { t.Error("expected error") } @@ -902,7 +668,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003")) // marshal int array var intArray [3]*big.Int - err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&intArray, "intArraySingle", buff.Bytes()) if err != nil { t.Error(err) } @@ -923,7 +689,7 @@ func TestUnmarshal(t *testing.T) { buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000")) var outAddr []common.Address - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) } @@ -950,7 +716,7 @@ func TestUnmarshal(t *testing.T) { A []common.Address B []common.Address } - err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddrStruct, "addressSliceDouble", buff.Bytes()) if err != nil { t.Fatal("didn't expect error:", err) } @@ -978,7 +744,7 @@ func TestUnmarshal(t *testing.T) { buff.Reset() buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100")) - err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes()) + err = abi.UnpackIntoInterface(&outAddr, "addressSliceSingle", buff.Bytes()) if err == nil { t.Fatal("expected error:", err) } @@ -1001,7 +767,7 @@ func TestUnpackTuple(t *testing.T) { B *big.Int }{new(big.Int), new(big.Int)} - err = abi.Unpack(&v, "tuple", buff.Bytes()) + err = abi.UnpackIntoInterface(&v, "tuple", buff.Bytes()) if err != nil { t.Error(err) } else { @@ -1073,7 +839,7 @@ func TestUnpackTuple(t *testing.T) { A: big.NewInt(1), } - err = abi.Unpack(&ret, "tuple", buff.Bytes()) + err = abi.UnpackIntoInterface(&ret, "tuple", buff.Bytes()) if err != nil { t.Error(err) } diff --git a/accounts/accounts.go b/accounts/accounts.go index ff4d0d49ef..177c6775ac 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -21,7 +21,7 @@ import ( "fmt" "math/big" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" @@ -89,7 +89,7 @@ type Wallet interface { // to discover non zero accounts and automatically add them to list of tracked // accounts. // - // Note, self derivaton will increment the last component of the specified path + // Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // @@ -114,7 +114,7 @@ type Wallet interface { SignData(account Account, mimeType string, data []byte) ([]byte, error) // SignDataWithPassphrase is identical to SignData, but also takes a password - // NOTE: there's an chance that an erroneous call might mistake the two strings, and + // NOTE: there's a chance that an erroneous call might mistake the two strings, and // supply password in the mimetype field, or vice versa. Thus, an implementation // should never echo the mimetype or return the mimetype in the error-response SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error) @@ -128,8 +128,10 @@ type Wallet interface { // a password to decrypt the account, or a PIN code o verify the transaction), // an AuthNeededError instance will be returned, containing infos for the user // about which fields or actions are needed. The user may retry by providing - // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock + // the needed details via SignTextWithPassphrase, or by other means (e.g. unlock // the account in a keystore). + // + // This method should return the signature in 'canonical' format, with v 0 or 1 SignText(account Account, text []byte) ([]byte, error) // SignTextWithPassphrase is identical to Signtext, but also takes a password diff --git a/accounts/external/backend.go b/accounts/external/backend.go index ba70b7728c..5a96aad6b4 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -27,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core" @@ -131,6 +130,12 @@ func (api *ExternalSigner) Accounts() []accounts.Account { func (api *ExternalSigner) Contains(account accounts.Account) bool { api.cacheMu.RLock() defer api.cacheMu.RUnlock() + if api.cache == nil { + // If we haven't already fetched the accounts, it's time to do so now + api.cacheMu.RUnlock() + api.Accounts() + api.cacheMu.RLock() + } for _, a := range api.cache { if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { return true @@ -169,19 +174,29 @@ func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, d } func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { - var res hexutil.Bytes + var signature hexutil.Bytes var signAddress = common.NewMixedcaseAddress(account.Address) - if err := api.client.Call(&res, "account_signData", + if err := api.client.Call(&signature, "account_signData", accounts.MimetypeTextPlain, &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined hexutil.Encode(text)); err != nil { return nil, err } - return res, nil + if signature[64] == 27 || signature[64] == 28 { + // If clef is used as a backend, it may already have transformed + // the signature to ethereum-type signature. + signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1 + } + return signature, nil +} + +// signTransactionResult represents the signinig result returned by clef. +type signTransactionResult struct { + Raw hexutil.Bytes `json:"raw"` + Tx *types.Transaction `json:"tx"` } func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - res := ethapi.SignTransactionResult{} data := hexutil.Bytes(tx.Data()) var to *common.MixedcaseAddress if tx.To() != nil { @@ -197,6 +212,21 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio To: to, From: common.NewMixedcaseAddress(account.Address), } + // We should request the default chain id that we're operating with + // (the chain we're executing on) + if chainID != nil { + args.ChainID = (*hexutil.Big)(chainID) + } + // However, if the user asked for a particular chain id, then we should + // use that instead. + if tx.Type() != types.LegacyTxType && tx.ChainId() != nil { + args.ChainID = (*hexutil.Big)(tx.ChainId()) + } + if tx.Type() == types.AccessListTxType { + accessList := tx.AccessList() + args.AccessList = &accessList + } + var res signTransactionResult if err := api.client.Call(&res, "account_signTransaction", args); err != nil { return nil, err } diff --git a/accounts/hd.go b/accounts/hd.go index 75c4761106..54acea3b26 100644 --- a/accounts/hd.go +++ b/accounts/hd.go @@ -150,3 +150,31 @@ func (path *DerivationPath) UnmarshalJSON(b []byte) error { *path, err = ParseDerivationPath(dp) return err } + +// DefaultIterator creates a BIP-32 path iterator, which progresses by increasing the last component: +// i.e. m/44'/60'/0'/0/0, m/44'/60'/0'/0/1, m/44'/60'/0'/0/2, ... m/44'/60'/0'/0/N. +func DefaultIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[len(path)-1]-- + return func() DerivationPath { + path[len(path)-1]++ + return path + } +} + +// LedgerLiveIterator creates a bip44 path iterator for Ledger Live. +// Ledger Live increments the third component rather than the fifth component +// i.e. m/44'/60'/0'/0/0, m/44'/60'/1'/0/0, m/44'/60'/2'/0/0, ... m/44'/60'/N'/0/0. +func LedgerLiveIterator(base DerivationPath) func() DerivationPath { + path := make(DerivationPath, len(base)) + copy(path[:], base[:]) + // Set it back by one, so the first call gives the first result + path[2]-- + return func() DerivationPath { + // ledgerLivePathIterator iterates on the third component + path[2]++ + return path + } +} diff --git a/accounts/hd_test.go b/accounts/hd_test.go index b6b23230dc..0743bbe666 100644 --- a/accounts/hd_test.go +++ b/accounts/hd_test.go @@ -17,6 +17,7 @@ package accounts import ( + "fmt" "reflect" "testing" ) @@ -61,7 +62,7 @@ func TestHDPathParsing(t *testing.T) { // Weird inputs just to ensure they work {" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, - // Invaid derivation paths + // Invalid derivation paths {"", nil}, // Empty relative derivation path {"m", nil}, // Empty absolute derivation path {"m/", nil}, // Missing last derivation component @@ -77,3 +78,41 @@ func TestHDPathParsing(t *testing.T) { } } } + +func testDerive(t *testing.T, next func() DerivationPath, expected []string) { + t.Helper() + for i, want := range expected { + if have := next(); fmt.Sprintf("%v", have) != want { + t.Errorf("step %d, have %v, want %v", i, have, want) + } + } +} + +func TestHdPathIteration(t *testing.T) { + testDerive(t, DefaultIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1", + "m/44'/60'/0'/0/2", "m/44'/60'/0'/0/3", + "m/44'/60'/0'/0/4", "m/44'/60'/0'/0/5", + "m/44'/60'/0'/0/6", "m/44'/60'/0'/0/7", + "m/44'/60'/0'/0/8", "m/44'/60'/0'/0/9", + }) + + testDerive(t, DefaultIterator(LegacyLedgerBaseDerivationPath), + []string{ + "m/44'/60'/0'/0", "m/44'/60'/0'/1", + "m/44'/60'/0'/2", "m/44'/60'/0'/3", + "m/44'/60'/0'/4", "m/44'/60'/0'/5", + "m/44'/60'/0'/6", "m/44'/60'/0'/7", + "m/44'/60'/0'/8", "m/44'/60'/0'/9", + }) + + testDerive(t, LedgerLiveIterator(DefaultBaseDerivationPath), + []string{ + "m/44'/60'/0'/0/0", "m/44'/60'/1'/0/0", + "m/44'/60'/2'/0/0", "m/44'/60'/3'/0/0", + "m/44'/60'/4'/0/0", "m/44'/60'/5'/0/0", + "m/44'/60'/6'/0/0", "m/44'/60'/7'/0/0", + "m/44'/60'/8'/0/0", "m/44'/60'/9'/0/0", + }) +} diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 8f660e282f..a3ec6e9c56 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -262,7 +262,7 @@ func (ac *accountCache) scanAccounts() error { switch { case err != nil: log.Debug("Failed to decode keystore key", "path", path, "err", err) - case (addr == common.Address{}): + case addr == common.Address{}: log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address") default: return &accounts.Account{ diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go index 73ff6ae9ee..8b309321d3 100644 --- a/accounts/keystore/file_cache.go +++ b/accounts/keystore/file_cache.go @@ -32,7 +32,7 @@ import ( type fileCache struct { all mapset.Set // Set of all files from the keystore folder lastMod time.Time // Last time instance when a file was modified - mu sync.RWMutex + mu sync.Mutex } // scan performs a new scan on the given directory, compares against the already diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 84d8df0c5a..2b815ce0f9 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" ) const ( @@ -110,7 +110,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } u := new(uuid.UUID) - *u = uuid.Parse(keyJSON.Id) + *u, err = uuid.Parse(keyJSON.Id) + if err != nil { + return err + } k.Id = *u addr, err := hex.DecodeString(keyJSON.Address) if err != nil { @@ -128,7 +131,10 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { - id := uuid.NewRandom() + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf("Could not create random uuid: %v", err)) + } key := &Key{ Id: id, Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 5b55175b1f..88dcfbeb69 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -24,7 +24,6 @@ import ( "crypto/ecdsa" crand "crypto/rand" "errors" - "fmt" "math/big" "os" "path/filepath" @@ -44,6 +43,10 @@ var ( ErrLocked = accounts.NewAuthNeededError("password or unlock") ErrNoMatch = errors.New("no key for given address or file") ErrDecrypt = errors.New("could not decrypt key with given password") + + // ErrAccountAlreadyExists is returned if an account attempted to import is + // already present in the keystore. + ErrAccountAlreadyExists = errors.New("account already exists") ) // KeyStoreType is the reflect type of a keystore backend. @@ -67,7 +70,8 @@ type KeyStore struct { updateScope event.SubscriptionScope // Subscription scope tracking current live listeners updating bool // Whether the event notification loop is running - mu sync.RWMutex + mu sync.RWMutex + importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing } type unlocked struct { @@ -279,11 +283,9 @@ func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *b if !found { return nil, ErrLocked } - // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil { - return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) - } - return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey) + // Depending on the presence of the chain ID, sign with 2718 or homestead + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, unlockedKey.PrivateKey) } // SignHashWithPassphrase signs hash if the private key matching the given address @@ -306,12 +308,9 @@ func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, return nil, err } defer zeroKey(key.PrivateKey) - - // Depending on the presence of the chain ID, sign with EIP155 or homestead - if chainID != nil { - return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) - } - return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey) + // Depending on the presence of the chain ID, sign with or without replay protection. + signer := types.LatestSignerForChainID(chainID) + return types.SignTx(tx, signer, key.PrivateKey) } // Unlock unlocks the given account indefinitely. @@ -443,14 +442,27 @@ func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (ac if err != nil { return accounts.Account{}, err } + ks.importMu.Lock() + defer ks.importMu.Unlock() + + if ks.cache.hasAddress(key.Address) { + return accounts.Account{ + Address: key.Address, + }, ErrAccountAlreadyExists + } return ks.importKey(key, newPassphrase) } // ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { + ks.importMu.Lock() + defer ks.importMu.Unlock() + key := newKeyFromECDSA(priv) if ks.cache.hasAddress(key.Address) { - return accounts.Account{}, fmt.Errorf("account already exists") + return accounts.Account{ + Address: key.Address, + }, ErrAccountAlreadyExists } return ks.importKey(key, passphrase) } diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index a691c50627..cb5de11c0d 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -23,11 +23,14 @@ import ( "runtime" "sort" "strings" + "sync" + "sync/atomic" "testing" "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" ) @@ -333,11 +336,95 @@ func TestWalletNotifications(t *testing.T) { // Shut down the event collector and check events. sub.Unsubscribe() - <-updates + for ev := range updates { + events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]}) + } checkAccounts(t, live, ks.Wallets()) checkEvents(t, wantEvents, events) } +// TestImportExport tests the import functionality of a keystore. +func TestImportECDSA(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", key) + } + if _, err = ks.ImportECDSA(key, "old"); err != nil { + t.Errorf("importing failed: %v", err) + } + if _, err = ks.ImportECDSA(key, "old"); err == nil { + t.Errorf("importing same key twice succeeded") + } + if _, err = ks.ImportECDSA(key, "new"); err == nil { + t.Errorf("importing same key twice succeeded") + } +} + +// TestImportECDSA tests the import and export functionality of a keystore. +func TestImportExport(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + acc, err := ks.NewAccount("old") + if err != nil { + t.Fatalf("failed to create account: %v", acc) + } + json, err := ks.Export(acc, "old", "new") + if err != nil { + t.Fatalf("failed to export account: %v", acc) + } + dir2, ks2 := tmpKeyStore(t, true) + defer os.RemoveAll(dir2) + if _, err = ks2.Import(json, "old", "old"); err == nil { + t.Errorf("importing with invalid password succeeded") + } + acc2, err := ks2.Import(json, "new", "new") + if err != nil { + t.Errorf("importing failed: %v", err) + } + if acc.Address != acc2.Address { + t.Error("imported account does not match exported account") + } + if _, err = ks2.Import(json, "new", "new"); err == nil { + t.Errorf("importing a key twice succeeded") + } + +} + +// TestImportRace tests the keystore on races. +// This test should fail under -race if importing races. +func TestImportRace(t *testing.T) { + dir, ks := tmpKeyStore(t, true) + defer os.RemoveAll(dir) + acc, err := ks.NewAccount("old") + if err != nil { + t.Fatalf("failed to create account: %v", acc) + } + json, err := ks.Export(acc, "old", "new") + if err != nil { + t.Fatalf("failed to export account: %v", acc) + } + dir2, ks2 := tmpKeyStore(t, true) + defer os.RemoveAll(dir2) + var atom uint32 + var wg sync.WaitGroup + wg.Add(2) + for i := 0; i < 2; i++ { + go func() { + defer wg.Done() + if _, err := ks2.Import(json, "new", "new"); err != nil { + atomic.AddUint32(&atom, 1) + } + + }() + } + wg.Wait() + if atom != 1 { + t.Errorf("Import is racy") + } +} + // checkAccounts checks that all known live accounts are present in the wallet list. func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) { if len(live) != len(wallets) { diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 89cdf0bfca..3b3e631888 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -42,7 +42,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" ) @@ -228,9 +228,12 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { return nil, err } key := crypto.ToECDSAUnsafe(keyBytes) - + id, err := uuid.FromBytes(keyId) + if err != nil { + return nil, err + } return &Key{ - Id: uuid.UUID(keyId), + Id: id, Address: crypto.PubkeyToAddress(key.PublicKey), PrivateKey: key, }, nil @@ -276,7 +279,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt if keyProtected.Version != version { return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version) } - keyId = uuid.Parse(keyProtected.Id) + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] plainText, err := DecryptDataV3(keyProtected.Crypto, auth) if err != nil { return nil, nil, err @@ -285,7 +292,11 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt } func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) { - keyId = uuid.Parse(keyProtected.Id) + keyUUID, err := uuid.Parse(keyProtected.Id) + if err != nil { + return nil, nil, err + } + keyId = keyUUID[:] mac, err := hex.DecodeString(keyProtected.Crypto.MAC) if err != nil { return nil, nil, err diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go index 03055245f5..0664dc2cdd 100644 --- a/accounts/keystore/presale.go +++ b/accounts/keystore/presale.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "golang.org/x/crypto/pbkdf2" ) @@ -37,7 +37,10 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou if err != nil { return accounts.Account{}, nil, err } - key.Id = uuid.NewRandom() + key.Id, err = uuid.NewRandom() + if err != nil { + return accounts.Account{}, nil, err + } a := accounts.Account{ Address: key.Address, URL: accounts.URL{ @@ -86,7 +89,7 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error ecKey := crypto.ToECDSAUnsafe(ethPriv) key = &Key{ - Id: nil, + Id: uuid.UUID{}, Address: crypto.PubkeyToAddress(ecKey.PublicKey), PrivateKey: ecKey, } diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go index 498067d497..1066095f6d 100644 --- a/accounts/keystore/wallet.go +++ b/accounts/keystore/wallet.go @@ -19,7 +19,7 @@ package keystore import ( "math/big" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -58,7 +58,7 @@ func (w *keystoreWallet) Open(passphrase string) error { return nil } func (w *keystoreWallet) Close() error { return nil } // Accounts implements accounts.Wallet, returning an account list consisting of -// a single account that the plain kestore wallet contains. +// a single account that the plain keystore wallet contains. func (w *keystoreWallet) Accounts() []accounts.Account { return []accounts.Account{w.account} } @@ -93,12 +93,12 @@ func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte return w.keystore.SignHash(account, hash) } -// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed +// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed. func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { return w.signHash(account, crypto.Keccak256(data)) } -// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed +// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed. func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { @@ -108,12 +108,14 @@ func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passph return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) } +// SignText implements accounts.Wallet, attempting to sign the hash of +// the given text with the given account. func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { return w.signHash(account, accounts.TextHash(text)) } // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the -// given hash with the given account using passphrase as extra authentication. +// hash of the given text with the given account using passphrase as extra authentication. func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { // Make sure the requested account is contained within if !w.Contains(account) { diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md index cfca916b3a..4313d9c6b2 100644 --- a/accounts/scwallet/README.md +++ b/accounts/scwallet/README.md @@ -31,12 +31,16 @@ Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet: ``` - > personal.openWallet("keycard://044def09") - Please enter the pairing password: + > personal.openWallet("keycard://044def09", "pairing password") ``` - Enter the pairing password that you have received during card initialization. Same with the PIN that you will subsequently be - asked for. + The pairing password has been generated during the card initialization process. + + The process needs to be repeated once more with the PIN: + + ``` + > personal.openWallet("keycard://044def09", "PIN number") + ``` If everything goes well, you should see your new account when typing `personal` on the console: diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go index 5f939c6586..811f8c695e 100644 --- a/accounts/scwallet/hub.go +++ b/accounts/scwallet/hub.go @@ -220,7 +220,7 @@ func (hub *Hub) refreshWallets() { // Mark the reader as present seen[reader] = struct{}{} - // If we alreay know about this card, skip to the next reader, otherwise clean up + // If we already know about this card, skip to the next reader, otherwise clean up if wallet, ok := hub.wallets[reader]; ok { if err := wallet.ping(); err == nil { continue diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go index 9b70c69dcc..10887a8b43 100644 --- a/accounts/scwallet/securechannel.go +++ b/accounts/scwallet/securechannel.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/aes" "crypto/cipher" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/sha512" @@ -27,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" pcsc "github.com/gballet/go-libpcsclite" - "github.com/wsddn/go-ecdh" "golang.org/x/crypto/pbkdf2" "golang.org/x/text/unicode/norm" ) @@ -63,26 +63,19 @@ type SecureChannelSession struct { // NewSecureChannelSession creates a new secure channel for the given card and public key. func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) { // Generate an ECDSA keypair for ourselves - gen := ecdh.NewEllipticECDH(crypto.S256()) - private, public, err := gen.GenerateKey(rand.Reader) + key, err := crypto.GenerateKey() if err != nil { return nil, err } - - cardPublic, ok := gen.Unmarshal(keyData) - if !ok { - return nil, fmt.Errorf("could not unmarshal public key from card") - } - - secret, err := gen.GenerateSharedSecret(private, cardPublic) + cardPublic, err := crypto.UnmarshalPubkey(keyData) if err != nil { - return nil, err + return nil, fmt.Errorf("could not unmarshal public key from card: %v", err) } - + secret, _ := key.Curve.ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes()) return &SecureChannelSession{ card: card, - secret: secret, - publicKey: gen.Marshal(public), + secret: secret.Bytes(), + publicKey: elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y), }, nil } diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 57804dfd11..b4d229bc0b 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -33,7 +33,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -362,7 +362,7 @@ func (w *Wallet) Open(passphrase string) error { return err } // Pairing succeeded, fall through to PIN checks. This will of course fail, - // but we can't return ErrPINNeeded directly here becase we don't know whether + // but we can't return ErrPINNeeded directly here because we don't know whether // a PIN check or a PIN reset is needed. passphrase = "" } @@ -637,7 +637,7 @@ func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun // to discover non zero accounts and automatically add them to list of tracked // accounts. // -// Note, self derivaton will increment the last component of the specified path +// Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // @@ -699,7 +699,7 @@ func (w *Wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock // the account in a keystore). func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - signer := types.NewEIP155Signer(chainID) + signer := types.LatestSignerForChainID(chainID) hash := signer.Hash(tx) sig, err := w.signHash(account, hash[:]) if err != nil { diff --git a/accounts/url.go b/accounts/url.go index a5add10216..12a84414a0 100644 --- a/accounts/url.go +++ b/accounts/url.go @@ -64,7 +64,7 @@ func (u URL) String() string { func (u URL) TerminalString() string { url := u.String() if len(url) > 32 { - return url[:31] + "…" + return url[:31] + ".." } return url } diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index 64eae64f68..3de3b4091c 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -52,8 +52,10 @@ const ( ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration + ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet + ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address @@ -162,7 +164,7 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio return common.Address{}, nil, accounts.ErrWalletClosed } // 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 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]) } @@ -170,6 +172,24 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio return w.ledgerSign(path, tx, chainID) } +// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and +// waiting for the user to sign or deny the transaction. +// +// Note: this was introduced in the ledger 1.5.0 firmware +func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { + // If the Ethereum app doesn't run, abort + if w.offline() { + return nil, accounts.ErrWalletClosed + } + // Ensure the wallet is capable of signing the given transaction + if w.version[0] < 1 && w.version[1] < 5 { + //lint:ignore ST1005 brand name displayed on the console + return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2]) + } + // All infos gathered and metadata checks out, request signing + return w.ledgerSignTypedMessage(path, domainHash, messageHash) +} + // ledgerVersion retrieves the current version of the Ethereum wallet app running // on the Ledger wallet. // @@ -367,6 +387,68 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction return sender, signed, nil } +// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user +// to confirm or deny the transaction. +// +// The signing protocol is defined as follows: +// +// CLA | INS | P1 | P2 | Lc | Le +// ----+-----+----+-----------------------------+-----+--- +// E0 | 0C | 00 | implementation version : 00 | variable | variable +// +// Where the input is: +// +// Description | Length +// -------------------------------------------------+---------- +// Number of BIP 32 derivations to perform (max 10) | 1 byte +// First derivation index (big endian) | 4 bytes +// ... | 4 bytes +// Last derivation index (big endian) | 4 bytes +// domain hash | 32 bytes +// message hash | 32 bytes +// +// +// +// And the output data is: +// +// Description | Length +// ------------+--------- +// signature V | 1 byte +// signature R | 32 bytes +// signature S | 32 bytes +func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) { + // Flatten the derivation path into the Ledger request + path := make([]byte, 1+4*len(derivationPath)) + path[0] = byte(len(derivationPath)) + for i, component := range derivationPath { + binary.BigEndian.PutUint32(path[1+4*i:], component) + } + // Create the 712 message + payload := append(path, domainHash...) + payload = append(payload, messageHash...) + + // Send the request and wait for the response + var ( + op = ledgerP1InitTypedMessageData + reply []byte + err error + ) + + // Send the message over, ensuring it's processed correctly + reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload) + + if err != nil { + return nil, err + } + + // Extract the Ethereum signature and do a sanity validation + if len(reply) != crypto.SignatureLength { + return nil, errors.New("reply lacks signature") + } + signature := append(reply[1:], reply[0]) + return signature, nil +} + // ledgerExchange performs a data exchange with the Ledger wallet, sending it a // message and retrieving the response. // diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go index 1892097baf..c2182b88d0 100644 --- a/accounts/usbwallet/trezor.go +++ b/accounts/usbwallet/trezor.go @@ -185,6 +185,10 @@ func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio return w.trezorSign(path, tx, chainID) } +func (w *trezorDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { + return nil, accounts.ErrNotSupported +} + // trezorDerive sends a derivation request to the Trezor device and returns the // Ethereum address located on that path. func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) { @@ -255,9 +259,11 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction if chainID == nil { signer = new(types.HomesteadSigner) } else { + // Trezor backend does not support typed transactions yet. signer = types.NewEIP155Signer(chainID) signature[64] -= byte(chainID.Uint64()*2 + 35) } + // Inject the final signature into the transaction and sanity check the sender signed, err := tx.WithSignature(signer, signature) if err != nil { diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index ee539d9653..b6f1814488 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -25,7 +25,7 @@ import ( "sync" "time" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -67,6 +67,8 @@ type driver interface { // SignTx sends the transaction to the USB device and waits for the user to confirm // or deny the transaction. SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) + + SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error) } // wallet represents the common functionality shared by all USB hardware @@ -368,18 +370,22 @@ func (w *wallet) selfDerive() { w.log.Warn("USB wallet nonce retrieval failed", "err", err) break } - // If the next account is empty, stop self-derivation, but add for the last base path + // We've just self-derived a new account, start tracking it locally + // unless the account was empty. + path := make(accounts.DerivationPath, len(nextPaths[i])) + copy(path[:], nextPaths[i][:]) if balance.Sign() == 0 && nonce == 0 { empty = true + // If it indeed was empty, make a log output for it anyway. In the case + // of legacy-ledger, the first account on the legacy-path will + // be shown to the user, even if we don't actively track it if i < len(nextAddrs)-1 { + w.log.Info("Skipping trakcking first account on legacy path, use personal.deriveAccount(,, false) to track", + "path", path, "address", nextAddrs[i]) break } } - // We've just self-derived a new account, start tracking it locally - path := make(accounts.DerivationPath, len(nextPaths[i])) - copy(path[:], nextPaths[i][:]) paths = append(paths, path) - account := accounts.Account{ Address: nextAddrs[i], URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, @@ -489,7 +495,7 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun // to discover non zero accounts and automatically add them to list of tracked // accounts. // -// Note, self derivaton will increment the last component of the specified path +// Note, self derivation will increment the last component of the specified path // opposed to decending into a child path to allow discovering accounts starting // from non zero components. // @@ -520,7 +526,46 @@ func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { - return w.signHash(account, crypto.Keccak256(data)) + + // Unless we are doing 712 signing, simply dispatch to signHash + if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) { + return w.signHash(account, crypto.Keccak256(data)) + } + + // dispatch to 712 signing if the mimetype is TypedData and the format matches + w.stateLock.RLock() // Comms have own mutex, this is for the state fields + defer w.stateLock.RUnlock() + + // If the wallet is closed, abort + if w.device == nil { + return nil, accounts.ErrWalletClosed + } + // Make sure the requested account is contained within + path, ok := w.paths[account.Address] + if !ok { + return nil, accounts.ErrUnknownAccount + } + // All infos gathered and metadata checks out, request signing + <-w.commsLock + defer func() { w.commsLock <- struct{}{} }() + + // Ensure the device isn't screwed with while user confirmation is pending + // TODO(karalabe): remove if hotplug lands on Windows + w.hub.commsLock.Lock() + w.hub.commsPend++ + w.hub.commsLock.Unlock() + + defer func() { + w.hub.commsLock.Lock() + w.hub.commsPend-- + w.hub.commsLock.Unlock() + }() + // Sign the transaction + signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66]) + if err != nil { + return nil, err + } + return signature, nil } // SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given diff --git a/appveyor.yml b/appveyor.yml index fe15cc7f0e..a72163382a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,41 +1,29 @@ -os: Visual Studio 2015 - -# Clone directly into GOPATH. -clone_folder: C:\gopath\src\github.com\ethereum\go-ethereum +os: Visual Studio 2019 clone_depth: 5 version: "{branch}.{build}" environment: - global: - GO111MODULE: on - GOPATH: C:\gopath - CC: gcc.exe matrix: + # We use gcc from MSYS2 because it is the most recent compiler version available on + # AppVeyor. Note: gcc.exe only works properly if the corresponding bin/ directory is + # contained in PATH. - GETH_ARCH: amd64 - MSYS2_ARCH: x86_64 - MSYS2_BITS: 64 - MSYSTEM: MINGW64 - PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% + GETH_CC: C:\msys64\mingw64\bin\gcc.exe + PATH: C:\msys64\mingw64\bin;C:\Program Files (x86)\NSIS\;%PATH% - GETH_ARCH: 386 - MSYS2_ARCH: i686 - MSYS2_BITS: 32 - MSYSTEM: MINGW32 - PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% + GETH_CC: C:\msys64\mingw32\bin\gcc.exe + PATH: C:\msys64\mingw32\bin;C:\Program Files (x86)\NSIS\;%PATH% install: - - git submodule update --init - - rmdir C:\go /s /q - - appveyor DownloadFile https://dl.google.com/go/go1.14.2.windows-%GETH_ARCH%.zip - - 7z x go1.14.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - git submodule update --init --depth 1 - go version - - gcc --version + - "%GETH_CC% --version" build_script: - - go run build\ci.go install + - go run build\ci.go install -dlgo -arch %GETH_ARCH% -cc %GETH_CC% after_build: - - go run build\ci.go archive -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - - go run build\ci.go nsis -signer WINDOWS_SIGNING_KEY -upload gethstore/builds + - go run build\ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds + - go run build\ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds test_script: - - set CGO_ENABLED=1 - - go run build\ci.go test -coverage + - go run build\ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -coverage diff --git a/build/checksums.txt b/build/checksums.txt index 2605abbe0d..0e33e07f2c 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,21 +1,33 @@ # This file contains sha256 checksums of optional build dependencies. -98de84e69726a66da7b4e58eac41b99cbe274d7e8906eeb8a5b7eb0aadee7f7c go1.14.2.src.tar.gz +b298d29de9236ca47a023e382313bcc2d2eed31dfa706b60a04103ce83a71a25 go1.16.3.src.tar.gz +6bb1cf421f8abc2a9a4e39140b7397cdae6aca3e8d36dcff39a1a77f4f1170ac go1.16.3.darwin-amd64.tar.gz +f4e96bbcd5d2d1942f5b55d9e4ab19564da4fad192012f6d7b0b9b055ba4208f go1.16.3.darwin-arm64.tar.gz +48b2d1481db756c88c18b1f064dbfc3e265ce4a775a23177ca17e25d13a24c5d go1.16.3.linux-386.tar.gz +951a3c7c6ce4e56ad883f97d9db74d3d6d80d5fec77455c6ada6c1f7ac4776d2 go1.16.3.linux-amd64.tar.gz +566b1d6f17d2bc4ad5f81486f0df44f3088c3ed47a3bec4099d8ed9939e90d5d go1.16.3.linux-arm64.tar.gz +0dae30385e3564a557dac7f12a63eedc73543e6da0f6017990e214ce8cc8797c go1.16.3.linux-armv6l.tar.gz +a3c16e1531bf9726f47911c4a9ed7cb665a6207a51c44f10ebad4db63b4bcc5a go1.16.3.windows-386.zip +a4400345135b36cb7942e52bbaf978b66814738b855eeff8de879a09fd99de7f go1.16.3.windows-amd64.zip +31ecd11d497684fa8b0f01ba784590c4c760943665fdc4fe0adaa1405c71736c go1.16.3.freebsd-386.tar.gz +ffbd920b309e62e807457b11d80e8c17fefe3ef6de423aaba4b1e270b2ca4c3d go1.16.3.freebsd-amd64.tar.gz +5eb046bbbbc7fe2591846a4303884cb5a01abb903e3e61e33459affe7874e811 go1.16.3.linux-ppc64le.tar.gz +3e8bd7bde533a73fd6fa75b5288678ef397e76c198cfb26b8ae086035383b1cf go1.16.3.linux-s390x.tar.gz -aeaa5498682246b87d0b77ece283897348ea03d98e816760a074058bfca60b2a golangci-lint-1.24.0-windows-amd64.zip -7e854a70d449fe77b7a91583ec88c8603eb3bf96c45d52797dc4ba3f2f278dbe golangci-lint-1.24.0-darwin-386.tar.gz -835101fae192c3a2e7a51cb19d5ac3e1a40b0e311955e89bc21d61de78635979 golangci-lint-1.24.0-linux-armv6.tar.gz -a041a6e6a61c9ff3dbe58673af13ea00c76bcd462abede0ade645808e97cdd6d golangci-lint-1.24.0-windows-386.zip -7cc73eb9ca02b7a766c72b913f8080401862b10e7bb90c09b085415a81f21609 golangci-lint-1.24.0-freebsd-armv6.tar.gz -537bb2186987b5e68ad4e8829230557f26087c3028eb736dea1662a851bad73d golangci-lint-1.24.0-linux-armv7.tar.gz -8cb1bc1e63d8f0d9b71fcb10b38887e1646a6b8a120ded2e0cd7c3284528f633 golangci-lint-1.24.0-linux-mips64.tar.gz -095d3f8bf7fc431739861574d0b58d411a617df2ed5698ce5ae5ecc66d23d44d golangci-lint-1.24.0-freebsd-armv7.tar.gz -e245df27cec3827aef9e7afbac59e92816978ee3b64f84f7b88562ff4b2ac225 golangci-lint-1.24.0-linux-arm64.tar.gz -35d6d5927e19f0577cf527f0e4441dbb37701d87e8cf729c98a510fce397fbf7 golangci-lint-1.24.0-linux-ppc64le.tar.gz -a1ed66353b8ceb575d78db3051491bce3ac1560e469a9bc87e8554486fec7dfe golangci-lint-1.24.0-freebsd-386.tar.gz -241ca454102e909de04957ff8a5754c757cefa255758b3e1fba8a4533d19d179 golangci-lint-1.24.0-linux-amd64.tar.gz -ff488423db01a0ec8ffbe4e1d65ef1be6a8d5e6d7930cf380ce8aaf714125470 golangci-lint-1.24.0-linux-386.tar.gz -f05af56f15ebbcf77663a8955d1e39009b584ce8ea4c5583669369d80353a113 golangci-lint-1.24.0-darwin-amd64.tar.gz -b0096796c0ffcd6c350a2ec006100e7ef5f0597b43a204349d4f997273fb32a7 golangci-lint-1.24.0-freebsd-amd64.tar.gz -c9c2867380e85628813f1f7d1c3cfc6c6f7931e89bea86f567ff451b8cdb6654 golangci-lint-1.24.0-linux-mips64le.tar.gz -2feb97fa61c934aa3eba9bc104ab5dd8fb946791d58e64060e8857e800eeae0b golangci-lint-1.24.0-linux-s390x.tar.gz +7e9a47ab540aa3e8472fbf8120d28bed3b9d9cf625b955818e8bc69628d7187c golangci-lint-1.39.0-darwin-amd64.tar.gz +574daa2c9c299b01672a6daeb1873b5f12e413cdb6dc0e30f2ff163956778064 golangci-lint-1.39.0-darwin-arm64.tar.gz +6225f7014987324ab78e9b511f294e3f25be013728283c33918c67c8576d543e golangci-lint-1.39.0-freebsd-386.tar.gz +6b3e76e1e5eaf0159411c8e2727f8d533989d3bb19f10e9caa6e0b9619ee267d golangci-lint-1.39.0-freebsd-amd64.tar.gz +a301cacfff87ed9b00313d95278533c25a4527a06b040a17d969b4b7e1b8a90d golangci-lint-1.39.0-freebsd-armv7.tar.gz +25bfd96a29c3112f508d5e4fc860dbad7afce657233c343acfa20715717d51e7 golangci-lint-1.39.0-freebsd-armv6.tar.gz +9687e4ff15545cfc722b0e46107a94195166a505023b48a316579af25ad09505 golangci-lint-1.39.0-linux-armv7.tar.gz +a7fa7ab2bfc99cbe5e5bcbf5684f5a997f920afbbe2f253d2feb1001d5e3c8b3 golangci-lint-1.39.0-linux-armv6.tar.gz +c8f9634115beddb4ed9129c1f7ecd4c97c99d07aeef33e3707234097eeb51b7b golangci-lint-1.39.0-linux-mips64le.tar.gz +d1234c213b74751f1af413302dde0e9a6d4d29aecef034af7abb07dc1b6e887f golangci-lint-1.39.0-linux-arm64.tar.gz +df25d9267168323b163147acb823ab0215a8a3bb6898a4a9320afdfedde66817 golangci-lint-1.39.0-linux-386.tar.gz +1767e75fba357b7651b1a796d38453558f371c60af805505ec99e166908c04b5 golangci-lint-1.39.0-linux-ppc64le.tar.gz +25fd75bf3186b3d930ecae10185689968fd18fd8fa6f9f555d6beb04348c20f6 golangci-lint-1.39.0-linux-s390x.tar.gz +3a73aa7468087caa62673c8adea99b4e4dff846dc72707222db85f8679b40cbf golangci-lint-1.39.0-linux-amd64.tar.gz +578caceccf81739bda67dbfec52816709d03608c6878888ecdc0e186a094a41b golangci-lint-1.39.0-linux-mips64.tar.gz +494b66ba0e32c8ddf6c4f6b1d05729b110900f6017eda943057e43598c17d7a8 golangci-lint-1.39.0-windows-386.zip +52ec2e13a3cbb47147244dff8cfc35103563deb76e0459133058086fc35fb2c7 golangci-lint-1.39.0-windows-amd64.zip diff --git a/build/ci.go b/build/ci.go index ff0451d1b2..d9f147ef0e 100644 --- a/build/ci.go +++ b/build/ci.go @@ -26,7 +26,7 @@ Available commands are: install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables test [ -coverage ] [ packages... ] -- runs the tests lint -- runs certain pre-selected linters - archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts + archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -signify key-envvar ] [ -upload dest ] -- archives build artifacts importkeys -- imports signing keys from env debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package nsis -- creates a Windows NSIS installer @@ -46,12 +46,11 @@ import ( "encoding/base64" "flag" "fmt" - "go/parser" - "go/token" "io/ioutil" "log" "os" "os/exec" + "path" "path/filepath" "regexp" "runtime" @@ -59,6 +58,7 @@ import ( "time" "github.com/cespare/cp" + "github.com/ethereum/go-ethereum/crypto/signify" "github.com/ethereum/go-ethereum/internal/build" "github.com/ethereum/go-ethereum/params" ) @@ -79,7 +79,6 @@ var ( executablePath("geth"), executablePath("puppeth"), executablePath("rlpdump"), - executablePath("wnode"), executablePath("clef"), } @@ -109,10 +108,6 @@ var ( BinaryName: "rlpdump", Description: "Developer utility tool that prints RLP structures.", }, - { - BinaryName: "wnode", - Description: "Ethereum Whisper diagnostic tool", - }, { BinaryName: "clef", Description: "Ethereum account management tool.", @@ -120,7 +115,6 @@ var ( } // A debian package is created for all executables listed here. - debEthereum = debPackage{ Name: "ethereum", Version: params.Version, @@ -139,19 +133,26 @@ var ( // Note: zesty 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: 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{ - "trusty": "golang-1.11", - "xenial": "golang-go", - "bionic": "golang-go", - "disco": "golang-go", - "eoan": "golang-go", - "focal": "golang-go", + "trusty": "golang-1.11", + "xenial": "golang-go", + "bionic": "golang-go", + "focal": "golang-go", + "groovy": "golang-go", + "hirsute": "golang-go", } debGoBootPaths = map[string]string{ "golang-1.11": "/usr/lib/go-1.11", "golang-go": "/usr/lib/go", } + + // This is the version of go that will be downloaded by + // + // go run ci.go install -dlgo + dlgoVersion = "1.16.3" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) @@ -202,130 +203,96 @@ func main() { func doInstall(cmdline []string) { var ( + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") arch = flag.String("arch", "", "Architecture to cross build for") cc = flag.String("cc", "", "C compiler to cross build with") ) flag.CommandLine.Parse(cmdline) - env := build.Env() - // Check Go version. People regularly open issues about compilation - // failure with outdated Go. This should save them the trouble. - if !strings.Contains(runtime.Version(), "devel") { - // Figure out the minor version number since we can't textually compare (1.10 < 1.9) - var minor int - fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) - - if minor < 11 { - log.Println("You have Go version", runtime.Version()) - log.Println("go-ethereum requires at least Go version 1.11 and cannot") - log.Println("be compiled with an earlier version. Please upgrade your Go installation.") - os.Exit(1) - } - } - // Compile packages given as arguments, or everything if there are no arguments. - packages := []string{"./..."} - if flag.NArg() > 0 { - packages = flag.Args() + // Configure the toolchain. + tc := build.GoToolchain{GOARCH: *arch, CC: *cc} + if *dlgo { + csdb := build.MustLoadChecksums("build/checksums.txt") + tc.Root = build.DownloadGo(csdb, dlgoVersion) } - if *arch == "" || *arch == runtime.GOARCH { - goinstall := goTool("install", buildFlags(env)...) - if runtime.GOARCH == "arm64" { - goinstall.Args = append(goinstall.Args, "-p", "1") - } - goinstall.Args = append(goinstall.Args, "-v") - goinstall.Args = append(goinstall.Args, packages...) - build.MustRun(goinstall) - return + // Configure the build. + env := build.Env() + gobuild := tc.Go("build", buildFlags(env)...) + + // arm64 CI builders are memory-constrained and can't handle concurrent builds, + // better disable it. This check isn't the best, it should probably + // check for something in env instead. + if env.CI && runtime.GOARCH == "arm64" { + gobuild.Args = append(gobuild.Args, "-p", "1") } - // Seems we are cross compiling, work around forbidden GOBIN - goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...) - goinstall.Args = append(goinstall.Args, "-v") - goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...) - goinstall.Args = append(goinstall.Args, packages...) - build.MustRun(goinstall) + // We use -trimpath to avoid leaking local paths into the built executables. + gobuild.Args = append(gobuild.Args, "-trimpath") - if cmds, err := ioutil.ReadDir("cmd"); err == nil { - for _, cmd := range cmds { - pkgs, err := parser.ParseDir(token.NewFileSet(), filepath.Join(".", "cmd", cmd.Name()), nil, parser.PackageClauseOnly) - if err != nil { - log.Fatal(err) - } - for name := range pkgs { - if name == "main" { - gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...) - gobuild.Args = append(gobuild.Args, "-v") - gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...) - gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name())) - 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) { var ld []string if env.Commit != "" { ld = append(ld, "-X", "main.gitCommit="+env.Commit) 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" { ld = append(ld, "-s") } - if len(ld) > 0 { flags = append(flags, "-ldflags", strings.Join(ld, " ")) } return flags } -func goTool(subcmd string, args ...string) *exec.Cmd { - return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...) -} - -func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd { - cmd := build.GoTool(subcmd, args...) - if arch == "" || arch == runtime.GOARCH { - cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) - } else { - cmd.Env = append(cmd.Env, "CGO_ENABLED=1") - cmd.Env = append(cmd.Env, "GOARCH="+arch) - } - if cc != "" { - cmd.Env = append(cmd.Env, "CC="+cc) - } - for _, e := range os.Environ() { - if strings.HasPrefix(e, "GOBIN=") { - continue - } - cmd.Env = append(cmd.Env, e) - } - return cmd -} - // Running The Tests // // "tests" also includes static analysis tools such as vet. func doTest(cmdline []string) { - coverage := flag.Bool("coverage", false, "Whether to record code coverage") - verbose := flag.Bool("v", false, "Whether to log verbosely") + var ( + dlgo = flag.Bool("dlgo", false, "Download Go and build with it") + arch = flag.String("arch", "", "Run tests for given architecture") + cc = flag.String("cc", "", "Sets C compiler binary") + coverage = flag.Bool("coverage", false, "Whether to record code coverage") + verbose = flag.Bool("v", false, "Whether to log verbosely") + ) flag.CommandLine.Parse(cmdline) - env := build.Env() - packages := []string{"./..."} - if len(flag.CommandLine.Args()) > 0 { - packages = flag.CommandLine.Args() + // Configure the toolchain. + tc := build.GoToolchain{GOARCH: *arch, CC: *cc} + if *dlgo { + csdb := build.MustLoadChecksums("build/checksums.txt") + tc.Root = build.DownloadGo(csdb, dlgoVersion) } + gotest := tc.Go("test") - // Run the actual tests. // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. - gotest := goTool("test", buildFlags(env)...) - gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "5m") + gotest.Args = append(gotest.Args, "-p", "1") if *coverage { gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover") } @@ -333,6 +300,10 @@ func doTest(cmdline []string) { gotest.Args = append(gotest.Args, "-v") } + packages := []string{"./..."} + if len(flag.CommandLine.Args()) > 0 { + packages = flag.CommandLine.Args() + } gotest.Args = append(gotest.Args, packages...) build.MustRun(gotest) } @@ -356,7 +327,7 @@ func doLint(cmdline []string) { // downloadLinter downloads and unpacks golangci-lint. func downloadLinter(cachedir string) string { - const version = "1.24.0" + const version = "1.39.0" csdb := build.MustLoadChecksums("build/checksums.txt") base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH) @@ -365,7 +336,7 @@ func downloadLinter(cachedir string) string { if err := csdb.DownloadFile(url, archivePath); err != nil { log.Fatal(err) } - if err := build.ExtractTarballArchive(archivePath, cachedir); err != nil { + if err := build.ExtractArchive(archivePath, cachedir); err != nil { log.Fatal(err) } return filepath.Join(cachedir, base, "golangci-lint") @@ -374,11 +345,12 @@ func downloadLinter(cachedir string) string { // Release Packaging func doArchive(cmdline []string) { var ( - arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") - atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) - upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) - ext string + arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging") + atype = flag.String("type", "zip", "Type of archive to write (zip|tar)") + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. LINUX_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify key (e.g. LINUX_SIGNIFY_KEY)`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + ext string ) flag.CommandLine.Parse(cmdline) switch *atype { @@ -391,8 +363,7 @@ func doArchive(cmdline []string) { } var ( - env = build.Env() - + env = build.Env() basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit)) geth = "geth-" + basegeth + ext alltools = "geth-alltools-" + basegeth + ext @@ -405,7 +376,7 @@ func doArchive(cmdline []string) { log.Fatal(err) } for _, archive := range []string{geth, alltools} { - if err := archiveUpload(archive, *upload, *signer); err != nil { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { log.Fatal(err) } } @@ -425,7 +396,7 @@ func archiveBasename(arch string, archiveVersion string) string { return platform + "-" + archiveVersion } -func archiveUpload(archive string, blobstore string, signer string) error { +func archiveUpload(archive string, blobstore string, signer string, signifyVar string) error { // If signing was requested, generate the signature files if signer != "" { key := getenvBase64(signer) @@ -433,6 +404,14 @@ func archiveUpload(archive string, blobstore string, signer string) error { return err } } + if signifyVar != "" { + key := os.Getenv(signifyVar) + untrustedComment := "verify with geth-release.pub" + trustedComment := fmt.Sprintf("%s (%s)", archive, time.Now().UTC().Format(time.RFC1123)) + if err := signify.SignFile(archive, archive+".sig", key, untrustedComment, trustedComment); err != nil { + return err + } + } // If uploading to Azure was requested, push the archive possibly with its signature if blobstore != "" { auth := build.AzureBlobstoreConfig{ @@ -448,6 +427,11 @@ func archiveUpload(archive string, blobstore string, signer string) error { return err } } + if signifyVar != "" { + if err := build.AzureBlobstoreUpload(archive+".sig", filepath.Base(archive+".sig"), auth); err != nil { + return err + } + } } return nil } @@ -455,15 +439,15 @@ func archiveUpload(archive string, blobstore string, signer string) error { // skips archiving for some build configurations. func maybeSkipArchive(env build.Environment) { if env.IsPullRequest { - log.Printf("skipping because this is a PR build") + log.Printf("skipping archive creation because this is a PR build") os.Exit(0) } if env.IsCronJob { - log.Printf("skipping because this is a cron job") + log.Printf("skipping archive creation because this is a cron job") os.Exit(0) } if env.Branch != "master" && !strings.HasPrefix(env.Tag, "v1.") { - log.Printf("skipping because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) + log.Printf("skipping archive creation because branch %q, tag %q is not on the whitelist", env.Branch, env.Tag) os.Exit(0) } } @@ -471,17 +455,17 @@ func maybeSkipArchive(env build.Environment) { // Debian Packaging func doDebianSource(cmdline []string) { 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`) - 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")`) - 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)`) - now = time.Now() + 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`) + 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")`) + workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) + now = time.Now() ) flag.CommandLine.Parse(cmdline) *workdir = makeWorkdir(*workdir) env := build.Env() + tc := new(build.GoToolchain) maybeSkipArchive(env) // Import the signing key. @@ -492,15 +476,15 @@ func doDebianSource(cmdline []string) { } // 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 - srcdepfetch := goTool("install", "-n", "./...") - srcdepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath")) + srcdepfetch := tc.Go("mod", "download") + srcdepfetch.Env = append(srcdepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) build.MustRun(srcdepfetch) - cidepfetch := goTool("run", "./build/ci.go") - cidepfetch.Env = append(os.Environ(), "GOPATH="+filepath.Join(*workdir, "modgopath")) + cidepfetch := tc.Go("run", "./build/ci.go") + cidepfetch.Env = append(cidepfetch.Env, "GOPATH="+filepath.Join(*workdir, "modgopath")) cidepfetch.Run() // Command fails, don't care, we only need the deps to start it // Create Debian packages and upload them. @@ -511,7 +495,7 @@ func doDebianSource(cmdline []string) { pkgdir := stageDebianSource(*workdir, meta) // 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) } if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".go")); err != nil { @@ -528,24 +512,26 @@ func doDebianSource(cmdline []string) { build.MustRun(debuild) var ( - basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) - source = filepath.Join(*workdir, basename+".tar.xz") - dsc = filepath.Join(*workdir, basename+".dsc") - changes = filepath.Join(*workdir, basename+"_source.changes") + basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString()) + source = filepath.Join(*workdir, basename+".tar.xz") + dsc = filepath.Join(*workdir, basename+".dsc") + changes = filepath.Join(*workdir, basename+"_source.changes") + buildinfo = filepath.Join(*workdir, basename+"_source.buildinfo") ) if *signer != "" { build.MustRunCommand("debsign", changes) } if *upload != "" { - ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes}) + ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes, buildinfo}) } } } } -func downloadGoSources(version string, cachedir string) string { +// downloadGoSources downloads the Go source tarball. +func downloadGoSources(cachedir string) string { 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 dst := filepath.Join(cachedir, file) if err := csdb.DownloadFile(url, dst); err != nil { @@ -749,6 +735,7 @@ func doWindowsInstaller(cmdline []string) { var ( arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging") signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`) + signify = flag.String("signify key", "", `Environment variable holding the signify signing key (e.g. WINDOWS_SIGNIFY_KEY)`) upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`) ) @@ -810,7 +797,7 @@ func doWindowsInstaller(cmdline []string) { filepath.Join(*workdir, "geth.nsi"), ) // Sign and publish installer. - if err := archiveUpload(installer, *upload, *signer); err != nil { + if err := archiveUpload(installer, *upload, *signer, *signify); err != nil { log.Fatal(err) } } @@ -819,25 +806,37 @@ func doWindowsInstaller(cmdline []string) { func doAndroidArchive(cmdline []string) { var ( - local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) - deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) - upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) + local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. ANDROID_SIGNIFY_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) + upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) ) flag.CommandLine.Parse(cmdline) env := build.Env() + tc := new(build.GoToolchain) // Sanity check that the SDK and NDK are installed and set if os.Getenv("ANDROID_HOME") == "" { log.Fatal("Please ensure ANDROID_HOME points to your Android SDK") } + + // Build gomobile. + install := tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest") + install.Env = append(install.Env) + build.MustRun(install) + + // Ensure all dependencies are available. This is required to make + // gomobile bind work because it expects go.sum to contain all checksums. + build.MustRun(tc.Go("mod", "download")) + // Build the Android archive and Maven resources - build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile")) if *local { // If we're building locally, copy bundle to build dir and skip Maven os.Rename("geth.aar", filepath.Join(GOBIN, "geth.aar")) + os.Rename("geth-sources.jar", filepath.Join(GOBIN, "geth-sources.jar")) return } meta := newMavenMetadata(env) @@ -850,7 +849,7 @@ func doAndroidArchive(cmdline []string) { archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar" os.Rename("geth.aar", archive) - if err := archiveUpload(archive, *upload, *signer); err != nil { + if err := archiveUpload(archive, *upload, *signer, *signify); err != nil { log.Fatal(err) } // Sign and upload all the artifacts to Maven Central @@ -884,11 +883,12 @@ func gomobileTool(subcmd string, args ...string) *exec.Cmd { "PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"), } for _, e := range os.Environ() { - if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") { + if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") || strings.HasPrefix(e, "GOBIN=") { continue } cmd.Env = append(cmd.Env, e) } + cmd.Env = append(cmd.Env, "GOBIN="+GOBIN) return cmd } @@ -942,45 +942,52 @@ func newMavenMetadata(env build.Environment) mavenMetadata { func doXCodeFramework(cmdline []string) { var ( - local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) - signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) - deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) - upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + local = flag.Bool("local", false, `Flag whether we're only doing a local build (skip Maven artifacts)`) + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) + signify = flag.String("signify", "", `Environment variable holding the signify signing key (e.g. IOS_SIGNIFY_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) ) flag.CommandLine.Parse(cmdline) env := build.Env() + tc := new(build.GoToolchain) + + // Build gomobile. + build.MustRun(tc.Install(GOBIN, "golang.org/x/mobile/cmd/gomobile@latest", "golang.org/x/mobile/cmd/gobind@latest")) + + // Ensure all dependencies are available. This is required to make + // gomobile bind work because it expects go.sum to contain all checksums. + build.MustRun(tc.Go("mod", "download")) // Build the iOS XCode framework - build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) - build.MustRun(gomobileTool("init")) bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "-v", "github.com/ethereum/go-ethereum/mobile") if *local { // If we're building locally, use the build folder and stop afterwards - bind.Dir, _ = filepath.Abs(GOBIN) + bind.Dir = GOBIN build.MustRun(bind) return } + + // Create the archive. + maybeSkipArchive(env) archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit)) - if err := os.Mkdir(archive, os.ModePerm); err != nil { + if err := os.MkdirAll(archive, 0755); err != nil { log.Fatal(err) } bind.Dir, _ = filepath.Abs(archive) build.MustRun(bind) build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) - // Skip CocoaPods deploy and Azure upload for PR builds - maybeSkipArchive(env) - // Sign and upload the framework to Azure - if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil { + if err := archiveUpload(archive+".tar.gz", *upload, *signer, *signify); err != nil { log.Fatal(err) } // Prepare and upload a PodSpec to CocoaPods if *deploy != "" { meta := newPodMetadata(env, archive) 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") } } @@ -1037,10 +1044,10 @@ func doXgo(cmdline []string) { ) flag.CommandLine.Parse(cmdline) env := build.Env() + var tc build.GoToolchain // Make sure xgo is available for cross compilation - gogetxgo := goTool("get", "github.com/karalabe/xgo") - build.MustRun(gogetxgo) + build.MustRun(tc.Install(GOBIN, "github.com/karalabe/xgo@latest")) // If all tools building is requested, build everything the builder wants args := append(buildFlags(env), flag.Args()...) @@ -1051,27 +1058,23 @@ func doXgo(cmdline []string) { if strings.HasPrefix(res, GOBIN) { // Binary tool found, cross build it explicitly args = append(args, "./"+filepath.Join("cmd", filepath.Base(res))) - xgo := xgoTool(args) - build.MustRun(xgo) + build.MustRun(xgoTool(args)) args = args[:len(args)-1] } } return } - // Otherwise xxecute the explicit cross compilation + + // Otherwise execute the explicit cross compilation path := args[len(args)-1] args = append(args[:len(args)-1], []string{"--dest", GOBIN, path}...) - - xgo := xgoTool(args) - build.MustRun(xgo) + build.MustRun(xgoTool(args)) } func xgoTool(args []string) *exec.Cmd { cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...) cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, []string{ - "GOBIN=" + GOBIN, - }...) + cmd.Env = append(cmd.Env, []string{"GOBIN=" + GOBIN}...) return cmd } @@ -1098,6 +1101,8 @@ func doPurge(cmdline []string) { if err != nil { log.Fatal(err) } + fmt.Printf("Found %d blobs\n", len(blobs)) + // Iterate over the blobs, collect and sort all unstable builds for i := 0; i < len(blobs); i++ { if !strings.Contains(blobs[i].Name, "unstable") { @@ -1119,6 +1124,7 @@ func doPurge(cmdline []string) { break } } + fmt.Printf("Deleting %d blobs\n", len(blobs)) // Delete all marked as such and return if err := build.AzureBlobstoreDelete(auth, blobs); err != nil { log.Fatal(err) diff --git a/build/nsis.envvarupdate.nsh b/build/nsis.envvarupdate.nsh index 9c3ecbe337..95c2f1f639 100644 --- a/build/nsis.envvarupdate.nsh +++ b/build/nsis.envvarupdate.nsh @@ -43,7 +43,7 @@ !ifndef Un${StrFuncName}_INCLUDED ${Un${StrFuncName}} !endif - !define un.${StrFuncName} "${Un${StrFuncName}}" + !define un.${StrFuncName} '${Un${StrFuncName}}' !macroend !insertmacro _IncludeStrFunction StrTok diff --git a/build/nsis.install.nsh b/build/nsis.install.nsh index 57ef5a37c6..9b73148a44 100644 --- a/build/nsis.install.nsh +++ b/build/nsis.install.nsh @@ -19,9 +19,9 @@ Section "Geth" GETH_IDX # Create start menu launcher createDirectory "$SMPROGRAMS\${APPNAME}" - createShortCut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\geth.exe" "--fast" "--cache=512" - createShortCut "$SMPROGRAMS\${APPNAME}\Attach.lnk" "$INSTDIR\geth.exe" "attach" "" "" - createShortCut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "" "" + createShortCut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\geth.exe" + createShortCut "$SMPROGRAMS\${APPNAME}\Attach.lnk" "$INSTDIR\geth.exe" "attach" + createShortCut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" # Firewall - remove rules (if exists) SimpleFC::AdvRemoveRule "Geth incoming peers (TCP:30303)" diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index ed4a3b8870..7b3b35e4e5 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1" ) @@ -95,12 +96,12 @@ var ( } aliasFlag = cli.StringFlag{ Name: "alias", - Usage: "Comma separated aliases for function and event renaming, e.g. foo=bar", + Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", } ) func init() { - app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") + app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") app.Flags = []cli.Flag{ abiFlag, binFlag, @@ -117,7 +118,7 @@ func init() { aliasFlag, } app.Action = utils.MigrateFlags(abigen) - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } func abigen(c *cli.Context) error { diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index f6e2a14c3b..036b968ef8 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/discover" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" @@ -44,7 +43,7 @@ func main() { natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:)") netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)") runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode") - verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-9)") + verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-5)") vmodule = flag.String("vmodule", "", "log verbosity pattern") nodeKey *ecdsa.PrivateKey @@ -121,17 +120,17 @@ func main() { printNotice(&nodeKey.PublicKey, *realaddr) + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) + cfg := discover.Config{ + PrivateKey: nodeKey, + NetRestrict: restrictList, + } if *runv5 { - if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil { + if _, err := discover.ListenV5(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } } else { - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, nodeKey) - cfg := discover.Config{ - PrivateKey: nodeKey, - NetRestrict: restrictList, - } if _, err := discover.ListenUDP(conn, ln, cfg); err != nil { utils.Fatalf("%v", err) } diff --git a/cmd/checkpoint-admin/main.go b/cmd/checkpoint-admin/main.go index b4d8e0db5a..0fb5532147 100644 --- a/cmd/checkpoint-admin/main.go +++ b/cmd/checkpoint-admin/main.go @@ -22,8 +22,8 @@ import ( "fmt" "os" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common/fdlimit" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1" ) @@ -37,7 +37,7 @@ var ( var app *cli.App func init() { - app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") + app = flags.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") app.Commands = []cli.Command{ commandStatus, commandDeploy, @@ -48,7 +48,7 @@ func init() { oracleFlag, nodeURLFlag, } - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } // Commonly used command line flags. diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 90afe8c8c8..27c62817f9 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -9,7 +9,7 @@ Clef can run as a daemon on the same machine, off a usb-stick like [USB armory]( Check out the * [CLI tutorial](tutorial.md) for some concrete examples on how Clef works. -* [Setup docs](docs/setup.md) for infos on how to configure Clef on QubesOS or USB Armory. +* [Setup docs](docs/setup.md) for information on how to configure Clef on QubesOS or USB Armory. * [Data types](datatypes.md) for details on the communication messages between Clef and an external UI. ## Command line flags @@ -33,12 +33,12 @@ GLOBAL OPTIONS: --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength --nousb Disables monitoring for and managing USB hardware wallets --pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm") - --rpcaddr value HTTP-RPC server listening interface (default: "localhost") - --rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") + --http.addr value HTTP-RPC server listening interface (default: "localhost") + --http.vhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") --ipcdisable Disable the IPC-RPC server --ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it) - --rpc Enable the HTTP-RPC server - --rpcport value HTTP-RPC server listening port (default: 8550) + --http Enable the HTTP-RPC server + --http.port value HTTP-RPC server listening port (default: 8550) --signersecret value A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash --4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json") --auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log") @@ -46,6 +46,7 @@ GLOBAL OPTIONS: --stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when Clef is started by an external process. --stdio-ui-test Mechanism to test interface between Clef and UI. Requires 'stdio-ui'. --advanced If enabled, issues warnings instead of rejections for suspicious requests. Default off + --suppress-bootwarn If set, does not show the warning during boot --help, -h show help --version, -v print the version ``` @@ -112,11 +113,11 @@ Some snags and todos ### External API -Clef listens to HTTP requests on `rpcaddr`:`rpcport` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification). +Clef listens to HTTP requests on `http.addr`:`http.port` (or to IPC on `ipcpath`), with the same JSON-RPC standard as Geth. The messages are expected to be [JSON-RPC 2.0 standard](https://www.jsonrpc.org/specification). -Some of these call can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a users decides to ignore the confirmation request. +Some of these calls can require user interaction. Clients must be aware that responses may be delayed significantly or may never be received if a user decides to ignore the confirmation request. -The External API is **untrusted**: it does not accept credentials over this API, nor does it expect that requests have any authority. +The External API is **untrusted**: it does not accept credentials, nor does it expect that requests have any authority. ### Internal UI API @@ -145,13 +146,11 @@ See the [external API changelog](extapi_changelog.md) for information about chan All hex encoded values must be prefixed with `0x`. -## Methods - ### account_new #### Create new password protected account -The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory. +The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and store it in the keystore directory. The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. #### Arguments @@ -160,7 +159,6 @@ None #### Result - address [string]: account address that is derived from the generated key - - url [string]: location of the keyfile #### Sample call ```json @@ -172,14 +170,11 @@ None } ``` Response -``` +```json { "id": 0, "jsonrpc": "2.0", - "result": { - "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", - "url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" - } + "result": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133" } ``` @@ -195,8 +190,6 @@ None #### Result - array with account records: - account.address [string]: account address that is derived from the generated key - - account.type [string]: type of the - - account.url [string]: location of the account #### Sample call ```json @@ -207,21 +200,13 @@ None } ``` Response -``` +```json { "id": 1, "jsonrpc": "2.0", "result": [ - { - "address": "0xafb2f771f58513609765698f65d3f2f0224a956f", - "type": "account", - "url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f" - }, - { - "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", - "type": "account", - "url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" - } + "0xafb2f771f58513609765698f65d3f2f0224a956f", + "0xbea9183f8f4f03d427f6bcea17388bdff1cab133" ] } ``` @@ -229,10 +214,10 @@ Response ### account_signTransaction #### Sign transactions - Signs a transactions and responds with the signed transaction in RLP encoded form. + Signs a transaction and responds with the signed transaction in RLP-encoded and JSON forms. #### Arguments - 2. transaction object: + 1. transaction object: - `from` [address]: account to send the transaction from - `to` [address]: receiver account. If omitted or `0x`, will cause contract creation. - `gas` [number]: maximum amount of gas to burn @@ -240,12 +225,13 @@ Response - `value` [number:optional]: amount of Wei to send with the transaction - `data` [data:optional]: input data - `nonce` [number]: account nonce - 3. method signature [string:optional] + 1. method signature [string:optional] - The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected. #### Result - - signed transaction in RLP encoded form [data] + - raw [data]: signed transaction in RLP encoded form + - tx [json]: signed transaction in JSON form #### Sample call ```json @@ -270,11 +256,22 @@ Response ```json { - "id": 2, "jsonrpc": "2.0", - "error": { - "code": -32000, - "message": "Request denied" + "id": 2, + "result": { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1234", + "gas": "0x55555", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x1234", + "input": "0xabcd", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } } } ``` @@ -326,7 +323,7 @@ Response Bash example: ```bash -#curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ +> curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} ``` @@ -373,7 +370,7 @@ Response ### account_signTypedData #### Sign data - Signs a chunk of structured data conformant to [EIP712]([EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)) and returns the calculated signature. + Signs a chunk of structured data conformant to [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and returns the calculated signature. #### Arguments - account [address]: account to sign with @@ -469,7 +466,7 @@ Response ### account_ecRecover -#### Sign data +#### Recover the signing address Derive the address from the account that was used to sign data with content type `text/plain` and the signature. @@ -487,7 +484,6 @@ Derive the address from the account that was used to sign data with content type "jsonrpc": "2.0", "method": "account_ecRecover", "params": [ - "data/plain", "0xaabbccdd", "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" ] @@ -503,117 +499,36 @@ Response } ``` -### account_import +### account_version -#### Import account - Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore - format. - -#### Arguments - - account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export) +#### Get external API version -#### Result - - imported key [object]: - - key.address [address]: address of the imported key - - key.type [string]: type of the account - - key.url [string]: key URL - -#### Sample call -```json -{ - "id": 6, - "jsonrpc": "2.0", - "method": "account_import", - "params": [ - { - "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "401c39a7c7af0388491c3d3ecb39f532" - }, - "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" - }, - "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" - }, - "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", - "version": 3 - } - ] -} -``` -Response - -```json -{ - "id": 6, - "jsonrpc": "2.0", - "result": { - "address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5", - "type": "account", - "url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5" - } -} -``` - -### account_export - -#### Export account from keystore - Export a private key from the keystore. The exported private key is encrypted with the original password. When the - key is imported later this password is required. +Get the version of the external API used by Clef. #### Arguments - - account [address]: export private key that is associated with this account + +None #### Result - - exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for - more information + +* external API version [string] #### Sample call ```json { - "id": 5, + "id": 0, "jsonrpc": "2.0", - "method": "account_export", - "params": [ - "0xc7412fc59930fd90099c917a50e5f11d0934b2f5" - ] + "method": "account_version", + "params": [] } ``` -Response +Response ```json { - "id": 5, - "jsonrpc": "2.0", - "result": { - "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "401c39a7c7af0388491c3d3ecb39f532" - }, - "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" - }, - "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" - }, - "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", - "version": 3 - } + "id": 0, + "jsonrpc": "2.0", + "result": "6.0.0" } ``` @@ -625,7 +540,7 @@ By starting the signer with the switch `--stdio-ui-test`, the signer will invoke denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented. See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'. -All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key. +All methods in this API use object-based parameters, so that there can be no mixup of parameters: each piece of data is accessed by key. See the [ui API changelog](intapi_changelog.md) for information about changes to this API. @@ -784,12 +699,10 @@ Invoked when a request for account listing has been made. { "accounts": [ { - "type": "Account", "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42", "address": "0x123409812340981234098123409812deadbeef42" }, { - "type": "Account", "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42", "address": "0xcafebabedeadbeef34098123409812deadbeef42" } @@ -819,7 +732,13 @@ Invoked when a request for account listing has been made. { "address": "0x123409812340981234098123409812deadbeef42", "raw_data": "0x01020304", - "message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "messages": [ + { + "name": "message", + "value": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "type": "text/plain" + } + ], "hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310", "meta": { "remote": "signer binary", @@ -829,12 +748,34 @@ Invoked when a request for account listing has been made. } ] } +``` + +### ApproveNewAccount / `ui_approveNewAccount` + +Invoked when a request for creating a new account has been made. +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "ui_approveNewAccount", + "params": [ + { + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} ``` ### ShowInfo / `ui_showInfo` -The UI should show the info to the user. Does not expect response. +The UI should show the info (a single message) to the user. Does not expect response. #### Sample call @@ -844,9 +785,7 @@ The UI should show the info to the user. Does not expect response. "id": 9, "method": "ui_showInfo", "params": [ - { - "text": "Tests completed" - } + "Tests completed" ] } @@ -854,18 +793,16 @@ The UI should show the info to the user. Does not expect response. ### ShowError / `ui_showError` -The UI should show the info to the user. Does not expect response. +The UI should show the error (a single message) to the user. Does not expect response. ```json { "jsonrpc": "2.0", "id": 2, - "method": "ShowError", + "method": "ui_showError", "params": [ - { - "text": "Testing 'ShowError'" - } + "Something bad happened!" ] } @@ -879,9 +816,36 @@ When implementing rate-limited rules, this callback should be used. TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`. +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onApprovedTx", + "params": [ + { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1", + "gas": "0x333", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } + } + ] +} +``` + ### OnSignerStartup / `ui_onSignerStartup` -This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external API, +This method provides the UI with information about what API version the signer uses (both internal and external) as well as build-info and external API, in k/v-form. Example call: @@ -905,6 +869,27 @@ Example call: ``` +### OnInputRequired / `ui_onInputRequired` + +Invoked when Clef requires user input (e.g. a password). + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ui_onInputRequired", + "params": [ + { + "title": "Account password", + "prompt": "Please enter the password for account 0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "isPassword": true + } + ] +} +``` + ### Rules for UI apis @@ -934,4 +919,4 @@ There are a couple of implementation for a UI. We'll try to keep this list up to | QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)| | GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: | | Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: | -| Clef UI| https://github.com/kyokan/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)| +| Clef UI| https://github.com/ethereum/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)| diff --git a/cmd/clef/datatypes.md b/cmd/clef/datatypes.md index 5ebf9adc97..dd8cda5846 100644 --- a/cmd/clef/datatypes.md +++ b/cmd/clef/datatypes.md @@ -3,7 +3,7 @@ These data types are defined in the channel between clef and the UI ### SignDataRequest -SignDataRequest contains information about a pending request to sign some data. The data to be signed can be of various types, defined by content-type. Clef has done most of the work in canonicalizing and making sense of the data, and it's up to the UI to presentthe user with the contents of the `message` +SignDataRequest contains information about a pending request to sign some data. The data to be signed can be of various types, defined by content-type. Clef has done most of the work in canonicalizing and making sense of the data, and it's up to the UI to present the user with the contents of the `message` Example: ```json diff --git a/cmd/clef/docs/setup.md b/cmd/clef/docs/setup.md index cbdd8808d3..6cc7a4120d 100644 --- a/cmd/clef/docs/setup.md +++ b/cmd/clef/docs/setup.md @@ -186,7 +186,7 @@ from other qubes. ## USBArmory -The [USB armory](https://inversepath.com/usbarmory) is an open source hardware design with an 800 Mhz ARM processor. It is a pocket-size +The [USB armory](https://inversepath.com/usbarmory) is an open source hardware design with an 800 MHz ARM processor. It is a pocket-size computer. When inserted into a laptop, it identifies itself as a USB network interface, basically adding another network to your computer. Over this new network interface, you can SSH into the device. diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md index dbc302631b..31554f0790 100644 --- a/cmd/clef/extapi_changelog.md +++ b/cmd/clef/extapi_changelog.md @@ -10,6 +10,64 @@ TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. +### 6.1.0 + +The API-method `account_signGnosisSafeTx` was added. This method takes two parameters, +`[address, safeTx]`. The latter, `safeTx`, can be copy-pasted from the gnosis relay. For example: + +``` +{ + "jsonrpc": "2.0", + "method": "account_signGnosisSafeTx", + "params": ["0xfd1c4226bfD1c436672092F4eCbfC270145b7256", + { + "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3", + "to": "0xB372a646f7F05Cc1785018dBDA7EBc734a2A20E2", + "value": "20000000000000000", + "data": null, + "operation": 0, + "gasToken": "0x0000000000000000000000000000000000000000", + "safeTxGas": 27845, + "baseGas": 0, + "gasPrice": "0", + "refundReceiver": "0x0000000000000000000000000000000000000000", + "nonce": 2, + "executionDate": null, + "submissionDate": "2020-09-15T21:54:49.617634Z", + "modified": "2020-09-15T21:54:49.617634Z", + "blockNumber": null, + "transactionHash": null, + "safeTxHash": "0x2edfbd5bc113ff18c0631595db32eb17182872d88d9bf8ee4d8c2dd5db6d95e2", + "executor": null, + "isExecuted": false, + "isSuccessful": null, + "ethGasPrice": null, + "gasUsed": null, + "fee": null, + "origin": null, + "dataDecoded": null, + "confirmationsRequired": null, + "confirmations": [ + { + "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34", + "submissionDate": "2020-09-15T21:54:49.663299Z", + "transactionHash": null, + "confirmationType": "CONFIRMATION", + "signature": "0x95a7250bb645f831c86defc847350e7faff815b2fb586282568e96cc859e39315876db20a2eed5f7a0412906ec5ab57652a6f645ad4833f345bda059b9da2b821c", + "signatureType": "EOA" + } + ], + "signatures": null + } + ], + "id": 67 +} +``` + +Not all fields are required, though. This method is really just a UX helper, which massages the +input to conform to the `EIP-712` [specification](https://docs.gnosis.io/safe/docs/contracts_tx_execution/#transaction-hash) +for the Gnosis Safe, and making the output be directly importable to by a relay service. + ### 6.0.0 diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md index f7e6993cf5..eaeb2e6862 100644 --- a/cmd/clef/intapi_changelog.md +++ b/cmd/clef/intapi_changelog.md @@ -12,7 +12,7 @@ Additional labels for pre-release and build metadata are available as extensions ### 7.0.1 -Added `clef_New` to the internal API calleable from a UI. +Added `clef_New` to the internal API callable from a UI. > `New` creates a new password protected Account. The private key is protected with > the given password. Users are responsible to backup the private key that is stored @@ -161,7 +161,7 @@ UserInputResponse struct { #### 1.2.0 * Add `OnStartup` method, to provide the UI with information about what API version -the signer uses (both internal and external) aswell as build-info and external api. +the signer uses (both internal and external) as well as build-info and external api. Example call: ```json diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 801c7e9efd..8befce88dc 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -29,9 +29,9 @@ import ( "math/big" "os" "os/signal" - "os/user" "path/filepath" "runtime" + "sort" "strings" "time" @@ -40,10 +40,10 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" @@ -53,7 +53,8 @@ import ( "github.com/ethereum/go-ethereum/signer/fourbyte" "github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/storage" - colorable "github.com/mattn/go-colorable" + + "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "gopkg.in/urfave/cli.v1" ) @@ -82,6 +83,10 @@ var ( Name: "advanced", Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off", } + acceptFlag = cli.BoolFlag{ + Name: "suppress-bootwarn", + Usage: "If set, does not show the warning during boot", + } keystoreFlag = cli.StringFlag{ Name: "keystore", Value: filepath.Join(node.DefaultDataDir(), "keystore"), @@ -98,7 +103,7 @@ var ( Usage: "Chain id to use for signing (1=mainnet, 3=Ropsten, 4=Rinkeby, 5=Goerli)", } rpcPortFlag = cli.IntFlag{ - Name: "rpcport", + Name: "http.port", Usage: "HTTP-RPC server listening port", Value: node.DefaultHTTPPort + 5, } @@ -196,6 +201,7 @@ The delpw command removes a password for a given address (keyfile). logLevelFlag, keystoreFlag, utils.LightKDFFlag, + acceptFlag, }, Description: ` The newaccount command creates a new keystore-backed account. It is a convenience-method @@ -211,6 +217,36 @@ The gendoc generates example structures of the json-rpc communication types. `} ) +// AppHelpFlagGroups is the application flags, grouped by functionality. +var AppHelpFlagGroups = []flags.FlagGroup{ + { + Name: "FLAGS", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + configdirFlag, + chainIdFlag, + utils.LightKDFFlag, + utils.NoUSBFlag, + utils.SmartCardDaemonPathFlag, + utils.HTTPListenAddrFlag, + utils.HTTPVirtualHostsFlag, + utils.IPCDisabledFlag, + utils.IPCPathFlag, + utils.HTTPEnabledFlag, + rpcPortFlag, + signerSecretFlag, + customDBFlag, + auditLogFlag, + ruleFlag, + stdiouiFlag, + testFlag, + advancedMode, + acceptFlag, + }, + }, +} + func init() { app.Name = "Clef" app.Usage = "Manage Ethereum account operations" @@ -222,11 +258,11 @@ func init() { utils.LightKDFFlag, utils.NoUSBFlag, utils.SmartCardDaemonPathFlag, - utils.RPCListenAddrFlag, - utils.RPCVirtualHostsFlag, + utils.HTTPListenAddrFlag, + utils.HTTPVirtualHostsFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, - utils.RPCEnabledFlag, + utils.HTTPEnabledFlag, rpcPortFlag, signerSecretFlag, customDBFlag, @@ -235,6 +271,7 @@ func init() { stdiouiFlag, testFlag, advancedMode, + acceptFlag, } app.Action = signer app.Commands = []cli.Command{initCommand, @@ -243,7 +280,41 @@ func init() { delCredentialCommand, newAccountCommand, gendocCommand} - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.CommandHelpTemplate + // Override the default app help template + cli.AppHelpTemplate = flags.ClefAppHelpTemplate + + // Override the default app help printer, but only for the global app help + originalHelpPrinter := cli.HelpPrinter + cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) { + if tmpl == flags.ClefAppHelpTemplate { + // Render out custom usage screen + originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups}) + } else if tmpl == flags.CommandHelpTemplate { + // Iterate over all command specific flags and categorize them + categorized := make(map[string][]cli.Flag) + for _, flag := range data.(cli.Command).Flags { + if _, ok := categorized[flag.String()]; !ok { + categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag) + } + } + + // sort to get a stable ordering + sorted := make([]flags.FlagGroup, 0, len(categorized)) + for cat, flgs := range categorized { + sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs}) + } + sort.Sort(flags.ByCategory(sorted)) + + // add sorted array to data and render with default printer + originalHelpPrinter(w, tmpl, map[string]interface{}{ + "cmd": data, + "categorizedFlags": sorted, + }) + } else { + originalHelpPrinter(w, tmpl, data) + } + } } func main() { @@ -283,7 +354,7 @@ func initializeSecrets(c *cli.Context) error { text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!" var password string for { - password = getPassPhrase(text, true) + password = utils.GetPassPhrase(text, true) if err := core.ValidatePasswordFormat(password); err != nil { fmt.Printf("invalid password: %v\n", err) } else { @@ -356,7 +427,7 @@ func setCredential(ctx *cli.Context) error { utils.Fatalf("Invalid address specified: %s", addr) } address := common.HexToAddress(addr) - password := getPassPhrase("Please enter a password to store for this address:", true) + password := utils.GetPassPhrase("Please enter a password to store for this address:", true) fmt.Println() stretchedKey, err := readMasterKey(ctx, nil) @@ -433,8 +504,10 @@ func initialize(c *cli.Context) error { if c.GlobalBool(stdiouiFlag.Name) { logOutput = os.Stderr // If using the stdioui, we can't do the 'confirm'-flow - fmt.Fprint(logOutput, legalWarning) - } else { + if !c.GlobalBool(acceptFlag.Name) { + fmt.Fprint(logOutput, legalWarning) + } + } else if !c.GlobalBool(acceptFlag.Name) { if !confirm(legalWarning) { return fmt.Errorf("aborted by user") } @@ -579,9 +652,9 @@ func signer(c *cli.Context) error { Service: api, Version: "1.0"}, } - if c.GlobalBool(utils.RPCEnabledFlag.Name) { - vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) - cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) + if c.GlobalBool(utils.HTTPEnabledFlag.Name) { + vhosts := utils.SplitAndTrim(c.GlobalString(utils.HTTPVirtualHostsFlag.Name)) + cors := utils.SplitAndTrim(c.GlobalString(utils.HTTPCORSDomainFlag.Name)) srv := rpc.NewServer() err := node.RegisterApisFromWhitelist(rpcAPI, []string{"account"}, srv, false) @@ -590,17 +663,21 @@ func signer(c *cli.Context) error { } handler := node.NewHTTPHandlerStack(srv, cors, vhosts) + // set port + port := c.Int(rpcPortFlag.Name) + // start http server - httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) - listener, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) + httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.HTTPListenAddrFlag.Name), port) + httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } - extapiURL = fmt.Sprintf("http://%v/", listener.Addr()) + extapiURL = fmt.Sprintf("http://%v/", addr) log.Info("HTTP endpoint opened", "url", extapiURL) defer func() { - listener.Close() + // Don't bother imposing a timeout here. + httpServer.Shutdown(context.Background()) log.Info("HTTP endpoint closed", "url", extapiURL) }() } @@ -640,21 +717,11 @@ func signer(c *cli.Context) error { return nil } -// splitAndTrim splits input separated by a comma -// and trims excessive white space from the substrings. -func splitAndTrim(input string) []string { - result := strings.Split(input, ",") - for i, r := range result { - result[i] = strings.TrimSpace(r) - } - return result -} - // DefaultConfigDir is the default config directory to use for the vaults and other // persistence requirements. func DefaultConfigDir() string { // Try to place the data folder in the user's home dir - home := homeDir() + home := utils.HomeDir() if home != "" { if runtime.GOOS == "darwin" { return filepath.Join(home, "Library", "Signer") @@ -662,26 +729,15 @@ func DefaultConfigDir() string { appdata := os.Getenv("APPDATA") if appdata != "" { return filepath.Join(appdata, "Signer") - } else { - return filepath.Join(home, "AppData", "Roaming", "Signer") } - } else { - return filepath.Join(home, ".clef") + return filepath.Join(home, "AppData", "Roaming", "Signer") } + return filepath.Join(home, ".clef") } // As we cannot guess a stable location, return empty and handle later return "" } -func homeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - if usr, err := user.Current(); err == nil { - return usr.HomeDir - } - return "" -} func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { var ( file string @@ -711,7 +767,7 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { } password = resp.Text } else { - password = getPassPhrase("Decrypt master seed of clef", false) + password = utils.GetPassPhrase("Decrypt master seed of clef", false) } masterSeed, err := decryptSeed(cipherKey, password) if err != nil { @@ -731,14 +787,16 @@ func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { // checkFile is a convenience function to check if a file // * exists -// * is mode 0400 +// * is mode 0400 (unix only) func checkFile(filename string) error { info, err := os.Stat(filename) if err != nil { return fmt.Errorf("failed stat on %s: %v", filename, err) } // Check the unix permission bits - if info.Mode().Perm()&0377 != 0 { + // However, on windows, we cannot use the unix perm-bits, see + // https://github.com/ethereum/go-ethereum/issues/20123 + if runtime.GOOS != "windows" && info.Mode().Perm()&0377 != 0 { return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) } return nil @@ -906,27 +964,6 @@ func testExternalUI(api *core.SignerAPI) { } -// getPassPhrase retrieves the password associated with clef, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -// TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one. -func getPassPhrase(prompt string, confirmation bool) string { - fmt.Println(prompt) - password, err := console.Stdin.PromptPassword("Password: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - if confirmation { - confirm, err := console.Stdin.PromptPassword("Repeat password: ") - if err != nil { - utils.Fatalf("Failed to read password confirmation: %v", err) - } - if password != confirm { - utils.Fatalf("Passwords do not match") - } - } - return password -} - type encryptedSeedStorage struct { Description string `json:"description"` Version int `json:"version"` @@ -977,7 +1014,7 @@ func GenDoc(ctx *cli.Context) { if data, err := json.MarshalIndent(v, "", " "); err == nil { output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data)) } else { - log.Error("Error generating output", err) + log.Error("Error generating output", "err", err) } } ) @@ -1069,7 +1106,7 @@ func GenDoc(ctx *cli.Context) { rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") var tx types.Transaction - rlp.DecodeBytes(rlpdata, &tx) + tx.UnmarshalBinary(rlpdata) add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) } diff --git a/cmd/clef/sign_flow.png b/cmd/clef/sign_flow.png index 93ef81a32e..e7010ab43f 100644 Binary files a/cmd/clef/sign_flow.png and b/cmd/clef/sign_flow.png differ diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md new file mode 100644 index 0000000000..7f816b602e --- /dev/null +++ b/cmd/devp2p/README.md @@ -0,0 +1,140 @@ +# The devp2p command + +The devp2p command line tool is a utility for low-level peer-to-peer debugging and +protocol development purposes. It can do many things. + +### ENR Decoding + +Use `devp2p enrdump ` to verify and display an Ethereum Node Record. + +### Node Key Management + +The `devp2p key ...` command family deals with node key files. + +Run `devp2p key generate mynode.key` to create a new node key in the `mynode.key` file. + +Run `devp2p key to-enode mynode.key -ip 127.0.0.1 -tcp 30303` to create an enode:// URL +corresponding to the given node key and address information. + +### Maintaining DNS Discovery Node Lists + +The devp2p command can create and publish DNS discovery node lists. + +Run `devp2p dns sign ` to update the signature of a DNS discovery tree. + +Run `devp2p dns sync ` to download a complete DNS discovery tree. + +Run `devp2p dns to-cloudflare ` to publish a tree to CloudFlare DNS. + +Run `devp2p dns to-route53 ` to publish a tree to Amazon Route53. + +You can find more information about these commands in the [DNS Discovery Setup Guide][dns-tutorial]. + +### Node Set Utilities + +There are several commands for working with JSON node set files. These files are generated +by the discovery crawlers and DNS client commands. Node sets also used as the input of the +DNS deployer commands. + +Run `devp2p nodeset info ` to display statistics of a node set. + +Run `devp2p nodeset filter ` to write a new, filtered node +set to standard output. The following filters are supported: + +- `-limit ` limits the output set to N entries, taking the top N nodes by score +- `-ip ` filters nodes by IP subnet +- `-min-age ` filters nodes by 'first seen' time +- `-eth-network ` filters nodes by "eth" ENR entry +- `-les-server` filters nodes by LES server support +- `-snap` filters nodes by snap protocol support + +For example, given a node set in `nodes.json`, you could create a filtered set containing +up to 20 eth mainnet nodes which also support snap sync using this command: + + devp2p nodeset filter nodes.json -eth-network mainnet -snap -limit 20 + +### Discovery v4 Utilities + +The `devp2p discv4 ...` command family deals with the [Node Discovery v4][discv4] +protocol. + +Run `devp2p discv4 ping ` to ping a node. + +Run `devp2p discv4 resolve ` to find the most recent node record of a node in +the DHT. + +Run `devp2p discv4 crawl ` to create or update a JSON node set. + +### Discovery v5 Utilities + +The `devp2p discv5 ...` command family deals with the [Node Discovery v5][discv5] +protocol. This protocol is currently under active development. + +Run `devp2p discv5 ping ` to ping a node. + +Run `devp2p discv5 resolve ` to find the most recent node record of a node in +the discv5 DHT. + +Run `devp2p discv5 listen` to run a Discovery v5 node. + +Run `devp2p discv5 crawl ` to create or update a JSON node set containing +discv5 nodes. + +### Discovery Test Suites + +The devp2p command also contains interactive test suites for Discovery v4 and Discovery +v5. + +To run these tests against your implementation, you need to set up a networking +environment where two separate UDP listening addresses are available on the same machine. +The two listening addresses must also be routed such that they are able to reach the node +you want to test. + +For example, if you want to run the test on your local host, and the node under test is +also on the local host, you need to assign two IP addresses (or a larger range) to your +loopback interface. On macOS, this can be done by executing the following command: + + sudo ifconfig lo0 add 127.0.0.2 + +You can now run either test suite as follows: Start the node under test first, ensuring +that it won't talk to the Internet (i.e. disable bootstrapping). An easy way to prevent +unintended connections to the global DHT is listening on `127.0.0.1`. + +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`. + +### 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 --nodiscover --nat=none --networkid 19763 --verbosity 5 +``` + +Then, run the following command, replacing `` with the enode of the geth node: + ``` + devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json +``` + +Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. + +#### Eth66 Test Suite + +The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically. +To run the eth66 protocol test suite, initialize a geth node as described above and run the following command, +replacing `` with the enode of the geth node: + + ``` + devp2p rlpx eth66-test cmd/devp2p/internal/ethtest/testdata/chain.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 +[discv4]: https://github.com/ethereum/devp2p/tree/master/discv4.md +[discv5]: https://github.com/ethereum/devp2p/tree/master/discv5/discv5.md diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 8580c61216..3b6dc09a1c 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/discover" @@ -40,6 +41,7 @@ var ( discv4ResolveCommand, discv4ResolveJSONCommand, discv4CrawlCommand, + discv4TestCommand, }, } discv4PingCommand = cli.Command{ @@ -74,6 +76,18 @@ var ( Action: discv4Crawl, Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, } + discv4TestCommand = cli.Command{ + Name: "test", + Usage: "Runs tests against a node", + Action: discv4Test, + Flags: []cli.Flag{ + remoteEnodeFlag, + testPatternFlag, + testTAPFlag, + testListen1Flag, + testListen2Flag, + }, + } ) var ( @@ -98,6 +112,11 @@ var ( Usage: "Time limit for the crawl.", Value: 30 * time.Minute, } + remoteEnodeFlag = cli.StringFlag{ + Name: "remote", + Usage: "Enode of the remote node under test", + EnvVar: "REMOTE_ENODE", + } ) func discv4Ping(ctx *cli.Context) error { @@ -184,6 +203,18 @@ func discv4Crawl(ctx *cli.Context) error { return nil } +// discv4Test runs the protocol test suite. +func discv4Test(ctx *cli.Context) error { + // Configure test package globals. + if !ctx.IsSet(remoteEnodeFlag.Name) { + return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) + } + v4test.Remote = ctx.String(remoteEnodeFlag.Name) + v4test.Listen1 = ctx.String(testListen1Flag.Name) + v4test.Listen2 = ctx.String(testListen2Flag.Name) + return runTests(ctx, v4test.AllTests) +} + // startV4 starts an ephemeral discovery V4 node. func startV4(ctx *cli.Context) *discover.UDPv4 { ln, config := makeDiscoveryConfig(ctx) @@ -235,7 +266,11 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { } usocket := socket.(*net.UDPConn) uaddr := socket.LocalAddr().(*net.UDPAddr) - ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + if uaddr.IP.IsUnspecified() { + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + } else { + ln.SetFallbackIP(uaddr.IP) + } ln.SetFallbackUDP(uaddr.Port) return usocket } @@ -243,7 +278,11 @@ func listen(ln *enode.LocalNode, addr string) *net.UDPConn { func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { s := params.RinkebyBootnodes if ctx.IsSet(bootnodesFlag.Name) { - s = strings.Split(ctx.String(bootnodesFlag.Name), ",") + input := ctx.String(bootnodesFlag.Name) + if input == "" { + return nil, nil + } + s = strings.Split(input, ",") } nodes := make([]*enode.Node, len(s)) var err error diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index f871821ea2..e20d7c9cfa 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -20,6 +20,7 @@ import ( "fmt" "time" + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v5test" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/p2p/discover" "gopkg.in/urfave/cli.v1" @@ -33,6 +34,7 @@ var ( discv5PingCommand, discv5ResolveCommand, discv5CrawlCommand, + discv5TestCommand, discv5ListenCommand, }, } @@ -53,6 +55,17 @@ var ( Action: discv5Crawl, Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, } + discv5TestCommand = cli.Command{ + Name: "test", + Usage: "Runs protocol tests against a node", + Action: discv5Test, + Flags: []cli.Flag{ + testPatternFlag, + testTAPFlag, + testListen1Flag, + testListen2Flag, + }, + } discv5ListenCommand = cli.Command{ Name: "listen", Usage: "Runs a node", @@ -103,6 +116,16 @@ func discv5Crawl(ctx *cli.Context) error { return nil } +// discv5Test runs the protocol test suite. +func discv5Test(ctx *cli.Context) error { + suite := &v5test.Suite{ + Dest: getNodeArg(ctx), + Listen1: ctx.String(testListen1Flag.Name), + Listen2: ctx.String(testListen2Flag.Name), + } + return runTests(ctx, suite.AllTests()) +} + func discv5Listen(ctx *cli.Context) error { disc := startV5(ctx) defer disc.Close() diff --git a/cmd/devp2p/dns_cloudflare.go b/cmd/devp2p/dns_cloudflare.go index a4d10dcfdd..596254df91 100644 --- a/cmd/devp2p/dns_cloudflare.go +++ b/cmd/devp2p/dns_cloudflare.go @@ -17,6 +17,7 @@ package main import ( + "context" "fmt" "strings" @@ -79,7 +80,7 @@ func (c *cloudflareClient) checkZone(name string) error { c.zoneID = id } log.Info(fmt.Sprintf("Checking Permissions on zone %s", c.zoneID)) - zone, err := c.ZoneDetails(c.zoneID) + zone, err := c.ZoneDetails(context.Background(), c.zoneID) if err != nil { return err } @@ -112,7 +113,7 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) records = lrecords log.Info(fmt.Sprintf("Retrieving existing TXT records on %s", name)) - entries, err := c.DNSRecords(c.zoneID, cloudflare.DNSRecord{Type: "TXT"}) + entries, err := c.DNSRecords(context.Background(), c.zoneID, cloudflare.DNSRecord{Type: "TXT"}) if err != nil { return err } @@ -134,14 +135,15 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) if path != name { ttl = treeNodeTTL // Max TTL permitted by Cloudflare } - _, err = c.CreateDNSRecord(c.zoneID, cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl}) + record := cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl} + _, err = c.CreateDNSRecord(context.Background(), c.zoneID, record) } else if old.Content != val { // Entry already exists, only change its content. log.Info(fmt.Sprintf("Updating %s from %q to %q", path, old.Content, val)) old.Content = val - err = c.UpdateDNSRecord(c.zoneID, old.ID, old) + err = c.UpdateDNSRecord(context.Background(), c.zoneID, old.ID, old) } else { - log.Info(fmt.Sprintf("Skipping %s = %q", path, val)) + log.Debug(fmt.Sprintf("Skipping %s = %q", path, val)) } if err != nil { return fmt.Errorf("failed to publish %s: %v", path, err) @@ -155,7 +157,7 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) } // Stale entry, nuke it. log.Info(fmt.Sprintf("Deleting %s = %q", path, entry.Content)) - if err := c.DeleteDNSRecord(c.zoneID, entry.ID); err != nil { + if err := c.DeleteDNSRecord(context.Background(), c.zoneID, entry.ID); err != nil { return fmt.Errorf("failed to delete %s: %v", path, err) } } diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index c5f99529b8..1d4f975dda 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -17,16 +17,19 @@ package main import ( + "context" "errors" "fmt" "sort" "strconv" "strings" + "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "gopkg.in/urfave/cli.v1" @@ -38,6 +41,7 @@ const ( // https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets route53ChangeSizeLimit = 32000 route53ChangeCountLimit = 1000 + maxRetryLimit = 60 ) var ( @@ -55,10 +59,15 @@ var ( Name: "zone-id", Usage: "Route53 Zone ID", } + route53RegionFlag = cli.StringFlag{ + Name: "aws-region", + Usage: "AWS Region", + Value: "eu-central-1", + } ) type route53Client struct { - api *route53.Route53 + api *route53.Client zoneID string } @@ -72,15 +81,16 @@ func newRoute53Client(ctx *cli.Context) *route53Client { akey := ctx.String(route53AccessKeyFlag.Name) asec := ctx.String(route53AccessSecretFlag.Name) if akey == "" || asec == "" { - exit(fmt.Errorf("need Route53 Access Key ID and secret proceed")) + exit(fmt.Errorf("need Route53 Access Key ID and secret to proceed")) } - config := &aws.Config{Credentials: credentials.NewStaticCredentials(akey, asec, "")} - session, err := session.NewSession(config) + creds := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(akey, asec, "")) + cfg, err := config.LoadDefaultConfig(context.Background(), config.WithCredentialsProvider(creds)) if err != nil { - exit(fmt.Errorf("can't create AWS session: %v", err)) + exit(fmt.Errorf("can't initialize AWS configuration: %v", err)) } + cfg.Region = ctx.String(route53RegionFlag.Name) return &route53Client{ - api: route53.New(session), + api: route53.NewFromConfig(cfg), zoneID: ctx.String(route53ZoneIDFlag.Name), } } @@ -97,31 +107,74 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { return err } log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) - records := t.ToTXT(name) changes := c.computeChanges(name, records, existing) + + // Submit to API. + comment := fmt.Sprintf("enrtree update of %s at seq %d", name, t.Seq()) + return c.submitChanges(changes, comment) +} + +// deleteDomain removes all TXT records of the given domain. +func (c *route53Client) deleteDomain(name string) error { + if err := c.checkZone(name); err != nil { + return err + } + + // Compute DNS changes. + existing, err := c.collectRecords(name) + if err != nil { + return err + } + log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) + changes := makeDeletionChanges(existing, nil) + + // Submit to API. + comment := "enrtree delete of " + name + return c.submitChanges(changes, comment) +} + +// submitChanges submits the given DNS changes to Route53. +func (c *route53Client) submitChanges(changes []types.Change, comment string) error { if len(changes) == 0 { log.Info("No DNS changes needed") return nil } - // Submit change batches. + var err error batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) + changesToCheck := make([]*route53.ChangeResourceRecordSetsOutput, len(batches)) for i, changes := range batches { log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) - batch := new(route53.ChangeBatch) - batch.SetChanges(changes) - batch.SetComment(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq())) + batch := &types.ChangeBatch{ + Changes: changes, + Comment: aws.String(fmt.Sprintf("%s (%d/%d)", comment, i+1, len(batches))), + } req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch} - resp, err := c.api.ChangeResourceRecordSets(req) + changesToCheck[i], err = c.api.ChangeResourceRecordSets(context.TODO(), req) if err != nil { return err } + } - log.Info(fmt.Sprintf("Waiting for change request %s", *resp.ChangeInfo.Id)) - wreq := &route53.GetChangeInput{Id: resp.ChangeInfo.Id} - if err := c.api.WaitUntilResourceRecordSetsChanged(wreq); err != nil { - return err + // Wait for all change batches to propagate. + for _, change := range changesToCheck { + log.Info(fmt.Sprintf("Waiting for change request %s", *change.ChangeInfo.Id)) + wreq := &route53.GetChangeInput{Id: change.ChangeInfo.Id} + var count int + for { + wresp, err := c.api.GetChange(context.TODO(), wreq) + if err != nil { + return err + } + + count++ + + if wresp.ChangeInfo.Status == types.ChangeStatusInsync || count >= maxRetryLimit { + break + } + + time.Sleep(30 * time.Second) } } return nil @@ -140,7 +193,7 @@ func (c *route53Client) findZoneID(name string) (string, error) { log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name)) var req route53.ListHostedZonesByNameInput for { - resp, err := c.api.ListHostedZonesByName(&req) + resp, err := c.api.ListHostedZonesByName(context.TODO(), &req) if err != nil { return "", err } @@ -149,7 +202,7 @@ func (c *route53Client) findZoneID(name string) (string, error) { return *zone.Id, nil } } - if !*resp.IsTruncated { + if !resp.IsTruncated { break } req.DNSName = resp.NextDNSName @@ -158,8 +211,9 @@ func (c *route53Client) findZoneID(name string) (string, error) { return "", errors.New("can't find zone ID for " + name) } -// computeChanges creates DNS changes for the given record. -func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []*route53.Change { +// computeChanges creates DNS changes for the given set of DNS discovery records. +// The 'existing' arg is the set of records that already exist on Route53. +func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []types.Change { // Convert all names to lowercase. lrecords := make(map[string]string, len(records)) for name, r := range records { @@ -167,58 +221,70 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e } records = lrecords - var changes []*route53.Change - for path, val := range records { + var changes []types.Change + for path, newValue := range records { + prevRecords, exists := existing[path] + prevValue := strings.Join(prevRecords.values, "") + + // prevValue contains quoted strings, encode newValue to compare. + newValue = splitTXT(newValue) + + // Assign TTL. ttl := int64(rootTTL) if path != name { ttl = int64(treeNodeTTL) } - prevRecords, exists := existing[path] - prevValue := strings.Join(prevRecords.values, "") if !exists { // Entry is unknown, push a new one - log.Info(fmt.Sprintf("Creating %s = %q", path, val)) - changes = append(changes, newTXTChange("CREATE", path, ttl, splitTXT(val))) - } else if prevValue != val || prevRecords.ttl != ttl { + log.Info(fmt.Sprintf("Creating %s = %s", path, newValue)) + changes = append(changes, newTXTChange("CREATE", path, ttl, newValue)) + } else if prevValue != newValue || prevRecords.ttl != ttl { // Entry already exists, only change its content. - log.Info(fmt.Sprintf("Updating %s from %q to %q", path, prevValue, val)) - changes = append(changes, newTXTChange("UPSERT", path, ttl, splitTXT(val))) + log.Info(fmt.Sprintf("Updating %s from %s to %s", path, prevValue, newValue)) + changes = append(changes, newTXTChange("UPSERT", path, ttl, newValue)) } else { - log.Info(fmt.Sprintf("Skipping %s = %q", path, val)) + log.Debug(fmt.Sprintf("Skipping %s = %s", path, newValue)) } } // Iterate over the old records and delete anything stale. - for path, set := range existing { - if _, ok := records[path]; ok { + changes = append(changes, makeDeletionChanges(existing, records)...) + + // Ensure changes are in the correct order. + sortChanges(changes) + return changes +} + +// makeDeletionChanges creates record changes which delete all records not contained in 'keep'. +func makeDeletionChanges(records map[string]recordSet, keep map[string]string) []types.Change { + var changes []types.Change + for path, set := range records { + if _, ok := keep[path]; ok { continue } - // Stale entry, nuke it. - log.Info(fmt.Sprintf("Deleting %s = %q", path, strings.Join(set.values, ""))) + log.Info(fmt.Sprintf("Deleting %s = %s", path, strings.Join(set.values, ""))) changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values...)) } - - sortChanges(changes) return changes } // sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order. -func sortChanges(changes []*route53.Change) { +func sortChanges(changes []types.Change) { score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3} sort.Slice(changes, func(i, j int) bool { - if *changes[i].Action == *changes[j].Action { + if changes[i].Action == changes[j].Action { return *changes[i].ResourceRecordSet.Name < *changes[j].ResourceRecordSet.Name } - return score[*changes[i].Action] < score[*changes[j].Action] + return score[string(changes[i].Action)] < score[string(changes[j].Action)] }) } // splitChanges splits up DNS changes such that each change batch // is smaller than the given RDATA limit. -func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*route53.Change { +func splitChanges(changes []types.Change, sizeLimit, countLimit int) [][]types.Change { var ( - batches [][]*route53.Change + batches [][]types.Change batchSize int batchCount int ) @@ -241,7 +307,7 @@ func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*rou } // changeSize returns the RDATA size of a DNS change. -func changeSize(ch *route53.Change) int { +func changeSize(ch types.Change) int { size := 0 for _, rr := range ch.ResourceRecordSet.ResourceRecords { if rr.Value != nil { @@ -251,8 +317,8 @@ func changeSize(ch *route53.Change) int { return size } -func changeCount(ch *route53.Change) int { - if *ch.Action == "UPSERT" { +func changeCount(ch types.Change) int { + if ch.Action == types.ChangeActionUpsert { return 2 } return 1 @@ -260,13 +326,17 @@ func changeCount(ch *route53.Change) int { // collectRecords collects all TXT records below the given name. func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) { - log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID)) var req route53.ListResourceRecordSetsInput - req.SetHostedZoneId(c.zoneID) + req.HostedZoneId = &c.zoneID existing := make(map[string]recordSet) - err := c.api.ListResourceRecordSetsPages(&req, func(resp *route53.ListResourceRecordSetsOutput, last bool) bool { + for page := 0; ; page++ { + log.Info("Loading existing TXT records", "name", name, "zone", c.zoneID, "page", page) + resp, err := c.api.ListResourceRecordSets(context.TODO(), &req) + if err != nil { + return existing, err + } for _, set := range resp.ResourceRecordSets { - if !isSubdomain(*set.Name, name) || *set.Type != "TXT" { + if !isSubdomain(*set.Name, name) || set.Type != types.RRTypeTxt { continue } s := recordSet{ttl: *set.TTL} @@ -276,28 +346,44 @@ func (c *route53Client) collectRecords(name string) (map[string]recordSet, error name := strings.TrimSuffix(*set.Name, ".") existing[name] = s } - return true - }) - return existing, err + + if !resp.IsTruncated { + break + } + // Set the cursor to the next batch. From the AWS docs: + // + // To display the next page of results, get the values of NextRecordName, + // NextRecordType, and NextRecordIdentifier (if any) from the response. Then submit + // another ListResourceRecordSets request, and specify those values for + // StartRecordName, StartRecordType, and StartRecordIdentifier. + req.StartRecordIdentifier = resp.NextRecordIdentifier + req.StartRecordName = resp.NextRecordName + req.StartRecordType = resp.NextRecordType + } + + return existing, nil } // newTXTChange creates a change to a TXT record. -func newTXTChange(action, name string, ttl int64, values ...string) *route53.Change { - var c route53.Change - var r route53.ResourceRecordSet - var rrs []*route53.ResourceRecord +func newTXTChange(action, name string, ttl int64, values ...string) types.Change { + r := types.ResourceRecordSet{ + Type: types.RRTypeTxt, + Name: &name, + TTL: &ttl, + } + var rrs []types.ResourceRecord for _, val := range values { - rr := new(route53.ResourceRecord) - rr.SetValue(val) + var rr types.ResourceRecord + rr.Value = aws.String(val) rrs = append(rrs, rr) } - r.SetType("TXT") - r.SetName(name) - r.SetTTL(ttl) - r.SetResourceRecords(rrs) - c.SetAction(action) - c.SetResourceRecordSet(&r) - return &c + + r.ResourceRecords = rrs + + return types.Change{ + Action: types.ChangeAction(action), + ResourceRecordSet: &r, + } } // isSubdomain returns true if name is a subdomain of domain. diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go index a2ef3791f6..e6eb516e6b 100644 --- a/cmd/devp2p/dns_route53_test.go +++ b/cmd/devp2p/dns_route53_test.go @@ -20,7 +20,7 @@ import ( "reflect" "testing" - "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go-v2/service/route53/types" ) // This test checks that computeChanges/splitChanges create DNS changes in @@ -43,93 +43,93 @@ func TestRoute53ChangeSort(t *testing.T) { "MHTDO6TMUBRIA2XWG5LUDACK24.n": "enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o", } - wantChanges := []*route53.Change{ + wantChanges := []types.Change{ { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("2xs2367yhaxjfglzhvawlqd4zy.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("c7hrfpf3blgf3yr4dy5kx3smbe.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@morenodes.example.org"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("h4fht4b454p6uxfd7jcyq5pwdy.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enr:-HW4QAggRauloj2SDLtIHN1XBkvhFZ1vtf1raYQp9TBW2RD5EEawDzbtSmlXUfnaHcvwOizhVYLtr7e6vw7NAf6mTuoCgmlkgnY0iXNlY3AyNTZrMaECjrXI8TLNXU0f8cthpAMxEshUyQlK-AM0PW2wfrnacNI"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("jwxydbpxywg6fx3gmdibfa6cj4.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree-branch:2XS2367YHAXJFGLZHVAWLQD4ZY,H4FHT4B454P6UXFD7JCYQ5PWDY,MHTDO6TMUBRIA2XWG5LUDACK24"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "CREATE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("mhtdo6tmubria2xwg5ludack24.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enr:-HW4QLAYqmrwllBEnzWWs7I5Ev2IAs7x_dZlbYdRdMUx5EyKHDXp7AV5CkuPGUPdvbv1_Ms1CPfhcGCvSElSosZmyoqAgmlkgnY0iXNlY3AyNTZrMaECriawHKWdDRk2xeZkrOXBQ0dfMFLHY4eENZwdufn1S1o"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("UPSERT"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "UPSERT", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`), }}, TTL: ip(rootTTL), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("DELETE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "DELETE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("2kfjogvxdqtxxugbh7gs7naaai.n"), - ResourceRecords: []*route53.ResourceRecord{ + ResourceRecords: []types.ResourceRecord{ {Value: sp(`"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`)}, }, TTL: ip(3333), - Type: sp("TXT"), + Type: "TXT", }, }, { - Action: sp("DELETE"), - ResourceRecordSet: &route53.ResourceRecordSet{ + Action: "DELETE", + ResourceRecordSet: &types.ResourceRecordSet{ Name: sp("fdxn3sn67na5dka4j2gok7bvqi.n"), - ResourceRecords: []*route53.ResourceRecord{{ + ResourceRecords: []types.ResourceRecord{{ Value: sp(`"enrtree-branch:"`), }}, TTL: ip(treeNodeTTL), - Type: sp("TXT"), + Type: "TXT", }, }, } @@ -141,7 +141,7 @@ func TestRoute53ChangeSort(t *testing.T) { } // Check splitting according to size. - wantSplit := [][]*route53.Change{ + wantSplit := [][]types.Change{ wantChanges[:4], wantChanges[4:6], wantChanges[6:], @@ -152,7 +152,7 @@ func TestRoute53ChangeSort(t *testing.T) { } // Check splitting according to count. - wantSplit = [][]*route53.Change{ + wantSplit = [][]types.Change{ wantChanges[:5], wantChanges[5:], } @@ -162,5 +162,29 @@ func TestRoute53ChangeSort(t *testing.T) { } } +// This test checks that computeChanges compares the quoted value of the records correctly. +func TestRoute53NoChange(t *testing.T) { + // Existing record set. + testTree0 := map[string]recordSet{ + "n": {ttl: rootTTL, values: []string{ + `"enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA"`, + }}, + "2xs2367yhaxjfglzhvawlqd4zy.n": {ttl: treeNodeTTL, values: []string{ + `"enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA"`, + }}, + } + // New set. + testTree1 := map[string]string{ + "n": "enrtree-root:v1 e=JWXYDBPXYWG6FX3GMDIBFA6CJ4 l=C7HRFPF3BLGF3YR4DY5KX3SMBE seq=1 sig=o908WmNp7LibOfPsr4btQwatZJ5URBr2ZAuxvK4UWHlsB9sUOTJQaGAlLPVAhM__XJesCHxLISo94z5Z2a463gA", + "2XS2367YHAXJFGLZHVAWLQD4ZY.n": "enr:-HW4QOFzoVLaFJnNhbgMoDXPnOvcdVuj7pDpqRvh6BRDO68aVi5ZcjB3vzQRZH2IcLBGHzo8uUN3snqmgTiE56CH3AMBgmlkgnY0iXNlY3AyNTZrMaECC2_24YYkYHEgdzxlSNKQEnHhuNAbNlMlWJxrJxbAFvA", + } + + var client route53Client + changes := client.computeChanges("n", testTree1, testTree0) + if len(changes) > 0 { + t.Fatalf("wrong changes (got %d, want 0)", len(changes)) + } +} + func sp(s string) *string { return &s } func ip(i int64) *int64 { return &i } diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index 8ca31504d3..66deef56ea 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -27,10 +27,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/ethereum/go-ethereum/p2p/enode" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var ( @@ -43,6 +43,7 @@ var ( dnsTXTCommand, dnsCloudflareCommand, dnsRoute53Command, + dnsRoute53NukeCommand, }, } dnsSyncCommand = cli.Command{ @@ -77,7 +78,24 @@ var ( Usage: "Deploy DNS TXT records to Amazon Route53", ArgsUsage: "", Action: dnsToRoute53, - Flags: []cli.Flag{route53AccessKeyFlag, route53AccessSecretFlag, route53ZoneIDFlag}, + Flags: []cli.Flag{ + route53AccessKeyFlag, + route53AccessSecretFlag, + route53ZoneIDFlag, + route53RegionFlag, + }, + } + dnsRoute53NukeCommand = cli.Command{ + Name: "nuke-route53", + Usage: "Deletes DNS TXT records of a subdomain on Amazon Route53", + ArgsUsage: "", + Action: dnsNukeRoute53, + Flags: []cli.Flag{ + route53AccessKeyFlag, + route53AccessSecretFlag, + route53ZoneIDFlag, + route53RegionFlag, + }, } ) @@ -169,6 +187,9 @@ func dnsSign(ctx *cli.Context) error { return nil } +// directoryName returns the directory name of the given path. +// For example, when dir is "foo/bar", it returns "bar". +// When dir is ".", and the working directory is "example/foo", it returns "foo". func directoryName(dir string) string { abs, err := filepath.Abs(dir) if err != nil { @@ -177,7 +198,7 @@ func directoryName(dir string) string { return filepath.Base(abs) } -// dnsToTXT peforms dnsTXTCommand. +// dnsToTXT performs dnsTXTCommand. func dnsToTXT(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need tree definition directory as argument") @@ -194,9 +215,9 @@ func dnsToTXT(ctx *cli.Context) error { return nil } -// dnsToCloudflare peforms dnsCloudflareCommand. +// dnsToCloudflare performs dnsCloudflareCommand. func dnsToCloudflare(ctx *cli.Context) error { - if ctx.NArg() < 1 { + if ctx.NArg() != 1 { return fmt.Errorf("need tree definition directory as argument") } domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) @@ -207,9 +228,9 @@ func dnsToCloudflare(ctx *cli.Context) error { return client.deploy(domain, t) } -// dnsToRoute53 peforms dnsRoute53Command. +// dnsToRoute53 performs dnsRoute53Command. func dnsToRoute53(ctx *cli.Context) error { - if ctx.NArg() < 1 { + if ctx.NArg() != 1 { return fmt.Errorf("need tree definition directory as argument") } domain, t, err := loadTreeDefinitionForExport(ctx.Args().Get(0)) @@ -220,13 +241,22 @@ func dnsToRoute53(ctx *cli.Context) error { return client.deploy(domain, t) } +// dnsNukeRoute53 performs dnsRoute53NukeCommand. +func dnsNukeRoute53(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("need domain name as argument") + } + client := newRoute53Client(ctx) + return client.deleteDomain(ctx.Args().First()) +} + // loadSigningKey loads a private key in Ethereum keystore format. func loadSigningKey(keyfile string) *ecdsa.PrivateKey { keyjson, err := ioutil.ReadFile(keyfile) if err != nil { exit(fmt.Errorf("failed to read the keyfile at '%s': %v", keyfile, err)) } - password, _ := console.Stdin.PromptPassword("Please enter the password for '" + keyfile + "': ") + password, _ := prompt.Stdin.PromptPassword("Please enter the password for '" + keyfile + "': ") key, err := keystore.DecryptKey(keyjson, password) if err != nil { exit(fmt.Errorf("error decrypting key: %v", err)) diff --git a/cmd/devp2p/enrcmd.go b/cmd/devp2p/enrcmd.go index 15d77dd011..48ede616ee 100644 --- a/cmd/devp2p/enrcmd.go +++ b/cmd/devp2p/enrcmd.go @@ -21,6 +21,7 @@ import ( "encoding/base64" "encoding/hex" "fmt" + "io" "io/ioutil" "net" "os" @@ -69,22 +70,30 @@ func enrdump(ctx *cli.Context) error { if err != nil { return fmt.Errorf("INVALID: %v", err) } - fmt.Print(dumpRecord(r)) + dumpRecord(os.Stdout, r) return nil } // dumpRecord creates a human-readable description of the given node record. -func dumpRecord(r *enr.Record) string { - out := new(bytes.Buffer) - if n, err := enode.New(enode.ValidSchemes, r); err != nil { +func dumpRecord(out io.Writer, r *enr.Record) { + n, err := enode.New(enode.ValidSchemes, r) + if err != nil { fmt.Fprintf(out, "INVALID: %v\n", err) } else { fmt.Fprintf(out, "Node ID: %v\n", n.ID()) + dumpNodeURL(out, n) } kv := r.AppendElements(nil)[1:] fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2) fmt.Fprint(out, dumpRecordKV(kv, 2)) - return out.String() +} + +func dumpNodeURL(out io.Writer, n *enode.Node) { + var key enode.Secp256k1 + if n.Load(&key) != nil { + return // no secp256k1 public key + } + fmt.Fprintf(out, "URLv4: %s\n", n.URLv4()) } func dumpRecordKV(kv []interface{}, indent int) string { diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go new file mode 100644 index 0000000000..83c55181ad --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -0,0 +1,184 @@ +// 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 . + +package ethtest + +import ( + "compress/gzip" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "math/big" + "os" + "strings" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +type Chain struct { + genesis core.Genesis + blocks []*types.Block + chainConfig *params.ChainConfig +} + +func (c *Chain) WriteTo(writer io.Writer) error { + for _, block := range c.blocks { + if err := rlp.Encode(writer, block); err != nil { + return err + } + } + + return nil +} + +// Len returns the length of the chain. +func (c *Chain) Len() int { + return len(c.blocks) +} + +// TD calculates the total difficulty of the chain. +func (c *Chain) TD(height int) *big.Int { // TODO later on channge scheme so that the height is included in range + sum := big.NewInt(0) + for _, block := range c.blocks[:height] { + sum.Add(sum, block.Difficulty()) + } + return sum +} + +// ForkID gets the fork id of the chain. +func (c *Chain) ForkID() forkid.ID { + return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len())) +} + +// Shorten returns a copy chain of a desired height from the imported +func (c *Chain) Shorten(height int) *Chain { + blocks := make([]*types.Block, height) + copy(blocks, c.blocks[:height]) + + config := *c.chainConfig + return &Chain{ + blocks: blocks, + chainConfig: &config, + } +} + +// Head returns the chain head. +func (c *Chain) Head() *types.Block { + return c.blocks[c.Len()-1] +} + +func (c *Chain) GetHeaders(req GetBlockHeaders) (BlockHeaders, error) { + if req.Amount < 1 { + return nil, fmt.Errorf("no block headers requested") + } + + headers := make(BlockHeaders, req.Amount) + var blockNumber uint64 + + // range over blocks to check if our chain has the requested header + for _, block := range c.blocks { + if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number { + headers[0] = block.Header() + blockNumber = block.Number().Uint64() + } + } + if headers[0] == nil { + return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash) + } + + if req.Reverse { + for i := 1; i < int(req.Amount); i++ { + blockNumber -= (1 - req.Skip) + headers[i] = c.blocks[blockNumber].Header() + + } + + return headers, nil + } + + for i := 1; i < int(req.Amount); i++ { + blockNumber += (1 + req.Skip) + headers[i] = c.blocks[blockNumber].Header() + } + + return headers, nil +} + +// loadChain takes the given chain.rlp file, and decodes and returns +// the blocks from the file. +func loadChain(chainfile string, genesis string) (*Chain, error) { + gen, err := loadGenesis(genesis) + if err != nil { + return nil, err + } + gblock := gen.ToBlock(nil) + + blocks, err := blocksFromFile(chainfile, gblock) + if err != nil { + return nil, err + } + + c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config} + return c, nil +} + +func loadGenesis(genesisFile string) (core.Genesis, error) { + chainConfig, err := ioutil.ReadFile(genesisFile) + if err != nil { + return core.Genesis{}, err + } + var gen core.Genesis + if err := json.Unmarshal(chainConfig, &gen); err != nil { + return core.Genesis{}, err + } + return gen, nil +} + +func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) { + // Load chain.rlp. + 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 = 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) + } + return blocks, nil +} diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go new file mode 100644 index 0000000000..ec98833ab5 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -0,0 +1,201 @@ +// 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 . + +package ethtest + +import ( + "path/filepath" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/p2p" + "github.com/stretchr/testify/assert" +) + +// TestEthProtocolNegotiation tests whether the test suite +// can negotiate the highest eth protocol in a status message exchange +func TestEthProtocolNegotiation(t *testing.T) { + var tests = []struct { + conn *Conn + caps []p2p.Cap + expected uint32 + }{ + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 64, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + expected: 64, + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 0}, + {Name: "eth", Version: 89}, + {Name: "eth", Version: 65}, + }, + expected: uint32(65), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 64, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + { + conn: &Conn{ + ourHighestProtoVersion: 65, + }, + caps: []p2p.Cap{ + {Name: "eth", Version: 63}, + {Name: "eth", Version: 64}, + {Name: "wrongProto", Version: 65}, + }, + expected: uint32(64), + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + tt.conn.negotiateEthProtocol(tt.caps) + assert.Equal(t, tt.expected, uint32(tt.conn.negotiatedProtoVersion)) + }) + } +} + +// TestChain_GetHeaders tests whether the test suite can correctly +// respond to a GetBlockHeaders request from a node. +func TestChain_GetHeaders(t *testing.T) { + chainFile, err := filepath.Abs("./testdata/chain.rlp") + if err != nil { + t.Fatal(err) + } + genesisFile, err := filepath.Abs("./testdata/genesis.json") + if err != nil { + t.Fatal(err) + } + + chain, err := loadChain(chainFile, genesisFile) + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + req GetBlockHeaders + expected BlockHeaders + }{ + { + req: GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Number: uint64(2), + }, + Amount: uint64(5), + Skip: 1, + Reverse: false, + }, + expected: BlockHeaders{ + chain.blocks[2].Header(), + chain.blocks[4].Header(), + chain.blocks[6].Header(), + chain.blocks[8].Header(), + chain.blocks[10].Header(), + }, + }, + { + req: GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Number: uint64(chain.Len() - 1), + }, + Amount: uint64(3), + Skip: 0, + Reverse: true, + }, + expected: BlockHeaders{ + chain.blocks[chain.Len()-1].Header(), + chain.blocks[chain.Len()-2].Header(), + chain.blocks[chain.Len()-3].Header(), + }, + }, + { + req: GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Hash: chain.Head().Hash(), + }, + Amount: uint64(1), + Skip: 0, + Reverse: false, + }, + expected: BlockHeaders{ + chain.Head().Header(), + }, + }, + } + + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + headers, err := chain.GetHeaders(tt.req) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, headers, tt.expected) + }) + } +} diff --git a/cmd/devp2p/internal/ethtest/eth66_suite.go b/cmd/devp2p/internal/ethtest/eth66_suite.go new file mode 100644 index 0000000000..903a90c7eb --- /dev/null +++ b/cmd/devp2p/internal/ethtest/eth66_suite.go @@ -0,0 +1,521 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" +) + +// Is_66 checks if the node supports the eth66 protocol version, +// and if not, exists the test suite +func (s *Suite) Is_66(t *utesting.T) { + conn := s.dial66(t) + conn.handshake(t) + if conn.negotiatedProtoVersion < 66 { + t.Fail() + } +} + +// TestStatus_66 attempts to connect to the given node and exchange +// a status message with it on the eth66 protocol, and then check to +// make sure the chain head is correct. +func (s *Suite) TestStatus_66(t *utesting.T) { + conn := s.dial66(t) + defer conn.Close() + // get protoHandshake + conn.handshake(t) + // get status + switch msg := conn.statusExchange66(t, s.chain).(type) { + case *Status: + status := *msg + if status.ProtocolVersion != uint32(66) { + t.Fatalf("mismatch in version: wanted 66, got %d", status.ProtocolVersion) + } + t.Logf("got status message: %s", pretty.Sdump(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestGetBlockHeaders_66 tests whether the given node can respond to +// an eth66 `GetBlockHeaders` request and that the response is accurate. +func (s *Suite) TestGetBlockHeaders_66(t *utesting.T) { + conn := s.setupConnection66(t) + defer conn.Close() + // get block headers + req := ð.GetBlockHeadersPacket66{ + RequestId: 3, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + // write message + headers, err := s.getBlockHeaders66(conn, req, req.RequestId) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + } + // check for correct headers + if !headersMatch(t, s.chain, headers) { + t.Fatal("received wrong header(s)") + } +} + +// TestSimultaneousRequests_66 sends two simultaneous `GetBlockHeader` requests +// with different request IDs and checks to make sure the node responds with the correct +// headers per request. +func (s *Suite) TestSimultaneousRequests_66(t *utesting.T) { + // create two connections + conn := s.setupConnection66(t) + defer conn.Close() + // create two requests + req1 := ð.GetBlockHeadersPacket66{ + RequestId: 111, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + }, + } + req2 := ð.GetBlockHeadersPacket66{ + RequestId: 222, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 4, + Skip: 1, + Reverse: false, + }, + } + // write first request + if err := conn.write66(req1, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // write second request + if err := conn.write66(req2, GetBlockHeaders{}.Code()); err != nil { + t.Fatalf("failed to write to connection: %v", err) + } + // wait for responses + headers1, err := s.waitForBlockHeadersResponse66(conn, req1.RequestId) + if err != nil { + t.Fatalf("error while waiting for block headers: %v", err) + } + headers2, err := s.waitForBlockHeadersResponse66(conn, req2.RequestId) + if err != nil { + t.Fatalf("error while waiting for block headers: %v", err) + } + // check headers of both responses + if !headersMatch(t, s.chain, headers1) { + t.Fatalf("wrong header(s) in response to req1: got %v", headers1) + } + if !headersMatch(t, s.chain, headers2) { + t.Fatalf("wrong header(s) in response to req2: got %v", headers2) + } +} + +// TestBroadcast_66 tests whether a block announcement is correctly +// propagated to the given node's peer(s) on the eth66 protocol. +func (s *Suite) TestBroadcast_66(t *utesting.T) { + s.sendNextBlock66(t) +} + +// TestGetBlockBodies_66 tests whether the given node can respond to +// a `GetBlockBodies` request and that the response is accurate over +// the eth66 protocol. +func (s *Suite) TestGetBlockBodies_66(t *utesting.T) { + conn := s.setupConnection66(t) + defer conn.Close() + // create block bodies request + id := uint64(55) + req := ð.GetBlockBodiesPacket66{ + RequestId: id, + GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + }, + } + if err := conn.write66(req, GetBlockBodies{}.Code()); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + reqID, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case BlockBodies: + if reqID != req.RequestId { + t.Fatalf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) + } + t.Logf("received %d block bodies", len(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestLargeAnnounce_66 tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce_66(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ + { + Block: largeBlock(), + TD: s.fullChain.TD(nextBlock + 1), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), + }, + { + Block: largeBlock(), + TD: largeNumber(2), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + }, + } + + for i, blockAnnouncement := range blocks[0:3] { + t.Logf("Testing malicious announcement: %v\n", i) + sendConn := s.setupConnection66(t) + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // Invalid announcement, check that peer disconnected + switch msg := sendConn.ReadAndServe(s.chain, time.Second*8).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + sendConn.Close() + } + // Test the last block as a valid block + s.sendNextBlock66(t) +} + +func (s *Suite) TestOldAnnounce_66(t *utesting.T) { + sendConn, recvConn := s.setupConnection66(t), s.setupConnection66(t) + defer sendConn.Close() + defer recvConn.Close() + + s.oldAnnounce(t, sendConn, recvConn) +} + +// TestMaliciousHandshake_66 tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake_66(t *utesting.T) { + conn := s.dial66(t) + defer conn.Close() + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 66}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + {Name: "eth", Version: 66}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 66}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + t.Logf("Testing malicious handshake %v\n", i) + // Init the handshake + if err := conn.Write(handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check that the peer disconnected + timeout := 20 * time.Second + // Discard one hello + for i := 0; i < 2; i++ { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Hello's are sent concurrently, so ignore them + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } + // Dial for the next round + conn = s.dial66(t) + } +} + +// TestMaliciousStatus_66 sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus_66(t *utesting.T) { + conn := s.dial66(t) + defer conn.Close() + // get protoHandshake + conn.handshake(t) + status := &Status{ + ProtocolVersion: uint32(66), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + switch msg := conn.statusExchange(t, s.chain, status).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("expected status, got: %#v ", msg) + } + // wait for disconnect + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + return + default: + t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} + +func (s *Suite) TestTransaction_66(t *utesting.T) { + tests := []*types.Transaction{ + getNextTxFromChain(t, s), + unknownTx(t, s), + } + for i, tx := range tests { + t.Logf("Testing tx propagation: %v\n", i) + sendSuccessfulTx66(t, s, tx) + } +} + +func (s *Suite) TestMaliciousTx_66(t *utesting.T) { + badTxs := []*types.Transaction{ + getOldTxFromChain(t, s), + invalidNonceTx(t, s), + hugeAmount(t, s), + hugeGasPrice(t, s), + hugeData(t, s), + } + sendConn := s.setupConnection66(t) + defer sendConn.Close() + // set up receiving connection before sending txs to make sure + // no announcements are missed + recvConn := s.setupConnection66(t) + defer recvConn.Close() + + for i, tx := range badTxs { + t.Logf("Testing malicious tx propagation: %v\n", i) + if err := sendConn.Write(&Transactions{tx}); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + } + // check to make sure bad txs aren't propagated + waitForTxPropagation(t, s, badTxs, recvConn) +} + +// TestZeroRequestID_66 checks that a request ID of zero is still handled +// by the node. +func (s *Suite) TestZeroRequestID_66(t *utesting.T) { + conn := s.setupConnection66(t) + defer conn.Close() + + req := ð.GetBlockHeadersPacket66{ + RequestId: 0, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 0, + }, + Amount: 2, + }, + } + headers, err := s.getBlockHeaders66(conn, req, req.RequestId) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + } + if !headersMatch(t, s.chain, headers) { + t.Fatal("received wrong header(s)") + } +} + +// TestSameRequestID_66 sends two requests with the same request ID +// concurrently to a single node. +func (s *Suite) TestSameRequestID_66(t *utesting.T) { + conn := s.setupConnection66(t) + // create two requests with the same request ID + reqID := uint64(1234) + request1 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 1, + }, + Amount: 2, + }, + } + request2 := ð.GetBlockHeadersPacket66{ + RequestId: reqID, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Number: 33, + }, + Amount: 2, + }, + } + // write the first request + err := conn.write66(request1, GetBlockHeaders{}.Code()) + if err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // perform second request + headers2, err := s.getBlockHeaders66(conn, request2, reqID) + if err != nil { + t.Fatalf("could not get block headers: %v", err) + return + } + // wait for response to first request + headers1, err := s.waitForBlockHeadersResponse66(conn, reqID) + if err != nil { + t.Fatalf("could not get BlockHeaders response: %v", err) + } + // check if headers match + if !headersMatch(t, s.chain, headers1) || !headersMatch(t, s.chain, headers2) { + t.Fatal("received wrong header(s)") + } +} + +// TestLargeTxRequest_66 tests whether a node can fulfill a large GetPooledTransactions +// request. +func (s *Suite) TestLargeTxRequest_66(t *utesting.T) { + // send the next block to ensure the node is no longer syncing and is able to accept + // txs + s.sendNextBlock66(t) + // send 2000 transactions to the node + hashMap, txs := generateTxs(t, s, 2000) + sendConn := s.setupConnection66(t) + defer sendConn.Close() + + sendMultipleSuccessfulTxs(t, s, sendConn, txs) + // set up connection to receive to ensure node is peered with the receiving connection + // before tx request is sent + recvConn := s.setupConnection66(t) + defer recvConn.Close() + // create and send pooled tx request + hashes := make([]common.Hash, 0) + for _, hash := range hashMap { + hashes = append(hashes, hash) + } + getTxReq := ð.GetPooledTransactionsPacket66{ + RequestId: 1234, + GetPooledTransactionsPacket: hashes, + } + if err := recvConn.write66(getTxReq, GetPooledTransactions{}.Code()); err != nil { + t.Fatalf("could not write to conn: %v", err) + } + // check that all received transactions match those that were sent to node + switch msg := recvConn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) { + case PooledTransactions: + for _, gotTx := range msg { + if _, exists := hashMap[gotTx.Hash()]; !exists { + t.Fatalf("unexpected tx received: %v", gotTx.Hash()) + } + } + default: + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } +} + +// TestNewPooledTxs_66 tests whether a node will do a GetPooledTransactions +// request upon receiving a NewPooledTransactionHashes announcement. +func (s *Suite) TestNewPooledTxs_66(t *utesting.T) { + // send the next block to ensure the node is no longer syncing and is able to accept + // txs + s.sendNextBlock66(t) + // generate 50 txs + hashMap, _ := generateTxs(t, s, 50) + // create new pooled tx hashes announcement + hashes := make([]common.Hash, 0) + for _, hash := range hashMap { + hashes = append(hashes, hash) + } + announce := NewPooledTransactionHashes(hashes) + // send announcement + conn := s.setupConnection66(t) + defer conn.Close() + if err := conn.Write(announce); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // wait for GetPooledTxs request + for { + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case GetPooledTransactions: + if len(msg) != len(hashes) { + t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg)) + } + return + case *NewPooledTransactionHashes, *NewBlock, *NewBlockHashes: + // ignore propagated txs and blocks from old tests + continue + default: + t.Fatalf("unexpected %s", pretty.Sdump(msg)) + } + } +} diff --git a/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go new file mode 100644 index 0000000000..3c5b22f0b5 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/eth66_suiteHelpers.go @@ -0,0 +1,333 @@ +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package ethtest + +import ( + "fmt" + "reflect" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/assert" +) + +func (c *Conn) statusExchange66(t *utesting.T, chain *Chain) Message { + status := &Status{ + ProtocolVersion: uint32(66), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + return c.statusExchange(t, chain, status) +} + +func (s *Suite) dial66(t *utesting.T) *Conn { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + conn.caps = append(conn.caps, p2p.Cap{Name: "eth", Version: 66}) + conn.ourHighestProtoVersion = 66 + return conn +} + +func (c *Conn) write66(req eth.Packet, code int) error { + payload, err := rlp.EncodeToBytes(req) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(code), payload) + return err +} + +func (c *Conn) read66() (uint64, Message) { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return 0, errorf("could not read from connection: %v", err) + } + + var msg Message + + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + ethMsg := new(eth.GetBlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockHeaders(*ethMsg.GetBlockHeadersPacket) + case (BlockHeaders{}).Code(): + ethMsg := new(eth.BlockHeadersPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockHeaders(ethMsg.BlockHeadersPacket) + case (GetBlockBodies{}).Code(): + ethMsg := new(eth.GetBlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetBlockBodies(ethMsg.GetBlockBodiesPacket) + case (BlockBodies{}).Code(): + ethMsg := new(eth.BlockBodiesPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, BlockBodies(ethMsg.BlockBodiesPacket) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) + case (GetPooledTransactions{}.Code()): + ethMsg := new(eth.GetPooledTransactionsPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, GetPooledTransactions(ethMsg.GetPooledTransactionsPacket) + case (PooledTransactions{}.Code()): + ethMsg := new(eth.PooledTransactionsPacket66) + if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return ethMsg.RequestId, PooledTransactions(ethMsg.PooledTransactionsPacket) + default: + msg = errorf("invalid message code: %d", code) + } + + if msg != nil { + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return 0, errorf("could not rlp decode message: %v", err) + } + return 0, msg + } + return 0, errorf("invalid message: %s", string(rawData)) +} + +func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { + for { + id, msg := c.readAndServe66(chain, timeout) + if id == requestID { + return msg + } + } +} + +// ReadAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) readAndServe66(chain *Chain, timeout time.Duration) (uint64, Message) { + start := time.Now() + for time.Since(start) < timeout { + c.SetReadDeadline(time.Now().Add(10 * time.Second)) + + reqID, msg := c.read66() + + switch msg := msg.(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + headers, err := chain.GetHeaders(*msg) + if err != nil { + return 0, errorf("could not get headers for inbound header request: %v", err) + } + resp := ð.BlockHeadersPacket66{ + RequestId: reqID, + BlockHeadersPacket: eth.BlockHeadersPacket(headers), + } + if err := c.write66(resp, BlockHeaders{}.Code()); err != nil { + return 0, errorf("could not write to connection: %v", err) + } + default: + return reqID, msg + } + } + return 0, errorf("no message received within %v", timeout) +} + +func (s *Suite) setupConnection66(t *utesting.T) *Conn { + // create conn + sendConn := s.dial66(t) + sendConn.handshake(t) + sendConn.statusExchange66(t, s.chain) + return sendConn +} + +func (s *Suite) testAnnounce66(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { + // Announce the block. + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + s.waitAnnounce66(t, receiveConn, blockAnnouncement) +} + +func (s *Suite) waitAnnounce66(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { + for { + _, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case *NewBlock: + t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) + assert.Equal(t, + blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement", + ) + assert.Equal(t, + blockAnnouncement.TD, msg.TD, + "wrong TD in announcement", + ) + return + case *NewBlockHashes: + blockHashes := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(blockHashes)) + assert.Equal(t, blockAnnouncement.Block.Hash(), blockHashes[0].Hash, + "wrong block hash in announcement", + ) + return + case *NewPooledTransactionHashes: + // ignore old txs being propagated + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } +} + +// waitForBlock66 waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock66(block *types.Block) error { + defer c.SetReadDeadline(time.Time{}) + + c.SetReadDeadline(time.Now().Add(20 * time.Second)) + // note: if the node has not yet imported the block, it will respond + // to the GetBlockHeaders request with an empty BlockHeaders response, + // so the GetBlockHeaders request must be sent again until the BlockHeaders + // response contains the desired header. + for { + req := eth.GetBlockHeadersPacket66{ + RequestId: 54, + GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + Origin: eth.HashOrNumber{ + Hash: block.Hash(), + }, + Amount: 1, + }, + } + if err := c.write66(req, GetBlockHeaders{}.Code()); err != nil { + return err + } + + reqID, msg := c.read66() + // check message + switch msg := msg.(type) { + case BlockHeaders: + // check request ID + if reqID != req.RequestId { + return fmt.Errorf("request ID mismatch: wanted %d, got %d", req.RequestId, reqID) + } + for _, header := range msg { + if header.Number.Uint64() == block.NumberU64() { + return nil + } + } + time.Sleep(100 * time.Millisecond) + case *NewPooledTransactionHashes: + // ignore old announcements + continue + default: + return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) + } + } +} + +func sendSuccessfulTx66(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn := s.setupConnection66(t) + defer sendConn.Close() + sendSuccessfulTxWithConn(t, s, tx, sendConn) +} + +// waitForBlockHeadersResponse66 waits for a BlockHeaders message with the given expected request ID +func (s *Suite) waitForBlockHeadersResponse66(conn *Conn, expectedID uint64) (BlockHeaders, error) { + reqID, msg := conn.readAndServe66(s.chain, timeout) + switch msg := msg.(type) { + case BlockHeaders: + if reqID != expectedID { + return nil, fmt.Errorf("request ID mismatch: wanted %d, got %d", expectedID, reqID) + } + return msg, nil + default: + return nil, fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) + } +} + +func (s *Suite) getBlockHeaders66(conn *Conn, req eth.Packet, expectedID uint64) (BlockHeaders, error) { + if err := conn.write66(req, GetBlockHeaders{}.Code()); err != nil { + return nil, fmt.Errorf("could not write to connection: %v", err) + } + return s.waitForBlockHeadersResponse66(conn, expectedID) +} + +func headersMatch(t *utesting.T, chain *Chain, headers BlockHeaders) bool { + mismatched := 0 + for _, header := range headers { + num := header.Number.Uint64() + t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) + if !reflect.DeepEqual(chain.blocks[int(num)].Header(), header) { + mismatched += 1 + t.Logf("received wrong header: %v", pretty.Sdump(header)) + } + } + return mismatched == 0 +} + +func (s *Suite) sendNextBlock66(t *utesting.T) { + sendConn, receiveConn := s.setupConnection66(t), s.setupConnection66(t) + defer sendConn.Close() + defer receiveConn.Close() + + // create new block announcement + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + // send announcement and wait for node to request the header + s.testAnnounce66(t, sendConn, receiveConn, blockAnnouncement) + // wait for client to update its chain + if err := receiveConn.waitForBlock66(s.fullChain.blocks[nextBlock]); err != nil { + t.Fatal(err) + } + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) +} diff --git a/cmd/devp2p/internal/ethtest/large.go b/cmd/devp2p/internal/ethtest/large.go new file mode 100644 index 0000000000..22421355ab --- /dev/null +++ b/cmd/devp2p/internal/ethtest/large.go @@ -0,0 +1,80 @@ +// 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 . + +package ethtest + +import ( + "crypto/rand" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +// largeNumber returns a very large big.Int. +func largeNumber(megabytes int) *big.Int { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + bigint := new(big.Int) + bigint.SetBytes(buf) + return bigint +} + +// largeBuffer returns a very large buffer. +func largeBuffer(megabytes int) []byte { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + return buf +} + +// largeString returns a very large string. +func largeString(megabytes int) string { + buf := make([]byte, megabytes*1024*1024) + rand.Read(buf) + return hexutil.Encode(buf) +} + +func largeBlock() *types.Block { + return types.NewBlockWithHeader(largeHeader()) +} + +// Returns a random hash +func randHash() common.Hash { + var h common.Hash + rand.Read(h[:]) + return h +} + +func largeHeader() *types.Header { + return &types.Header{ + MixDigest: randHash(), + ReceiptHash: randHash(), + TxHash: randHash(), + Nonce: types.BlockNonce{}, + Extra: []byte{}, + Bloom: types.Bloom{}, + GasUsed: 0, + Coinbase: common.Address{}, + GasLimit: 0, + UncleHash: types.EmptyUncleHash, + Time: 1337, + ParentHash: randHash(), + Root: randHash(), + Number: largeNumber(2), + Difficulty: largeNumber(2), + } +} diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go new file mode 100644 index 0000000000..abc6bcddce --- /dev/null +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -0,0 +1,550 @@ +// 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 . + +package ethtest + +import ( + "fmt" + "net" + "strings" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/rlpx" + "github.com/stretchr/testify/assert" +) + +var pretty = spew.ConfigState{ + Indent: " ", + DisableCapacities: true, + DisablePointerAddresses: true, + SortKeys: true, +} + +var timeout = 20 * time.Second + +// Suite represents a structure used to test the eth +// protocol of a node(s). +type Suite struct { + Dest *enode.Node + + chain *Chain + fullChain *Chain +} + +// NewSuite creates and returns a new eth-test suite that can +// be used to test the given node against the given blockchain +// data. +func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) { + chain, err := loadChain(chainfile, genesisfile) + if err != nil { + return nil, err + } + return &Suite{ + Dest: dest, + chain: chain.Shorten(1000), + fullChain: chain, + }, nil +} + +func (s *Suite) AllEthTests() []utesting.Test { + return []utesting.Test{ + // status + {Name: "TestStatus", Fn: s.TestStatus}, + {Name: "TestStatus_66", Fn: s.TestStatus_66}, + // get block headers + {Name: "TestGetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "TestGetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, + {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, + {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, + // get block bodies + {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "TestGetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + // broadcast + {Name: "TestBroadcast", Fn: s.TestBroadcast}, + {Name: "TestBroadcast_66", Fn: s.TestBroadcast_66}, + {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, + {Name: "TestOldAnnounce_66", Fn: s.TestOldAnnounce_66}, + // malicious handshakes + status + {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, + // test transactions + {Name: "TestTransaction", Fn: s.TestTransaction}, + {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, + {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, + {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, + {Name: "TestLargeTxRequest_66", Fn: s.TestLargeTxRequest_66}, + {Name: "TestNewPooledTxs_66", Fn: s.TestNewPooledTxs_66}, + } +} + +func (s *Suite) EthTests() []utesting.Test { + return []utesting.Test{ + {Name: "TestStatus", Fn: s.TestStatus}, + {Name: "TestGetBlockHeaders", Fn: s.TestGetBlockHeaders}, + {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, + {Name: "TestBroadcast", Fn: s.TestBroadcast}, + {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, + {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, + {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, + {Name: "TestTransaction", Fn: s.TestTransaction}, + {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, + } +} + +func (s *Suite) Eth66Tests() []utesting.Test { + return []utesting.Test{ + // only proceed with eth66 test suite if node supports eth 66 protocol + {Name: "TestStatus_66", Fn: s.TestStatus_66}, + {Name: "TestGetBlockHeaders_66", Fn: s.TestGetBlockHeaders_66}, + {Name: "TestSimultaneousRequests_66", Fn: s.TestSimultaneousRequests_66}, + {Name: "TestSameRequestID_66", Fn: s.TestSameRequestID_66}, + {Name: "TestZeroRequestID_66", Fn: s.TestZeroRequestID_66}, + {Name: "TestGetBlockBodies_66", Fn: s.TestGetBlockBodies_66}, + {Name: "TestBroadcast_66", Fn: s.TestBroadcast_66}, + {Name: "TestLargeAnnounce_66", Fn: s.TestLargeAnnounce_66}, + {Name: "TestMaliciousHandshake_66", Fn: s.TestMaliciousHandshake_66}, + {Name: "TestMaliciousStatus_66", Fn: s.TestMaliciousStatus_66}, + {Name: "TestTransaction_66", Fn: s.TestTransaction_66}, + {Name: "TestMaliciousTx_66", Fn: s.TestMaliciousTx_66}, + {Name: "TestLargeTxRequest_66", Fn: s.TestLargeTxRequest_66}, + {Name: "TestNewPooledTxs_66", Fn: s.TestNewPooledTxs_66}, + } +} + +// TestStatus attempts to connect to the given node and exchange +// a status message with it, and then check to make sure +// the chain head is correct. +func (s *Suite) TestStatus(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + defer conn.Close() + // get protoHandshake + conn.handshake(t) + // get status + switch msg := conn.statusExchange(t, s.chain, nil).(type) { + case *Status: + t.Logf("got status message: %s", pretty.Sdump(msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestMaliciousStatus sends a status package with a large total difficulty. +func (s *Suite) TestMaliciousStatus(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + defer conn.Close() + // get protoHandshake + conn.handshake(t) + status := &Status{ + ProtocolVersion: uint32(conn.negotiatedProtoVersion), + NetworkID: s.chain.chainConfig.ChainID.Uint64(), + TD: largeNumber(2), + Head: s.chain.blocks[s.chain.Len()-1].Hash(), + Genesis: s.chain.blocks[0].Hash(), + ForkID: s.chain.ForkID(), + } + // get status + switch msg := conn.statusExchange(t, s.chain, status).(type) { + case *Status: + t.Logf("%+v\n", msg) + default: + t.Fatalf("expected status, got: %#v ", msg) + } + // wait for disconnect + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + return + default: + t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) + } +} + +// TestGetBlockHeaders tests whether the given node can respond to +// a `GetBlockHeaders` request and that the response is accurate. +func (s *Suite) TestGetBlockHeaders(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + defer conn.Close() + + conn.handshake(t) + conn.statusExchange(t, s.chain, nil) + + // get block headers + req := &GetBlockHeaders{ + Origin: eth.HashOrNumber{ + Hash: s.chain.blocks[1].Hash(), + }, + Amount: 2, + Skip: 1, + Reverse: false, + } + + if err := conn.Write(req); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *BlockHeaders: + headers := *msg + for _, header := range headers { + num := header.Number.Uint64() + t.Logf("received header (%d): %s", num, pretty.Sdump(header.Hash())) + assert.Equal(t, s.chain.blocks[int(num)].Header(), header) + } + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestGetBlockBodies tests whether the given node can respond to +// a `GetBlockBodies` request and that the response is accurate. +func (s *Suite) TestGetBlockBodies(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + defer conn.Close() + + conn.handshake(t) + conn.statusExchange(t, s.chain, nil) + // create block bodies request + req := &GetBlockBodies{ + s.chain.blocks[54].Hash(), + s.chain.blocks[75].Hash(), + } + if err := conn.Write(req); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *BlockBodies: + t.Logf("received %d block bodies", len(*msg)) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +// TestBroadcast tests whether a block announcement is correctly +// propagated to the given node's peer(s). +func (s *Suite) TestBroadcast(t *utesting.T) { + s.sendNextBlock(t) +} + +func (s *Suite) sendNextBlock(t *utesting.T) { + sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) + defer sendConn.Close() + defer receiveConn.Close() + + // create new block announcement + nextBlock := len(s.chain.blocks) + blockAnnouncement := &NewBlock{ + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + } + // send announcement and wait for node to request the header + s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement) + // wait for client to update its chain + if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { + t.Fatal(err) + } + // update test suite chain + s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) +} + +// TestMaliciousHandshake tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake(t *utesting.T) { + conn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + defer conn.Close() + // write hello to client + pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] + handshakes := []*Hello{ + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: pub0, + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, byte(0)), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + }, + ID: largeBuffer(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: largeString(2), Version: 64}, + }, + ID: largeBuffer(2), + }, + } + for i, handshake := range handshakes { + t.Logf("Testing malicious handshake %v\n", i) + // Init the handshake + if err := conn.Write(handshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // check that the peer disconnected + timeout := 20 * time.Second + // Discard one hello + for i := 0; i < 2; i++ { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *Disconnect: + case *Error: + case *Hello: + // Hello's are send concurrently, so ignore them + continue + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } + } + // Dial for the next round + conn, err = s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + } +} + +// TestLargeAnnounce tests the announcement mechanism with a large block. +func (s *Suite) TestLargeAnnounce(t *utesting.T) { + nextBlock := len(s.chain.blocks) + blocks := []*NewBlock{ + { + Block: largeBlock(), + TD: s.fullChain.TD(nextBlock + 1), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: largeNumber(2), + }, + { + Block: largeBlock(), + TD: largeNumber(2), + }, + { + Block: s.fullChain.blocks[nextBlock], + TD: s.fullChain.TD(nextBlock + 1), + }, + } + + for i, blockAnnouncement := range blocks[0:3] { + t.Logf("Testing malicious announcement: %v\n", i) + sendConn := s.setupConnection(t) + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // Invalid announcement, check that peer disconnected + switch msg := sendConn.ReadAndServe(s.chain, time.Second*8).(type) { + case *Disconnect: + case *Error: + break + default: + t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + } + sendConn.Close() + } + // Test the last block as a valid block + s.sendNextBlock(t) +} + +func (s *Suite) TestOldAnnounce(t *utesting.T) { + sendConn, recvConn := s.setupConnection(t), s.setupConnection(t) + defer sendConn.Close() + defer recvConn.Close() + + s.oldAnnounce(t, sendConn, recvConn) +} + +func (s *Suite) oldAnnounce(t *utesting.T, sendConn, receiveConn *Conn) { + oldBlockAnnounce := &NewBlock{ + Block: s.chain.blocks[len(s.chain.blocks)/2], + TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), + } + + if err := sendConn.Write(oldBlockAnnounce); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + switch msg := receiveConn.ReadAndServe(s.chain, time.Second*8).(type) { + case *NewBlock: + block := *msg + if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { + t.Fatalf("unexpected: block propagated: %s", pretty.Sdump(msg)) + } + case *NewBlockHashes: + hashes := *msg + for _, hash := range hashes { + if hash.Hash == oldBlockAnnounce.Block.Hash() { + t.Fatalf("unexpected: block announced: %s", pretty.Sdump(msg)) + } + } + case *Error: + errMsg := *msg + // check to make sure error is timeout (propagation didn't come through == test successful) + if !strings.Contains(errMsg.String(), "timeout") { + t.Fatalf("unexpected error: %v", pretty.Sdump(msg)) + } + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { + // Announce the block. + if err := sendConn.Write(blockAnnouncement); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + s.waitAnnounce(t, receiveConn, blockAnnouncement) +} + +func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { + switch msg := conn.ReadAndServe(s.chain, timeout).(type) { + case *NewBlock: + t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) + assert.Equal(t, + blockAnnouncement.Block.Header(), msg.Block.Header(), + "wrong block header in announcement", + ) + assert.Equal(t, + blockAnnouncement.TD, msg.TD, + "wrong TD in announcement", + ) + case *NewBlockHashes: + message := *msg + t.Logf("received NewBlockHashes message: %s", pretty.Sdump(message)) + assert.Equal(t, blockAnnouncement.Block.Hash(), message[0].Hash, + "wrong block hash in announcement", + ) + default: + t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + } +} + +func (s *Suite) setupConnection(t *utesting.T) *Conn { + // create conn + sendConn, err := s.dial() + if err != nil { + t.Fatalf("could not dial: %v", err) + } + sendConn.handshake(t) + sendConn.statusExchange(t, s.chain, nil) + return sendConn +} + +// dial attempts to dial the given node and perform a handshake, +// returning the created Conn if successful. +func (s *Suite) dial() (*Conn, error) { + var conn Conn + // dial + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) + if err != nil { + return nil, err + } + conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey()) + // do encHandshake + conn.ourKey, _ = crypto.GenerateKey() + _, err = conn.Handshake(conn.ourKey) + if err != nil { + return nil, err + } + // set default p2p capabilities + conn.caps = []p2p.Cap{ + {Name: "eth", Version: 64}, + {Name: "eth", Version: 65}, + } + conn.ourHighestProtoVersion = 65 + return &conn, nil +} + +func (s *Suite) TestTransaction(t *utesting.T) { + tests := []*types.Transaction{ + getNextTxFromChain(t, s), + unknownTx(t, s), + } + for i, tx := range tests { + t.Logf("Testing tx propagation: %v\n", i) + sendSuccessfulTx(t, s, tx) + } +} + +func (s *Suite) TestMaliciousTx(t *utesting.T) { + badTxs := []*types.Transaction{ + getOldTxFromChain(t, s), + invalidNonceTx(t, s), + hugeAmount(t, s), + hugeGasPrice(t, s), + hugeData(t, s), + } + sendConn := s.setupConnection(t) + defer sendConn.Close() + // set up receiving connection before sending txs to make sure + // no announcements are missed + recvConn := s.setupConnection(t) + defer recvConn.Close() + + for i, tx := range badTxs { + t.Logf("Testing malicious tx propagation: %v\n", i) + if err := sendConn.Write(&Transactions{tx}); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + } + // check to make sure bad txs aren't propagated + waitForTxPropagation(t, s, badTxs, recvConn) +} diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go new file mode 100644 index 0000000000..6e3217151a --- /dev/null +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -0,0 +1,107 @@ +// 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 . + +package ethtest + +import ( + "os" + "testing" + "time" + + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" +) + +var ( + genesisFile = "./testdata/genesis.json" + halfchainFile = "./testdata/halfchain.rlp" + fullchainFile = "./testdata/chain.rlp" +) + +func TestEthSuite(t *testing.T) { + geth, err := runGeth() + if err != nil { + t.Fatalf("could not run geth: %v", err) + } + defer geth.Close() + + suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile) + if err != nil { + t.Fatalf("could not create new test suite: %v", err) + } + for _, test := range suite.AllEthTests() { + t.Run(test.Name, func(t *testing.T) { + result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) + if result[0].Failed { + t.Fatal() + } + }) + } +} + +// runGeth creates and starts a geth node +func runGeth() (*node.Node, error) { + stack, err := node.New(&node.Config{ + P2P: p2p.Config{ + ListenAddr: "127.0.0.1:0", + NoDiscovery: true, + MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future + NoDial: true, + }, + }) + if err != nil { + return nil, err + } + + err = setupGeth(stack) + if err != nil { + stack.Close() + return nil, err + } + if err = stack.Start(); err != nil { + stack.Close() + return nil, err + } + return stack, nil +} + +func setupGeth(stack *node.Node) error { + chain, err := loadChain(halfchainFile, genesisFile) + if err != nil { + return err + } + + backend, err := eth.New(stack, ðconfig.Config{ + Genesis: &chain.genesis, + NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 + DatabaseCache: 10, + TrieCleanCache: 10, + TrieCleanCacheJournal: "", + TrieCleanCacheRejournal: 60 * time.Minute, + TrieDirtyCache: 16, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 10, + }) + if err != nil { + return err + } + + _, err = backend.BlockChain().InsertChain(chain.blocks[1:]) + return err +} diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp new file mode 100644 index 0000000000..5ebc2f3bb7 Binary files /dev/null and b/cmd/devp2p/internal/ethtest/testdata/chain.rlp differ diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json new file mode 100644 index 0000000000..d8b5d22502 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -0,0 +1,26 @@ +{ + "config": { + "chainId": 19763, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "ethash": {} + }, + "nonce": "0xdeadbeefdeadbeef", + "timestamp": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x80000000", + "difficulty": "0x20000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "71562b71999873db5b286df957af199ec94617f7": { + "balance": "0xffffffffffffffffffffffffff" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp new file mode 100644 index 0000000000..1a820734e1 Binary files /dev/null and b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp differ diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go new file mode 100644 index 0000000000..a6166bd2e3 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -0,0 +1,297 @@ +// 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 . + +package ethtest + +import ( + "math/big" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/params" +) + +//var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") +var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + +func sendSuccessfulTx(t *utesting.T, s *Suite, tx *types.Transaction) { + sendConn := s.setupConnection(t) + defer sendConn.Close() + sendSuccessfulTxWithConn(t, s, tx, sendConn) +} + +func sendSuccessfulTxWithConn(t *utesting.T, s *Suite, tx *types.Transaction, sendConn *Conn) { + t.Logf("sending tx: %v %v %v\n", tx.Hash().String(), tx.GasPrice(), tx.Gas()) + // Send the transaction + if err := sendConn.Write(&Transactions{tx}); err != nil { + t.Fatal(err) + } + // update last nonce seen + nonce = tx.Nonce() + + recvConn := s.setupConnection(t) + // Wait for the transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + recTxs := *msg + for _, gotTx := range recTxs { + if gotTx.Hash() == tx.Hash() { + // Ok + return + } + } + t.Fatalf("missing transaction: got %v missing %v", recTxs, tx.Hash()) + case *NewPooledTransactionHashes: + txHashes := *msg + for _, gotHash := range txHashes { + if gotHash == tx.Hash() { + return + } + } + t.Fatalf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) + default: + t.Fatalf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) + } +} + +var nonce = uint64(99) + +func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, sendConn *Conn, txs []*types.Transaction) { + txMsg := Transactions(txs) + t.Logf("sending %d txs\n", len(txs)) + + recvConn := s.setupConnection(t) + defer recvConn.Close() + + // Send the transactions + if err := sendConn.Write(&txMsg); err != nil { + t.Fatal(err) + } + // update nonce + nonce = txs[len(txs)-1].Nonce() + // Wait for the transaction announcement(s) and make sure all sent txs are being propagated + recvHashes := make([]common.Hash, 0) + // all txs should be announced within 3 announcements + for i := 0; i < 3; i++ { + switch msg := recvConn.ReadAndServe(s.chain, timeout).(type) { + case *Transactions: + for _, tx := range *msg { + recvHashes = append(recvHashes, tx.Hash()) + } + case *NewPooledTransactionHashes: + recvHashes = append(recvHashes, *msg...) + default: + if !strings.Contains(pretty.Sdump(msg), "i/o timeout") { + t.Fatalf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg)) + } + } + // break once all 2000 txs have been received + if len(recvHashes) == 2000 { + break + } + if len(recvHashes) > 0 { + _, missingTxs := compareReceivedTxs(recvHashes, txs) + if len(missingTxs) > 0 { + continue + } else { + t.Logf("successfully received all %d txs", len(txs)) + return + } + } + } + _, missingTxs := compareReceivedTxs(recvHashes, txs) + if len(missingTxs) > 0 { + for _, missing := range missingTxs { + t.Logf("missing tx: %v", missing.Hash()) + } + t.Fatalf("missing %d txs", len(missingTxs)) + } +} + +func waitForTxPropagation(t *utesting.T, s *Suite, txs []*types.Transaction, recvConn *Conn) { + // Wait for another transaction announcement + switch msg := recvConn.ReadAndServe(s.chain, time.Second*8).(type) { + case *Transactions: + // check to see if any of the failing txs were in the announcement + recvTxs := make([]common.Hash, len(*msg)) + for i, recvTx := range *msg { + recvTxs[i] = recvTx.Hash() + } + badTxs, _ := compareReceivedTxs(recvTxs, txs) + if len(badTxs) > 0 { + for _, tx := range badTxs { + t.Logf("received bad tx: %v", tx) + } + t.Fatalf("received %d bad txs", len(badTxs)) + } + case *NewPooledTransactionHashes: + badTxs, _ := compareReceivedTxs(*msg, txs) + if len(badTxs) > 0 { + for _, tx := range badTxs { + t.Logf("received bad tx: %v", tx) + } + t.Fatalf("received %d bad txs", len(badTxs)) + } + case *Error: + // Transaction should not be announced -> wait for timeout + return + default: + t.Fatalf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) + } +} + +// compareReceivedTxs compares the received set of txs against the given set of txs, +// returning both the set received txs that were present within the given txs, and +// the set of txs that were missing from the set of received txs +func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (present []*types.Transaction, missing []*types.Transaction) { + // create a map of the hashes received from node + recvHashes := make(map[common.Hash]common.Hash) + for _, hash := range recvTxs { + recvHashes[hash] = hash + } + + // collect present txs and missing txs separately + present = make([]*types.Transaction, 0) + missing = make([]*types.Transaction, 0) + for _, tx := range txs { + if _, exists := recvHashes[tx.Hash()]; exists { + present = append(present, tx) + } else { + missing = append(missing, tx) + } + } + return present, missing +} + +func unknownTx(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, s.chain.chainConfig, txNew) +} + +func getNextTxFromChain(t *utesting.T, s *Suite) *types.Transaction { + // Get a new transaction + var tx *types.Transaction + for _, blocks := range s.fullChain.blocks[s.chain.Len():] { + txs := blocks.Transactions() + if txs.Len() != 0 { + tx = txs[0] + break + } + } + if tx == nil { + t.Fatal("could not find transaction") + } + return tx +} + +func generateTxs(t *utesting.T, s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction) { + txHashMap := make(map[common.Hash]common.Hash, numTxs) + txs := make([]*types.Transaction, numTxs) + + nextTx := getNextTxFromChain(t, s) + gas := nextTx.Gas() + + nonce = nonce + 1 + // generate txs + for i := 0; i < numTxs; i++ { + tx := generateTx(t, s.chain.chainConfig, nonce, gas) + txHashMap[tx.Hash()] = tx.Hash() + txs[i] = tx + nonce = nonce + 1 + } + return txHashMap, txs +} + +func generateTx(t *utesting.T, chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction { + var to common.Address + tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{}) + return signWithFaucet(t, chainConfig, tx) +} + +func getOldTxFromChain(t *utesting.T, s *Suite) *types.Transaction { + var tx *types.Transaction + for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] { + txs := blocks.Transactions() + if txs.Len() != 0 { + tx = txs[0] + break + } + } + if tx == nil { + t.Fatal("could not find transaction") + } + return tx +} + +func invalidNonceTx(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, s.chain.chainConfig, txNew) +} + +func hugeAmount(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + amount := largeNumber(2) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data()) + return signWithFaucet(t, s.chain.chainConfig, txNew) +} + +func hugeGasPrice(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + gasPrice := largeNumber(2) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data()) + return signWithFaucet(t, s.chain.chainConfig, txNew) +} + +func hugeData(t *utesting.T, s *Suite) *types.Transaction { + tx := getNextTxFromChain(t, s) + var to common.Address + if tx.To() != nil { + to = *tx.To() + } + txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2)) + return signWithFaucet(t, s.chain.chainConfig, txNew) +} + +func signWithFaucet(t *utesting.T, chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction { + signer := types.LatestSigner(chainConfig) + signedTx, err := types.SignTx(tx, signer, faucetKey) + if err != nil { + t.Fatalf("could not sign tx: %v\n", err) + } + return signedTx +} diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go new file mode 100644 index 0000000000..50a69b9418 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/types.go @@ -0,0 +1,359 @@ +// 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 . + +package ethtest + +import ( + "crypto/ecdsa" + "fmt" + "reflect" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/rlpx" + "github.com/ethereum/go-ethereum/rlp" +) + +type Message interface { + Code() int +} + +type Error struct { + err error +} + +func (e *Error) Unwrap() error { return e.err } +func (e *Error) Error() string { return e.err.Error() } +func (e *Error) Code() int { return -1 } +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. +type Hello struct { + Version uint64 + Name string + Caps []p2p.Cap + ListenPort uint64 + ID []byte // secp256k1 public key + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` +} + +func (h Hello) Code() int { return 0x00 } + +// Disconnect is the RLP structure for a disconnect message. +type Disconnect struct { + Reason p2p.DiscReason +} + +func (d Disconnect) Code() int { return 0x01 } + +type Ping struct{} + +func (p Ping) Code() int { return 0x02 } + +type Pong struct{} + +func (p Pong) Code() int { return 0x03 } + +// Status is the network packet for the status message for eth/64 and later. +type Status eth.StatusPacket + +func (s Status) Code() int { return 16 } + +// NewBlockHashes is the network packet for the block announcements. +type NewBlockHashes eth.NewBlockHashesPacket + +func (nbh NewBlockHashes) Code() int { return 17 } + +type Transactions eth.TransactionsPacket + +func (t Transactions) Code() int { return 18 } + +// GetBlockHeaders represents a block header query. +type GetBlockHeaders eth.GetBlockHeadersPacket + +func (g GetBlockHeaders) Code() int { return 19 } + +type BlockHeaders eth.BlockHeadersPacket + +func (bh BlockHeaders) Code() int { return 20 } + +// GetBlockBodies represents a GetBlockBodies request +type GetBlockBodies eth.GetBlockBodiesPacket + +func (gbb GetBlockBodies) Code() int { return 21 } + +// BlockBodies is the network packet for block content distribution. +type BlockBodies eth.BlockBodiesPacket + +func (bb BlockBodies) Code() int { return 22 } + +// NewBlock is the network packet for the block propagation message. +type NewBlock eth.NewBlockPacket + +func (nb NewBlock) Code() int { return 23 } + +// NewPooledTransactionHashes is the network packet for the tx hash propagation message. +type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket + +func (nb NewPooledTransactionHashes) Code() int { return 24 } + +type GetPooledTransactions eth.GetPooledTransactionsPacket + +func (gpt GetPooledTransactions) Code() int { return 25 } + +type PooledTransactions eth.PooledTransactionsPacket + +func (pt PooledTransactions) Code() int { return 26 } + +// Conn represents an individual connection with a peer +type Conn struct { + *rlpx.Conn + ourKey *ecdsa.PrivateKey + negotiatedProtoVersion uint + ourHighestProtoVersion uint + caps []p2p.Cap +} + +func (c *Conn) Read() Message { + code, rawData, _, err := c.Conn.Read() + if err != nil { + return errorf("could not read from connection: %v", err) + } + + var msg Message + switch int(code) { + case (Hello{}).Code(): + msg = new(Hello) + case (Ping{}).Code(): + msg = new(Ping) + case (Pong{}).Code(): + msg = new(Pong) + case (Disconnect{}).Code(): + msg = new(Disconnect) + case (Status{}).Code(): + msg = new(Status) + case (GetBlockHeaders{}).Code(): + msg = new(GetBlockHeaders) + case (BlockHeaders{}).Code(): + msg = new(BlockHeaders) + case (GetBlockBodies{}).Code(): + msg = new(GetBlockBodies) + case (BlockBodies{}).Code(): + msg = new(BlockBodies) + case (NewBlock{}).Code(): + msg = new(NewBlock) + case (NewBlockHashes{}).Code(): + msg = new(NewBlockHashes) + case (Transactions{}).Code(): + msg = new(Transactions) + case (NewPooledTransactionHashes{}).Code(): + msg = new(NewPooledTransactionHashes) + case (GetPooledTransactions{}.Code()): + msg = new(GetPooledTransactions) + case (PooledTransactions{}.Code()): + msg = new(PooledTransactions) + default: + return errorf("invalid message code: %d", code) + } + // if message is devp2p, decode here + if err := rlp.DecodeBytes(rawData, msg); err != nil { + return errorf("could not rlp decode message: %v", err) + } + return msg +} + +// ReadAndServe serves GetBlockHeaders requests while waiting +// on another message from the node. +func (c *Conn) ReadAndServe(chain *Chain, timeout time.Duration) Message { + start := time.Now() + for time.Since(start) < timeout { + c.SetReadDeadline(time.Now().Add(5 * time.Second)) + switch msg := c.Read().(type) { + case *Ping: + c.Write(&Pong{}) + case *GetBlockHeaders: + req := *msg + headers, err := chain.GetHeaders(req) + if err != nil { + return errorf("could not get headers for inbound header request: %v", err) + } + + if err := c.Write(headers); err != nil { + return errorf("could not write to connection: %v", err) + } + default: + return msg + } + } + return errorf("no message received within %v", timeout) +} + +func (c *Conn) Write(msg Message) error { + // check if message is eth protocol message + var ( + payload []byte + err error + ) + payload, err = rlp.EncodeToBytes(msg) + if err != nil { + return err + } + _, err = c.Conn.Write(uint64(msg.Code()), payload) + return err +} + +// handshake checks to make sure a `HELLO` is received. +func (c *Conn) handshake(t *utesting.T) Message { + defer c.SetDeadline(time.Time{}) + c.SetDeadline(time.Now().Add(10 * time.Second)) + + // write hello to client + pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &Hello{ + Version: 5, + Caps: c.caps, + ID: pub0, + } + if err := c.Write(ourHandshake); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + // read hello from client + switch msg := c.Read().(type) { + case *Hello: + // set snappy if version is at least 5 + if msg.Version >= 5 { + c.SetSnappy(true) + } + c.negotiateEthProtocol(msg.Caps) + if c.negotiatedProtoVersion == 0 { + t.Fatalf("unexpected eth protocol version") + } + return msg + default: + t.Fatalf("bad handshake: %#v", msg) + return nil + } +} + +// negotiateEthProtocol sets the Conn's eth protocol version +// to highest advertised capability from peer +func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { + var highestEthVersion uint + for _, capability := range caps { + if capability.Name != "eth" { + continue + } + if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { + highestEthVersion = capability.Version + } + } + c.negotiatedProtoVersion = highestEthVersion +} + +// statusExchange performs a `Status` message exchange with the given +// node. +func (c *Conn) statusExchange(t *utesting.T, chain *Chain, status *Status) Message { + defer c.SetDeadline(time.Time{}) + c.SetDeadline(time.Now().Add(20 * time.Second)) + + // read status message from client + var message Message +loop: + for { + switch msg := c.Read().(type) { + case *Status: + if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { + t.Fatalf("wrong head block in status, want: %#x (block %d) have %#x", + want, chain.blocks[chain.Len()-1].NumberU64(), have) + } + if have, want := msg.TD.Cmp(chain.TD(chain.Len())), 0; have != want { + t.Fatalf("wrong TD in status: have %v want %v", have, want) + } + if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { + t.Fatalf("wrong fork ID in status: have %v, want %v", have, want) + } + message = msg + break loop + case *Disconnect: + t.Fatalf("disconnect received: %v", msg.Reason) + case *Ping: + c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error + // (PINGs should not be a response upon fresh connection) + default: + t.Fatalf("bad status message: %s", pretty.Sdump(msg)) + } + } + // make sure eth protocol version is set for negotiation + if c.negotiatedProtoVersion == 0 { + t.Fatalf("eth protocol version must be set in Conn") + } + if status == nil { + // write status message to client + status = &Status{ + ProtocolVersion: uint32(c.negotiatedProtoVersion), + NetworkID: chain.chainConfig.ChainID.Uint64(), + TD: chain.TD(chain.Len()), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + } + + if err := c.Write(status); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + + return message +} + +// waitForBlock waits for confirmation from the client that it has +// imported the given block. +func (c *Conn) waitForBlock(block *types.Block) error { + defer c.SetReadDeadline(time.Time{}) + + c.SetReadDeadline(time.Now().Add(20 * time.Second)) + // note: if the node has not yet imported the block, it will respond + // to the GetBlockHeaders request with an empty BlockHeaders response, + // so the GetBlockHeaders request must be sent again until the BlockHeaders + // response contains the desired header. + for { + req := &GetBlockHeaders{Origin: eth.HashOrNumber{Hash: block.Hash()}, Amount: 1} + if err := c.Write(req); err != nil { + return err + } + switch msg := c.Read().(type) { + case *BlockHeaders: + for _, header := range *msg { + if header.Number.Uint64() == block.NumberU64() { + return nil + } + } + time.Sleep(100 * time.Millisecond) + default: + return fmt.Errorf("invalid message: %s", pretty.Sdump(msg)) + } + } +} diff --git a/cmd/devp2p/internal/v4test/discv4tests.go b/cmd/devp2p/internal/v4test/discv4tests.go new file mode 100644 index 0000000000..140b96bfa5 --- /dev/null +++ b/cmd/devp2p/internal/v4test/discv4tests.go @@ -0,0 +1,467 @@ +// 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 . + +package v4test + +import ( + "bytes" + "crypto/rand" + "fmt" + "net" + "reflect" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p/discover/v4wire" +) + +const ( + expiration = 20 * time.Second + wrongPacket = 66 + macSize = 256 / 8 +) + +var ( + // Remote node under test + Remote string + // IP where the first tester is listening, port will be assigned + Listen1 string = "127.0.0.1" + // IP where the second tester is listening, port will be assigned + // Before running the test, you may have to `sudo ifconfig lo0 add 127.0.0.2` (on MacOS at least) + Listen2 string = "127.0.0.2" +) + +type pingWithJunk struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 + JunkData1 uint + JunkData2 []byte +} + +func (req *pingWithJunk) Name() string { return "PING/v4" } +func (req *pingWithJunk) Kind() byte { return v4wire.PingPacket } + +type pingWrongType struct { + Version uint + From, To v4wire.Endpoint + Expiration uint64 +} + +func (req *pingWrongType) Name() string { return "WRONG/v4" } +func (req *pingWrongType) Kind() byte { return wrongPacket } + +func futureExpiration() uint64 { + return uint64(time.Now().Add(expiration).Unix()) +} + +// This test just sends a PING packet and expects a response. +func BasicPing(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// checkPong verifies that reply is a valid PONG matching the given ping hash. +func (te *testenv) checkPong(reply v4wire.Packet, pingHash []byte) error { + if reply == nil || reply.Kind() != v4wire.PongPacket { + return fmt.Errorf("expected PONG reply, got %v", reply) + } + pong := reply.(*v4wire.Pong) + if !bytes.Equal(pong.ReplyTok, pingHash) { + return fmt.Errorf("PONG reply token mismatch: got %x, want %x", pong.ReplyTok, pingHash) + } + wantEndpoint := te.localEndpoint(te.l1) + if !reflect.DeepEqual(pong.To, wantEndpoint) { + return fmt.Errorf("PONG 'to' endpoint mismatch: got %+v, want %+v", pong.To, wantEndpoint) + } + if v4wire.Expired(pong.Expiration) { + return fmt.Errorf("PONG is expired (%v)", pong.Expiration) + } + return nil +} + +// This test sends a PING packet with wrong 'to' field and expects a PONG response. +func PingWrongTo(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: wrongEndpoint, + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with wrong 'from' field and expects a PONG response. +func PingWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data at the end and expects a PONG +// response. The remote node should respond because EIP-8 mandates ignoring additional +// trailing data. +func PingExtraData(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + pingHash := te.send(te.l1, &pingWithJunk{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with additional data and wrong 'from' field +// and expects a PONG response. +func PingExtraDataWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + req := pingWithJunk{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + JunkData1: 42, + JunkData2: []byte{9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + pingHash := te.send(te.l1, &req) + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test sends a PING packet with an expiration in the past. +// The remote node should not respond. +func PingPastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: -futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no reply, got", reply) + } +} + +// This test sends an invalid packet. The remote node should not respond. +func WrongPacketType(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + te.send(te.l1, &pingWrongType{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no reply, got", reply) + } +} + +// This test verifies that the default behaviour of ignoring 'from' fields is unaffected by +// the bonding process. After bonding, it pings the target with a different from endpoint. +func BondThenPingWithWrongFrom(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + wrongEndpoint := v4wire.Endpoint{IP: net.ParseIP("192.0.2.0")} + pingHash := te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: wrongEndpoint, + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + reply, _, _ := te.read(te.l1) + if err := te.checkPong(reply, pingHash); err != nil { + t.Fatal(err) + } +} + +// This test just sends FINDNODE. The remote node should not reply +// because the endpoint proof has not completed. +func FindnodeWithoutEndpointProof(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + req := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(req.Target[:]) + te.send(te.l1, &req) + + reply, _, _ := te.read(te.l1) + if reply != nil { + t.Fatal("Expected no response, got", reply) + } +} + +// BasicFindnode sends a FINDNODE request after performing the endpoint +// proof. The remote node should respond. +func BasicFindnode(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatal("Expected neighbors, got", reply.Name()) + } +} + +// This test sends an unsolicited NEIGHBORS packet after the endpoint proof, then sends +// FINDNODE to read the remote table. The remote node should not return the node contained +// in the unsolicited NEIGHBORS packet. +func UnsolicitedNeighbors(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + // Send unsolicited NEIGHBORS response. + fakeKey, _ := crypto.GenerateKey() + encFakeKey := v4wire.EncodePubkey(&fakeKey.PublicKey) + neighbors := v4wire.Neighbors{ + Expiration: futureExpiration(), + Nodes: []v4wire.Node{{ + ID: encFakeKey, + IP: net.IP{1, 2, 3, 4}, + UDP: 30303, + TCP: 30303, + }}, + } + te.send(te.l1, &neighbors) + + // Check if the remote node included the fake node. + te.send(te.l1, &v4wire.Findnode{ + Expiration: futureExpiration(), + Target: encFakeKey, + }) + + reply, _, err := te.read(te.l1) + if err != nil { + t.Fatal("read find nodes", err) + } + if reply.Kind() != v4wire.NeighborsPacket { + t.Fatal("Expected neighbors, got", reply.Name()) + } + nodes := reply.(*v4wire.Neighbors).Nodes + if contains(nodes, encFakeKey) { + t.Fatal("neighbors response contains node from earlier unsolicited neighbors response") + } +} + +// This test sends FINDNODE with an expiration timestamp in the past. +// The remote node should not respond. +func FindnodePastExpiration(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + bond(t, te) + + findnode := v4wire.Findnode{Expiration: -futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + for { + reply, _, _ := te.read(te.l1) + if reply == nil { + return + } else if reply.Kind() == v4wire.NeighborsPacket { + t.Fatal("Unexpected NEIGHBORS response for expired FINDNODE request") + } + } +} + +// bond performs the endpoint proof with the remote node. +func bond(t *utesting.T, te *testenv) { + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, hash, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: hash, + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + // TODO: maybe verify pong data here + gotPong = true + } + } +} + +// This test attempts to perform a traffic amplification attack against a +// 'victim' endpoint using FINDNODE. In this attack scenario, the attacker +// attempts to complete the endpoint proof non-interactively by sending a PONG +// with mismatching reply token from the 'victim' endpoint. The attack works if +// the remote node does not verify the PONG reply token field correctly. The +// attacker could then perform traffic amplification by sending many FINDNODE +// requests to the discovery node, which would reply to the 'victim' address. +func FindnodeAmplificationInvalidPongHash(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Send PING to start endpoint verification. + te.send(te.l1, &v4wire.Ping{ + Version: 4, + From: te.localEndpoint(te.l1), + To: te.remoteEndpoint(), + Expiration: futureExpiration(), + }) + + var gotPing, gotPong bool + for !gotPing || !gotPong { + req, _, err := te.read(te.l1) + if err != nil { + t.Fatal(err) + } + switch req.(type) { + case *v4wire.Ping: + // Send PONG from this node ID, but with invalid ReplyTok. + te.send(te.l1, &v4wire.Pong{ + To: te.remoteEndpoint(), + ReplyTok: make([]byte, macSize), + Expiration: futureExpiration(), + }) + gotPing = true + case *v4wire.Pong: + gotPong = true + } + } + + // Now send FINDNODE. The remote node should not respond because our + // PONG did not reference the PING hash. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l1, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l1) + if reply != nil && reply.Kind() == v4wire.NeighborsPacket { + t.Error("Got neighbors") + } +} + +// This test attempts to perform a traffic amplification attack using FINDNODE. +// The attack works if the remote node does not verify the IP address of FINDNODE +// against the endpoint verification proof done by PING/PONG. +func FindnodeAmplificationWrongIP(t *utesting.T) { + te := newTestEnv(Remote, Listen1, Listen2) + defer te.close() + + // Do the endpoint proof from the l1 IP. + bond(t, te) + + // Now send FINDNODE from the same node ID, but different IP address. + // The remote node should not respond. + findnode := v4wire.Findnode{Expiration: futureExpiration()} + rand.Read(findnode.Target[:]) + te.send(te.l2, &findnode) + + // If we receive a NEIGHBORS response, the attack worked and the test fails. + reply, _, _ := te.read(te.l2) + if reply != nil { + t.Error("Got NEIGHORS response for FINDNODE from wrong IP") + } +} + +var AllTests = []utesting.Test{ + {Name: "Ping/Basic", Fn: BasicPing}, + {Name: "Ping/WrongTo", Fn: PingWrongTo}, + {Name: "Ping/WrongFrom", Fn: PingWrongFrom}, + {Name: "Ping/ExtraData", Fn: PingExtraData}, + {Name: "Ping/ExtraDataWrongFrom", Fn: PingExtraDataWrongFrom}, + {Name: "Ping/PastExpiration", Fn: PingPastExpiration}, + {Name: "Ping/WrongPacketType", Fn: WrongPacketType}, + {Name: "Ping/BondThenPingWithWrongFrom", Fn: BondThenPingWithWrongFrom}, + {Name: "Findnode/WithoutEndpointProof", Fn: FindnodeWithoutEndpointProof}, + {Name: "Findnode/BasicFindnode", Fn: BasicFindnode}, + {Name: "Findnode/UnsolicitedNeighbors", Fn: UnsolicitedNeighbors}, + {Name: "Findnode/PastExpiration", Fn: FindnodePastExpiration}, + {Name: "Amplification/InvalidPongHash", Fn: FindnodeAmplificationInvalidPongHash}, + {Name: "Amplification/WrongIP", Fn: FindnodeAmplificationWrongIP}, +} diff --git a/cmd/devp2p/internal/v4test/framework.go b/cmd/devp2p/internal/v4test/framework.go new file mode 100644 index 0000000000..9286594181 --- /dev/null +++ b/cmd/devp2p/internal/v4test/framework.go @@ -0,0 +1,123 @@ +// 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 . + +package v4test + +import ( + "crypto/ecdsa" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discover/v4wire" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +const waitTime = 300 * time.Millisecond + +type testenv struct { + l1, l2 net.PacketConn + key *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr +} + +func newTestEnv(remote string, listen1, listen2 string) *testenv { + l1, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen1)) + if err != nil { + panic(err) + } + l2, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", listen2)) + if err != nil { + panic(err) + } + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + node, err := enode.Parse(enode.ValidSchemes, remote) + if err != nil { + panic(err) + } + if node.IP() == nil || node.UDP() == 0 { + var ip net.IP + var tcpPort, udpPort int + if ip = node.IP(); ip == nil { + ip = net.ParseIP("127.0.0.1") + } + if tcpPort = node.TCP(); tcpPort == 0 { + tcpPort = 30303 + } + if udpPort = node.TCP(); udpPort == 0 { + udpPort = 30303 + } + node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort) + } + addr := &net.UDPAddr{IP: node.IP(), Port: node.UDP()} + return &testenv{l1, l2, key, node, addr} +} + +func (te *testenv) close() { + te.l1.Close() + te.l2.Close() +} + +func (te *testenv) send(c net.PacketConn, req v4wire.Packet) []byte { + packet, hash, err := v4wire.Encode(te.key, req) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", req.Name(), err)) + } + if _, err := c.WriteTo(packet, te.remoteAddr); err != nil { + panic(fmt.Errorf("can't send %v: %v", req.Name(), err)) + } + return hash +} + +func (te *testenv) read(c net.PacketConn) (v4wire.Packet, []byte, error) { + buf := make([]byte, 2048) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return nil, nil, err + } + n, _, err := c.ReadFrom(buf) + if err != nil { + return nil, nil, err + } + p, _, hash, err := v4wire.Decode(buf[:n]) + return p, hash, err +} + +func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint { + addr := c.LocalAddr().(*net.UDPAddr) + return v4wire.Endpoint{ + IP: addr.IP.To4(), + UDP: uint16(addr.Port), + TCP: 0, + } +} + +func (te *testenv) remoteEndpoint() v4wire.Endpoint { + return v4wire.NewEndpoint(te.remoteAddr, 0) +} + +func contains(ns []v4wire.Node, key v4wire.Pubkey) bool { + for _, n := range ns { + if n.ID == key { + return true + } + } + return false +} diff --git a/cmd/devp2p/internal/v5test/discv5tests.go b/cmd/devp2p/internal/v5test/discv5tests.go new file mode 100644 index 0000000000..7866498f73 --- /dev/null +++ b/cmd/devp2p/internal/v5test/discv5tests.go @@ -0,0 +1,377 @@ +// 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 . + +package v5test + +import ( + "bytes" + "net" + "sync" + "time" + + "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/netutil" +) + +// Suite is the discv5 test suite. +type Suite struct { + Dest *enode.Node + Listen1, Listen2 string // listening addresses +} + +func (s *Suite) listen1(log logger) (*conn, net.PacketConn) { + c := newConn(s.Dest, log) + l := c.listen(s.Listen1) + return c, l +} + +func (s *Suite) listen2(log logger) (*conn, net.PacketConn, net.PacketConn) { + c := newConn(s.Dest, log) + l1, l2 := c.listen(s.Listen1), c.listen(s.Listen2) + return c, l1, l2 +} + +func (s *Suite) AllTests() []utesting.Test { + return []utesting.Test{ + {Name: "Ping", Fn: s.TestPing}, + {Name: "PingLargeRequestID", Fn: s.TestPingLargeRequestID}, + {Name: "PingMultiIP", Fn: s.TestPingMultiIP}, + {Name: "PingHandshakeInterrupted", Fn: s.TestPingHandshakeInterrupted}, + {Name: "TalkRequest", Fn: s.TestTalkRequest}, + {Name: "FindnodeZeroDistance", Fn: s.TestFindnodeZeroDistance}, + {Name: "FindnodeResults", Fn: s.TestFindnodeResults}, + } +} + +// This test sends PING and expects a PONG response. +func (s *Suite) TestPing(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping, l1) + default: + t.Fatal("expected PONG, got", resp.Name()) + } +} + +func checkPong(t *utesting.T, pong *v5wire.Pong, ping *v5wire.Ping, c net.PacketConn) { + if !bytes.Equal(pong.ReqID, ping.ReqID) { + t.Fatalf("wrong request ID %x in PONG, want %x", pong.ReqID, ping.ReqID) + } + if !pong.ToIP.Equal(laddr(c).IP) { + t.Fatalf("wrong destination IP %v in PONG, want %v", pong.ToIP, laddr(c).IP) + } + if int(pong.ToPort) != laddr(c).Port { + t.Fatalf("wrong destination port %v in PONG, want %v", pong.ToPort, laddr(c).Port) + } +} + +// This test sends PING with a 9-byte request ID, which isn't allowed by the spec. +// The remote node should not respond. +func (s *Suite) TestPingLargeRequestID(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + ping := &v5wire.Ping{ReqID: make([]byte, 9)} + switch resp := conn.reqresp(l1, ping).(type) { + case *v5wire.Pong: + t.Errorf("PONG response with unknown request ID %x", resp.ReqID) + case *readError: + if resp.err == v5wire.ErrInvalidReqID { + t.Error("response with oversized request ID") + } else if !netutil.IsTimeout(resp.err) { + t.Error(resp) + } + } +} + +// In this test, a session is established from one IP as usual. The session is then reused +// on another IP, which shouldn't work. The remote node should respond with WHOAREYOU for +// the attempt from a different IP. +func (s *Suite) TestPingMultiIP(t *utesting.T) { + conn, l1, l2 := s.listen2(t) + defer conn.close() + + // Create the session on l1. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + resp := conn.reqresp(l1, ping) + if resp.Kind() != v5wire.PongMsg { + t.Fatal("expected PONG, got", resp) + } + checkPong(t, resp.(*v5wire.Pong), ping, l1) + + // Send on l2. This reuses the session because there is only one codec. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l2, ping2, nil) + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l2).IP, laddr(l1).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + resp.Node = s.Dest + conn.write(l2, ping2, resp) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Catch the PONG on l2. + switch resp := conn.read(l2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l2) + default: + t.Fatal("expected PONG, got", resp) + } + + // Try on l1 again. + ping3 := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping3, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Pong: + t.Fatalf("remote responded to PING from %v for session on IP %v", laddr(l1).IP, laddr(l2).IP) + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for new session as expected") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// This test starts a handshake, but doesn't finish it and sends a second ordinary message +// packet instead of a handshake message packet. The remote node should respond with +// another WHOAREYOU challenge for the second packet. +func (s *Suite) TestPingHandshakeInterrupted(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // First PING triggers challenge. + ping := &v5wire.Ping{ReqID: conn.nextReqID()} + conn.write(l1, ping, nil) + switch resp := conn.read(l1).(type) { + case *v5wire.Whoareyou: + t.Logf("got WHOAREYOU for PING") + default: + t.Fatal("expected WHOAREYOU, got", resp) + } + + // Send second PING. + ping2 := &v5wire.Ping{ReqID: conn.nextReqID()} + switch resp := conn.reqresp(l1, ping2).(type) { + case *v5wire.Pong: + checkPong(t, resp, ping2, l1) + default: + t.Fatal("expected WHOAREYOU, got", resp) + } +} + +// This test sends TALKREQ and expects an empty TALKRESP response. +func (s *Suite) TestTalkRequest(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + // Non-empty request ID. + id := conn.nextReqID() + resp := conn.reqresp(l1, &v5wire.TalkRequest{ReqID: id, Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if !bytes.Equal(resp.ReqID, id) { + t.Fatalf("wrong request ID %x in TALKRESP, want %x", resp.ReqID, id) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } + + // Empty request ID. + resp = conn.reqresp(l1, &v5wire.TalkRequest{Protocol: "test-protocol"}) + switch resp := resp.(type) { + case *v5wire.TalkResponse: + if len(resp.ReqID) > 0 { + t.Fatalf("wrong request ID %x in TALKRESP, want empty byte array", resp.ReqID) + } + if len(resp.Message) > 0 { + t.Fatalf("non-empty message %x in TALKRESP", resp.Message) + } + default: + t.Fatal("expected TALKRESP, got", resp.Name()) + } +} + +// This test checks that the remote node returns itself for FINDNODE with distance zero. +func (s *Suite) TestFindnodeZeroDistance(t *utesting.T) { + conn, l1 := s.listen1(t) + defer conn.close() + + nodes, err := conn.findnode(l1, []uint{0}) + if err != nil { + t.Fatal(err) + } + if len(nodes) != 1 { + t.Fatalf("remote returned more than one node for FINDNODE [0]") + } + if nodes[0].ID() != conn.remote.ID() { + t.Errorf("ID of response node is %v, want %v", nodes[0].ID(), conn.remote.ID()) + } +} + +// In this test, multiple nodes ping the node under test. After waiting for them to be +// accepted into the remote table, the test checks that they are returned by FINDNODE. +func (s *Suite) TestFindnodeResults(t *utesting.T) { + // Create bystanders. + nodes := make([]*bystander, 5) + added := make(chan enode.ID, len(nodes)) + for i := range nodes { + nodes[i] = newBystander(t, s, added) + defer nodes[i].close() + } + + // Get them added to the remote table. + timeout := 60 * time.Second + timeoutCh := time.After(timeout) + for count := 0; count < len(nodes); { + select { + case id := <-added: + t.Logf("bystander node %v added to remote table", id) + count++ + case <-timeoutCh: + t.Errorf("remote added %d bystander nodes in %v, need %d to continue", count, timeout, len(nodes)) + t.Logf("this can happen if the node has a non-empty table from previous runs") + return + } + } + t.Logf("all %d bystander nodes were added", len(nodes)) + + // Collect our nodes by distance. + var dists []uint + expect := make(map[enode.ID]*enode.Node) + for _, bn := range nodes { + n := bn.conn.localNode.Node() + expect[n.ID()] = n + d := uint(enode.LogDist(n.ID(), s.Dest.ID())) + if !containsUint(dists, d) { + dists = append(dists, d) + } + } + + // Send FINDNODE for all distances. + conn, l1 := s.listen1(t) + defer conn.close() + foundNodes, err := conn.findnode(l1, dists) + if err != nil { + t.Fatal(err) + } + t.Logf("remote returned %d nodes for distance list %v", len(foundNodes), dists) + for _, n := range foundNodes { + delete(expect, n.ID()) + } + if len(expect) > 0 { + t.Errorf("missing %d nodes in FINDNODE result", len(expect)) + t.Logf("this can happen if the test is run multiple times in quick succession") + t.Logf("and the remote node hasn't removed dead nodes from previous runs yet") + } else { + t.Logf("all %d expected nodes were returned", len(nodes)) + } +} + +// A bystander is a node whose only purpose is filling a spot in the remote table. +type bystander struct { + dest *enode.Node + conn *conn + l net.PacketConn + + addedCh chan enode.ID + done sync.WaitGroup +} + +func newBystander(t *utesting.T, s *Suite, added chan enode.ID) *bystander { + conn, l := s.listen1(t) + conn.setEndpoint(l) // bystander nodes need IP/port to get pinged + bn := &bystander{ + conn: conn, + l: l, + dest: s.Dest, + addedCh: added, + } + bn.done.Add(1) + go bn.loop() + return bn +} + +// id returns the node ID of the bystander. +func (bn *bystander) id() enode.ID { + return bn.conn.localNode.ID() +} + +// close shuts down loop. +func (bn *bystander) close() { + bn.conn.close() + bn.done.Wait() +} + +// loop answers packets from the remote node until quit. +func (bn *bystander) loop() { + defer bn.done.Done() + + var ( + lastPing time.Time + wasAdded bool + ) + for { + // Ping the remote node. + if !wasAdded && time.Since(lastPing) > 10*time.Second { + bn.conn.reqresp(bn.l, &v5wire.Ping{ + ReqID: bn.conn.nextReqID(), + ENRSeq: bn.dest.Seq(), + }) + lastPing = time.Now() + } + // Answer packets. + switch p := bn.conn.read(bn.l).(type) { + case *v5wire.Ping: + bn.conn.write(bn.l, &v5wire.Pong{ + ReqID: p.ReqID, + ENRSeq: bn.conn.localNode.Seq(), + ToIP: bn.dest.IP(), + ToPort: uint16(bn.dest.UDP()), + }, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.Findnode: + bn.conn.write(bn.l, &v5wire.Nodes{ReqID: p.ReqID, Total: 1}, nil) + wasAdded = true + bn.notifyAdded() + case *v5wire.TalkRequest: + bn.conn.write(bn.l, &v5wire.TalkResponse{ReqID: p.ReqID}, nil) + case *readError: + if !netutil.IsTemporaryError(p.err) { + bn.conn.logf("shutting down: %v", p.err) + return + } + } + } +} + +func (bn *bystander) notifyAdded() { + if bn.addedCh != nil { + bn.addedCh <- bn.id() + bn.addedCh = nil + } +} diff --git a/cmd/devp2p/internal/v5test/framework.go b/cmd/devp2p/internal/v5test/framework.go new file mode 100644 index 0000000000..9eac37520f --- /dev/null +++ b/cmd/devp2p/internal/v5test/framework.go @@ -0,0 +1,263 @@ +// 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 . + +package v5test + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "fmt" + "net" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/discover/v5wire" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" +) + +// readError represents an error during packet reading. +// This exists to facilitate type-switching on the result of conn.read. +type readError struct { + err error +} + +func (p *readError) Kind() byte { return 99 } +func (p *readError) Name() string { return fmt.Sprintf("error: %v", p.err) } +func (p *readError) Error() string { return p.err.Error() } +func (p *readError) Unwrap() error { return p.err } +func (p *readError) RequestID() []byte { return nil } +func (p *readError) SetRequestID([]byte) {} + +// readErrorf creates a readError with the given text. +func readErrorf(format string, args ...interface{}) *readError { + return &readError{fmt.Errorf(format, args...)} +} + +// This is the response timeout used in tests. +const waitTime = 300 * time.Millisecond + +// conn is a connection to the node under test. +type conn struct { + localNode *enode.LocalNode + localKey *ecdsa.PrivateKey + remote *enode.Node + remoteAddr *net.UDPAddr + listeners []net.PacketConn + + log logger + codec *v5wire.Codec + lastRequest v5wire.Packet + lastChallenge *v5wire.Whoareyou + idCounter uint32 +} + +type logger interface { + Logf(string, ...interface{}) +} + +// newConn sets up a connection to the given node. +func newConn(dest *enode.Node, log logger) *conn { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + db, err := enode.OpenDB("") + if err != nil { + panic(err) + } + ln := enode.NewLocalNode(db, key) + + return &conn{ + localKey: key, + localNode: ln, + remote: dest, + remoteAddr: &net.UDPAddr{IP: dest.IP(), Port: dest.UDP()}, + codec: v5wire.NewCodec(ln, key, mclock.System{}), + log: log, + } +} + +func (tc *conn) setEndpoint(c net.PacketConn) { + tc.localNode.SetStaticIP(laddr(c).IP) + tc.localNode.SetFallbackUDP(laddr(c).Port) +} + +func (tc *conn) listen(ip string) net.PacketConn { + l, err := net.ListenPacket("udp", fmt.Sprintf("%v:0", ip)) + if err != nil { + panic(err) + } + tc.listeners = append(tc.listeners, l) + return l +} + +// close shuts down all listeners and the local node. +func (tc *conn) close() { + for _, l := range tc.listeners { + l.Close() + } + tc.localNode.Database().Close() +} + +// nextReqID creates a request id. +func (tc *conn) nextReqID() []byte { + id := make([]byte, 4) + tc.idCounter++ + binary.BigEndian.PutUint32(id, tc.idCounter) + return id +} + +// reqresp performs a request/response interaction on the given connection. +// The request is retried if a handshake is requested. +func (tc *conn) reqresp(c net.PacketConn, req v5wire.Packet) v5wire.Packet { + reqnonce := tc.write(c, req, nil) + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + if resp.Nonce != reqnonce { + return readErrorf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], reqnonce[:]) + } + resp.Node = tc.remote + tc.write(c, req, resp) + return tc.read(c) + default: + return resp + } +} + +// findnode sends a FINDNODE request and waits for its responses. +func (tc *conn) findnode(c net.PacketConn, dists []uint) ([]*enode.Node, error) { + var ( + findnode = &v5wire.Findnode{ReqID: tc.nextReqID(), Distances: dists} + reqnonce = tc.write(c, findnode, nil) + first = true + total uint8 + results []*enode.Node + ) + for n := 1; n > 0; { + switch resp := tc.read(c).(type) { + case *v5wire.Whoareyou: + // Handle handshake. + if resp.Nonce == reqnonce { + resp.Node = tc.remote + tc.write(c, findnode, resp) + } else { + return nil, fmt.Errorf("unexpected WHOAREYOU (nonce %x), waiting for NODES", resp.Nonce[:]) + } + case *v5wire.Ping: + // Handle ping from remote. + tc.write(c, &v5wire.Pong{ + ReqID: resp.ReqID, + ENRSeq: tc.localNode.Seq(), + }, nil) + case *v5wire.Nodes: + // Got NODES! Check request ID. + if !bytes.Equal(resp.ReqID, findnode.ReqID) { + return nil, fmt.Errorf("NODES response has wrong request id %x", resp.ReqID) + } + // Check total count. It should be greater than one + // and needs to be the same across all responses. + if first { + if resp.Total == 0 || resp.Total > 6 { + return nil, fmt.Errorf("invalid NODES response 'total' %d (not in (0,7))", resp.Total) + } + total = resp.Total + n = int(total) - 1 + first = false + } else { + n-- + if resp.Total != total { + return nil, fmt.Errorf("invalid NODES response 'total' %d (!= %d)", resp.Total, total) + } + } + // Check nodes. + nodes, err := checkRecords(resp.Nodes) + if err != nil { + return nil, fmt.Errorf("invalid node in NODES response: %v", err) + } + results = append(results, nodes...) + default: + return nil, fmt.Errorf("expected NODES, got %v", resp) + } + } + return results, nil +} + +// write sends a packet on the given connection. +func (tc *conn) write(c net.PacketConn, p v5wire.Packet, challenge *v5wire.Whoareyou) v5wire.Nonce { + packet, nonce, err := tc.codec.Encode(tc.remote.ID(), tc.remoteAddr.String(), p, challenge) + if err != nil { + panic(fmt.Errorf("can't encode %v packet: %v", p.Name(), err)) + } + if _, err := c.WriteTo(packet, tc.remoteAddr); err != nil { + tc.logf("Can't send %s: %v", p.Name(), err) + } else { + tc.logf(">> %s", p.Name()) + } + return nonce +} + +// read waits for an incoming packet on the given connection. +func (tc *conn) read(c net.PacketConn) v5wire.Packet { + buf := make([]byte, 1280) + if err := c.SetReadDeadline(time.Now().Add(waitTime)); err != nil { + return &readError{err} + } + n, fromAddr, err := c.ReadFrom(buf) + if err != nil { + return &readError{err} + } + _, _, p, err := tc.codec.Decode(buf[:n], fromAddr.String()) + if err != nil { + return &readError{err} + } + tc.logf("<< %s", p.Name()) + return p +} + +// logf prints to the test log. +func (tc *conn) logf(format string, args ...interface{}) { + if tc.log != nil { + tc.log.Logf("(%s) %s", tc.localNode.ID().TerminalString(), fmt.Sprintf(format, args...)) + } +} + +func laddr(c net.PacketConn) *net.UDPAddr { + return c.LocalAddr().(*net.UDPAddr) +} + +func checkRecords(records []*enr.Record) ([]*enode.Node, error) { + nodes := make([]*enode.Node, len(records)) + for i := range records { + n, err := enode.New(enode.ValidSchemes, records[i]) + if err != nil { + return nil, err + } + nodes[i] = n + } + return nodes, nil +} + +func containsUint(ints []uint, x uint) bool { + for i := range ints { + if ints[i] == x { + return true + } + } + return false +} diff --git a/cmd/devp2p/keycmd.go b/cmd/devp2p/keycmd.go new file mode 100644 index 0000000000..869b8c2a44 --- /dev/null +++ b/cmd/devp2p/keycmd.go @@ -0,0 +1,105 @@ +// 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 . + +package main + +import ( + "fmt" + "net" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" + "gopkg.in/urfave/cli.v1" +) + +var ( + keyCommand = cli.Command{ + Name: "key", + Usage: "Operations on node keys", + Subcommands: []cli.Command{ + keyGenerateCommand, + keyToNodeCommand, + }, + } + keyGenerateCommand = cli.Command{ + Name: "generate", + Usage: "Generates node key files", + ArgsUsage: "keyfile", + Action: genkey, + } + keyToNodeCommand = cli.Command{ + Name: "to-enode", + Usage: "Creates an enode URL from a node key file", + ArgsUsage: "keyfile", + Action: keyToURL, + Flags: []cli.Flag{hostFlag, tcpPortFlag, udpPortFlag}, + } +) + +var ( + hostFlag = cli.StringFlag{ + Name: "ip", + Usage: "IP address of the node", + Value: "127.0.0.1", + } + tcpPortFlag = cli.IntFlag{ + Name: "tcp", + Usage: "TCP port of the node", + Value: 30303, + } + udpPortFlag = cli.IntFlag{ + Name: "udp", + Usage: "UDP port of the node", + Value: 30303, + } +) + +func genkey(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("need key file as argument") + } + file := ctx.Args().Get(0) + + key, err := crypto.GenerateKey() + if err != nil { + return fmt.Errorf("could not generate key: %v", err) + } + return crypto.SaveECDSA(file, key) +} + +func keyToURL(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("need key file as argument") + } + + var ( + file = ctx.Args().Get(0) + host = ctx.String(hostFlag.Name) + tcp = ctx.Int(tcpPortFlag.Name) + udp = ctx.Int(udpPortFlag.Name) + ) + key, err := crypto.LoadECDSA(file) + if err != nil { + return err + } + ip := net.ParseIP(host) + if ip == nil { + return fmt.Errorf("invalid IP address %q", host) + } + node := enode.NewV4(&key.PublicKey, ip, tcp, udp) + fmt.Println(node.URLv4()) + return nil +} diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go index 19aec77ed4..4a4e905a42 100644 --- a/cmd/devp2p/main.go +++ b/cmd/devp2p/main.go @@ -58,10 +58,12 @@ func init() { // Add subcommands. app.Commands = []cli.Command{ enrdumpCommand, + keyCommand, discv4Command, discv5Command, dnsCommand, nodesetCommand, + rlpxCommand, } } @@ -79,7 +81,7 @@ func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool { // getNodeArg handles the common case of a single node descriptor argument. func getNodeArg(ctx *cli.Context) *enode.Node { - if ctx.NArg() != 1 { + if ctx.NArg() < 1 { exit("missing node as command-line argument") } n, err := parseNode(ctx.Args()[0]) diff --git a/cmd/devp2p/nodeset.go b/cmd/devp2p/nodeset.go index 2d86c3f65a..1d78e34c73 100644 --- a/cmd/devp2p/nodeset.go +++ b/cmd/devp2p/nodeset.go @@ -71,6 +71,7 @@ func writeNodesJSON(file string, nodes nodeSet) { } } +// nodes returns the node records contained in the set. func (ns nodeSet) nodes() []*enode.Node { result := make([]*enode.Node, 0, len(ns)) for _, n := range ns { @@ -83,12 +84,37 @@ func (ns nodeSet) nodes() []*enode.Node { return result } +// add ensures the given nodes are present in the set. func (ns nodeSet) add(nodes ...*enode.Node) { for _, n := range nodes { - ns[n.ID()] = nodeJSON{Seq: n.Seq(), N: n} + v := ns[n.ID()] + v.N = n + v.Seq = n.Seq() + ns[n.ID()] = v } } +// topN returns the top n nodes by score as a new set. +func (ns nodeSet) topN(n int) nodeSet { + if n >= len(ns) { + return ns + } + + byscore := make([]nodeJSON, 0, len(ns)) + for _, v := range ns { + byscore = append(byscore, v) + } + sort.Slice(byscore, func(i, j int) bool { + return byscore[i].Score >= byscore[j].Score + }) + result := make(nodeSet, n) + for _, v := range byscore[:n] { + result[v.N.ID()] = v + } + return result +} + +// verify performs integrity checks on the node set. func (ns nodeSet) verify() error { for id, n := range ns { if n.N.ID() != id { diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index 228d3319e3..848288c9cf 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -17,8 +17,12 @@ package main import ( + "errors" "fmt" "net" + "sort" + "strconv" + "strings" "time" "github.com/ethereum/go-ethereum/core/forkid" @@ -60,25 +64,64 @@ func nodesetInfo(ctx *cli.Context) error { ns := loadNodesJSON(ctx.Args().First()) fmt.Printf("Set contains %d nodes.\n", len(ns)) + showAttributeCounts(ns) return nil } +// showAttributeCounts prints the distribution of ENR attributes in a node set. +func showAttributeCounts(ns nodeSet) { + attrcount := make(map[string]int) + var attrlist []interface{} + for _, n := range ns { + r := n.N.Record() + attrlist = r.AppendElements(attrlist[:0])[1:] + for i := 0; i < len(attrlist); i += 2 { + key := attrlist[i].(string) + attrcount[key]++ + } + } + + var keys []string + var maxlength int + for key := range attrcount { + keys = append(keys, key) + if len(key) > maxlength { + maxlength = len(key) + } + } + sort.Strings(keys) + fmt.Println("ENR attribute counts:") + for _, key := range keys { + fmt.Printf("%s%s: %d\n", strings.Repeat(" ", maxlength-len(key)+1), key, attrcount[key]) + } +} + func nodesetFilter(ctx *cli.Context) error { if ctx.NArg() < 1 { return fmt.Errorf("need nodes file as argument") } - ns := loadNodesJSON(ctx.Args().First()) + // Parse -limit. + limit, err := parseFilterLimit(ctx.Args().Tail()) + if err != nil { + return err + } + // Parse the filters. filter, err := andFilter(ctx.Args().Tail()) if err != nil { return err } + // Load nodes and apply filters. + ns := loadNodesJSON(ctx.Args().First()) result := make(nodeSet) for id, n := range ns { if filter(n) { result[id] = n } } + if limit >= 0 { + result = result.topN(limit) + } writeNodesJSON("-", result) return nil } @@ -91,12 +134,15 @@ type nodeFilterC struct { } var filterFlags = map[string]nodeFilterC{ + "-limit": {1, trueFilter}, // needed to skip over -limit "-ip": {1, ipFilter}, "-min-age": {1, minAgeFilter}, "-eth-network": {1, ethFilter}, "-les-server": {0, lesFilter}, + "-snap": {0, snapFilter}, } +// parseFilters parses nodeFilters from args. func parseFilters(args []string) ([]nodeFilter, error) { var filters []nodeFilter for len(args) > 0 { @@ -104,19 +150,39 @@ func parseFilters(args []string) ([]nodeFilter, error) { if !ok { return nil, fmt.Errorf("invalid filter %q", args[0]) } - if len(args) < fc.narg { - return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)) + if len(args)-1 < fc.narg { + return nil, fmt.Errorf("filter %q wants %d arguments, have %d", args[0], fc.narg, len(args)-1) } - filter, err := fc.fn(args[1:]) + filter, err := fc.fn(args[1 : 1+fc.narg]) if err != nil { return nil, fmt.Errorf("%s: %v", args[0], err) } filters = append(filters, filter) - args = args[fc.narg+1:] + args = args[1+fc.narg:] } return filters, nil } +// parseFilterLimit parses the -limit option in args. It returns -1 if there is no limit. +func parseFilterLimit(args []string) (int, error) { + limit := -1 + for i, arg := range args { + if arg == "-limit" { + if i == len(args)-1 { + return -1, errors.New("-limit requires an argument") + } + n, err := strconv.Atoi(args[i+1]) + if err != nil { + return -1, fmt.Errorf("invalid -limit %q", args[i+1]) + } + limit = n + } + } + return limit, nil +} + +// andFilter parses node filters in args and and returns a single filter that requires all +// of them to match. func andFilter(args []string) (nodeFilter, error) { checks, err := parseFilters(args) if err != nil { @@ -133,6 +199,10 @@ func andFilter(args []string) (nodeFilter, error) { return f, nil } +func trueFilter(args []string) (nodeFilter, error) { + return func(n nodeJSON) bool { return true }, nil +} + func ipFilter(args []string) (nodeFilter, error) { _, cidr, err := net.ParseCIDR(args[0]) if err != nil { @@ -172,7 +242,7 @@ func ethFilter(args []string) (nodeFilter, error) { f := func(n nodeJSON) bool { var eth struct { ForkID forkid.ID - _ []rlp.RawValue `rlp:"tail"` + Tail []rlp.RawValue `rlp:"tail"` } if n.N.Load(enr.WithEntry("eth", ð)) != nil { return false @@ -185,9 +255,19 @@ func ethFilter(args []string) (nodeFilter, error) { func lesFilter(args []string) (nodeFilter, error) { f := func(n nodeJSON) bool { var les struct { - _ []rlp.RawValue `rlp:"tail"` + Tail []rlp.RawValue `rlp:"tail"` } return n.N.Load(enr.WithEntry("les", &les)) == nil } return f, nil } + +func snapFilter(args []string) (nodeFilter, error) { + f := func(n nodeJSON) bool { + var snap struct { + Tail []rlp.RawValue `rlp:"tail"` + } + return n.N.Load(enr.WithEntry("snap", &snap)) == nil + } + return f, nil +} diff --git a/cmd/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go new file mode 100644 index 0000000000..24a16f0b3c --- /dev/null +++ b/cmd/devp2p/rlpxcmd.go @@ -0,0 +1,108 @@ +// 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 . + +package main + +import ( + "fmt" + "net" + + "github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest" + "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/rlpx" + "github.com/ethereum/go-ethereum/rlp" + "gopkg.in/urfave/cli.v1" +) + +var ( + rlpxCommand = cli.Command{ + Name: "rlpx", + Usage: "RLPx Commands", + Subcommands: []cli.Command{ + rlpxPingCommand, + rlpxEthTestCommand, + }, + } + rlpxPingCommand = cli.Command{ + Name: "ping", + Usage: "ping ", + Action: rlpxPing, + } + rlpxEthTestCommand = cli.Command{ + Name: "eth-test", + Usage: "Runs tests against a node", + ArgsUsage: " ", + Action: rlpxEthTest, + Flags: []cli.Flag{ + testPatternFlag, + testTAPFlag, + }, + } +) + +func rlpxPing(ctx *cli.Context) error { + n := getNodeArg(ctx) + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP())) + if err != nil { + return err + } + conn := rlpx.NewConn(fd, n.Pubkey()) + ourKey, _ := crypto.GenerateKey() + _, err = conn.Handshake(ourKey) + if err != nil { + return err + } + code, data, _, err := conn.Read() + if err != nil { + return err + } + switch code { + case 0: + var h ethtest.Hello + if err := rlp.DecodeBytes(data, &h); err != nil { + return fmt.Errorf("invalid handshake: %v", err) + } + fmt.Printf("%+v\n", h) + case 1: + var msg []p2p.DiscReason + if rlp.DecodeBytes(data, &msg); len(msg) == 0 { + return fmt.Errorf("invalid disconnect message") + } + return fmt.Errorf("received disconnect message: %v", msg[0]) + default: + return fmt.Errorf("invalid message code %d, expected handshake (code zero)", code) + } + return nil +} + +// rlpxEthTest runs the eth protocol test suite. +func rlpxEthTest(ctx *cli.Context) error { + if ctx.NArg() < 3 { + exit("missing path to chain.rlp as command-line argument") + } + suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args()[1], ctx.Args()[2]) + if err != nil { + exit(err) + } + // check if given node supports eth66, and if so, run eth66 protocol tests as well + is66Failed, _ := utesting.Run(utesting.Test{Name: "Is_66", Fn: suite.Is_66}) + if is66Failed { + return runTests(ctx, suite.EthTests()) + } + return runTests(ctx, suite.AllEthTests()) +} diff --git a/cmd/devp2p/runtest.go b/cmd/devp2p/runtest.go new file mode 100644 index 0000000000..4168f8555b --- /dev/null +++ b/cmd/devp2p/runtest.go @@ -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 . + +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 +} diff --git a/cmd/ethkey/changepassword.go b/cmd/ethkey/changepassword.go index 32fde4ed6d..b9402c2f96 100644 --- a/cmd/ethkey/changepassword.go +++ b/cmd/ethkey/changepassword.go @@ -51,7 +51,7 @@ Change the password of a keyfile.`, } // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) + passphrase := getPassphrase(ctx, false) key, err := keystore.DecryptKey(keyjson, passphrase) if err != nil { utils.Fatalf("Error decrypting key: %v", err) @@ -67,7 +67,7 @@ Change the password of a keyfile.`, } newPhrase = strings.TrimRight(string(content), "\r\n") } else { - newPhrase = promptPassphrase(true) + newPhrase = utils.GetPassPhrase("", true) } // Encrypt the key with the new passphrase. diff --git a/cmd/ethkey/generate.go b/cmd/ethkey/generate.go index 09b00cc9dc..629d23da5b 100644 --- a/cmd/ethkey/generate.go +++ b/cmd/ethkey/generate.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/crypto" - "github.com/pborman/uuid" + "github.com/google/uuid" "gopkg.in/urfave/cli.v1" ) @@ -86,15 +86,18 @@ If you want to encrypt an existing private key, it can be specified by setting } // Create the keyfile object with a random UUID. - id := uuid.NewRandom() + UUID, err := uuid.NewRandom() + if err != nil { + utils.Fatalf("Failed to generate random uuid: %v", err) + } key := &keystore.Key{ - Id: id, + Id: UUID, Address: crypto.PubkeyToAddress(privateKey.PublicKey), PrivateKey: privateKey, } // Encrypt key with passphrase. - passphrase := promptPassphrase(true) + passphrase := getPassphrase(ctx, true) scryptN, scryptP := keystore.StandardScryptN, keystore.StandardScryptP if ctx.Bool("lightkdf") { scryptN, scryptP = keystore.LightScryptN, keystore.LightScryptP diff --git a/cmd/ethkey/inspect.go b/cmd/ethkey/inspect.go index ba03d4d936..b646e43aa5 100644 --- a/cmd/ethkey/inspect.go +++ b/cmd/ethkey/inspect.go @@ -60,7 +60,7 @@ make sure to use this feature with great caution!`, } // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) + passphrase := getPassphrase(ctx, false) key, err := keystore.DecryptKey(keyjson, passphrase) if err != nil { utils.Fatalf("Error decrypting key: %v", err) diff --git a/cmd/ethkey/main.go b/cmd/ethkey/main.go index dbc4960588..6db39174c4 100644 --- a/cmd/ethkey/main.go +++ b/cmd/ethkey/main.go @@ -20,7 +20,7 @@ import ( "fmt" "os" - "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/flags" "gopkg.in/urfave/cli.v1" ) @@ -35,7 +35,7 @@ var gitDate = "" var app *cli.App func init() { - app = utils.NewApp(gitCommit, gitDate, "an Ethereum key manager") + app = flags.NewApp(gitCommit, gitDate, "an Ethereum key manager") app.Commands = []cli.Command{ commandGenerate, commandInspect, @@ -43,7 +43,7 @@ func init() { commandSignMessage, commandVerifyMessage, } - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } // Commonly used command line flags. diff --git a/cmd/ethkey/message.go b/cmd/ethkey/message.go index 5caea69ff6..69c8cf0923 100644 --- a/cmd/ethkey/message.go +++ b/cmd/ethkey/message.go @@ -62,7 +62,7 @@ To sign a message contained in a file, use the --msgfile flag. } // Decrypt key with passphrase. - passphrase := getPassphrase(ctx) + passphrase := getPassphrase(ctx, false) key, err := keystore.DecryptKey(keyjson, passphrase) if err != nil { utils.Fatalf("Error decrypting key: %v", err) diff --git a/cmd/ethkey/utils.go b/cmd/ethkey/utils.go index c6cf5c25a3..f2986e8ee9 100644 --- a/cmd/ethkey/utils.go +++ b/cmd/ethkey/utils.go @@ -23,36 +23,14 @@ import ( "strings" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/crypto" "gopkg.in/urfave/cli.v1" ) -// promptPassphrase prompts the user for a passphrase. Set confirmation to true -// to require the user to confirm the passphrase. -func promptPassphrase(confirmation bool) string { - passphrase, err := console.Stdin.PromptPassword("Password: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - - if confirmation { - confirm, err := console.Stdin.PromptPassword("Repeat password: ") - if err != nil { - utils.Fatalf("Failed to read password confirmation: %v", err) - } - if passphrase != confirm { - utils.Fatalf("Passwords do not match") - } - } - - return passphrase -} - // getPassphrase obtains a passphrase given by the user. It first checks the // --passfile command line flag and ultimately prompts the user for a // passphrase. -func getPassphrase(ctx *cli.Context) string { +func getPassphrase(ctx *cli.Context, confirmation bool) string { // Look for the --passwordfile flag. passphraseFile := ctx.String(passphraseFlag.Name) if passphraseFile != "" { @@ -65,7 +43,7 @@ func getPassphrase(ctx *cli.Context) string { } // Otherwise prompt the user for the passphrase. - return promptPassphrase(false) + return utils.GetPassPhrase("", confirmation) } // signHash is a helper function that calculates a hash for the given message diff --git a/cmd/evm/README.md b/cmd/evm/README.md new file mode 100644 index 0000000000..cdff41f904 --- /dev/null +++ b/cmd/evm/README.md @@ -0,0 +1,269 @@ +## EVM state transition tool + +The `evm t8n` tool is a stateless state transition utility. It is a utility +which can + +1. Take a prestate, including + - Accounts, + - Block context information, + - Previous blockshashes (*optional) +2. Apply a set of transactions, +3. Apply a mining-reward (*optional), +4. And generate a post-state, including + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump + +## Specification + +The idea is to specify the behaviour of this binary very _strict_, so that other +node implementors can build replicas based on their own state-machines, and the +state generators can swap between a `geth`-based implementation and a `parityvm`-based +implementation. + +### Command line params + +Command line params that has to be supported are +``` + + --trace Output full trace logs to files .jsonl + --trace.nomemory Disable full memory dump in traces + --trace.nostack Disable stack output in traces + --trace.noreturndata Disable return data output in traces + --output.basedir value Specifies where output files are placed. Will be created if it does not exist. + --output.alloc alloc Determines where to put the alloc of the post-state. + `stdout` - into the stdout output + `stderr` - into the stderr output + --output.result result Determines where to put the result (stateroot, txroot etc) of the post-state. + `stdout` - into the stdout output + `stderr` - into the stderr output + --state.fork value Name of ruleset to use. + --state.chainid value ChainID to use (default: 1) + --state.reward value Mining reward. Set to -1 to disable (default: 0) + +``` + +### Error codes and output + +All logging should happen against the `stderr`. +There are a few (not many) errors that can occur, those are defined below. + +#### EVM-based errors (`2` to `9`) + +- Other EVM error. Exit code `2` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. +- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` + is invoked targeting a block which history has not been provided for, the program will + exit with code `4`. + +#### IO errors (`10`-`20`) + +- Invalid input json: the supplied data could not be marshalled. + The program will exit with code `10` +- IO problems: failure to load or save files, the program will exit with code `11` + +## Examples +### Basic usage + +Invoking it with the provided example files +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json +``` +Two resulting files: + +`alloc.json`: +```json +{ + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } +} +``` +`result.json`: +```json +{ + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + 1 + ] +} +``` + +We can make them spit out the data to e.g. `stdout` like this: +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout +``` +Output: +```json +{ + "alloc": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "0xfeed1a9d", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "nonce": "0xac" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xa410" + } + }, + "result": { + "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", + "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", + "receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "rejected": [ + 1 + ] + } +} +``` + +## About Ommers + +Mining rewards and ommer rewards might need to be added. This is how those are applied: + +- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. +- For each ommer (mined by `0xbb`), with blocknumber `N-delta` + - (where `delta` is the difference between the current block and the ommer) + - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` + - The account `0xaa` (block miner) is awarded `block_reward / 32` + +To make `state_t8n` apply these, the following inputs are required: + +- `state.reward` + - For ethash, it is `5000000000000000000` `wei`, + - If this is not defined, mining rewards are not applied, + - A value of `0` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an `address` and a `delta`. This + is done via the `env`. + +Note: the tool does not verify that e.g. the normal uncle rules apply, +and allows e.g two uncles at the same height, or the uncle-distance. This means that +the tool allows for negative uncle reward (distance > 8) + +Example: +`./testdata/5/env.json`: +```json +{ + "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000", + "ommers": [ + {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } + ] +} +``` +When applying this, using a reward of `0x08` +Output: +```json +{ + "alloc": { + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { + "balance": "0x88" + }, + "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { + "balance": "0x70" + }, + "0xcccccccccccccccccccccccccccccccccccccccc": { + "balance": "0x60" + } + } +} +``` +### Future EIPS + +It is also possible to experiment with future eips that are not yet defined in a hard fork. +Example, putting EIP-1344 into Frontier: +``` +./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json +``` + +### Block history + +The `BLOCKHASH` opcode requires blockhashes to be provided by the caller, inside the `env`. +If a required blockhash is not provided, the exit code should be `4`: +Example where blockhashes are provided: +``` +./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace +``` +``` +cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 +``` +``` +{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"PUSH1","error":""} +{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"BLOCKHASH","error":""} +{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"STOP","error":""} +{"output":"","gasUsed":"0x17","time":142709} +``` + +In this example, the caller has not provided the required blockhash: +``` +./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace +``` +``` +ERROR(4): getHash(3) invoked, blockhash for that block not provided +``` +Error code: 4 +### Chaining + +Another thing that can be done, is to chain invocations: +``` +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json +INFO [01-21|22:41:22.963] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.966] rejected tx index=0 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" +INFO [01-21|22:41:22.967] rejected tx index=1 hash=0557ba..18d673 from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" + +``` +What happened here, is that we first applied two identical transactions, so the second one was rejected. +Then, taking the poststate alloc as the input for the next state, we tried again to include +the same two transactions: this time, both failed due to too low nonce. + +In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the +actual blocknumber (exposed to the EVM) would not increase. diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go index c019a2fe70..40ad9313c5 100644 --- a/cmd/evm/compiler.go +++ b/cmd/evm/compiler.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var compileCommand = cli.Command{ diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go index 5bc743aa88..68a09cbf50 100644 --- a/cmd/evm/disasm.go +++ b/cmd/evm/disasm.go @@ -23,7 +23,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/core/asm" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var disasmCommand = cli.Command{ diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go new file mode 100644 index 0000000000..c3f1b16efc --- /dev/null +++ b/cmd/evm/internal/t8ntool/execution.go @@ -0,0 +1,265 @@ +// 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 . + +package t8ntool + +import ( + "fmt" + "math/big" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +type Prestate struct { + Env stEnv `json:"env"` + Pre core.GenesisAlloc `json:"pre"` +} + +// ExecutionResult contains the execution status after running a state test, any +// error that might have occurred and a dump of the final state if requested. +type ExecutionResult struct { + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom" gencodec:"required"` + Receipts types.Receipts `json:"receipts"` + Rejected []int `json:"rejected,omitempty"` +} + +type ommer struct { + Delta uint64 `json:"delta"` + Address common.Address `json:"address"` +} + +//go:generate gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go +type stEnv struct { + Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` + Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"` + GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` + Number uint64 `json:"currentNumber" gencodec:"required"` + Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` +} + +type stEnvMarshaling struct { + Coinbase common.UnprefixedAddress + Difficulty *math.HexOrDecimal256 + GasLimit math.HexOrDecimal64 + Number math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 +} + +// Apply applies a set of transactions to a pre-state +func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, + txs types.Transactions, miningReward int64, + getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) { + + // Capture errors for BLOCKHASH operation, if we haven't been supplied the + // required blockhashes + var hashError error + getHash := func(num uint64) common.Hash { + if pre.Env.BlockHashes == nil { + hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num) + return common.Hash{} + } + h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)] + if !ok { + hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num) + } + return h + } + var ( + statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) + signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number)) + gaspool = new(core.GasPool) + blockHash = common.Hash{0x13, 0x37} + rejectedTxs []int + includedTxs types.Transactions + gasUsed = uint64(0) + receipts = make(types.Receipts, 0) + txIndex = 0 + ) + gaspool.AddGas(pre.Env.GasLimit) + vmContext := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + Coinbase: pre.Env.Coinbase, + BlockNumber: new(big.Int).SetUint64(pre.Env.Number), + Time: new(big.Int).SetUint64(pre.Env.Timestamp), + Difficulty: pre.Env.Difficulty, + GasLimit: pre.Env.GasLimit, + GetHash: getHash, + } + // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's + // done in StateProcessor.Process(block, ...), right before transactions are applied. + if chainConfig.DAOForkSupport && + chainConfig.DAOForkBlock != nil && + chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { + misc.ApplyDAOHardFork(statedb) + } + + for i, tx := range txs { + msg, err := tx.AsMessage(signer) + if err != nil { + log.Info("rejected tx", "index", i, "hash", tx.Hash(), "error", err) + rejectedTxs = append(rejectedTxs, i) + continue + } + tracer, err := getTracerFn(txIndex, tx.Hash()) + if err != nil { + return nil, nil, err + } + vmConfig.Tracer = tracer + vmConfig.Debug = (tracer != nil) + statedb.Prepare(tx.Hash(), blockHash, txIndex) + txContext := core.NewEVMTxContext(msg) + snapshot := statedb.Snapshot() + evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) + + // (ret []byte, usedGas uint64, failed bool, err error) + msgResult, err := core.ApplyMessage(evm, msg, gaspool) + if err != nil { + statedb.RevertToSnapshot(snapshot) + log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err) + rejectedTxs = append(rejectedTxs, i) + continue + } + includedTxs = append(includedTxs, tx) + if hashError != nil { + return nil, nil, NewError(ErrorMissingBlockhash, hashError) + } + gasUsed += msgResult.UsedGas + + // Receipt: + { + var root []byte + if chainConfig.IsByzantium(vmContext.BlockNumber) { + statedb.Finalise(true) + } else { + root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() + } + + // Create a new receipt for the transaction, storing the intermediate root and + // gas used by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: gasUsed} + if msgResult.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = msgResult.UsedGas + + // If the transaction created a contract, store the creation address in the receipt. + if msg.To() == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) + } + + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.GetLogs(tx.Hash()) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + // These three are non-consensus fields: + //receipt.BlockHash + //receipt.BlockNumber + receipt.TransactionIndex = uint(txIndex) + receipts = append(receipts, receipt) + } + + txIndex++ + } + statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) + // Add mining reward? + if miningReward > 0 { + // Add mining reward. The mining reward may be `0`, which only makes a difference in the cases + // where + // - the coinbase suicided, or + // - there are only 'bad' transactions, which aren't executed. In those cases, + // the coinbase gets no txfee, so isn't created, and thus needs to be touched + var ( + blockReward = big.NewInt(miningReward) + minerReward = new(big.Int).Set(blockReward) + perOmmer = new(big.Int).Div(blockReward, big.NewInt(32)) + ) + for _, ommer := range pre.Env.Ommers { + // Add 1/32th for each ommer included + minerReward.Add(minerReward, perOmmer) + // Add (8-delta)/8 + reward := big.NewInt(8) + reward.Sub(reward, big.NewInt(0).SetUint64(ommer.Delta)) + reward.Mul(reward, blockReward) + reward.Div(reward, big.NewInt(8)) + statedb.AddBalance(ommer.Address, reward) + } + statedb.AddBalance(pre.Env.Coinbase, minerReward) + } + // Commit block + root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not commit state: %v", err) + return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) + } + execRs := &ExecutionResult{ + StateRoot: root, + TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), + ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), + Bloom: types.CreateBloom(receipts), + LogsHash: rlpHash(statedb.Logs()), + Receipts: receipts, + Rejected: rejectedTxs, + } + return statedb, execRs, nil +} + +func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { + sdb := state.NewDatabase(db) + statedb, _ := state.New(common.Hash{}, sdb, nil) + for addr, a := range accounts { + statedb.SetCode(addr, a.Code) + statedb.SetNonce(addr, a.Nonce) + statedb.SetBalance(addr, a.Balance) + for k, v := range a.Storage { + statedb.SetState(addr, k, v) + } + } + // Commit and re-open to start with a clean state. + root, _ := statedb.Commit(false) + statedb, _ = state.New(root, sdb, nil) + return statedb +} + +func rlpHash(x interface{}) (h common.Hash) { + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, x) + hw.Sum(h[:0]) + return h +} diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go new file mode 100644 index 0000000000..a599462cc6 --- /dev/null +++ b/cmd/evm/internal/t8ntool/flags.go @@ -0,0 +1,113 @@ +// 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 . + +package t8ntool + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/tests" + "gopkg.in/urfave/cli.v1" +) + +var ( + TraceFlag = cli.BoolFlag{ + Name: "trace", + Usage: "Output full trace logs to files .jsonl", + } + TraceDisableMemoryFlag = cli.BoolFlag{ + Name: "trace.nomemory", + Usage: "Disable full memory dump in traces", + } + TraceDisableStackFlag = cli.BoolFlag{ + Name: "trace.nostack", + Usage: "Disable stack output in traces", + } + TraceDisableReturnDataFlag = cli.BoolFlag{ + Name: "trace.noreturndata", + Usage: "Disable return data output in traces", + } + OutputBasedir = cli.StringFlag{ + Name: "output.basedir", + Usage: "Specifies where output files are placed. Will be created if it does not exist.", + Value: "", + } + OutputBodyFlag = cli.StringFlag{ + Name: "output.body", + Usage: "If set, the RLP of the transactions (block body) will be written to this file.", + Value: "", + } + OutputAllocFlag = cli.StringFlag{ + Name: "output.alloc", + Usage: "Determines where to put the `alloc` of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "alloc.json", + } + OutputResultFlag = cli.StringFlag{ + Name: "output.result", + Usage: "Determines where to put the `result` (stateroot, txroot etc) of the post-state.\n" + + "\t`stdout` - into the stdout output\n" + + "\t`stderr` - into the stderr output\n" + + "\t - into the file ", + Value: "result.json", + } + InputAllocFlag = cli.StringFlag{ + Name: "input.alloc", + Usage: "`stdin` or file name of where to find the prestate alloc to use.", + Value: "alloc.json", + } + InputEnvFlag = cli.StringFlag{ + Name: "input.env", + Usage: "`stdin` or file name of where to find the prestate env to use.", + Value: "env.json", + } + InputTxsFlag = cli.StringFlag{ + Name: "input.txs", + Usage: "`stdin` or file name of where to find the transactions to apply.", + Value: "txs.json", + } + RewardFlag = cli.Int64Flag{ + Name: "state.reward", + Usage: "Mining reward. Set to -1 to disable", + Value: 0, + } + ChainIDFlag = cli.Int64Flag{ + Name: "state.chainid", + Usage: "ChainID to use", + Value: 1, + } + ForknameFlag = cli.StringFlag{ + Name: "state.fork", + Usage: fmt.Sprintf("Name of ruleset to use."+ + "\n\tAvailable forknames:"+ + "\n\t %v"+ + "\n\tAvailable extra eips:"+ + "\n\t %v"+ + "\n\tSyntax (+ExtraEip)", + strings.Join(tests.AvailableForks(), "\n\t "), + strings.Join(vm.ActivateableEips(), ", ")), + Value: "Istanbul", + } + VerbosityFlag = cli.IntFlag{ + Name: "verbosity", + Usage: "sets the verbosity level", + Value: 3, + } +) diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go new file mode 100644 index 0000000000..ab5951534e --- /dev/null +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -0,0 +1,80 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package t8ntool + +import ( + "encoding/json" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ = (*stEnvMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (s stEnv) MarshalJSON() ([]byte, error) { + type stEnv struct { + Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + } + var enc stEnv + enc.Coinbase = common.UnprefixedAddress(s.Coinbase) + enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) + enc.GasLimit = math.HexOrDecimal64(s.GasLimit) + enc.Number = math.HexOrDecimal64(s.Number) + enc.Timestamp = math.HexOrDecimal64(s.Timestamp) + enc.BlockHashes = s.BlockHashes + enc.Ommers = s.Ommers + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (s *stEnv) UnmarshalJSON(input []byte) error { + type stEnv struct { + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + } + var dec stEnv + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Coinbase == nil { + return errors.New("missing required field 'currentCoinbase' for stEnv") + } + s.Coinbase = common.Address(*dec.Coinbase) + if dec.Difficulty == nil { + return errors.New("missing required field 'currentDifficulty' for stEnv") + } + s.Difficulty = (*big.Int)(dec.Difficulty) + if dec.GasLimit == nil { + return errors.New("missing required field 'currentGasLimit' for stEnv") + } + s.GasLimit = uint64(*dec.GasLimit) + if dec.Number == nil { + return errors.New("missing required field 'currentNumber' for stEnv") + } + s.Number = uint64(*dec.Number) + if dec.Timestamp == nil { + return errors.New("missing required field 'currentTimestamp' for stEnv") + } + s.Timestamp = uint64(*dec.Timestamp) + if dec.BlockHashes != nil { + s.BlockHashes = dec.BlockHashes + } + if dec.Ommers != nil { + s.Ommers = dec.Ommers + } + return nil +} diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go new file mode 100644 index 0000000000..fedcd12435 --- /dev/null +++ b/cmd/evm/internal/t8ntool/transition.go @@ -0,0 +1,372 @@ +// 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 . + +package t8ntool + +import ( + "crypto/ecdsa" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "os" + "path" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/tests" + "gopkg.in/urfave/cli.v1" +) + +const ( + ErrorEVM = 2 + ErrorVMConfig = 3 + ErrorMissingBlockhash = 4 + + ErrorJson = 10 + ErrorIO = 11 + + stdinSelector = "stdin" +) + +type NumberedError struct { + errorCode int + err error +} + +func NewError(errorCode int, err error) *NumberedError { + return &NumberedError{errorCode, err} +} + +func (n *NumberedError) Error() string { + return fmt.Sprintf("ERROR(%d): %v", n.errorCode, n.err.Error()) +} + +func (n *NumberedError) Code() int { + return n.errorCode +} + +type input struct { + Alloc core.GenesisAlloc `json:"alloc,omitempty"` + Env *stEnv `json:"env,omitempty"` + Txs []*txWithKey `json:"txs,omitempty"` +} + +func Main(ctx *cli.Context) error { + // Configure the go-ethereum logger + glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name))) + log.Root().SetHandler(glogger) + + var ( + err error + tracer vm.Tracer + baseDir = "" + ) + var getTracer func(txIndex int, txHash common.Hash) (vm.Tracer, error) + + // If user specified a basedir, make sure it exists + if ctx.IsSet(OutputBasedir.Name) { + if base := ctx.String(OutputBasedir.Name); len(base) > 0 { + err := os.MkdirAll(base, 0755) // //rw-r--r-- + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) + } + baseDir = base + } + } + if ctx.Bool(TraceFlag.Name) { + // Configure the EVM logger + logConfig := &vm.LogConfig{ + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), + DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name), + Debug: true, + } + var prevFile *os.File + // This one closes the last file + defer func() { + if prevFile != nil { + prevFile.Close() + } + }() + getTracer = func(txIndex int, txHash common.Hash) (vm.Tracer, error) { + if prevFile != nil { + prevFile.Close() + } + traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) + if err != nil { + return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) + } + prevFile = traceFile + return vm.NewJSONLogger(logConfig, traceFile), nil + } + } else { + getTracer = func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error) { + return nil, nil + } + } + // We need to load three things: alloc, env and transactions. May be either in + // stdin input or in files. + // Check if anything needs to be read from stdin + var ( + prestate Prestate + txs types.Transactions // txs to apply + allocStr = ctx.String(InputAllocFlag.Name) + + envStr = ctx.String(InputEnvFlag.Name) + txStr = ctx.String(InputTxsFlag.Name) + inputData = &input{} + ) + // Figure out the prestate alloc + if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { + decoder := json.NewDecoder(os.Stdin) + decoder.Decode(inputData) + } + if allocStr != stdinSelector { + inFile, err := os.Open(allocStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + if err := decoder.Decode(&inputData.Alloc); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling alloc-file: %v", err)) + } + } + prestate.Pre = inputData.Alloc + + // Set the block environment + if envStr != stdinSelector { + inFile, err := os.Open(envStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading env file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + var env stEnv + if err := decoder.Decode(&env); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling env-file: %v", err)) + } + inputData.Env = &env + } + prestate.Env = *inputData.Env + + vmConfig := vm.Config{ + Tracer: tracer, + Debug: (tracer != nil), + } + // Construct the chainconfig + var chainConfig *params.ChainConfig + if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { + return NewError(ErrorVMConfig, fmt.Errorf("Failed constructing chain configuration: %v", err)) + } else { + chainConfig = cConf + vmConfig.ExtraEips = extraEips + } + // Set the chain id + chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) + + var txsWithKeys []*txWithKey + if txStr != stdinSelector { + inFile, err := os.Open(txStr) + if err != nil { + return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) + } + defer inFile.Close() + decoder := json.NewDecoder(inFile) + if err := decoder.Decode(&txsWithKeys); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) + } + } else { + txsWithKeys = inputData.Txs + } + // We may have to sign the transactions. + signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number))) + + if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { + return NewError(ErrorJson, fmt.Errorf("Failed signing transactions: %v", err)) + } + + // Iterate over all the tests, run them and aggregate the results + + // Run the test and aggregate the result + state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) + if err != nil { + return err + } + body, _ := rlp.EncodeToBytes(txs) + // Dump the excution result + collector := make(Alloc) + state.DumpToCollector(collector, false, false, false, nil, -1) + return dispatchOutput(ctx, baseDir, result, collector, body) + +} + +// txWithKey is a helper-struct, to allow us to use the types.Transaction along with +// a `secretKey`-field, for input +type txWithKey struct { + key *ecdsa.PrivateKey + tx *types.Transaction +} + +func (t *txWithKey) UnmarshalJSON(input []byte) error { + // Read the secretKey, if present + type sKey struct { + Key *common.Hash `json:"secretKey"` + } + var key sKey + if err := json.Unmarshal(input, &key); err != nil { + return err + } + if key.Key != nil { + k := key.Key.Hex()[2:] + if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { + return err + } else { + t.key = ecdsaKey + } + } + // Now, read the transaction itself + var tx types.Transaction + if err := json.Unmarshal(input, &tx); err != nil { + return err + } + t.tx = &tx + return nil +} + +// signUnsignedTransactions converts the input txs to canonical transactions. +// +// The transactions can have two forms, either +// 1. unsigned or +// 2. signed +// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set. +// If so, we sign it here and now, with the given `secretKey` +// If the condition above is not met, then it's considered a signed transaction. +// +// To manage this, we read the transactions twice, first trying to read the secretKeys, +// and secondly to read them with the standard tx json format +func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) { + var signedTxs []*types.Transaction + for i, txWithKey := range txs { + tx := txWithKey.tx + key := txWithKey.key + v, r, s := tx.RawSignatureValues() + if key != nil && v.BitLen()+r.BitLen()+s.BitLen() == 0 { + // This transaction needs to be signed + signed, err := types.SignTx(tx, signer, key) + if err != nil { + return nil, NewError(ErrorJson, fmt.Errorf("Tx %d: failed to sign tx: %v", i, err)) + } + signedTxs = append(signedTxs, signed) + } else { + // Already signed + signedTxs = append(signedTxs, tx) + } + } + return signedTxs, nil +} + +type Alloc map[common.Address]core.GenesisAccount + +func (g Alloc) OnRoot(common.Hash) {} + +func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) { + balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10) + var storage map[common.Hash]common.Hash + if dumpAccount.Storage != nil { + storage = make(map[common.Hash]common.Hash) + for k, v := range dumpAccount.Storage { + storage[k] = common.HexToHash(v) + } + } + genesisAccount := core.GenesisAccount{ + Code: common.FromHex(dumpAccount.Code), + Storage: storage, + Balance: balance, + Nonce: dumpAccount.Nonce, + } + g[addr] = genesisAccount +} + +// saveFile marshalls the object to the given file +func saveFile(baseDir, filename string, data interface{}) error { + b, err := json.MarshalIndent(data, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + location := path.Join(baseDir, filename) + if err = ioutil.WriteFile(location, b, 0644); err != nil { + return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) + } + log.Info("Wrote file", "file", location) + return nil +} + +// dispatchOutput writes the output data to either stderr or stdout, or to the specified +// files +func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error { + stdOutObject := make(map[string]interface{}) + stdErrObject := make(map[string]interface{}) + dispatch := func(baseDir, fName, name string, obj interface{}) error { + switch fName { + case "stdout": + stdOutObject[name] = obj + case "stderr": + stdErrObject[name] = obj + case "": + // don't save + default: // save to file + if err := saveFile(baseDir, fName, obj); err != nil { + return err + } + } + return nil + } + if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil { + return err + } + if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil { + return err + } + if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil { + return err + } + if len(stdOutObject) > 0 { + b, err := json.MarshalIndent(stdOutObject, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + os.Stdout.Write(b) + } + if len(stdErrObject) > 0 { + b, err := json.MarshalIndent(stdErrObject, "", " ") + if err != nil { + return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) + } + os.Stderr.Write(b) + } + return nil +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 72cb1ab852..8a3e4e0ea2 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -22,7 +22,9 @@ import ( "math/big" "os" + "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/internal/flags" "gopkg.in/urfave/cli.v1" ) @@ -30,7 +32,7 @@ var gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags) var gitDate = "" var ( - app = utils.NewApp(gitCommit, gitDate, "the evm command line interface") + app = flags.NewApp(gitCommit, gitDate, "the evm command line interface") DebugFlag = cli.BoolFlag{ Name: "debug", @@ -119,6 +121,14 @@ var ( Name: "nostack", Usage: "disable stack output", } + DisableStorageFlag = cli.BoolFlag{ + Name: "nostorage", + Usage: "disable storage output", + } + DisableReturnDataFlag = cli.BoolFlag{ + Name: "noreturndata", + Usage: "disable return data output", + } EVMInterpreterFlag = cli.StringFlag{ Name: "vm.evm", Usage: "External EVM configuration (default = built-in interpreter)", @@ -126,6 +136,30 @@ var ( } ) +var stateTransitionCommand = cli.Command{ + Name: "transition", + Aliases: []string{"t8n"}, + Usage: "executes a full state transition", + Action: t8ntool.Main, + Flags: []cli.Flag{ + t8ntool.TraceFlag, + t8ntool.TraceDisableMemoryFlag, + t8ntool.TraceDisableStackFlag, + t8ntool.TraceDisableReturnDataFlag, + t8ntool.OutputBasedir, + t8ntool.OutputAllocFlag, + t8ntool.OutputResultFlag, + t8ntool.OutputBodyFlag, + t8ntool.InputAllocFlag, + t8ntool.InputEnvFlag, + t8ntool.InputTxsFlag, + t8ntool.ForknameFlag, + t8ntool.ChainIDFlag, + t8ntool.RewardFlag, + t8ntool.VerbosityFlag, + }, +} + func init() { app.Flags = []cli.Flag{ BenchFlag, @@ -149,6 +183,8 @@ func init() { ReceiverFlag, DisableMemoryFlag, DisableStackFlag, + DisableStorageFlag, + DisableReturnDataFlag, EVMInterpreterFlag, } app.Commands = []cli.Command{ @@ -156,13 +192,18 @@ func init() { disasmCommand, runCommand, stateTestCommand, + stateTransitionCommand, } - cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate + cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate } func main() { if err := app.Run(os.Args); err != nil { + code := 1 + if ec, ok := err.(*t8ntool.NumberedError); ok { + code = ec.Code() + } fmt.Fprintln(os.Stderr, err) - os.Exit(1) + os.Exit(code) } } diff --git a/cmd/evm/poststate.json b/cmd/evm/poststate.json new file mode 100644 index 0000000000..9ee17f18d1 --- /dev/null +++ b/cmd/evm/poststate.json @@ -0,0 +1,23 @@ +{ + "root": "f4157bb27bcb1d1a63001434a249a80948f2e9fe1f53d551244c1dae826b5b23", + "accounts": { + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { + "balance": "4276951709", + "nonce": 1, + "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "6916764286133345652", + "nonce": 172, + "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + }, + "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "42500", + "nonce": 0, + "root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + } + } +} \ No newline at end of file diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 639f0c0ac9..4063767cb8 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -38,7 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var runCommand = cli.Command{ @@ -108,9 +108,11 @@ func runCmd(ctx *cli.Context) error { glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) log.Root().SetHandler(glogger) logconfig := &vm.LogConfig{ - DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), - Debug: ctx.GlobalBool(DebugFlag.Name), + DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name), + DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name), + Debug: ctx.GlobalBool(DebugFlag.Name), } var ( diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 52c1eca715..c4df936c75 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) var stateTestCommand = cli.Command{ @@ -59,8 +59,10 @@ func stateTestCmd(ctx *cli.Context) error { // Configure the EVM logger config := &vm.LogConfig{ - DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name), + DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name), } var ( tracer vm.Tracer @@ -96,7 +98,7 @@ func stateTestCmd(ctx *cli.Context) error { for _, st := range test.Subtests() { // Run the test and aggregate the result result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} - state, err := test.Run(st, cfg, false) + _, state, err := test.Run(st, cfg, false) // print state root for evmlab tracing if ctx.GlobalBool(MachineFlag.Name) && state != nil { fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false)) diff --git a/cmd/evm/testdata/1/alloc.json b/cmd/evm/testdata/1/alloc.json new file mode 100644 index 0000000000..cef1a25ff0 --- /dev/null +++ b/cmd/evm/testdata/1/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/env.json b/cmd/evm/testdata/1/env.json new file mode 100644 index 0000000000..dd60abd205 --- /dev/null +++ b/cmd/evm/testdata/1/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/1/txs.json b/cmd/evm/testdata/1/txs.json new file mode 100644 index 0000000000..50b31ff31b --- /dev/null +++ b/cmd/evm/testdata/1/txs.json @@ -0,0 +1,26 @@ +[ + { + "gas": "0x5208", + "gasPrice": "0x2", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0x0", + "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", + "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "v": "0x1b", + "value": "0x1" + }, + { + "gas": "0x5208", + "gasPrice": "0x2", + "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", + "input": "0x", + "nonce": "0x0", + "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", + "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", + "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", + "v": "0x1b", + "value": "0x1" + } +] diff --git a/cmd/evm/testdata/2/alloc.json b/cmd/evm/testdata/2/alloc.json new file mode 100644 index 0000000000..a9720afc93 --- /dev/null +++ b/cmd/evm/testdata/2/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x6001600053600160006001f0ff00", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/2/env.json b/cmd/evm/testdata/2/env.json new file mode 100644 index 0000000000..ebadd3f06a --- /dev/null +++ b/cmd/evm/testdata/2/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8" +} \ No newline at end of file diff --git a/cmd/evm/testdata/2/readme.md b/cmd/evm/testdata/2/readme.md new file mode 100644 index 0000000000..c116f0e792 --- /dev/null +++ b/cmd/evm/testdata/2/readme.md @@ -0,0 +1 @@ +These files examplify a selfdestruct to the `0`-address. \ No newline at end of file diff --git a/cmd/evm/testdata/2/txs.json b/cmd/evm/testdata/2/txs.json new file mode 100644 index 0000000000..3044458588 --- /dev/null +++ b/cmd/evm/testdata/2/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/3/alloc.json b/cmd/evm/testdata/3/alloc.json new file mode 100644 index 0000000000..dca318ee54 --- /dev/null +++ b/cmd/evm/testdata/3/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x600140", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/3/env.json b/cmd/evm/testdata/3/env.json new file mode 100644 index 0000000000..e283eff461 --- /dev/null +++ b/cmd/evm/testdata/3/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8", + "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} +} \ No newline at end of file diff --git a/cmd/evm/testdata/3/readme.md b/cmd/evm/testdata/3/readme.md new file mode 100644 index 0000000000..499f03d7aa --- /dev/null +++ b/cmd/evm/testdata/3/readme.md @@ -0,0 +1,2 @@ +These files examplify a transition where a transaction (excuted on block 5) requests +the blockhash for block `1`. diff --git a/cmd/evm/testdata/3/txs.json b/cmd/evm/testdata/3/txs.json new file mode 100644 index 0000000000..3044458588 --- /dev/null +++ b/cmd/evm/testdata/3/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/4/alloc.json b/cmd/evm/testdata/4/alloc.json new file mode 100644 index 0000000000..fadf2bdc4e --- /dev/null +++ b/cmd/evm/testdata/4/alloc.json @@ -0,0 +1,16 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x600340", + "nonce" : "0x00", + "storage" : { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/4/env.json b/cmd/evm/testdata/4/env.json new file mode 100644 index 0000000000..e283eff461 --- /dev/null +++ b/cmd/evm/testdata/4/env.json @@ -0,0 +1,8 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "0x020000", + "currentGasLimit" : "0x3b9aca00", + "currentNumber" : "0x05", + "currentTimestamp" : "0x03e8", + "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} +} \ No newline at end of file diff --git a/cmd/evm/testdata/4/readme.md b/cmd/evm/testdata/4/readme.md new file mode 100644 index 0000000000..08840d37bd --- /dev/null +++ b/cmd/evm/testdata/4/readme.md @@ -0,0 +1,3 @@ +These files examplify a transition where a transaction (excuted on block 5) requests +the blockhash for block `4`, but where the hash for that block is missing. +It's expected that executing these should cause `exit` with errorcode `4`. diff --git a/cmd/evm/testdata/4/txs.json b/cmd/evm/testdata/4/txs.json new file mode 100644 index 0000000000..3044458588 --- /dev/null +++ b/cmd/evm/testdata/4/txs.json @@ -0,0 +1,14 @@ +[ + { + "input" : "0x", + "gas" : "0x5f5e100", + "gasPrice" : "0x1", + "nonce" : "0x0", + "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "0x186a0", + "v" : "0x1b", + "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", + "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", + "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/5/alloc.json b/cmd/evm/testdata/5/alloc.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/cmd/evm/testdata/5/alloc.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/cmd/evm/testdata/5/env.json b/cmd/evm/testdata/5/env.json new file mode 100644 index 0000000000..1085f63e62 --- /dev/null +++ b/cmd/evm/testdata/5/env.json @@ -0,0 +1,11 @@ +{ + "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "1", + "currentTimestamp": "1000", + "ommers": [ + {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, + {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } + ] +} \ No newline at end of file diff --git a/cmd/evm/testdata/5/readme.md b/cmd/evm/testdata/5/readme.md new file mode 100644 index 0000000000..e2b608face --- /dev/null +++ b/cmd/evm/testdata/5/readme.md @@ -0,0 +1 @@ +These files examplify a transition where there are no transcations, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). \ No newline at end of file diff --git a/cmd/evm/testdata/5/txs.json b/cmd/evm/testdata/5/txs.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/cmd/evm/testdata/5/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/7/alloc.json b/cmd/evm/testdata/7/alloc.json new file mode 100644 index 0000000000..cef1a25ff0 --- /dev/null +++ b/cmd/evm/testdata/7/alloc.json @@ -0,0 +1,12 @@ +{ + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x5ffd4878be161d74", + "code": "0x", + "nonce": "0xac", + "storage": {} + }, + "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ + "balance": "0xfeedbead", + "nonce" : "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/7/env.json b/cmd/evm/testdata/7/env.json new file mode 100644 index 0000000000..8fd9bc041b --- /dev/null +++ b/cmd/evm/testdata/7/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000", + "currentGasLimit": "0x750a163df65e8a", + "currentNumber": "5", + "currentTimestamp": "1000" +} \ No newline at end of file diff --git a/cmd/evm/testdata/7/readme.md b/cmd/evm/testdata/7/readme.md new file mode 100644 index 0000000000..c9826e0ba6 --- /dev/null +++ b/cmd/evm/testdata/7/readme.md @@ -0,0 +1,7 @@ +This is a test for HomesteadToDao, checking if the +DAO-transition works + +Example: +``` +./statet8n --input.alloc=./testdata/7/alloc.json --input.txs=./testdata/7/txs.json --input.env=./testdata/7/env.json --output.alloc=stdout --state.fork=HomesteadToDaoAt5 +``` \ No newline at end of file diff --git a/cmd/evm/testdata/7/txs.json b/cmd/evm/testdata/7/txs.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/cmd/evm/testdata/7/txs.json @@ -0,0 +1 @@ +[] diff --git a/cmd/evm/testdata/8/alloc.json b/cmd/evm/testdata/8/alloc.json new file mode 100644 index 0000000000..1d1b5f86c6 --- /dev/null +++ b/cmd/evm/testdata/8/alloc.json @@ -0,0 +1,11 @@ +{ + "0x000000000000000000000000000000000000aaaa": { + "balance": "0x03", + "code": "0x5854505854", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x100000", + "nonce": "0x00" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/env.json b/cmd/evm/testdata/8/env.json new file mode 100644 index 0000000000..8b91934724 --- /dev/null +++ b/cmd/evm/testdata/8/env.json @@ -0,0 +1,7 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty": "0x20000", + "currentGasLimit": "0x1000000000", + "currentNumber": "0x1000000", + "currentTimestamp": "0x04" +} \ No newline at end of file diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md new file mode 100644 index 0000000000..e021cd7e2e --- /dev/null +++ b/cmd/evm/testdata/8/readme.md @@ -0,0 +1,63 @@ +## EIP-2930 testing + +This test contains testcases for EIP-2930, which uses transactions with access lists. + +### Prestate + +The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the +following code: `0x5854505854`: `PC ;SLOAD; POP; PC; SLOAD`. + +Essentialy, this contract does `SLOAD(0)` and `SLOAD(3)`. + +The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. + +## Transactions + +There are three transactions, each invokes the contract above. + +1. ACL-transaction, which contains some non-used slots +2. Regular transaction +3. ACL-transaction, which contains the slots `1` and `3` in `0x000000000000000000000000000000000000aaaa` + +## Execution + +Running it yields: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace && cat trace-* | grep SLOAD +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} + +``` + +Simlarly, we can provide the input transactions via `stdin` instead of as file: + +``` +dir=./testdata/8 \ + && cat $dir/txs.json | jq "{txs: .}" \ + | ./evm t8n --state.fork=Berlin \ + --input.alloc=$dir/alloc.json \ + --input.txs=stdin \ + --input.env=$dir/env.json \ + --trace \ + && cat trace-* | grep SLOAD + +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} +``` + +If we try to execute it on older rules: +``` +dir=./testdata/8 && ./evm t8n --state.fork=Istanbul --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json +INFO [01-21|23:21:51.265] rejected tx index=0 hash=d2818d..6ab3da error="tx type not supported" +INFO [01-21|23:21:51.265] rejected tx index=1 hash=26ea00..81c01b from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" +INFO [01-21|23:21:51.265] rejected tx index=2 hash=698d01..369cee error="tx type not supported" +``` +Number `1` and `3` are not applicable, and therefore number `2` has wrong nonce. \ No newline at end of file diff --git a/cmd/evm/testdata/8/txs.json b/cmd/evm/testdata/8/txs.json new file mode 100644 index 0000000000..35142ba234 --- /dev/null +++ b/cmd/evm/testdata/8/txs.json @@ -0,0 +1,58 @@ +[ + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x0", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x0000000000000000000000000000000000000000", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "input": "0x", + "nonce": "0x1", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x2", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + { + "gas": "0x4ef00", + "gasPrice": "0x1", + "chainId": "0x1", + "input": "0x", + "nonce": "0x2", + "to": "0x000000000000000000000000000000000000aaaa", + "value": "0x1", + "type" : "0x1", + "accessList": [ + {"address": "0x000000000000000000000000000000000000aaaa", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ] + } + ], + "v": "0x0", + "r": "0x0", + "s": "0x0", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + } +] diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh new file mode 100644 index 0000000000..34c9249855 --- /dev/null +++ b/cmd/evm/transition-test.sh @@ -0,0 +1,191 @@ +#!/bin/bash +ticks="\`\`\`" + +function showjson(){ + echo "\`$1\`:" + echo "${ticks}json" + cat $1 + echo "" + echo "$ticks" +} +function demo(){ + echo "$ticks" + echo "$1" + echo "$ticks" + echo "" +} +function tick(){ + echo "$ticks" +} + +cat << EOF +## EVM state transition tool + +The \`evm t8n\` tool is a stateless state transition utility. It is a utility +which can + +1. Take a prestate, including + - Accounts, + - Block context information, + - Previous blockshashes (*optional) +2. Apply a set of transactions, +3. Apply a mining-reward (*optional), +4. And generate a post-state, including + - State root, transaction root, receipt root, + - Information about rejected transactions, + - Optionally: a full or partial post-state dump + +## Specification + +The idea is to specify the behaviour of this binary very _strict_, so that other +node implementors can build replicas based on their own state-machines, and the +state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based +implementation. + +### Command line params + +Command line params that has to be supported are +$(tick) + +` ./evm t8n -h | grep "trace\|output\|state\."` + +$(tick) + +### Error codes and output + +All logging should happen against the \`stderr\`. +There are a few (not many) errors that can occur, those are defined below. + +#### EVM-based errors (\`2\` to \`9\`) + +- Other EVM error. Exit code \`2\` +- Failed configuration: when a non-supported or invalid fork was specified. Exit code \`3\`. +- Block history is not supplied, but needed for a \`BLOCKHASH\` operation. If \`BLOCKHASH\` + is invoked targeting a block which history has not been provided for, the program will + exit with code \`4\`. + +#### IO errors (\`10\`-\`20\`) + +- Invalid input json: the supplied data could not be marshalled. + The program will exit with code \`10\` +- IO problems: failure to load or save files, the program will exit with code \`11\` + +EOF + +# This should exit with 3 +./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null +if [ $? != 3 ]; then + echo "Failed, exitcode should be 3" +fi +cat << EOF +## Examples +### Basic usage + +Invoking it with the provided example files +EOF +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json" +tick;echo "$cmd"; tick +$cmd 2>/dev/null +echo "Two resulting files:" +echo "" +showjson alloc.json +showjson result.json +echo "" + +echo "We can make them spit out the data to e.g. \`stdout\` like this:" +cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout" +tick;echo "$cmd"; tick +output=`$cmd 2>/dev/null` +echo "Output:" +echo "${ticks}json" +echo "$output" +echo "$ticks" + +cat << EOF + +## About Ommers + +Mining rewards and ommer rewards might need to be added. This is how those are applied: + +- \`block_reward\` is the block mining reward for the miner (\`0xaa\`), of a block at height \`N\`. +- For each ommer (mined by \`0xbb\`), with blocknumber \`N-delta\` + - (where \`delta\` is the difference between the current block and the ommer) + - The account \`0xbb\` (ommer miner) is awarded \`(8-delta)/ 8 * block_reward\` + - The account \`0xaa\` (block miner) is awarded \`block_reward / 32\` + +To make \`state_t8n\` apply these, the following inputs are required: + +- \`state.reward\` + - For ethash, it is \`5000000000000000000\` \`wei\`, + - If this is not defined, mining rewards are not applied, + - A value of \`0\` is valid, and causes accounts to be 'touched'. +- For each ommer, the tool needs to be given an \`address\` and a \`delta\`. This + is done via the \`env\`. + +Note: the tool does not verify that e.g. the normal uncle rules apply, +and allows e.g two uncles at the same height, or the uncle-distance. This means that +the tool allows for negative uncle reward (distance > 8) + +Example: +EOF + +showjson ./testdata/5/env.json + +echo "When applying this, using a reward of \`0x08\`" +cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80" +output=`$cmd 2>/dev/null` +echo "Output:" +echo "${ticks}json" +echo "$output" +echo "$ticks" + +echo "### Future EIPS" +echo "" +echo "It is also possible to experiment with future eips that are not yet defined in a hard fork." +echo "Example, putting EIP-1344 into Frontier: " +cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json" +tick;echo "$cmd"; tick +echo "" + +echo "### Block history" +echo "" +echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." +echo "If a required blockhash is not provided, the exit code should be \`4\`:" +echo "Example where blockhashes are provided: " +cmd="./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace" +tick && echo $cmd && tick +$cmd 2>&1 >/dev/null +cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2" +tick && echo $cmd && tick +echo "$ticks" +cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 +echo "$ticks" +echo "" + +echo "In this example, the caller has not provided the required blockhash:" +cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace" +tick && echo $cmd && tick +tick +$cmd +errc=$? +tick +echo "Error code: $errc" + + +echo "### Chaining" +echo "" +echo "Another thing that can be done, is to chain invocations:" +cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout" +cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json" +echo "$ticks" +echo "$cmd1 | $cmd2" +output=$($cmd1 | $cmd2 ) +echo $output +echo "$ticks" +echo "What happened here, is that we first applied two identical transactions, so the second one was rejected. " +echo "Then, taking the poststate alloc as the input for the next state, we tried again to include" +echo "the same two transactions: this time, both failed due to too low nonce." +echo "" +echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the" +echo "actual blocknumber (exposed to the EVM) would not increase." +echo "" diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md new file mode 100644 index 0000000000..364689a782 --- /dev/null +++ b/cmd/faucet/README.md @@ -0,0 +1,50 @@ +# Faucet + +The `faucet` is a simplistic web application with the goal of distributing small amounts of Ether in private and test networks. + +Users need to post their Ethereum addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the Ether. After a funding round, the faucet prevents the same user requesting again for a pre-configured amount of time, proportional to the amount of Ether requested. + +## Operation + +The `faucet` is a single binary app (everything included) with all configurations set via command line flags and a few files. + +First thing's first, the `faucet` needs to connect to an Ethereum network, for which it needs the necessary genesis and network infos. Each of the following flags must be set: + +- `--genesis` is a path to a file containin the network `genesis.json` +- `--network` is the devp2p network id used during connection +- `--bootnodes` is a list of `enode://` ids to join the network through + +The `faucet` will use the `les` protocol to join the configured Ethereum network and will store its data in `$HOME/.faucet` (currently not configurable). + +## Funding + +To be able to distribute funds, the `faucet` needs access to an already funded Ethereum account. This can be configured via: + +- `--account.json` is a path to the Ethereum account's JSON key file +- `--account.pass` is a path to a text file with the decryption passphrase + +The faucet is able to distribute various amounts of Ether in exchange for various timeouts. These can be configured via: + +- `--faucet.amount` is the number of Ethers to send by default +- `--faucet.minutes` is the time to wait before allowing a rerequest +- `--faucet.tiers` is the funding tiers to support (x3 time, x2.5 funds) + +## Sybil protection + +To prevent the same user from exhausting funds in a loop, the `faucet` ties requests to social networks and captcha resolvers. + +Captcha protection uses Google's invisible ReCaptcha, thus the `faucet` needs to run on a live domain. The domain needs to be registered in Google's systems to retrieve the captcha API token and secrets. After doing so, captcha protection may be enabled via: + +- `--captcha.token` is the API token for ReCaptcha +- `--captcha.secret` is the API secret for ReCaptcha + +Sybil protection via Twitter requires an API key as of 15th December, 2020. To obtain it, a Twitter user must be upgraded to developer status and a new Twitter App deployed with it. The app's `Bearer` token is required by the faucet to retrieve tweet data: + +- `--twitter.token` is the Bearer token for `v2` API access +- `--twitter.token.v1` is the Bearer token for `v1` API access + +Sybil protection via Facebook uses the website to directly download post data thus does not currently require an API configuration. + +## Miscellaneous + +Beside the above - mostly essential - CLI flags, there are a number that can be used to fine tune the `faucet`'s operation. Please see `faucet --help` for a full list. \ No newline at end of file diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 6c857a2987..5aab7598a1 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with go-ethereum. If not, see . -// faucet is a Ether faucet backed by a light client. +// faucet is an Ether faucet backed by a light client. package main //go:generate go-bindata -nometadata -o website.go faucet.html @@ -44,18 +44,18 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" @@ -84,10 +84,15 @@ var ( noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") - bep2eContracts = flag.String("bep2eContracts", "", "the list of bep2p contracts") - bep2eSymbols = flag.String("bep2eSymbols", "", "the symbol of bep2p tokens") - bep2eAmounts = flag.String("bep2eAmounts", "", "the amount of bep2p tokens") - fixGasPrice = flag.Int64("faucet.fixedprice", 0, "Will use fixed gas price if specified") + bep2eContracts = flag.String("bep2eContracts", "", "the list of bep2p contracts") + bep2eSymbols = flag.String("bep2eSymbols", "", "the symbol of bep2p tokens") + bep2eAmounts = flag.String("bep2eAmounts", "", "the amount of bep2p tokens") + fixGasPrice = flag.Int64("faucet.fixedprice", 0, "Will use fixed gas price if specified") + twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API") + twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API") + + goerliFlag = flag.Bool("goerli", false, "Initializes the faucet with Görli network config") + rinkebyFlag = flag.Bool("rinkeby", false, "Initializes the faucet with Rinkeby network config") ) var ( @@ -165,28 +170,24 @@ func main() { log.Crit("Failed to render the faucet template", "err", err) } // Load and parse the genesis block requested by the user - blob, err := ioutil.ReadFile(*genesisFlag) + genesis, err := getGenesis(genesisFlag, *goerliFlag, *rinkebyFlag) if err != nil { - log.Crit("Failed to read genesis block contents", "genesis", *genesisFlag, "err", err) - } - genesis := new(core.Genesis) - if err = json.Unmarshal(blob, genesis); err != nil { - log.Crit("Failed to parse genesis block json", "err", err) + log.Crit("Failed to parse genesis config", "err", err) } // Convert the bootnodes to internal enode representations - var enodes []*discv5.Node + var enodes []*enode.Node for _, boot := range strings.Split(*bootFlag, ",") { - if url, err := discv5.ParseNode(boot); err == nil { + if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil { enodes = append(enodes, url) } else { log.Error("Failed to parse bootnode URL", "url", boot, "err", err) } } // Load up the account key and decrypt its password - if blob, err = ioutil.ReadFile(*accPassFlag); err != nil { + blob, err := ioutil.ReadFile(*accPassFlag) + if err != nil { log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err) } - // Delete trailing newline in password pass := strings.TrimSuffix(string(blob), "\n") ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP) @@ -194,11 +195,12 @@ func main() { log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err) } acc, err := ks.Import(blob, pass, pass) - if err != nil { + if err != nil && err != keystore.ErrAccountAlreadyExists { log.Crit("Failed to import faucet signer account", "err", err) } - ks.Unlock(acc, pass) - + if err := ks.Unlock(acc, pass); err != nil { + log.Crit("Failed to unlock faucet signer account", "err", err) + } // Assemble and start the faucet light service faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes(), bep2eInfos) if err != nil { @@ -239,7 +241,7 @@ type faucet struct { nonce uint64 // Current pending nonce of the faucet price *big.Int // Current gas price to issue funds with - conns []*websocket.Conn // Currently live websocket connections + conns []*wsConn // Currently live websocket connections timeouts map[string]time.Time // History of users and their funding timeouts reqs []*request // Currently pending funding requests update chan struct{} // Channel to signal request updates @@ -250,7 +252,14 @@ type faucet struct { bep2eAbi abi.ABI } -func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte, bep2eInfos map[string]bep2eInfo) (*faucet, error) { +// wsConn wraps a websocket connection with a write mutex as the underlying +// websocket library does not synchronize access to the stream. +type wsConn struct { + conn *websocket.Conn + wlock sync.Mutex +} + +func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte, bep2eInfos map[string]bep2eInfo) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ Name: "geth", @@ -274,22 +283,20 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u return nil, err } // Assemble the Ethereum light client protocol - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - cfg := eth.DefaultConfig - cfg.SyncMode = downloader.LightSync - cfg.NetworkId = network - cfg.Genesis = genesis - return les.New(ctx, &cfg) - }); err != nil { - return nil, err + cfg := ethconfig.Defaults + cfg.SyncMode = downloader.LightSync + cfg.NetworkId = network + cfg.Genesis = genesis + utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash()) + + lesBackend, err := les.New(stack, &cfg) + if err != nil { + return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } + // Assemble the ethstats monitoring and reporting service' if stats != "" { - if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - var serv *les.LightEthereum - ctx.Service(&serv) - return ethstats.New(stats, nil, serv) - }); err != nil { + if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil { return nil, err } } @@ -306,7 +313,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u // Attach to the client and retrieve and interesting metadatas api, err := stack.Attach() if err != nil { - stack.Stop() + stack.Close() return nil, err } client := ethclient.NewClient(api) @@ -359,13 +366,14 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { defer conn.Close() f.lock.Lock() - f.conns = append(f.conns, conn) + wsconn := &wsConn{conn: conn} + f.conns = append(f.conns, wsconn) f.lock.Unlock() defer func() { f.lock.Lock() for i, c := range f.conns { - if c == conn { + if c.conn == conn { f.conns = append(f.conns[:i], f.conns[i+1:]...) break } @@ -393,7 +401,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if head == nil || balance == nil { // Report the faucet offline until initial stats are ready //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(conn, errors.New("Faucet offline")); err != nil { + if err = sendError(wsconn, errors.New("Faucet offline")); err != nil { log.Warn("Failed to send faucet error to client", "err", err) return } @@ -404,7 +412,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { f.lock.RLock() reqs := f.reqs f.lock.RUnlock() - if err = send(conn, map[string]interface{}{ + if err = send(wsconn, map[string]interface{}{ "funds": new(big.Int).Div(balance, ether), "funded": nonce, "peers": f.stack.Server().PeerCount(), @@ -413,7 +421,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { log.Warn("Failed to send initial stats to client", "err", err) return } - if err = send(conn, head, 3*time.Second); err != nil { + if err = send(wsconn, head, 3*time.Second); err != nil { log.Warn("Failed to send initial header to client", "err", err) return } @@ -429,9 +437,8 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if err = conn.ReadJSON(&msg); err != nil { return } - if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") && - !strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { - if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil { + if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { + if err = sendError(wsconn, errors.New("URL doesn't link to supported services")); err != nil { log.Warn("Failed to send URL error to client", "err", err) return } @@ -439,7 +446,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } if msg.Tier >= uint(*tiersFlag) { //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil { + if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil { log.Warn("Failed to send tier error to client", "err", err) return } @@ -455,7 +462,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form) if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha post error to client", "err", err) return } @@ -468,7 +475,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { err = json.NewDecoder(res.Body).Decode(&result) res.Body.Close() if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send captcha decode error to client", "err", err) return } @@ -477,7 +484,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { if !result.Success { log.Warn("Captcha verification failed", "err", string(result.Errors)) //lint:ignore ST1005 it's funny and the robot won't mind - if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil { + if err = sendError(wsconn, errors.New("Beep-bop, you're a robot!")); err != nil { log.Warn("Failed to send captcha failure to client", "err", err) return } @@ -486,36 +493,26 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { } // Retrieve the Ethereum address to fund, the requesting user and a profile picture var ( + id string username string avatar string address common.Address ) switch { - case strings.HasPrefix(msg.URL, "https://gist.github.com/"): - if err = sendError(conn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil { - log.Warn("Failed to send GitHub deprecation to client", "err", err) - return - } - continue - case strings.HasPrefix(msg.URL, "https://plus.google.com/"): - //lint:ignore ST1005 Google is a company name and should be capitalized. - if err = sendError(conn, errors.New("Google+ authentication discontinued as the service was sunset")); err != nil { - log.Warn("Failed to send Google+ deprecation to client", "err", err) - return - } - continue case strings.HasPrefix(msg.URL, "https://twitter.com/"): - username, avatar, address, err = authTwitter(msg.URL) + id, username, avatar, address, err = authTwitter(msg.URL, *twitterTokenV1Flag, *twitterTokenFlag) case strings.HasPrefix(msg.URL, "https://www.facebook.com/"): username, avatar, address, err = authFacebook(msg.URL) + id = username case *noauthFlag: username, avatar, address, err = authNoAuth(msg.URL) + id = username default: //lint:ignore ST1005 This error is to be displayed in the browser err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues") } if err != nil { - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send prefix error to client", "err", err) return } @@ -529,8 +526,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { fund bool timeout time.Time ) - - if timeout = f.timeouts[username]; time.Now().After(timeout) { + if timeout = f.timeouts[id]; time.Now().After(timeout) { var tx *types.Transaction if msg.Symbol == "BNB" { // User wasn't funded recently, create the funding transaction @@ -557,7 +553,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) if err != nil { f.lock.Unlock() - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction creation error to client", "err", err) return } @@ -566,7 +562,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { // Submit the transaction and mark as funded if successful if err := f.client.SendTransaction(context.Background(), signed); err != nil { f.lock.Unlock() - if err = sendError(conn, err); err != nil { + if err = sendError(wsconn, err); err != nil { log.Warn("Failed to send transaction transmission error to client", "err", err) return } @@ -581,20 +577,20 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute grace := timeout / 288 // 24h timeout => 5m grace - f.timeouts[username] = time.Now().Add(timeout - grace) + f.timeouts[id] = time.Now().Add(timeout - grace) fund = true } f.lock.Unlock() // Send an error if too frequent funding, othewise a success if !fund { - if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple + if err = sendError(wsconn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple log.Warn("Failed to send funding error to client", "err", err) return } continue } - if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { + if err = sendSuccess(wsconn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { log.Warn("Failed to send funding success to client", "err", err) return } @@ -691,12 +687,12 @@ func (f *faucet) loop() { "requests": f.reqs, }, time.Second); err != nil { log.Warn("Failed to send stats to client", "err", err) - conn.Close() + conn.conn.Close() continue } if err := send(conn, head, time.Second); err != nil { log.Warn("Failed to send header to client", "err", err) - conn.Close() + conn.conn.Close() } } f.lock.RUnlock() @@ -718,7 +714,7 @@ func (f *faucet) loop() { for _, conn := range f.conns { if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil { log.Warn("Failed to send requests to client", "err", err) - conn.Close() + conn.conn.Close() } } f.lock.RUnlock() @@ -728,41 +724,63 @@ func (f *faucet) loop() { // sends transmits a data packet to the remote end of the websocket, but also // setting a write deadline to prevent waiting forever on the node. -func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error { +func send(conn *wsConn, value interface{}, timeout time.Duration) error { if timeout == 0 { timeout = 60 * time.Second } - conn.SetWriteDeadline(time.Now().Add(timeout)) - return conn.WriteJSON(value) + conn.wlock.Lock() + defer conn.wlock.Unlock() + conn.conn.SetWriteDeadline(time.Now().Add(timeout)) + return conn.conn.WriteJSON(value) } // sendError transmits an error to the remote end of the websocket, also setting // the write deadline to 1 second to prevent waiting forever. -func sendError(conn *websocket.Conn, err error) error { +func sendError(conn *wsConn, err error) error { return send(conn, map[string]string{"error": err.Error()}, time.Second) } // sendSuccess transmits a success message to the remote end of the websocket, also // setting the write deadline to 1 second to prevent waiting forever. -func sendSuccess(conn *websocket.Conn, msg string) error { +func sendSuccess(conn *wsConn, msg string) error { return send(conn, map[string]string{"success": msg}, time.Second) } // authTwitter tries to authenticate a faucet request using Twitter posts, returning -// the username, avatar URL and Ethereum address to fund on success. -func authTwitter(url string) (string, string, common.Address, error) { +// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. +func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense parts := strings.Split(url, "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Twitter status URL") - } - // Twitter's API isn't really friendly with direct links. Still, we don't - // want to do ask read permissions from users, so just load the public posts and - // scrape it for the Ethereum address and profile URL. + return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") + } + // Strip any query parameters from the tweet id and ensure it's numeric + tweetID := strings.Split(parts[len(parts)-1], "?")[0] + if !regexp.MustCompile("^[0-9]+$").MatchString(tweetID) { + return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") + } + // Twitter's API isn't really friendly with direct links. + // It is restricted to 300 queries / 15 minute with an app api key. + // Anything more will require read only authorization from the users and that we want to avoid. + + // If Twitter bearer token is provided, use the API, selecting the version + // the user would prefer (currently there's a limit of 1 v2 app / developer + // but unlimited v1.1 apps). + switch { + case tokenV1 != "": + return authTwitterWithTokenV1(tweetID, tokenV1) + case tokenV2 != "": + return authTwitterWithTokenV2(tweetID, tokenV2) + } + // Twiter API token isn't provided so we just load the public posts + // and scrape it for the Ethereum address and profile URL. We need to load + // the mobile page though since the main page loads tweet contents via JS. + url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1) + res, err := http.Get(url) if err != nil { - return "", "", common.Address{}, err + return "", "", "", common.Address{}, err } defer res.Body.Close() @@ -770,31 +788,115 @@ func authTwitter(url string) (string, string, common.Address, error) { parts = strings.Split(res.Request.URL.String(), "/") if len(parts) < 4 || parts[len(parts)-2] != "status" { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Twitter status URL") + return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") } username := parts[len(parts)-3] body, err := ioutil.ReadAll(res.Body) if err != nil { - return "", "", common.Address{}, err + return "", "", "", common.Address{}, err } address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) if address == (common.Address{}) { //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("No Binance Smart Chain address found to fund") + return "", "", "", common.Address{}, errors.New("No Binance Smart Chain address found to fund") } var avatar string if parts = regexp.MustCompile("src=\"([^\"]+twimg.com/profile_images[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 { avatar = parts[1] } - return username + "@twitter", avatar, address, nil + return username + "@twitter", username, avatar, address, nil +} + +// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1 +// API, returning the user id, username, avatar URL and Ethereum address to fund on +// success. +func authTwitterWithTokenV1(tweetID string, token string) (string, string, string, common.Address, error) { + // Query the tweet details from Twitter + url := fmt.Sprintf("https://api.twitter.com/1.1/statuses/show.json?id=%s", tweetID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "", "", common.Address{}, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", common.Address{}, err + } + defer res.Body.Close() + + var result struct { + Text string `json:"text"` + User struct { + ID string `json:"id_str"` + Username string `json:"screen_name"` + Avatar string `json:"profile_image_url"` + } `json:"user"` + } + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return "", "", "", common.Address{}, err + } + address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Text)) + if address == (common.Address{}) { + //lint:ignore ST1005 This error is to be displayed in the browser + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") + } + return result.User.ID + "@twitter", result.User.Username, result.User.Avatar, address, nil +} + +// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2 +// API, returning the user id, username, avatar URL and Ethereum address to fund on +// success. +func authTwitterWithTokenV2(tweetID string, token string) (string, string, string, common.Address, error) { + // Query the tweet details from Twitter + url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", tweetID) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "", "", common.Address{}, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", "", common.Address{}, err + } + defer res.Body.Close() + + var result struct { + Data struct { + AuthorID string `json:"author_id"` + Text string `json:"text"` + } `json:"data"` + Includes struct { + Users []struct { + ID string `json:"id"` + Username string `json:"username"` + Avatar string `json:"profile_image_url"` + } `json:"users"` + } `json:"includes"` + } + + err = json.NewDecoder(res.Body).Decode(&result) + if err != nil { + return "", "", "", common.Address{}, err + } + + address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Data.Text)) + if address == (common.Address{}) { + //lint:ignore ST1005 This error is to be displayed in the browser + return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") + } + return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].Avatar, address, nil } // authFacebook tries to authenticate a faucet request using Facebook posts, // returning the username, avatar URL and Ethereum address to fund on success. func authFacebook(url string) (string, string, common.Address, error) { // Ensure the user specified a meaningful URL, no fancy nonsense - parts := strings.Split(url, "/") + parts := strings.Split(strings.Split(url, "?")[0], "/") + if parts[len(parts)-1] == "" { + parts = parts[0 : len(parts)-1] + } if len(parts) < 4 || parts[len(parts)-2] != "posts" { //lint:ignore ST1005 This error is to be displayed in the browser return "", "", common.Address{}, errors.New("Invalid Facebook post URL") @@ -804,7 +906,13 @@ func authFacebook(url string) (string, string, common.Address, error) { // Facebook's Graph API isn't really friendly with direct links. Still, we don't // want to do ask read permissions from users, so just load the public posts and // scrape it for the Ethereum address and profile URL. - res, err := http.Get(url) + // + // Facebook recently changed their desktop webpage to use AJAX for loading post + // content, so switch over to the mobile site for now. Will probably end up having + // to use the API eventually. + crawl := strings.Replace(url, "www.facebook.com", "m.facebook.com", 1) + + res, err := http.Get(crawl) if err != nil { return "", "", common.Address{}, err } @@ -837,3 +945,19 @@ func authNoAuth(url string) (string, string, common.Address, error) { } return address.Hex() + "@noauth", "", address, nil } + +// getGenesis returns a genesis based on input args +func getGenesis(genesisFlag *string, goerliFlag bool, rinkebyFlag bool) (*core.Genesis, error) { + switch { + case genesisFlag != nil: + var genesis core.Genesis + err := common.LoadJSON(*genesisFlag, &genesis) + return &genesis, err + case goerliFlag: + return core.DefaultGoerliGenesisBlock(), nil + case rinkebyFlag: + return core.DefaultRinkebyGenesisBlock(), nil + default: + return nil, fmt.Errorf("no genesis flag provided") + } +} diff --git a/cmd/faucet/faucet.html b/cmd/faucet/faucet.html index 36748ae9e1..849145a60d 100644 --- a/cmd/faucet/faucet.html +++ b/cmd/faucet/faucet.html @@ -1,220 +1,220 @@ - - - - + + + + - {{.Network}}: Faucet + {{.Network}}: Faucet - - + + - - - - + + + + - - + + - -
-
-
-
-

{{.Network}} Faucet

-
-
-
-
-
- - + +
+
+
+
+

{{.Network}} Faucet

+
+
+
+
+
+ + - + -
{{if .Recaptcha}} -
{{end}} +
{{if .Recaptcha}} +
{{end}} +
+
+
+
+
+
+
-
-
-
-
-
-
-
- -
+
+
+
- {{if .Recaptcha}} - {{end}} - + // Establish a websocket connection to the API server + reconnect(); +{{if .Recaptcha}} +{{end}} + diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go new file mode 100644 index 0000000000..4f3e47084e --- /dev/null +++ b/cmd/faucet/faucet_test.go @@ -0,0 +1,43 @@ +// Copyright 2021 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 . + +package main + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestFacebook(t *testing.T) { + for _, tt := range []struct { + url string + want common.Address + }{ + { + "https://www.facebook.com/fooz.gazonk/posts/2837228539847129", + common.HexToAddress("0xDeadDeaDDeaDbEefbEeFbEEfBeeFBeefBeeFbEEF"), + }, + } { + _, _, gotAddress, err := authFacebook(tt.url) + if err != nil { + t.Fatal(err) + } + if gotAddress != tt.want { + t.Fatalf("address wrong, have %v want %v", gotAddress, tt.want) + } + } +} diff --git a/cmd/faucet/website.go b/cmd/faucet/website.go index 32a94a9780..854f4404d9 100644 --- a/cmd/faucet/website.go +++ b/cmd/faucet/website.go @@ -1,6 +1,6 @@ // Code generated by go-bindata. DO NOT EDIT. // sources: -// faucet.html (9.293kB) +// faucet.html (8.959kB) package main @@ -20,7 +20,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } var buf bytes.Buffer @@ -28,7 +28,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } if clErr != nil { return nil, err @@ -69,7 +69,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _faucetHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x5a\x5b\x93\xdb\x36\xb2\x7e\x96\x7f\x45\x87\xc7\x89\xa8\x33\x43\x52\xf2\xe4\x56\x12\xa9\x53\x1e\x27\x27\x35\x5b\xb5\x4e\x6a\x9d\xd4\xee\x56\x92\x07\x88\x68\x89\xf0\x80\x00\x03\x80\xd2\x28\x2a\xfd\xf7\x2d\x80\x17\x91\x94\x66\x62\xc7\xde\xda\x5a\x3f\x8c\x71\x69\x74\x7f\xe8\x6e\xa0\xbb\x41\xc5\x9f\x7c\xf3\xfd\xab\x1f\xff\xf9\xc3\xb7\x90\x99\x9c\x2f\x9f\xc5\xf6\x3f\xe0\x44\x6c\x12\x0f\x85\xb7\x7c\x36\x8a\x33\x24\x74\xf9\x6c\x34\x8a\x73\x34\x04\xd2\x8c\x28\x8d\x26\xf1\x4a\xb3\x0e\xbe\xf6\x4e\x13\x99\x31\x45\x80\xbf\x95\x6c\x9b\x78\xff\x08\x7e\x7a\x19\xbc\x92\x79\x41\x0c\x5b\x71\xf4\x20\x95\xc2\xa0\x30\x89\x77\xf7\x6d\x82\x74\x83\x9d\x75\x82\xe4\x98\x78\x5b\x86\xbb\x42\x2a\xd3\x21\xdd\x31\x6a\xb2\x84\xe2\x96\xa5\x18\xb8\xce\x35\x30\xc1\x0c\x23\x3c\xd0\x29\xe1\x98\xcc\xbc\xe5\x33\xcb\xc7\x30\xc3\x71\x79\x38\x84\xaf\xd1\xec\xa4\xba\x3f\x1e\xe7\xf0\xff\xa4\x4c\xd1\xc4\x51\x35\xe7\xc8\x38\x13\xf7\x90\x29\x5c\x27\x9e\x05\xab\xe7\x51\x94\x52\xf1\x56\x87\x29\x97\x25\x5d\x73\xa2\x30\x4c\x65\x1e\x91\xb7\xe4\x21\xe2\x6c\xa5\x23\xb3\x63\xc6\xa0\x0a\x56\x52\x1a\x6d\x14\x29\xa2\x9b\xf0\x26\xfc\x2a\x4a\xb5\x8e\xda\xb1\x30\x67\x22\x4c\xb5\xf6\x40\x21\x4f\x3c\x6d\xf6\x1c\x75\x86\x68\x3c\x88\x96\x7f\x4e\xee\x5a\x0a\x13\x90\x1d\x6a\x99\x63\xf4\x79\xf8\x55\x38\x75\x22\xbb\xc3\x4f\x4b\xb5\x62\x75\xaa\x58\x61\x40\xab\xf4\x9d\xe5\xbe\xfd\xad\x44\xb5\x8f\x6e\xc2\x59\x38\xab\x3b\x4e\xce\x5b\xed\x2d\xe3\xa8\x62\xb8\xfc\x20\xde\x81\x90\x66\x1f\xbd\x08\x3f\x0f\x67\x51\x41\xd2\x7b\xb2\x41\xda\x48\xb2\x53\x61\x33\xf8\xd1\xe4\x3e\x66\xc3\xb7\x43\x13\x7e\x0c\x61\xb9\xcc\x51\x98\xf0\xad\x8e\x5e\x84\xb3\xaf\xc3\x69\x33\x70\xce\xdf\x09\xb0\x46\xb3\xa2\x46\xe1\x16\x95\x61\x29\xe1\x41\x8a\xc2\xa0\x82\x83\x1d\x1d\xe5\x4c\x04\x19\xb2\x4d\x66\xe6\x30\x9b\x4e\x3f\x5d\x5c\x1a\xdd\x66\xd5\x30\x65\xba\xe0\x64\x3f\x87\x35\xc7\x87\x6a\x88\x70\xb6\x11\x01\x33\x98\xeb\x39\x54\x9c\xdd\xc4\xd1\xc9\x2c\x94\xdc\x28\xd4\xba\x16\x56\x48\xcd\x0c\x93\x62\x6e\x3d\x8a\x18\xb6\xc5\x4b\xb4\xba\x20\xe2\x6c\x01\x59\x69\xc9\x4b\x83\x03\x20\x2b\x2e\xd3\xfb\x6a\xcc\x9d\xdf\xee\x26\x52\xc9\xa5\x9a\xc3\x2e\x63\xf5\x32\x70\x82\xa0\x50\x58\xb3\x87\x82\x50\xca\xc4\x66\x0e\x5f\x16\xf5\x7e\x20\x27\x6a\xc3\xc4\x1c\xa6\xa7\x25\x71\xd4\xa8\x31\x8e\xaa\xab\xea\xd9\x28\x5e\x49\xba\x77\x36\xa4\x6c\x0b\x29\x27\x5a\x27\xde\x40\xc5\xee\x0a\xea\x11\xd8\x9b\x87\x30\xd1\x4c\xf5\xe6\x94\xdc\x79\xe0\x04\x25\x5e\x05\x22\x58\x49\x63\x64\x3e\x87\x99\x85\x57\x2f\x19\xf0\xe3\x01\xdf\x04\xb3\x17\xcd\xe4\x28\xce\x66\x0d\x13\x83\x0f\x26\x70\xf6\x69\x2d\xe3\x2d\x63\xd6\xac\x5d\x13\x58\x93\x60\x45\x4c\xe6\x01\x51\x8c\x04\x19\xa3\x14\x45\xe2\x19\x55\xa2\xf5\x23\xb6\x84\xee\x85\xd7\xde\x77\xd9\xac\x41\x12\x51\xb6\xad\x37\xd2\x69\x0e\xf6\xf4\x38\xec\xaf\xa1\x6e\xc8\xf5\x5a\xa3\x09\x3a\xbb\xe8\x10\x33\x51\x94\x26\xd8\x28\x59\x16\xed\xfc\x28\x76\xa3\xc0\x68\xe2\x95\x8a\x7b\xf5\x15\xef\x9a\x66\x5f\xd4\x9b\xf7\xda\xad\x4a\x95\x07\x56\xf7\x4a\x72\x0f\x0a\x4e\x52\xcc\x24\xa7\xa8\x12\xef\xce\xf1\xd9\xcb\x52\xc1\x2d\x13\x44\xa4\x08\x6f\x72\xa2\x0c\xbc\xca\x08\x13\x40\x28\xb5\x3e\x19\x86\x61\x47\xb6\x73\xd0\x73\x74\xc1\xca\x88\x13\xd5\x28\x5e\x95\xc6\xc8\x96\x70\x65\x04\xac\x8c\x08\x28\xae\x49\xc9\x0d\x50\x25\x0b\x2a\x77\x22\x30\x72\xb3\xb1\x01\xac\xc2\x5d\x2d\xf2\x80\x12\x43\xea\xa9\xc4\x6b\x68\x1b\x43\x11\x5d\xc8\xa2\x2c\x6a\x53\x55\x83\xf8\x50\x10\x41\x91\x5a\xc3\x72\x8d\xde\xf2\x3b\xb6\x45\xc8\x11\x6e\x5f\xdf\x8e\x86\x56\x4f\x89\x42\x13\x74\x59\x9e\xd9\x3e\x8e\x2a\x28\xd5\x86\xa0\xfe\x17\x97\xbc\xe1\xd4\x6e\x20\x47\x51\x42\xaf\x17\x28\x7b\x75\x78\xcb\xc3\x41\x11\xb1\x41\x78\xce\xe8\xc3\x35\x3c\x27\xb9\x2c\x85\x81\x79\x02\xe1\x4b\xd7\xd4\xc7\x63\x8f\x3b\x40\xcc\xd9\x32\x26\x4f\x79\x30\x48\x91\x72\x96\xde\x27\x9e\x61\xa8\x92\xc3\xc1\x32\x3f\x1e\x17\x7a\x9f\xaf\x24\x4f\xc6\xb7\xaf\x6f\xc7\x0b\x38\x1c\xd8\x1a\x9e\x87\x7f\xc3\x94\x14\x26\xcd\xc8\xf1\xb8\x51\x4d\x3b\xc4\x07\x4c\x4b\x83\xfe\xe4\x70\x40\xae\xf1\x78\xd4\xe5\x2a\x67\xc6\x6f\x78\xd9\x71\x41\x8f\x47\xbb\x81\x1a\xf4\xf1\x18\x47\x64\x19\x47\x9c\x2d\xeb\xc9\xbe\x5a\xa2\x92\x9f\xfc\x23\xb2\x0e\xf2\xdf\xe6\x2e\x3f\xe0\x66\xb3\x07\x23\xef\x51\xe8\xff\x90\xbb\x40\xeb\x2f\x95\x31\xaf\xe1\xf9\x0a\x8b\x17\x78\x27\xd6\xd2\x79\xcd\x6d\xd3\x6b\x1c\xc7\x29\xee\x3d\x5c\xa6\x76\x92\xc3\xa1\x96\x70\x3c\xfe\x39\x57\x71\x40\xfc\x0e\x9b\x9e\xcb\xb4\xa0\x6b\x37\x7f\x63\xd4\xf1\x08\x1d\xea\x3f\xeb\x4b\xd5\x2d\xeb\x00\x77\xf1\x5e\xb8\x34\x37\x41\xbb\x87\xda\x39\x34\x33\x78\x8f\xfb\xc4\x3b\x1c\xba\x6b\xeb\xd9\x94\x70\xbe\x22\x4e\x45\x6e\x83\xed\xa2\xdf\xd1\x3a\xed\x96\x69\x97\x65\x2f\x1b\x04\x27\xd8\xef\x18\x05\x06\x91\xcd\xc8\x62\x0e\x37\x2f\x3a\x61\xed\x52\x80\xf8\x72\x10\x20\x6e\x2e\x12\x17\x44\x20\x07\xf7\x37\xd0\x39\xe1\x4d\xbb\x3e\x3a\x9d\x8b\x7b\xb8\x28\xb0\x41\xbc\x85\xd6\x26\x03\xd3\x05\xc8\x2d\xaa\x35\x97\xbb\x39\x90\xd2\xc8\x05\xe4\xe4\xa1\x4d\x88\x6e\xa6\xd3\x2e\x6e\x5b\x1d\x90\x15\x47\x17\x8c\x14\xfe\x56\xa2\x36\xba\x0d\x3d\xd5\x94\xfb\x6b\x23\x10\x45\xa1\x91\x0e\xb4\x61\x25\x5a\xd5\x3a\xaa\x8e\xe9\x5b\x65\x5e\xc4\xbe\x96\xb2\xcd\x31\xba\x30\x6a\xd6\x9d\x74\xc8\x5b\xc6\x46\x9d\xe8\x46\xb1\xa1\xef\x95\x23\x28\x5b\x03\x3c\x96\x22\x54\xd7\x9b\xdd\x7b\x81\xa8\xaa\x04\xd4\xba\x2c\xb8\x6e\x1c\x19\xfa\x01\x92\xad\x13\xae\x88\xc6\x77\x11\xef\x52\xc1\x93\x78\xd7\xfd\x50\xf9\x19\x12\x65\x56\x48\xcc\xbb\x00\x58\x97\x82\x76\xf6\x7f\xfb\xfa\xf6\x43\xc5\x97\x82\x6d\x51\x69\x66\xf6\xef\x2a\x1f\xe9\x09\x40\xd5\xef\x43\x88\x23\xa3\x9e\xf6\xb4\x6e\xe7\xe2\xd1\x6e\x5b\x75\xe3\x54\xc7\xb8\xe9\x28\x82\xef\xb8\x5c\x11\x0e\x5b\x0b\x79\xc5\x51\x83\x91\x60\xf3\x2d\x30\x19\x42\x5a\x2a\x85\xc2\x80\x36\xc4\x94\x1a\xe4\xda\x8d\xae\x5d\x6e\x69\xd7\x6f\x89\x02\x62\x0c\xe6\x85\x81\xa4\xce\xc2\xed\x98\x46\xb5\xad\x6b\x0b\xdb\xb5\xc1\xbf\x3f\x5f\x5d\xed\x9e\xd7\x8e\x34\x67\x11\x12\xf8\xf9\xd7\xc5\xb3\x1a\xdc\x37\xb8\x66\x02\x81\x58\xf5\xa4\xb6\xb6\x00\x93\x11\x03\xa9\x42\x62\x50\x43\xca\xa5\x2e\x55\x85\xd9\x06\x29\xb0\xb8\x1b\x4e\x0d\x67\x3b\x51\x38\xf9\x0d\x13\x3f\x23\x3a\x9b\xd4\x65\x85\x42\x53\x2a\x71\x9a\x6b\xc6\x47\x6b\xa9\xc0\xb7\x0c\x58\x32\x5d\x00\x8b\x1b\xbe\x21\x47\xb1\x31\xd9\x02\xd8\xd5\x55\x4b\x3c\x62\x6b\xf0\x1b\x8a\x9f\xd9\xaf\xa1\x79\x08\xad\x14\x48\x12\xe8\x4a\x73\x02\x6b\x3e\xba\xe0\x2c\x45\x9f\x5d\xc3\x6c\xb2\x68\x66\x57\x0a\xc9\x7d\xd3\xab\x2f\xed\xea\x3f\xf7\xf7\xb8\xe8\x6b\xc6\x99\xa3\xa7\x9b\x2a\x24\x68\x20\xb0\x61\xda\x40\xa9\xb8\xd5\x8e\xa5\xab\x8c\xd2\x9a\xc0\xd1\x75\xb5\x72\x16\xaa\xea\x46\x1d\x40\x9a\x2d\x54\x6c\x42\x8d\x82\xfa\x7f\x79\xf3\xfd\xeb\x50\x1b\xc5\xc4\x86\xad\xf7\xfe\xa1\x54\x7c\x0e\xcf\x7d\xef\x7f\x6c\x72\x3f\xf9\x79\xfa\x6b\xb8\x25\xbc\xc4\xeb\xda\xe0\x73\x68\x72\x05\xeb\x11\x73\xf7\xf7\x4c\xea\x35\xd4\xcd\x39\xf4\x01\x1c\x27\x93\xc5\xe5\x70\xda\xc9\x01\x14\x6a\x34\xbe\x25\x6c\xa3\xde\x50\x67\x04\x72\x34\x99\xa4\x56\x2f\x0a\x53\x29\x04\xa6\x06\xca\x42\x8a\x5a\x45\xc0\xa5\xd6\x27\xc7\x6c\x28\x92\x73\x27\xa9\xe9\x13\x10\xb8\x83\xbf\xe3\xea\x8d\x4c\xef\xd1\xf8\xbe\xbf\x63\x82\xca\x5d\xc8\x65\x4a\xec\x02\x5b\x2f\x1b\x99\x4a\x0e\x49\x92\x40\xfd\x7a\xe0\x4d\xe0\xff\xc0\xdb\x69\x3d\x8f\x22\x0f\xe6\xb6\x69\x5b\x13\xb8\x82\xe1\xf2\x4c\x6a\x03\x57\xe0\x45\xd5\xd1\xb3\x91\x53\x99\x88\x14\xcc\x9b\x54\x67\xa5\xb1\x8a\x14\x39\x6a\x4d\x36\xd8\x45\x8b\x5b\x14\xa6\xf5\x40\xbb\xa9\x5c\x6f\x20\x01\x67\xbd\x82\x28\x8d\x15\x49\x68\x6f\xf0\xc6\x15\xad\x43\x3b\xb2\x24\x01\x51\x72\x7e\xf2\xe0\xea\xc4\x2c\x1a\xdf\xec\x91\x87\xee\x5e\x85\x4f\x92\x04\xec\x85\x66\xf5\x4d\x4f\x2b\xad\x67\x54\x17\xef\x24\xb4\x77\xea\x69\xc5\x64\xd1\x75\xf5\x1e\x37\xa4\x7f\xc4\x0e\xe9\x90\x1f\xd2\x47\x18\xba\x38\xf7\x14\xbf\x2a\x2e\x76\xd8\xb9\x81\x47\xb8\x89\x32\x5f\xa1\x7a\x8a\x5d\x15\xe7\x6a\x76\x4e\xd5\x77\xc2\x74\xd6\x5e\xc3\xec\xcb\xc9\x23\xdc\x51\x29\xf9\x28\x73\x21\xcd\xde\x3f\x70\xb2\x97\xa5\x99\xc3\xd8\xc8\xe2\x95\x0b\x4c\xe3\x6b\xb0\xb2\xe6\xd0\x72\xb8\x76\xd5\xc7\x1c\xc6\xae\x67\xe7\x59\x8e\x6e\xd5\x17\xd3\xe9\xf4\x1a\x9a\xa7\x9c\x5b\x62\x4f\xa4\x2a\xf1\xf8\x08\x1e\x5d\xa6\x29\xea\x47\xb5\xf7\x4e\x88\x6a\x1e\x2d\xa6\xba\xff\x01\xa8\xda\xc0\xd1\x83\x05\x9f\x7d\x06\x67\xb3\x7d\x37\x8e\x22\xf8\x2b\x51\xf7\xe0\x72\x50\x85\x5b\x26\x4b\x7d\x0a\x43\x39\xd3\x9a\x89\x0d\x10\x0d\x54\x0a\xac\xd7\xbc\x5f\x4c\x38\xc3\x58\x93\xc1\x12\xa6\x43\x80\xf6\xae\xec\xc4\x8c\x0b\xa1\xa4\xc3\xb7\x1f\x25\x46\xc7\xae\xbc\xde\x4a\x96\x23\x7c\x92\x80\xe7\x75\x17\x9f\x51\x58\x82\x96\xd9\x48\xa3\xf9\xb1\xb2\x85\x5f\x87\xce\x4b\x81\x6d\x72\x6d\x53\xeb\xe9\xe4\x0c\xc4\xf1\xa4\xde\x97\x45\x81\x82\x02\x11\x7b\x77\x3f\xb6\xba\x65\xc2\x48\x90\xa5\xbd\x67\x53\xc2\x6d\xd9\xc0\xd1\xdd\x54\xf5\x52\xab\xe0\x54\xe6\xb9\x14\x90\x40\x30\x5b\x5c\x08\xb1\x1d\x4d\x76\xb6\x36\x34\xcf\x05\xdd\x0f\x4d\xd4\xd7\xd9\x80\x38\x98\xf5\x8c\xd2\xb3\xd7\x65\xc3\x8c\x5a\xdc\xec\xa4\xd1\x81\xb9\x4e\xf6\x1a\xea\xac\x83\xbf\xe2\x73\x35\x7b\xc7\x6d\xb4\xd3\x45\xa9\x33\x7f\x00\x74\xb2\x38\xb7\xcd\x9d\x41\x45\x0c\xba\xda\xc9\xd9\x02\x85\x61\x0a\xcf\x4c\x02\x44\xd8\x94\x2a\x50\x28\x28\xaa\x26\xdf\xb0\xa5\x57\x55\x27\xf5\x4c\xe6\xbe\xd4\xf4\xdc\xe9\x3d\x0f\x8c\xcb\xd7\xa4\x40\x5b\x5a\x0f\x0e\x81\x73\xd4\x9e\xa7\x5a\x62\xe4\xa4\xd0\x48\x21\x81\xea\x65\xdd\x9f\x84\xa5\x60\x0f\xfe\x24\xa8\xfb\x43\x1e\xcd\x7c\x1d\x36\x9d\xc5\x2a\xd8\x57\x09\x78\xb1\x51\x36\x3b\x1f\x7b\x70\x75\xe9\x08\xda\x10\x3c\x5e\x9e\x10\x74\x97\x02\xc4\x86\x2e\x5d\xe5\x57\x55\x0d\xbf\x78\xb6\x46\xdf\x28\x59\x0a\x3a\xb7\x79\x98\x7f\xc6\x96\x6c\x89\x21\xca\x71\x9d\x2c\xe0\x44\xee\x4a\xf9\x39\xa4\xd6\x38\x0b\xa8\x8a\x43\x57\x7f\x43\x5b\xd6\xba\xde\x4a\x2a\x8a\x2a\x50\x84\xb2\x52\xcf\xe1\xf3\xe2\x61\xf1\x4b\x53\xf6\xbb\x52\xe2\x49\xa8\x85\xc2\xe5\x19\xa2\x34\x75\xcf\x7e\x57\xe0\xc5\x91\x25\xf8\x23\x36\xed\x66\xbb\x2f\xfa\x70\xa1\x60\x82\xf6\xbd\xbd\x1e\xcf\x19\xa5\x1c\x2d\xe0\x13\x7b\x7b\x18\xad\xfd\xbb\x47\xaa\x2f\x12\xea\x4a\xe9\xb4\xe6\x08\xc8\x35\x3e\xb1\xa0\x2d\xba\xc6\xd6\x01\x02\xbb\x65\xe6\x74\x5e\xd7\x6f\x6e\x58\x8d\x9d\x2e\xea\xef\x33\xb4\x54\x2e\xf1\xf2\x83\xda\xc1\xae\x61\xac\x6d\x22\x48\xf5\x78\x12\x66\x65\x4e\x04\xfb\x1d\x7d\x1b\x97\x26\x95\xae\x5c\x15\xe7\x9d\x5f\xc9\x67\x60\x4e\x4f\x03\xe3\x26\xc6\x8d\x6b\x25\x8e\x1b\xeb\x5a\x43\x76\xbe\x6d\x8c\xdf\x53\x43\x97\xa5\x04\x2b\xa2\xa0\xdb\x09\x9a\xe0\x0b\x4a\x5a\xe9\xcd\xdc\x8a\xa8\x71\x55\xc1\xba\xe4\x5d\xc8\x5d\x32\xbe\x99\xb6\x20\x2b\x43\x3b\x3b\x8f\x6b\x5f\x3b\x33\x86\x45\xd9\x1c\xcd\x25\xdc\x4c\x3f\x06\x5a\x4a\xc4\x06\x87\x3b\x30\x8a\x15\x48\x81\xa4\x86\x6d\xf1\xdf\xb0\x91\x8f\xa0\xe4\xf7\x86\x68\xfd\xb0\x51\x9e\x73\xd3\x1e\x5e\x3b\xdb\xea\xf6\x7f\xed\x79\x83\xc8\x69\xf8\x0a\xbc\x8b\x1b\x79\xd4\x13\x07\x84\x83\xa3\xfd\xf8\xb9\x77\xcf\x12\xde\x30\xa6\xd8\x6c\xb7\x7d\x50\x9b\x84\x99\xc9\xb9\xef\xc5\xc6\x7d\x79\xb3\x98\x5b\x0e\x8e\x41\x35\xdc\x4f\xe9\x8e\xfd\x42\xc6\x16\xf7\x38\x28\xba\xa0\x93\x9c\xb4\x85\x59\x93\x89\xc0\xf1\xf4\x81\x32\x8a\xe0\x8d\x21\xca\x00\x81\x9f\xee\xa0\x2c\x28\x31\x36\x7a\x49\xb0\xf1\xd1\x45\xb1\xf6\x0b\xe6\x8a\x28\x0d\x6b\xa9\x76\x44\x51\x28\x85\x61\xdc\xce\xef\x81\x28\x6c\x53\x3f\x8d\xe6\xce\xde\x62\x5b\xc2\xfd\xb3\x22\xf0\xb9\x3f\x0e\xbb\x26\x1f\x4f\x42\x24\x69\x76\x4e\xe8\x22\x56\x2b\x37\x81\xd7\xae\x04\xf0\x9f\xfb\x26\x63\x7a\x12\x12\x63\x94\x3f\xee\x39\xc3\x78\x62\xed\x3a\xeb\x94\x64\xed\xf2\xb8\x77\xac\x9e\xe2\x71\x4a\xa6\xdb\x44\xa0\x21\x4f\xb5\xf6\x2b\xbf\x1a\x5f\x77\x78\xf7\xdd\x6a\xfc\xe9\xb8\x35\xd4\xe9\x78\x9f\xf6\x91\x5c\x44\xd2\x63\x3d\xb6\xa7\x6c\x7c\x26\x9e\x50\xfa\xca\x9e\x1f\xdf\xbb\x70\xd2\x87\xde\x31\x69\x95\x5d\xdd\xd7\x4f\x6a\x99\x09\x8a\x0f\x8f\xa9\x98\xd1\xf1\x24\xd4\xe5\xaa\x7a\xb8\xf0\xbf\x68\x0b\xb0\x86\xcc\x39\xef\x30\x14\x9c\x25\x14\x56\x44\x3f\xa9\x08\x06\x49\xc8\x13\x51\xa3\x16\x59\xed\xea\x78\x6d\x15\x3e\x9d\xb4\xef\x5e\xdf\x6a\x9b\x5c\x31\x9d\x01\x81\x1d\xae\xb4\x7b\x56\x80\xda\xdf\xdd\x53\x4f\xf5\xa4\xf3\xf2\x87\xbb\xce\xb3\x4e\x7b\x22\x7c\xc7\xbd\xfd\x71\xc1\xa5\x47\x93\x8b\xbf\x66\xd8\xed\x76\xe1\x46\xca\x0d\xaf\x7e\xc7\xd0\xbe\xaa\x44\xa4\x60\xe1\x5b\xed\x01\xd1\x7b\x91\x02\xc5\x35\xaa\x65\x87\x7d\xfd\xd4\x12\x47\xd5\x77\xf6\x38\xaa\x7e\x3c\xf4\xaf\x00\x00\x00\xff\xff\xd9\xd8\xd1\x5e\x4d\x24\x00\x00") +var _faucetHtml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x5a\x5b\x8f\xe3\x36\xb2\x7e\x76\xff\x8a\x8a\xce\x24\x96\x4f\xb7\x24\x7b\x3a\x37\xd8\x92\x0f\xa6\x27\x39\xc1\x2c\x76\x27\x41\x26\xc1\xee\x22\xc9\x03\x2d\x95\x2d\x4e\x53\xa4\x42\x96\xec\x76\x0c\xff\xf7\x05\xa9\x8b\xe5\x4b\xf7\xce\x25\xc0\x62\xe7\xc1\x2d\x92\xc5\xaa\x8f\x55\xc5\xba\x48\x13\x7f\xf2\xcd\xf7\x2f\x7f\xfa\xe7\x0f\xdf\x42\x4e\x85\x98\x5f\xc5\xf6\x0f\x08\x26\x57\x89\x87\xd2\xb3\x13\xc8\xb2\xf9\xd5\x20\x2e\x90\x18\xa4\x39\xd3\x06\x29\xf1\x2a\x5a\x06\x5f\x7b\xdd\x7c\x4e\x54\x06\xf8\x7b\xc5\xd7\x89\xf7\x8f\xe0\xe7\x17\xc1\x4b\x55\x94\x8c\xf8\x42\xa0\x07\xa9\x92\x84\x92\x12\xef\xd5\xb7\x09\x66\x2b\x3c\x6c\x93\xac\xc0\xc4\x5b\x73\xdc\x94\x4a\x53\x8f\x72\xc3\x33\xca\x93\x0c\xd7\x3c\xc5\xc0\x0d\x6e\x80\x4b\x4e\x9c\x89\xc0\xa4\x4c\x60\x32\xf1\xe6\x57\x57\x83\x98\x38\x09\x9c\xef\x76\xe1\x6b\xa4\x8d\xd2\xf7\xfb\xfd\x14\xfe\x9f\x55\x29\x52\x1c\xd5\x6b\x96\x4a\x70\x79\x0f\xb9\xc6\x65\xe2\x59\xa4\x66\x1a\x45\x69\x26\xdf\x9a\x30\x15\xaa\xca\x96\x82\x69\x0c\x53\x55\x44\xec\x2d\x7b\x88\x04\x5f\x98\x88\x36\x9c\x08\x75\xb0\x50\x8a\x0c\x69\x56\x46\xb7\xe1\x6d\xf8\x55\x94\x1a\x13\x75\x73\x61\xc1\x65\x98\x1a\xe3\x81\x46\x91\x78\x86\xb6\x02\x4d\x8e\x48\x1e\x44\xf3\x0f\x12\xbb\x54\x92\x02\xb6\x41\xa3\x0a\x8c\x3e\x0f\xbf\x0a\xc7\x4e\x62\x7f\xfa\x69\xa1\x57\x83\xd8\xa4\x9a\x97\x04\x46\xa7\xef\x2c\xf6\xed\xef\x15\xea\x6d\x74\x1b\x4e\xc2\x49\x33\x70\x62\xde\x1a\x6f\x1e\x47\x35\xc3\xf9\xc7\xb0\x0e\xa4\xa2\x6d\xf4\x3c\xfc\x3c\x9c\x44\x25\x4b\xef\xd9\x0a\xb3\x56\x90\x5d\x0a\xdb\xc9\x3f\x4b\xec\x63\xf6\x7b\x7b\x6a\xbe\x3f\x41\x56\xa1\x0a\x94\x14\xbe\x35\xd1\xf3\x70\xf2\x75\x38\x6e\x27\xce\xd9\x5b\xfe\xd6\x5e\xf3\xab\xc1\x20\x5c\xa3\x26\x9e\x32\x11\xa4\x28\x09\x35\xec\xae\x06\x83\x41\xc1\x65\x90\x23\x5f\xe5\x34\x85\xc9\x78\xfc\xe9\xec\xc2\xe4\x3a\x77\xb3\x19\x37\xa5\x60\xdb\x29\x2c\x05\x3e\xb8\x19\x26\xf8\x4a\x06\x9c\xb0\x30\x53\xa8\xb9\xda\xf9\xbd\x95\x56\x6a\xb5\xd2\x68\x4c\x2d\xa6\x54\x86\x13\x57\x72\x6a\x9d\x88\x11\x5f\xe3\x39\xa1\x29\x99\x3c\xa5\x66\x0b\xa3\x44\x45\x78\x0c\x60\x21\x54\x7a\xef\xa6\xdc\x55\xed\x21\x4f\x95\x50\x7a\x0a\x9b\x9c\x53\x27\xa1\xd4\xd8\xb0\x65\x59\xc6\xe5\x6a\x0a\x5f\x96\x35\xfe\x82\xe9\x15\x97\x53\x18\x37\xa4\x71\xd4\x68\x2b\x8e\xea\x28\x74\x15\x2f\x54\xb6\x9d\x5f\xc5\x19\x5f\x43\x2a\x98\x31\x89\x77\xa2\x46\x17\x5c\x7a\xcb\x36\xa4\x30\x2e\xeb\x85\xa3\x15\xad\x36\x1e\x38\x01\x89\x57\x4b\x0e\x16\x8a\x48\x15\x53\x98\x58\x44\x6e\xc3\x09\x2f\x11\x88\x55\x30\x79\x5e\x2f\x0d\xe2\x7c\xd2\x32\x20\x7c\xa0\xc0\xe9\xbf\xd3\xbc\x37\x8f\x79\xbb\x73\xc9\x60\xc9\x82\x05\xa3\xdc\x03\xa6\x39\x0b\x72\x9e\x65\x28\x13\x8f\x74\x85\xd6\x43\xf8\x1c\xfa\x41\xac\x8b\x61\xf9\xa4\x46\x11\x65\x7c\xed\x0e\xd0\x3d\x9c\x9c\xe4\x31\xb0\x5f\x43\xf3\xa0\x96\x4b\x83\x14\x74\xd8\x7b\xa4\x5c\x96\x15\x05\x2b\xad\xaa\xb2\x59\x1d\xc4\x6e\x0e\x78\x96\x78\x95\x16\x5e\x13\xa9\xdd\x23\x6d\xcb\xe6\xc0\x5e\x77\x3c\xa5\x8b\xc0\x6a\x5a\x2b\xe1\x41\x29\x58\x8a\xb9\x12\x19\xea\xc4\x7b\xa3\x52\xce\x04\xc8\xfa\x64\xf0\xf3\x8f\x7f\x85\xc6\x24\x5c\xae\x60\xab\x2a\x0d\x77\x5c\x32\x99\x22\xbc\x29\x98\x26\x78\x99\x33\x2e\x81\x65\x99\x75\xc2\x30\x0c\x3b\x44\xce\x1f\xcf\x11\x07\x0b\x92\x2d\x8d\x25\x5b\x54\x44\xaa\x23\x5c\x90\x84\x05\xc9\x20\xc3\x25\xab\x04\x41\xa6\x55\x99\xa9\x8d\x0c\x48\xad\x56\x36\x39\xd5\xa7\xa9\x37\x79\x90\x31\x62\xcd\x52\xe2\xb5\xb4\xad\xc9\x98\x29\x55\x59\x95\x8d\xd1\xea\x49\x7c\x28\x99\xcc\x30\xb3\x26\x16\x06\xbd\xf9\x77\x7c\x8d\x50\x20\xdc\xbd\xbe\x1b\x9c\xda\x3f\x65\x1a\x29\xe8\xb3\x3c\xf3\x82\x38\xaa\xa1\xd4\x07\x82\xe6\x5f\x5c\x89\x96\x53\x77\x80\x02\x65\x05\x47\xa3\x40\xdb\x10\xe1\xcd\x77\x3b\xcd\xe4\x0a\xe1\x19\xcf\x1e\x6e\xe0\x19\x2b\x54\x25\x09\xa6\x09\x84\x2f\xdc\xa3\xd9\xef\x8f\xb8\x03\xc4\x82\xcf\x63\xf6\x94\x2f\x83\x92\xa9\xe0\xe9\x7d\xe2\x11\x47\x9d\xec\x76\x96\xf9\x7e\x3f\x33\xdb\x62\xa1\x44\x32\xbc\x7b\x7d\x37\x9c\xc1\x6e\xc7\x97\xf0\x2c\xfc\x11\x53\x56\x52\x9a\xb3\xfd\x7e\xa5\xdb\xe7\x10\x1f\x30\xad\x08\xfd\xd1\x6e\x87\xc2\xe0\x7e\x6f\xaa\x45\xc1\xc9\x6f\x79\xd9\x79\x99\xed\xf7\xf6\x00\x0d\xe8\xfd\x3e\x8e\xd8\x3c\x8e\x04\x9f\x37\x8b\xc7\x6a\x89\x2a\xd1\x59\x3e\x8e\xac\x83\xfc\x77\x39\xcb\x0f\xb8\x5a\x6d\x81\xd4\x3d\x4a\xf3\x1f\x72\x16\xe8\xbc\xa5\x36\xe5\x0d\x3c\x5b\x60\xf9\x1c\x5f\xc9\xa5\x72\x3e\x73\xd7\x8e\x5a\xb7\x71\x8a\x7b\x0f\x87\x69\x5c\x64\xb7\x6b\x24\xec\xf7\x1f\xe6\x28\x0e\x88\xdf\x63\x73\xe4\x30\x1d\xe8\xc6\xc9\xdf\x90\xde\xef\xa1\x47\xfd\x61\x9e\x54\xc7\x5b\x07\xb7\x8f\xf6\x2c\x84\xae\x82\x0e\x7f\xe3\x18\x86\x13\xde\xe3\x36\xf1\x76\xbb\xfe\xce\x66\x35\x65\x42\x2c\x98\x53\x8f\x3b\x5c\xb7\xe9\x0f\xb4\x0e\xbb\xe6\xc6\xd5\xce\xf3\x56\x7e\x07\xf9\xdf\x67\x82\x93\x9c\x46\xaa\x9c\xc2\xed\xf3\xa7\x12\xda\x97\x27\x39\xe2\xf6\x42\x8e\x28\x99\x44\x01\xee\x37\x30\x05\x13\xed\x73\x73\x57\xba\x28\x7d\xba\x25\xb0\x19\xbb\xc3\xd4\xa5\xfc\xf1\x0c\xd4\x1a\xf5\x52\xa8\xcd\x14\x58\x45\x6a\x06\x05\x7b\xe8\xaa\x9c\xdb\xf1\xb8\x03\x6c\xb9\x12\x5b\x08\x74\xd9\x48\xe3\xef\x15\x1a\x32\x5d\xee\xa9\x97\xdc\xaf\x4d\x41\x19\x4a\x83\xd9\x89\x12\xac\x3c\xab\x4b\x47\xd5\x22\x6d\xf5\x77\x11\xf5\x52\xa9\xa6\x98\xe8\x03\x68\x98\xf6\xaa\x1c\x6f\x1e\x93\x3e\x78\x0e\x65\xef\x55\x10\x68\x5b\xc6\x3f\x56\x0f\xd4\x31\xcc\x9e\xb9\x44\xd4\x75\x1d\x69\xfd\x12\xdc\x30\x8e\x28\xfb\x60\xb9\xd6\xd7\x16\xcc\xe0\xbb\x08\x77\xc5\xdd\x41\xb8\x1b\x7e\x9c\xf4\x1c\x99\xa6\x05\x32\x7a\x17\xf1\xcb\x4a\x66\xbd\xb3\xdf\xbd\xbe\xfb\x38\xe1\x95\xe4\x6b\xd4\x86\xd3\xf6\x5d\xa5\x63\x76\x10\x5f\x8f\xfb\x00\xe2\x88\xf4\xe3\x9e\x75\x78\x3c\xbb\xb7\xcd\xdf\xe6\xcf\x55\xdc\xb5\x20\x51\x04\xdf\x09\xb5\x60\x02\xd6\x16\xe0\x42\xa0\x01\x52\x60\x0b\x2a\xa0\x1c\x21\xad\xb4\x46\x49\x60\x88\x51\x65\x40\x2d\xdd\xec\xd2\x15\x8c\x57\x83\x35\xd3\xc0\x88\xb0\x28\x09\x12\x57\x49\xdb\x19\x83\x7a\xed\x9a\x01\x3b\xb0\x19\xbc\xbf\x56\xc7\x67\xcf\x6b\xc6\xed\x0d\x83\x04\x7e\xf9\x6d\x76\xe5\x00\x7d\x83\x4b\x2e\x11\x98\x55\x40\x6a\x5b\x01\xa0\x9c\x11\xa4\x1a\x19\xa1\x81\x54\x28\x53\xe9\x1a\xa7\xcd\x32\x60\xb1\xb6\x7c\x6a\xae\x76\xba\x74\x72\x5b\x16\x7e\xce\x4c\x3e\x72\xad\x80\x46\xaa\xb4\x3c\xac\xd4\xb3\x83\xa5\xd2\xe0\xdb\xcd\x3c\x19\xcf\x80\xc7\x2d\xc7\x50\xa0\x5c\x51\x3e\x03\x7e\x7d\xdd\x90\x0e\xf8\x12\xfc\x76\xfd\x17\xfe\x5b\x48\x0f\xa1\xe5\x0f\x49\x02\x07\x39\x03\x2b\xaa\xe1\x61\x4a\xc1\x53\xf4\xf9\x0d\x4c\x46\xb3\x7a\x6d\xa1\x91\xd5\x7d\x8c\x6b\x54\xdc\xcf\xfe\x6a\xb0\x9f\xf5\x75\xe0\x94\x7d\xa4\x85\x3a\x82\x1b\x60\xb0\xe2\x86\xa0\xd2\xc2\xea\xc1\xd2\xd5\x6a\x6f\xd4\xec\xa8\xfa\xe7\x3f\xcb\x2a\xcd\x43\x13\xed\x6b\xc8\x35\x8b\xd0\xa0\xcc\xfc\xbf\xbc\xf9\xfe\x75\x68\x48\x73\xb9\xe2\xcb\xad\xbf\xab\xb4\x98\xc2\x33\xdf\xfb\x1f\x5b\x97\x8f\x7e\x19\xff\x16\xae\x99\xa8\xf0\xa6\x31\xe9\x14\xda\x94\x6e\x2d\x3e\x75\xbf\x67\x32\x6f\xa0\x79\x9c\xc2\xb1\xf8\xfd\x68\x34\xbb\x94\xf7\x7a\x89\x5a\xa3\x41\xf2\x2d\x59\x93\x9e\x8e\x35\xc5\xa0\x40\xca\x55\x66\xb5\xa1\x31\x55\x52\x62\x4a\x50\x95\x4a\x36\x8a\x01\xa1\x8c\x69\x9d\xae\x5d\x4f\x4e\xdd\xa0\xa1\x4d\x40\xe2\x06\xfe\x8e\x8b\x37\x2a\xbd\x47\xf2\x7d\x7f\xc3\x65\xa6\x36\xa1\x50\x29\xb3\xe4\xb6\x73\x25\x95\x2a\x01\x49\x92\x40\xd3\xbc\x7b\x23\xf8\x3f\xf0\x36\xc6\xb6\xf1\x1e\x4c\xed\xa3\x7d\x1a\xc1\x35\x9c\x6e\xcf\x95\x21\xb8\x06\x2f\xaa\xaf\x92\xcd\x72\x9a\x22\x56\x72\x6f\x64\x6f\x41\x6b\x09\x25\x0b\x34\x86\xad\xb0\x8f\x14\xd7\x28\xa9\xf1\x31\x7b\x9c\xc2\xac\x20\x01\x67\xaf\x92\x69\x83\x35\x41\x68\xe3\x6e\xed\x6c\xd6\x5d\x1d\x51\x92\x80\xac\x84\x68\xfd\xb3\xbe\x09\xb3\xda\xfb\x7a\x84\xa1\x8b\x83\xf0\x49\x92\x80\x0d\x41\x56\xbf\x59\xbb\xc7\x7a\x40\x1d\x26\x47\xa1\x8d\x81\x07\xfa\xd1\xac\x75\xe3\x23\x3e\x98\x3d\xcd\x08\xb3\x53\x4e\x98\x5d\x60\xe5\xf2\xd0\xe3\x9c\xea\xac\xd5\x63\xe4\x26\x2e\xf0\x91\x55\xb1\x40\xfd\x38\xa3\x3a\x03\x35\x8c\x9c\x3a\x5f\x49\xea\xed\xbc\x81\xc9\x97\xa3\x0b\x7c\x51\x6b\xf5\x08\x5b\xa9\x68\xeb\xef\x04\xdb\xaa\x8a\xa6\x30\x24\x55\xbe\x74\x09\x63\x78\x03\x56\xca\x14\xba\xfd\x37\xae\xe8\x9f\xc2\xd0\x8d\xec\x3a\x2f\xd0\xed\xfa\x62\x3c\x1e\xdf\x40\xfb\xba\xe4\x8e\xd9\x1b\xa6\x2b\xdc\x5f\x40\x62\xaa\x34\x45\xf3\x88\xae\xde\x09\x4b\xc3\xa1\x43\xd3\x8c\x3f\x10\x4f\x17\xe2\x8f\x00\xc1\x67\x9f\xc1\xd9\x6a\xdf\x39\xa3\x08\xfe\xc6\xf4\x3d\xb8\xea\x4f\xe3\x9a\xab\xca\x1c\xd2\x45\xc1\x8d\xb1\x3d\x3d\x33\x90\x29\x89\x6e\xc7\xfb\x44\xf0\x33\x74\x0d\x11\xcc\x61\x7c\x0a\xcd\xc6\xba\x5e\x84\xbf\x10\xf8\x3b\xae\xfd\xa8\x3e\xd8\x1f\x24\x1d\xed\xe1\x05\xc2\x27\x09\x78\xde\x61\xdb\xd9\xba\x5d\x6e\xd8\x0c\x0c\xd2\x4f\xb5\xde\xfd\x26\xb1\x5d\x4a\x3e\xa3\x1b\x5b\xc8\x8e\x47\x47\xc2\xf7\xad\x2a\x5f\x94\x25\xca\x0c\x98\xdc\xba\xc8\xd6\xe9\x91\x4b\x52\xa0\x2a\x1b\x1d\x53\x26\x6c\x69\x2e\xd0\x45\x19\xb7\xd1\x2a\x33\x55\x45\xa1\x24\x24\x10\x4c\x66\x67\xc9\xaf\xa7\xb5\xee\x30\xa7\x66\xb8\xa0\xe5\x63\x53\x1c\x6b\xe8\x84\x34\x98\x1c\x29\xff\xc8\x2e\x97\x0c\x30\xe8\xf0\xf2\x56\x7f\x47\x46\x69\xad\xd2\xd7\x4f\x0f\x71\xbd\xfb\x7a\xf2\x4e\xc0\xbb\xc5\xb2\x32\xb9\x7f\x02\x6d\x34\x3b\xb6\xc0\x2b\x42\xcd\x08\x5d\x27\xe2\x34\x8e\x92\xb8\xc6\x33\xc5\x03\x93\xb6\x9c\x09\x34\xca\x0c\x75\x5b\x01\xd8\x46\xa6\xee\x3b\x7a\x86\x71\xdf\x2f\x7a\xae\xd2\x3b\xc7\x99\x16\x67\xc0\x61\x6e\xeb\x30\xe0\x41\xd0\x9d\xc0\x15\x4b\x4a\xa2\x6d\x4b\x4f\x3c\xdb\x79\x61\xcf\x0d\x2d\x29\x0a\x56\x1a\xcc\x20\x81\xfa\xad\xb3\x3f\x0a\x2b\xc9\x1f\xfc\x51\xd0\x8c\x4f\x39\xb4\xeb\x2e\xa7\x39\xe3\xd4\x98\xaf\x13\xf0\x62\xd2\xb6\xec\x1d\x7a\x70\x7d\xe9\x4e\xd9\xec\x38\x9c\xb7\xd2\xfb\x1b\x01\x62\xca\xe6\xae\x8d\xaa\x8b\xf1\x5f\x3d\xdb\xdd\xae\xb4\xaa\x64\x36\xb5\x25\x91\x7f\xc6\x94\xad\x19\x31\xed\x78\x8e\x66\x70\x20\x77\x4d\xf0\x14\x52\x6b\x95\x19\xd4\xbd\x96\xeb\x5f\xa1\xeb\x0e\xdd\x68\xa1\x74\x86\x3a\xd0\x2c\xe3\x95\x99\xc2\xe7\xe5\xc3\xec\xd7\xb6\x61\x76\x15\xfa\x13\x40\x4b\x8d\xf3\x33\x3c\x69\xea\x5e\x94\x5d\x83\x17\x47\x96\xe0\x69\x26\xdd\x41\xfb\xef\xbc\xe1\x42\x0f\x02\xdd\x1b\xea\x66\xbe\xe0\x59\x26\xd0\x82\x6d\x99\xdb\xdb\x66\x6d\x7e\xb8\x33\xc7\xe2\xa0\x69\x3c\x5a\xfa\x3d\xa0\x30\xf8\x28\x71\xd7\xbf\x0c\xad\xc1\x03\x7b\x50\xee\xf4\xdc\xb4\x42\x6e\x5a\x0f\x9d\x06\x9a\x6f\x15\x59\xa5\x5d\x15\xe4\x07\x8d\x43\xdd\xc0\xd0\xd8\x9a\x2c\x33\xc3\x51\x98\x57\x05\x93\xfc\x0f\xf4\x6d\x42\x19\xd5\x1a\x72\x0d\x91\x77\x1c\x57\xcf\x80\x1c\xfa\xea\x61\x9b\x98\x86\x8d\xe2\x86\xad\x35\xad\xe1\xe0\xd0\xa7\x0f\xdf\x43\x2b\x97\x25\x04\x0b\xa6\xa1\x3f\x08\xda\x6c\x09\x5a\x59\xc9\xed\xda\x82\xe9\x61\xdd\x04\xba\xba\x59\xaa\x4d\x32\xbc\x1d\x77\x00\x6b\xc3\x3a\xbb\x0e\x1b\xbf\x3a\x31\x80\x45\xd8\x5e\xc0\x39\xdc\x8e\x3f\x1e\x69\xc6\xe4\x0a\x4f\xd1\x93\xe6\x25\x66\xc0\x52\xe2\x6b\xfc\xd3\x0f\xf1\xd1\xca\x7d\x6f\x78\xd6\xef\x5a\xb5\x39\xb7\x3c\xc2\x6a\x57\x3b\xad\xfe\xaf\xbd\x57\x10\x39\xdd\x5e\x83\x77\xe1\x10\x8f\x78\xde\x11\xd1\xc9\xe5\x7d\xec\x5e\xbb\x5e\xde\xeb\x27\x09\x5b\x7e\x76\x6f\x9c\x46\x61\x4e\x85\xf0\xbd\x98\xdc\x57\x28\x8b\xb3\xdb\xed\x36\xd7\xd3\x87\x7a\x6b\xdf\xef\x1a\x6c\x8f\x8c\x27\xdd\x0d\xf4\xea\x88\xae\x03\x6a\x8b\x06\xb0\xdd\xd4\xde\x35\x54\x6f\x88\x69\x02\x06\x3f\xbf\x82\xaa\xcc\x18\xd9\x24\xa4\xc0\xa6\x38\x97\x8c\xba\x4f\x76\x0b\xa6\x0d\x2c\x95\xde\x30\x9d\x41\x25\x89\x0b\xbb\xbe\x05\xa6\xb1\xa9\xc9\x0c\xd2\x2b\x1b\x95\xd6\x4c\xf8\x27\x7d\xd6\x33\x7f\x18\xf6\x0d\x3b\x1c\x85\xc8\xd2\xfc\x94\xcc\x65\x9d\x4e\x62\x02\xaf\x5d\x0d\xee\x3f\xf3\x29\xe7\x66\x14\x32\x22\xed\x0f\x8f\x0c\x3e\x1c\x59\xdb\x4d\xba\xbe\xa7\xdb\x1c\xf7\xae\xcc\x53\xfb\x0f\x55\x6d\x93\xc1\x5b\xe2\xd4\x18\xbf\xf6\x9a\xe1\x4d\x8f\xef\xb1\xd3\x0c\x3f\x1d\x36\x26\x39\x5c\xda\x03\xfe\xe4\x02\x86\x23\xb6\x43\x7b\x77\x86\x27\x82\x59\x96\xbd\xb4\xf7\xc2\xf7\x2e\xdc\xde\xbe\x07\x8c\x1a\xc5\xd6\x11\xf7\x09\x8d\x72\x99\xe1\xc3\x63\xea\xe4\xd9\x70\x14\x9a\x6a\x51\xf7\xfe\xfe\x17\x4d\xb7\xd3\x12\x39\xb7\x3c\x0d\xe4\x67\xe9\xdf\x0a\x38\x2e\x01\x82\x93\x92\xe1\x89\x98\xef\x04\xda\xd3\xec\x6f\xac\x72\xc7\xa3\xe6\xc5\xd0\xb7\xc6\x56\x40\xdc\xe4\xc0\x60\x83\x0b\xe3\x7a\x73\x68\x3c\xd9\xbd\x21\xa9\xdf\x84\xbc\xf8\xe1\x55\xf7\x36\xa4\xf3\x74\x5b\x84\x74\x1f\xc7\xcf\xdf\x35\x5c\xfc\x16\xbf\xd9\x6c\xc2\x95\x52\x2b\x51\x7f\x85\xef\x5e\x46\xd8\x46\x3d\x7c\x6b\x3c\x60\x66\x2b\x53\xc8\x70\x89\x7a\xde\x63\x5e\xbf\xa1\x88\xa3\xe6\x03\x72\x54\xff\x77\x97\x7f\x05\x00\x00\xff\xff\x1d\xd7\x27\x7a\xff\x22\x00\x00") func faucetHtmlBytes() ([]byte, error) { return bindataRead( @@ -85,7 +85,7 @@ func faucetHtml() (*asset, error) { } info := bindataFileInfo{name: "faucet.html", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc0, 0x42, 0xb4, 0x67, 0xdf, 0x31, 0x8e, 0x4f, 0xcc, 0x2c, 0xe0, 0x3, 0x84, 0x98, 0xd4, 0x9a, 0x57, 0x86, 0x2f, 0x32, 0xf3, 0x4, 0x88, 0x0, 0xc2, 0x31, 0x7d, 0xea, 0x46, 0x4a, 0x1b, 0x3a}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x39, 0x4d, 0x5d, 0x6d, 0x7f, 0xf1, 0x26, 0x19, 0x7, 0xee, 0xd2, 0xb3, 0x2, 0xdf, 0xd2, 0x8c, 0x36, 0x87, 0x8e, 0x17, 0x50, 0xb6, 0x8f, 0xb0, 0xb5, 0xce, 0x73, 0xcc, 0x24, 0xd3, 0xd6, 0x8e}} return a, nil } @@ -183,6 +183,9 @@ var _bindata = map[string]func() (*asset, error){ "faucet.html": faucetHtml, } +// AssetDebug is true if the assets were built with the debug flag enabled. +const AssetDebug = false + // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index bf0e583187..6d45c88763 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1" @@ -212,7 +211,7 @@ func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []str } for trials := 0; trials < 3; trials++ { prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) - password := getPassPhrase(prompt, false, i, passwords) + password := utils.GetPassPhraseWithList(prompt, false, i, passwords) err = ks.Unlock(account, password) if err == nil { log.Info("Unlocked account", "address", account.Address.Hex()) @@ -233,36 +232,6 @@ func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []str return accounts.Account{}, "" } -// getPassPhrase retrieves the password associated with an account, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { - // If a list of passwords was supplied, retrieve from them - if len(passwords) > 0 { - if i < len(passwords) { - return passwords[i] - } - return passwords[len(passwords)-1] - } - // Otherwise prompt the user for the password - if prompt != "" { - fmt.Println(prompt) - } - password, err := console.Stdin.PromptPassword("Password: ") - if err != nil { - utils.Fatalf("Failed to read password: %v", err) - } - if confirmation { - confirm, err := console.Stdin.PromptPassword("Repeat password: ") - if err != nil { - utils.Fatalf("Failed to read password confirmation: %v", err) - } - if password != confirm { - utils.Fatalf("Passwords do not match") - } - } - return password -} - func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account { fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) for _, a := range err.Matches { @@ -305,7 +274,7 @@ func accountCreate(ctx *cli.Context) error { utils.Fatalf("Failed to read configuration: %v", err) } - password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + password := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) account, err := keystore.StoreKey(keydir, password, scryptN, scryptP) @@ -333,7 +302,7 @@ func accountUpdate(ctx *cli.Context) error { for _, addr := range ctx.Args() { account, oldPassword := unlockAccount(ks, addr, 0, nil) - newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) + newPassword := utils.GetPassPhraseWithList("Please give a new password. Do not forget this password.", true, 0, nil) if err := ks.Update(account, oldPassword, newPassword); err != nil { utils.Fatalf("Could not update the account: %v", err) } @@ -352,7 +321,7 @@ func importWallet(ctx *cli.Context) error { } stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhraseWithList("", false, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) @@ -373,7 +342,7 @@ func accountImport(ctx *cli.Context) error { utils.Fatalf("Failed to load the private key: %v", err) } stack, _ := makeConfigNode(ctx) - passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhraseWithList("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) acct, err := ks.ImportECDSA(key, passphrase) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 3ee4278e5f..9455eeda36 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -89,18 +89,24 @@ Path of the secret key file: .*UTC--.+--[0-9a-f]{40} } func TestAccountImport(t *testing.T) { - tests := []struct{ key, output string }{ + tests := []struct{ name, key, output string }{ { + name: "correct account", key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", output: "Address: {fcad0b19bb29d4674531d6f115237e16afce377c}\n", }, { + name: "invalid character", key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1", output: "Fatal: Failed to load the private key: invalid character '1' at end of key file\n", }, } for _, test := range tests { - importAccountWithExpect(t, test.key, test.output) + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + importAccountWithExpect(t, test.key, test.output) + }) } } @@ -173,11 +179,8 @@ Fatal: could not decrypt key with given password } func TestUnlockFlag(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -197,10 +200,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") + defer geth.ExpectExit() geth.Expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -216,11 +218,9 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could // https://github.com/ethereum/go-ethereum/issues/1785 func TestUnlockFlagMultiIndex(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "0,2", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--unlock", "0,2", "js", "testdata/empty.js") + geth.Expect(` Unlocking account 0 | Attempt 1/3 !! Unsupported terminal, password will be echoed. @@ -243,11 +243,9 @@ Password: {{.InputLine "foobar"}} } func TestUnlockFlagPasswordFile(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/passwords.txt", "--unlock", "0,2", - "js", "testdata/empty.js") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", "testdata/passwords.txt", "--unlock", "0,2", "js", "testdata/empty.js") + geth.ExpectExit() wantMessages := []string{ @@ -263,10 +261,9 @@ func TestUnlockFlagPasswordFile(t *testing.T) { } func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { - datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, - "--datadir", datadir, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", + "testdata/wrong-passwords.txt", "--unlock", "0,2") defer geth.ExpectExit() geth.Expect(` Fatal: Failed to unlock account 0 (could not decrypt key with given password) @@ -275,9 +272,9 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) func TestUnlockFlagAmbiguous(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "js", "testdata/empty.js") defer geth.ExpectExit() @@ -313,9 +310,10 @@ In order to avoid this warning, you need to remove the following duplicate key f func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") - geth := runGeth(t, - "--keystore", store, "--nat", "none", "--nodiscover", "--maxpeers", "0", "--port", "0", - "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), + "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", + store, "--unlock", "f466859ead1932d743d622cb74fc058882e8648a") + defer geth.ExpectExit() // Helper for the expect template, returns absolute keystore path. diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index ccfe2609fe..1152bfdfdd 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -22,7 +22,6 @@ import ( "net" "os" "path" - "path/filepath" "runtime" "strconv" "strings" @@ -31,18 +30,14 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/trie" "gopkg.in/urfave/cli.v1" ) @@ -86,7 +81,11 @@ It expects the genesis file as argument.`, Usage: "Dumps genesis block JSON configuration to stdout", ArgsUsage: "", Flags: []cli.Flag{ - utils.DataDirFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -107,12 +106,15 @@ The dumpgenesis command dumps the genesis block configuration in JSON format to utils.CacheGCFlag, utils.MetricsEnabledFlag, utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, utils.MetricsEnableInfluxDBFlag, utils.MetricsInfluxDBEndpointFlag, utils.MetricsInfluxDBDatabaseFlag, utils.MetricsInfluxDBUsernameFlag, utils.MetricsInfluxDBPasswordFlag, utils.MetricsInfluxDBTagsFlag, + utils.TxLookupLimitFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -167,37 +169,6 @@ be gzipped.`, Category: "BLOCKCHAIN COMMANDS", Description: ` The export-preimages command export hash preimages to an RLP encoded stream`, - } - copydbCommand = cli.Command{ - Action: utils.MigrateFlags(copyDb), - Name: "copydb", - Usage: "Create a local chain from a target chaindata folder", - ArgsUsage: "", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.CacheFlag, - utils.SyncModeFlag, - utils.FakePoWFlag, - utils.RopstenFlag, - utils.RinkebyFlag, - utils.GoerliFlag, - utils.LegacyTestnetFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - Description: ` -The first argument must be the directory containing the blockchain to download from`, - } - removedbCommand = cli.Command{ - Action: utils.MigrateFlags(removeDB), - Name: "removedb", - Usage: "Remove blockchain and state databases", - ArgsUsage: " ", - Flags: []cli.Flag{ - utils.DataDirFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - Description: ` -Remove blockchain and state databases`, } dumpCommand = cli.Command{ Action: utils.MigrateFlags(dump), @@ -218,23 +189,6 @@ Remove blockchain and state databases`, The arguments are interpreted as block numbers or hashes. Use "ethereum dump 0" to dump the genesis block.`, } - inspectCommand = cli.Command{ - Action: utils.MigrateFlags(inspect), - Name: "inspect", - Usage: "Inspect the storage size for each type of data in the database", - ArgsUsage: " ", - Flags: []cli.Flag{ - utils.DataDirFlag, - utils.AncientFlag, - utils.CacheFlag, - utils.RopstenFlag, - utils.RinkebyFlag, - utils.GoerliFlag, - utils.LegacyTestnetFlag, - utils.SyncModeFlag, - }, - Category: "BLOCKCHAIN COMMANDS", - } ) // initGenesis will initialise the given JSON format genesis file and writes it as @@ -255,12 +209,12 @@ func initGenesis(ctx *cli.Context) error { if err := json.NewDecoder(file).Decode(genesis); err != nil { utils.Fatalf("invalid genesis file: %v", err) } - // Open an initialise both full and light databases - stack := makeFullNode(ctx) + // Open and initialise both full and light databases + stack, _ := makeConfigNode(ctx) defer stack.Close() for _, name := range []string{"chaindata", "lightchaindata"} { - chaindb, err := stack.OpenDatabase(name, 0, 0, "") + chaindb, err := stack.OpenDatabase(name, 0, 0, "", false) if err != nil { utils.Fatalf("Failed to open database: %v", err) } @@ -367,6 +321,7 @@ func initNetwork(ctx *cli.Context) error { } func dumpGenesis(ctx *cli.Context) error { + // TODO(rjl493456442) support loading from the custom datadir genesis := utils.MakeGenesis(ctx) if genesis == nil { genesis = core.DefaultGenesisBlock() @@ -385,7 +340,8 @@ func importChain(ctx *cli.Context) error { utils.SetupMetrics(ctx) // Start system runtime metrics collection go metrics.CollectProcessMetrics(3 * time.Second) - stack := makeFullNode(ctx) + + stack, _ := makeConfigNode(ctx) defer stack.Close() chain, db := utils.MakeChain(ctx, stack) @@ -409,13 +365,17 @@ func importChain(ctx *cli.Context) error { // Import the chain start := time.Now() + var importErr error + if len(ctx.Args()) == 1 { if err := utils.ImportChain(chain, ctx.Args().First()); err != nil { + importErr = err log.Error("Import error", "err", err) } } else { for _, arg := range ctx.Args() { if err := utils.ImportChain(chain, arg); err != nil { + importErr = err log.Error("Import error", "file", arg, "err", err) } } @@ -424,17 +384,7 @@ func importChain(ctx *cli.Context) error { fmt.Printf("Import done in %v.\n\n", time.Since(start)) // Output pre-compaction stats mostly to see the import trashing - stats, err := db.Stat("leveldb.stats") - if err != nil { - utils.Fatalf("Failed to read database stats: %v", err) - } - fmt.Println(stats) - - ioStats, err := db.Stat("leveldb.iostats") - if err != nil { - utils.Fatalf("Failed to read database iostats: %v", err) - } - fmt.Println(ioStats) + showLeveldbStats(db) // Print the memory statistics used by the importing mem := new(runtime.MemStats) @@ -452,30 +402,21 @@ func importChain(ctx *cli.Context) error { // Compact the entire database to more accurately measure disk io and print the stats start = time.Now() fmt.Println("Compacting entire database...") - if err = db.Compact(nil, nil); err != nil { + if err := db.Compact(nil, nil); err != nil { utils.Fatalf("Compaction failed: %v", err) } fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) - stats, err = db.Stat("leveldb.stats") - if err != nil { - utils.Fatalf("Failed to read database stats: %v", err) - } - fmt.Println(stats) - - ioStats, err = db.Stat("leveldb.iostats") - if err != nil { - utils.Fatalf("Failed to read database iostats: %v", err) - } - fmt.Println(ioStats) - return nil + showLeveldbStats(db) + return importErr } func exportChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + + stack, _ := makeConfigNode(ctx) defer stack.Close() chain, _ := utils.MakeChain(ctx, stack) @@ -495,6 +436,9 @@ func exportChain(ctx *cli.Context) error { if first < 0 || last < 0 { utils.Fatalf("Export error: block number must be greater than 0\n") } + if head := chain.CurrentFastBlock(); uint64(last) > head.NumberU64() { + utils.Fatalf("Export error: block number %d larger than head block %d\n", uint64(last), head.NumberU64()) + } err = utils.ExportAppendChain(chain, fp, uint64(first), uint64(last)) } @@ -510,10 +454,11 @@ func importPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + + stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack) + db := utils.MakeChainDatabase(ctx, stack, false) start := time.Now() if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { @@ -528,10 +473,11 @@ func exportPreimages(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } - stack := makeFullNode(ctx) + + stack, _ := makeConfigNode(ctx) defer stack.Close() - db := utils.MakeChainDatabase(ctx, stack) + db := utils.MakeChainDatabase(ctx, stack, true) start := time.Now() if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { @@ -541,141 +487,31 @@ func exportPreimages(ctx *cli.Context) error { return nil } -func copyDb(ctx *cli.Context) error { - // Ensure we have a source chain directory to copy - if len(ctx.Args()) < 1 { - utils.Fatalf("Source chaindata directory path argument missing") - } - if len(ctx.Args()) < 2 { - utils.Fatalf("Source ancient chain directory path argument missing") - } - // Initialize a new chain for the running node to sync into - stack := makeFullNode(ctx) - defer stack.Close() - - chain, chainDb := utils.MakeChain(ctx, stack) - syncMode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode) - - var syncBloom *trie.SyncBloom - if syncMode == downloader.FastSync { - syncBloom = trie.NewSyncBloom(uint64(ctx.GlobalInt(utils.CacheFlag.Name)/2), chainDb) - } - dl := downloader.New(0, chainDb, syncBloom, new(event.TypeMux), chain, nil, nil) - - // Create a source peer to satisfy downloader requests from - db, err := rawdb.NewLevelDBDatabaseWithFreezer(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name)/2, 256, ctx.Args().Get(1), "") - if err != nil { - return err - } - hc, err := core.NewHeaderChain(db, chain.Config(), chain.Engine(), func() bool { return false }) - if err != nil { - return err - } - peer := downloader.NewFakePeer("local", db, hc, dl) - if err = dl.RegisterPeer("local", 63, peer); err != nil { - return err - } - // Synchronise with the simulated peer - start := time.Now() - - currentHeader := hc.CurrentHeader() - if err = dl.Synchronise("local", currentHeader.Hash(), hc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()), syncMode); err != nil { - return err - } - for dl.Synchronising() { - time.Sleep(10 * time.Millisecond) - } - fmt.Printf("Database copy done in %v\n", time.Since(start)) - - // Compact the entire database to remove any sync overhead - start = time.Now() - fmt.Println("Compacting entire database...") - if err = db.Compact(nil, nil); err != nil { - utils.Fatalf("Compaction failed: %v", err) - } - fmt.Printf("Compaction done in %v.\n\n", time.Since(start)) - return nil -} - -func removeDB(ctx *cli.Context) error { - stack, config := makeConfigNode(ctx) - - // Remove the full node state database - path := stack.ResolvePath("chaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node state database") - } else { - log.Info("Full node state database missing", "path", path) - } - // Remove the full node ancient database - path = config.Eth.DatabaseFreezer - switch { - case path == "": - path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") - case !filepath.IsAbs(path): - path = config.Node.ResolvePath(path) - } - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node ancient database") - } else { - log.Info("Full node ancient database missing", "path", path) - } - // Remove the light node database - path = stack.ResolvePath("lightchaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "light node database") - } else { - log.Info("Light node database missing", "path", path) - } - return nil -} - -// confirmAndRemoveDB prompts the user for a last confirmation and removes the -// folder if accepted. -func confirmAndRemoveDB(database string, kind string) { - confirm, err := console.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) - switch { - case err != nil: - utils.Fatalf("%v", err) - case !confirm: - log.Info("Database deletion skipped", "path", database) - default: - start := time.Now() - filepath.Walk(database, func(path string, info os.FileInfo, err error) error { - // If we're at the top level folder, recurse into - if path == database { - return nil - } - // Delete all the files, but not subfolders - if !info.IsDir() { - os.Remove(path) - return nil - } - return filepath.SkipDir - }) - log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) - } -} - func dump(ctx *cli.Context) error { - stack := makeFullNode(ctx) + stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, chainDb := utils.MakeChain(ctx, stack) - defer chainDb.Close() + db := utils.MakeChainDatabase(ctx, stack, true) for _, arg := range ctx.Args() { - var block *types.Block + var header *types.Header if hashish(arg) { - block = chain.GetBlockByHash(common.HexToHash(arg)) + hash := common.HexToHash(arg) + number := rawdb.ReadHeaderNumber(db, hash) + if number != nil { + header = rawdb.ReadHeader(db, hash, *number) + } } else { - num, _ := strconv.Atoi(arg) - block = chain.GetBlockByNumber(uint64(num)) + number, _ := strconv.Atoi(arg) + hash := rawdb.ReadCanonicalHash(db, uint64(number)) + if hash != (common.Hash{}) { + header = rawdb.ReadHeader(db, hash, uint64(number)) + } } - if block == nil { + if header == nil { fmt.Println("{}") utils.Fatalf("block not found") } else { - state, err := state.New(block.Root(), state.NewDatabase(chainDb), nil) + state, err := state.New(header.Root, state.NewDatabase(db), nil) if err != nil { utils.Fatalf("could not create new state: %v", err) } @@ -696,16 +532,6 @@ func dump(ctx *cli.Context) error { return nil } -func inspect(ctx *cli.Context) error { - node, _ := makeConfigNode(ctx) - defer node.Close() - - _, chainDb := utils.MakeChain(ctx, node) - defer chainDb.Close() - - return rawdb.InspectDatabase(chainDb) -} - // hashish returns true for strings that look like hashes. func hashish(x string) bool { _, err := strconv.Atoi(x) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 71e8355348..c867877ee6 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -25,13 +25,15 @@ import ( "reflect" "unicode" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/catalyst" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" "github.com/naoina/toml" ) @@ -41,7 +43,7 @@ var ( Name: "dumpconfig", Usage: "Show configuration values", ArgsUsage: "", - Flags: append(append(nodeFlags, rpcFlags...), whisperFlags...), + Flags: append(nodeFlags, rpcFlags...), Category: "MISCELLANEOUS COMMANDS", Description: `The dumpconfig command shows configuration values.`, } @@ -74,10 +76,10 @@ type ethstatsConfig struct { } type gethConfig struct { - Eth eth.Config - Shh whisper.Config + Eth ethconfig.Config Node node.Config Ethstats ethstatsConfig + Metrics metrics.Config } func loadConfig(file string, cfg *gethConfig) error { @@ -105,12 +107,13 @@ func defaultNodeConfig() node.Config { return cfg } +// makeConfigNode loads geth configuration and creates a blank node instance. func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { // Load defaults. cfg := gethConfig{ - Eth: eth.DefaultConfig, - Shh: whisper.DefaultConfig, - Node: defaultNodeConfig(), + Eth: ethconfig.Defaults, + Node: defaultNodeConfig(), + Metrics: metrics.DefaultConfig, } // Load config file. @@ -130,55 +133,38 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) } - utils.SetShhConfig(ctx, stack, &cfg.Shh) + applyMetricConfig(ctx, &cfg) return stack, cfg } -// enableWhisper returns true in case one of the whisper flags is set. -func enableWhisper(ctx *cli.Context) bool { - for _, flag := range whisperFlags { - if ctx.GlobalIsSet(flag.GetName()) { - return true - } - } - return false -} - -func makeFullNode(ctx *cli.Context) *node.Node { +// makeFullNode loads geth configuration and creates the Ethereum backend. +func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { stack, cfg := makeConfigNode(ctx) - if ctx.GlobalIsSet(utils.OverrideIstanbulFlag.Name) { - cfg.Eth.OverrideIstanbul = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideIstanbulFlag.Name)) - } - if ctx.GlobalIsSet(utils.OverrideMuirGlacierFlag.Name) { - cfg.Eth.OverrideMuirGlacier = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideMuirGlacierFlag.Name)) + if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) { + cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name)) } - utils.RegisterEthService(stack, &cfg.Eth) + backend, eth := utils.RegisterEthService(stack, &cfg.Eth) - // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode - shhEnabled := enableWhisper(ctx) - shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name) - if shhEnabled || shhAutoEnabled { - if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) { - cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name)) + // Configure catalyst. + if ctx.GlobalBool(utils.CatalystFlag.Name) { + if eth == nil { + utils.Fatalf("Catalyst does not work in light client mode.") } - if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) { - cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name) - } - if ctx.GlobalIsSet(utils.WhisperRestrictConnectionBetweenLightClientsFlag.Name) { - cfg.Shh.RestrictConnectionBetweenLightClients = true + if err := catalyst.Register(stack, eth); err != nil { + utils.Fatalf("%v", err) } - utils.RegisterShhService(stack, &cfg.Shh) } + // Configure GraphQL if requested if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { - utils.RegisterGraphQLService(stack, cfg.Node.GraphQLEndpoint(), cfg.Node.GraphQLCors, cfg.Node.GraphQLVirtualHosts, cfg.Node.HTTPTimeouts) + utils.RegisterGraphQLService(stack, backend, cfg.Node) } // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { - utils.RegisterEthStatsService(stack, cfg.Ethstats.URL) + utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL) } - return stack + return stack, backend } // dumpConfig is the dumpconfig command. @@ -209,3 +195,36 @@ func dumpConfig(ctx *cli.Context) error { return nil } + +func applyMetricConfig(ctx *cli.Context, cfg *gethConfig) { + if ctx.GlobalIsSet(utils.MetricsEnabledFlag.Name) { + cfg.Metrics.Enabled = ctx.GlobalBool(utils.MetricsEnabledFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsEnabledExpensiveFlag.Name) { + cfg.Metrics.EnabledExpensive = ctx.GlobalBool(utils.MetricsEnabledExpensiveFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsHTTPFlag.Name) { + cfg.Metrics.HTTP = ctx.GlobalString(utils.MetricsHTTPFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsPortFlag.Name) { + cfg.Metrics.Port = ctx.GlobalInt(utils.MetricsPortFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsEnableInfluxDBFlag.Name) { + cfg.Metrics.EnableInfluxDB = ctx.GlobalBool(utils.MetricsEnableInfluxDBFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBEndpointFlag.Name) { + cfg.Metrics.InfluxDBEndpoint = ctx.GlobalString(utils.MetricsInfluxDBEndpointFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBDatabaseFlag.Name) { + cfg.Metrics.InfluxDBDatabase = ctx.GlobalString(utils.MetricsInfluxDBDatabaseFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBUsernameFlag.Name) { + cfg.Metrics.InfluxDBUsername = ctx.GlobalString(utils.MetricsInfluxDBUsernameFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBPasswordFlag.Name) { + cfg.Metrics.InfluxDBPassword = ctx.GlobalString(utils.MetricsInfluxDBPasswordFlag.Name) + } + if ctx.GlobalIsSet(utils.MetricsInfluxDBTagsFlag.Name) { + cfg.Metrics.InfluxDBTags = ctx.GlobalString(utils.MetricsInfluxDBTagsFlag.Name) + } +} diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 8773208adf..9d8794eb15 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -19,10 +19,8 @@ package main import ( "fmt" "os" - "os/signal" "path/filepath" "strings" - "syscall" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" @@ -38,12 +36,12 @@ var ( Action: utils.MigrateFlags(localConsole), Name: "console", Usage: "Start an interactive JavaScript environment", - Flags: append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...), + Flags: append(append(nodeFlags, rpcFlags...), consoleFlags...), Category: "CONSOLE COMMANDS", Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, +See https://geth.ethereum.org/docs/interface/javascript-console.`, } attachCommand = cli.Command{ @@ -56,7 +54,7 @@ See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`, Description: ` The Geth console is an interactive shell for the JavaScript runtime environment which exposes a node admin interface as well as the Ðapp JavaScript API. -See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console. +See https://geth.ethereum.org/docs/interface/javascript-console. This command allows to open a console on a running geth node.`, } @@ -69,7 +67,7 @@ This command allows to open a console on a running geth node.`, Category: "CONSOLE COMMANDS", Description: ` The JavaScript VM exposes a node admin interface as well as the Ðapp -JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console`, +JavaScript API. See https://geth.ethereum.org/docs/interface/javascript-console`, } ) @@ -78,12 +76,12 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Cons func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags prepare(ctx) - node := makeFullNode(ctx) - startNode(ctx, node) - defer node.Close() + stack, backend := makeFullNode(ctx) + startNode(ctx, stack, backend) + defer stack.Close() // Attach to the newly started node and start the JavaScript console - client, err := node.Attach() + client, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to the inproc geth: %v", err) } @@ -123,7 +121,7 @@ func remoteConsole(ctx *cli.Context) error { path = ctx.GlobalString(utils.DataDirFlag.Name) } if path != "" { - if ctx.GlobalBool(utils.LegacyTestnetFlag.Name) || ctx.GlobalBool(utils.RopstenFlag.Name) { + if ctx.GlobalBool(utils.RopstenFlag.Name) { // Maintain compatibility with older Geth configurations storing the // Ropsten database in `testnet` instead of `ropsten`. legacyPath := filepath.Join(path, "testnet") @@ -136,6 +134,8 @@ func remoteConsole(ctx *cli.Context) error { path = filepath.Join(path, "rinkeby") } else if ctx.GlobalBool(utils.GoerliFlag.Name) { path = filepath.Join(path, "goerli") + } else if ctx.GlobalBool(utils.YoloV3Flag.Name) { + path = filepath.Join(path, "yolo-v3") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) @@ -188,12 +188,12 @@ func dialRPC(endpoint string) (*rpc.Client, error) { // everything down. func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags - node := makeFullNode(ctx) - startNode(ctx, node) - defer node.Close() + stack, backend := makeFullNode(ctx) + startNode(ctx, stack, backend) + defer stack.Close() // Attach to the newly started node and start the JavaScript console - client, err := node.Attach() + client, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to the inproc geth: %v", err) } @@ -216,13 +216,10 @@ func ephemeralConsole(ctx *cli.Context) error { utils.Fatalf("Failed to execute %s: %v", file, err) } } - // Wait for pending callbacks, but stop for Ctrl-C. - abort := make(chan os.Signal, 1) - signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM) go func() { - <-abort - os.Exit(0) + stack.Wait() + console.Stop(false) }() console.Stop(true) diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 8f6a102d73..c3f41b187c 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -31,20 +31,29 @@ import ( ) const ( - ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0" + ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0" httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" ) +// spawns geth with the given command line args, using a set of flags to minimise +// memory and disk IO. If the args don't set --datadir, the +// child g gets a temporary data directory. +func runMinimalGeth(t *testing.T, args ...string) *testgeth { + // --ropsten to make the 'writing genesis to disk' faster (no accounts) + // --networkid=1337 to avoid cache bump + // --syncmode=full to avoid allocating fast sync bloom + allArgs := []string{"--ropsten", "--networkid", "1337", "--syncmode=full", "--port", "0", + "--nat", "none", "--nodiscover", "--maxpeers", "0", "--cache", "64"} + return runGeth(t, append(allArgs, args...)...) +} + // Tests that a node embedded within a console can be started up properly and // then terminated by closing the input stream. func TestConsoleWelcome(t *testing.T) { coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" // Start a geth console, make sure it's cleaned up and terminate the console - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--shh", - "console") + geth := runMinimalGeth(t, "--miner.etherbase", coinbase, "console") // Gather all the infos the welcome message needs to contain geth.SetTemplateFunc("goos", func() string { return runtime.GOOS }) @@ -66,16 +75,20 @@ at block: 0 ({{niltime}}) datadir: {{.Datadir}} modules: {{apis}} +To exit, press ctrl-d > {{.InputLine "exit"}} `) geth.ExpectExit() } // Tests that a console can be attached to a running node via various means. -func TestIPCAttachWelcome(t *testing.T) { - // Configure the instance for IPC attachement - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - var ipc string +func TestAttachWelcome(t *testing.T) { + var ( + ipc string + httpPort string + wsPort string + ) + // Configure the instance for IPC attachment if runtime.GOOS == "windows" { ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999)) } else { @@ -83,53 +96,28 @@ func TestIPCAttachWelcome(t *testing.T) { defer os.RemoveAll(ws) ipc = filepath.Join(ws, "geth.ipc") } - // Note: we need --shh because testAttachWelcome checks for default - // list of ipc modules and shh is included there. - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--shh", "--ipcpath", ipc) - - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - waitForEndpoint(t, ipc, 3*time.Second) - testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) - -} - -func TestHTTPAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--rpc", "--rpcport", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - endpoint := "http://127.0.0.1:" + port - waitForEndpoint(t, endpoint, 3*time.Second) - testAttachWelcome(t, geth, endpoint, httpAPIs) -} - -func TestWSAttachWelcome(t *testing.T) { - coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P - - geth := runGeth(t, - "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ws", "--wsport", port) - defer func() { - geth.Interrupt() - geth.ExpectExit() - }() - - endpoint := "ws://127.0.0.1:" + port - waitForEndpoint(t, endpoint, 3*time.Second) - testAttachWelcome(t, geth, endpoint, httpAPIs) + // And HTTP + WS attachment + p := trulyRandInt(1024, 65533) // Yeah, sometimes this will fail, sorry :P + httpPort = strconv.Itoa(p) + wsPort = strconv.Itoa(p + 1) + geth := runMinimalGeth(t, "--miner.etherbase", "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182", + "--ipcpath", ipc, + "--http", "--http.port", httpPort, + "--ws", "--ws.port", wsPort) + t.Run("ipc", func(t *testing.T) { + waitForEndpoint(t, ipc, 3*time.Second) + testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs) + }) + t.Run("http", func(t *testing.T) { + endpoint := "http://127.0.0.1:" + httpPort + waitForEndpoint(t, endpoint, 3*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) + t.Run("ws", func(t *testing.T) { + endpoint := "ws://127.0.0.1:" + wsPort + waitForEndpoint(t, endpoint, 3*time.Second) + testAttachWelcome(t, geth, endpoint, httpAPIs) + }) } func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { @@ -161,6 +149,7 @@ at block: 0 ({{niltime}}){{if ipc}} datadir: {{datadir}}{{end}} modules: {{apis}} +To exit, press ctrl-d > {{.InputLine "exit" }} `) attach.ExpectExit() diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index cb06038ec8..b7f26b3652 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -39,7 +39,9 @@ var daoOldGenesis = `{ "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", - "config" : {} + "config" : { + "homesteadBlock" : 0 + } }` // Genesis block for nodes which actively oppose the DAO fork @@ -54,6 +56,7 @@ var daoNoForkGenesis = `{ "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", "config" : { + "homesteadBlock" : 0, "daoForkBlock" : 314, "daoForkSupport" : false } @@ -71,6 +74,7 @@ var daoProForkGenesis = `{ "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", "config" : { + "homesteadBlock" : 0, "daoForkBlock" : 314, "daoForkSupport" : true } @@ -111,16 +115,15 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", test, err) } - runGeth(t, "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "--networkid", "1337", "init", json).WaitExit() } else { // Force chain initialization - args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} - geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...) - geth.WaitExit() + args := []string{"--port", "0", "--networkid", "1337", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir} + runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...).WaitExit() } // Retrieve the DAO config flag from the database path := filepath.Join(datadir, "geth", "chaindata") - db, err := rawdb.NewLevelDBDatabase(path, 0, 0, "") + db, err := rawdb.NewLevelDBDatabase(path, 0, 0, "", false) if err != nil { t.Fatalf("test %d: failed to open test database: %v", test, err) } diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go new file mode 100644 index 0000000000..4c70373e9a --- /dev/null +++ b/cmd/geth/dbcmd.go @@ -0,0 +1,509 @@ +// 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 . + +package main + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/console/prompt" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie" + "gopkg.in/urfave/cli.v1" +) + +var ( + removedbCommand = cli.Command{ + Action: utils.MigrateFlags(removeDB), + Name: "removedb", + Usage: "Remove blockchain and state databases", + ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + }, + Category: "DATABASE COMMANDS", + Description: ` +Remove blockchain and state databases`, + } + dbCommand = cli.Command{ + Name: "db", + Usage: "Low level database operations", + ArgsUsage: "", + Category: "DATABASE COMMANDS", + Subcommands: []cli.Command{ + dbInspectCmd, + dbStatCmd, + dbCompactCmd, + dbGetCmd, + dbDeleteCmd, + dbPutCmd, + dbGetSlotsCmd, + dbDumpFreezerIndex, + }, + } + dbInspectCmd = cli.Command{ + Action: utils.MigrateFlags(inspect), + Name: "inspect", + ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Usage: "Inspect the storage size for each type of data in the database", + Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, + } + dbStatCmd = cli.Command{ + Action: utils.MigrateFlags(dbStats), + Name: "stats", + Usage: "Print leveldb statistics", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + } + dbCompactCmd = cli.Command{ + Action: utils.MigrateFlags(dbCompact), + Name: "compact", + Usage: "Compact leveldb database. WARNING: May take a very long time", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + utils.CacheFlag, + utils.CacheDatabaseFlag, + }, + Description: `This command performs a database compaction. +WARNING: This operation may take a very long time to finish, and may cause database +corruption if it is aborted during execution'!`, + } + dbGetCmd = cli.Command{ + Action: utils.MigrateFlags(dbGet), + Name: "get", + Usage: "Show the value of a database key", + ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: "This command looks up the specified database key from the database.", + } + dbDeleteCmd = cli.Command{ + Action: utils.MigrateFlags(dbDelete), + Name: "delete", + Usage: "Delete a database key (WARNING: may corrupt your database)", + ArgsUsage: "", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: `This command deletes the specified database key from the database. +WARNING: This is a low-level operation which may cause database corruption!`, + } + dbPutCmd = cli.Command{ + Action: utils.MigrateFlags(dbPut), + Name: "put", + Usage: "Set the value of a database key (WARNING: may corrupt your database)", + ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: `This command sets a given database key to the given value. +WARNING: This is a low-level operation which may cause database corruption!`, + } + dbGetSlotsCmd = cli.Command{ + Action: utils.MigrateFlags(dbDumpTrie), + Name: "dumptrie", + Usage: "Show the storage key/values of a given storage trie", + ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: "This command looks up the specified database key from the database.", + } + dbDumpFreezerIndex = cli.Command{ + Action: utils.MigrateFlags(freezerInspect), + Name: "freezer-index", + Usage: "Dump out the index of a given freezer type", + ArgsUsage: " ", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.SyncModeFlag, + utils.MainnetFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.YoloV3Flag, + }, + Description: "This command displays information about the freezer index.", + } +) + +func removeDB(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + + // Remove the full node state database + path := stack.ResolvePath("chaindata") + if common.FileExist(path) { + confirmAndRemoveDB(path, "full node state database") + } else { + log.Info("Full node state database missing", "path", path) + } + // Remove the full node ancient database + path = config.Eth.DatabaseFreezer + switch { + case path == "": + path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") + case !filepath.IsAbs(path): + path = config.Node.ResolvePath(path) + } + if common.FileExist(path) { + confirmAndRemoveDB(path, "full node ancient database") + } else { + log.Info("Full node ancient database missing", "path", path) + } + // Remove the light node database + path = stack.ResolvePath("lightchaindata") + if common.FileExist(path) { + confirmAndRemoveDB(path, "light node database") + } else { + log.Info("Light node database missing", "path", path) + } + return nil +} + +// confirmAndRemoveDB prompts the user for a last confirmation and removes the +// folder if accepted. +func confirmAndRemoveDB(database string, kind string) { + confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) + switch { + case err != nil: + utils.Fatalf("%v", err) + case !confirm: + log.Info("Database deletion skipped", "path", database) + default: + start := time.Now() + filepath.Walk(database, func(path string, info os.FileInfo, err error) error { + // If we're at the top level folder, recurse into + if path == database { + return nil + } + // Delete all the files, but not subfolders + if !info.IsDir() { + os.Remove(path) + return nil + } + return filepath.SkipDir + }) + log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +func inspect(ctx *cli.Context) error { + var ( + prefix []byte + start []byte + ) + if ctx.NArg() > 2 { + return fmt.Errorf("Max 2 arguments: %v", ctx.Command.ArgsUsage) + } + if ctx.NArg() >= 1 { + if d, err := hexutil.Decode(ctx.Args().Get(0)); err != nil { + return fmt.Errorf("failed to hex-decode 'prefix': %v", err) + } else { + prefix = d + } + } + if ctx.NArg() >= 2 { + if d, err := hexutil.Decode(ctx.Args().Get(1)); err != nil { + return fmt.Errorf("failed to hex-decode 'start': %v", err) + } else { + start = d + } + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + return rawdb.InspectDatabase(db, prefix, start) +} + +func showLeveldbStats(db ethdb.Stater) { + if stats, err := db.Stat("leveldb.stats"); err != nil { + log.Warn("Failed to read database stats", "error", err) + } else { + fmt.Println(stats) + } + if ioStats, err := db.Stat("leveldb.iostats"); err != nil { + log.Warn("Failed to read database iostats", "error", err) + } else { + fmt.Println(ioStats) + } +} + +func dbStats(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + showLeveldbStats(db) + return nil +} + +func dbCompact(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + log.Info("Stats before compaction") + showLeveldbStats(db) + + log.Info("Triggering compaction") + if err := db.Compact(nil, nil); err != nil { + log.Info("Compact err", "error", err) + return err + } + log.Info("Stats after compaction") + showLeveldbStats(db) + return nil +} + +// dbGet shows the value of a given database key +func dbGet(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + + key, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + data, err := db.Get(key) + if err != nil { + log.Info("Get operation failed", "error", err) + return err + } + fmt.Printf("key %#x: %#x\n", key, data) + return nil +} + +// dbDelete deletes a key from the database +func dbDelete(ctx *cli.Context) error { + if ctx.NArg() != 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + key, err := hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + data, err := db.Get(key) + if err == nil { + fmt.Printf("Previous value: %#x\n", data) + } + if err = db.Delete(key); err != nil { + log.Info("Delete operation returned an error", "error", err) + return err + } + return nil +} + +// dbPut overwrite a value in the database +func dbPut(ctx *cli.Context) error { + if ctx.NArg() != 2 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() + + var ( + key []byte + value []byte + data []byte + err error + ) + key, err = hexutil.Decode(ctx.Args().Get(0)) + if err != nil { + log.Info("Could not decode the key", "error", err) + return err + } + value, err = hexutil.Decode(ctx.Args().Get(1)) + if err != nil { + log.Info("Could not decode the value", "error", err) + return err + } + data, err = db.Get(key) + if err == nil { + fmt.Printf("Previous value: %#x\n", data) + } + return db.Put(key, value) +} + +// dbDumpTrie shows the key-value slots of a given storage trie +func dbDumpTrie(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + var ( + root []byte + start []byte + max = int64(-1) + err error + ) + if root, err = hexutil.Decode(ctx.Args().Get(0)); err != nil { + log.Info("Could not decode the root", "error", err) + return err + } + stRoot := common.BytesToHash(root) + if ctx.NArg() >= 2 { + if start, err = hexutil.Decode(ctx.Args().Get(1)); err != nil { + log.Info("Could not decode the seek position", "error", err) + return err + } + } + if ctx.NArg() >= 3 { + if max, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil { + log.Info("Could not decode the max count", "error", err) + return err + } + } + theTrie, err := trie.New(stRoot, trie.NewDatabase(db)) + if err != nil { + return err + } + var count int64 + it := trie.NewIterator(theTrie.NodeIterator(start)) + for it.Next() { + if max > 0 && count == max { + fmt.Printf("Exiting after %d values\n", count) + break + } + fmt.Printf(" %d. key %#x: %#x\n", count, it.Key, it.Value) + count++ + } + return it.Err +} + +func freezerInspect(ctx *cli.Context) error { + var ( + start, end int64 + disableSnappy bool + err error + ) + if ctx.NArg() < 3 { + return fmt.Errorf("required arguments: %v", ctx.Command.ArgsUsage) + } + kind := ctx.Args().Get(0) + if noSnap, ok := rawdb.FreezerNoSnappy[kind]; !ok { + var options []string + for opt := range rawdb.FreezerNoSnappy { + options = append(options, opt) + } + sort.Strings(options) + return fmt.Errorf("Could read freezer-type '%v'. Available options: %v", kind, options) + } else { + disableSnappy = noSnap + } + if start, err = strconv.ParseInt(ctx.Args().Get(1), 10, 64); err != nil { + log.Info("Could read start-param", "error", err) + return err + } + if end, err = strconv.ParseInt(ctx.Args().Get(2), 10, 64); err != nil { + log.Info("Could read count param", "error", err) + return err + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + path := filepath.Join(stack.ResolvePath("chaindata"), "ancient") + log.Info("Opening freezer", "location", path, "name", kind) + if f, err := rawdb.NewFreezerTable(path, kind, disableSnappy); err != nil { + return err + } else { + f.DumpIndex(start, end) + } + return nil +} diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index 9ac2dded9a..cbc1b38374 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -58,7 +58,7 @@ var customGenesisTests = []struct { "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", "config" : { - "homesteadBlock" : 314, + "homesteadBlock" : 42, "daoForkBlock" : 141, "daoForkSupport" : true } @@ -81,10 +81,10 @@ func TestCustomGenesis(t *testing.T) { if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", i, err) } - runGeth(t, "--nousb", "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, "--nousb", + geth := runGeth(t, "--networkid", "1337", "--syncmode=full", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go new file mode 100644 index 0000000000..053ce96aa3 --- /dev/null +++ b/cmd/geth/les_test.go @@ -0,0 +1,220 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" +) + +type gethrpc struct { + name string + rpc *rpc.Client + geth *testgeth + nodeInfo *p2p.NodeInfo +} + +func (g *gethrpc) killAndWait() { + g.geth.Kill() + g.geth.WaitExit() +} + +func (g *gethrpc) callRPC(result interface{}, method string, args ...interface{}) { + if err := g.rpc.Call(&result, method, args...); err != nil { + g.geth.Fatalf("callRPC %v: %v", method, err) + } +} + +func (g *gethrpc) addPeer(peer *gethrpc) { + g.geth.Logf("%v.addPeer(%v)", g.name, peer.name) + enode := peer.getNodeInfo().Enode + peerCh := make(chan *p2p.PeerEvent) + sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents") + if err != nil { + g.geth.Fatalf("subscribe %v: %v", g.name, err) + } + defer sub.Unsubscribe() + g.callRPC(nil, "admin_addPeer", enode) + dur := 14 * time.Second + timeout := time.After(dur) + select { + case ev := <-peerCh: + g.geth.Logf("%v received event: type=%v, peer=%v", g.name, ev.Type, ev.Peer) + case err := <-sub.Err(): + g.geth.Fatalf("%v sub error: %v", g.name, err) + case <-timeout: + g.geth.Error("timeout adding peer after", dur) + } +} + +// Use this function instead of `g.nodeInfo` directly +func (g *gethrpc) getNodeInfo() *p2p.NodeInfo { + if g.nodeInfo != nil { + return g.nodeInfo + } + g.nodeInfo = &p2p.NodeInfo{} + g.callRPC(&g.nodeInfo, "admin_nodeInfo") + return g.nodeInfo +} + +func (g *gethrpc) waitSynced() { + // Check if it's synced now + var result interface{} + g.callRPC(&result, "eth_syncing") + syncing, ok := result.(bool) + if ok && !syncing { + g.geth.Logf("%v already synced", g.name) + return + } + + // Actually wait, subscribe to the event + ch := make(chan interface{}) + sub, err := g.rpc.Subscribe(context.Background(), "eth", ch, "syncing") + if err != nil { + g.geth.Fatalf("%v syncing: %v", g.name, err) + } + defer sub.Unsubscribe() + timeout := time.After(4 * time.Second) + select { + case ev := <-ch: + g.geth.Log("'syncing' event", ev) + syncing, ok := ev.(bool) + if ok && !syncing { + break + } + g.geth.Log("Other 'syncing' event", ev) + case err := <-sub.Err(): + g.geth.Fatalf("%v notification: %v", g.name, err) + break + case <-timeout: + g.geth.Fatalf("%v timeout syncing", g.name) + break + } +} + +// ipcEndpoint resolves an IPC endpoint based on a configured value, taking into +// account the set data folders as well as the designated platform we're currently +// running on. +func ipcEndpoint(ipcPath, datadir string) string { + // On windows we can only use plain top-level pipes + if runtime.GOOS == "windows" { + if strings.HasPrefix(ipcPath, `\\.\pipe\`) { + return ipcPath + } + return `\\.\pipe\` + ipcPath + } + // Resolve names into the data directory full paths otherwise + if filepath.Base(ipcPath) == ipcPath { + if datadir == "" { + return filepath.Join(os.TempDir(), ipcPath) + } + return filepath.Join(datadir, ipcPath) + } + return ipcPath +} + +// nextIPC ensures that each ipc pipe gets a unique name. +// On linux, it works well to use ipc pipes all over the filesystem (in datadirs), +// but windows require pipes to sit in "\\.\pipe\". Therefore, to run several +// nodes simultaneously, we need to distinguish between them, which we do by +// the pipe filename instead of folder. +var nextIPC = uint32(0) + +func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { + ipcName := fmt.Sprintf("geth-%d.ipc", atomic.AddUint32(&nextIPC, 1)) + args = append([]string{"--networkid=42", "--port=0", "--ipcpath", ipcName}, args...) + t.Logf("Starting %v with rpc: %v", name, args) + + g := &gethrpc{ + name: name, + geth: runGeth(t, args...), + } + // wait before we can attach to it. TODO: probe for it properly + time.Sleep(1 * time.Second) + var err error + ipcpath := ipcEndpoint(ipcName, g.geth.Datadir) + if g.rpc, err = rpc.Dial(ipcpath); err != nil { + t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) + } + return g +} + +func initGeth(t *testing.T) string { + args := []string{"--networkid=42", "init", "./testdata/clique.json"} + t.Logf("Initializing geth: %v ", args) + g := runGeth(t, args...) + datadir := g.Datadir + g.WaitExit() + return datadir +} + +func startLightServer(t *testing.T) *gethrpc { + datadir := initGeth(t) + t.Logf("Importing keys to geth") + runGeth(t, "--datadir", datadir, "--password", "./testdata/password.txt", "account", "import", "./testdata/key.prv", "--lightkdf").WaitExit() + account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" + server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") + return server +} + +func startClient(t *testing.T, name string) *gethrpc { + datadir := initGeth(t) + return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4") +} + +func TestPriorityClient(t *testing.T) { + lightServer := startLightServer(t) + defer lightServer.killAndWait() + + // Start client and add lightServer as peer + freeCli := startClient(t, "freeCli") + defer freeCli.killAndWait() + freeCli.addPeer(lightServer) + + var peers []*p2p.PeerInfo + freeCli.callRPC(&peers, "admin_peers") + if len(peers) != 1 { + t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers)) + return + } + + // Set up priority client, get its nodeID, increase its balance on the lightServer + prioCli := startClient(t, "prioCli") + defer prioCli.killAndWait() + // 3_000_000_000 once we move to Go 1.13 + tokens := uint64(3000000000) + lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens) + prioCli.addPeer(lightServer) + + // Check if priority client is actually syncing and the regular client got kicked out + prioCli.callRPC(&peers, "admin_peers") + if len(peers) != 1 { + t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers)) + } + + nodes := map[string]*gethrpc{ + lightServer.getNodeInfo().ID: lightServer, + freeCli.getNodeInfo().ID: freeCli, + prioCli.getNodeInfo().ID: prioCli, + } + time.Sleep(1 * time.Second) + lightServer.callRPC(&peers, "admin_peers") + peersWithNames := make(map[string]string) + for _, p := range peers { + peersWithNames[nodes[p.ID].name] = p.ID + } + if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound { + t.Error("client is still a peer of lightServer", peersWithNames) + } + if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound { + t.Error("prio client is not among lightServer peers", peersWithNames) + } +} diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 96ed8c479f..ed54827c97 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -19,30 +19,27 @@ package main import ( "fmt" - "math" "os" - "runtime" - godebug "runtime/debug" "sort" "strconv" "strings" "time" - "github.com/elastic/gosigar" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/console" + "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/internal/debug" - "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" - cli "gopkg.in/urfave/cli.v1" + "gopkg.in/urfave/cli.v1" ) const ( @@ -54,25 +51,24 @@ var ( gitCommit = "" gitDate = "" // The app that holds all commands and flags. - app = utils.NewApp(gitCommit, gitDate, "the go-ethereum command line interface") + app = flags.NewApp(gitCommit, gitDate, "the go-ethereum command line interface") // flags that configure the node nodeFlags = []cli.Flag{ utils.IdentityFlag, utils.UnlockedAccountFlag, utils.PasswordFileFlag, utils.BootnodesFlag, - utils.BootnodesV4Flag, - utils.BootnodesV5Flag, utils.DataDirFlag, utils.AncientFlag, + utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.ExternalSignerFlag, utils.NoUSBFlag, utils.DirectBroadcastFlag, utils.RangeLimitFlag, + utils.USBFlag, utils.SmartCardDaemonPathFlag, - utils.OverrideIstanbulFlag, - utils.OverrideMuirGlacierFlag, + utils.OverrideBerlinFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, @@ -96,39 +92,39 @@ var ( utils.ExitWhenSyncedFlag, utils.GCModeFlag, utils.SnapshotFlag, + utils.TxLookupLimitFlag, utils.LightServeFlag, - utils.LightLegacyServFlag, utils.LightIngressFlag, utils.LightEgressFlag, utils.LightMaxPeersFlag, - utils.LightLegacyPeersFlag, + utils.LightNoPruneFlag, utils.LightKDFFlag, utils.UltraLightServersFlag, utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, + utils.LightNoSyncServeFlag, utils.WhitelistFlag, + utils.BloomFilterSizeFlag, utils.CacheFlag, utils.CacheDatabaseFlag, utils.CacheTrieFlag, + utils.CacheTrieJournalFlag, + utils.CacheTrieRejournalFlag, utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, utils.ListenPortFlag, utils.MaxPeersFlag, utils.MaxPendingPeersFlag, utils.MiningEnabledFlag, utils.MinerThreadsFlag, - utils.MinerLegacyThreadsFlag, utils.MinerNotifyFlag, utils.MinerGasTargetFlag, - utils.MinerLegacyGasTargetFlag, utils.MinerGasLimitFlag, utils.MinerGasPriceFlag, - utils.MinerLegacyGasPriceFlag, utils.MinerEtherbaseFlag, - utils.MinerLegacyEtherbaseFlag, utils.MinerExtraDataFlag, - utils.MinerLegacyExtraDataFlag, utils.MinerRecommitIntervalFlag, utils.MinerDelayLeftoverFlag, utils.MinerNoVerfiyFlag, @@ -139,12 +135,13 @@ var ( utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, utils.DNSDiscoveryFlag, + utils.MainnetFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, - utils.LegacyTestnetFlag, utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, + utils.YoloV3Flag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, @@ -152,44 +149,50 @@ var ( utils.NoCompactionFlag, utils.GpoBlocksFlag, utils.GpoPercentileFlag, + utils.GpoMaxGasPriceFlag, utils.EWASMInterpreterFlag, utils.EVMInterpreterFlag, + utils.MinerNotifyFullFlag, configFileFlag, + utils.CatalystFlag, } rpcFlags = []cli.Flag{ - utils.RPCEnabledFlag, - utils.RPCListenAddrFlag, - utils.RPCPortFlag, - utils.RPCCORSDomainFlag, - utils.RPCVirtualHostsFlag, + utils.HTTPEnabledFlag, + utils.HTTPListenAddrFlag, + utils.HTTPPortFlag, + utils.HTTPCORSDomainFlag, + utils.HTTPVirtualHostsFlag, + utils.LegacyRPCEnabledFlag, + utils.LegacyRPCListenAddrFlag, + utils.LegacyRPCPortFlag, + utils.LegacyRPCCORSDomainFlag, + utils.LegacyRPCVirtualHostsFlag, + utils.LegacyRPCApiFlag, utils.GraphQLEnabledFlag, - utils.GraphQLListenAddrFlag, - utils.GraphQLPortFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, - utils.RPCApiFlag, + utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, utils.WSPortFlag, utils.WSApiFlag, utils.WSAllowedOriginsFlag, + utils.WSPathPrefixFlag, utils.IPCDisabledFlag, utils.IPCPathFlag, utils.InsecureUnlockAllowedFlag, - utils.RPCGlobalGasCap, - } - - whisperFlags = []cli.Flag{ - utils.WhisperEnabledFlag, - utils.WhisperMaxMessageSizeFlag, - utils.WhisperMinPOWFlag, - utils.WhisperRestrictConnectionBetweenLightClientsFlag, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, } metricsFlags = []cli.Flag{ utils.MetricsEnabledFlag, utils.MetricsEnabledExpensiveFlag, + utils.MetricsHTTPFlag, + utils.MetricsPortFlag, utils.MetricsEnableInfluxDBFlag, utils.MetricsInfluxDBEndpointFlag, utils.MetricsInfluxDBDatabaseFlag, @@ -212,11 +215,9 @@ func init() { exportCommand, importPreimagesCommand, exportPreimagesCommand, - copydbCommand, removedbCommand, dumpCommand, dumpGenesisCommand, - inspectCommand, // See accountcmd.go: accountCommand, walletCommand, @@ -228,11 +229,16 @@ func init() { makecacheCommand, makedagCommand, versionCommand, + versionCheckCommand, licenseCommand, // See config.go dumpConfigCommand, - // See retesteth.go - retestethCommand, + // see dbcmd.go + dbCommand, + // See cmd/utils/flags_legacy.go + utils.ShowDeprecated, + // See snapshot.go + snapshotCommand, } sort.Sort(cli.CommandsByName(app.Commands)) @@ -240,7 +246,6 @@ func init() { app.Flags = append(app.Flags, rpcFlags...) app.Flags = append(app.Flags, consoleFlags...) app.Flags = append(app.Flags, debug.Flags...) - app.Flags = append(app.Flags, whisperFlags...) app.Flags = append(app.Flags, metricsFlags...) app.Before = func(ctx *cli.Context) error { @@ -248,7 +253,7 @@ func init() { } app.After = func(ctx *cli.Context) error { debug.Exit() - console.Stdin.Close() // Resets terminal mode. + prompt.Stdin.Close() // Resets terminal mode. return nil } } @@ -265,11 +270,6 @@ func main() { func prepare(ctx *cli.Context) { // If we're running a known preset, log it for convenience. switch { - case ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name): - log.Info("Starting Geth on Ropsten testnet...") - log.Warn("The --testnet flag is ambiguous! Please specify one of --goerli, --rinkeby, or --ropsten.") - log.Warn("The generic --testnet flag is deprecated and will be removed in the future!") - case ctx.GlobalIsSet(utils.RopstenFlag.Name): log.Info("Starting Geth on Ropsten testnet...") @@ -279,6 +279,9 @@ func prepare(ctx *cli.Context) { case ctx.GlobalIsSet(utils.GoerliFlag.Name): log.Info("Starting Geth on Görli testnet...") + case ctx.GlobalIsSet(utils.YoloV3Flag.Name): + log.Info("Starting Geth on YOLOv3 testnet...") + case ctx.GlobalIsSet(utils.DeveloperFlag.Name): log.Info("Starting Geth in ephemeral dev mode...") @@ -288,7 +291,7 @@ func prepare(ctx *cli.Context) { // If we're a full node on mainnet without --cache specified, bump default cache allowance if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) { // Make sure we're not on any supported preconfigured testnet either - if !ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name) && !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { + if !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { // Nope, we're really on mainnet. Bump that cache up! log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096)) @@ -299,25 +302,6 @@ func prepare(ctx *cli.Context) { log.Info("Dropping default light client cache", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 128) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128)) } - // Cap the cache allowance and tune the garbage collector - var mem gosigar.Mem - // Workaround until OpenBSD support lands into gosigar - // Check https://github.com/elastic/gosigar#supported-platforms - if runtime.GOOS != "openbsd" { - if err := mem.Get(); err == nil { - allowance := int(mem.Total / 1024 / 1024 / 3) - if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance { - log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance) - ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance)) - } - } - } - // Ensure Go's GC ignores the database cache for trigger percentage - cache := ctx.GlobalInt(utils.CacheFlag.Name) - gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024))) - - log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) - godebug.SetGCPercent(int(gogc)) // Start metrics export if enabled utils.SetupMetrics(ctx) @@ -333,22 +317,24 @@ func geth(ctx *cli.Context) error { if args := ctx.Args(); len(args) > 0 { return fmt.Errorf("invalid command: %q", args[0]) } + prepare(ctx) - node := makeFullNode(ctx) - defer node.Close() - startNode(ctx, node) - node.Wait() + stack, backend := makeFullNode(ctx) + defer stack.Close() + + startNode(ctx, stack, backend) + stack.Wait() return nil } // startNode boots up the system node and all registered protocols, after which // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. -func startNode(ctx *cli.Context, stack *node.Node) { +func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend) { debug.Memsize.Add("node", stack) // Start up the node itself - utils.StartNode(stack) + utils.StartNode(ctx, stack) // Unlock any account specifically requested unlockAccounts(ctx, stack) @@ -364,25 +350,6 @@ func startNode(ctx *cli.Context, stack *node.Node) { } ethClient := ethclient.NewClient(rpcClient) - // Set contract backend for ethereum service if local node - // is serving LES requests. - if ctx.GlobalInt(utils.LightLegacyServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 { - var ethService *eth.Ethereum - if err := stack.Service(ðService); err != nil { - utils.Fatalf("Failed to retrieve ethereum service: %v", err) - } - ethService.SetContractBackend(ethClient) - } - // Set contract backend for les service if local node is - // running as a light client. - if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { - var lesService *les.LightEthereum - if err := stack.Service(&lesService); err != nil { - utils.Fatalf("Failed to retrieve light ethereum service: %v", err) - } - lesService.SetContractBackend(ethClient) - } - go func() { // Open any wallets already attached for _, wallet := range stack.AccountManager().Wallets() { @@ -434,7 +401,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute { log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(), "age", common.PrettyAge(timestamp)) - stack.Stop() + stack.Close() } } }() @@ -446,22 +413,16 @@ func startNode(ctx *cli.Context, stack *node.Node) { if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } - var ethereum *eth.Ethereum - if err := stack.Service(ðereum); err != nil { + ethBackend, ok := backend.(*eth.EthAPIBackend) + if !ok { utils.Fatalf("Ethereum service not running: %v", err) } // Set the gas price to the limits from the CLI and start mining - gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name) - if ctx.IsSet(utils.MinerGasPriceFlag.Name) { - gasprice = utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) - } - ethereum.TxPool().SetGasPrice(gasprice) - - threads := ctx.GlobalInt(utils.MinerLegacyThreadsFlag.Name) - if ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { - threads = ctx.GlobalInt(utils.MinerThreadsFlag.Name) - } - if err := ethereum.StartMining(threads); err != nil { + gasprice := utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) + ethBackend.TxPool().SetGasPrice(gasprice) + // start mining + threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name) + if err := ethBackend.StartMining(threads); err != nil { utils.Fatalf("Failed to start mining: %v", err) } } diff --git a/cmd/geth/misccmd.go b/cmd/geth/misccmd.go index 0e7ee96513..b347d31d97 100644 --- a/cmd/geth/misccmd.go +++ b/cmd/geth/misccmd.go @@ -25,12 +25,23 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/params" "gopkg.in/urfave/cli.v1" ) var ( + VersionCheckUrlFlag = cli.StringFlag{ + Name: "check.url", + Usage: "URL to use when checking vulnerabilities", + Value: "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json", + } + VersionCheckVersionFlag = cli.StringFlag{ + Name: "check.version", + Usage: "Version to check", + Value: fmt.Sprintf("Geth/v%v/%v-%v/%v", + params.VersionWithCommit(gitCommit, gitDate), + runtime.GOOS, runtime.GOARCH, runtime.Version()), + } makecacheCommand = cli.Command{ Action: utils.MigrateFlags(makecache), Name: "makecache", @@ -65,6 +76,21 @@ Regular users do not need to execute it. Category: "MISCELLANEOUS COMMANDS", Description: ` The output of this command is supposed to be machine-readable. +`, + } + versionCheckCommand = cli.Command{ + Action: utils.MigrateFlags(versionCheck), + Flags: []cli.Flag{ + VersionCheckUrlFlag, + VersionCheckVersionFlag, + }, + Name: "version-check", + Usage: "Checks (online) whether the current version suffers from any known security vulnerabilities", + ArgsUsage: "", + Category: "MISCELLANEOUS COMMANDS", + Description: ` +The version-check command fetches vulnerability-information from https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json, +and displays information about any security vulnerabilities that affect the currently executing version. `, } licenseCommand = cli.Command{ @@ -116,7 +142,6 @@ func version(ctx *cli.Context) error { fmt.Println("Git Commit Date:", gitDate) } fmt.Println("Architecture:", runtime.GOARCH) - fmt.Println("Protocol Versions:", eth.ProtocolVersions) fmt.Println("Go Version:", runtime.Version()) fmt.Println("Operating System:", runtime.GOOS) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go deleted file mode 100644 index e254890ca0..0000000000 --- a/cmd/geth/retesteth.go +++ /dev/null @@ -1,945 +0,0 @@ -// Copyright 2019 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 . - -package main - -import ( - "bytes" - "context" - "fmt" - "math/big" - "os" - "os/signal" - "strings" - "time" - - "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/consensus/misc" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/systemcontracts" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" - - cli "gopkg.in/urfave/cli.v1" -) - -var ( - rpcPortFlag = cli.IntFlag{ - Name: "rpcport", - Usage: "HTTP-RPC server listening port", - Value: node.DefaultHTTPPort, - } - retestethCommand = cli.Command{ - Action: utils.MigrateFlags(retesteth), - Name: "retesteth", - Usage: "Launches geth in retesteth mode", - ArgsUsage: "", - Flags: []cli.Flag{rpcPortFlag}, - Category: "MISCELLANEOUS COMMANDS", - Description: `Launches geth in retesteth mode (no database, no network, only retesteth RPC interface)`, - } -) - -type RetestethTestAPI interface { - SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) - MineBlocks(ctx context.Context, number uint64) (bool, error) - ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) - ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) - RewindToBlock(ctx context.Context, number uint64) (bool, error) - GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) -} - -type RetestethEthAPI interface { - SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) - BlockNumber(ctx context.Context) (uint64, error) - GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) - GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) - GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) - GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) - GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) -} - -type RetestethDebugAPI interface { - AccountRange(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - addressHash *math.HexOrDecimal256, maxResults uint64, - ) (AccountRangeResult, error) - StorageRangeAt(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - address common.Address, - begin *math.HexOrDecimal256, maxResults uint64, - ) (StorageRangeResult, error) -} - -type RetestWeb3API interface { - ClientVersion(ctx context.Context) (string, error) -} - -type RetestethAPI struct { - ethDb ethdb.Database - db state.Database - chainConfig *params.ChainConfig - author common.Address - extraData []byte - genesisHash common.Hash - engine *NoRewardEngine - blockchain *core.BlockChain - txMap map[common.Address]map[uint64]*types.Transaction // Sender -> Nonce -> Transaction - txSenders map[common.Address]struct{} // Set of transaction senders - blockInterval uint64 -} - -type ChainParams struct { - SealEngine string `json:"sealEngine"` - Params CParamsParams `json:"params"` - Genesis CParamsGenesis `json:"genesis"` - Accounts map[common.Address]CParamsAccount `json:"accounts"` -} - -type CParamsParams struct { - AccountStartNonce math.HexOrDecimal64 `json:"accountStartNonce"` - HomesteadForkBlock *math.HexOrDecimal64 `json:"homesteadForkBlock"` - EIP150ForkBlock *math.HexOrDecimal64 `json:"EIP150ForkBlock"` - EIP158ForkBlock *math.HexOrDecimal64 `json:"EIP158ForkBlock"` - DaoHardforkBlock *math.HexOrDecimal64 `json:"daoHardforkBlock"` - ByzantiumForkBlock *math.HexOrDecimal64 `json:"byzantiumForkBlock"` - ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"` - ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"` - IstanbulBlock *math.HexOrDecimal64 `json:"istanbulForkBlock"` - RamanujanForkBlock *math.HexOrDecimal64 `json:"ramanujanForkBlock"` - MirrorSyncForkBlock *math.HexOrDecimal64 `json:"mirrorSyncForkBlock"` - ChainID *math.HexOrDecimal256 `json:"chainID"` - MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"` - TieBreakingGas bool `json:"tieBreakingGas"` - MinGasLimit math.HexOrDecimal64 `json:"minGasLimit"` - MaxGasLimit math.HexOrDecimal64 `json:"maxGasLimit"` - GasLimitBoundDivisor math.HexOrDecimal64 `json:"gasLimitBoundDivisor"` - MinimumDifficulty math.HexOrDecimal256 `json:"minimumDifficulty"` - DifficultyBoundDivisor math.HexOrDecimal256 `json:"difficultyBoundDivisor"` - DurationLimit math.HexOrDecimal256 `json:"durationLimit"` - BlockReward math.HexOrDecimal256 `json:"blockReward"` - NetworkID math.HexOrDecimal256 `json:"networkID"` -} - -type CParamsGenesis struct { - Nonce math.HexOrDecimal64 `json:"nonce"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - MixHash *math.HexOrDecimal256 `json:"mixHash"` - Author common.Address `json:"author"` - Timestamp math.HexOrDecimal64 `json:"timestamp"` - ParentHash common.Hash `json:"parentHash"` - ExtraData hexutil.Bytes `json:"extraData"` - GasLimit math.HexOrDecimal64 `json:"gasLimit"` -} - -type CParamsAccount struct { - Balance *math.HexOrDecimal256 `json:"balance"` - Precompiled *CPAccountPrecompiled `json:"precompiled"` - Code hexutil.Bytes `json:"code"` - Storage map[string]string `json:"storage"` - Nonce *math.HexOrDecimal64 `json:"nonce"` -} - -type CPAccountPrecompiled struct { - Name string `json:"name"` - StartingBlock math.HexOrDecimal64 `json:"startingBlock"` - Linear *CPAPrecompiledLinear `json:"linear"` -} - -type CPAPrecompiledLinear struct { - Base uint64 `json:"base"` - Word uint64 `json:"word"` -} - -type AccountRangeResult struct { - AddressMap map[common.Hash]common.Address `json:"addressMap"` - NextKey common.Hash `json:"nextKey"` -} - -type StorageRangeResult struct { - Complete bool `json:"complete"` - Storage map[common.Hash]SRItem `json:"storage"` -} - -type SRItem struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type NoRewardEngine struct { - inner consensus.Engine - rewardsOn bool -} - -func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) { - return e.inner.Author(header) -} - -func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error { - return e.inner.VerifyHeader(chain, header, seal) -} - -func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { - return e.inner.VerifyHeaders(chain, headers, seals) -} - -func (e *NoRewardEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { - return e.inner.VerifyUncles(chain, block) -} - -func (e *NoRewardEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error { - return e.inner.VerifySeal(chain, header) -} - -func (e *NoRewardEngine) Prepare(chain consensus.ChainReader, header *types.Header) error { - return e.inner.Prepare(chain, header) -} - -func (e *NoRewardEngine) accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { - // Simply touch miner and uncle coinbase accounts - reward := big.NewInt(0) - for _, uncle := range uncles { - state.AddBalance(uncle.Coinbase, reward) - } - state.AddBalance(header.Coinbase, reward) -} - -func (e *NoRewardEngine) Finalize(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs *[]*types.Transaction, - uncles []*types.Header, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, usedGas *uint64) (err error) { - if e.rewardsOn { - err = e.inner.Finalize(chain, header, statedb, txs, uncles, receipts, systemTxs, usedGas) - } else { - e.accumulateRewards(chain.Config(), statedb, header, uncles) - header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - } - return -} - -func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction, - uncles []*types.Header, receipts []*types.Receipt) (*types.Block, []*types.Receipt, error) { - if e.rewardsOn { - return e.inner.FinalizeAndAssemble(chain, header, statedb, txs, uncles, receipts) - } else { - e.accumulateRewards(chain.Config(), statedb, header, uncles) - header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - - // Header seems complete, assemble into a block and return - return types.NewBlock(header, txs, uncles, receipts), receipts, nil - } -} - -func (e *NoRewardEngine) Delay(_ consensus.ChainReader, _ *types.Header) *time.Duration { - return nil -} - -func (e *NoRewardEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { - return e.inner.Seal(chain, block, results, stop) -} - -func (e *NoRewardEngine) SealHash(header *types.Header) common.Hash { - return e.inner.SealHash(header) -} - -func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { - return e.inner.CalcDifficulty(chain, time, parent) -} - -func (e *NoRewardEngine) APIs(chain consensus.ChainReader) []rpc.API { - return e.inner.APIs(chain) -} - -func (e *NoRewardEngine) Close() error { - return e.inner.Close() -} - -func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) { - // Clean up - if api.blockchain != nil { - api.blockchain.Stop() - } - if api.engine != nil { - api.engine.Close() - } - if api.ethDb != nil { - api.ethDb.Close() - } - ethDb := rawdb.NewMemoryDatabase() - accounts := make(core.GenesisAlloc) - for address, account := range chainParams.Accounts { - balance := big.NewInt(0) - if account.Balance != nil { - balance.Set((*big.Int)(account.Balance)) - } - var nonce uint64 - if account.Nonce != nil { - nonce = uint64(*account.Nonce) - } - if account.Precompiled == nil || account.Balance != nil { - storage := make(map[common.Hash]common.Hash) - for k, v := range account.Storage { - storage[common.HexToHash(k)] = common.HexToHash(v) - } - accounts[address] = core.GenesisAccount{ - Balance: balance, - Code: account.Code, - Nonce: nonce, - Storage: storage, - } - } - } - chainId := big.NewInt(1) - if chainParams.Params.ChainID != nil { - chainId.Set((*big.Int)(chainParams.Params.ChainID)) - } - var ( - homesteadBlock *big.Int - daoForkBlock *big.Int - eip150Block *big.Int - eip155Block *big.Int - eip158Block *big.Int - byzantiumBlock *big.Int - constantinopleBlock *big.Int - petersburgBlock *big.Int - istanbulBlock *big.Int - ramanujanBlock *big.Int - mirrorSyncBlock *big.Int - ) - if chainParams.Params.HomesteadForkBlock != nil { - homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock)) - } - if chainParams.Params.DaoHardforkBlock != nil { - daoForkBlock = big.NewInt(int64(*chainParams.Params.DaoHardforkBlock)) - } - if chainParams.Params.EIP150ForkBlock != nil { - eip150Block = big.NewInt(int64(*chainParams.Params.EIP150ForkBlock)) - } - if chainParams.Params.EIP158ForkBlock != nil { - eip158Block = big.NewInt(int64(*chainParams.Params.EIP158ForkBlock)) - eip155Block = eip158Block - } - if chainParams.Params.ByzantiumForkBlock != nil { - byzantiumBlock = big.NewInt(int64(*chainParams.Params.ByzantiumForkBlock)) - } - if chainParams.Params.ConstantinopleForkBlock != nil { - constantinopleBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleForkBlock)) - } - if chainParams.Params.ConstantinopleFixForkBlock != nil { - petersburgBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleFixForkBlock)) - } - if constantinopleBlock != nil && petersburgBlock == nil { - petersburgBlock = big.NewInt(100000000000) - } - if chainParams.Params.IstanbulBlock != nil { - istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock)) - } - if chainParams.Params.RamanujanForkBlock != nil { - ramanujanBlock = big.NewInt(int64(*chainParams.Params.RamanujanForkBlock)) - } - if chainParams.Params.MirrorSyncForkBlock != nil { - mirrorSyncBlock = big.NewInt(int64(*chainParams.Params.MirrorSyncForkBlock)) - } - - genesis := &core.Genesis{ - Config: ¶ms.ChainConfig{ - ChainID: chainId, - HomesteadBlock: homesteadBlock, - DAOForkBlock: daoForkBlock, - DAOForkSupport: true, - EIP150Block: eip150Block, - EIP155Block: eip155Block, - EIP158Block: eip158Block, - ByzantiumBlock: byzantiumBlock, - ConstantinopleBlock: constantinopleBlock, - PetersburgBlock: petersburgBlock, - IstanbulBlock: istanbulBlock, - RamanujanBlock: ramanujanBlock, - MirrorSyncBlock: mirrorSyncBlock, - }, - Nonce: uint64(chainParams.Genesis.Nonce), - Timestamp: uint64(chainParams.Genesis.Timestamp), - ExtraData: chainParams.Genesis.ExtraData, - GasLimit: uint64(chainParams.Genesis.GasLimit), - Difficulty: big.NewInt(0).Set((*big.Int)(chainParams.Genesis.Difficulty)), - Mixhash: common.BigToHash((*big.Int)(chainParams.Genesis.MixHash)), - Coinbase: chainParams.Genesis.Author, - ParentHash: chainParams.Genesis.ParentHash, - Alloc: accounts, - } - chainConfig, genesisHash, err := core.SetupGenesisBlock(ethDb, genesis) - if err != nil { - return false, err - } - fmt.Printf("Chain config: %v\n", chainConfig) - - var inner consensus.Engine - switch chainParams.SealEngine { - case "NoProof", "NoReward": - inner = ethash.NewFaker() - case "Ethash": - inner = ethash.New(ethash.Config{ - CacheDir: "ethash", - CachesInMem: 2, - CachesOnDisk: 3, - CachesLockMmap: false, - DatasetsInMem: 1, - DatasetsOnDisk: 2, - DatasetsLockMmap: false, - }, nil, false) - default: - return false, fmt.Errorf("unrecognised seal engine: %s", chainParams.SealEngine) - } - engine := &NoRewardEngine{inner: inner, rewardsOn: chainParams.SealEngine != "NoReward"} - - blockchain, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil) - if err != nil { - return false, err - } - - api.chainConfig = chainConfig - api.genesisHash = genesisHash - api.author = chainParams.Genesis.Author - api.extraData = chainParams.Genesis.ExtraData - api.ethDb = ethDb - api.engine = engine - api.blockchain = blockchain - api.db = state.NewDatabase(api.ethDb) - api.txMap = make(map[common.Address]map[uint64]*types.Transaction) - api.txSenders = make(map[common.Address]struct{}) - api.blockInterval = 0 - return true, nil -} - -func (api *RetestethAPI) SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) { - tx := new(types.Transaction) - if err := rlp.DecodeBytes(rawTx, tx); err != nil { - // Return nil is not by mistake - some tests include sending transaction where gasLimit overflows uint64 - return common.Hash{}, nil - } - signer := types.MakeSigner(api.chainConfig, big.NewInt(int64(api.currentNumber()))) - sender, err := types.Sender(signer, tx) - if err != nil { - return common.Hash{}, err - } - if nonceMap, ok := api.txMap[sender]; ok { - nonceMap[tx.Nonce()] = tx - } else { - nonceMap = make(map[uint64]*types.Transaction) - nonceMap[tx.Nonce()] = tx - api.txMap[sender] = nonceMap - } - api.txSenders[sender] = struct{}{} - return tx.Hash(), nil -} - -func (api *RetestethAPI) MineBlocks(ctx context.Context, number uint64) (bool, error) { - for i := 0; i < int(number); i++ { - if err := api.mineBlock(); err != nil { - return false, err - } - } - fmt.Printf("Mined %d blocks\n", number) - return true, nil -} - -func (api *RetestethAPI) currentNumber() uint64 { - if current := api.blockchain.CurrentBlock(); current != nil { - return current.NumberU64() - } - return 0 -} - -func (api *RetestethAPI) mineBlock() error { - number := api.currentNumber() - parentHash := rawdb.ReadCanonicalHash(api.ethDb, number) - parent := rawdb.ReadBlock(api.ethDb, parentHash, number) - var timestamp uint64 - if api.blockInterval == 0 { - timestamp = uint64(time.Now().Unix()) - } else { - timestamp = parent.Time() + api.blockInterval - } - gasLimit := core.CalcGasLimit(parent, 9223372036854775807, 9223372036854775807) - header := &types.Header{ - ParentHash: parent.Hash(), - Number: big.NewInt(int64(number + 1)), - GasLimit: gasLimit, - Extra: api.extraData, - Time: timestamp, - } - header.Coinbase = api.author - if api.engine != nil { - api.engine.Prepare(api.blockchain, header) - } - // If we are care about TheDAO hard-fork check whether to override the extra-data or not - if daoBlock := api.chainConfig.DAOForkBlock; daoBlock != nil { - // Check whether the block is among the fork extra-override range - limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) - if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { - // Depending whether we support or oppose the fork, override differently - if api.chainConfig.DAOForkSupport { - header.Extra = common.CopyBytes(params.DAOForkBlockExtra) - } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { - header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data - } - } - } - statedb, err := api.blockchain.StateAt(parent.Root()) - if err != nil { - return err - } - if api.chainConfig.DAOForkSupport && api.chainConfig.DAOForkBlock != nil && api.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 { - misc.ApplyDAOHardFork(statedb) - } - systemcontracts.UpgradeBuildInSystemContract(api.chainConfig, header.Number, statedb) - gasPool := new(core.GasPool).AddGas(header.GasLimit) - txCount := 0 - var txs []*types.Transaction - var receipts []*types.Receipt - var blockFull = gasPool.Gas() < params.TxGas - for address := range api.txSenders { - if blockFull { - break - } - m := api.txMap[address] - for nonce := statedb.GetNonce(address); ; nonce++ { - if tx, ok := m[nonce]; ok { - // Try to apply transactions to the state - statedb.Prepare(tx.Hash(), common.Hash{}, txCount) - snap := statedb.Snapshot() - - receipt, err := core.ApplyTransaction( - api.chainConfig, - api.blockchain, - &api.author, - gasPool, - statedb, - header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(), - ) - if err != nil { - statedb.RevertToSnapshot(snap) - break - } - txs = append(txs, tx) - receipts = append(receipts, receipt) - delete(m, nonce) - if len(m) == 0 { - // Last tx for the sender - delete(api.txMap, address) - delete(api.txSenders, address) - } - txCount++ - if gasPool.Gas() < params.TxGas { - blockFull = true - break - } - } else { - break // Gap in the nonces - } - } - } - block, _, err := api.engine.FinalizeAndAssemble(api.blockchain, header, statedb, txs, []*types.Header{}, receipts) - if err != nil { - return err - } - return api.importBlock(block) -} - -func (api *RetestethAPI) importBlock(block *types.Block) error { - if _, err := api.blockchain.InsertChain([]*types.Block{block}); err != nil { - return err - } - fmt.Printf("Imported block %d, head is %d\n", block.NumberU64(), api.currentNumber()) - return nil -} - -func (api *RetestethAPI) ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) { - api.blockInterval = interval - return true, nil -} - -func (api *RetestethAPI) ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) { - block := new(types.Block) - if err := rlp.DecodeBytes(rawBlock, block); err != nil { - return common.Hash{}, err - } - fmt.Printf("Importing block %d with parent hash: %x, genesisHash: %x\n", block.NumberU64(), block.ParentHash(), api.genesisHash) - if err := api.importBlock(block); err != nil { - return common.Hash{}, err - } - return block.Hash(), nil -} - -func (api *RetestethAPI) RewindToBlock(ctx context.Context, newHead uint64) (bool, error) { - if err := api.blockchain.SetHead(newHead); err != nil { - return false, err - } - // When we rewind, the transaction pool should be cleaned out. - api.txMap = make(map[common.Address]map[uint64]*types.Transaction) - api.txSenders = make(map[common.Address]struct{}) - return true, nil -} - -var emptyListHash common.Hash = common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") - -func (api *RetestethAPI) GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) { - receipt, _, _, _ := rawdb.ReadReceipt(api.ethDb, txHash, api.chainConfig) - if receipt == nil { - return emptyListHash, nil - } else { - if logListRlp, err := rlp.EncodeToBytes(receipt.Logs); err != nil { - return common.Hash{}, err - } else { - return common.BytesToHash(crypto.Keccak256(logListRlp)), nil - } - } -} - -func (api *RetestethAPI) BlockNumber(ctx context.Context) (uint64, error) { - return api.currentNumber(), nil -} - -func (api *RetestethAPI) GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) { - block := api.blockchain.GetBlockByNumber(uint64(blockNr)) - if block != nil { - response, err := RPCMarshalBlock(block, true, fullTx) - if err != nil { - return nil, err - } - response["author"] = response["miner"] - response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), uint64(blockNr))) - return response, err - } - return nil, fmt.Errorf("block %d not found", blockNr) -} - -func (api *RetestethAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (map[string]interface{}, error) { - block := api.blockchain.GetBlockByHash(blockHash) - if block != nil { - response, err := RPCMarshalBlock(block, true, fullTx) - if err != nil { - return nil, err - } - response["author"] = response["miner"] - response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), block.Number().Uint64())) - return response, err - } - return nil, fmt.Errorf("block 0x%x not found", blockHash) -} - -func (api *RetestethAPI) AccountRange(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - addressHash *math.HexOrDecimal256, maxResults uint64, -) (AccountRangeResult, error) { - var ( - header *types.Header - block *types.Block - ) - if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 { - blockHash := common.BigToHash((*big.Int)(blockHashOrNumber)) - header = api.blockchain.GetHeaderByHash(blockHash) - block = api.blockchain.GetBlockByHash(blockHash) - //fmt.Printf("Account range: %x, txIndex %d, start: %x, maxResults: %d\n", blockHash, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults) - } else { - blockNumber := (*big.Int)(blockHashOrNumber).Uint64() - header = api.blockchain.GetHeaderByNumber(blockNumber) - block = api.blockchain.GetBlockByNumber(blockNumber) - //fmt.Printf("Account range: %d, txIndex %d, start: %x, maxResults: %d\n", blockNumber, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults) - } - parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var root common.Hash - var statedb *state.StateDB - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - root = header.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return AccountRangeResult{}, err - } - } else { - root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return AccountRangeResult{}, err - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.blockchain.Config(), block.Number()) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number())) - if idx == int(txIndex) { - // This is to make sure root can be opened by OpenTrie - root, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number())) - if err != nil { - return AccountRangeResult{}, err - } - break - } - } - } - accountTrie, err := statedb.Database().OpenTrie(root) - if err != nil { - return AccountRangeResult{}, err - } - it := trie.NewIterator(accountTrie.NodeIterator(common.BigToHash((*big.Int)(addressHash)).Bytes())) - result := AccountRangeResult{AddressMap: make(map[common.Hash]common.Address)} - for i := 0; i < int(maxResults) && it.Next(); i++ { - if preimage := accountTrie.GetKey(it.Key); preimage != nil { - result.AddressMap[common.BytesToHash(it.Key)] = common.BytesToAddress(preimage) - } - } - //fmt.Printf("Number of entries returned: %d\n", len(result.AddressMap)) - // Add the 'next key' so clients can continue downloading. - if it.Next() { - next := common.BytesToHash(it.Key) - result.NextKey = next - } - return result, nil -} - -func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) { - //fmt.Printf("GetBalance %x, block %d\n", address, blockNr) - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - return (*math.HexOrDecimal256)(statedb.GetBalance(address)), nil -} - -func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) { - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return nil, err - } - return statedb.GetCode(address), nil -} - -func (api *RetestethAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) { - header := api.blockchain.GetHeaderByNumber(uint64(blockNr)) - statedb, err := api.blockchain.StateAt(header.Root) - if err != nil { - return 0, err - } - return statedb.GetNonce(address), nil -} - -func (api *RetestethAPI) StorageRangeAt(ctx context.Context, - blockHashOrNumber *math.HexOrDecimal256, txIndex uint64, - address common.Address, - begin *math.HexOrDecimal256, maxResults uint64, -) (StorageRangeResult, error) { - var ( - header *types.Header - block *types.Block - ) - if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 { - blockHash := common.BigToHash((*big.Int)(blockHashOrNumber)) - header = api.blockchain.GetHeaderByHash(blockHash) - block = api.blockchain.GetBlockByHash(blockHash) - //fmt.Printf("Storage range: %x, txIndex %d, addr: %x, start: %x, maxResults: %d\n", - // blockHash, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults) - } else { - blockNumber := (*big.Int)(blockHashOrNumber).Uint64() - header = api.blockchain.GetHeaderByNumber(blockNumber) - block = api.blockchain.GetBlockByNumber(blockNumber) - //fmt.Printf("Storage range: %d, txIndex %d, addr: %x, start: %x, maxResults: %d\n", - // blockNumber, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults) - } - parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash) - var root common.Hash - var statedb *state.StateDB - var err error - if parentHeader == nil || int(txIndex) >= len(block.Transactions()) { - root = header.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return StorageRangeResult{}, err - } - } else { - root = parentHeader.Root - statedb, err = api.blockchain.StateAt(root) - if err != nil { - return StorageRangeResult{}, err - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(api.blockchain.Config(), block.Number()) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := tx.AsMessage(signer) - context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - _ = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number())) - if idx == int(txIndex) { - // This is to make sure root can be opened by OpenTrie - _, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number())) - if err != nil { - return StorageRangeResult{}, err - } - } - } - } - storageTrie := statedb.StorageTrie(address) - it := trie.NewIterator(storageTrie.NodeIterator(common.BigToHash((*big.Int)(begin)).Bytes())) - result := StorageRangeResult{Storage: make(map[common.Hash]SRItem)} - for i := 0; /*i < int(maxResults) && */ it.Next(); i++ { - if preimage := storageTrie.GetKey(it.Key); preimage != nil { - key := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(preimage)) - v, _, err := rlp.SplitString(it.Value) - if err != nil { - return StorageRangeResult{}, err - } - value := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(v)) - ks, _ := key.MarshalText() - vs, _ := value.MarshalText() - if len(ks)%2 != 0 { - ks = append(append(append([]byte{}, ks[:2]...), byte('0')), ks[2:]...) - } - if len(vs)%2 != 0 { - vs = append(append(append([]byte{}, vs[:2]...), byte('0')), vs[2:]...) - } - result.Storage[common.BytesToHash(it.Key)] = SRItem{ - Key: string(ks), - Value: string(vs), - } - } - } - if it.Next() { - result.Complete = false - } else { - result.Complete = true - } - return result, nil -} - -func (api *RetestethAPI) ClientVersion(ctx context.Context) (string, error) { - return "Geth-" + params.VersionWithCommit(gitCommit, gitDate), nil -} - -// splitAndTrim splits input separated by a comma -// and trims excessive white space from the substrings. -func splitAndTrim(input string) []string { - result := strings.Split(input, ",") - for i, r := range result { - result[i] = strings.TrimSpace(r) - } - return result -} - -func retesteth(ctx *cli.Context) error { - log.Info("Welcome to retesteth!") - // register signer API with server - var ( - extapiURL string - ) - apiImpl := &RetestethAPI{} - var testApi RetestethTestAPI = apiImpl - var ethApi RetestethEthAPI = apiImpl - var debugApi RetestethDebugAPI = apiImpl - var web3Api RetestWeb3API = apiImpl - rpcAPI := []rpc.API{ - { - Namespace: "test", - Public: true, - Service: testApi, - Version: "1.0", - }, - { - Namespace: "eth", - Public: true, - Service: ethApi, - Version: "1.0", - }, - { - Namespace: "debug", - Public: true, - Service: debugApi, - Version: "1.0", - }, - { - Namespace: "web3", - Public: true, - Service: web3Api, - Version: "1.0", - }, - } - vhosts := splitAndTrim(ctx.GlobalString(utils.RPCVirtualHostsFlag.Name)) - cors := splitAndTrim(ctx.GlobalString(utils.RPCCORSDomainFlag.Name)) - - // register apis and create handler stack - srv := rpc.NewServer() - err := node.RegisterApisFromWhitelist(rpcAPI, []string{"test", "eth", "debug", "web3"}, srv, false) - if err != nil { - utils.Fatalf("Could not register RPC apis: %w", err) - } - handler := node.NewHTTPHandlerStack(srv, cors, vhosts) - - // start http server - var RetestethHTTPTimeouts = rpc.HTTPTimeouts{ - ReadTimeout: 120 * time.Second, - WriteTimeout: 120 * time.Second, - IdleTimeout: 120 * time.Second, - } - httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.RPCListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name)) - listener, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler) - if err != nil { - utils.Fatalf("Could not start RPC api: %v", err) - } - extapiURL = fmt.Sprintf("http://%s", httpEndpoint) - log.Info("HTTP endpoint opened", "url", extapiURL) - - defer func() { - listener.Close() - log.Info("HTTP endpoint closed", "url", httpEndpoint) - }() - - abortChan := make(chan os.Signal, 11) - signal.Notify(abortChan, os.Interrupt) - - sig := <-abortChan - log.Info("Exiting...", "signal", sig) - return nil -} diff --git a/cmd/geth/retesteth_copypaste.go b/cmd/geth/retesteth_copypaste.go deleted file mode 100644 index e2795af7f9..0000000000 --- a/cmd/geth/retesteth_copypaste.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2019 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 . - -package main - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" -) - -// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction -type RPCTransaction struct { - BlockHash common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` -} - -// newRPCTransaction returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - v, r, s := tx.RawSignatureValues() - - result := &RPCTransaction{ - From: from, - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), - Nonce: hexutil.Uint64(tx.Nonce()), - To: tx.To(), - Value: (*hexutil.Big)(tx.Value()), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - } - if blockHash != (common.Hash{}) { - result.BlockHash = blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = hexutil.Uint(index) - } - return result -} - -// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { - txs := b.Transactions() - if index >= uint64(len(txs)) { - return nil - } - return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) -} - -// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { - for idx, tx := range b.Transactions() { - if tx.Hash() == hash { - return newRPCTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are -// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain -// transaction hashes. -func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { - head := b.Header() // copies the header once - fields := map[string]interface{}{ - "number": (*hexutil.Big)(head.Number), - "hash": b.Hash(), - "parentHash": head.ParentHash, - "nonce": head.Nonce, - "mixHash": head.MixDigest, - "sha3Uncles": head.UncleHash, - "logsBloom": head.Bloom, - "stateRoot": head.Root, - "miner": head.Coinbase, - "difficulty": (*hexutil.Big)(head.Difficulty), - "extraData": hexutil.Bytes(head.Extra), - "size": hexutil.Uint64(b.Size()), - "gasLimit": hexutil.Uint64(head.GasLimit), - "gasUsed": hexutil.Uint64(head.GasUsed), - "timestamp": hexutil.Uint64(head.Time), - "transactionsRoot": head.TxHash, - "receiptsRoot": head.ReceiptHash, - } - - if inclTx { - formatTx := func(tx *types.Transaction) (interface{}, error) { - return tx.Hash(), nil - } - if fullTx { - formatTx = func(tx *types.Transaction) (interface{}, error) { - return newRPCTransactionFromBlockHash(b, tx.Hash()), nil - } - } - txs := b.Transactions() - transactions := make([]interface{}, len(txs)) - var err error - for i, tx := range txs { - if transactions[i], err = formatTx(tx); err != nil { - return nil, err - } - } - fields["transactions"] = transactions - } - - uncles := b.Uncles() - uncleHashes := make([]common.Hash, len(uncles)) - for i, uncle := range uncles { - uncleHashes[i] = uncle.Hash() - } - fields["uncles"] = uncleHashes - - return fields, nil -} diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index f7b735b84c..527c38a657 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -70,12 +70,12 @@ func runGeth(t *testing.T, args ...string) *testgeth { tt := &testgeth{} tt.TestCmd = cmdtest.NewTestCmd(t, tt) for i, arg := range args { - switch { - case arg == "-datadir" || arg == "--datadir": + switch arg { + case "--datadir": if i < len(args)-1 { tt.Datadir = args[i+1] } - case arg == "-etherbase" || arg == "--etherbase": + case "--miner.etherbase": if i < len(args)-1 { tt.Etherbase = args[i+1] } @@ -84,7 +84,7 @@ func runGeth(t *testing.T, args ...string) *testgeth { if tt.Datadir == "" { tt.Datadir = tmpdir(t) tt.Cleanup = func() { os.RemoveAll(tt.Datadir) } - args = append([]string{"-datadir", tt.Datadir}, args...) + args = append([]string{"--datadir", tt.Datadir}, args...) // Remove the temporary datadir if something fails below. defer func() { if t.Failed() { diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go new file mode 100644 index 0000000000..1af458af20 --- /dev/null +++ b/cmd/geth/snapshot.go @@ -0,0 +1,432 @@ +// 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 . + +package main + +import ( + "bytes" + "errors" + "time" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/pruner" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + cli "gopkg.in/urfave/cli.v1" +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256(nil) +) + +var ( + snapshotCommand = cli.Command{ + Name: "snapshot", + Usage: "A set of commands based on the snapshot", + Category: "MISCELLANEOUS COMMANDS", + Description: "", + Subcommands: []cli.Command{ + { + Name: "prune-state", + Usage: "Prune stale ethereum state data based on the snapshot", + ArgsUsage: "", + Action: utils.MigrateFlags(pruneState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + utils.CacheTrieJournalFlag, + utils.BloomFilterSizeFlag, + }, + Description: ` +geth snapshot prune-state +will prune historical state data with the help of the state snapshot. +All trie nodes and contract codes that do not belong to the specified +version state will be deleted from the database. After pruning, only +two version states are available: genesis and the specific one. + +The default pruning target is the HEAD-127 state. + +WARNING: It's necessary to delete the trie clean cache after the pruning. +If you specify another directory for the trie clean cache via "--cache.trie.journal" +during the use of Geth, please also specify it here for correct deletion. Otherwise +the trie clean cache with default directory will be deleted. +`, + }, + { + Name: "verify-state", + Usage: "Recalculate state hash based on the snapshot for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(verifyState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + }, + Description: ` +geth snapshot verify-state +will traverse the whole accounts and storages set based on the specified +snapshot and recalculate the root hash of state for verification. +In other words, this command does the snapshot to trie conversion. +`, + }, + { + Name: "traverse-state", + Usage: "Traverse the state with given root hash for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(traverseState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + }, + Description: ` +geth snapshot traverse-state +will traverse the whole state from the given state root and will abort if any +referenced trie node or contract code is missing. This command can be used for +state integrity verification. The default checking target is the HEAD state. + +It's also usable without snapshot enabled. +`, + }, + { + Name: "traverse-rawstate", + Usage: "Traverse the state with given root hash for verification", + ArgsUsage: "", + Action: utils.MigrateFlags(traverseRawState), + Category: "MISCELLANEOUS COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.AncientFlag, + utils.RopstenFlag, + utils.RinkebyFlag, + utils.GoerliFlag, + }, + Description: ` +geth snapshot traverse-rawstate +will traverse the whole state from the given root and will abort if any referenced +trie node or contract code is missing. This command can be used for state integrity +verification. The default checking target is the HEAD state. It's basically identical +to traverse-state, but the check granularity is smaller. + +It's also usable without snapshot enabled. +`, + }, + }, + } +) + +func pruneState(ctx *cli.Context) error { + stack, config := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, false) + pruner, err := pruner.NewPruner(chaindb, stack.ResolvePath(""), stack.ResolvePath(config.Eth.TrieCleanCacheJournal), ctx.GlobalUint64(utils.BloomFilterSizeFlag.Name)) + if err != nil { + log.Error("Failed to open snapshot tree", "err", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var targetRoot common.Hash + if ctx.NArg() == 1 { + targetRoot, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + } + if err = pruner.Prune(targetRoot); err != nil { + log.Error("Failed to prune state", "err", err) + return err + } + return nil +} + +func verifyState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false) + if err != nil { + log.Error("Failed to open snapshot tree", "err", err) + return err + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var root = headBlock.Root() + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + } + if err := snaptree.Verify(root); err != nil { + log.Error("Failed to verfiy state", "root", root, "err", err) + return err + } + log.Info("Verified the state", "root", root) + return nil +} + +// traverseState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. +func traverseState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = headBlock.Root() + log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) + } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewSecure(root, triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "err", err) + return err + } + var ( + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + accIter := trie.NewIterator(t.NodeIterator(nil)) + for accIter.Next() { + accounts += 1 + var acc state.Account + if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil { + log.Error("Invalid account encountered during traversal", "err", err) + return err + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "err", err) + return err + } + storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) + for storageIter.Next() { + slots += 1 + } + if storageIter.Err != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) + return storageIter.Err + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) + if len(code) == 0 { + log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash)) + return errors.New("missing code") + } + codes += 1 + } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + if accIter.Err != nil { + log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err) + return accIter.Err + } + log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// traverseRawState is a helper function used for pruning verification. +// Basically it just iterates the trie, ensure all nodes and associated +// contract codes are present. It's basically identical to traverseState +// but it will check each trie node. +func traverseRawState(ctx *cli.Context) error { + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, true) + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + if ctx.NArg() > 1 { + log.Error("Too many arguments given") + return errors.New("too many arguments") + } + var ( + root common.Hash + err error + ) + if ctx.NArg() == 1 { + root, err = parseRoot(ctx.Args()[0]) + if err != nil { + log.Error("Failed to resolve state root", "err", err) + return err + } + log.Info("Start traversing the state", "root", root) + } else { + root = headBlock.Root() + log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) + } + triedb := trie.NewDatabase(chaindb) + t, err := trie.NewSecure(root, triedb) + if err != nil { + log.Error("Failed to open trie", "root", root, "err", err) + return err + } + var ( + nodes int + accounts int + slots int + codes int + lastReport time.Time + start = time.Now() + ) + accIter := t.NodeIterator(nil) + for accIter.Next(true) { + nodes += 1 + node := accIter.Hash() + + if node != (common.Hash{}) { + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + blob := rawdb.ReadTrieNode(chaindb, node) + if len(blob) == 0 { + log.Error("Missing trie node(account)", "hash", node) + return errors.New("missing account") + } + } + // If it's a leaf node, yes we are touching an account, + // dig into the storage trie further. + if accIter.Leaf() { + accounts += 1 + var acc state.Account + if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil { + log.Error("Invalid account encountered during traversal", "err", err) + return errors.New("invalid account") + } + if acc.Root != emptyRoot { + storageTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + log.Error("Failed to open storage trie", "root", acc.Root, "err", err) + return errors.New("missing storage trie") + } + storageIter := storageTrie.NodeIterator(nil) + for storageIter.Next(true) { + nodes += 1 + node := storageIter.Hash() + + // Check the present for non-empty hash node(embedded node doesn't + // have their own hash). + if node != (common.Hash{}) { + blob := rawdb.ReadTrieNode(chaindb, node) + if len(blob) == 0 { + log.Error("Missing trie node(storage)", "hash", node) + return errors.New("missing storage") + } + } + // Bump the counter if it's leaf node. + if storageIter.Leaf() { + slots += 1 + } + } + if storageIter.Error() != nil { + log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) + return storageIter.Error() + } + } + if !bytes.Equal(acc.CodeHash, emptyCode) { + code := rawdb.ReadCode(chaindb, common.BytesToHash(acc.CodeHash)) + if len(code) == 0 { + log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey())) + return errors.New("missing code") + } + codes += 1 + } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } + } + } + if accIter.Error() != nil { + log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error()) + return accIter.Error() + } + log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +func parseRoot(input string) (common.Hash, error) { + var h common.Hash + if err := h.UnmarshalText([]byte(input)); err != nil { + return h, err + } + return h, nil +} diff --git a/cmd/geth/testdata/blockchain.blocks b/cmd/geth/testdata/blockchain.blocks new file mode 100644 index 0000000000..d29453d3e5 Binary files /dev/null and b/cmd/geth/testdata/blockchain.blocks differ diff --git a/cmd/geth/testdata/clique.json b/cmd/geth/testdata/clique.json new file mode 100644 index 0000000000..b54b4a7d3b --- /dev/null +++ b/cmd/geth/testdata/clique.json @@ -0,0 +1,24 @@ +{ + "config": { + "chainId": 15, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "clique": { + "period": 5, + "epoch": 30000 + } + }, + "difficulty": "1", + "gasLimit": "8000000", + "extradata": "0x000000000000000000000000000000000000000000000000000000000000000002f0d131f1f97aef08aec6e3291b957d9efe71050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "alloc": { + "02f0d131f1f97aef08aec6e3291b957d9efe7105": { + "balance": "300000" + } + } +} \ No newline at end of file diff --git a/cmd/geth/testdata/key.prv b/cmd/geth/testdata/key.prv new file mode 100644 index 0000000000..1d2687ea63 --- /dev/null +++ b/cmd/geth/testdata/key.prv @@ -0,0 +1 @@ +48aa455c373ec5ce7fefb0e54f44a215decdc85b9047bc4d09801e038909bdbe \ No newline at end of file diff --git a/cmd/geth/testdata/password.txt b/cmd/geth/testdata/password.txt new file mode 100644 index 0000000000..f6ea049518 --- /dev/null +++ b/cmd/geth/testdata/password.txt @@ -0,0 +1 @@ +foobar \ No newline at end of file diff --git a/cmd/geth/testdata/vcheck/data.json b/cmd/geth/testdata/vcheck/data.json new file mode 100644 index 0000000000..e7ee2bf7e4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/data.json @@ -0,0 +1,61 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.2(1|2|3)-.*" + }, + { + "name": "GoCrash", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552" + ], + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000000..f9066d4fe0 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000000..a89a83d21a --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000000..6fd33b19a3 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/minisign.pub b/cmd/geth/testdata/vcheck/minisign.pub new file mode 100644 index 0000000000..183dce5f6b --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.pub @@ -0,0 +1,2 @@ +untrusted comment: minisign public key 284E00B52C269624 +RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp diff --git a/cmd/geth/testdata/vcheck/minisign.sec b/cmd/geth/testdata/vcheck/minisign.sec new file mode 100644 index 0000000000..5c50715b20 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisign.sec @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0Iyz8kmPMKrqk6DCtlO9a33akKiaOQG1aLolqDxs52qvPoAAAACAAAAAAAAAEAAAAAArEiggdvyn6+WzTprirLtgiYQoU+ihz/HyGgjhuF+Pz2ddMduyCO+xjCHeq+vgVVW039fbsI8hW6LRGJZLBKV5/jdxCXAVVQE7qTQ6xpEdO0z8Z731/pV1hlspQXG2PNd16NMtwd9dWw= diff --git a/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig new file mode 100644 index 0000000000..3d5fcacf9a --- /dev/null +++ b/cmd/geth/testdata/vcheck/signify-sigs/data.json.sig @@ -0,0 +1,2 @@ +untrusted comment: verify with ./signifykey.pub +RWSKLNhZb0KdAbhRUhW2LQZXdnwttu2SYhM9EuC4mMgOJB85h7/YIPupf8/ldTs4N8e9Y/fhgdY40q5LQpt5IFC62fq0v8U1/w8= diff --git a/cmd/geth/testdata/vcheck/signifykey.pub b/cmd/geth/testdata/vcheck/signifykey.pub new file mode 100644 index 0000000000..328f973ab4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/ diff --git a/cmd/geth/testdata/vcheck/signifykey.sec b/cmd/geth/testdata/vcheck/signifykey.sec new file mode 100644 index 0000000000..3279a2e58b --- /dev/null +++ b/cmd/geth/testdata/vcheck/signifykey.sec @@ -0,0 +1,2 @@ +untrusted comment: signify secret key +RWRCSwAAACpLQDLawSQCtI7eAVIvaiHzjTsTyJsfV5aKLNhZb0KdAWeICXJGa93/bHAcsY6jUh9I8RdEcDWEoGxmaXZC+IdVBPxDpkix9fBRGEUdKWHi3dOfqME0YRzErWI5AVg3cRw= diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 new file mode 100644 index 0000000000..f9066d4fe0 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.1 @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: timestamp:1605618622 file:vulnerabilities.json +osAPs4QPdDkmiWQxqeMIzYv/b+ZGxJ+19Sbrk1Cpq4t2gHBT+lqFtwL3OCzKWWyjGRTmHfsVGBYpzEdPRQ0/BQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 new file mode 100644 index 0000000000..a89a83d21a --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.2 @@ -0,0 +1,4 @@ +untrusted comment: Here's a comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 new file mode 100644 index 0000000000..6fd33b19a3 --- /dev/null +++ b/cmd/geth/testdata/vcheck/sigs/vulnerabilities.json.minisig.3 @@ -0,0 +1,4 @@ +untrusted comment: One more (untrusted) comment +RWQkliYstQBOKFQFQTjmCd6TPw07VZyWFSB3v4+1BM1kv8eHLE5FDy2OkPEqtdaL53xftlrHoJQie0uCcovdlSV8kpyxiLrxEQ0= +trusted comment: Here's a trusted comment +3CnkIuz9MEDa7uNyGZAbKZhuirwfiqm7E1uQHrd2SiO4Y8+Akw9vs052AyKw0s5nhbYHCZE2IMQdHNjKwxEGAQ== diff --git a/cmd/geth/testdata/vcheck/vulnerabilities.json b/cmd/geth/testdata/vcheck/vulnerabilities.json new file mode 100644 index 0000000000..36509f95a9 --- /dev/null +++ b/cmd/geth/testdata/vcheck/vulnerabilities.json @@ -0,0 +1,70 @@ +[ + { + "name": "CorruptedDAG", + "uid": "GETH-2020-01", + "summary": "Mining nodes will generate erroneous PoW on epochs > `385`.", + "description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.", + "links": [ + "https://github.com/ethereum/go-ethereum/pull/21793", + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p" + ], + "introduced": "v1.6.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Medium", + "CVE": "CVE-2020-26240", + "check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*" + }, + { + "name": "Denial of service due to Go CVE-2020-28362", + "uid": "GETH-2020-02", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`", + "description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM", + "https://github.com/golang/go/issues/42552", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52" + ], + "introduced": "v0.0.0", + "fixed": "v1.9.24", + "published": "2020-11-12", + "severity": "Critical", + "CVE": "CVE-2020-28362", + "check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$" + }, + { + "name": "ShallowCopy", + "uid": "GETH-2020-03", + "summary": "A consensus flaw in Geth, related to `datacopy` precompile", + "description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf" + ], + "introduced": "v1.9.7", + "fixed": "v1.9.17", + "published": "2020-11-12", + "severity": "Critical", + "CVE": "CVE-2020-26241", + "check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$" + }, + { + "name": "GethCrash", + "uid": "GETH-2020-04", + "summary": "A denial-of-service issue can be used to crash Geth nodes during block processing", + "description": "Full details to be disclosed at a later date", + "links": [ + "https://blog.ethereum.org/2020/11/12/geth_security_release/", + "https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m" + ], + "introduced": "v1.9.16", + "fixed": "v1.9.18", + "published": "2020-11-12", + "severity": "Critical", + "CVE": "CVE-2020-26242", + "check": "Geth\\/v1\\.9.(16|17).*$" + } +] diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 1a53e22447..1cc6693ec0 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -24,62 +24,34 @@ import ( "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/internal/debug" - cli "gopkg.in/urfave/cli.v1" + "github.com/ethereum/go-ethereum/internal/flags" + "gopkg.in/urfave/cli.v1" ) -// AppHelpTemplate is the test template for the default, global app help topic. -var AppHelpTemplate = `NAME: - {{.App.Name}} - {{.App.Usage}} - - Copyright 2013-2019 The go-ethereum Authors and BSC Authors - -USAGE: - {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if .App.Version}} -VERSION: - {{.App.Version}} - {{end}}{{if len .App.Authors}} -AUTHOR(S): - {{range .App.Authors}}{{ . }}{{end}} - {{end}}{{if .App.Commands}} -COMMANDS: - {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} - {{end}}{{end}}{{if .FlagGroups}} -{{range .FlagGroups}}{{.Name}} OPTIONS: - {{range .Flags}}{{.}} - {{end}} -{{end}}{{end}}{{if .App.Copyright }} -COPYRIGHT: - {{.App.Copyright}} - {{end}} -` - -// flagGroup is a collection of flags belonging to a single topic. -type flagGroup struct { - Name string - Flags []cli.Flag -} - // AppHelpFlagGroups is the application flags, grouped by functionality. -var AppHelpFlagGroups = []flagGroup{ +var AppHelpFlagGroups = []flags.FlagGroup{ { Name: "ETHEREUM", Flags: []cli.Flag{ configFileFlag, utils.DataDirFlag, utils.AncientFlag, + utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.NoUSBFlag, utils.DirectBroadcastFlag, utils.RangeLimitFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, + utils.MainnetFlag, utils.GoerliFlag, utils.RinkebyFlag, + utils.YoloV3Flag, utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, utils.GCModeFlag, + utils.TxLookupLimitFlag, utils.EthStatsURLFlag, utils.IdentityFlag, utils.LightKDFFlag, @@ -96,6 +68,8 @@ var AppHelpFlagGroups = []flagGroup{ utils.UltraLightServersFlag, utils.UltraLightFractionFlag, utils.UltraLightOnlyAnnounceFlag, + utils.LightNoPruneFlag, + utils.LightNoSyncServeFlag, }, }, { @@ -140,9 +114,12 @@ var AppHelpFlagGroups = []flagGroup{ utils.CacheFlag, utils.CacheDatabaseFlag, utils.CacheTrieFlag, + utils.CacheTrieJournalFlag, + utils.CacheTrieRejournalFlag, utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, + utils.CachePreimagesFlag, }, }, { @@ -159,23 +136,25 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ utils.IPCDisabledFlag, utils.IPCPathFlag, - utils.RPCEnabledFlag, - utils.RPCListenAddrFlag, - utils.RPCPortFlag, - utils.RPCApiFlag, - utils.RPCGlobalGasCap, - utils.RPCCORSDomainFlag, - utils.RPCVirtualHostsFlag, + utils.HTTPEnabledFlag, + utils.HTTPListenAddrFlag, + utils.HTTPPortFlag, + utils.HTTPApiFlag, + utils.HTTPPathPrefixFlag, + utils.HTTPCORSDomainFlag, + utils.HTTPVirtualHostsFlag, utils.WSEnabledFlag, utils.WSListenAddrFlag, utils.WSPortFlag, utils.WSApiFlag, + utils.WSPathPrefixFlag, utils.WSAllowedOriginsFlag, utils.GraphQLEnabledFlag, - utils.GraphQLListenAddrFlag, - utils.GraphQLPortFlag, utils.GraphQLCORSDomainFlag, utils.GraphQLVirtualHostsFlag, + utils.RPCGlobalGasCapFlag, + utils.RPCGlobalTxFeeCapFlag, + utils.AllowUnprotectedTxs, utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag, @@ -185,8 +164,6 @@ var AppHelpFlagGroups = []flagGroup{ Name: "NETWORKING", Flags: []cli.Flag{ utils.BootnodesFlag, - utils.BootnodesV4Flag, - utils.BootnodesV5Flag, utils.DNSDiscoveryFlag, utils.ListenPortFlag, utils.MaxPeersFlag, @@ -205,6 +182,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.MiningEnabledFlag, utils.MinerThreadsFlag, utils.MinerNotifyFlag, + utils.MinerNotifyFullFlag, utils.MinerGasPriceFlag, utils.MinerGasTargetFlag, utils.MinerGasLimitFlag, @@ -220,6 +198,7 @@ var AppHelpFlagGroups = []flagGroup{ Flags: []cli.Flag{ utils.GpoBlocksFlag, utils.GpoPercentileFlag, + utils.GpoMaxGasPriceFlag, }, }, { @@ -242,74 +221,36 @@ var AppHelpFlagGroups = []flagGroup{ Flags: metricsFlags, }, { - Name: "WHISPER (EXPERIMENTAL)", - Flags: whisperFlags, - }, - { - Name: "DEPRECATED", + Name: "ALIASED (deprecated)", Flags: []cli.Flag{ - utils.LegacyTestnetFlag, - utils.LightLegacyServFlag, - utils.LightLegacyPeersFlag, - utils.MinerLegacyThreadsFlag, - utils.MinerLegacyGasTargetFlag, - utils.MinerLegacyGasPriceFlag, - utils.MinerLegacyEtherbaseFlag, - utils.MinerLegacyExtraDataFlag, + utils.NoUSBFlag, + utils.LegacyRPCEnabledFlag, + utils.LegacyRPCListenAddrFlag, + utils.LegacyRPCPortFlag, + utils.LegacyRPCCORSDomainFlag, + utils.LegacyRPCVirtualHostsFlag, + utils.LegacyRPCApiFlag, }, }, { Name: "MISC", + Flags: []cli.Flag{ + utils.SnapshotFlag, + utils.BloomFilterSizeFlag, + cli.HelpFlag, + utils.CatalystFlag, + }, }, } -// byCategory sorts an array of flagGroup by Name in the order -// defined in AppHelpFlagGroups. -type byCategory []flagGroup - -func (a byCategory) Len() int { return len(a) } -func (a byCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byCategory) Less(i, j int) bool { - iCat, jCat := a[i].Name, a[j].Name - iIdx, jIdx := len(AppHelpFlagGroups), len(AppHelpFlagGroups) // ensure non categorized flags come last - - for i, group := range AppHelpFlagGroups { - if iCat == group.Name { - iIdx = i - } - if jCat == group.Name { - jIdx = i - } - } - - return iIdx < jIdx -} - -func flagCategory(flag cli.Flag) string { - for _, category := range AppHelpFlagGroups { - for _, flg := range category.Flags { - if flg.GetName() == flag.GetName() { - return category.Name - } - } - } - return "MISC" -} - func init() { // Override the default app help template - cli.AppHelpTemplate = AppHelpTemplate - - // Define a one shot struct to pass to the usage template - type helpData struct { - App interface{} - FlagGroups []flagGroup - } + cli.AppHelpTemplate = flags.AppHelpTemplate // Override the default app help printer, but only for the global app help originalHelpPrinter := cli.HelpPrinter cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) { - if tmpl == AppHelpTemplate { + if tmpl == flags.AppHelpTemplate { // Iterate over all the flags and add any uncategorized ones categorized := make(map[string]struct{}) for _, group := range AppHelpFlagGroups { @@ -317,10 +258,17 @@ func init() { categorized[flag.String()] = struct{}{} } } + deprecated := make(map[string]struct{}) + for _, flag := range utils.DeprecatedFlags { + deprecated[flag.String()] = struct{}{} + } + // Only add uncategorized flags if they are not deprecated var uncategorized []cli.Flag for _, flag := range data.(*cli.App).Flags { if _, ok := categorized[flag.String()]; !ok { - uncategorized = append(uncategorized, flag) + if _, ok := deprecated[flag.String()]; !ok { + uncategorized = append(uncategorized, flag) + } } } if len(uncategorized) > 0 { @@ -334,22 +282,22 @@ func init() { }() } // Render out custom usage screen - originalHelpPrinter(w, tmpl, helpData{data, AppHelpFlagGroups}) - } else if tmpl == utils.CommandHelpTemplate { + originalHelpPrinter(w, tmpl, flags.HelpData{App: data, FlagGroups: AppHelpFlagGroups}) + } else if tmpl == flags.CommandHelpTemplate { // Iterate over all command specific flags and categorize them categorized := make(map[string][]cli.Flag) for _, flag := range data.(cli.Command).Flags { if _, ok := categorized[flag.String()]; !ok { - categorized[flagCategory(flag)] = append(categorized[flagCategory(flag)], flag) + categorized[flags.FlagCategory(flag, AppHelpFlagGroups)] = append(categorized[flags.FlagCategory(flag, AppHelpFlagGroups)], flag) } } // sort to get a stable ordering - sorted := make([]flagGroup, 0, len(categorized)) + sorted := make([]flags.FlagGroup, 0, len(categorized)) for cat, flgs := range categorized { - sorted = append(sorted, flagGroup{cat, flgs}) + sorted = append(sorted, flags.FlagGroup{Name: cat, Flags: flgs}) } - sort.Sort(byCategory(sorted)) + sort.Sort(flags.ByCategory(sorted)) // add sorted array to data and render with default printer originalHelpPrinter(w, tmpl, map[string]interface{}{ diff --git a/cmd/geth/version_check.go b/cmd/geth/version_check.go new file mode 100644 index 0000000000..2101a69e98 --- /dev/null +++ b/cmd/geth/version_check.go @@ -0,0 +1,169 @@ +// 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 . + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/ethereum/go-ethereum/log" + "github.com/jedisct1/go-minisign" + "gopkg.in/urfave/cli.v1" +) + +var gethPubKeys []string = []string{ + //@holiman, minisign public key FB1D084D39BAEC24 + "RWQk7Lo5TQgd+wxBNZM+Zoy+7UhhMHaWKzqoes9tvSbFLJYZhNTbrIjx", + //minisign public key 138B1CA303E51687 + "RWSHFuUDoxyLEzjszuWZI1xStS66QTyXFFZG18uDfO26CuCsbckX1e9J", + //minisign public key FD9813B2D2098484 + "RWSEhAnSshOY/b+GmaiDkObbCWefsAoavjoLcPjBo1xn71yuOH5I+Lts", +} + +type vulnJson struct { + Name string + Uid string + Summary string + Description string + Links []string + Introduced string + Fixed string + Published string + Severity string + Check string + CVE string +} + +func versionCheck(ctx *cli.Context) error { + url := ctx.String(VersionCheckUrlFlag.Name) + version := ctx.String(VersionCheckVersionFlag.Name) + log.Info("Checking vulnerabilities", "version", version, "url", url) + return checkCurrent(url, version) +} + +func checkCurrent(url, current string) error { + var ( + data []byte + sig []byte + err error + ) + if data, err = fetch(url); err != nil { + return fmt.Errorf("could not retrieve data: %w", err) + } + if sig, err = fetch(fmt.Sprintf("%v.minisig", url)); err != nil { + return fmt.Errorf("could not retrieve signature: %w", err) + } + if err = verifySignature(gethPubKeys, data, sig); err != nil { + return err + } + var vulns []vulnJson + if err = json.Unmarshal(data, &vulns); err != nil { + return err + } + allOk := true + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + if err != nil { + return err + } + if r.MatchString(current) { + allOk = false + fmt.Printf("## Vulnerable to %v (%v)\n\n", vuln.Uid, vuln.Name) + fmt.Printf("Severity: %v\n", vuln.Severity) + fmt.Printf("Summary : %v\n", vuln.Summary) + fmt.Printf("Fixed in: %v\n", vuln.Fixed) + if len(vuln.CVE) > 0 { + fmt.Printf("CVE: %v\n", vuln.CVE) + } + if len(vuln.Links) > 0 { + fmt.Printf("References:\n") + for _, ref := range vuln.Links { + fmt.Printf("\t- %v\n", ref) + } + } + fmt.Println() + } + } + if allOk { + fmt.Println("No vulnerabilities found") + } + return nil +} + +// fetch makes an HTTP request to the given url and returns the response body +func fetch(url string) ([]byte, error) { + if filep := strings.TrimPrefix(url, "file://"); filep != url { + return ioutil.ReadFile(filep) + } + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + return body, nil +} + +// verifySignature checks that the sigData is a valid signature of the given +// data, for pubkey GethPubkey +func verifySignature(pubkeys []string, data, sigdata []byte) error { + sig, err := minisign.DecodeSignature(string(sigdata)) + if err != nil { + return err + } + // find the used key + var key *minisign.PublicKey + for _, pubkey := range pubkeys { + pub, err := minisign.NewPublicKey(pubkey) + if err != nil { + // our pubkeys should be parseable + return err + } + if pub.KeyId != sig.KeyId { + continue + } + key = &pub + break + } + if key == nil { + log.Info("Signing key not trusted", "keyid", keyID(sig.KeyId), "error", err) + return errors.New("signature could not be verified") + } + if ok, err := key.Verify(data, sig); !ok || err != nil { + log.Info("Verification failed error", "keyid", keyID(key.KeyId), "error", err) + return errors.New("signature could not be verified") + } + return nil +} + +// keyID turns a binary minisign key ID into a hex string. +// Note: key IDs are printed in reverse byte order. +func keyID(id [8]byte) string { + var rev [8]byte + for i := range id { + rev[len(rev)-1-i] = id[i] + } + return fmt.Sprintf("%X", rev) +} diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go new file mode 100644 index 0000000000..0f056d1967 --- /dev/null +++ b/cmd/geth/version_check_test.go @@ -0,0 +1,130 @@ +// 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 . + +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "regexp" + "strconv" + "strings" + "testing" +) + +func TestVerification(t *testing.T) { + // Signatures generated with `minisign` + t.Run("minisig", func(t *testing.T) { + // For this test, the pubkey is in testdata/minisign.pub + // (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' ) + pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp" + testVerification(t, pub, "./testdata/vcheck/minisig-sigs/") + }) + // Signatures generated with `signify-openbsd` + t.Run("signify-openbsd", func(t *testing.T) { + t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2") + // For this test, the pubkey is in testdata/signifykey.pub + // (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' ) + pub := "RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/" + testVerification(t, pub, "./testdata/vcheck/signify-sigs/") + }) +} + +func testVerification(t *testing.T, pubkey, sigdir string) { + // Data to verify + data, err := ioutil.ReadFile("./testdata/vcheck/data.json") + if err != nil { + t.Fatal(err) + } + // Signatures, with and without comments, both trusted and untrusted + files, err := ioutil.ReadDir(sigdir) + if err != nil { + t.Fatal(err) + } + for _, f := range files { + sig, err := ioutil.ReadFile(filepath.Join(sigdir, f.Name())) + if err != nil { + t.Fatal(err) + } + err = verifySignature([]string{pubkey}, data, sig) + if err != nil { + t.Fatal(err) + } + } +} + +func versionUint(v string) int { + mustInt := func(s string) int { + a, err := strconv.Atoi(s) + if err != nil { + panic(v) + } + return a + } + components := strings.Split(strings.TrimPrefix(v, "v"), ".") + a := mustInt(components[0]) + b := mustInt(components[1]) + c := mustInt(components[2]) + return a*100*100 + b*100 + c +} + +// TestMatching can be used to check that the regexps are correct +func TestMatching(t *testing.T) { + data, _ := ioutil.ReadFile("./testdata/vcheck/vulnerabilities.json") + var vulns []vulnJson + if err := json.Unmarshal(data, &vulns); err != nil { + t.Fatal(err) + } + check := func(version string) { + vFull := fmt.Sprintf("Geth/%v-unstable-15339cf1-20201204/linux-amd64/go1.15.4", version) + for _, vuln := range vulns { + r, err := regexp.Compile(vuln.Check) + vulnIntro := versionUint(vuln.Introduced) + vulnFixed := versionUint(vuln.Fixed) + current := versionUint(version) + if err != nil { + t.Fatal(err) + } + if vuln.Name == "Denial of service due to Go CVE-2020-28362" { + // this one is not tied to geth-versions + continue + } + if vulnIntro <= current && vulnFixed > current { + // Should be vulnerable + if !r.MatchString(vFull) { + t.Errorf("Should be vulnerable, version %v, intro: %v, fixed: %v %v %v", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vuln.Check) + } + } else { + if r.MatchString(vFull) { + t.Errorf("Should not be flagged vulnerable, version %v, intro: %v, fixed: %v %v %d %d %d", + version, vuln.Introduced, vuln.Fixed, vuln.Name, vulnIntro, current, vulnFixed) + } + } + + } + } + for major := 1; major < 2; major++ { + for minor := 0; minor < 30; minor++ { + for patch := 0; patch < 30; patch++ { + vShort := fmt.Sprintf("v%d.%d.%d", major, minor, patch) + check(vShort) + } + } + } +} diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go index f2c1bf9703..812954a680 100644 --- a/cmd/p2psim/main.go +++ b/cmd/p2psim/main.go @@ -289,7 +289,7 @@ func createNode(ctx *cli.Context) error { config.PrivateKey = privKey } if services := ctx.String("services"); services != "" { - config.Services = strings.Split(services, ",") + config.Lifecycles = strings.Split(services, ",") } node, err := client.CreateNode(config) if err != nil { diff --git a/cmd/puppeth/genesis.go b/cmd/puppeth/genesis.go index b3e1709dbf..ef1f977bf0 100644 --- a/cmd/puppeth/genesis.go +++ b/cmd/puppeth/genesis.go @@ -152,7 +152,7 @@ func newAlethGenesisSpec(network string, genesis *core.Genesis) (*alethGenesisSp spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) spec.Genesis.ParentHash = genesis.ParentHash - spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData) + spec.Genesis.ExtraData = genesis.ExtraData spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit) for address, account := range genesis.Alloc { @@ -425,12 +425,12 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin spec.Params.EIP98Transition = math.MaxInt64 spec.Genesis.Seal.Ethereum.Nonce = types.EncodeNonce(genesis.Nonce) - spec.Genesis.Seal.Ethereum.MixHash = (genesis.Mixhash[:]) + spec.Genesis.Seal.Ethereum.MixHash = genesis.Mixhash[:] spec.Genesis.Difficulty = (*hexutil.Big)(genesis.Difficulty) spec.Genesis.Author = genesis.Coinbase spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp) spec.Genesis.ParentHash = genesis.ParentHash - spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData) + spec.Genesis.ExtraData = genesis.ExtraData spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit) spec.Accounts = make(map[common.UnprefixedAddress]*parityChainSpecAccount) diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go index 39ccdd9aa0..a76ee19a06 100644 --- a/cmd/puppeth/module_dashboard.go +++ b/cmd/puppeth/module_dashboard.go @@ -257,7 +257,7 @@ var dashboardContent = `

Under the hood the Android library is backed by a go-ethereum light node, meaning that given a not-too-old Android device, you should be able to join the network without significant issues. Certain functionality is not yet available and rough edges are bound to appear here and there, please report issues if you find any.


The stable Android archives are distributed via Maven Central, and the develop snapshots via the Sonatype repositories. Before proceeding, please ensure you have a recent version configured in your Android project. You can find details in Mobile: Introduction – Android archive. -

Before connecting to the Ethereum network, download the {{.GethGenesis}} genesis json file and either store it in your Android project as a resource file you can access, or save it as a string in a variable. You're going to need to to initialize your client.

+

Before connecting to the Ethereum network, download the {{.GethGenesis}} genesis json file and either store it in your Android project as a resource file you can access, or save it as a string in a variable. You're going to need to initialize your client.

Inside your Java code you can now import the geth archive and connect to Ethereum:

import org.ethereum.geth.*;
@@ -288,7 +288,7 @@ node.start();
 										

Under the hood the iOS library is backed by a go-ethereum light node, meaning that given a not-too-old Apple device, you should be able to join the network without significant issues. Certain functionality is not yet available and rough edges are bound to appear here and there, please report issues if you find any.


Both stable and develop builds of the iOS framework are available via CocoaPods. Before proceeding, please ensure you have a recent version configured in your iOS project. You can find details in Mobile: Introduction – iOS framework. -

Before connecting to the Ethereum network, download the {{.GethGenesis}} genesis json file and either store it in your iOS project as a resource file you can access, or save it as a string in a variable. You're going to need to to initialize your client.

+

Before connecting to the Ethereum network, download the {{.GethGenesis}} genesis json file and either store it in your iOS project as a resource file you can access, or save it as a string in a variable. You're going to need to initialize your client.

Inside your Swift code you can now import the geth framework and connect to Ethereum (ObjC should be analogous):

import Geth
@@ -518,6 +518,8 @@ var dashboardMascot = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01s\x
 var dashboardDockerfile = `
 FROM mhart/alpine-node:latest
 
+WORKDIR /usr/app
+
 RUN \
 	npm install connect serve-static && \
 	\
diff --git a/cmd/puppeth/module_explorer.go b/cmd/puppeth/module_explorer.go
index 8fffe1a1c4..3ce9d612b9 100644
--- a/cmd/puppeth/module_explorer.go
+++ b/cmd/puppeth/module_explorer.go
@@ -35,8 +35,8 @@ FROM puppeth/blockscout:latest
 ADD genesis.json /genesis.json
 RUN \
   echo 'geth --cache 512 init /genesis.json' > explorer.sh && \
-  echo $'geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcapi "net,web3,eth,shh,debug" --rpccorsdomain "*" --rpcvhosts "*" --ws --wsorigins "*" --exitwhensynced' >> explorer.sh && \
-  echo $'exec geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcapi "net,web3,eth,shh,debug" --rpccorsdomain "*" --rpcvhosts "*" --ws --wsorigins "*" &' >> explorer.sh && \
+  echo $'geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --http --http.api "net,web3,eth,shh,debug" --http.corsdomain "*" --http.vhosts "*" --ws --ws.origins "*" --exitwhensynced' >> explorer.sh && \
+  echo $'exec geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --http --http.api "net,web3,eth,shh,debug" --http.corsdomain "*" --http.vhosts "*" --ws --ws.origins "*" &' >> explorer.sh && \
   echo '/usr/local/bin/docker-entrypoint.sh postgres &' >> explorer.sh && \
   echo 'sleep 5' >> explorer.sh && \
   echo 'mix do ecto.drop --force, ecto.create, ecto.migrate' >> explorer.sh && \
diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go
index 3a06bf3c68..88cb80ae4c 100644
--- a/cmd/puppeth/module_faucet.go
+++ b/cmd/puppeth/module_faucet.go
@@ -46,6 +46,7 @@ ENTRYPOINT [ \
 	"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}",             \
 	"--account.json", "/account.json", "--account.pass", "/account.pass"                                                                                                    \
 	{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}}                          \
+	{{if .TwitterToken}}, "--twitter.token.v1", "{{.TwitterToken}}"{{end}}                                                                                                  \
 ]`
 
 // faucetComposefile is the docker-compose.yml file required to deploy and maintain
@@ -71,6 +72,7 @@ services:
       - FAUCET_TIERS={{.FaucetTiers}}
       - CAPTCHA_TOKEN={{.CaptchaToken}}
       - CAPTCHA_SECRET={{.CaptchaSecret}}
+      - TWITTER_TOKEN={{.TwitterToken}}
       - NO_AUTH={{.NoAuth}}{{if .VHost}}
       - VIRTUAL_HOST={{.VHost}}
       - VIRTUAL_PORT=8080{{end}}
@@ -103,6 +105,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
 		"FaucetMinutes": config.minutes,
 		"FaucetTiers":   config.tiers,
 		"NoAuth":        config.noauth,
+		"TwitterToken":  config.twitterToken,
 	})
 	files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
 
@@ -120,6 +123,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
 		"FaucetMinutes": config.minutes,
 		"FaucetTiers":   config.tiers,
 		"NoAuth":        config.noauth,
+		"TwitterToken":  config.twitterToken,
 	})
 	files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
 
@@ -152,6 +156,7 @@ type faucetInfos struct {
 	noauth        bool
 	captchaToken  string
 	captchaSecret string
+	twitterToken  string
 }
 
 // Report converts the typed struct into a plain string->string map, containing
@@ -165,6 +170,7 @@ func (info *faucetInfos) Report() map[string]string {
 		"Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes),
 		"Funding tiers":                strconv.Itoa(info.tiers),
 		"Captha protection":            fmt.Sprintf("%v", info.captchaToken != ""),
+		"Using Twitter API":            fmt.Sprintf("%v", info.twitterToken != ""),
 		"Ethstats username":            info.node.ethstats,
 	}
 	if info.noauth {
@@ -213,7 +219,7 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
 	minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
 	tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"])
 
-	// Retrieve the funding account informations
+	// Retrieve the funding account information
 	var out []byte
 	keyJSON, keyPass := "", ""
 	if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.json", network)); err == nil {
@@ -243,5 +249,6 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
 		captchaToken:  infos.envvars["CAPTCHA_TOKEN"],
 		captchaSecret: infos.envvars["CAPTCHA_SECRET"],
 		noauth:        infos.envvars["NO_AUTH"] == "true",
+		twitterToken:  infos.envvars["TWITTER_TOKEN"],
 	}, nil
 }
diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go
index 5d9ef46523..3ea96870d4 100644
--- a/cmd/puppeth/module_node.go
+++ b/cmd/puppeth/module_node.go
@@ -94,7 +94,7 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n
 
 	lightFlag := ""
 	if config.peersLight > 0 {
-		lightFlag = fmt.Sprintf("--lightpeers=%d --lightserv=50", config.peersLight)
+		lightFlag = fmt.Sprintf("--light.maxpeers=%d --light.serve=50", config.peersLight)
 	}
 	dockerfile := new(bytes.Buffer)
 	template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{
diff --git a/cmd/puppeth/module_wallet.go b/cmd/puppeth/module_wallet.go
index e3e0862eeb..336e408903 100644
--- a/cmd/puppeth/module_wallet.go
+++ b/cmd/puppeth/module_wallet.go
@@ -37,7 +37,7 @@ ADD genesis.json /genesis.json
 RUN \
   echo 'node server.js &'                     > wallet.sh && \
 	echo 'geth --cache 512 init /genesis.json' >> wallet.sh && \
-	echo $'exec geth --networkid {{.NetworkID}} --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcaddr=0.0.0.0 --rpccorsdomain "*" --rpcvhosts "*"' >> wallet.sh
+	echo $'exec geth --networkid {{.NetworkID}} --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --http --http.addr=0.0.0.0 --http.corsdomain "*" --http.vhosts "*"' >> wallet.sh
 
 RUN \
 	sed -i 's/PuppethNetworkID/{{.NetworkID}}/g' dist/js/etherwallet-master.js && \
diff --git a/cmd/puppeth/ssh.go b/cmd/puppeth/ssh.go
index da2862db2f..039cb6cb45 100644
--- a/cmd/puppeth/ssh.go
+++ b/cmd/puppeth/ssh.go
@@ -30,6 +30,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/log"
 	"golang.org/x/crypto/ssh"
+	"golang.org/x/crypto/ssh/agent"
 	"golang.org/x/crypto/ssh/terminal"
 )
 
@@ -43,6 +44,8 @@ type sshClient struct {
 	logger  log.Logger
 }
 
+const EnvSSHAuthSock = "SSH_AUTH_SOCK"
+
 // dial establishes an SSH connection to a remote node using the current user and
 // the user's configured private RSA key. If that fails, password authentication
 // is fallen back to. server can be a string like user:identity@server:port.
@@ -79,38 +82,49 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
 	if username == "" {
 		username = user.Username
 	}
-	// Configure the supported authentication methods (private key and password)
-	var auths []ssh.AuthMethod
 
-	path := filepath.Join(user.HomeDir, ".ssh", identity)
-	if buf, err := ioutil.ReadFile(path); err != nil {
-		log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
+	// Configure the supported authentication methods (ssh agent, private key and password)
+	var (
+		auths []ssh.AuthMethod
+		conn  net.Conn
+	)
+	if conn, err = net.Dial("unix", os.Getenv(EnvSSHAuthSock)); err != nil {
+		log.Warn("Unable to dial SSH agent, falling back to private keys", "err", err)
 	} else {
-		key, err := ssh.ParsePrivateKey(buf)
-		if err != nil {
-			fmt.Printf("What's the decryption password for %s? (won't be echoed)\n>", path)
-			blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
-			fmt.Println()
-			if err != nil {
-				log.Warn("Couldn't read password", "err", err)
-			}
-			key, err := ssh.ParsePrivateKeyWithPassphrase(buf, blob)
+		client := agent.NewClient(conn)
+		auths = append(auths, ssh.PublicKeysCallback(client.Signers))
+	}
+	if err != nil {
+		path := filepath.Join(user.HomeDir, ".ssh", identity)
+		if buf, err := ioutil.ReadFile(path); err != nil {
+			log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
+		} else {
+			key, err := ssh.ParsePrivateKey(buf)
 			if err != nil {
-				log.Warn("Failed to decrypt SSH key, falling back to passwords", "path", path, "err", err)
+				fmt.Printf("What's the decryption password for %s? (won't be echoed)\n>", path)
+				blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
+				fmt.Println()
+				if err != nil {
+					log.Warn("Couldn't read password", "err", err)
+				}
+				key, err := ssh.ParsePrivateKeyWithPassphrase(buf, blob)
+				if err != nil {
+					log.Warn("Failed to decrypt SSH key, falling back to passwords", "path", path, "err", err)
+				} else {
+					auths = append(auths, ssh.PublicKeys(key))
+				}
 			} else {
 				auths = append(auths, ssh.PublicKeys(key))
 			}
-		} else {
-			auths = append(auths, ssh.PublicKeys(key))
 		}
-	}
-	auths = append(auths, ssh.PasswordCallback(func() (string, error) {
-		fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server)
-		blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
+		auths = append(auths, ssh.PasswordCallback(func() (string, error) {
+			fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server)
+			blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
 
-		fmt.Println()
-		return string(blob), err
-	}))
+			fmt.Println()
+			return string(blob), err
+		}))
+	}
 	// Resolve the IP address of the remote server
 	addr, err := net.LookupHost(hostname)
 	if err != nil {
diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go
index 9f753ad68b..65d4e8b8ed 100644
--- a/cmd/puppeth/wizard_faucet.go
+++ b/cmd/puppeth/wizard_faucet.go
@@ -102,6 +102,21 @@ func (w *wizard) deployFaucet() {
 			infos.captchaSecret = w.readPassword()
 		}
 	}
+	// Accessing the Twitter API requires a bearer token, request it
+	if infos.twitterToken != "" {
+		fmt.Println()
+		fmt.Println("Reuse previous Twitter API token (y/n)? (default = yes)")
+		if !w.readDefaultYesNo(true) {
+			infos.twitterToken = ""
+		}
+	}
+	if infos.twitterToken == "" {
+		// No previous twitter token (or old one discarded)
+		fmt.Println()
+		fmt.Println()
+		fmt.Printf("What is the Twitter API app Bearer token?\n")
+		infos.twitterToken = w.readString()
+	}
 	// Figure out where the user wants to store the persistent data
 	fmt.Println()
 	if infos.node.datadir == "" {
diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go
index ab3e2247b6..4f701fa1c3 100644
--- a/cmd/puppeth/wizard_genesis.go
+++ b/cmd/puppeth/wizard_genesis.go
@@ -179,7 +179,7 @@ func (w *wizard) importGenesis() {
 	// Parse the genesis file and inject it successful
 	var genesis core.Genesis
 	if err := json.NewDecoder(reader).Decode(&genesis); err != nil {
-		log.Error("Invalid genesis spec: %v", err)
+		log.Error("Invalid genesis spec", "err", err)
 		return
 	}
 	log.Info("Imported genesis block")
@@ -235,6 +235,14 @@ func (w *wizard) manageGenesis() {
 		fmt.Printf("Which block should Istanbul come into effect? (default = %v)\n", w.conf.Genesis.Config.IstanbulBlock)
 		w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock)
 
+		fmt.Println()
+		fmt.Printf("Which block should Berlin come into effect? (default = %v)\n", w.conf.Genesis.Config.BerlinBlock)
+		w.conf.Genesis.Config.BerlinBlock = w.readDefaultBigInt(w.conf.Genesis.Config.BerlinBlock)
+
+		fmt.Println()
+		fmt.Printf("Which block should YOLOv3 come into effect? (default = %v)\n", w.conf.Genesis.Config.YoloV3Block)
+		w.conf.Genesis.Config.YoloV3Block = w.readDefaultBigInt(w.conf.Genesis.Config.YoloV3Block)
+
 		out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", "  ")
 		fmt.Printf("Chain configuration updated:\n\n%s\n", out)
 
@@ -255,7 +263,7 @@ func (w *wizard) manageGenesis() {
 
 		// Export the native genesis spec used by puppeth and Geth
 		gethJson := filepath.Join(folder, fmt.Sprintf("%s.json", w.network))
-		if err := ioutil.WriteFile((gethJson), out, 0644); err != nil {
+		if err := ioutil.WriteFile(gethJson, out, 0644); err != nil {
 			log.Error("Failed to save genesis file", "err", err)
 			return
 		}
diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go
index a40978bcb7..d4051e59ef 100644
--- a/cmd/utils/cmd.go
+++ b/cmd/utils/cmd.go
@@ -26,17 +26,20 @@ import (
 	"runtime"
 	"strings"
 	"syscall"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/internal/debug"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/rlp"
+	"gopkg.in/urfave/cli.v1"
 )
 
 const (
@@ -63,7 +66,7 @@ func Fatalf(format string, args ...interface{}) {
 	os.Exit(1)
 }
 
-func StartNode(stack *node.Node) {
+func StartNode(ctx *cli.Context, stack *node.Node) {
 	if err := stack.Start(); err != nil {
 		Fatalf("Error starting protocol stack: %v", err)
 	}
@@ -71,9 +74,20 @@ func StartNode(stack *node.Node) {
 		sigc := make(chan os.Signal, 1)
 		signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
 		defer signal.Stop(sigc)
+
+		minFreeDiskSpace := ethconfig.Defaults.TrieDirtyCache
+		if ctx.GlobalIsSet(MinFreeDiskSpaceFlag.Name) {
+			minFreeDiskSpace = ctx.GlobalInt(MinFreeDiskSpaceFlag.Name)
+		} else if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
+			minFreeDiskSpace = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
+		}
+		if minFreeDiskSpace > 0 {
+			go monitorFreeDiskSpace(sigc, stack.InstanceDir(), uint64(minFreeDiskSpace)*1024*1024)
+		}
+
 		<-sigc
 		log.Info("Got interrupt, shutting down...")
-		go stack.Stop()
+		go stack.Close()
 		for i := 10; i > 0; i-- {
 			<-sigc
 			if i > 1 {
@@ -85,6 +99,24 @@ func StartNode(stack *node.Node) {
 	}()
 }
 
+func monitorFreeDiskSpace(sigc chan os.Signal, path string, freeDiskSpaceCritical uint64) {
+	for {
+		freeSpace, err := getFreeDiskSpace(path)
+		if err != nil {
+			log.Warn("Failed to get free disk space", "path", path, "err", err)
+			break
+		}
+		if freeSpace < freeDiskSpaceCritical {
+			log.Error("Low disk space. Gracefully shutting down Geth to prevent database corruption.", "available", common.StorageSize(freeSpace))
+			sigc <- syscall.SIGTERM
+			break
+		} else if freeSpace < 2*freeDiskSpaceCritical {
+			log.Warn("Disk space is running low. Geth will shutdown if disk space runs below critical level.", "available", common.StorageSize(freeSpace), "critical_level", common.StorageSize(freeDiskSpaceCritical))
+		}
+		time.Sleep(60 * time.Second)
+	}
+}
+
 func ImportChain(chain *core.BlockChain, fn string) error {
 	// Watch for Ctrl-C while the import is running.
 	// If a signal is received, the import will stop at the next batch.
diff --git a/cmd/utils/customflags.go b/cmd/utils/customflags.go
index 17dcba5f74..0a72e80349 100644
--- a/cmd/utils/customflags.go
+++ b/cmd/utils/customflags.go
@@ -192,14 +192,14 @@ func GlobalBig(ctx *cli.Context, name string) *big.Int {
 // Note, it has limitations, e.g. ~someuser/tmp will not be expanded
 func expandPath(p string) string {
 	if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
-		if home := homeDir(); home != "" {
+		if home := HomeDir(); home != "" {
 			p = home + p[1:]
 		}
 	}
 	return path.Clean(os.ExpandEnv(p))
 }
 
-func homeDir() string {
+func HomeDir() string {
 	if home := os.Getenv("HOME"); home != "" {
 		return home
 	}
diff --git a/cmd/utils/diskusage.go b/cmd/utils/diskusage.go
new file mode 100644
index 0000000000..c3d51765f8
--- /dev/null
+++ b/cmd/utils/diskusage.go
@@ -0,0 +1,42 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// +build !windows,!openbsd
+
+package utils
+
+import (
+	"fmt"
+
+	"golang.org/x/sys/unix"
+)
+
+func getFreeDiskSpace(path string) (uint64, error) {
+	var stat unix.Statfs_t
+	if err := unix.Statfs(path, &stat); err != nil {
+		return 0, fmt.Errorf("failed to call Statfs: %v", err)
+	}
+
+	// Available blocks * size per block = available space in bytes
+	var bavail = stat.Bavail
+	if stat.Bavail < 0 {
+		// FreeBSD can have a negative number of blocks available
+		// because of the grace limit.
+		bavail = 0
+	}
+	//nolint:unconvert
+	return uint64(bavail) * uint64(stat.Bsize), nil
+}
diff --git a/p2p/discv5/sim_testmain_test.go b/cmd/utils/diskusage_openbsd.go
similarity index 53%
rename from p2p/discv5/sim_testmain_test.go
rename to cmd/utils/diskusage_openbsd.go
index 77e751c419..54f759d291 100644
--- a/p2p/discv5/sim_testmain_test.go
+++ b/cmd/utils/diskusage_openbsd.go
@@ -1,4 +1,4 @@
-// Copyright 2016 The go-ethereum Authors
+// Copyright 2021 The go-ethereum Authors
 // This file is part of the go-ethereum library.
 //
 // The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,30 +14,30 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// +build go1.4,nacl,faketime_simulation
+// +build openbsd
 
-package discv5
+package utils
 
 import (
-	"os"
-	"runtime"
-	"testing"
-	"unsafe"
-)
-
-// Enable fake time mode in the runtime, like on the go playground.
-// There is a slight chance that this won't work because some go code
-// might have executed before the variable is set.
+	"fmt"
 
-//go:linkname faketime runtime.faketime
-var faketime = 1
+	"golang.org/x/sys/unix"
+)
 
-func TestMain(m *testing.M) {
-	// We need to use unsafe somehow in order to get access to go:linkname.
-	_ = unsafe.Sizeof(0)
+func getFreeDiskSpace(path string) (uint64, error) {
+	var stat unix.Statfs_t
+	if err := unix.Statfs(path, &stat); err != nil {
+		return 0, fmt.Errorf("failed to call Statfs: %v", err)
+	}
 
-	// Run the actual test. runWithPlaygroundTime ensures that the only test
-	// that runs is the one calling it.
-	runtime.GOMAXPROCS(8)
-	os.Exit(m.Run())
+	// Available blocks * size per block = available space in bytes
+	var bavail = stat.F_bavail
+	// Not sure if the following check is necessary for OpenBSD
+	if stat.F_bavail < 0 {
+		// FreeBSD can have a negative number of blocks available
+		// because of the grace limit.
+		bavail = 0
+	}
+	//nolint:unconvert
+	return uint64(bavail) * uint64(stat.F_bsize), nil
 }
diff --git a/cmd/utils/diskusage_windows.go b/cmd/utils/diskusage_windows.go
new file mode 100644
index 0000000000..9bf7740b99
--- /dev/null
+++ b/cmd/utils/diskusage_windows.go
@@ -0,0 +1,38 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package utils
+
+import (
+	"fmt"
+
+	"golang.org/x/sys/windows"
+)
+
+func getFreeDiskSpace(path string) (uint64, error) {
+
+	cwd, err := windows.UTF16PtrFromString(path)
+	if err != nil {
+		return 0, fmt.Errorf("failed to call UTF16PtrFromString: %v", err)
+	}
+
+	var freeBytesAvailableToCaller, totalNumberOfBytes, totalNumberOfFreeBytes uint64
+	if err := windows.GetDiskFreeSpaceEx(cwd, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); err != nil {
+		return 0, fmt.Errorf("failed to call GetDiskFreeSpaceEx: %v", err)
+	}
+
+	return freeBytesAvailableToCaller, nil
+}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 1afd987a6d..f02f326bcf 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -19,13 +19,14 @@ package utils
 
 import (
 	"crypto/ecdsa"
-	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"math"
 	"math/big"
 	"os"
 	"path/filepath"
+	godebug "runtime/debug"
 	"strconv"
 	"strings"
 	"text/tabwriter"
@@ -40,54 +41,34 @@ import (
 	"github.com/ethereum/go-ethereum/consensus/clique"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/eth/gasprice"
+	"github.com/ethereum/go-ethereum/eth/tracers"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/ethstats"
 	"github.com/ethereum/go-ethereum/graphql"
+	"github.com/ethereum/go-ethereum/internal/ethapi"
+	"github.com/ethereum/go-ethereum/internal/flags"
 	"github.com/ethereum/go-ethereum/les"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/metrics/exp"
 	"github.com/ethereum/go-ethereum/metrics/influxdb"
 	"github.com/ethereum/go-ethereum/miner"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/discv5"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/nat"
 	"github.com/ethereum/go-ethereum/p2p/netutil"
 	"github.com/ethereum/go-ethereum/params"
-	"github.com/ethereum/go-ethereum/rpc"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
 	pcsclite "github.com/gballet/go-libpcsclite"
-	cli "gopkg.in/urfave/cli.v1"
-)
-
-var (
-	CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} [arguments...]
-{{if .cmd.Description}}{{.cmd.Description}}
-{{end}}{{if .cmd.Subcommands}}
-SUBCOMMANDS:
-	{{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
-	{{end}}{{end}}{{if .categorizedFlags}}
-{{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS:
-{{range $categorized.Flags}}{{"\t"}}{{.}}
-{{end}}
-{{end}}{{end}}`
-
-	OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...]
-{{if .Description}}{{.Description}}
-{{end}}{{if .Subcommands}}
-SUBCOMMANDS:
-	{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
-	{{end}}{{end}}{{if .Flags}}
-OPTIONS:
-{{range $.Flags}}{{"\t"}}{{.}}
-{{end}}
-{{end}}`
+	gopsutil "github.com/shirou/gopsutil/mem"
+	"gopkg.in/urfave/cli.v1"
 )
 
 func init() {
@@ -103,21 +84,10 @@ GLOBAL OPTIONS:
    {{range .Flags}}{{.}}
    {{end}}{{end}}
 `
-	cli.CommandHelpTemplate = CommandHelpTemplate
+	cli.CommandHelpTemplate = flags.CommandHelpTemplate
 	cli.HelpPrinter = printHelp
 }
 
-// NewApp creates an app with sane defaults.
-func NewApp(gitCommit, gitDate, usage string) *cli.App {
-	app := cli.NewApp()
-	app.Name = filepath.Base(os.Args[0])
-	app.Author = ""
-	app.Email = ""
-	app.Version = params.VersionWithCommit(gitCommit, gitDate)
-	app.Usage = usage
-	return app
-}
-
 func printHelp(out io.Writer, templ string, data interface{}) {
 	funcMap := template.FuncMap{"join": strings.Join}
 	t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
@@ -155,13 +125,21 @@ var (
 		Name:  "datadir.ancient",
 		Usage: "Data directory for ancient chain segments (default = inside chaindata)",
 	}
+	MinFreeDiskSpaceFlag = DirectoryFlag{
+		Name:  "datadir.minfreedisk",
+		Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)",
+	}
 	KeyStoreDirFlag = DirectoryFlag{
 		Name:  "keystore",
 		Usage: "Directory for the keystore (default = inside the datadir)",
 	}
 	NoUSBFlag = cli.BoolFlag{
 		Name:  "nousb",
-		Usage: "Disables monitoring for and managing USB hardware wallets",
+		Usage: "Disables monitoring for and managing USB hardware wallets (deprecated)",
+	}
+	USBFlag = cli.BoolFlag{
+		Name:  "usb",
+		Usage: "Enable monitoring and management of USB hardware wallets",
 	}
 	SmartCardDaemonPathFlag = cli.StringFlag{
 		Name:  "pcscdpath",
@@ -170,17 +148,21 @@ var (
 	}
 	NetworkIdFlag = cli.Uint64Flag{
 		Name:  "networkid",
-		Usage: "Network identifier (integer, 1=Frontier, 3=Ropsten, 4=Rinkeby, 5=Görli)",
-		Value: eth.DefaultConfig.NetworkId,
+		Usage: "Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead)",
+		Value: ethconfig.Defaults.NetworkId,
 	}
-	LegacyTestnetFlag = cli.BoolFlag{ // TODO(q9f): Remove after Ropsten is discontinued.
-		Name:  "testnet",
-		Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)",
+	MainnetFlag = cli.BoolFlag{
+		Name:  "mainnet",
+		Usage: "Ethereum mainnet",
 	}
 	GoerliFlag = cli.BoolFlag{
 		Name:  "goerli",
 		Usage: "Görli network: pre-configured proof-of-authority test network",
 	}
+	YoloV3Flag = cli.BoolFlag{
+		Name:  "yolov3",
+		Usage: "YOLOv3 network: pre-configured proof-of-authority shortlived test network.",
+	}
 	RinkebyFlag = cli.BoolFlag{
 		Name:  "rinkeby",
 		Usage: "Rinkeby network: pre-configured proof-of-authority test network",
@@ -204,7 +186,7 @@ var (
 	DocRootFlag = DirectoryFlag{
 		Name:  "docroot",
 		Usage: "Document Root for HTTPClient file scheme",
-		Value: DirectoryString(homeDir()),
+		Value: DirectoryString(HomeDir()),
 	}
 	ExitWhenSyncedFlag = cli.BoolFlag{
 		Name:  "exitwhensynced",
@@ -226,10 +208,10 @@ var (
 		Name:  "nocode",
 		Usage: "Exclude contract code (save db lookups)",
 	}
-	defaultSyncMode = eth.DefaultConfig.SyncMode
+	defaultSyncMode = ethconfig.Defaults.SyncMode
 	SyncModeFlag    = TextMarshalerFlag{
 		Name:  "syncmode",
-		Usage: `Blockchain sync mode ("fast", "full", or "light")`,
+		Usage: `Blockchain sync mode ("fast", "full", "snap" or "light")`,
 		Value: &defaultSyncMode,
 	}
 	GCModeFlag = cli.StringFlag{
@@ -237,9 +219,14 @@ var (
 		Usage: `Blockchain garbage collection mode ("full", "archive")`,
 		Value: "full",
 	}
-	SnapshotFlag = cli.BoolFlag{
+	SnapshotFlag = cli.BoolTFlag{
 		Name:  "snapshot",
-		Usage: `Enables snapshot-database mode -- experimental work in progress feature`,
+		Usage: `Enables snapshot-database mode (default = enable)`,
+	}
+	TxLookupLimitFlag = cli.Uint64Flag{
+		Name:  "txlookuplimit",
+		Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)",
+		Value: ethconfig.Defaults.TxLookupLimit,
 	}
 	LightKDFFlag = cli.BoolFlag{
 		Name:  "lightkdf",
@@ -249,59 +236,58 @@ var (
 		Name:  "whitelist",
 		Usage: "Comma separated block number-to-hash mappings to enforce (=)",
 	}
-	OverrideIstanbulFlag = cli.Uint64Flag{
-		Name:  "override.istanbul",
-		Usage: "Manually specify Istanbul fork-block, overriding the bundled setting",
+	BloomFilterSizeFlag = cli.Uint64Flag{
+		Name:  "bloomfilter.size",
+		Usage: "Megabytes of memory allocated to bloom-filter for pruning",
+		Value: 2048,
 	}
-	OverrideMuirGlacierFlag = cli.Uint64Flag{
-		Name:  "override.muirglacier",
-		Usage: "Manually specify Muir Glacier fork-block, overriding the bundled setting",
+	OverrideBerlinFlag = cli.Uint64Flag{
+		Name:  "override.berlin",
+		Usage: "Manually specify Berlin fork-block, overriding the bundled setting",
 	}
 	// Light server and client settings
-	LightLegacyServFlag = cli.IntFlag{ // Deprecated in favor of light.serve, remove in 2021
-		Name:  "lightserv",
-		Usage: "Maximum percentage of time allowed for serving LES requests (deprecated, use --light.serve)",
-		Value: eth.DefaultConfig.LightServ,
-	}
 	LightServeFlag = cli.IntFlag{
 		Name:  "light.serve",
 		Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)",
-		Value: eth.DefaultConfig.LightServ,
+		Value: ethconfig.Defaults.LightServ,
 	}
 	LightIngressFlag = cli.IntFlag{
 		Name:  "light.ingress",
 		Usage: "Incoming bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)",
-		Value: eth.DefaultConfig.LightIngress,
+		Value: ethconfig.Defaults.LightIngress,
 	}
 	LightEgressFlag = cli.IntFlag{
 		Name:  "light.egress",
 		Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)",
-		Value: eth.DefaultConfig.LightEgress,
-	}
-	LightLegacyPeersFlag = cli.IntFlag{ // Deprecated in favor of light.maxpeers, remove in 2021
-		Name:  "lightpeers",
-		Usage: "Maximum number of light clients to serve, or light servers to attach to  (deprecated, use --light.maxpeers)",
-		Value: eth.DefaultConfig.LightPeers,
+		Value: ethconfig.Defaults.LightEgress,
 	}
 	LightMaxPeersFlag = cli.IntFlag{
 		Name:  "light.maxpeers",
 		Usage: "Maximum number of light clients to serve, or light servers to attach to",
-		Value: eth.DefaultConfig.LightPeers,
+		Value: ethconfig.Defaults.LightPeers,
 	}
 	UltraLightServersFlag = cli.StringFlag{
 		Name:  "ulc.servers",
 		Usage: "List of trusted ultra-light servers",
-		Value: strings.Join(eth.DefaultConfig.UltraLightServers, ","),
+		Value: strings.Join(ethconfig.Defaults.UltraLightServers, ","),
 	}
 	UltraLightFractionFlag = cli.IntFlag{
 		Name:  "ulc.fraction",
 		Usage: "Minimum % of trusted ultra-light servers required to announce a new head",
-		Value: eth.DefaultConfig.UltraLightFraction,
+		Value: ethconfig.Defaults.UltraLightFraction,
 	}
 	UltraLightOnlyAnnounceFlag = cli.BoolFlag{
 		Name:  "ulc.onlyannounce",
 		Usage: "Ultra light server sends announcements only",
 	}
+	LightNoPruneFlag = cli.BoolFlag{
+		Name:  "light.nopruning",
+		Usage: "Disable ancient light chain data pruning",
+	}
+	LightNoSyncServeFlag = cli.BoolFlag{
+		Name:  "light.nosyncserve",
+		Usage: "Enables serving light clients before syncing",
+	}
 	// Ethash settings
 	EthashCacheDirFlag = DirectoryFlag{
 		Name:  "ethash.cachedir",
@@ -310,12 +296,12 @@ var (
 	EthashCachesInMemoryFlag = cli.IntFlag{
 		Name:  "ethash.cachesinmem",
 		Usage: "Number of recent ethash caches to keep in memory (16MB each)",
-		Value: eth.DefaultConfig.Ethash.CachesInMem,
+		Value: ethconfig.Defaults.Ethash.CachesInMem,
 	}
 	EthashCachesOnDiskFlag = cli.IntFlag{
 		Name:  "ethash.cachesondisk",
 		Usage: "Number of recent ethash caches to keep on disk (16MB each)",
-		Value: eth.DefaultConfig.Ethash.CachesOnDisk,
+		Value: ethconfig.Defaults.Ethash.CachesOnDisk,
 	}
 	EthashCachesLockMmapFlag = cli.BoolFlag{
 		Name:  "ethash.cacheslockmmap",
@@ -324,17 +310,17 @@ var (
 	EthashDatasetDirFlag = DirectoryFlag{
 		Name:  "ethash.dagdir",
 		Usage: "Directory to store the ethash mining DAGs",
-		Value: DirectoryString(eth.DefaultConfig.Ethash.DatasetDir),
+		Value: DirectoryString(ethconfig.Defaults.Ethash.DatasetDir),
 	}
 	EthashDatasetsInMemoryFlag = cli.IntFlag{
 		Name:  "ethash.dagsinmem",
 		Usage: "Number of recent ethash mining DAGs to keep in memory (1+GB each)",
-		Value: eth.DefaultConfig.Ethash.DatasetsInMem,
+		Value: ethconfig.Defaults.Ethash.DatasetsInMem,
 	}
 	EthashDatasetsOnDiskFlag = cli.IntFlag{
 		Name:  "ethash.dagsondisk",
 		Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)",
-		Value: eth.DefaultConfig.Ethash.DatasetsOnDisk,
+		Value: ethconfig.Defaults.Ethash.DatasetsOnDisk,
 	}
 	EthashDatasetsLockMmapFlag = cli.BoolFlag{
 		Name:  "ethash.dagslockmmap",
@@ -362,37 +348,37 @@ var (
 	TxPoolPriceLimitFlag = cli.Uint64Flag{
 		Name:  "txpool.pricelimit",
 		Usage: "Minimum gas price limit to enforce for acceptance into the pool",
-		Value: eth.DefaultConfig.TxPool.PriceLimit,
+		Value: ethconfig.Defaults.TxPool.PriceLimit,
 	}
 	TxPoolPriceBumpFlag = cli.Uint64Flag{
 		Name:  "txpool.pricebump",
 		Usage: "Price bump percentage to replace an already existing transaction",
-		Value: eth.DefaultConfig.TxPool.PriceBump,
+		Value: ethconfig.Defaults.TxPool.PriceBump,
 	}
 	TxPoolAccountSlotsFlag = cli.Uint64Flag{
 		Name:  "txpool.accountslots",
 		Usage: "Minimum number of executable transaction slots guaranteed per account",
-		Value: eth.DefaultConfig.TxPool.AccountSlots,
+		Value: ethconfig.Defaults.TxPool.AccountSlots,
 	}
 	TxPoolGlobalSlotsFlag = cli.Uint64Flag{
 		Name:  "txpool.globalslots",
 		Usage: "Maximum number of executable transaction slots for all accounts",
-		Value: eth.DefaultConfig.TxPool.GlobalSlots,
+		Value: ethconfig.Defaults.TxPool.GlobalSlots,
 	}
 	TxPoolAccountQueueFlag = cli.Uint64Flag{
 		Name:  "txpool.accountqueue",
 		Usage: "Maximum number of non-executable transaction slots permitted per account",
-		Value: eth.DefaultConfig.TxPool.AccountQueue,
+		Value: ethconfig.Defaults.TxPool.AccountQueue,
 	}
 	TxPoolGlobalQueueFlag = cli.Uint64Flag{
 		Name:  "txpool.globalqueue",
 		Usage: "Maximum number of non-executable transaction slots for all accounts",
-		Value: eth.DefaultConfig.TxPool.GlobalQueue,
+		Value: ethconfig.Defaults.TxPool.GlobalQueue,
 	}
 	TxPoolLifetimeFlag = cli.DurationFlag{
 		Name:  "txpool.lifetime",
 		Usage: "Maximum amount of time non-executable transaction are queued",
-		Value: eth.DefaultConfig.TxPool.Lifetime,
+		Value: ethconfig.Defaults.TxPool.Lifetime,
 	}
 	// Performance tuning settings
 	CacheFlag = cli.IntFlag{
@@ -410,6 +396,16 @@ var (
 		Usage: "Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)",
 		Value: 15,
 	}
+	CacheTrieJournalFlag = cli.StringFlag{
+		Name:  "cache.trie.journal",
+		Usage: "Disk journal directory for trie cache to survive node restarts",
+		Value: ethconfig.Defaults.TrieCleanCacheJournal,
+	}
+	CacheTrieRejournalFlag = cli.DurationFlag{
+		Name:  "cache.trie.rejournal",
+		Usage: "Time interval to regenerate the trie cache journal",
+		Value: ethconfig.Defaults.TrieCleanCacheRejournal,
+	}
 	CacheGCFlag = cli.IntFlag{
 		Name:  "cache.gc",
 		Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)",
@@ -424,6 +420,10 @@ var (
 		Name:  "cache.noprefetch",
 		Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)",
 	}
+	CachePreimagesFlag = cli.BoolFlag{
+		Name:  "cache.preimages",
+		Usage: "Enable recording the SHA3/keccak preimages of trie keys",
+	}
 	// Miner settings
 	MiningEnabledFlag = cli.BoolFlag{
 		Name:  "mine",
@@ -434,67 +434,47 @@ var (
 		Usage: "Number of CPU threads to use for mining",
 		Value: 0,
 	}
-	MinerLegacyThreadsFlag = cli.IntFlag{
-		Name:  "minerthreads",
-		Usage: "Number of CPU threads to use for mining (deprecated, use --miner.threads)",
-		Value: 0,
-	}
 	MinerNotifyFlag = cli.StringFlag{
 		Name:  "miner.notify",
 		Usage: "Comma separated HTTP URL list to notify of new work packages",
 	}
+	MinerNotifyFullFlag = cli.BoolFlag{
+		Name:  "miner.notify.full",
+		Usage: "Notify with pending block headers instead of work packages",
+	}
 	MinerGasTargetFlag = cli.Uint64Flag{
 		Name:  "miner.gastarget",
 		Usage: "Target gas floor for mined blocks",
-		Value: eth.DefaultConfig.Miner.GasFloor,
-	}
-	MinerLegacyGasTargetFlag = cli.Uint64Flag{
-		Name:  "targetgaslimit",
-		Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)",
-		Value: eth.DefaultConfig.Miner.GasFloor,
+		Value: ethconfig.Defaults.Miner.GasFloor,
 	}
 	MinerGasLimitFlag = cli.Uint64Flag{
 		Name:  "miner.gaslimit",
 		Usage: "Target gas ceiling for mined blocks",
-		Value: eth.DefaultConfig.Miner.GasCeil,
+		Value: ethconfig.Defaults.Miner.GasCeil,
 	}
 	MinerGasPriceFlag = BigFlag{
 		Name:  "miner.gasprice",
 		Usage: "Minimum gas price for mining a transaction",
-		Value: eth.DefaultConfig.Miner.GasPrice,
-	}
-	MinerLegacyGasPriceFlag = BigFlag{
-		Name:  "gasprice",
-		Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)",
-		Value: eth.DefaultConfig.Miner.GasPrice,
+		Value: ethconfig.Defaults.Miner.GasPrice,
 	}
 	MinerEtherbaseFlag = cli.StringFlag{
 		Name:  "miner.etherbase",
 		Usage: "Public address for block mining rewards (default = first account)",
 		Value: "0",
 	}
-	MinerLegacyEtherbaseFlag = cli.StringFlag{
-		Name:  "etherbase",
-		Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)",
-		Value: "0",
-	}
 	MinerExtraDataFlag = cli.StringFlag{
 		Name:  "miner.extradata",
 		Usage: "Block extra data set by the miner (default = client version)",
 	}
-	MinerLegacyExtraDataFlag = cli.StringFlag{
-		Name:  "extradata",
-		Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)",
-	}
 	MinerRecommitIntervalFlag = cli.DurationFlag{
 		Name:  "miner.recommit",
 		Usage: "Time interval to recreate the block being mined",
-		Value: eth.DefaultConfig.Miner.Recommit,
+		Value: ethconfig.Defaults.Miner.Recommit,
 	}
 	MinerDelayLeftoverFlag = cli.DurationFlag{
 		Name:  "miner.delayleftover",
 		Usage: "Time interval to for broadcast block",
-		Value: eth.DefaultConfig.Miner.DelayLeftOver,
+		Value: ethconfig.Defaults.Miner.DelayLeftOver,
 	}
 	MinerNoVerfiyFlag = cli.BoolFlag{
 		Name:  "miner.noverify",
@@ -502,6 +482,7 @@ var (
 	}
 	// Account settings
 	UnlockedAccountFlag = cli.StringFlag{
+
 		Name:  "unlock",
 		Usage: "Comma separated list of accounts to unlock",
 		Value: "",
@@ -524,9 +505,15 @@ var (
 		Name:  "allow-insecure-unlock",
 		Usage: "Allow insecure account unlocking when account-related RPCs are exposed by http",
 	}
-	RPCGlobalGasCap = cli.Uint64Flag{
+	RPCGlobalGasCapFlag = cli.Uint64Flag{
 		Name:  "rpc.gascap",
-		Usage: "Sets a cap on gas that can be used in eth_call/estimateGas",
+		Usage: "Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite)",
+		Value: ethconfig.Defaults.RPCGasCap,
+	}
+	RPCGlobalTxFeeCapFlag = cli.Float64Flag{
+		Name:  "rpc.txfeecap",
+		Usage: "Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap)",
+		Value: ethconfig.Defaults.RPCTxFeeCap,
 	}
 	// Logging and debug settings
 	EthStatsURLFlag = cli.StringFlag{
@@ -550,83 +537,83 @@ var (
 		Name:  "ipcpath",
 		Usage: "Filename for IPC socket/pipe within the datadir (explicit paths escape it)",
 	}
-	RPCEnabledFlag = cli.BoolFlag{
-		Name:  "rpc",
+	HTTPEnabledFlag = cli.BoolFlag{
+		Name:  "http",
 		Usage: "Enable the HTTP-RPC server",
 	}
-	RPCListenAddrFlag = cli.StringFlag{
-		Name:  "rpcaddr",
+	HTTPListenAddrFlag = cli.StringFlag{
+		Name:  "http.addr",
 		Usage: "HTTP-RPC server listening interface",
 		Value: node.DefaultHTTPHost,
 	}
-	RPCPortFlag = cli.IntFlag{
-		Name:  "rpcport",
+	HTTPPortFlag = cli.IntFlag{
+		Name:  "http.port",
 		Usage: "HTTP-RPC server listening port",
 		Value: node.DefaultHTTPPort,
 	}
-	RPCCORSDomainFlag = cli.StringFlag{
-		Name:  "rpccorsdomain",
+	HTTPCORSDomainFlag = cli.StringFlag{
+		Name:  "http.corsdomain",
 		Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
 		Value: "",
 	}
-	RPCVirtualHostsFlag = cli.StringFlag{
-		Name:  "rpcvhosts",
+	HTTPVirtualHostsFlag = cli.StringFlag{
+		Name:  "http.vhosts",
 		Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
 		Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","),
 	}
-	RPCApiFlag = cli.StringFlag{
-		Name:  "rpcapi",
+	HTTPApiFlag = cli.StringFlag{
+		Name:  "http.api",
 		Usage: "API's offered over the HTTP-RPC interface",
 		Value: "",
 	}
+	HTTPPathPrefixFlag = cli.StringFlag{
+		Name:  "http.rpcprefix",
+		Usage: "HTTP path path prefix on which JSON-RPC is served. Use '/' to serve on all paths.",
+		Value: "",
+	}
+	GraphQLEnabledFlag = cli.BoolFlag{
+		Name:  "graphql",
+		Usage: "Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.",
+	}
+	GraphQLCORSDomainFlag = cli.StringFlag{
+		Name:  "graphql.corsdomain",
+		Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
+		Value: "",
+	}
+	GraphQLVirtualHostsFlag = cli.StringFlag{
+		Name:  "graphql.vhosts",
+		Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
+		Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","),
+	}
 	WSEnabledFlag = cli.BoolFlag{
 		Name:  "ws",
 		Usage: "Enable the WS-RPC server",
 	}
 	WSListenAddrFlag = cli.StringFlag{
-		Name:  "wsaddr",
+		Name:  "ws.addr",
 		Usage: "WS-RPC server listening interface",
 		Value: node.DefaultWSHost,
 	}
 	WSPortFlag = cli.IntFlag{
-		Name:  "wsport",
+		Name:  "ws.port",
 		Usage: "WS-RPC server listening port",
 		Value: node.DefaultWSPort,
 	}
 	WSApiFlag = cli.StringFlag{
-		Name:  "wsapi",
+		Name:  "ws.api",
 		Usage: "API's offered over the WS-RPC interface",
 		Value: "",
 	}
 	WSAllowedOriginsFlag = cli.StringFlag{
-		Name:  "wsorigins",
+		Name:  "ws.origins",
 		Usage: "Origins from which to accept websockets requests",
 		Value: "",
 	}
-	GraphQLEnabledFlag = cli.BoolFlag{
-		Name:  "graphql",
-		Usage: "Enable the GraphQL server",
-	}
-	GraphQLListenAddrFlag = cli.StringFlag{
-		Name:  "graphql.addr",
-		Usage: "GraphQL server listening interface",
-		Value: node.DefaultGraphQLHost,
-	}
-	GraphQLPortFlag = cli.IntFlag{
-		Name:  "graphql.port",
-		Usage: "GraphQL server listening port",
-		Value: node.DefaultGraphQLPort,
-	}
-	GraphQLCORSDomainFlag = cli.StringFlag{
-		Name:  "graphql.corsdomain",
-		Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
+	WSPathPrefixFlag = cli.StringFlag{
+		Name:  "ws.rpcprefix",
+		Usage: "HTTP path prefix on which JSON-RPC is served. Use '/' to serve on all paths.",
 		Value: "",
 	}
-	GraphQLVirtualHostsFlag = cli.StringFlag{
-		Name:  "graphql.vhosts",
-		Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
-		Value: strings.Join(node.DefaultConfig.GraphQLVirtualHosts, ","),
-	}
 	ExecFlag = cli.StringFlag{
 		Name:  "exec",
 		Usage: "Execute JavaScript statement",
@@ -635,6 +622,10 @@ var (
 		Name:  "preload",
 		Usage: "Comma separated list of JavaScript files to preload into the console",
 	}
+	AllowUnprotectedTxs = cli.BoolFlag{
+		Name:  "rpc.allow-unprotected-txs",
+		Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC",
+	}
 
 	// Network Settings
 	MaxPeersFlag = cli.IntFlag{
@@ -654,17 +645,7 @@ var (
 	}
 	BootnodesFlag = cli.StringFlag{
 		Name:  "bootnodes",
-		Usage: "Comma separated enode URLs for P2P discovery bootstrap (set v4+v5 instead for light servers)",
-		Value: "",
-	}
-	BootnodesV4Flag = cli.StringFlag{
-		Name:  "bootnodesv4",
-		Usage: "Comma separated enode URLs for P2P v4 discovery bootstrap (light server, full nodes)",
-		Value: "",
-	}
-	BootnodesV5Flag = cli.StringFlag{
-		Name:  "bootnodesv5",
-		Usage: "Comma separated enode URLs for P2P v5 discovery bootstrap (light server, light nodes)",
+		Usage: "Comma separated enode URLs for P2P discovery bootstrap",
 		Value: "",
 	}
 	NodeKeyFileFlag = cli.StringFlag{
@@ -706,32 +687,19 @@ var (
 
 	// Gas price oracle settings
 	GpoBlocksFlag = cli.IntFlag{
-		Name:  "gpoblocks",
+		Name:  "gpo.blocks",
 		Usage: "Number of recent blocks to check for gas prices",
-		Value: eth.DefaultConfig.GPO.Blocks,
+		Value: ethconfig.Defaults.GPO.Blocks,
 	}
 	GpoPercentileFlag = cli.IntFlag{
-		Name:  "gpopercentile",
+		Name:  "gpo.percentile",
 		Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices",
-		Value: eth.DefaultConfig.GPO.Percentile,
-	}
-	WhisperEnabledFlag = cli.BoolFlag{
-		Name:  "shh",
-		Usage: "Enable Whisper",
-	}
-	WhisperMaxMessageSizeFlag = cli.IntFlag{
-		Name:  "shh.maxmessagesize",
-		Usage: "Max message size accepted",
-		Value: int(whisper.DefaultMaxMessageSize),
+		Value: ethconfig.Defaults.GPO.Percentile,
 	}
-	WhisperMinPOWFlag = cli.Float64Flag{
-		Name:  "shh.pow",
-		Usage: "Minimum POW accepted",
-		Value: whisper.DefaultMinimumPoW,
-	}
-	WhisperRestrictConnectionBetweenLightClientsFlag = cli.BoolFlag{
-		Name:  "shh.restrict-light",
-		Usage: "Restrict connection between two whisper light clients",
+	GpoMaxGasPriceFlag = cli.Int64Flag{
+		Name:  "gpo.maxprice",
+		Usage: "Maximum gas price will be recommended by gpo",
+		Value: ethconfig.Defaults.GPO.MaxPrice.Int64(),
 	}
 
 	// Metrics flags
@@ -743,6 +711,21 @@ var (
 		Name:  "metrics.expensive",
 		Usage: "Enable expensive metrics collection and reporting",
 	}
+
+	// MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint.
+	// Since the pprof service enables sensitive/vulnerable behavior, this allows a user
+	// to enable a public-OK metrics endpoint without having to worry about ALSO exposing
+	// other profiling behavior or information.
+	MetricsHTTPFlag = cli.StringFlag{
+		Name:  "metrics.addr",
+		Usage: "Enable stand-alone metrics HTTP server listening interface",
+		Value: metrics.DefaultConfig.HTTP,
+	}
+	MetricsPortFlag = cli.IntFlag{
+		Name:  "metrics.port",
+		Usage: "Metrics HTTP server listening port",
+		Value: metrics.DefaultConfig.Port,
+	}
 	MetricsEnableInfluxDBFlag = cli.BoolFlag{
 		Name:  "metrics.influxdb",
 		Usage: "Enable metrics export/push to an external InfluxDB database",
@@ -750,22 +733,22 @@ var (
 	MetricsInfluxDBEndpointFlag = cli.StringFlag{
 		Name:  "metrics.influxdb.endpoint",
 		Usage: "InfluxDB API endpoint to report metrics to",
-		Value: "http://localhost:8086",
+		Value: metrics.DefaultConfig.InfluxDBEndpoint,
 	}
 	MetricsInfluxDBDatabaseFlag = cli.StringFlag{
 		Name:  "metrics.influxdb.database",
 		Usage: "InfluxDB database name to push reported metrics to",
-		Value: "geth",
+		Value: metrics.DefaultConfig.InfluxDBDatabase,
 	}
 	MetricsInfluxDBUsernameFlag = cli.StringFlag{
 		Name:  "metrics.influxdb.username",
 		Usage: "Username to authorize access to the database",
-		Value: "test",
+		Value: metrics.DefaultConfig.InfluxDBUsername,
 	}
 	MetricsInfluxDBPasswordFlag = cli.StringFlag{
 		Name:  "metrics.influxdb.password",
 		Usage: "Password to authorize access to the database",
-		Value: "test",
+		Value: metrics.DefaultConfig.InfluxDBPassword,
 	}
 	// Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB.
 	// For example `host` tag could be used so that we can group all nodes and average a measurement
@@ -774,7 +757,7 @@ var (
 	MetricsInfluxDBTagsFlag = cli.StringFlag{
 		Name:  "metrics.influxdb.tags",
 		Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements",
-		Value: "host=localhost",
+		Value: metrics.DefaultConfig.InfluxDBTags,
 	}
 	EWASMInterpreterFlag = cli.StringFlag{
 		Name:  "vm.ewasm",
@@ -811,6 +794,11 @@ var (
 		Usage: "the p2p port of the nodes in the network",
 		Value: 30311,
 	}
+
+	CatalystFlag = cli.BoolFlag{
+		Name:  "catalyst",
+		Usage: "Catalyst mode (eth2 integration testing)",
+	}
 )
 
 // MakeDataDir retrieves the currently requested data directory, terminating
@@ -818,13 +806,9 @@ var (
 // then a subdirectory of the specified datadir will be used.
 func MakeDataDir(ctx *cli.Context) string {
 	if path := ctx.GlobalString(DataDirFlag.Name); path != "" {
-		if ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name) {
+		if ctx.GlobalBool(RopstenFlag.Name) {
 			// Maintain compatibility with older Geth configurations storing the
 			// Ropsten database in `testnet` instead of `ropsten`.
-			legacyPath := filepath.Join(path, "testnet")
-			if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
-				return legacyPath
-			}
 			return filepath.Join(path, "ropsten")
 		}
 		if ctx.GlobalBool(RinkebyFlag.Name) {
@@ -833,6 +817,9 @@ func MakeDataDir(ctx *cli.Context) string {
 		if ctx.GlobalBool(GoerliFlag.Name) {
 			return filepath.Join(path, "goerli")
 		}
+		if ctx.GlobalBool(YoloV3Flag.Name) {
+			return filepath.Join(path, "yolo-v3")
+		}
 		return path
 	}
 	Fatalf("Cannot determine default data directory, please set manually (--datadir)")
@@ -877,18 +864,16 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) {
 func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
 	urls := params.MainnetBootnodes
 	switch {
-	case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(BootnodesV4Flag.Name):
-		if ctx.GlobalIsSet(BootnodesV4Flag.Name) {
-			urls = splitAndTrim(ctx.GlobalString(BootnodesV4Flag.Name))
-		} else {
-			urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
-		}
-	case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
+	case ctx.GlobalIsSet(BootnodesFlag.Name):
+		urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
+	case ctx.GlobalBool(RopstenFlag.Name):
 		urls = params.RopstenBootnodes
 	case ctx.GlobalBool(RinkebyFlag.Name):
 		urls = params.RinkebyBootnodes
 	case ctx.GlobalBool(GoerliFlag.Name):
 		urls = params.GoerliBootnodes
+	case ctx.GlobalBool(YoloV3Flag.Name):
+		urls = params.YoloV3Bootnodes
 	case cfg.BootstrapNodes != nil:
 		return // already set, don't apply defaults.
 	}
@@ -909,26 +894,18 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
 // setBootstrapNodesV5 creates a list of bootstrap nodes from the command line
 // flags, reverting to pre-configured ones if none have been specified.
 func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) {
-	urls := params.DiscoveryV5Bootnodes
+	urls := params.V5Bootnodes
 	switch {
-	case ctx.GlobalIsSet(BootnodesFlag.Name) || ctx.GlobalIsSet(BootnodesV5Flag.Name):
-		if ctx.GlobalIsSet(BootnodesV5Flag.Name) {
-			urls = splitAndTrim(ctx.GlobalString(BootnodesV5Flag.Name))
-		} else {
-			urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
-		}
-	case ctx.GlobalBool(RinkebyFlag.Name):
-		urls = params.RinkebyBootnodes
-	case ctx.GlobalBool(GoerliFlag.Name):
-		urls = params.GoerliBootnodes
+	case ctx.GlobalIsSet(BootnodesFlag.Name):
+		urls = SplitAndTrim(ctx.GlobalString(BootnodesFlag.Name))
 	case cfg.BootstrapNodesV5 != nil:
 		return // already set, don't apply defaults.
 	}
 
-	cfg.BootstrapNodesV5 = make([]*discv5.Node, 0, len(urls))
+	cfg.BootstrapNodesV5 = make([]*enode.Node, 0, len(urls))
 	for _, url := range urls {
 		if url != "" {
-			node, err := discv5.ParseNode(url)
+			node, err := enode.Parse(enode.ValidSchemes, url)
 			if err != nil {
 				log.Error("Bootstrap URL invalid", "enode", url, "err", err)
 				continue
@@ -957,54 +934,84 @@ func setNAT(ctx *cli.Context, cfg *p2p.Config) {
 	}
 }
 
-// splitAndTrim splits input separated by a comma
+// SplitAndTrim splits input separated by a comma
 // and trims excessive white space from the substrings.
-func splitAndTrim(input string) []string {
-	result := strings.Split(input, ",")
-	for i, r := range result {
-		result[i] = strings.TrimSpace(r)
+func SplitAndTrim(input string) (ret []string) {
+	l := strings.Split(input, ",")
+	for _, r := range l {
+		if r = strings.TrimSpace(r); r != "" {
+			ret = append(ret, r)
+		}
 	}
-	return result
+	return ret
 }
 
 // setHTTP creates the HTTP RPC listener interface string from the set
 // command line flags, returning empty if the HTTP endpoint is disabled.
 func setHTTP(ctx *cli.Context, cfg *node.Config) {
-	if ctx.GlobalBool(RPCEnabledFlag.Name) && cfg.HTTPHost == "" {
+	if ctx.GlobalBool(LegacyRPCEnabledFlag.Name) && cfg.HTTPHost == "" {
+		log.Warn("The flag --rpc is deprecated and will be removed June 2021, please use --http")
 		cfg.HTTPHost = "127.0.0.1"
-		if ctx.GlobalIsSet(RPCListenAddrFlag.Name) {
-			cfg.HTTPHost = ctx.GlobalString(RPCListenAddrFlag.Name)
+		if ctx.GlobalIsSet(LegacyRPCListenAddrFlag.Name) {
+			cfg.HTTPHost = ctx.GlobalString(LegacyRPCListenAddrFlag.Name)
+			log.Warn("The flag --rpcaddr is deprecated and will be removed June 2021, please use --http.addr")
 		}
 	}
-	if ctx.GlobalIsSet(RPCPortFlag.Name) {
-		cfg.HTTPPort = ctx.GlobalInt(RPCPortFlag.Name)
+	if ctx.GlobalBool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" {
+		cfg.HTTPHost = "127.0.0.1"
+		if ctx.GlobalIsSet(HTTPListenAddrFlag.Name) {
+			cfg.HTTPHost = ctx.GlobalString(HTTPListenAddrFlag.Name)
+		}
 	}
-	if ctx.GlobalIsSet(RPCCORSDomainFlag.Name) {
-		cfg.HTTPCors = splitAndTrim(ctx.GlobalString(RPCCORSDomainFlag.Name))
+
+	if ctx.GlobalIsSet(LegacyRPCPortFlag.Name) {
+		cfg.HTTPPort = ctx.GlobalInt(LegacyRPCPortFlag.Name)
+		log.Warn("The flag --rpcport is deprecated and will be removed June 2021, please use --http.port")
 	}
-	if ctx.GlobalIsSet(RPCApiFlag.Name) {
-		cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name))
+	if ctx.GlobalIsSet(HTTPPortFlag.Name) {
+		cfg.HTTPPort = ctx.GlobalInt(HTTPPortFlag.Name)
 	}
-	if ctx.GlobalIsSet(RPCVirtualHostsFlag.Name) {
-		cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name))
+
+	if ctx.GlobalIsSet(LegacyRPCCORSDomainFlag.Name) {
+		cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(LegacyRPCCORSDomainFlag.Name))
+		log.Warn("The flag --rpccorsdomain is deprecated and will be removed June 2021, please use --http.corsdomain")
+	}
+	if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) {
+		cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name))
+	}
+
+	if ctx.GlobalIsSet(LegacyRPCApiFlag.Name) {
+		cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(LegacyRPCApiFlag.Name))
+		log.Warn("The flag --rpcapi is deprecated and will be removed June 2021, please use --http.api")
+	}
+	if ctx.GlobalIsSet(HTTPApiFlag.Name) {
+		cfg.HTTPModules = SplitAndTrim(ctx.GlobalString(HTTPApiFlag.Name))
+	}
+
+	if ctx.GlobalIsSet(LegacyRPCVirtualHostsFlag.Name) {
+		cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(LegacyRPCVirtualHostsFlag.Name))
+		log.Warn("The flag --rpcvhosts is deprecated and will be removed June 2021, please use --http.vhosts")
+	}
+	if ctx.GlobalIsSet(HTTPVirtualHostsFlag.Name) {
+		cfg.HTTPVirtualHosts = SplitAndTrim(ctx.GlobalString(HTTPVirtualHostsFlag.Name))
+	}
+
+	if ctx.GlobalIsSet(HTTPPathPrefixFlag.Name) {
+		cfg.HTTPPathPrefix = ctx.GlobalString(HTTPPathPrefixFlag.Name)
+	}
+	if ctx.GlobalIsSet(AllowUnprotectedTxs.Name) {
+		cfg.AllowUnprotectedTxs = ctx.GlobalBool(AllowUnprotectedTxs.Name)
 	}
 }
 
 // setGraphQL creates the GraphQL listener interface string from the set
 // command line flags, returning empty if the GraphQL endpoint is disabled.
 func setGraphQL(ctx *cli.Context, cfg *node.Config) {
-	if ctx.GlobalBool(GraphQLEnabledFlag.Name) && cfg.GraphQLHost == "" {
-		cfg.GraphQLHost = "127.0.0.1"
-		if ctx.GlobalIsSet(GraphQLListenAddrFlag.Name) {
-			cfg.GraphQLHost = ctx.GlobalString(GraphQLListenAddrFlag.Name)
-		}
-	}
-	cfg.GraphQLPort = ctx.GlobalInt(GraphQLPortFlag.Name)
 	if ctx.GlobalIsSet(GraphQLCORSDomainFlag.Name) {
-		cfg.GraphQLCors = splitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name))
+		cfg.GraphQLCors = SplitAndTrim(ctx.GlobalString(GraphQLCORSDomainFlag.Name))
 	}
 	if ctx.GlobalIsSet(GraphQLVirtualHostsFlag.Name) {
-		cfg.GraphQLVirtualHosts = splitAndTrim(ctx.GlobalString(GraphQLVirtualHostsFlag.Name))
+		cfg.GraphQLVirtualHosts = SplitAndTrim(ctx.GlobalString(GraphQLVirtualHostsFlag.Name))
 	}
 }
 
@@ -1020,11 +1027,17 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
 	if ctx.GlobalIsSet(WSPortFlag.Name) {
 		cfg.WSPort = ctx.GlobalInt(WSPortFlag.Name)
 	}
+
 	if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) {
-		cfg.WSOrigins = splitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name))
+		cfg.WSOrigins = SplitAndTrim(ctx.GlobalString(WSAllowedOriginsFlag.Name))
 	}
+
 	if ctx.GlobalIsSet(WSApiFlag.Name) {
-		cfg.WSModules = splitAndTrim(ctx.GlobalString(WSApiFlag.Name))
+		cfg.WSModules = SplitAndTrim(ctx.GlobalString(WSApiFlag.Name))
+	}
+
+	if ctx.GlobalIsSet(WSPathPrefixFlag.Name) {
+		cfg.WSPathPrefix = ctx.GlobalString(WSPathPrefixFlag.Name)
 	}
 }
 
@@ -1041,10 +1054,7 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
 }
 
 // setLes configures the les server and ultra light client settings from the command line flags.
-func setLes(ctx *cli.Context, cfg *eth.Config) {
-	if ctx.GlobalIsSet(LightLegacyServFlag.Name) {
-		cfg.LightServ = ctx.GlobalInt(LightLegacyServFlag.Name)
-	}
+func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
 	if ctx.GlobalIsSet(LightServeFlag.Name) {
 		cfg.LightServ = ctx.GlobalInt(LightServeFlag.Name)
 	}
@@ -1054,9 +1064,6 @@ func setLes(ctx *cli.Context, cfg *eth.Config) {
 	if ctx.GlobalIsSet(LightEgressFlag.Name) {
 		cfg.LightEgress = ctx.GlobalInt(LightEgressFlag.Name)
 	}
-	if ctx.GlobalIsSet(LightLegacyPeersFlag.Name) {
-		cfg.LightPeers = ctx.GlobalInt(LightLegacyPeersFlag.Name)
-	}
 	if ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
 		cfg.LightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name)
 	}
@@ -1067,17 +1074,23 @@ func setLes(ctx *cli.Context, cfg *eth.Config) {
 		cfg.UltraLightFraction = ctx.GlobalInt(UltraLightFractionFlag.Name)
 	}
 	if cfg.UltraLightFraction <= 0 && cfg.UltraLightFraction > 100 {
-		log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", eth.DefaultConfig.UltraLightFraction)
-		cfg.UltraLightFraction = eth.DefaultConfig.UltraLightFraction
+		log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", ethconfig.Defaults.UltraLightFraction)
+		cfg.UltraLightFraction = ethconfig.Defaults.UltraLightFraction
 	}
 	if ctx.GlobalIsSet(UltraLightOnlyAnnounceFlag.Name) {
 		cfg.UltraLightOnlyAnnounce = ctx.GlobalBool(UltraLightOnlyAnnounceFlag.Name)
 	}
+	if ctx.GlobalIsSet(LightNoPruneFlag.Name) {
+		cfg.LightNoPrune = ctx.GlobalBool(LightNoPruneFlag.Name)
+	}
+	if ctx.GlobalIsSet(LightNoSyncServeFlag.Name) {
+		cfg.LightNoSyncServe = ctx.GlobalBool(LightNoSyncServeFlag.Name)
+	}
 }
 
-// makeDatabaseHandles raises out the number of allowed file handles per process
+// MakeDatabaseHandles raises out the number of allowed file handles per process
 // for Geth and returns half of the allowance to assign to the database.
-func makeDatabaseHandles() int {
+func MakeDatabaseHandles() int {
 	limit, err := fdlimit.Maximum()
 	if err != nil {
 		Fatalf("Failed to retrieve file descriptor allowance: %v", err)
@@ -1116,12 +1129,9 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
 
 // setEtherbase retrieves the etherbase either from the directly specified
 // command line flags or from the keystore if CLI indexed.
-func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
-	// Extract the current etherbase, new flag overriding legacy one
+func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *ethconfig.Config) {
+	// Extract the current etherbase
 	var etherbase string
-	if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) {
-		etherbase = ctx.GlobalString(MinerLegacyEtherbaseFlag.Name)
-	}
 	if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) {
 		etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name)
 	}
@@ -1165,27 +1175,24 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
 	setBootstrapNodesV5(ctx, cfg)
 
 	lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light"
-	lightServer := (ctx.GlobalInt(LightLegacyServFlag.Name) != 0 || ctx.GlobalInt(LightServeFlag.Name) != 0)
+	lightServer := (ctx.GlobalInt(LightServeFlag.Name) != 0)
 
-	lightPeers := ctx.GlobalInt(LightLegacyPeersFlag.Name)
-	if ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
-		lightPeers = ctx.GlobalInt(LightMaxPeersFlag.Name)
-	}
-	if lightClient && !ctx.GlobalIsSet(LightLegacyPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
+	lightPeers := ctx.GlobalInt(LightMaxPeersFlag.Name)
+	if lightClient && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
 		// dynamic default - for clients we use 1/10th of the default for servers
 		lightPeers /= 10
 	}
 
 	if ctx.GlobalIsSet(MaxPeersFlag.Name) {
 		cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name)
-		if lightServer && !ctx.GlobalIsSet(LightLegacyPeersFlag.Name) && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
+		if lightServer && !ctx.GlobalIsSet(LightMaxPeersFlag.Name) {
 			cfg.MaxPeers += lightPeers
 		}
 	} else {
 		if lightServer {
 			cfg.MaxPeers += lightPeers
 		}
-		if lightClient && (ctx.GlobalIsSet(LightLegacyPeersFlag.Name) || ctx.GlobalIsSet(LightMaxPeersFlag.Name)) && cfg.MaxPeers < lightPeers {
+		if lightClient && ctx.GlobalIsSet(LightMaxPeersFlag.Name) && cfg.MaxPeers < lightPeers {
 			cfg.MaxPeers = lightPeers
 		}
 	}
@@ -1223,10 +1230,11 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
 		cfg.NetRestrict = list
 	}
 
-	if ctx.GlobalBool(DeveloperFlag.Name) {
+	if ctx.GlobalBool(DeveloperFlag.Name) || ctx.GlobalBool(CatalystFlag.Name) {
 		// --dev mode can't use p2p networking.
 		cfg.MaxPeers = 0
-		cfg.ListenAddr = ":0"
+		cfg.ListenAddr = ""
+		cfg.NoDial = true
 		cfg.NoDiscovery = true
 		cfg.DiscoveryV5 = false
 	}
@@ -1253,8 +1261,11 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
 	if ctx.GlobalIsSet(LightKDFFlag.Name) {
 		cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name)
 	}
-	if ctx.GlobalIsSet(NoUSBFlag.Name) {
-		cfg.NoUSB = ctx.GlobalBool(NoUSBFlag.Name)
+	if ctx.GlobalIsSet(NoUSBFlag.Name) || cfg.NoUSB {
+		log.Warn("Option nousb is deprecated and USB is deactivated by default. Use --usb to enable")
+	}
+	if ctx.GlobalIsSet(USBFlag.Name) {
+		cfg.USB = ctx.GlobalBool(USBFlag.Name)
 	}
 	if ctx.GlobalIsSet(DirectBroadcastFlag.Name) {
 		cfg.DirectBroadcast = ctx.GlobalBool(DirectBroadcastFlag.Name)
@@ -1293,7 +1304,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) {
 		cfg.DataDir = ctx.GlobalString(DataDirFlag.Name)
 	case ctx.GlobalBool(DeveloperFlag.Name):
 		cfg.DataDir = "" // unless explicitly requested, use memory databases
-	case (ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name)) && cfg.DataDir == node.DefaultDataDir():
+	case ctx.GlobalBool(RopstenFlag.Name) && cfg.DataDir == node.DefaultDataDir():
 		// Maintain compatibility with older Geth configurations storing the
 		// Ropsten database in `testnet` instead of `ropsten`.
 		legacyPath := filepath.Join(node.DefaultDataDir(), "testnet")
@@ -1303,20 +1314,33 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) {
 		} else {
 			cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten")
 		}
+
+		cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten")
 	case ctx.GlobalBool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir():
 		cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby")
 	case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir():
 		cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli")
+	case ctx.GlobalBool(YoloV3Flag.Name) && cfg.DataDir == node.DefaultDataDir():
+		cfg.DataDir = filepath.Join(node.DefaultDataDir(), "yolo-v3")
 	}
 }
 
-func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
+func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) {
+	// If we are running the light client, apply another group
+	// settings for gas oracle.
+	if light {
+		cfg.Blocks = ethconfig.LightClientGPO.Blocks
+		cfg.Percentile = ethconfig.LightClientGPO.Percentile
+	}
 	if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
 		cfg.Blocks = ctx.GlobalInt(GpoBlocksFlag.Name)
 	}
 	if ctx.GlobalIsSet(GpoPercentileFlag.Name) {
 		cfg.Percentile = ctx.GlobalInt(GpoPercentileFlag.Name)
 	}
+	if ctx.GlobalIsSet(GpoMaxGasPriceFlag.Name) {
+		cfg.MaxPrice = big.NewInt(ctx.GlobalInt64(GpoMaxGasPriceFlag.Name))
+	}
 }
 
 func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
@@ -1362,7 +1386,7 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
 	}
 }
 
-func setEthash(ctx *cli.Context, cfg *eth.Config) {
+func setEthash(ctx *cli.Context, cfg *ethconfig.Config) {
 	if ctx.GlobalIsSet(EthashCacheDirFlag.Name) {
 		cfg.Ethash.CacheDir = ctx.GlobalString(EthashCacheDirFlag.Name)
 	}
@@ -1393,39 +1417,31 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
 	if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
 		cfg.Notify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
 	}
-	if ctx.GlobalIsSet(MinerLegacyExtraDataFlag.Name) {
-		cfg.ExtraData = []byte(ctx.GlobalString(MinerLegacyExtraDataFlag.Name))
-	}
+	cfg.NotifyFull = ctx.GlobalBool(MinerNotifyFullFlag.Name)
 	if ctx.GlobalIsSet(MinerExtraDataFlag.Name) {
 		cfg.ExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name))
 	}
-	if ctx.GlobalIsSet(MinerLegacyGasTargetFlag.Name) {
-		cfg.GasFloor = ctx.GlobalUint64(MinerLegacyGasTargetFlag.Name)
-	}
 	if ctx.GlobalIsSet(MinerGasTargetFlag.Name) {
 		cfg.GasFloor = ctx.GlobalUint64(MinerGasTargetFlag.Name)
 	}
 	if ctx.GlobalIsSet(MinerGasLimitFlag.Name) {
 		cfg.GasCeil = ctx.GlobalUint64(MinerGasLimitFlag.Name)
 	}
-	if ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
-		cfg.GasPrice = GlobalBig(ctx, MinerLegacyGasPriceFlag.Name)
-	}
 	if ctx.GlobalIsSet(MinerGasPriceFlag.Name) {
 		cfg.GasPrice = GlobalBig(ctx, MinerGasPriceFlag.Name)
 	}
 	if ctx.GlobalIsSet(MinerRecommitIntervalFlag.Name) {
-		cfg.Recommit = ctx.Duration(MinerRecommitIntervalFlag.Name)
+		cfg.Recommit = ctx.GlobalDuration(MinerRecommitIntervalFlag.Name)
 	}
 	if ctx.GlobalIsSet(MinerDelayLeftoverFlag.Name) {
 		cfg.DelayLeftOver = ctx.Duration(MinerDelayLeftoverFlag.Name)
 	}
 	if ctx.GlobalIsSet(MinerNoVerfiyFlag.Name) {
-		cfg.Noverify = ctx.Bool(MinerNoVerfiyFlag.Name)
+		cfg.Noverify = ctx.GlobalBool(MinerNoVerfiyFlag.Name)
 	}
 }
 
-func setWhitelist(ctx *cli.Context, cfg *eth.Config) {
+func setWhitelist(ctx *cli.Context, cfg *ethconfig.Config) {
 	whitelist := ctx.GlobalString(WhitelistFlag.Name)
 	if whitelist == "" {
 		return
@@ -1489,38 +1505,51 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) {
 	}
 }
 
-// SetShhConfig applies shh-related command line flags to the config.
-func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
-	if ctx.GlobalIsSet(WhisperMaxMessageSizeFlag.Name) {
-		cfg.MaxMessageSize = uint32(ctx.GlobalUint(WhisperMaxMessageSizeFlag.Name))
-	}
-	if ctx.GlobalIsSet(WhisperMinPOWFlag.Name) {
-		cfg.MinimumAcceptedPOW = ctx.GlobalFloat64(WhisperMinPOWFlag.Name)
-	}
-	if ctx.GlobalIsSet(WhisperRestrictConnectionBetweenLightClientsFlag.Name) {
-		cfg.RestrictConnectionBetweenLightClients = true
-	}
-}
-
 // 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 *ethconfig.Config) {
 	// Avoid conflicting network flags
-	CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag)
-	CheckExclusive(ctx, LightLegacyServFlag, LightServeFlag, SyncModeFlag, "light")
+	CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, YoloV3Flag)
+	CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light")
 	CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
-
+	if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 {
+		ctx.GlobalSet(TxLookupLimitFlag.Name, "0")
+		log.Warn("Disable transaction unindexing for archive node")
+	}
+	if ctx.GlobalIsSet(LightServeFlag.Name) && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 {
+		log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited")
+	}
 	var ks *keystore.KeyStore
 	if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
 		ks = keystores[0].(*keystore.KeyStore)
 	}
 	setEtherbase(ctx, ks, cfg)
-	setGPO(ctx, &cfg.GPO)
+	setGPO(ctx, &cfg.GPO, ctx.GlobalString(SyncModeFlag.Name) == "light")
 	setTxPool(ctx, &cfg.TxPool)
 	setEthash(ctx, cfg)
 	setMiner(ctx, &cfg.Miner)
 	setWhitelist(ctx, cfg)
 	setLes(ctx, cfg)
 
+	// Cap the cache allowance and tune the garbage collector
+	mem, err := gopsutil.VirtualMemory()
+	if err == nil {
+		if 32<<(^uintptr(0)>>63) == 32 && mem.Total > 2*1024*1024*1024 {
+			log.Warn("Lowering memory allowance on 32bit arch", "available", mem.Total/1024/1024, "addressable", 2*1024)
+			mem.Total = 2 * 1024 * 1024 * 1024
+		}
+		allowance := int(mem.Total / 1024 / 1024 / 3)
+		if cache := ctx.GlobalInt(CacheFlag.Name); cache > allowance {
+			log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
+			ctx.GlobalSet(CacheFlag.Name, strconv.Itoa(allowance))
+		}
+	}
+	// Ensure Go's GC ignores the database cache for trigger percentage
+	cache := ctx.GlobalInt(CacheFlag.Name)
+	gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))
+
+	log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
+	godebug.SetGCPercent(int(gogc))
+
 	if ctx.GlobalIsSet(SyncModeFlag.Name) {
 		cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
 	}
@@ -1530,7 +1559,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
 	if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) {
 		cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
 	}
-	cfg.DatabaseHandles = makeDatabaseHandles()
+	cfg.DatabaseHandles = MakeDatabaseHandles()
 	if ctx.GlobalIsSet(AncientFlag.Name) {
 		cfg.DatabaseFreezer = ctx.GlobalString(AncientFlag.Name)
 	}
@@ -1550,17 +1579,38 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
 	if ctx.GlobalIsSet(CacheNoPrefetchFlag.Name) {
 		cfg.NoPrefetch = ctx.GlobalBool(CacheNoPrefetchFlag.Name)
 	}
+	// Read the value from the flag no matter if it's set or not.
+	cfg.Preimages = ctx.GlobalBool(CachePreimagesFlag.Name)
+	if cfg.NoPruning && !cfg.Preimages {
+		cfg.Preimages = true
+		log.Info("Enabling recording of key preimages since archive mode is used")
+	}
+	if ctx.GlobalIsSet(TxLookupLimitFlag.Name) {
+		cfg.TxLookupLimit = ctx.GlobalUint64(TxLookupLimitFlag.Name)
+	}
 	if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
 		cfg.TrieCleanCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100
 	}
+	if ctx.GlobalIsSet(CacheTrieJournalFlag.Name) {
+		cfg.TrieCleanCacheJournal = ctx.GlobalString(CacheTrieJournalFlag.Name)
+	}
+	if ctx.GlobalIsSet(CacheTrieRejournalFlag.Name) {
+		cfg.TrieCleanCacheRejournal = ctx.GlobalDuration(CacheTrieRejournalFlag.Name)
+	}
 	if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
 		cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
 	}
 	if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) {
 		cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100
 	}
-	if !ctx.GlobalIsSet(SnapshotFlag.Name) {
-		cfg.SnapshotCache = 0 // Disabled
+	if !ctx.GlobalBool(SnapshotFlag.Name) {
+		// If snap-sync is requested, this flag is also required
+		if cfg.SyncMode == downloader.SnapSync {
+			log.Info("Snap sync requested, enabling --snapshot")
+		} else {
+			cfg.TrieCleanCache += cfg.SnapshotCache
+			cfg.SnapshotCache = 0 // Disabled
+		}
 	}
 	if ctx.GlobalIsSet(DocRootFlag.Name) {
 		cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
@@ -1577,145 +1627,164 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
 	if ctx.GlobalIsSet(EVMInterpreterFlag.Name) {
 		cfg.EVMInterpreter = ctx.GlobalString(EVMInterpreterFlag.Name)
 	}
-	if ctx.GlobalIsSet(RPCGlobalGasCap.Name) {
-		cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name))
+	if ctx.GlobalIsSet(RPCGlobalGasCapFlag.Name) {
+		cfg.RPCGasCap = ctx.GlobalUint64(RPCGlobalGasCapFlag.Name)
+	}
+	if cfg.RPCGasCap != 0 {
+		log.Info("Set global gas cap", "cap", cfg.RPCGasCap)
+	} else {
+		log.Info("Global gas cap disabled")
+	}
+	if ctx.GlobalIsSet(RPCGlobalTxFeeCapFlag.Name) {
+		cfg.RPCTxFeeCap = ctx.GlobalFloat64(RPCGlobalTxFeeCapFlag.Name)
 	}
-	if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) {
+	if ctx.GlobalIsSet(NoDiscoverFlag.Name) {
+		cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
+	} else if ctx.GlobalIsSet(DNSDiscoveryFlag.Name) {
 		urls := ctx.GlobalString(DNSDiscoveryFlag.Name)
 		if urls == "" {
-			cfg.DiscoveryURLs = []string{}
+			cfg.EthDiscoveryURLs = []string{}
 		} else {
-			cfg.DiscoveryURLs = splitAndTrim(urls)
+			cfg.EthDiscoveryURLs = SplitAndTrim(urls)
 		}
 	}
-
 	// Override any default configs for hard coded networks.
 	switch {
-	case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
+	case ctx.GlobalBool(MainnetFlag.Name):
+		if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
+			cfg.NetworkId = 1
+		}
+		cfg.Genesis = core.DefaultGenesisBlock()
+		SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
+	case ctx.GlobalBool(RopstenFlag.Name):
 		if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
 			cfg.NetworkId = 3
 		}
 		cfg.Genesis = core.DefaultRopstenGenesisBlock()
-		setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.RopstenGenesisHash])
+		SetDNSDiscoveryDefaults(cfg, params.RopstenGenesisHash)
 	case ctx.GlobalBool(RinkebyFlag.Name):
 		if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
 			cfg.NetworkId = 4
 		}
 		cfg.Genesis = core.DefaultRinkebyGenesisBlock()
-		setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.RinkebyGenesisHash])
+		SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash)
 	case ctx.GlobalBool(GoerliFlag.Name):
 		if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
 			cfg.NetworkId = 5
 		}
 		cfg.Genesis = core.DefaultGoerliGenesisBlock()
-		setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.GoerliGenesisHash])
+		SetDNSDiscoveryDefaults(cfg, params.GoerliGenesisHash)
+	case ctx.GlobalBool(YoloV3Flag.Name):
+		if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
+			cfg.NetworkId = new(big.Int).SetBytes([]byte("yolov3x")).Uint64() // "yolov3x"
+		}
+		cfg.Genesis = core.DefaultYoloV3GenesisBlock()
 	case ctx.GlobalBool(DeveloperFlag.Name):
 		if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
 			cfg.NetworkId = 1337
 		}
 		// Create new developer account or reuse existing one
 		var (
-			developer accounts.Account
-			err       error
+			developer  accounts.Account
+			passphrase string
+			err        error
 		)
-		if accs := ks.Accounts(); len(accs) > 0 {
+		if list := MakePasswordList(ctx); len(list) > 0 {
+			// Just take the first value. Although the function returns a possible multiple values and
+			// some usages iterate through them as attempts, that doesn't make sense in this setting,
+			// when we're definitely concerned with only one account.
+			passphrase = list[0]
+		}
+		// setEtherbase has been called above, configuring the miner address from command line flags.
+		if cfg.Miner.Etherbase != (common.Address{}) {
+			developer = accounts.Account{Address: cfg.Miner.Etherbase}
+		} else if accs := ks.Accounts(); len(accs) > 0 {
 			developer = ks.Accounts()[0]
 		} else {
-			developer, err = ks.NewAccount("")
+			developer, err = ks.NewAccount(passphrase)
 			if err != nil {
 				Fatalf("Failed to create developer account: %v", err)
 			}
 		}
-		if err := ks.Unlock(developer, ""); err != nil {
+		if err := ks.Unlock(developer, passphrase); err != nil {
 			Fatalf("Failed to unlock developer account: %v", err)
 		}
 		log.Info("Using developer account", "address", developer.Address)
 
+		// Create a new developer genesis block or reuse existing one
 		cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
-		if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
+		if ctx.GlobalIsSet(DataDirFlag.Name) {
+			// Check if we have an already initialized chain and fall back to
+			// that if so. Otherwise we need to generate a new genesis spec.
+			chaindb := MakeChainDatabase(ctx, stack, false) // TODO (MariusVanDerWijden) make this read only
+			if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) {
+				cfg.Genesis = nil // fallback to db content
+			}
+			chaindb.Close()
+		}
+		if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) {
 			cfg.Miner.GasPrice = big.NewInt(1)
 		}
 	default:
 		if cfg.NetworkId == 1 {
-			setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.MainnetGenesisHash])
+			SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
 		}
 	}
 }
 
-// setDNSDiscoveryDefaults configures DNS discovery with the given URL if
+// SetDNSDiscoveryDefaults configures DNS discovery with the given URL if
 // no URLs are set.
-func setDNSDiscoveryDefaults(cfg *eth.Config, url string) {
-	if cfg.DiscoveryURLs != nil {
-		return
+func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
+	if cfg.EthDiscoveryURLs != nil {
+		return // already set through flags/config
+	}
+	protocol := "all"
+	if cfg.SyncMode == downloader.LightSync {
+		protocol = "les"
+	}
+	if url := params.KnownDNSNetwork(genesis, protocol); url != "" {
+		cfg.EthDiscoveryURLs = []string{url}
+		cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs
 	}
-	cfg.DiscoveryURLs = []string{url}
 }
 
 // RegisterEthService adds an Ethereum client to the stack.
-func RegisterEthService(stack *node.Node, cfg *eth.Config) {
-	var err error
+// The second return value is the full node instance, which may be nil if the
+// node is running as a light client.
+func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
 	if cfg.SyncMode == downloader.LightSync {
-		err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-			return les.New(ctx, cfg)
-		})
-	} else {
-		err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-			fullNode, err := eth.New(ctx, cfg)
-			if fullNode != nil && cfg.LightServ > 0 {
-				ls, _ := les.NewLesServer(fullNode, cfg)
-				fullNode.AddLesServer(ls)
-			}
-			return fullNode, err
-		})
+		backend, err := les.New(stack, cfg)
+		if err != nil {
+			Fatalf("Failed to register the Ethereum service: %v", err)
+		}
+		stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
+		return backend.ApiBackend, nil
 	}
+	backend, err := eth.New(stack, cfg)
 	if err != nil {
 		Fatalf("Failed to register the Ethereum service: %v", err)
 	}
-}
-
-// RegisterShhService configures Whisper and adds it to the given node.
-func RegisterShhService(stack *node.Node, cfg *whisper.Config) {
-	if err := stack.Register(func(n *node.ServiceContext) (node.Service, error) {
-		return whisper.New(cfg), nil
-	}); err != nil {
-		Fatalf("Failed to register the Whisper service: %v", err)
+	if cfg.LightServ > 0 {
+		_, err := les.NewLesServer(stack, backend, cfg)
+		if err != nil {
+			Fatalf("Failed to create the LES server: %v", err)
+		}
 	}
+	stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
+	return backend.APIBackend, backend
 }
 
 // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to
 // the given node.
-func RegisterEthStatsService(stack *node.Node, url string) {
-	if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-		// Retrieve both eth and les services
-		var ethServ *eth.Ethereum
-		ctx.Service(ðServ)
-
-		var lesServ *les.LightEthereum
-		ctx.Service(&lesServ)
-
-		// Let ethstats use whichever is not nil
-		return ethstats.New(url, ethServ, lesServ)
-	}); err != nil {
+func RegisterEthStatsService(stack *node.Node, backend ethapi.Backend, url string) {
+	if err := ethstats.New(stack, backend, backend.Engine(), url); err != nil {
 		Fatalf("Failed to register the Ethereum Stats service: %v", err)
 	}
 }
 
 // RegisterGraphQLService is a utility function to construct a new service and register it against a node.
-func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) {
-	if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-		// Try to construct the GraphQL service backed by a full node
-		var ethServ *eth.Ethereum
-		if err := ctx.Service(ðServ); err == nil {
-			return graphql.New(ethServ.APIBackend, endpoint, cors, vhosts, timeouts)
-		}
-		// Try to construct the GraphQL service backed by a light node
-		var lesServ *les.LightEthereum
-		if err := ctx.Service(&lesServ); err == nil {
-			return graphql.New(lesServ.ApiBackend, endpoint, cors, vhosts, timeouts)
-		}
-		// Well, this should not have happened, bail out
-		return nil, errors.New("no Ethereum service")
-	}); err != nil {
+func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, cfg node.Config) {
+	if err := graphql.New(stack, backend, cfg.GraphQLCors, cfg.GraphQLVirtualHosts); err != nil {
 		Fatalf("Failed to register the GraphQL service: %v", err)
 	}
 }
@@ -1723,6 +1792,7 @@ func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []st
 func SetupMetrics(ctx *cli.Context) {
 	if metrics.Enabled {
 		log.Info("Enabling metrics collection")
+
 		var (
 			enableExport = ctx.GlobalBool(MetricsEnableInfluxDBFlag.Name)
 			endpoint     = ctx.GlobalString(MetricsInfluxDBEndpointFlag.Name)
@@ -1738,6 +1808,12 @@ func SetupMetrics(ctx *cli.Context) {
 
 			go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "geth.", tagsMap)
 		}
+
+		if ctx.GlobalIsSet(MetricsHTTPFlag.Name) {
+			address := fmt.Sprintf("%s:%d", ctx.GlobalString(MetricsHTTPFlag.Name), ctx.GlobalInt(MetricsPortFlag.Name))
+			log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address)
+			exp.Setup(address)
+		}
 	}
 }
 
@@ -1759,16 +1835,21 @@ func SplitTagsFlag(tagsFlag string) map[string]string {
 }
 
 // MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
-func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
+func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.Database {
 	var (
 		cache   = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
-		handles = makeDatabaseHandles()
+		handles = MakeDatabaseHandles()
+
+		err     error
+		chainDb ethdb.Database
 	)
-	name := "chaindata"
 	if ctx.GlobalString(SyncModeFlag.Name) == "light" {
-		name = "lightchaindata"
+		name := "lightchaindata"
+		chainDb, err = stack.OpenDatabase(name, cache, handles, "", readonly)
+	} else {
+		name := "chaindata"
+		chainDb, err = stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly)
 	}
-	chainDb, err := stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "")
 	if err != nil {
 		Fatalf("Could not open database: %v", err)
 	}
@@ -1778,12 +1859,16 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
 func MakeGenesis(ctx *cli.Context) *core.Genesis {
 	var genesis *core.Genesis
 	switch {
-	case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name):
+	case ctx.GlobalBool(MainnetFlag.Name):
+		genesis = core.DefaultGenesisBlock()
+	case ctx.GlobalBool(RopstenFlag.Name):
 		genesis = core.DefaultRopstenGenesisBlock()
 	case ctx.GlobalBool(RinkebyFlag.Name):
 		genesis = core.DefaultRinkebyGenesisBlock()
 	case ctx.GlobalBool(GoerliFlag.Name):
 		genesis = core.DefaultGoerliGenesisBlock()
+	case ctx.GlobalBool(YoloV3Flag.Name):
+		genesis = core.DefaultYoloV3GenesisBlock()
 	case ctx.GlobalBool(DeveloperFlag.Name):
 		Fatalf("Developer chains are ephemeral")
 	}
@@ -1793,7 +1878,7 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis {
 // MakeChain creates a chain manager from set command line flags.
 func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chainDb ethdb.Database) {
 	var err error
-	chainDb = MakeChainDatabase(ctx, stack)
+	chainDb = MakeChainDatabase(ctx, stack, false) // TODO(rjl493456442) support read-only database
 	config, _, err := core.SetupGenesisBlock(chainDb, MakeGenesis(ctx))
 	if err != nil {
 		Fatalf("%v", err)
@@ -1805,14 +1890,14 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
 		engine = ethash.NewFaker()
 		if !ctx.GlobalBool(FakePoWFlag.Name) {
 			engine = ethash.New(ethash.Config{
-				CacheDir:         stack.ResolvePath(eth.DefaultConfig.Ethash.CacheDir),
-				CachesInMem:      eth.DefaultConfig.Ethash.CachesInMem,
-				CachesOnDisk:     eth.DefaultConfig.Ethash.CachesOnDisk,
-				CachesLockMmap:   eth.DefaultConfig.Ethash.CachesLockMmap,
-				DatasetDir:       stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir),
-				DatasetsInMem:    eth.DefaultConfig.Ethash.DatasetsInMem,
-				DatasetsOnDisk:   eth.DefaultConfig.Ethash.DatasetsOnDisk,
-				DatasetsLockMmap: eth.DefaultConfig.Ethash.DatasetsLockMmap,
+				CacheDir:         stack.ResolvePath(ethconfig.Defaults.Ethash.CacheDir),
+				CachesInMem:      ethconfig.Defaults.Ethash.CachesInMem,
+				CachesOnDisk:     ethconfig.Defaults.Ethash.CachesOnDisk,
+				CachesLockMmap:   ethconfig.Defaults.Ethash.CachesLockMmap,
+				DatasetDir:       stack.ResolvePath(ethconfig.Defaults.Ethash.DatasetDir),
+				DatasetsInMem:    ethconfig.Defaults.Ethash.DatasetsInMem,
+				DatasetsOnDisk:   ethconfig.Defaults.Ethash.DatasetsOnDisk,
+				DatasetsLockMmap: ethconfig.Defaults.Ethash.DatasetsLockMmap,
 			}, nil, false)
 		}
 	}
@@ -1820,14 +1905,19 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
 		Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
 	}
 	cache := &core.CacheConfig{
-		TrieCleanLimit:      eth.DefaultConfig.TrieCleanCache,
+		TrieCleanLimit:      ethconfig.Defaults.TrieCleanCache,
 		TrieCleanNoPrefetch: ctx.GlobalBool(CacheNoPrefetchFlag.Name),
-		TrieDirtyLimit:      eth.DefaultConfig.TrieDirtyCache,
+		TrieDirtyLimit:      ethconfig.Defaults.TrieDirtyCache,
 		TrieDirtyDisabled:   ctx.GlobalString(GCModeFlag.Name) == "archive",
-		TrieTimeLimit:       eth.DefaultConfig.TrieTimeout,
-		SnapshotLimit:       eth.DefaultConfig.SnapshotCache,
+		TrieTimeLimit:       ethconfig.Defaults.TrieTimeout,
+		SnapshotLimit:       ethconfig.Defaults.SnapshotCache,
+		Preimages:           ctx.GlobalBool(CachePreimagesFlag.Name),
 	}
-	if !ctx.GlobalIsSet(SnapshotFlag.Name) {
+	if cache.TrieDirtyDisabled && !cache.Preimages {
+		cache.Preimages = true
+		log.Info("Enabling recording of key preimages since archive mode is used")
+	}
+	if !ctx.GlobalBool(SnapshotFlag.Name) {
 		cache.SnapshotLimit = 0 // Disabled
 	}
 	if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) {
@@ -1837,7 +1927,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
 		cache.TrieDirtyLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
 	}
 	vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name)}
-	chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil)
+
+	// TODO(rjl493456442) disable snapshot generation/wiping if the chain is read only.
+	// Disable transaction indexing/unindexing by default.
+	chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg, nil, nil)
 	if err != nil {
 		Fatalf("Can't create BlockChain: %v", err)
 	}
@@ -1854,9 +1947,8 @@ func MakeConsolePreloads(ctx *cli.Context) []string {
 	// Otherwise resolve absolute paths and return them
 	var preloads []string
 
-	assets := ctx.GlobalString(JSpathFlag.Name)
 	for _, file := range strings.Split(ctx.GlobalString(PreloadJSFlag.Name), ",") {
-		preloads = append(preloads, common.AbsolutePath(assets, strings.TrimSpace(file)))
+		preloads = append(preloads, strings.TrimSpace(file))
 	}
 	return preloads
 }
diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go
new file mode 100644
index 0000000000..fb5fde6576
--- /dev/null
+++ b/cmd/utils/flags_legacy.go
@@ -0,0 +1,80 @@
+// 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 .
+
+package utils
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/node"
+	"gopkg.in/urfave/cli.v1"
+)
+
+var ShowDeprecated = cli.Command{
+	Action:      showDeprecated,
+	Name:        "show-deprecated-flags",
+	Usage:       "Show flags that have been deprecated",
+	ArgsUsage:   " ",
+	Category:    "MISCELLANEOUS COMMANDS",
+	Description: "Show flags that have been deprecated and will soon be removed",
+}
+
+var DeprecatedFlags = []cli.Flag{}
+
+var (
+	// (Deprecated May 2020, shown in aliased flags section)
+	LegacyRPCEnabledFlag = cli.BoolFlag{
+		Name:  "rpc",
+		Usage: "Enable the HTTP-RPC server (deprecated and will be removed June 2021, use --http)",
+	}
+	LegacyRPCListenAddrFlag = cli.StringFlag{
+		Name:  "rpcaddr",
+		Usage: "HTTP-RPC server listening interface (deprecated and will be removed June 2021, use --http.addr)",
+		Value: node.DefaultHTTPHost,
+	}
+	LegacyRPCPortFlag = cli.IntFlag{
+		Name:  "rpcport",
+		Usage: "HTTP-RPC server listening port (deprecated and will be removed June 2021, use --http.port)",
+		Value: node.DefaultHTTPPort,
+	}
+	LegacyRPCCORSDomainFlag = cli.StringFlag{
+		Name:  "rpccorsdomain",
+		Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated and will be removed June 2021, use --http.corsdomain)",
+		Value: "",
+	}
+	LegacyRPCVirtualHostsFlag = cli.StringFlag{
+		Name:  "rpcvhosts",
+		Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (deprecated and will be removed June 2021, use --http.vhosts)",
+		Value: strings.Join(node.DefaultConfig.HTTPVirtualHosts, ","),
+	}
+	LegacyRPCApiFlag = cli.StringFlag{
+		Name:  "rpcapi",
+		Usage: "API's offered over the HTTP-RPC interface (deprecated and will be removed June 2021, use --http.api)",
+		Value: "",
+	}
+)
+
+// showDeprecated displays deprecated flags that will be soon removed from the codebase.
+func showDeprecated(*cli.Context) {
+	fmt.Println("--------------------------------------------------------------------")
+	fmt.Println("The following flags are deprecated and will be removed in the future!")
+	fmt.Println("--------------------------------------------------------------------")
+	fmt.Println()
+	// TODO remove when there are newly deprecated flags
+	fmt.Println("no deprecated flags to show at this time")
+	fmt.Println()
+}
diff --git a/cmd/utils/prompt.go b/cmd/utils/prompt.go
new file mode 100644
index 0000000000..5c7cbabcb0
--- /dev/null
+++ b/cmd/utils/prompt.go
@@ -0,0 +1,62 @@
+// Copyright 2020 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// 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 .
+
+// Package utils contains internal helper functions for go-ethereum commands.
+package utils
+
+import (
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/console/prompt"
+)
+
+// GetPassPhrase displays the given text(prompt) to the user and requests some textual
+// data to be entered, but one which must not be echoed out into the terminal.
+// The method returns the input provided by the user.
+func GetPassPhrase(text string, confirmation bool) string {
+	if text != "" {
+		fmt.Println(text)
+	}
+	password, err := prompt.Stdin.PromptPassword("Password: ")
+	if err != nil {
+		Fatalf("Failed to read password: %v", err)
+	}
+	if confirmation {
+		confirm, err := prompt.Stdin.PromptPassword("Repeat password: ")
+		if err != nil {
+			Fatalf("Failed to read password confirmation: %v", err)
+		}
+		if password != confirm {
+			Fatalf("Passwords do not match")
+		}
+	}
+	return password
+}
+
+// GetPassPhraseWithList retrieves the password associated with an account, either fetched
+// from a list of preloaded passphrases, or requested interactively from the user.
+func GetPassPhraseWithList(text string, confirmation bool, index int, passwords []string) string {
+	// If a list of passwords was supplied, retrieve from them
+	if len(passwords) > 0 {
+		if index < len(passwords) {
+			return passwords[index]
+		}
+		return passwords[len(passwords)-1]
+	}
+	// Otherwise prompt the user for the password
+	password := GetPassPhrase(text, confirmation)
+	return password
+}
diff --git a/cmd/utils/prompt_test.go b/cmd/utils/prompt_test.go
new file mode 100644
index 0000000000..62ea75a3f6
--- /dev/null
+++ b/cmd/utils/prompt_test.go
@@ -0,0 +1,74 @@
+// Copyright 2020 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// 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 .
+
+// Package utils contains internal helper functions for go-ethereum commands.
+package utils
+
+import (
+	"testing"
+)
+
+func TestGetPassPhraseWithList(t *testing.T) {
+	type args struct {
+		text         string
+		confirmation bool
+		index        int
+		passwords    []string
+	}
+	tests := []struct {
+		name string
+		args args
+		want string
+	}{
+		{
+			"test1",
+			args{
+				"text1",
+				false,
+				0,
+				[]string{"zero", "one", "two"},
+			},
+			"zero",
+		},
+		{
+			"test2",
+			args{
+				"text2",
+				false,
+				5,
+				[]string{"zero", "one", "two"},
+			},
+			"two",
+		},
+		{
+			"test3",
+			args{
+				"text3",
+				true,
+				1,
+				[]string{"zero", "one", "two"},
+			},
+			"one",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := GetPassPhraseWithList(tt.args.text, tt.args.confirmation, tt.args.index, tt.args.passwords); got != tt.want {
+				t.Errorf("GetPassPhraseWithList() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go
deleted file mode 100644
index a94ed947d0..0000000000
--- a/cmd/wnode/main.go
+++ /dev/null
@@ -1,774 +0,0 @@
-// Copyright 2017 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 .
-
-// This is a simple Whisper node. It could be used as a stand-alone bootstrap node.
-// Also, could be used for different test and diagnostics purposes.
-
-package main
-
-import (
-	"bufio"
-	"crypto/ecdsa"
-	crand "crypto/rand"
-	"crypto/sha512"
-	"encoding/binary"
-	"encoding/hex"
-	"flag"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/ethereum/go-ethereum/cmd/utils"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	"github.com/ethereum/go-ethereum/console"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/p2p/nat"
-	"github.com/ethereum/go-ethereum/whisper/mailserver"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
-	"golang.org/x/crypto/pbkdf2"
-)
-
-const quitCommand = "~Q"
-const entropySize = 32
-
-// singletons
-var (
-	server     *p2p.Server
-	shh        *whisper.Whisper
-	done       chan struct{}
-	mailServer mailserver.WMailServer
-	entropy    [entropySize]byte
-
-	input = bufio.NewReader(os.Stdin)
-)
-
-// encryption
-var (
-	symKey  []byte
-	pub     *ecdsa.PublicKey
-	asymKey *ecdsa.PrivateKey
-	nodeid  *ecdsa.PrivateKey
-	topic   whisper.TopicType
-
-	asymKeyID    string
-	asymFilterID string
-	symFilterID  string
-	symPass      string
-	msPassword   string
-)
-
-// cmd arguments
-var (
-	bootstrapMode  = flag.Bool("standalone", false, "boostrap node: don't initiate connection to peers, just wait for incoming connections")
-	forwarderMode  = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither encrypt nor decrypt messages")
-	mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand")
-	requestMail    = flag.Bool("mailclient", false, "request expired messages from the bootstrap server")
-	asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption")
-	generateKey    = flag.Bool("generatekey", false, "generate and show the private key")
-	fileExMode     = flag.Bool("fileexchange", false, "file exchange mode")
-	fileReader     = flag.Bool("filereader", false, "load and decrypt messages saved as files, display as plain text")
-	testMode       = flag.Bool("test", false, "use of predefined parameters for diagnostics (password, etc.)")
-	echoMode       = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics")
-
-	argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level")
-	argTTL       = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
-	argWorkTime  = flag.Uint("work", 5, "work time in seconds")
-	argMaxSize   = flag.Uint("maxsize", uint(whisper.DefaultMaxMessageSize), "max size of message")
-	argPoW       = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)")
-	argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request")
-
-	argIP      = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)")
-	argPub     = flag.String("pub", "", "public key for asymmetric encryption")
-	argDBPath  = flag.String("dbpath", "", "path to the server's DB directory")
-	argIDFile  = flag.String("idfile", "", "file name with node id (private key)")
-	argEnode   = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)")
-	argTopic   = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)")
-	argSaveDir = flag.String("savedir", "", "directory where all incoming messages will be saved as files")
-)
-
-func main() {
-	processArgs()
-	initialize()
-	run()
-	shutdown()
-}
-
-func processArgs() {
-	flag.Parse()
-
-	if len(*argIDFile) > 0 {
-		var err error
-		nodeid, err = crypto.LoadECDSA(*argIDFile)
-		if err != nil {
-			utils.Fatalf("Failed to load file [%s]: %s.", *argIDFile, err)
-		}
-	}
-
-	const enodePrefix = "enode://"
-	if len(*argEnode) > 0 {
-		if (*argEnode)[:len(enodePrefix)] != enodePrefix {
-			*argEnode = enodePrefix + *argEnode
-		}
-	}
-
-	if len(*argTopic) > 0 {
-		x, err := hex.DecodeString(*argTopic)
-		if err != nil {
-			utils.Fatalf("Failed to parse the topic: %s", err)
-		}
-		topic = whisper.BytesToTopic(x)
-	}
-
-	if *asymmetricMode && len(*argPub) > 0 {
-		var err error
-		if pub, err = crypto.UnmarshalPubkey(common.FromHex(*argPub)); err != nil {
-			utils.Fatalf("invalid public key")
-		}
-	}
-
-	if len(*argSaveDir) > 0 {
-		if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) {
-			utils.Fatalf("Download directory '%s' does not exist", *argSaveDir)
-		}
-	} else if *fileExMode {
-		utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode")
-	}
-
-	if *echoMode {
-		echo()
-	}
-}
-
-func echo() {
-	fmt.Printf("ttl = %d \n", *argTTL)
-	fmt.Printf("workTime = %d \n", *argWorkTime)
-	fmt.Printf("pow = %f \n", *argPoW)
-	fmt.Printf("mspow = %f \n", *argServerPoW)
-	fmt.Printf("ip = %s \n", *argIP)
-	fmt.Printf("pub = %s \n", hexutil.Encode(crypto.FromECDSAPub(pub)))
-	fmt.Printf("idfile = %s \n", *argIDFile)
-	fmt.Printf("dbpath = %s \n", *argDBPath)
-	fmt.Printf("boot = %s \n", *argEnode)
-}
-
-func initialize() {
-	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
-
-	done = make(chan struct{})
-	var peers []*enode.Node
-	var err error
-
-	if *generateKey {
-		key, err := crypto.GenerateKey()
-		if err != nil {
-			utils.Fatalf("Failed to generate private key: %s", err)
-		}
-		k := hex.EncodeToString(crypto.FromECDSA(key))
-		fmt.Printf("Random private key: %s \n", k)
-		os.Exit(0)
-	}
-
-	if *testMode {
-		symPass = "wwww" // ascii code: 0x77777777
-		msPassword = "wwww"
-	}
-
-	if *bootstrapMode {
-		if len(*argIP) == 0 {
-			argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ")
-		}
-	} else if *fileReader {
-		*bootstrapMode = true
-	} else {
-		if len(*argEnode) == 0 {
-			argEnode = scanLineA("Please enter the peer's enode: ")
-		}
-		peer := enode.MustParse(*argEnode)
-		peers = append(peers, peer)
-	}
-
-	if *mailServerMode {
-		if len(msPassword) == 0 {
-			msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
-			if err != nil {
-				utils.Fatalf("Failed to read Mail Server password: %s", err)
-			}
-		}
-	}
-
-	cfg := &whisper.Config{
-		MaxMessageSize:     uint32(*argMaxSize),
-		MinimumAcceptedPOW: *argPoW,
-	}
-
-	shh = whisper.New(cfg)
-
-	if *argPoW != whisper.DefaultMinimumPoW {
-		err := shh.SetMinimumPoW(*argPoW)
-		if err != nil {
-			utils.Fatalf("Failed to set PoW: %s", err)
-		}
-	}
-
-	if uint32(*argMaxSize) != whisper.DefaultMaxMessageSize {
-		err := shh.SetMaxMessageSize(uint32(*argMaxSize))
-		if err != nil {
-			utils.Fatalf("Failed to set max message size: %s", err)
-		}
-	}
-
-	asymKeyID, err = shh.NewKeyPair()
-	if err != nil {
-		utils.Fatalf("Failed to generate a new key pair: %s", err)
-	}
-
-	asymKey, err = shh.GetPrivateKey(asymKeyID)
-	if err != nil {
-		utils.Fatalf("Failed to retrieve a new key pair: %s", err)
-	}
-
-	if nodeid == nil {
-		tmpID, err := shh.NewKeyPair()
-		if err != nil {
-			utils.Fatalf("Failed to generate a new key pair: %s", err)
-		}
-
-		nodeid, err = shh.GetPrivateKey(tmpID)
-		if err != nil {
-			utils.Fatalf("Failed to retrieve a new key pair: %s", err)
-		}
-	}
-
-	maxPeers := 80
-	if *bootstrapMode {
-		maxPeers = 800
-	}
-
-	_, err = crand.Read(entropy[:])
-	if err != nil {
-		utils.Fatalf("crypto/rand failed: %s", err)
-	}
-
-	if *mailServerMode {
-		shh.RegisterServer(&mailServer)
-		if err := mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW); err != nil {
-			utils.Fatalf("Failed to init MailServer: %s", err)
-		}
-	}
-
-	server = &p2p.Server{
-		Config: p2p.Config{
-			PrivateKey:     nodeid,
-			MaxPeers:       maxPeers,
-			Name:           common.MakeName("wnode", "6.0"),
-			Protocols:      shh.Protocols(),
-			ListenAddr:     *argIP,
-			NAT:            nat.Any(),
-			BootstrapNodes: peers,
-			StaticNodes:    peers,
-			TrustedNodes:   peers,
-		},
-	}
-}
-
-func startServer() error {
-	err := server.Start()
-	if err != nil {
-		fmt.Printf("Failed to start Whisper peer: %s.", err)
-		return err
-	}
-
-	fmt.Printf("my public key: %s \n", hexutil.Encode(crypto.FromECDSAPub(&asymKey.PublicKey)))
-	fmt.Println(server.NodeInfo().Enode)
-
-	if *bootstrapMode {
-		configureNode()
-		fmt.Println("Bootstrap Whisper node started")
-	} else {
-		fmt.Println("Whisper node started")
-		// first see if we can establish connection, then ask for user input
-		waitForConnection(true)
-		configureNode()
-	}
-
-	if *fileExMode {
-		fmt.Printf("Please type the file name to be send. To quit type: '%s'\n", quitCommand)
-	} else if *fileReader {
-		fmt.Printf("Please type the file name to be decrypted. To quit type: '%s'\n", quitCommand)
-	} else if !*forwarderMode {
-		fmt.Printf("Please type the message. To quit type: '%s'\n", quitCommand)
-	}
-	return nil
-}
-
-func configureNode() {
-	var err error
-	var p2pAccept bool
-
-	if *forwarderMode {
-		return
-	}
-
-	if *asymmetricMode {
-		if len(*argPub) == 0 {
-			s := scanLine("Please enter the peer's public key: ")
-			b := common.FromHex(s)
-			if b == nil {
-				utils.Fatalf("Error: can not convert hexadecimal string")
-			}
-			if pub, err = crypto.UnmarshalPubkey(b); err != nil {
-				utils.Fatalf("Error: invalid peer public key")
-			}
-		}
-	}
-
-	if *requestMail {
-		p2pAccept = true
-		if len(msPassword) == 0 {
-			msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
-			if err != nil {
-				utils.Fatalf("Failed to read Mail Server password: %s", err)
-			}
-		}
-	}
-
-	if !*asymmetricMode && !*forwarderMode {
-		if len(symPass) == 0 {
-			symPass, err = console.Stdin.PromptPassword("Please enter the password for symmetric encryption: ")
-			if err != nil {
-				utils.Fatalf("Failed to read password: %v", err)
-			}
-		}
-
-		symKeyID, err := shh.AddSymKeyFromPassword(symPass)
-		if err != nil {
-			utils.Fatalf("Failed to create symmetric key: %s", err)
-		}
-		symKey, err = shh.GetSymKey(symKeyID)
-		if err != nil {
-			utils.Fatalf("Failed to save symmetric key: %s", err)
-		}
-		if len(*argTopic) == 0 {
-			generateTopic([]byte(symPass))
-		}
-
-		fmt.Printf("Filter is configured for the topic: %x \n", topic)
-	}
-
-	if *mailServerMode {
-		if len(*argDBPath) == 0 {
-			argDBPath = scanLineA("Please enter the path to DB file: ")
-		}
-	}
-
-	symFilter := whisper.Filter{
-		KeySym:   symKey,
-		Topics:   [][]byte{topic[:]},
-		AllowP2P: p2pAccept,
-	}
-	symFilterID, err = shh.Subscribe(&symFilter)
-	if err != nil {
-		utils.Fatalf("Failed to install filter: %s", err)
-	}
-
-	asymFilter := whisper.Filter{
-		KeyAsym:  asymKey,
-		Topics:   [][]byte{topic[:]},
-		AllowP2P: p2pAccept,
-	}
-	asymFilterID, err = shh.Subscribe(&asymFilter)
-	if err != nil {
-		utils.Fatalf("Failed to install filter: %s", err)
-	}
-}
-
-func generateTopic(password []byte) {
-	x := pbkdf2.Key(password, password, 4096, 128, sha512.New)
-	for i := 0; i < len(x); i++ {
-		topic[i%whisper.TopicLength] ^= x[i]
-	}
-}
-
-func waitForConnection(timeout bool) {
-	var cnt int
-	var connected bool
-	for !connected {
-		time.Sleep(time.Millisecond * 50)
-		connected = server.PeerCount() > 0
-		if timeout {
-			cnt++
-			if cnt > 1000 {
-				utils.Fatalf("Timeout expired, failed to connect")
-			}
-		}
-	}
-
-	fmt.Println("Connected to peer.")
-}
-
-func run() {
-	err := startServer()
-	if err != nil {
-		return
-	}
-	defer server.Stop()
-	shh.Start(nil)
-	defer shh.Stop()
-
-	if !*forwarderMode {
-		go messageLoop()
-	}
-
-	if *requestMail {
-		requestExpiredMessagesLoop()
-	} else if *fileExMode {
-		sendFilesLoop()
-	} else if *fileReader {
-		fileReaderLoop()
-	} else {
-		sendLoop()
-	}
-}
-
-func shutdown() {
-	close(done)
-	mailServer.Close()
-}
-
-func sendLoop() {
-	for {
-		s := scanLine("")
-		if s == quitCommand {
-			fmt.Println("Quit command received")
-			return
-		}
-		sendMsg([]byte(s))
-		if *asymmetricMode {
-			// print your own message for convenience,
-			// because in asymmetric mode it is impossible to decrypt it
-			timestamp := time.Now().Unix()
-			from := crypto.PubkeyToAddress(asymKey.PublicKey)
-			fmt.Printf("\n%d <%x>: %s\n", timestamp, from, s)
-		}
-	}
-}
-
-func sendFilesLoop() {
-	for {
-		s := scanLine("")
-		if s == quitCommand {
-			fmt.Println("Quit command received")
-			return
-		}
-		b, err := ioutil.ReadFile(s)
-		if err != nil {
-			fmt.Printf(">>> Error: %s \n", err)
-		} else {
-			h := sendMsg(b)
-			if (h == common.Hash{}) {
-				fmt.Printf(">>> Error: message was not sent \n")
-			} else {
-				timestamp := time.Now().Unix()
-				from := crypto.PubkeyToAddress(asymKey.PublicKey)
-				fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h)
-			}
-		}
-	}
-}
-
-func fileReaderLoop() {
-	watcher1 := shh.GetFilter(symFilterID)
-	watcher2 := shh.GetFilter(asymFilterID)
-	if watcher1 == nil && watcher2 == nil {
-		fmt.Println("Error: neither symmetric nor asymmetric filter is installed")
-		return
-	}
-
-	for {
-		s := scanLine("")
-		if s == quitCommand {
-			fmt.Println("Quit command received")
-			return
-		}
-		raw, err := ioutil.ReadFile(s)
-		if err != nil {
-			fmt.Printf(">>> Error: %s \n", err)
-		} else {
-			env := whisper.Envelope{Data: raw} // the topic is zero
-			msg := env.Open(watcher1)          // force-open envelope regardless of the topic
-			if msg == nil {
-				msg = env.Open(watcher2)
-			}
-			if msg == nil {
-				fmt.Printf(">>> Error: failed to decrypt the message \n")
-			} else {
-				printMessageInfo(msg)
-			}
-		}
-	}
-}
-
-func scanLine(prompt string) string {
-	if len(prompt) > 0 {
-		fmt.Print(prompt)
-	}
-	txt, err := input.ReadString('\n')
-	if err != nil {
-		utils.Fatalf("input error: %s", err)
-	}
-	txt = strings.TrimRight(txt, "\n\r")
-	return txt
-}
-
-func scanLineA(prompt string) *string {
-	s := scanLine(prompt)
-	return &s
-}
-
-func scanUint(prompt string) uint32 {
-	s := scanLine(prompt)
-	i, err := strconv.Atoi(s)
-	if err != nil {
-		utils.Fatalf("Fail to parse the lower time limit: %s", err)
-	}
-	return uint32(i)
-}
-
-func sendMsg(payload []byte) common.Hash {
-	params := whisper.MessageParams{
-		Src:      asymKey,
-		Dst:      pub,
-		KeySym:   symKey,
-		Payload:  payload,
-		Topic:    topic,
-		TTL:      uint32(*argTTL),
-		PoW:      *argPoW,
-		WorkTime: uint32(*argWorkTime),
-	}
-
-	msg, err := whisper.NewSentMessage(¶ms)
-	if err != nil {
-		utils.Fatalf("failed to create new message: %s", err)
-	}
-
-	envelope, err := msg.Wrap(¶ms)
-	if err != nil {
-		fmt.Printf("failed to seal message: %v \n", err)
-		return common.Hash{}
-	}
-
-	err = shh.Send(envelope)
-	if err != nil {
-		fmt.Printf("failed to send message: %v \n", err)
-		return common.Hash{}
-	}
-
-	return envelope.Hash()
-}
-
-func messageLoop() {
-	sf := shh.GetFilter(symFilterID)
-	if sf == nil {
-		utils.Fatalf("symmetric filter is not installed")
-	}
-
-	af := shh.GetFilter(asymFilterID)
-	if af == nil {
-		utils.Fatalf("asymmetric filter is not installed")
-	}
-
-	ticker := time.NewTicker(time.Millisecond * 50)
-	defer ticker.Stop()
-
-	for {
-		select {
-		case <-ticker.C:
-			m1 := sf.Retrieve()
-			m2 := af.Retrieve()
-			messages := append(m1, m2...)
-			for _, msg := range messages {
-				reportedOnce := false
-				if !*fileExMode && len(msg.Payload) <= 2048 {
-					printMessageInfo(msg)
-					reportedOnce = true
-				}
-
-				// All messages are saved upon specifying argSaveDir.
-				// fileExMode only specifies how messages are displayed on the console after they are saved.
-				// if fileExMode == true, only the hashes are displayed, since messages might be too big.
-				if len(*argSaveDir) > 0 {
-					writeMessageToFile(*argSaveDir, msg, !reportedOnce)
-				}
-			}
-		case <-done:
-			return
-		}
-	}
-}
-
-func printMessageInfo(msg *whisper.ReceivedMessage) {
-	timestamp := fmt.Sprintf("%d", msg.Sent) // unix timestamp for diagnostics
-	text := string(msg.Payload)
-
-	var address common.Address
-	if msg.Src != nil {
-		address = crypto.PubkeyToAddress(*msg.Src)
-	}
-
-	if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) {
-		fmt.Printf("\n%s <%x>: %s\n", timestamp, address, text) // message from myself
-	} else {
-		fmt.Printf("\n%s [%x]: %s\n", timestamp, address, text) // message from a peer
-	}
-}
-
-func writeMessageToFile(dir string, msg *whisper.ReceivedMessage, show bool) {
-	if len(dir) == 0 {
-		return
-	}
-
-	timestamp := fmt.Sprintf("%d", msg.Sent)
-	name := fmt.Sprintf("%x", msg.EnvelopeHash)
-
-	var address common.Address
-	if msg.Src != nil {
-		address = crypto.PubkeyToAddress(*msg.Src)
-	}
-
-	env := shh.GetEnvelope(msg.EnvelopeHash)
-	if env == nil {
-		fmt.Printf("\nUnexpected error: envelope not found: %x\n", msg.EnvelopeHash)
-		return
-	}
-
-	// this is a sample code; uncomment if you don't want to save your own messages.
-	//if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) {
-	//	fmt.Printf("\n%s <%x>: message from myself received, not saved: '%s'\n", timestamp, address, name)
-	//	return
-	//}
-
-	fullpath := filepath.Join(dir, name)
-	err := ioutil.WriteFile(fullpath, env.Data, 0644)
-	if err != nil {
-		fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err)
-	} else if show {
-		fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(env.Data))
-	}
-}
-
-func requestExpiredMessagesLoop() {
-	var key, peerID, bloom []byte
-	var timeLow, timeUpp uint32
-	var t string
-	var xt whisper.TopicType
-
-	keyID, err := shh.AddSymKeyFromPassword(msPassword)
-	if err != nil {
-		utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
-	}
-	key, err = shh.GetSymKey(keyID)
-	if err != nil {
-		utils.Fatalf("Failed to save symmetric key for mail request: %s", err)
-	}
-	peerID = extractIDFromEnode(*argEnode)
-	shh.AllowP2PMessagesFromPeer(peerID)
-
-	for {
-		timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
-		timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ")
-		t = scanLine("Enter the topic (hex). Press enter to request all messages, regardless of the topic: ")
-		if len(t) == whisper.TopicLength*2 {
-			x, err := hex.DecodeString(t)
-			if err != nil {
-				fmt.Printf("Failed to parse the topic: %s \n", err)
-				continue
-			}
-			xt = whisper.BytesToTopic(x)
-			bloom = whisper.TopicToBloom(xt)
-			obfuscateBloom(bloom)
-		} else if len(t) == 0 {
-			bloom = whisper.MakeFullNodeBloom()
-		} else {
-			fmt.Println("Error: topic is invalid, request aborted")
-			continue
-		}
-
-		if timeUpp == 0 {
-			timeUpp = 0xFFFFFFFF
-		}
-
-		data := make([]byte, 8, 8+whisper.BloomFilterSize)
-		binary.BigEndian.PutUint32(data, timeLow)
-		binary.BigEndian.PutUint32(data[4:], timeUpp)
-		data = append(data, bloom...)
-
-		var params whisper.MessageParams
-		params.PoW = *argServerPoW
-		params.Payload = data
-		params.KeySym = key
-		params.Src = asymKey
-		params.WorkTime = 5
-
-		msg, err := whisper.NewSentMessage(¶ms)
-		if err != nil {
-			utils.Fatalf("failed to create new message: %s", err)
-		}
-		env, err := msg.Wrap(¶ms)
-		if err != nil {
-			utils.Fatalf("Wrap failed: %s", err)
-		}
-
-		err = shh.RequestHistoricMessages(peerID, env)
-		if err != nil {
-			utils.Fatalf("Failed to send P2P message: %s", err)
-		}
-
-		time.Sleep(time.Second * 5)
-	}
-}
-
-func extractIDFromEnode(s string) []byte {
-	n, err := enode.Parse(enode.ValidSchemes, s)
-	if err != nil {
-		utils.Fatalf("Failed to parse node: %s", err)
-	}
-	return n.ID().Bytes()
-}
-
-// obfuscateBloom adds 16 random bits to the bloom
-// filter, in order to obfuscate the containing topics.
-// it does so deterministically within every session.
-// despite additional bits, it will match on average
-// 32000 times less messages than full node's bloom filter.
-func obfuscateBloom(bloom []byte) {
-	const half = entropySize / 2
-	for i := 0; i < half; i++ {
-		x := int(entropy[i])
-		if entropy[half+i] < 128 {
-			x += 256
-		}
-
-		bloom[x/8] = 1 << uint(x%8) // set the bit number X
-	}
-}
diff --git a/common/bytes.go b/common/bytes.go
index 634041804d..7827bb572e 100644
--- a/common/bytes.go
+++ b/common/bytes.go
@@ -17,28 +17,9 @@
 // Package common contains various helper functions.
 package common
 
-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
-}
+import (
+	"encoding/hex"
+)
 
 // FromHex returns the bytes represented by the hexadecimal string s.
 // s may be prefixed with "0x".
diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go
index b689f258a3..01de3d4c65 100644
--- a/common/compiler/solidity.go
+++ b/common/compiler/solidity.go
@@ -44,6 +44,20 @@ type solcOutput struct {
 	Version string
 }
 
+// solidity v.0.8 changes the way ABI, Devdoc and Userdoc are serialized
+type solcOutputV8 struct {
+	Contracts map[string]struct {
+		BinRuntime            string `json:"bin-runtime"`
+		SrcMapRuntime         string `json:"srcmap-runtime"`
+		Bin, SrcMap, Metadata string
+		Abi                   interface{}
+		Devdoc                interface{}
+		Userdoc               interface{}
+		Hashes                map[string]string
+	}
+	Version string
+}
+
 func (s *Solidity) makeArgs() []string {
 	p := []string{
 		"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
@@ -125,7 +139,6 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro
 	if err := cmd.Run(); err != nil {
 		return nil, fmt.Errorf("solc: %v\n%s", err, stderr.Bytes())
 	}
-
 	return ParseCombinedJSON(stdout.Bytes(), source, s.Version, s.Version, strings.Join(s.makeArgs(), " "))
 }
 
@@ -141,7 +154,8 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro
 func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) {
 	var output solcOutput
 	if err := json.Unmarshal(combinedJSON, &output); err != nil {
-		return nil, err
+		// Try to parse the output with the new solidity v.0.8.0 rules
+		return parseCombinedJSONV8(combinedJSON, source, languageVersion, compilerVersion, compilerOptions)
 	}
 	// Compilation succeeded, assemble and return the contracts.
 	contracts := make(map[string]*Contract)
@@ -176,3 +190,35 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin
 	}
 	return contracts, nil
 }
+
+// parseCombinedJSONV8 parses the direct output of solc --combined-output
+// and parses it using the rules from solidity v.0.8.0 and later.
+func parseCombinedJSONV8(combinedJSON []byte, source string, languageVersion string, compilerVersion string, compilerOptions string) (map[string]*Contract, error) {
+	var output solcOutputV8
+	if err := json.Unmarshal(combinedJSON, &output); err != nil {
+		return nil, err
+	}
+	// Compilation succeeded, assemble and return the contracts.
+	contracts := make(map[string]*Contract)
+	for name, info := range output.Contracts {
+		contracts[name] = &Contract{
+			Code:        "0x" + info.Bin,
+			RuntimeCode: "0x" + info.BinRuntime,
+			Hashes:      info.Hashes,
+			Info: ContractInfo{
+				Source:          source,
+				Language:        "Solidity",
+				LanguageVersion: languageVersion,
+				CompilerVersion: compilerVersion,
+				CompilerOptions: compilerOptions,
+				SrcMap:          info.SrcMap,
+				SrcMapRuntime:   info.SrcMapRuntime,
+				AbiDefinition:   info.Abi,
+				UserDoc:         info.Userdoc,
+				DeveloperDoc:    info.Devdoc,
+				Metadata:        info.Metadata,
+			},
+		}
+	}
+	return contracts, nil
+}
diff --git a/common/fdlimit/fdlimit_freebsd.go b/common/fdlimit/fdlimit_bsd.go
similarity index 96%
rename from common/fdlimit/fdlimit_freebsd.go
rename to common/fdlimit/fdlimit_bsd.go
index 0d8727138e..86181337a2 100644
--- a/common/fdlimit/fdlimit_freebsd.go
+++ b/common/fdlimit/fdlimit_bsd.go
@@ -14,14 +14,14 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// +build freebsd
+// +build freebsd dragonfly
 
 package fdlimit
 
 import "syscall"
 
 // This file is largely identical to fdlimit_unix.go,
-// but Rlimit fields have type int64 on FreeBSD so it needs
+// but Rlimit fields have type int64 on *BSD so it needs
 // an extra conversion.
 
 // Raise tries to maximize the file descriptor allowance of this process
diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go
index 8a6b8643a1..ed7d6fad1a 100644
--- a/common/hexutil/json_test.go
+++ b/common/hexutil/json_test.go
@@ -88,7 +88,7 @@ func TestUnmarshalBytes(t *testing.T) {
 		if !checkError(t, test.input, err, test.wantErr) {
 			continue
 		}
-		if !bytes.Equal(test.want.([]byte), []byte(v)) {
+		if !bytes.Equal(test.want.([]byte), v) {
 			t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want)
 			continue
 		}
diff --git a/common/math/big.go b/common/math/big.go
index d31c59af10..1af5b4d879 100644
--- a/common/math/big.go
+++ b/common/math/big.go
@@ -67,6 +67,40 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) {
 	return []byte(fmt.Sprintf("%#x", (*big.Int)(i))), nil
 }
 
+// Decimal256 unmarshals big.Int as a decimal string. When unmarshalling,
+// it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal)
+type Decimal256 big.Int
+
+// NewHexOrDecimal256 creates a new Decimal256
+func NewDecimal256(x int64) *Decimal256 {
+	b := big.NewInt(x)
+	d := Decimal256(*b)
+	return &d
+}
+
+// UnmarshalText implements encoding.TextUnmarshaler.
+func (i *Decimal256) UnmarshalText(input []byte) error {
+	bigint, ok := ParseBig256(string(input))
+	if !ok {
+		return fmt.Errorf("invalid hex or decimal integer %q", input)
+	}
+	*i = Decimal256(*bigint)
+	return nil
+}
+
+// MarshalText implements encoding.TextMarshaler.
+func (i *Decimal256) MarshalText() ([]byte, error) {
+	return []byte(i.String()), nil
+}
+
+// String implements Stringer.
+func (i *Decimal256) String() string {
+	if i == nil {
+		return "0"
+	}
+	return fmt.Sprintf("%#d", (*big.Int)(i))
+}
+
 // ParseBig256 parses s as a 256 bit integer in decimal or hexadecimal syntax.
 // Leading zeros are accepted. The empty string parses as zero.
 func ParseBig256(s string) (*big.Int, bool) {
@@ -184,6 +218,12 @@ func U256(x *big.Int) *big.Int {
 	return x.And(x, tt256m1)
 }
 
+// U256Bytes converts a big Int into a 256bit EVM number.
+// This operation is destructive.
+func U256Bytes(n *big.Int) []byte {
+	return PaddedBigBytes(U256(n), 32)
+}
+
 // S256 interprets x as a two's complement number.
 // x must not exceed 256 bits (the result is undefined if it does) and is not modified.
 //
diff --git a/common/math/big_test.go b/common/math/big_test.go
index be9810dc8c..f896ec65be 100644
--- a/common/math/big_test.go
+++ b/common/math/big_test.go
@@ -212,6 +212,16 @@ func TestU256(t *testing.T) {
 	}
 }
 
+func TestU256Bytes(t *testing.T) {
+	ubytes := make([]byte, 32)
+	ubytes[31] = 1
+
+	unsigned := U256Bytes(big.NewInt(1))
+	if !bytes.Equal(unsigned, ubytes) {
+		t.Errorf("expected %x got %x", ubytes, unsigned)
+	}
+}
+
 func TestBigEndianByteAt(t *testing.T) {
 	tests := []struct {
 		x   string
diff --git a/common/math/integer.go b/common/math/integer.go
index 93b1d036dd..50d3eba1f5 100644
--- a/common/math/integer.go
+++ b/common/math/integer.go
@@ -18,6 +18,7 @@ package math
 
 import (
 	"fmt"
+	"math/bits"
 	"strconv"
 )
 
@@ -78,22 +79,20 @@ func MustParseUint64(s string) uint64 {
 	return v
 }
 
-// NOTE: The following methods need to be optimised using either bit checking or asm
-
-// SafeSub returns subtraction result and whether overflow occurred.
+// SafeSub returns x-y and checks for overflow.
 func SafeSub(x, y uint64) (uint64, bool) {
-	return x - y, x < y
+	diff, borrowOut := bits.Sub64(x, y, 0)
+	return diff, borrowOut != 0
 }
 
-// SafeAdd returns the result and whether overflow occurred.
+// SafeAdd returns x+y and checks for overflow.
 func SafeAdd(x, y uint64) (uint64, bool) {
-	return x + y, y > MaxUint64-x
+	sum, carryOut := bits.Add64(x, y, 0)
+	return sum, carryOut != 0
 }
 
-// SafeMul returns multiplication result and whether overflow occurred.
+// SafeMul returns x*y and checks for overflow.
 func SafeMul(x, y uint64) (uint64, bool) {
-	if x == 0 || y == 0 {
-		return 0, false
-	}
-	return x * y, y > MaxUint64/x
+	hi, lo := bits.Mul64(x, y)
+	return lo, hi != 0
 }
diff --git a/common/mclock/mclock.go b/common/mclock/mclock.go
index 3aca257cb3..c05738cf2b 100644
--- a/common/mclock/mclock.go
+++ b/common/mclock/mclock.go
@@ -20,15 +20,19 @@ package mclock
 import (
 	"time"
 
-	"github.com/aristanetworks/goarista/monotime"
+	_ "unsafe" // for go:linkname
 )
 
+//go:noescape
+//go:linkname nanotime runtime.nanotime
+func nanotime() int64
+
 // AbsTime represents absolute monotonic time.
-type AbsTime time.Duration
+type AbsTime int64
 
 // Now returns the current absolute monotonic time.
 func Now() AbsTime {
-	return AbsTime(monotime.Now())
+	return AbsTime(nanotime())
 }
 
 // Add returns t + d as absolute time.
@@ -74,7 +78,7 @@ type System struct{}
 
 // Now returns the current monotonic time.
 func (c System) Now() AbsTime {
-	return AbsTime(monotime.Now())
+	return Now()
 }
 
 // Sleep blocks for the given duration.
diff --git a/common/mclock/mclock.s b/common/mclock/mclock.s
new file mode 100644
index 0000000000..99a7a878f0
--- /dev/null
+++ b/common/mclock/mclock.s
@@ -0,0 +1 @@
+// This file exists in order to be able to use go:linkname.
diff --git a/common/prque/lazyqueue.go b/common/prque/lazyqueue.go
index 92ddd77f67..37c2f3bd42 100644
--- a/common/prque/lazyqueue.go
+++ b/common/prque/lazyqueue.go
@@ -36,44 +36,58 @@ type LazyQueue struct {
 	// Items are stored in one of two internal queues ordered by estimated max
 	// priority until the next and the next-after-next refresh. Update and Refresh
 	// always places items in queue[1].
-	queue       [2]*sstack
-	popQueue    *sstack
-	period      time.Duration
-	maxUntil    mclock.AbsTime
-	indexOffset int
-	setIndex    SetIndexCallback
-	priority    PriorityCallback
-	maxPriority MaxPriorityCallback
+	queue                      [2]*sstack
+	popQueue                   *sstack
+	period                     time.Duration
+	maxUntil                   mclock.AbsTime
+	indexOffset                int
+	setIndex                   SetIndexCallback
+	priority                   PriorityCallback
+	maxPriority                MaxPriorityCallback
+	lastRefresh1, lastRefresh2 mclock.AbsTime
 }
 
 type (
-	PriorityCallback    func(data interface{}, now mclock.AbsTime) int64   // actual priority callback
+	PriorityCallback    func(data interface{}) int64                       // actual priority callback
 	MaxPriorityCallback func(data interface{}, until mclock.AbsTime) int64 // estimated maximum priority callback
 )
 
 // NewLazyQueue creates a new lazy queue
 func NewLazyQueue(setIndex SetIndexCallback, priority PriorityCallback, maxPriority MaxPriorityCallback, clock mclock.Clock, refreshPeriod time.Duration) *LazyQueue {
 	q := &LazyQueue{
-		popQueue:    newSstack(nil),
-		setIndex:    setIndex,
-		priority:    priority,
-		maxPriority: maxPriority,
-		clock:       clock,
-		period:      refreshPeriod}
+		popQueue:     newSstack(nil, false),
+		setIndex:     setIndex,
+		priority:     priority,
+		maxPriority:  maxPriority,
+		clock:        clock,
+		period:       refreshPeriod,
+		lastRefresh1: clock.Now(),
+		lastRefresh2: clock.Now(),
+	}
 	q.Reset()
-	q.Refresh()
+	q.refresh(clock.Now())
 	return q
 }
 
 // Reset clears the contents of the queue
 func (q *LazyQueue) Reset() {
-	q.queue[0] = newSstack(q.setIndex0)
-	q.queue[1] = newSstack(q.setIndex1)
+	q.queue[0] = newSstack(q.setIndex0, false)
+	q.queue[1] = newSstack(q.setIndex1, false)
 }
 
-// Refresh should be called at least with the frequency specified by the refreshPeriod parameter
+// Refresh performs queue re-evaluation if necessary
 func (q *LazyQueue) Refresh() {
-	q.maxUntil = q.clock.Now() + mclock.AbsTime(q.period)
+	now := q.clock.Now()
+	for time.Duration(now-q.lastRefresh2) >= q.period*2 {
+		q.refresh(now)
+		q.lastRefresh2 = q.lastRefresh1
+		q.lastRefresh1 = now
+	}
+}
+
+// refresh re-evaluates items in the older queue and swaps the two queues
+func (q *LazyQueue) refresh(now mclock.AbsTime) {
+	q.maxUntil = now + mclock.AbsTime(q.period)
 	for q.queue[0].Len() != 0 {
 		q.Push(heap.Pop(q.queue[0]).(*item).value)
 	}
@@ -125,11 +139,10 @@ func (q *LazyQueue) peekIndex() int {
 // Pop multiple times. Popped items are passed to the callback. MultiPop returns
 // when the callback returns false or there are no more items to pop.
 func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) bool) {
-	now := q.clock.Now()
 	nextIndex := q.peekIndex()
 	for nextIndex != -1 {
 		data := heap.Pop(q.queue[nextIndex]).(*item).value
-		heap.Push(q.popQueue, &item{data, q.priority(data, now)})
+		heap.Push(q.popQueue, &item{data, q.priority(data)})
 		nextIndex = q.peekIndex()
 		for q.popQueue.Len() != 0 && (nextIndex == -1 || q.queue[nextIndex].blocks[0][0].priority < q.popQueue.blocks[0][0].priority) {
 			i := heap.Pop(q.popQueue).(*item)
@@ -139,6 +152,7 @@ func (q *LazyQueue) MultiPop(callback func(data interface{}, priority int64) boo
 				}
 				return
 			}
+			nextIndex = q.peekIndex() // re-check because callback is allowed to push items back
 		}
 	}
 }
diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go
index be9491e24e..9a831d628b 100644
--- a/common/prque/lazyqueue_test.go
+++ b/common/prque/lazyqueue_test.go
@@ -40,7 +40,7 @@ type lazyItem struct {
 	index   int
 }
 
-func testPriority(a interface{}, now mclock.AbsTime) int64 {
+func testPriority(a interface{}) int64 {
 	return a.(*lazyItem).p
 }
 
diff --git a/common/prque/prque.go b/common/prque/prque.go
index 3cc5a1adaf..54c78b5fc2 100755
--- a/common/prque/prque.go
+++ b/common/prque/prque.go
@@ -28,7 +28,12 @@ type Prque struct {
 
 // New creates a new priority queue.
 func New(setIndex SetIndexCallback) *Prque {
-	return &Prque{newSstack(setIndex)}
+	return &Prque{newSstack(setIndex, false)}
+}
+
+// NewWrapAround creates a new priority queue with wrap-around priority handling.
+func NewWrapAround(setIndex SetIndexCallback) *Prque {
+	return &Prque{newSstack(setIndex, true)}
 }
 
 // Pushes a value with a given priority into the queue, expanding if necessary.
diff --git a/common/prque/prque_test.go b/common/prque/prque_test.go
new file mode 100644
index 0000000000..1cffcebad4
--- /dev/null
+++ b/common/prque/prque_test.go
@@ -0,0 +1,130 @@
+// CookieJar - A contestant's algorithm toolbox
+// Copyright (c) 2013 Peter Szilagyi. All rights reserved.
+//
+// CookieJar is dual licensed: use of this source code is governed by a BSD
+// license that can be found in the LICENSE file. Alternatively, the CookieJar
+// toolbox may be used in accordance with the terms and conditions contained
+// in a signed written agreement between you and the author(s).
+
+package prque
+
+import (
+	"math/rand"
+	"testing"
+)
+
+func TestPrque(t *testing.T) {
+	// Generate a batch of random data and a specific priority order
+	size := 16 * blockSize
+	prio := rand.Perm(size)
+	data := make([]int, size)
+	for i := 0; i < size; i++ {
+		data[i] = rand.Int()
+	}
+	queue := New(nil)
+	for rep := 0; rep < 2; rep++ {
+		// Fill a priority queue with the above data
+		for i := 0; i < size; i++ {
+			queue.Push(data[i], int64(prio[i]))
+			if queue.Size() != i+1 {
+				t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1)
+			}
+		}
+		// Create a map the values to the priorities for easier verification
+		dict := make(map[int64]int)
+		for i := 0; i < size; i++ {
+			dict[int64(prio[i])] = data[i]
+		}
+		// Pop out the elements in priority order and verify them
+		prevPrio := int64(size + 1)
+		for !queue.Empty() {
+			val, prio := queue.Pop()
+			if prio > prevPrio {
+				t.Errorf("invalid priority order: %v after %v.", prio, prevPrio)
+			}
+			prevPrio = prio
+			if val != dict[prio] {
+				t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio])
+			}
+			delete(dict, prio)
+		}
+	}
+}
+
+func TestReset(t *testing.T) {
+	// Generate a batch of random data and a specific priority order
+	size := 16 * blockSize
+	prio := rand.Perm(size)
+	data := make([]int, size)
+	for i := 0; i < size; i++ {
+		data[i] = rand.Int()
+	}
+	queue := New(nil)
+	for rep := 0; rep < 2; rep++ {
+		// Fill a priority queue with the above data
+		for i := 0; i < size; i++ {
+			queue.Push(data[i], int64(prio[i]))
+			if queue.Size() != i+1 {
+				t.Errorf("queue size mismatch: have %v, want %v.", queue.Size(), i+1)
+			}
+		}
+		// Create a map the values to the priorities for easier verification
+		dict := make(map[int64]int)
+		for i := 0; i < size; i++ {
+			dict[int64(prio[i])] = data[i]
+		}
+		// Pop out half the elements in priority order and verify them
+		prevPrio := int64(size + 1)
+		for i := 0; i < size/2; i++ {
+			val, prio := queue.Pop()
+			if prio > prevPrio {
+				t.Errorf("invalid priority order: %v after %v.", prio, prevPrio)
+			}
+			prevPrio = prio
+			if val != dict[prio] {
+				t.Errorf("push/pop mismatch: have %v, want %v.", val, dict[prio])
+			}
+			delete(dict, prio)
+		}
+		// Reset and ensure it's empty
+		queue.Reset()
+		if !queue.Empty() {
+			t.Errorf("priority queue not empty after reset: %v", queue)
+		}
+	}
+}
+
+func BenchmarkPush(b *testing.B) {
+	// Create some initial data
+	data := make([]int, b.N)
+	prio := make([]int64, b.N)
+	for i := 0; i < len(data); i++ {
+		data[i] = rand.Int()
+		prio[i] = rand.Int63()
+	}
+	// Execute the benchmark
+	b.ResetTimer()
+	queue := New(nil)
+	for i := 0; i < len(data); i++ {
+		queue.Push(data[i], prio[i])
+	}
+}
+
+func BenchmarkPop(b *testing.B) {
+	// Create some initial data
+	data := make([]int, b.N)
+	prio := make([]int64, b.N)
+	for i := 0; i < len(data); i++ {
+		data[i] = rand.Int()
+		prio[i] = rand.Int63()
+	}
+	queue := New(nil)
+	for i := 0; i < len(data); i++ {
+		queue.Push(data[i], prio[i])
+	}
+	// Execute the benchmark
+	b.ResetTimer()
+	for !queue.Empty() {
+		queue.Pop()
+	}
+}
diff --git a/common/prque/sstack.go b/common/prque/sstack.go
index 8518af54ff..b06a95413d 100755
--- a/common/prque/sstack.go
+++ b/common/prque/sstack.go
@@ -31,22 +31,24 @@ type SetIndexCallback func(data interface{}, index int)
 // the stack (heap) functionality and the Len, Less and Swap methods for the
 // sortability requirements of the heaps.
 type sstack struct {
-	setIndex SetIndexCallback
-	size     int
-	capacity int
-	offset   int
+	setIndex   SetIndexCallback
+	size       int
+	capacity   int
+	offset     int
+	wrapAround bool
 
 	blocks [][]*item
 	active []*item
 }
 
 // Creates a new, empty stack.
-func newSstack(setIndex SetIndexCallback) *sstack {
+func newSstack(setIndex SetIndexCallback, wrapAround bool) *sstack {
 	result := new(sstack)
 	result.setIndex = setIndex
 	result.active = make([]*item, blockSize)
 	result.blocks = [][]*item{result.active}
 	result.capacity = blockSize
+	result.wrapAround = wrapAround
 	return result
 }
 
@@ -94,7 +96,11 @@ func (s *sstack) Len() int {
 // Compares the priority of two elements of the stack (higher is first).
 // Required by sort.Interface.
 func (s *sstack) Less(i, j int) bool {
-	return (s.blocks[i/blockSize][i%blockSize].priority - s.blocks[j/blockSize][j%blockSize].priority) > 0
+	a, b := s.blocks[i/blockSize][i%blockSize].priority, s.blocks[j/blockSize][j%blockSize].priority
+	if s.wrapAround {
+		return a-b > 0
+	}
+	return a > b
 }
 
 // Swaps two elements in the stack. Required by sort.Interface.
@@ -110,5 +116,5 @@ func (s *sstack) Swap(i, j int) {
 
 // Resets the stack, effectively clearing its contents.
 func (s *sstack) Reset() {
-	*s = *newSstack(s.setIndex)
+	*s = *newSstack(s.setIndex, false)
 }
diff --git a/common/prque/sstack_test.go b/common/prque/sstack_test.go
new file mode 100644
index 0000000000..bc6298979c
--- /dev/null
+++ b/common/prque/sstack_test.go
@@ -0,0 +1,100 @@
+// CookieJar - A contestant's algorithm toolbox
+// Copyright (c) 2013 Peter Szilagyi. All rights reserved.
+//
+// CookieJar is dual licensed: use of this source code is governed by a BSD
+// license that can be found in the LICENSE file. Alternatively, the CookieJar
+// toolbox may be used in accordance with the terms and conditions contained
+// in a signed written agreement between you and the author(s).
+
+package prque
+
+import (
+	"math/rand"
+	"sort"
+	"testing"
+)
+
+func TestSstack(t *testing.T) {
+	// Create some initial data
+	size := 16 * blockSize
+	data := make([]*item, size)
+	for i := 0; i < size; i++ {
+		data[i] = &item{rand.Int(), rand.Int63()}
+	}
+	stack := newSstack(nil, false)
+	for rep := 0; rep < 2; rep++ {
+		// Push all the data into the stack, pop out every second
+		secs := []*item{}
+		for i := 0; i < size; i++ {
+			stack.Push(data[i])
+			if i%2 == 0 {
+				secs = append(secs, stack.Pop().(*item))
+			}
+		}
+		rest := []*item{}
+		for stack.Len() > 0 {
+			rest = append(rest, stack.Pop().(*item))
+		}
+		// Make sure the contents of the resulting slices are ok
+		for i := 0; i < size; i++ {
+			if i%2 == 0 && data[i] != secs[i/2] {
+				t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i])
+			}
+			if i%2 == 1 && data[i] != rest[len(rest)-i/2-1] {
+				t.Errorf("push/pop mismatch: have %v, want %v.", rest[len(rest)-i/2-1], data[i])
+			}
+		}
+	}
+}
+
+func TestSstackSort(t *testing.T) {
+	// Create some initial data
+	size := 16 * blockSize
+	data := make([]*item, size)
+	for i := 0; i < size; i++ {
+		data[i] = &item{rand.Int(), int64(i)}
+	}
+	// Push all the data into the stack
+	stack := newSstack(nil, false)
+	for _, val := range data {
+		stack.Push(val)
+	}
+	// Sort and pop the stack contents (should reverse the order)
+	sort.Sort(stack)
+	for _, val := range data {
+		out := stack.Pop()
+		if out != val {
+			t.Errorf("push/pop mismatch after sort: have %v, want %v.", out, val)
+		}
+	}
+}
+
+func TestSstackReset(t *testing.T) {
+	// Create some initial data
+	size := 16 * blockSize
+	data := make([]*item, size)
+	for i := 0; i < size; i++ {
+		data[i] = &item{rand.Int(), rand.Int63()}
+	}
+	stack := newSstack(nil, false)
+	for rep := 0; rep < 2; rep++ {
+		// Push all the data into the stack, pop out every second
+		secs := []*item{}
+		for i := 0; i < size; i++ {
+			stack.Push(data[i])
+			if i%2 == 0 {
+				secs = append(secs, stack.Pop().(*item))
+			}
+		}
+		// Reset and verify both pulled and stack contents
+		stack.Reset()
+		if stack.Len() != 0 {
+			t.Errorf("stack not empty after reset: %v", stack)
+		}
+		for i := 0; i < size; i++ {
+			if i%2 == 0 && data[i] != secs[i/2] {
+				t.Errorf("push/pop mismatch: have %v, want %v.", secs[i/2], data[i])
+			}
+		}
+	}
+}
diff --git a/common/types.go b/common/types.go
index cdcc6c20ad..d715356692 100644
--- a/common/types.go
+++ b/common/types.go
@@ -17,6 +17,7 @@
 package common
 
 import (
+	"bytes"
 	"database/sql/driver"
 	"encoding/hex"
 	"encoding/json"
@@ -75,7 +76,7 @@ func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
 // TerminalString implements log.TerminalStringer, formatting a string for console
 // output during logging.
 func (h Hash) TerminalString() string {
-	return fmt.Sprintf("%x…%x", h[:3], h[29:])
+	return fmt.Sprintf("%x..%x", h[:3], h[29:])
 }
 
 // String implements the stringer interface and is used also by the logger when
@@ -84,10 +85,34 @@ func (h Hash) String() string {
 	return h.Hex()
 }
 
-// Format implements fmt.Formatter, forcing the byte slice to be formatted as is,
-// without going through the stringer interface used for logging.
+// Format implements fmt.Formatter.
+// Hash supports the %v, %s, %v, %x, %X and %d format verbs.
 func (h Hash) Format(s fmt.State, c rune) {
-	fmt.Fprintf(s, "%"+string(c), h[:])
+	hexb := make([]byte, 2+len(h)*2)
+	copy(hexb, "0x")
+	hex.Encode(hexb[2:], h[:])
+
+	switch c {
+	case 'x', 'X':
+		if !s.Flag('#') {
+			hexb = hexb[2:]
+		}
+		if c == 'X' {
+			hexb = bytes.ToUpper(hexb)
+		}
+		fallthrough
+	case 'v', 's':
+		s.Write(hexb)
+	case 'q':
+		q := []byte{'"'}
+		s.Write(q)
+		s.Write(hexb)
+		s.Write(q)
+	case 'd':
+		fmt.Fprint(s, ([len(h)]byte)(h))
+	default:
+		fmt.Fprintf(s, "%%!%c(hash=%x)", c, h)
+	}
 }
 
 // UnmarshalText parses a hash in hex syntax.
@@ -208,39 +233,72 @@ func (a Address) Hash() Hash { return BytesToHash(a[:]) }
 
 // Hex returns an EIP55-compliant hex string representation of the address.
 func (a Address) Hex() string {
-	unchecksummed := hex.EncodeToString(a[:])
+	return string(a.checksumHex())
+}
+
+// String implements fmt.Stringer.
+func (a Address) String() string {
+	return a.Hex()
+}
+
+func (a *Address) checksumHex() []byte {
+	buf := a.hex()
+
+	// compute checksum
 	sha := sha3.NewLegacyKeccak256()
-	sha.Write([]byte(unchecksummed))
+	sha.Write(buf[2:])
 	hash := sha.Sum(nil)
-
-	result := []byte(unchecksummed)
-	for i := 0; i < len(result); i++ {
-		hashByte := hash[i/2]
+	for i := 2; i < len(buf); i++ {
+		hashByte := hash[(i-2)/2]
 		if i%2 == 0 {
 			hashByte = hashByte >> 4
 		} else {
 			hashByte &= 0xf
 		}
-		if result[i] > '9' && hashByte > 7 {
-			result[i] -= 32
+		if buf[i] > '9' && hashByte > 7 {
+			buf[i] -= 32
 		}
 	}
-	return "0x" + string(result)
+	return buf[:]
 }
 
-// String implements fmt.Stringer.
-func (a Address) String() string {
-	return a.Hex()
+func (a Address) hex() []byte {
+	var buf [len(a)*2 + 2]byte
+	copy(buf[:2], "0x")
+	hex.Encode(buf[2:], a[:])
+	return buf[:]
 }
 
-// Format implements fmt.Formatter, forcing the byte slice to be formatted as is,
-// without going through the stringer interface used for logging.
+// Format implements fmt.Formatter.
+// Address supports the %v, %s, %v, %x, %X and %d format verbs.
 func (a Address) Format(s fmt.State, c rune) {
-	fmt.Fprintf(s, "%"+string(c), a[:])
+	switch c {
+	case 'v', 's':
+		s.Write(a.checksumHex())
+	case 'q':
+		q := []byte{'"'}
+		s.Write(q)
+		s.Write(a.checksumHex())
+		s.Write(q)
+	case 'x', 'X':
+		// %x disables the checksum.
+		hex := a.hex()
+		if !s.Flag('#') {
+			hex = hex[2:]
+		}
+		if c == 'X' {
+			hex = bytes.ToUpper(hex)
+		}
+		s.Write(hex)
+	case 'd':
+		fmt.Fprint(s, ([len(a)]byte)(a))
+	default:
+		fmt.Fprintf(s, "%%!%c(address=%x)", c, a)
+	}
 }
 
 // SetBytes sets the address to the value of b.
-// If b is larger than len(a) it will panic.
+// If b is larger than len(a), b will be cropped from the left.
 func (a *Address) SetBytes(b []byte) {
 	if len(b) > len(a) {
 		b = b[len(b)-AddressLength:]
diff --git a/common/types_test.go b/common/types_test.go
index fffd673c6e..318e985f87 100644
--- a/common/types_test.go
+++ b/common/types_test.go
@@ -17,8 +17,10 @@
 package common
 
 import (
+	"bytes"
 	"database/sql/driver"
 	"encoding/json"
+	"fmt"
 	"math/big"
 	"reflect"
 	"strings"
@@ -371,3 +373,167 @@ func TestAddress_Value(t *testing.T) {
 		})
 	}
 }
+
+func TestAddress_Format(t *testing.T) {
+	b := []byte{
+		0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
+		0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
+	}
+	var addr Address
+	addr.SetBytes(b)
+
+	tests := []struct {
+		name string
+		out  string
+		want string
+	}{
+		{
+			name: "println",
+			out:  fmt.Sprintln(addr),
+			want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15\n",
+		},
+		{
+			name: "print",
+			out:  fmt.Sprint(addr),
+			want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15",
+		},
+		{
+			name: "printf-s",
+			out: func() string {
+				buf := new(bytes.Buffer)
+				fmt.Fprintf(buf, "%s", addr)
+				return buf.String()
+			}(),
+			want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15",
+		},
+		{
+			name: "printf-q",
+			out:  fmt.Sprintf("%q", addr),
+			want: `"0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15"`,
+		},
+		{
+			name: "printf-x",
+			out:  fmt.Sprintf("%x", addr),
+			want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15",
+		},
+		{
+			name: "printf-X",
+			out:  fmt.Sprintf("%X", addr),
+			want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15",
+		},
+		{
+			name: "printf-#x",
+			out:  fmt.Sprintf("%#x", addr),
+			want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15",
+		},
+		{
+			name: "printf-v",
+			out:  fmt.Sprintf("%v", addr),
+			want: "0xB26f2b342AAb24BCF63ea218c6A9274D30Ab9A15",
+		},
+		// The original default formatter for byte slice
+		{
+			name: "printf-d",
+			out:  fmt.Sprintf("%d", addr),
+			want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21]",
+		},
+		// Invalid format char.
+		{
+			name: "printf-t",
+			out:  fmt.Sprintf("%t", addr),
+			want: "%!t(address=b26f2b342aab24bcf63ea218c6a9274d30ab9a15)",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.out != tt.want {
+				t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want)
+			}
+		})
+	}
+}
+
+func TestHash_Format(t *testing.T) {
+	var hash Hash
+	hash.SetBytes([]byte{
+		0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e,
+		0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
+		0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15,
+		0x10, 0x00,
+	})
+
+	tests := []struct {
+		name string
+		out  string
+		want string
+	}{
+		{
+			name: "println",
+			out:  fmt.Sprintln(hash),
+			want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000\n",
+		},
+		{
+			name: "print",
+			out:  fmt.Sprint(hash),
+			want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000",
+		},
+		{
+			name: "printf-s",
+			out: func() string {
+				buf := new(bytes.Buffer)
+				fmt.Fprintf(buf, "%s", hash)
+				return buf.String()
+			}(),
+			want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000",
+		},
+		{
+			name: "printf-q",
+			out:  fmt.Sprintf("%q", hash),
+			want: `"0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000"`,
+		},
+		{
+			name: "printf-x",
+			out:  fmt.Sprintf("%x", hash),
+			want: "b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000",
+		},
+		{
+			name: "printf-X",
+			out:  fmt.Sprintf("%X", hash),
+			want: "B26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000",
+		},
+		{
+			name: "printf-#x",
+			out:  fmt.Sprintf("%#x", hash),
+			want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000",
+		},
+		{
+			name: "printf-#X",
+			out:  fmt.Sprintf("%#X", hash),
+			want: "0XB26F2B342AAB24BCF63EA218C6A9274D30AB9A15A218C6A9274D30AB9A151000",
+		},
+		{
+			name: "printf-v",
+			out:  fmt.Sprintf("%v", hash),
+			want: "0xb26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000",
+		},
+		// The original default formatter for byte slice
+		{
+			name: "printf-d",
+			out:  fmt.Sprintf("%d", hash),
+			want: "[178 111 43 52 42 171 36 188 246 62 162 24 198 169 39 77 48 171 154 21 162 24 198 169 39 77 48 171 154 21 16 0]",
+		},
+		// Invalid format char.
+		{
+			name: "printf-t",
+			out:  fmt.Sprintf("%t", hash),
+			want: "%!t(hash=b26f2b342aab24bcf63ea218c6a9274d30ab9a15a218c6a9274d30ab9a151000)",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.out != tt.want {
+				t.Errorf("%s does not render as expected:\n got %s\nwant %s", tt.name, tt.out, tt.want)
+			}
+		})
+	}
+}
diff --git a/consensus/clique/api.go b/consensus/clique/api.go
index 4739a403af..8e9a1e7deb 100644
--- a/consensus/clique/api.go
+++ b/consensus/clique/api.go
@@ -28,7 +28,7 @@ import (
 // API is a user facing RPC API to allow controlling the signer and voting
 // mechanisms of the proof-of-authority scheme.
 type API struct {
-	chain  consensus.ChainReader
+	chain  consensus.ChainHeaderReader
 	clique *Clique
 }
 
@@ -170,7 +170,7 @@ func (api *API) Status() (*status, error) {
 		signStatus[sealer]++
 	}
 	return &status{
-		InturnPercent: float64((100 * optimals)) / float64(numBlocks),
+		InturnPercent: float64(100*optimals) / float64(numBlocks),
 		SigningStatus: signStatus,
 		NumBlocks:     numBlocks,
 	}, nil
diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go
index ec0bffa93f..2355950424 100644
--- a/consensus/clique/clique.go
+++ b/consensus/clique/clique.go
@@ -39,6 +39,7 @@ import (
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/trie"
 	lru "github.com/hashicorp/golang-lru"
 	"golang.org/x/crypto/sha3"
 )
@@ -121,9 +122,9 @@ var (
 	// turn of the signer.
 	errWrongDifficulty = errors.New("wrong difficulty")
 
-	// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
+	// errInvalidTimestamp is returned if the timestamp of a block is lower than
 	// the previous block's timestamp + the minimum block period.
-	ErrInvalidTimestamp = errors.New("invalid timestamp")
+	errInvalidTimestamp = errors.New("invalid timestamp")
 
 	// errInvalidVotingChain is returned if an authorization list is attempted to
 	// be modified via out-of-range or non-contiguous headers.
@@ -137,9 +138,8 @@ var (
 	errRecentlySigned = errors.New("recently signed")
 )
 
-// SignerFn is a signer callback function to request a header to be signed by a
-// backing account.
-type SignerFn func(accounts.Account, string, []byte) ([]byte, error)
+// SignerFn hashes and signs the data to be signed by a backing account.
+type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error)
 
 // ecrecover extracts the Ethereum account address from a signed header.
 func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
@@ -213,14 +213,14 @@ func (c *Clique) Author(header *types.Header) (common.Address, error) {
 }
 
 // VerifyHeader checks whether a header conforms to the consensus rules.
-func (c *Clique) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
+func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
 	return c.verifyHeader(chain, header, nil)
 }
 
 // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
 // method returns a quit channel to abort the operations and a results channel to
 // retrieve the async verifications (the order is that of the input slice).
-func (c *Clique) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
+func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
 	abort := make(chan struct{})
 	results := make(chan error, len(headers))
 
@@ -242,7 +242,7 @@ func (c *Clique) VerifyHeaders(chain consensus.ChainReader, headers []*types.Hea
 // caller may optionally pass in a batch of parents (ascending order) to avoid
 // looking those up from the database. This is useful for concurrently verifying
 // a batch of new headers.
-func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
 	if header.Number == nil {
 		return errUnknownBlock
 	}
@@ -305,7 +305,7 @@ func (c *Clique) verifyHeader(chain consensus.ChainReader, header *types.Header,
 // rather depend on a batch of previous headers. The caller may optionally pass
 // in a batch of parents (ascending order) to avoid looking those up from the
 // database. This is useful for concurrently verifying a batch of new headers.
-func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
 	// The genesis block is the always valid dead-end
 	number := header.Number.Uint64()
 	if number == 0 {
@@ -322,7 +322,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type
 		return consensus.ErrUnknownAncestor
 	}
 	if parent.Time+c.config.Period > header.Time {
-		return ErrInvalidTimestamp
+		return errInvalidTimestamp
 	}
 	// Retrieve the snapshot needed to verify this header and cache it
 	snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
@@ -345,7 +345,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainReader, header *type
 }
 
 // snapshot retrieves the authorization snapshot at a given point in time.
-func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
+func (c *Clique) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
 	// Search for a snapshot in memory or on disk for checkpoints
 	var (
 		headers []*types.Header
@@ -369,7 +369,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
 		// at a checkpoint block without a parent (light client CHT), or we have piled
 		// up more headers than allowed to be reorged (chain reinit from a freezer),
 		// consider the checkpoint trusted and snapshot it.
-		if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.ImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
+		if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
 			checkpoint := chain.GetHeaderByNumber(number)
 			if checkpoint != nil {
 				hash := checkpoint.Hash()
@@ -434,17 +434,11 @@ func (c *Clique) VerifyUncles(chain consensus.ChainReader, block *types.Block) e
 	return nil
 }
 
-// VerifySeal implements consensus.Engine, checking whether the signature contained
-// in the header satisfies the consensus protocol requirements.
-func (c *Clique) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
-	return c.verifySeal(chain, header, nil)
-}
-
 // verifySeal checks whether the signature contained in the header satisfies the
 // consensus protocol requirements. The method accepts an optional list of parent
 // headers that aren't yet part of the local blockchain to generate the snapshots
 // from.
-func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+func (c *Clique) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
 	// Verifying the genesis block is not supported
 	number := header.Number.Uint64()
 	if number == 0 {
@@ -487,7 +481,7 @@ func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, p
 
 // Prepare implements consensus.Engine, preparing all the consensus fields of the
 // header for running the transactions on top.
-func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) error {
+func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
 	// If the block isn't a checkpoint, cast a random vote (good enough for now)
 	header.Coinbase = common.Address{}
 	header.Nonce = types.BlockNonce{}
@@ -520,7 +514,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
 		c.lock.RUnlock()
 	}
 	// Set the correct difficulty
-	header.Difficulty = CalcDifficulty(snap, c.signer)
+	header.Difficulty = calcDifficulty(snap, c.signer)
 
 	// Ensure the extra data has all its components
 	if len(header.Extra) < extraVanity {
@@ -552,7 +546,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
 
 // Finalize implements consensus.Engine, ensuring no uncles are set, nor block
 // rewards given.
-func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction, uncles []*types.Header,
+func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction, uncles []*types.Header,
 	receipts *[]*types.Receipt, _ *[]*types.Transaction, _ *uint64) (err error) {
 	// No block rewards in PoA, so the state remains as is and uncles are dropped
 	header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
@@ -562,14 +556,14 @@ func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, sta
 
 // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
 // nor block rewards given, and returns the final block.
-func (c *Clique) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB,
+func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB,
 	txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, []*types.Receipt, error) {
 	// No block rewards in PoA, so the state remains as is and uncles are dropped
 	header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
 	header.UncleHash = types.CalcUncleHash(nil)
 
 	// Assemble and return the final block for sealing
-	return types.NewBlock(header, txs, nil, receipts), receipts, nil
+	return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), receipts, nil
 }
 
 // Authorize injects a private key into the consensus engine to mint new blocks
@@ -588,7 +582,7 @@ func (c *Clique) Delay(chain consensus.ChainReader, header *types.Header) *time.
 
 // Seal implements consensus.Engine, attempting to create a sealed block using
 // the local signing credentials.
-func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
+func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
 	header := block.Header()
 
 	// Sealing the genesis block is not supported
@@ -659,20 +653,18 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
 }
 
 // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
-// that a new block should have based on the previous blocks in the chain and the
-// current signer.
-func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
+// that a new block should have:
+// * DIFF_NOTURN(2) if BLOCK_NUMBER % SIGNER_COUNT != SIGNER_INDEX
+// * DIFF_INTURN(1) if BLOCK_NUMBER % SIGNER_COUNT == SIGNER_INDEX
+func (c *Clique) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
 	snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
 	if err != nil {
 		return nil
 	}
-	return CalcDifficulty(snap, c.signer)
+	return calcDifficulty(snap, c.signer)
 }
 
-// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
-// that a new block should have based on the previous blocks in the chain and the
-// current signer.
-func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
+func calcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
 	if snap.inturn(snap.Number+1, signer) {
 		return new(big.Int).Set(diffInTurn)
 	}
@@ -691,7 +683,7 @@ func (c *Clique) Close() error {
 
 // APIs implements consensus.Engine, returning the user facing RPC API to allow
 // controlling the signer voting.
-func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
+func (c *Clique) APIs(chain consensus.ChainHeaderReader) []rpc.API {
 	return []rpc.API{{
 		Namespace: "clique",
 		Version:   "1.0",
diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go
index 710f448055..e33a212a3b 100644
--- a/consensus/clique/clique_test.go
+++ b/consensus/clique/clique_test.go
@@ -54,7 +54,7 @@ func TestReimportMirroredState(t *testing.T) {
 	genesis := genspec.MustCommit(db)
 
 	// Generate a batch of blocks, each properly signed
-	chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil)
+	chain, _ := core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil)
 	defer chain.Stop()
 
 	blocks, _ := core.GenerateChain(params.AllCliqueProtocolChanges, genesis, engine, db, 3, func(i int, block *core.BlockGen) {
@@ -88,7 +88,7 @@ func TestReimportMirroredState(t *testing.T) {
 	db = rawdb.NewMemoryDatabase()
 	genspec.MustCommit(db)
 
-	chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil)
+	chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil)
 	defer chain.Stop()
 
 	if _, err := chain.InsertChain(blocks[:2]); err != nil {
@@ -99,9 +99,9 @@ func TestReimportMirroredState(t *testing.T) {
 	}
 
 	// Simulate a crash by creating a new chain on top of the database, without
-	// flushing the dirty states out. Insert the last block, trigerring a sidechain
+	// flushing the dirty states out. Insert the last block, triggering a sidechain
 	// reimport.
-	chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil)
+	chain, _ = core.NewBlockChain(db, nil, params.AllCliqueProtocolChanges, engine, vm.Config{}, nil, nil)
 	defer chain.Stop()
 
 	if _, err := chain.InsertChain(blocks[2:]); err != nil {
diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go
index fc08722efd..039ba919bf 100644
--- a/consensus/clique/snapshot_test.go
+++ b/consensus/clique/snapshot_test.go
@@ -423,7 +423,7 @@ func TestClique(t *testing.T) {
 		})
 		// Iterate through the blocks and seal them individually
 		for j, block := range blocks {
-			// Geth the header and prepare it for signing
+			// Get the header and prepare it for signing
 			header := block.Header()
 			if j > 0 {
 				header.ParentHash = blocks[j-1].Hash()
@@ -448,7 +448,7 @@ func TestClique(t *testing.T) {
 			batches[len(batches)-1] = append(batches[len(batches)-1], block)
 		}
 		// Pass all the headers through clique and ensure tallying succeeds
-		chain, err := core.NewBlockChain(db, nil, &config, engine, vm.Config{}, nil)
+		chain, err := core.NewBlockChain(db, nil, &config, engine, vm.Config{}, nil, nil)
 		if err != nil {
 			t.Errorf("test %d: failed to create test chain: %v", i, err)
 			continue
diff --git a/consensus/consensus.go b/consensus/consensus.go
index a2929e87f4..69a5f2f2ef 100644
--- a/consensus/consensus.go
+++ b/consensus/consensus.go
@@ -32,9 +32,9 @@ var (
 	SystemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE")
 )
 
-// ChainReader defines a small collection of methods needed to access the local
-// blockchain during header and/or uncle verification.
-type ChainReader interface {
+// ChainHeaderReader defines a small collection of methods needed to access the local
+// blockchain during header verification.
+type ChainHeaderReader interface {
 	// Config retrieves the blockchain's chain configuration.
 	Config() *params.ChainConfig
 
@@ -49,6 +49,12 @@ type ChainReader interface {
 
 	// GetHeaderByHash retrieves a block header from the database by its hash.
 	GetHeaderByHash(hash common.Hash) *types.Header
+}
+
+// ChainReader defines a small collection of methods needed to access the local
+// blockchain during header and/or uncle verification.
+type ChainReader interface {
+	ChainHeaderReader
 
 	// GetBlock retrieves a block from the database by hash and number.
 	GetBlock(hash common.Hash, number uint64) *types.Block
@@ -64,32 +70,28 @@ type Engine interface {
 	// VerifyHeader checks whether a header conforms to the consensus rules of a
 	// given engine. Verifying the seal may be done optionally here, or explicitly
 	// via the VerifySeal method.
-	VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
+	VerifyHeader(chain ChainHeaderReader, header *types.Header, seal bool) error
 
 	// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
 	// concurrently. The method returns a quit channel to abort the operations and
 	// a results channel to retrieve the async verifications (the order is that of
 	// the input slice).
-	VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
+	VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error)
 
 	// VerifyUncles verifies that the given block's uncles conform to the consensus
 	// rules of a given engine.
 	VerifyUncles(chain ChainReader, block *types.Block) error
 
-	// VerifySeal checks whether the crypto seal on a header is valid according to
-	// the consensus rules of the given engine.
-	VerifySeal(chain ChainReader, header *types.Header) error
-
 	// Prepare initializes the consensus fields of a block header according to the
 	// rules of a particular engine. The changes are executed inline.
-	Prepare(chain ChainReader, header *types.Header) error
+	Prepare(chain ChainHeaderReader, header *types.Header) error
 
 	// Finalize runs any post-transaction state modifications (e.g. block rewards)
 	// but does not assemble the block.
 	//
 	// Note: The block header and state database might be updated to reflect any
 	// consensus rules that happen at finalization (e.g. block rewards).
-	Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction,
+	Finalize(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction,
 		uncles []*types.Header, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, usedGas *uint64) error
 
 	// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
@@ -97,7 +99,7 @@ type Engine interface {
 	//
 	// Note: The block header and state database might be updated to reflect any
 	// consensus rules that happen at finalization (e.g. block rewards).
-	FinalizeAndAssemble(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
+	FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
 		uncles []*types.Header, receipts []*types.Receipt) (*types.Block, []*types.Receipt, error)
 
 	// Seal generates a new sealing request for the given input block and pushes
@@ -105,17 +107,17 @@ type Engine interface {
 	//
 	// Note, the method returns immediately and will send the result async. More
 	// than one result may also be returned depending on the consensus algorithm.
-	Seal(chain ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
+	Seal(chain ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error
 
 	// SealHash returns the hash of a block prior to it being sealed.
 	SealHash(header *types.Header) common.Hash
 
 	// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
 	// that a new block should have.
-	CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
+	CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
 
 	// APIs returns the RPC APIs this consensus engine provides.
-	APIs(chain ChainReader) []rpc.API
+	APIs(chain ChainHeaderReader) []rpc.API
 
 	// Delay returns the max duration the miner can commit txs
 	Delay(chain ChainReader, header *types.Header) *time.Duration
diff --git a/consensus/ethash/algorithm.go b/consensus/ethash/algorithm.go
index eb94d136b9..065e60b90b 100644
--- a/consensus/ethash/algorithm.go
+++ b/consensus/ethash/algorithm.go
@@ -151,10 +151,12 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) {
 		logFn("Generated ethash verification cache", "elapsed", common.PrettyDuration(elapsed))
 	}()
 	// Convert our destination slice to a byte buffer
-	header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest))
-	header.Len *= 4
-	header.Cap *= 4
-	cache := *(*[]byte)(unsafe.Pointer(&header))
+	var cache []byte
+	cacheHdr := (*reflect.SliceHeader)(unsafe.Pointer(&cache))
+	dstHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest))
+	cacheHdr.Data = dstHdr.Data
+	cacheHdr.Len = dstHdr.Len * 4
+	cacheHdr.Cap = dstHdr.Cap * 4
 
 	// Calculate the number of theoretical rows (we'll store in one buffer nonetheless)
 	size := uint64(len(cache))
@@ -172,7 +174,7 @@ func generateCache(dest []uint32, epoch uint64, seed []byte) {
 			case <-done:
 				return
 			case <-time.After(3 * time.Second):
-				logger.Info("Generating ethash verification cache", "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/4, "elapsed", common.PrettyDuration(time.Since(start)))
+				logger.Info("Generating ethash verification cache", "percentage", atomic.LoadUint32(&progress)*100/uint32(rows)/(cacheRounds+1), "elapsed", common.PrettyDuration(time.Since(start)))
 			}
 		}
 	}()
@@ -283,10 +285,12 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
 	swapped := !isLittleEndian()
 
 	// Convert our destination slice to a byte buffer
-	header := *(*reflect.SliceHeader)(unsafe.Pointer(&dest))
-	header.Len *= 4
-	header.Cap *= 4
-	dataset := *(*[]byte)(unsafe.Pointer(&header))
+	var dataset []byte
+	datasetHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dataset))
+	destHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dest))
+	datasetHdr.Data = destHdr.Data
+	datasetHdr.Len = destHdr.Len * 4
+	datasetHdr.Cap = destHdr.Cap * 4
 
 	// Generate the dataset on many goroutines since it takes a while
 	threads := runtime.NumCPU()
@@ -295,7 +299,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
 	var pend sync.WaitGroup
 	pend.Add(threads)
 
-	var progress uint32
+	var progress uint64
 	for i := 0; i < threads; i++ {
 		go func(id int) {
 			defer pend.Done()
@@ -311,7 +315,7 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
 				limit = size / hashBytes
 			}
 			// Calculate the dataset segment
-			percent := uint32(size / hashBytes / 100)
+			percent := size / hashBytes / 100
 			for index := first; index < limit; index++ {
 				item := generateDatasetItem(cache, uint32(index), keccak512)
 				if swapped {
@@ -319,8 +323,8 @@ func generateDataset(dest []uint32, epoch uint64, cache []uint32) {
 				}
 				copy(dataset[index*hashBytes:], item)
 
-				if status := atomic.AddUint32(&progress, 1); status%percent == 0 {
-					logger.Info("Generating DAG in progress", "percentage", uint64(status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start)))
+				if status := atomic.AddUint64(&progress, 1); status%percent == 0 {
+					logger.Info("Generating DAG in progress", "percentage", (status*100)/(size/hashBytes), "elapsed", common.PrettyDuration(time.Since(start)))
 				}
 			}
 		}(i)
diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go
index 51fb6b124d..9cc9d535d4 100644
--- a/consensus/ethash/algorithm_test.go
+++ b/consensus/ethash/algorithm_test.go
@@ -726,12 +726,16 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) {
 
 	for i := 0; i < 3; i++ {
 		pend.Add(1)
-
 		go func(idx int) {
 			defer pend.Done()
-			ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false)
+
+			config := Config{
+				CacheDir:     cachedir,
+				CachesOnDisk: 1,
+			}
+			ethash := New(config, nil, false)
 			defer ethash.Close()
-			if err := ethash.VerifySeal(nil, block.Header()); err != nil {
+			if err := ethash.verifySeal(nil, block.Header(), false); err != nil {
 				t.Errorf("proc %d: block verification failed: %v", idx, err)
 			}
 		}(i)
diff --git a/consensus/ethash/api.go b/consensus/ethash/api.go
index 68b3a84b09..f4d3802e0b 100644
--- a/consensus/ethash/api.go
+++ b/consensus/ethash/api.go
@@ -89,7 +89,7 @@ func (api *API) SubmitWork(nonce types.BlockNonce, hash, digest common.Hash) boo
 //
 // It accepts the miner hash rate and an identifier which must be unique
 // between nodes.
-func (api *API) SubmitHashRate(rate hexutil.Uint64, id common.Hash) bool {
+func (api *API) SubmitHashrate(rate hexutil.Uint64, id common.Hash) bool {
 	if api.ethash.remote == nil {
 		return false
 	}
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
index 72ec3e9934..f8a84c3d00 100644
--- a/consensus/ethash/consensus.go
+++ b/consensus/ethash/consensus.go
@@ -33,16 +33,17 @@ import (
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
 	"golang.org/x/crypto/sha3"
 )
 
 // Ethash proof-of-work protocol constants.
 var (
-	FrontierBlockReward       = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
-	ByzantiumBlockReward      = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
-	ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
-	maxUncles                 = 2                 // Maximum number of uncles allowed in a single block
-	allowedFutureBlockTime    = 15 * time.Second  // Max time from current time allowed for blocks, before they're considered future blocks
+	FrontierBlockReward           = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
+	ByzantiumBlockReward          = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
+	ConstantinopleBlockReward     = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
+	maxUncles                     = 2                 // Maximum number of uncles allowed in a single block
+	allowedFutureBlockTimeSeconds = int64(15)         // Max seconds from current time allowed for blocks, before they're considered future blocks
 
 	// calcDifficultyEip2384 is the difficulty adjustment algorithm as specified by EIP 2384.
 	// It offsets the bomb 4M blocks from Constantinople, so in total 9M blocks.
@@ -86,7 +87,7 @@ func (ethash *Ethash) Author(header *types.Header) (common.Address, error) {
 
 // VerifyHeader checks whether a header conforms to the consensus rules of the
 // stock Ethereum ethash engine.
-func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
+func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
 	// If we're running a full engine faking, accept any input as valid
 	if ethash.config.PowMode == ModeFullFake {
 		return nil
@@ -101,13 +102,13 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.He
 		return consensus.ErrUnknownAncestor
 	}
 	// Sanity checks passed, do a proper verification
-	return ethash.verifyHeader(chain, header, parent, false, seal)
+	return ethash.verifyHeader(chain, header, parent, false, seal, time.Now().Unix())
 }
 
 // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
 // concurrently. The method returns a quit channel to abort the operations and
 // a results channel to retrieve the async verifications.
-func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
+func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
 	// If we're running a full engine faking, accept any input as valid
 	if ethash.config.PowMode == ModeFullFake || len(headers) == 0 {
 		abort, results := make(chan struct{}), make(chan error, len(headers))
@@ -125,15 +126,16 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*type
 
 	// Create a task channel and spawn the verifiers
 	var (
-		inputs = make(chan int)
-		done   = make(chan int, workers)
-		errors = make([]error, len(headers))
-		abort  = make(chan struct{})
+		inputs  = make(chan int)
+		done    = make(chan int, workers)
+		errors  = make([]error, len(headers))
+		abort   = make(chan struct{})
+		unixNow = time.Now().Unix()
 	)
 	for i := 0; i < workers; i++ {
 		go func() {
 			for index := range inputs {
-				errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index)
+				errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index, unixNow)
 				done <- index
 			}
 		}()
@@ -169,7 +171,7 @@ func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*type
 	return abort, errorsOut
 }
 
-func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error {
+func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, index int, unixNow int64) error {
 	var parent *types.Header
 	if index == 0 {
 		parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1)
@@ -179,10 +181,7 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []
 	if parent == nil {
 		return consensus.ErrUnknownAncestor
 	}
-	if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil {
-		return nil // known block
-	}
-	return ethash.verifyHeader(chain, headers[index], parent, false, seals[index])
+	return ethash.verifyHeader(chain, headers[index], parent, false, seals[index], unixNow)
 }
 
 // VerifyUncles verifies that the given block's uncles conform to the consensus
@@ -204,15 +203,23 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo
 
 	number, parent := block.NumberU64()-1, block.ParentHash()
 	for i := 0; i < 7; i++ {
-		ancestor := chain.GetBlock(parent, number)
-		if ancestor == nil {
+		ancestorHeader := chain.GetHeader(parent, number)
+		if ancestorHeader == nil {
 			break
 		}
-		ancestors[ancestor.Hash()] = ancestor.Header()
-		for _, uncle := range ancestor.Uncles() {
-			uncles.Add(uncle.Hash())
+		ancestors[parent] = ancestorHeader
+		// If the ancestor doesn't have any uncles, we don't have to iterate them
+		if ancestorHeader.UncleHash != types.EmptyUncleHash {
+			// Need to add those uncles to the blacklist too
+			ancestor := chain.GetBlock(parent, number)
+			if ancestor == nil {
+				break
+			}
+			for _, uncle := range ancestor.Uncles() {
+				uncles.Add(uncle.Hash())
+			}
 		}
-		parent, number = ancestor.ParentHash(), number-1
+		parent, number = ancestorHeader.ParentHash, number-1
 	}
 	ancestors[block.Hash()] = block.Header()
 	uncles.Add(block.Hash())
@@ -233,7 +240,7 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo
 		if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == block.ParentHash() {
 			return errDanglingUncle
 		}
-		if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true); err != nil {
+		if err := ethash.verifyHeader(chain, uncle, ancestors[uncle.ParentHash], true, true, time.Now().Unix()); err != nil {
 			return err
 		}
 	}
@@ -243,14 +250,14 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo
 // verifyHeader checks whether a header conforms to the consensus rules of the
 // stock Ethereum ethash engine.
 // See YP section 4.3.4. "Block Header Validity"
-func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
+func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, uncle bool, seal bool, unixNow int64) error {
 	// Ensure that the header's extra-data section is of a reasonable size
 	if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
 		return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
 	}
 	// Verify the header's timestamp
 	if !uncle {
-		if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
+		if header.Time > uint64(unixNow+allowedFutureBlockTimeSeconds) {
 			return consensus.ErrFutureBlock
 		}
 	}
@@ -289,7 +296,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
 	}
 	// Verify the engine specific seal securing the block
 	if seal {
-		if err := ethash.VerifySeal(chain, header); err != nil {
+		if err := ethash.verifySeal(chain, header, false); err != nil {
 			return err
 		}
 	}
@@ -306,7 +313,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
 // CalcDifficulty is the difficulty adjustment algorithm. It returns
 // the difficulty that a new block should have when created at time
 // given the parent block's time and difficulty.
-func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
+func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
 	return CalcDifficulty(chain.Config(), time, parent)
 }
 
@@ -316,6 +323,8 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, p
 func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
 	next := new(big.Int).Add(parent.Number, big1)
 	switch {
+	case config.IsCatalyst(next):
+		return big.NewInt(1)
 	case config.IsMuirGlacier(next):
 		return calcDifficultyEip2384(time, parent)
 	case config.IsConstantinople(next):
@@ -484,16 +493,15 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
 	return diff
 }
 
-// VerifySeal implements consensus.Engine, checking whether the given block satisfies
-// the PoW difficulty requirements.
-func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
-	return ethash.verifySeal(chain, header, false)
-}
+// Exported for fuzzing
+var FrontierDifficultyCalulator = calcDifficultyFrontier
+var HomesteadDifficultyCalulator = calcDifficultyHomestead
+var DynamicDifficultyCalculator = makeDifficultyCalculator
 
 // verifySeal checks whether a block satisfies the PoW difficulty requirements,
 // either using the usual ethash cache for it, or alternatively using a full DAG
 // to make remote mining fast.
-func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error {
+func (ethash *Ethash) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, fulldag bool) error {
 	// If we're running a fake PoW, accept any seal as valid
 	if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
 		time.Sleep(ethash.fakeDelay)
@@ -558,7 +566,7 @@ func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Head
 
 // Prepare implements consensus.Engine, initializing the difficulty field of a
 // header to conform to the ethash protocol. The changes are done inline.
-func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) error {
+func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
 	parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
 	if parent == nil {
 		return consensus.ErrUnknownAncestor
@@ -569,7 +577,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
 
 // Finalize implements consensus.Engine, accumulating the block and uncle rewards,
 // setting the final state on the header
-func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction, uncles []*types.Header,
+func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction, uncles []*types.Header,
 	receipts *[]*types.Receipt, _ *[]*types.Transaction, _ *uint64) (err error) {
 	// Accumulate any block and uncle rewards and commit the final state root
 	accumulateRewards(chain.Config(), state, header, uncles)
@@ -579,14 +587,13 @@ func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header
 
 // FinalizeAndAssemble implements consensus.Engine, accumulating the block and
 // uncle rewards, setting the final state and assembling the block.
-func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB,
+func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB,
 	txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, []*types.Receipt, error) {
-	// Accumulate any block and uncle rewards and commit the final state root
-	accumulateRewards(chain.Config(), state, header, uncles)
-	header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
+
+	ethash.Finalize(chain, header, state, &txs, uncles, nil, nil, nil)
 
 	// Header seems complete, assemble into a block and return
-	return types.NewBlock(header, txs, uncles, receipts), receipts, nil
+	return types.NewBlock(header, txs, uncles, receipts, trie.NewStackTrie(nil)), receipts, nil
 }
 
 func (ethash *Ethash) Delay(_ consensus.ChainReader, _ *types.Header) *time.Duration {
@@ -626,6 +633,10 @@ var (
 // reward. The total reward consists of the static block reward and rewards for
 // included uncles. The coinbase of each uncle block is also rewarded.
 func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
+	// Skip block reward in catalyst mode
+	if config.IsCatalyst(header.Number) {
+		return
+	}
 	// Select the correct block reward based on chain progression
 	blockReward := FrontierBlockReward
 	if config.IsByzantium(header.Number) {
diff --git a/consensus/ethash/consensus_test.go b/consensus/ethash/consensus_test.go
index 675737d9e1..6f6dc79fd8 100644
--- a/consensus/ethash/consensus_test.go
+++ b/consensus/ethash/consensus_test.go
@@ -17,12 +17,15 @@
 package ethash
 
 import (
+	"encoding/binary"
 	"encoding/json"
 	"math/big"
+	"math/rand"
 	"os"
 	"path/filepath"
 	"testing"
 
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/params"
@@ -84,3 +87,102 @@ func TestCalcDifficulty(t *testing.T) {
 		}
 	}
 }
+
+func randSlice(min, max uint32) []byte {
+	var b = make([]byte, 4)
+	rand.Read(b)
+	a := binary.LittleEndian.Uint32(b)
+	size := min + a%(max-min)
+	out := make([]byte, size)
+	rand.Read(out)
+	return out
+}
+
+func TestDifficultyCalculators(t *testing.T) {
+	rand.Seed(2)
+	for i := 0; i < 5000; i++ {
+		// 1 to 300 seconds diff
+		var timeDelta = uint64(1 + rand.Uint32()%3000)
+		diffBig := big.NewInt(0).SetBytes(randSlice(2, 10))
+		if diffBig.Cmp(params.MinimumDifficulty) < 0 {
+			diffBig.Set(params.MinimumDifficulty)
+		}
+		//rand.Read(difficulty)
+		header := &types.Header{
+			Difficulty: diffBig,
+			Number:     new(big.Int).SetUint64(rand.Uint64() % 50_000_000),
+			Time:       rand.Uint64() - timeDelta,
+		}
+		if rand.Uint32()&1 == 0 {
+			header.UncleHash = types.EmptyUncleHash
+		}
+		bombDelay := new(big.Int).SetUint64(rand.Uint64() % 50_000_000)
+		for i, pair := range []struct {
+			bigFn  func(time uint64, parent *types.Header) *big.Int
+			u256Fn func(time uint64, parent *types.Header) *big.Int
+		}{
+			{FrontierDifficultyCalulator, CalcDifficultyFrontierU256},
+			{HomesteadDifficultyCalulator, CalcDifficultyHomesteadU256},
+			{DynamicDifficultyCalculator(bombDelay), MakeDifficultyCalculatorU256(bombDelay)},
+		} {
+			time := header.Time + timeDelta
+			want := pair.bigFn(time, header)
+			have := pair.u256Fn(time, header)
+			if want.BitLen() > 256 {
+				continue
+			}
+			if want.Cmp(have) != 0 {
+				t.Fatalf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have,
+					header.Number, header.Time, time, bombDelay)
+			}
+		}
+	}
+}
+
+func BenchmarkDifficultyCalculator(b *testing.B) {
+	x1 := makeDifficultyCalculator(big.NewInt(1000000))
+	x2 := MakeDifficultyCalculatorU256(big.NewInt(1000000))
+	h := &types.Header{
+		ParentHash: common.Hash{},
+		UncleHash:  types.EmptyUncleHash,
+		Difficulty: big.NewInt(0xffffff),
+		Number:     big.NewInt(500000),
+		Time:       1000000,
+	}
+	b.Run("big-frontier", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			calcDifficultyFrontier(1000014, h)
+		}
+	})
+	b.Run("u256-frontier", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			CalcDifficultyFrontierU256(1000014, h)
+		}
+	})
+	b.Run("big-homestead", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			calcDifficultyHomestead(1000014, h)
+		}
+	})
+	b.Run("u256-homestead", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			CalcDifficultyHomesteadU256(1000014, h)
+		}
+	})
+	b.Run("big-generic", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			x1(1000014, h)
+		}
+	})
+	b.Run("u256-generic", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			x2(1000014, h)
+		}
+	})
+}
diff --git a/consensus/ethash/difficulty.go b/consensus/ethash/difficulty.go
new file mode 100644
index 0000000000..59c4ac7419
--- /dev/null
+++ b/consensus/ethash/difficulty.go
@@ -0,0 +1,193 @@
+// 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 .
+
+package ethash
+
+import (
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/holiman/uint256"
+)
+
+const (
+	// frontierDurationLimit is for Frontier:
+	// The decision boundary on the blocktime duration used to determine
+	// whether difficulty should go up or down.
+	frontierDurationLimit = 13
+	// minimumDifficulty The minimum that the difficulty may ever be.
+	minimumDifficulty = 131072
+	// expDiffPeriod is the exponential difficulty period
+	expDiffPeriodUint = 100000
+	// difficultyBoundDivisorBitShift is the bound divisor of the difficulty (2048),
+	// This constant is the right-shifts to use for the division.
+	difficultyBoundDivisor = 11
+)
+
+// CalcDifficultyFrontierU256 is the difficulty adjustment algorithm. It returns the
+// difficulty that a new block should have when created at time given the parent
+// block's time and difficulty. The calculation uses the Frontier rules.
+func CalcDifficultyFrontierU256(time uint64, parent *types.Header) *big.Int {
+	/*
+		Algorithm
+		block_diff = pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1) + int(2^((num // 100000) - 2))
+
+		Where:
+		- pdiff  = parent.difficulty
+		- ptime = parent.time
+		- time = block.timestamp
+		- num = block.number
+	*/
+
+	pDiff := uint256.NewInt()
+	pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff
+	adjust := pDiff.Clone()
+	adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048
+
+	if time-parent.Time < frontierDurationLimit {
+		pDiff.Add(pDiff, adjust)
+	} else {
+		pDiff.Sub(pDiff, adjust)
+	}
+	if pDiff.LtUint64(minimumDifficulty) {
+		pDiff.SetUint64(minimumDifficulty)
+	}
+	// 'pdiff' now contains:
+	// pdiff + pdiff / 2048 * (1 if time - ptime < 13 else -1)
+
+	if periodCount := (parent.Number.Uint64() + 1) / expDiffPeriodUint; periodCount > 1 {
+		// diff = diff + 2^(periodCount - 2)
+		expDiff := adjust.SetOne()
+		expDiff.Lsh(expDiff, uint(periodCount-2)) // expdiff: 2 ^ (periodCount -2)
+		pDiff.Add(pDiff, expDiff)
+	}
+	return pDiff.ToBig()
+}
+
+// CalcDifficultyHomesteadU256 is the difficulty adjustment algorithm. It returns
+// the difficulty that a new block should have when created at time given the
+// parent block's time and difficulty. The calculation uses the Homestead rules.
+func CalcDifficultyHomesteadU256(time uint64, parent *types.Header) *big.Int {
+	/*
+		https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
+		Algorithm:
+		block_diff = pdiff + pdiff / 2048 * max(1 - (time - ptime) / 10, -99) + 2 ^ int((num / 100000) - 2))
+
+		Our modification, to use unsigned ints:
+		block_diff = pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99) + 2 ^ int((num / 100000) - 2))
+
+		Where:
+		- pdiff  = parent.difficulty
+		- ptime = parent.time
+		- time = block.timestamp
+		- num = block.number
+	*/
+
+	pDiff := uint256.NewInt()
+	pDiff.SetFromBig(parent.Difficulty) // pDiff: pdiff
+	adjust := pDiff.Clone()
+	adjust.Rsh(adjust, difficultyBoundDivisor) // adjust: pDiff / 2048
+
+	x := (time - parent.Time) / 10 // (time - ptime) / 10)
+	var neg = true
+	if x == 0 {
+		x = 1
+		neg = false
+	} else if x >= 100 {
+		x = 99
+	} else {
+		x = x - 1
+	}
+	z := new(uint256.Int).SetUint64(x)
+	adjust.Mul(adjust, z) // adjust: (pdiff / 2048) * max((time - ptime) / 10 - 1, 99)
+	if neg {
+		pDiff.Sub(pDiff, adjust) // pdiff - pdiff / 2048 * max((time - ptime) / 10 - 1, 99)
+	} else {
+		pDiff.Add(pDiff, adjust) // pdiff + pdiff / 2048 * max((time - ptime) / 10 - 1, 99)
+	}
+	if pDiff.LtUint64(minimumDifficulty) {
+		pDiff.SetUint64(minimumDifficulty)
+	}
+	// for the exponential factor, a.k.a "the bomb"
+	// diff = diff + 2^(periodCount - 2)
+	if periodCount := (1 + parent.Number.Uint64()) / expDiffPeriodUint; periodCount > 1 {
+		expFactor := adjust.Lsh(adjust.SetOne(), uint(periodCount-2))
+		pDiff.Add(pDiff, expFactor)
+	}
+	return pDiff.ToBig()
+}
+
+// MakeDifficultyCalculatorU256 creates a difficultyCalculator with the given bomb-delay.
+// the difficulty is calculated with Byzantium rules, which differs from Homestead in
+// how uncles affect the calculation
+func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int {
+	// Note, the calculations below looks at the parent number, which is 1 below
+	// the block number. Thus we remove one from the delay given
+	bombDelayFromParent := bombDelay.Uint64() - 1
+	return func(time uint64, parent *types.Header) *big.Int {
+		/*
+			https://github.com/ethereum/EIPs/issues/100
+			pDiff = parent.difficulty
+			BLOCK_DIFF_FACTOR = 9
+			a = pDiff + (pDiff // BLOCK_DIFF_FACTOR) * adj_factor
+			b = min(parent.difficulty, MIN_DIFF)
+			child_diff = max(a,b )
+		*/
+		x := (time - parent.Time) / 9 // (block_timestamp - parent_timestamp) // 9
+		c := uint64(1)                // if parent.unclehash == emptyUncleHashHash
+		if parent.UncleHash != types.EmptyUncleHash {
+			c = 2
+		}
+		xNeg := x >= c
+		if xNeg {
+			// x is now _negative_ adjustment factor
+			x = x - c // - ( (t-p)/p -( 2 or 1) )
+		} else {
+			x = c - x // (2 or 1) - (t-p)/9
+		}
+		if x > 99 {
+			x = 99 // max(x, 99)
+		}
+		// parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
+		y := new(uint256.Int)
+		y.SetFromBig(parent.Difficulty)    // y: p_diff
+		pDiff := y.Clone()                 // pdiff: p_diff
+		z := new(uint256.Int).SetUint64(x) //z : +-adj_factor (either pos or negative)
+		y.Rsh(y, difficultyBoundDivisor)   // y: p__diff / 2048
+		z.Mul(y, z)                        // z: (p_diff / 2048 ) * (+- adj_factor)
+
+		if xNeg {
+			y.Sub(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor
+		} else {
+			y.Add(pDiff, z) // y: parent_diff + parent_diff/2048 * adjustment_factor
+		}
+		// minimum difficulty can ever be (before exponential factor)
+		if y.LtUint64(minimumDifficulty) {
+			y.SetUint64(minimumDifficulty)
+		}
+		// calculate a fake block number for the ice-age delay
+		// Specification: https://eips.ethereum.org/EIPS/eip-1234
+		var pNum = parent.Number.Uint64()
+		if pNum >= bombDelayFromParent {
+			if fakeBlockNumber := pNum - bombDelayFromParent; fakeBlockNumber >= 2*expDiffPeriodUint {
+				z.SetOne()
+				z.Lsh(z, uint(fakeBlockNumber/expDiffPeriodUint-2))
+				y.Add(z, y)
+			}
+		}
+		return y.ToBig()
+	}
+}
diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go
index 31bdceb4c3..ec06d02a54 100644
--- a/consensus/ethash/ethash.go
+++ b/consensus/ethash/ethash.go
@@ -33,7 +33,7 @@ import (
 	"time"
 	"unsafe"
 
-	mmap "github.com/edsrzf/mmap-go"
+	"github.com/edsrzf/mmap-go"
 	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
@@ -48,7 +48,7 @@ var (
 	two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0))
 
 	// sharedEthash is a full instance that can be shared between multiple users.
-	sharedEthash = New(Config{"", 3, 0, false, "", 1, 0, false, ModeNormal, nil}, nil, false)
+	sharedEthash *Ethash
 
 	// algorithmRevision is the data structure version used for file naming.
 	algorithmRevision = 23
@@ -57,6 +57,15 @@ var (
 	dumpMagic = []uint32{0xbaddcafe, 0xfee1dead}
 )
 
+func init() {
+	sharedConfig := Config{
+		PowMode:       ModeNormal,
+		CachesInMem:   3,
+		DatasetsInMem: 1,
+	}
+	sharedEthash = New(sharedConfig, nil, false)
+}
+
 // isLittleEndian returns whether the local system is running in little or big
 // endian byte order.
 func isLittleEndian() bool {
@@ -103,12 +112,13 @@ func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) {
 	if err != nil {
 		return nil, nil, err
 	}
-	// Yay, we managed to memory map the file, here be dragons
-	header := *(*reflect.SliceHeader)(unsafe.Pointer(&mem))
-	header.Len /= 4
-	header.Cap /= 4
-
-	return mem, *(*[]uint32)(unsafe.Pointer(&header)), nil
+	// The file is now memory-mapped. Create a []uint32 view of the file.
+	var view []uint32
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&view))
+	header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&mem)).Data
+	header.Cap = len(mem) / 4
+	header.Len = header.Cap
+	return mem, view, nil
 }
 
 // memoryMapAndGenerate tries to memory map a temporary file of uint32s for write
@@ -411,6 +421,10 @@ type Config struct {
 	DatasetsLockMmap bool
 	PowMode          Mode
 
+	// When set, notifications sent by the remote sealer will
+	// be block header JSON objects instead of work package arrays.
+	NotifyFull bool
+
 	Log log.Logger `toml:"-"`
 }
 
@@ -462,6 +476,9 @@ func New(config Config, notify []string, noverify bool) *Ethash {
 		update:   make(chan struct{}),
 		hashrate: metrics.NewMeterForced(),
 	}
+	if config.PowMode == ModeShared {
+		ethash.shared = sharedEthash
+	}
 	ethash.remote = startRemoteSealer(ethash, notify, noverify)
 	return ethash
 }
@@ -469,15 +486,7 @@ func New(config Config, notify []string, noverify bool) *Ethash {
 // NewTester creates a small sized ethash PoW scheme useful only for testing
 // purposes.
 func NewTester(notify []string, noverify bool) *Ethash {
-	ethash := &Ethash{
-		config:   Config{PowMode: ModeTest, Log: log.Root()},
-		caches:   newlru("cache", 1, newCache),
-		datasets: newlru("dataset", 1, newDataset),
-		update:   make(chan struct{}),
-		hashrate: metrics.NewMeterForced(),
-	}
-	ethash.remote = startRemoteSealer(ethash, notify, noverify)
-	return ethash
+	return New(Config{PowMode: ModeTest}, notify, noverify)
 }
 
 // NewFaker creates a ethash consensus engine with a fake PoW scheme that accepts
@@ -537,7 +546,6 @@ func NewShared() *Ethash {
 
 // Close closes the exit channel to notify all backend threads exiting.
 func (ethash *Ethash) Close() error {
-	var err error
 	ethash.closeOnce.Do(func() {
 		// Short circuit if the exit channel is not allocated.
 		if ethash.remote == nil {
@@ -546,7 +554,7 @@ func (ethash *Ethash) Close() error {
 		close(ethash.remote.requestExit)
 		<-ethash.remote.exitCh
 	})
-	return err
+	return nil
 }
 
 // cache tries to retrieve a verification cache for the specified block number
@@ -656,7 +664,7 @@ func (ethash *Ethash) Hashrate() float64 {
 }
 
 // APIs implements consensus.Engine, returning the user facing RPC APIs.
-func (ethash *Ethash) APIs(chain consensus.ChainReader) []rpc.API {
+func (ethash *Ethash) APIs(chain consensus.ChainHeaderReader) []rpc.API {
 	// In order to ensure backward compatibility, we exposes ethash RPC APIs
 	// to both eth and ethash namespaces.
 	return []rpc.API{
diff --git a/consensus/ethash/ethash_test.go b/consensus/ethash/ethash_test.go
index fdfd81320f..382eefeecf 100644
--- a/consensus/ethash/ethash_test.go
+++ b/consensus/ethash/ethash_test.go
@@ -46,10 +46,10 @@ func TestTestMode(t *testing.T) {
 	case block := <-results:
 		header.Nonce = types.EncodeNonce(block.Nonce())
 		header.MixDigest = block.MixDigest()
-		if err := ethash.VerifySeal(nil, header); err != nil {
+		if err := ethash.verifySeal(nil, header, false); err != nil {
 			t.Fatalf("unexpected verification error: %v", err)
 		}
-	case <-time.NewTimer(2 * time.Second).C:
+	case <-time.NewTimer(4 * time.Second).C:
 		t.Error("sealing result timeout")
 	}
 }
@@ -62,7 +62,14 @@ func TestCacheFileEvict(t *testing.T) {
 		t.Fatal(err)
 	}
 	defer os.RemoveAll(tmpdir)
-	e := New(Config{CachesInMem: 3, CachesOnDisk: 10, CacheDir: tmpdir, PowMode: ModeTest}, nil, false)
+
+	config := Config{
+		CachesInMem:  3,
+		CachesOnDisk: 10,
+		CacheDir:     tmpdir,
+		PowMode:      ModeTest,
+	}
+	e := New(config, nil, false)
 	defer e.Close()
 
 	workers := 8
@@ -86,7 +93,7 @@ func verifyTest(wg *sync.WaitGroup, e *Ethash, workerIndex, epochs int) {
 			block = 0
 		}
 		header := &types.Header{Number: big.NewInt(block), Difficulty: big.NewInt(100)}
-		e.VerifySeal(nil, header)
+		e.verifySeal(nil, header, false)
 	}
 }
 
@@ -128,7 +135,7 @@ func TestRemoteSealer(t *testing.T) {
 	}
 }
 
-func TestHashRate(t *testing.T) {
+func TestHashrate(t *testing.T) {
 	var (
 		hashrate = []hexutil.Uint64{100, 200, 300}
 		expect   uint64
@@ -143,7 +150,7 @@ func TestHashRate(t *testing.T) {
 
 	api := &API{ethash}
 	for i := 0; i < len(hashrate); i += 1 {
-		if res := api.SubmitHashRate(hashrate[i], ids[i]); !res {
+		if res := api.SubmitHashrate(hashrate[i], ids[i]); !res {
 			t.Error("remote miner submit hashrate failed")
 		}
 		expect += uint64(hashrate[i])
@@ -163,7 +170,7 @@ func TestClosedRemoteSealer(t *testing.T) {
 		t.Error("expect to return an error to indicate ethash is stopped")
 	}
 
-	if res := api.SubmitHashRate(hexutil.Uint64(100), common.HexToHash("a")); res {
+	if res := api.SubmitHashrate(hexutil.Uint64(100), common.HexToHash("a")); res {
 		t.Error("expect to return false when submit hashrate to a stopped ethash")
 	}
 }
diff --git a/consensus/ethash/sealer.go b/consensus/ethash/sealer.go
index 52c4ed46dc..1830e672b1 100644
--- a/consensus/ethash/sealer.go
+++ b/consensus/ethash/sealer.go
@@ -48,7 +48,7 @@ var (
 
 // Seal implements consensus.Engine, attempting to find a nonce that satisfies
 // the block's difficulty requirements.
-func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
+func (ethash *Ethash) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
 	// If we're running a fake PoW, simply return a 0 nonce immediately
 	if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
 		header := block.Header()
@@ -358,7 +358,16 @@ func (s *remoteSealer) makeWork(block *types.Block) {
 // new work to be processed.
 func (s *remoteSealer) notifyWork() {
 	work := s.currentWork
-	blob, _ := json.Marshal(work)
+
+	// Encode the JSON payload of the notification. When NotifyFull is set,
+	// this is the complete block header, otherwise it is a JSON array.
+	var blob []byte
+	if s.ethash.config.NotifyFull {
+		blob, _ = json.Marshal(s.currentBlock.Header())
+	} else {
+		blob, _ = json.Marshal(work)
+	}
+
 	s.reqWG.Add(len(s.notifyURLs))
 	for _, url := range s.notifyURLs {
 		go s.sendNotification(s.notifyCtx, url, blob, work)
diff --git a/consensus/ethash/sealer_test.go b/consensus/ethash/sealer_test.go
index 7f83def072..c34e76aec2 100644
--- a/consensus/ethash/sealer_test.go
+++ b/consensus/ethash/sealer_test.go
@@ -22,6 +22,7 @@ import (
 	"math/big"
 	"net/http"
 	"net/http/httptest"
+	"strconv"
 	"testing"
 	"time"
 
@@ -74,6 +75,50 @@ func TestRemoteNotify(t *testing.T) {
 	}
 }
 
+// Tests whether remote HTTP servers are correctly notified of new work. (Full pending block body / --miner.notify.full)
+func TestRemoteNotifyFull(t *testing.T) {
+	// Start a simple web server to capture notifications.
+	sink := make(chan map[string]interface{})
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+		blob, err := ioutil.ReadAll(req.Body)
+		if err != nil {
+			t.Errorf("failed to read miner notification: %v", err)
+		}
+		var work map[string]interface{}
+		if err := json.Unmarshal(blob, &work); err != nil {
+			t.Errorf("failed to unmarshal miner notification: %v", err)
+		}
+		sink <- work
+	}))
+	defer server.Close()
+
+	// Create the custom ethash engine.
+	config := Config{
+		PowMode:    ModeTest,
+		NotifyFull: true,
+		Log:        testlog.Logger(t, log.LvlWarn),
+	}
+	ethash := New(config, []string{server.URL}, false)
+	defer ethash.Close()
+
+	// Stream a work task and ensure the notification bubbles out.
+	header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
+	block := types.NewBlockWithHeader(header)
+
+	ethash.Seal(nil, block, nil, nil)
+	select {
+	case work := <-sink:
+		if want := "0x" + strconv.FormatUint(header.Number.Uint64(), 16); work["number"] != want {
+			t.Errorf("pending block number mismatch: have %v, want %v", work["number"], want)
+		}
+		if want := "0x" + header.Difficulty.Text(16); work["difficulty"] != want {
+			t.Errorf("pending block difficulty mismatch: have %s, want %s", work["difficulty"], want)
+		}
+	case <-time.After(3 * time.Second):
+		t.Fatalf("notification timed out")
+	}
+}
+
 // Tests that pushing work packages fast to the miner doesn't cause any data race
 // issues in the notifications.
 func TestRemoteMultiNotify(t *testing.T) {
@@ -97,16 +142,71 @@ func TestRemoteMultiNotify(t *testing.T) {
 	ethash.config.Log = testlog.Logger(t, log.LvlWarn)
 	defer ethash.Close()
 
+	// Provide a results reader.
+	// Otherwise the unread results will be logged asynchronously
+	// and this can happen after the test is finished, causing a panic.
+	results := make(chan *types.Block, cap(sink))
+
+	// Stream a lot of work task and ensure all the notifications bubble out.
+	for i := 0; i < cap(sink); i++ {
+		header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
+		block := types.NewBlockWithHeader(header)
+		ethash.Seal(nil, block, results, nil)
+	}
+
+	for i := 0; i < cap(sink); i++ {
+		select {
+		case <-sink:
+			<-results
+		case <-time.After(10 * time.Second):
+			t.Fatalf("notification %d timed out", i)
+		}
+	}
+}
+
+// Tests that pushing work packages fast to the miner doesn't cause any data race
+// issues in the notifications. Full pending block body / --miner.notify.full)
+func TestRemoteMultiNotifyFull(t *testing.T) {
+	// Start a simple web server to capture notifications.
+	sink := make(chan map[string]interface{}, 64)
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+		blob, err := ioutil.ReadAll(req.Body)
+		if err != nil {
+			t.Errorf("failed to read miner notification: %v", err)
+		}
+		var work map[string]interface{}
+		if err := json.Unmarshal(blob, &work); err != nil {
+			t.Errorf("failed to unmarshal miner notification: %v", err)
+		}
+		sink <- work
+	}))
+	defer server.Close()
+
+	// Create the custom ethash engine.
+	config := Config{
+		PowMode:    ModeTest,
+		NotifyFull: true,
+		Log:        testlog.Logger(t, log.LvlWarn),
+	}
+	ethash := New(config, []string{server.URL}, false)
+	defer ethash.Close()
+
+	// Provide a results reader.
+	// Otherwise the unread results will be logged asynchronously
+	// and this can happen after the test is finished, causing a panic.
+	results := make(chan *types.Block, cap(sink))
+
 	// Stream a lot of work task and ensure all the notifications bubble out.
 	for i := 0; i < cap(sink); i++ {
 		header := &types.Header{Number: big.NewInt(int64(i)), Difficulty: big.NewInt(100)}
 		block := types.NewBlockWithHeader(header)
-		ethash.Seal(nil, block, nil, nil)
+		ethash.Seal(nil, block, results, nil)
 	}
 
 	for i := 0; i < cap(sink); i++ {
 		select {
 		case <-sink:
+			<-results
 		case <-time.After(10 * time.Second):
 			t.Fatalf("notification %d timed out", i)
 		}
diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go
index 9b22bd7a52..36df036f27 100644
--- a/consensus/misc/dao.go
+++ b/consensus/misc/dao.go
@@ -27,7 +27,7 @@ import (
 )
 
 var (
-	// ErrBadProDAOExtra is returned if a header doens't support the DAO fork on a
+	// ErrBadProDAOExtra is returned if a header doesn't support the DAO fork on a
 	// pro-fork client.
 	ErrBadProDAOExtra = errors.New("bad DAO pro-fork extra-data")
 
diff --git a/consensus/parlia/api.go b/consensus/parlia/api.go
index c723c462ef..77d58ac6bc 100644
--- a/consensus/parlia/api.go
+++ b/consensus/parlia/api.go
@@ -25,7 +25,7 @@ import (
 
 // API is a user facing RPC API to allow query snapshot and validators
 type API struct {
-	chain  consensus.ChainReader
+	chain  consensus.ChainHeaderReader
 	parlia *Parlia
 }
 
diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go
index d789930e32..438042aff2 100644
--- a/consensus/parlia/parlia.go
+++ b/consensus/parlia/parlia.go
@@ -38,6 +38,7 @@ import (
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 const (
@@ -289,14 +290,14 @@ func (p *Parlia) Author(header *types.Header) (common.Address, error) {
 }
 
 // VerifyHeader checks whether a header conforms to the consensus rules.
-func (p *Parlia) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
+func (p *Parlia) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
 	return p.verifyHeader(chain, header, nil)
 }
 
 // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
 // method returns a quit channel to abort the operations and a results channel to
 // retrieve the async verifications (the order is that of the input slice).
-func (p *Parlia) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
+func (p *Parlia) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
 	abort := make(chan struct{})
 	results := make(chan error, len(headers))
 
@@ -318,7 +319,7 @@ func (p *Parlia) VerifyHeaders(chain consensus.ChainReader, headers []*types.Hea
 // caller may optionally pass in a batch of parents (ascending order) to avoid
 // looking those up from the database. This is useful for concurrently verifying
 // a batch of new headers.
-func (p *Parlia) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+func (p *Parlia) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
 	if header.Number == nil {
 		return errUnknownBlock
 	}
@@ -374,7 +375,7 @@ func (p *Parlia) verifyHeader(chain consensus.ChainReader, header *types.Header,
 // rather depend on a batch of previous headers. The caller may optionally pass
 // in a batch of parents (ascending order) to avoid looking those up from the
 // database. This is useful for concurrently verifying a batch of new headers.
-func (p *Parlia) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+func (p *Parlia) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
 	// The genesis block is the always valid dead-end
 	number := header.Number.Uint64()
 	if number == 0 {
@@ -428,7 +429,7 @@ func (p *Parlia) verifyCascadingFields(chain consensus.ChainReader, header *type
 }
 
 // snapshot retrieves the authorization snapshot at a given point in time.
-func (p *Parlia) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
+func (p *Parlia) snapshot(chain consensus.ChainHeaderReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
 	// Search for a snapshot in memory or on disk for checkpoints
 	var (
 		headers []*types.Header
@@ -540,7 +541,7 @@ func (p *Parlia) VerifySeal(chain consensus.ChainReader, header *types.Header) e
 // consensus protocol requirements. The method accepts an optional list of parent
 // headers that aren't yet part of the local blockchain to generate the snapshots
 // from.
-func (p *Parlia) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+func (p *Parlia) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
 	// Verifying the genesis block is not supported
 	number := header.Number.Uint64()
 	if number == 0 {
@@ -591,7 +592,7 @@ func (p *Parlia) verifySeal(chain consensus.ChainReader, header *types.Header, p
 
 // Prepare implements consensus.Engine, preparing all the consensus fields of the
 // header for running the transactions on top.
-func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) error {
+func (p *Parlia) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
 	header.Coinbase = p.val
 	header.Nonce = types.BlockNonce{}
 
@@ -644,7 +645,7 @@ func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) erro
 
 // Finalize implements consensus.Engine, ensuring no uncles are set, nor block
 // rewards given.
-func (p *Parlia) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction,
+func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction,
 	uncles []*types.Header, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, usedGas *uint64) error {
 	// warn if not in majority fork
 	number := header.Number.Uint64()
@@ -716,7 +717,7 @@ func (p *Parlia) Finalize(chain consensus.ChainReader, header *types.Header, sta
 
 // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
 // nor block rewards given, and returns the final block.
-func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB,
+func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB,
 	txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, []*types.Receipt, error) {
 	// No block rewards in PoA, so the state remains as is and uncles are dropped
 	cx := chainContext{Chain: chain, parlia: p}
@@ -766,7 +767,7 @@ func (p *Parlia) FinalizeAndAssemble(chain consensus.ChainReader, header *types.
 	header.UncleHash = types.CalcUncleHash(nil)
 
 	// Assemble and return the final block for sealing
-	return types.NewBlock(header, txs, nil, receipts), receipts, nil
+	return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), receipts, nil
 }
 
 // Authorize injects a private key into the consensus engine to mint new blocks
@@ -792,7 +793,7 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header) *time.
 
 // Seal implements consensus.Engine, attempting to create a sealed block using
 // the local signing credentials.
-func (p *Parlia) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
+func (p *Parlia) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
 	header := block.Header()
 
 	// Sealing the genesis block is not supported
@@ -873,7 +874,7 @@ func (p *Parlia) EnoughDistance(chain consensus.ChainReader, header *types.Heade
 // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
 // that a new block should have based on the previous blocks in the chain and the
 // current signer.
-func (p *Parlia) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
+func (p *Parlia) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
 	snap, err := p.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
 	if err != nil {
 		return nil
@@ -897,7 +898,7 @@ func (p *Parlia) SealHash(header *types.Header) common.Hash {
 }
 
 // APIs implements consensus.Engine, returning the user facing RPC API to query snapshot.
-func (p *Parlia) APIs(chain consensus.ChainReader) []rpc.API {
+func (p *Parlia) APIs(chain consensus.ChainHeaderReader) []rpc.API {
 	return []rpc.API{{
 		Namespace: "parlia",
 		Version:   "1.0",
@@ -947,7 +948,7 @@ func (p *Parlia) getCurrentValidators(blockHash common.Hash) ([]common.Address,
 	)
 	out := ret0
 
-	if err := p.validatorSetABI.Unpack(out, method, result); err != nil {
+	if err := p.validatorSetABI.UnpackIntoInterface(out, method, result); err != nil {
 		return nil, err
 	}
 
@@ -1198,7 +1199,7 @@ func backOffTime(snap *Snapshot, val common.Address) uint64 {
 
 // chain context
 type chainContext struct {
-	Chain  consensus.ChainReader
+	Chain  consensus.ChainHeaderReader
 	parlia consensus.Engine
 }
 
@@ -1233,10 +1234,10 @@ func applyMessage(
 	chainContext core.ChainContext,
 ) (uint64, error) {
 	// Create a new context to be used in the EVM environment
-	context := core.NewEVMContext(msg, header, chainContext, nil)
+	context := core.NewEVMBlockContext(header, chainContext, nil)
 	// Create a new environment which holds all relevant information
 	// about the transaction and calling mechanisms.
-	vmenv := vm.NewEVM(context, state, chainConfig, vm.Config{})
+	vmenv := vm.NewEVM(context, vm.TxContext{Origin: msg.From(), GasPrice: big.NewInt(0)}, state, chainConfig, vm.Config{})
 	// Apply the transaction to the current state (included in the env)
 	ret, returnGas, err := vmenv.Call(
 		vm.AccountRef(msg.From()),
diff --git a/consensus/parlia/snapshot.go b/consensus/parlia/snapshot.go
index 851a20f2df..05aa8de64f 100644
--- a/consensus/parlia/snapshot.go
+++ b/consensus/parlia/snapshot.go
@@ -141,7 +141,7 @@ func (s *Snapshot) isMajorityFork(forkHash string) bool {
 	return ally > len(s.RecentForkHashes)/2
 }
 
-func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainReader, parents []*types.Header, chainId *big.Int) (*Snapshot, error) {
+func (s *Snapshot) apply(headers []*types.Header, chain consensus.ChainHeaderReader, parents []*types.Header, chainId *big.Int) (*Snapshot, error) {
 	// Allow passing in no headers for cleaner code
 	if len(headers) == 0 {
 		return s, nil
@@ -288,7 +288,7 @@ func ParseValidators(validatorsBytes []byte) ([]common.Address, error) {
 	return result, nil
 }
 
-func FindAncientHeader(header *types.Header, ite uint64, chain consensus.ChainReader, candidateParents []*types.Header) *types.Header {
+func FindAncientHeader(header *types.Header, ite uint64, chain consensus.ChainHeaderReader, candidateParents []*types.Header) *types.Header {
 	ancient := header
 	for i := uint64(1); i <= ite; i++ {
 		parentHash := ancient.ParentHash
diff --git a/console/bridge.go b/console/bridge.go
index 2625c481d5..21ef0e8e7b 100644
--- a/console/bridge.go
+++ b/console/bridge.go
@@ -28,6 +28,7 @@ import (
 	"github.com/ethereum/go-ethereum/accounts/scwallet"
 	"github.com/ethereum/go-ethereum/accounts/usbwallet"
 	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/console/prompt"
 	"github.com/ethereum/go-ethereum/internal/jsre"
 	"github.com/ethereum/go-ethereum/rpc"
 )
@@ -35,13 +36,13 @@ import (
 // bridge is a collection of JavaScript utility methods to bride the .js runtime
 // environment and the Go RPC connection backing the remote method calls.
 type bridge struct {
-	client   *rpc.Client  // RPC client to execute Ethereum requests through
-	prompter UserPrompter // Input prompter to allow interactive user feedback
-	printer  io.Writer    // Output writer to serialize any display strings to
+	client   *rpc.Client         // RPC client to execute Ethereum requests through
+	prompter prompt.UserPrompter // Input prompter to allow interactive user feedback
+	printer  io.Writer           // Output writer to serialize any display strings to
 }
 
 // newBridge creates a new JavaScript wrapper around an RPC client.
-func newBridge(client *rpc.Client, prompter UserPrompter, printer io.Writer) *bridge {
+func newBridge(client *rpc.Client, prompter prompt.UserPrompter, printer io.Writer) *bridge {
 	return &bridge{
 		client:   client,
 		prompter: prompter,
@@ -143,15 +144,14 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) {
 		if val, err = openWallet(goja.Null(), wallet, passwd); err != nil {
 			if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) {
 				return nil, err
-			} else {
-				// PIN input requested, fetch from the user and call open again
-				input, err := b.prompter.PromptPassword("Please enter current PIN: ")
-				if err != nil {
-					return nil, err
-				}
-				if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil {
-					return nil, err
-				}
+			}
+			// PIN input requested, fetch from the user and call open again
+			input, err := b.prompter.PromptPassword("Please enter current PIN: ")
+			if err != nil {
+				return nil, err
+			}
+			if val, err = openWallet(goja.Null(), wallet, call.VM.ToValue(input)); err != nil {
+				return nil, err
 			}
 		}
 
@@ -229,11 +229,15 @@ func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) {
 // original RPC method (saved in jeth.unlockAccount) with it to actually execute
 // the RPC call.
 func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) {
+	if len(call.Arguments) < 1 {
+		return nil, fmt.Errorf("usage: unlockAccount(account, [ password, duration ])")
+	}
+
+	account := call.Argument(0)
 	// Make sure we have an account specified to unlock.
-	if call.Argument(0).ExportType().Kind() != reflect.String {
+	if goja.IsUndefined(account) || goja.IsNull(account) || account.ExportType().Kind() != reflect.String {
 		return nil, fmt.Errorf("first argument must be the account to unlock")
 	}
-	account := call.Argument(0)
 
 	// If password is not given or is the null value, prompt the user for it.
 	var passwd goja.Value
@@ -272,16 +276,19 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) {
 // prompt to acquire the passphrase and executes the original RPC method (saved in
 // jeth.sign) with it to actually execute the RPC call.
 func (b *bridge) Sign(call jsre.Call) (goja.Value, error) {
+	if nArgs := len(call.Arguments); nArgs < 2 {
+		return nil, fmt.Errorf("usage: sign(message, account, [ password ])")
+	}
 	var (
 		message = call.Argument(0)
 		account = call.Argument(1)
 		passwd  = call.Argument(2)
 	)
 
-	if message.ExportType().Kind() != reflect.String {
+	if goja.IsUndefined(message) || message.ExportType().Kind() != reflect.String {
 		return nil, fmt.Errorf("first argument must be the message to sign")
 	}
-	if account.ExportType().Kind() != reflect.String {
+	if goja.IsUndefined(account) || account.ExportType().Kind() != reflect.String {
 		return nil, fmt.Errorf("second argument must be the account to sign with")
 	}
 
@@ -298,19 +305,23 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) {
 	}
 
 	// Send the request to the backend and return
-	sign, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount"))
+	sign, callable := goja.AssertFunction(getJeth(call.VM).Get("sign"))
 	if !callable {
-		return nil, fmt.Errorf("jeth.unlockAccount is not callable")
+		return nil, fmt.Errorf("jeth.sign is not callable")
 	}
 	return sign(goja.Null(), message, account, passwd)
 }
 
 // Sleep will block the console for the specified number of seconds.
 func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) {
-	if !isNumber(call.Argument(0)) {
+	if nArgs := len(call.Arguments); nArgs < 1 {
+		return nil, fmt.Errorf("usage: sleep()")
+	}
+	sleepObj := call.Argument(0)
+	if goja.IsUndefined(sleepObj) || goja.IsNull(sleepObj) || !isNumber(sleepObj) {
 		return nil, fmt.Errorf("usage: sleep()")
 	}
-	sleep := call.Argument(0).ToFloat()
+	sleep := sleepObj.ToFloat()
 	time.Sleep(time.Duration(sleep * float64(time.Second)))
 	return call.VM.ToValue(true), nil
 }
@@ -328,27 +339,27 @@ func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) {
 		return nil, fmt.Errorf("usage: sleepBlocks([, max sleep in seconds])")
 	}
 	if nArgs >= 1 {
-		if !isNumber(call.Argument(0)) {
+		if goja.IsNull(call.Argument(0)) || goja.IsUndefined(call.Argument(0)) || !isNumber(call.Argument(0)) {
 			return nil, fmt.Errorf("expected number as first argument")
 		}
 		blocks = call.Argument(0).ToInteger()
 	}
 	if nArgs >= 2 {
-		if isNumber(call.Argument(1)) {
+		if goja.IsNull(call.Argument(1)) || goja.IsUndefined(call.Argument(1)) || !isNumber(call.Argument(1)) {
 			return nil, fmt.Errorf("expected number as second argument")
 		}
 		sleep = call.Argument(1).ToInteger()
 	}
 
 	// Poll the current block number until either it or a timeout is reached.
-	var (
-		deadline   = time.Now().Add(time.Duration(sleep) * time.Second)
-		lastNumber = ^hexutil.Uint64(0)
-	)
+	deadline := time.Now().Add(time.Duration(sleep) * time.Second)
+	var lastNumber hexutil.Uint64
+	if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil {
+		return nil, err
+	}
 	for time.Now().Before(deadline) {
 		var number hexutil.Uint64
-		err := b.client.Call(&number, "eth_blockNumber")
-		if err != nil {
+		if err := b.client.Call(&number, "eth_blockNumber"); err != nil {
 			return nil, err
 		}
 		if number != lastNumber {
@@ -401,9 +412,7 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
 		resp.Set("id", req.ID)
 
 		var result json.RawMessage
-		err = b.client.Call(&result, req.Method, req.Params...)
-		switch err := err.(type) {
-		case nil:
+		if err = b.client.Call(&result, req.Method, req.Params...); err == nil {
 			if result == nil {
 				// Special case null because it is decoded as an empty
 				// raw message for some reason.
@@ -416,19 +425,24 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
 				}
 				resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result)))
 				if err != nil {
-					setError(resp, -32603, err.Error())
+					setError(resp, -32603, err.Error(), nil)
 				} else {
 					resp.Set("result", resultVal)
 				}
 			}
-		case rpc.Error:
-			setError(resp, err.ErrorCode(), err.Error())
-		default:
-			setError(resp, -32603, err.Error())
+		} else {
+			code := -32603
+			var data interface{}
+			if err, ok := err.(rpc.Error); ok {
+				code = err.ErrorCode()
+			}
+			if err, ok := err.(rpc.DataError); ok {
+				data = err.ErrorData()
+			}
+			setError(resp, code, err.Error(), data)
 		}
 		resps = append(resps, resp)
 	}
-
 	// Return the responses either to the callback (if supplied)
 	// or directly as the return value.
 	var result goja.Value
@@ -444,8 +458,14 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) {
 	return result, nil
 }
 
-func setError(resp *goja.Object, code int, msg string) {
-	resp.Set("error", map[string]interface{}{"code": code, "message": msg})
+func setError(resp *goja.Object, code int, msg string, data interface{}) {
+	err := make(map[string]interface{})
+	err["code"] = code
+	err["message"] = msg
+	if data != nil {
+		err["data"] = data
+	}
+	resp.Set("error", err)
 }
 
 // isNumber returns true if input value is a JS number.
diff --git a/console/bridge_test.go b/console/bridge_test.go
new file mode 100644
index 0000000000..e57e294fc5
--- /dev/null
+++ b/console/bridge_test.go
@@ -0,0 +1,48 @@
+// 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 .
+
+package console
+
+import (
+	"testing"
+
+	"github.com/dop251/goja"
+	"github.com/ethereum/go-ethereum/internal/jsre"
+)
+
+// TestUndefinedAsParam ensures that personal functions can receive
+// `undefined` as a parameter.
+func TestUndefinedAsParam(t *testing.T) {
+	b := bridge{}
+	call := jsre.Call{}
+	call.Arguments = []goja.Value{goja.Undefined()}
+
+	b.UnlockAccount(call)
+	b.Sign(call)
+	b.Sleep(call)
+}
+
+// TestNullAsParam ensures that personal functions can receive
+// `null` as a parameter.
+func TestNullAsParam(t *testing.T) {
+	b := bridge{}
+	call := jsre.Call{}
+	call.Arguments = []goja.Value{goja.Null()}
+
+	b.UnlockAccount(call)
+	b.Sign(call)
+	b.Sleep(call)
+}
diff --git a/console/console.go b/console/console.go
index e2b4835e46..ae9f28da04 100644
--- a/console/console.go
+++ b/console/console.go
@@ -29,6 +29,7 @@ import (
 	"syscall"
 
 	"github.com/dop251/goja"
+	"github.com/ethereum/go-ethereum/console/prompt"
 	"github.com/ethereum/go-ethereum/internal/jsre"
 	"github.com/ethereum/go-ethereum/internal/jsre/deps"
 	"github.com/ethereum/go-ethereum/internal/web3ext"
@@ -38,7 +39,8 @@ import (
 )
 
 var (
-	passwordRegexp = regexp.MustCompile(`personal.[nus]`)
+	// u: unlock, s: signXX, sendXX, n: newAccount, i: importXX
+	passwordRegexp = regexp.MustCompile(`personal.[nusi]`)
 	onlyWhitespace = regexp.MustCompile(`^\s*$`)
 	exit           = regexp.MustCompile(`^\s*exit\s*;*\s*$`)
 )
@@ -52,26 +54,26 @@ const DefaultPrompt = "> "
 // Config is the collection of configurations to fine tune the behavior of the
 // JavaScript console.
 type Config struct {
-	DataDir  string       // Data directory to store the console history at
-	DocRoot  string       // Filesystem path from where to load JavaScript files from
-	Client   *rpc.Client  // RPC client to execute Ethereum requests through
-	Prompt   string       // Input prompt prefix string (defaults to DefaultPrompt)
-	Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter)
-	Printer  io.Writer    // Output writer to serialize any display strings to (defaults to os.Stdout)
-	Preload  []string     // Absolute paths to JavaScript files to preload
+	DataDir  string              // Data directory to store the console history at
+	DocRoot  string              // Filesystem path from where to load JavaScript files from
+	Client   *rpc.Client         // RPC client to execute Ethereum requests through
+	Prompt   string              // Input prompt prefix string (defaults to DefaultPrompt)
+	Prompter prompt.UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter)
+	Printer  io.Writer           // Output writer to serialize any display strings to (defaults to os.Stdout)
+	Preload  []string            // Absolute paths to JavaScript files to preload
 }
 
 // Console is a JavaScript interpreted runtime environment. It is a fully fledged
 // JavaScript console attached to a running node via an external or in-process RPC
 // client.
 type Console struct {
-	client   *rpc.Client  // RPC client to execute Ethereum requests through
-	jsre     *jsre.JSRE   // JavaScript runtime environment running the interpreter
-	prompt   string       // Input prompt prefix string
-	prompter UserPrompter // Input prompter to allow interactive user feedback
-	histPath string       // Absolute path to the console scrollback history
-	history  []string     // Scroll history maintained by the console
-	printer  io.Writer    // Output writer to serialize any display strings to
+	client   *rpc.Client         // RPC client to execute Ethereum requests through
+	jsre     *jsre.JSRE          // JavaScript runtime environment running the interpreter
+	prompt   string              // Input prompt prefix string
+	prompter prompt.UserPrompter // Input prompter to allow interactive user feedback
+	histPath string              // Absolute path to the console scrollback history
+	history  []string            // Scroll history maintained by the console
+	printer  io.Writer           // Output writer to serialize any display strings to
 }
 
 // New initializes a JavaScript interpreted runtime environment and sets defaults
@@ -79,7 +81,7 @@ type Console struct {
 func New(config Config) (*Console, error) {
 	// Handle unset config values gracefully
 	if config.Prompter == nil {
-		config.Prompter = Stdin
+		config.Prompter = prompt.Stdin
 	}
 	if config.Prompt == "" {
 		config.Prompt = DefaultPrompt
@@ -322,6 +324,7 @@ func (c *Console) Welcome() {
 		sort.Strings(modules)
 		message += " modules: " + strings.Join(modules, " ") + "\n"
 	}
+	message += "\nTo exit, press ctrl-d"
 	fmt.Fprintln(c.printer, message)
 }
 
@@ -370,7 +373,7 @@ func (c *Console) Interactive() {
 			return
 
 		case err := <-inputErr:
-			if err == liner.ErrPromptAborted && indents > 0 {
+			if err == liner.ErrPromptAborted {
 				// When prompting for multi-line input, the first Ctrl-C resets
 				// the multi-line state.
 				prompt, indents, input = c.prompt, 0, ""
diff --git a/console/console_test.go b/console/console_test.go
index 9a2b474442..f6ab781410 100644
--- a/console/console_test.go
+++ b/console/console_test.go
@@ -28,8 +28,10 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/console/prompt"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/internal/jsre"
 	"github.com/ethereum/go-ethereum/miner"
 	"github.com/ethereum/go-ethereum/node"
@@ -67,10 +69,10 @@ func (p *hookedPrompter) PromptPassword(prompt string) (string, error) {
 func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) {
 	return false, errors.New("not implemented")
 }
-func (p *hookedPrompter) SetHistory(history []string)              {}
-func (p *hookedPrompter) AppendHistory(command string)             {}
-func (p *hookedPrompter) ClearHistory()                            {}
-func (p *hookedPrompter) SetWordCompleter(completer WordCompleter) {}
+func (p *hookedPrompter) SetHistory(history []string)                     {}
+func (p *hookedPrompter) AppendHistory(command string)                    {}
+func (p *hookedPrompter) ClearHistory()                                   {}
+func (p *hookedPrompter) SetWordCompleter(completer prompt.WordCompleter) {}
 
 // tester is a console test environment for the console tests to operate on.
 type tester struct {
@@ -84,7 +86,7 @@ type tester struct {
 
 // newTester creates a test environment based on which the console can operate.
 // Please ensure you call Close() on the returned tester to avoid leaks.
-func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
+func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester {
 	// Create a temporary storage for the node keys and initialize it
 	workspace, err := ioutil.TempDir("", "console-tester-")
 	if err != nil {
@@ -96,7 +98,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
 	if err != nil {
 		t.Fatalf("failed to create node: %v", err)
 	}
-	ethConf := ð.Config{
+	ethConf := ðconfig.Config{
 		Genesis: core.DeveloperGenesisBlock(15, common.Address{}),
 		Miner: miner.Config{
 			Etherbase: common.HexToAddress(testAddress),
@@ -108,7 +110,8 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
 	if confOverride != nil {
 		confOverride(ethConf)
 	}
-	if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
+	ethBackend, err := eth.New(stack, ethConf)
+	if err != nil {
 		t.Fatalf("failed to register Ethereum protocol: %v", err)
 	}
 	// Start the node and assemble the JavaScript console around it
@@ -134,13 +137,10 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
 		t.Fatalf("failed to create JavaScript console: %v", err)
 	}
 	// Create the final tester and return
-	var ethereum *eth.Ethereum
-	stack.Service(ðereum)
-
 	return &tester{
 		workspace: workspace,
 		stack:     stack,
-		ethereum:  ethereum,
+		ethereum:  ethBackend,
 		console:   console,
 		input:     prompter,
 		output:    printer,
diff --git a/console/prompter.go b/console/prompt/prompter.go
similarity index 99%
rename from console/prompter.go
rename to console/prompt/prompter.go
index 65675061a7..810b6c3e14 100644
--- a/console/prompter.go
+++ b/console/prompt/prompter.go
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package console
+package prompt
 
 import (
 	"fmt"
diff --git a/contracts/checkpointoracle/contract/oracle.go b/contracts/checkpointoracle/contract/oracle.go
index 3bb351792f..a4a308f5c5 100644
--- a/contracts/checkpointoracle/contract/oracle.go
+++ b/contracts/checkpointoracle/contract/oracle.go
@@ -20,7 +20,6 @@ var (
 	_ = big.NewInt
 	_ = strings.NewReader
 	_ = ethereum.NotFound
-	_ = abi.U256
 	_ = bind.Bind
 	_ = common.Big1
 	_ = types.BloomLookup
@@ -28,10 +27,17 @@ var (
 )
 
 // CheckpointOracleABI is the input ABI used to generate the binding from.
-const CheckpointOracleABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"name\":\"_hash\",\"type\":\"bytes32\"},{\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"name\":\"v\",\"type\":\"uint8[]\"},{\"name\":\"r\",\"type\":\"bytes32[]\"},{\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"name\":\"_threshold\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"}]"
+const CheckpointOracleABI = "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_hash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"internalType\":\"uint8[]\",\"name\":\"v\",\"type\":\"uint8[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"r\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
+
+// CheckpointOracleFuncSigs maps the 4-byte function signature to its string representation.
+var CheckpointOracleFuncSigs = map[string]string{
+	"45848dfc": "GetAllAdmin()",
+	"4d6a304c": "GetLatestCheckpoint()",
+	"d459fc46": "SetCheckpoint(uint256,bytes32,bytes32,uint64,uint8[],bytes32[],bytes32[])",
+}
 
 // CheckpointOracleBin is the compiled bytecode used for deploying new contracts.
-const CheckpointOracleBin = `0x608060405234801561001057600080fd5b506040516108153803806108158339818101604052608081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8201602081018481111561005e57600080fd5b815185602082028301116401000000008211171561007b57600080fd5b505060208201516040830151606090930151919450925060005b84518110156101415760016000808784815181106100af57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff02191690831515021790555060018582815181106100fc57fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b039093169290921790915501610095565b50600592909255600655600755506106b78061015e6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a661034f565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061036a945050505050565b604080519115158252519081900360200190f35b6060806001805490506040519080825280602002602001820160405280156102e2578160200160208202803883390190505b50905060005b60015481101561034957600181815481106102ff57fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061032957fe5b6001600160a01b03909216602092830291909101909101526001016102e8565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661038657600080fd5b8688401461039357600080fd5b82518451146103a157600080fd5b81518451146103af57600080fd5b6006546005548660010167ffffffffffffffff1602014310156103d457506000610677565b60025467ffffffffffffffff90811690861610156103f457506000610677565b60025467ffffffffffffffff8681169116148015610426575067ffffffffffffffff8516151580610426575060035415155b1561043357506000610677565b8561044057506000610677565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106715760006001848984815181106104b457fe5b60200260200101518985815181106104c857fe5b60200260200101518986815181106104dc57fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561053b573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061057057600080fd5b826001600160a01b0316816001600160a01b03161161058e57600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105ca57fe5b60200260200101518a86815181106105de57fe5b60200260200101518a87815181106105f257fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a260075482600101106106685750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff86161790556001610677565b5060010161049a565b50600080fd5b97965050505050505056fea265627a7a723058207f6a191ce575596a2f1e907c8c0a01003d16b69fb2c4f432d10878e8c0a99a0264736f6c634300050a0032`
+var CheckpointOracleBin = "0x608060405234801561001057600080fd5b506040516108703803806108708339818101604052608081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825186602082028301116401000000008211171561008557600080fd5b82525081516020918201928201910280838360005b838110156100b257818101518382015260200161009a565b50505050919091016040908152602083015190830151606090930151909450919250600090505b84518110156101855760016000808784815181106100f357fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550600185828151811061014057fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155016100d9565b50600592909255600655600755506106ce806101a26000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a6610365565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610380945050505050565b604080519115158252519081900360200190f35b600154606090819067ffffffffffffffff811180156102ce57600080fd5b506040519080825280602002602001820160405280156102f8578160200160208202803683370190505b50905060005b60015481101561035f576001818154811061031557fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061033f57fe5b6001600160a01b03909216602092830291909101909101526001016102fe565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661039c57600080fd5b868840146103a957600080fd5b82518451146103b757600080fd5b81518451146103c557600080fd5b6006546005548660010167ffffffffffffffff1602014310156103ea5750600061068d565b60025467ffffffffffffffff908116908616101561040a5750600061068d565b60025467ffffffffffffffff868116911614801561043c575067ffffffffffffffff851615158061043c575060035415155b156104495750600061068d565b856104565750600061068d565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106875760006001848984815181106104ca57fe5b60200260200101518985815181106104de57fe5b60200260200101518986815181106104f257fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015610551573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061058657600080fd5b826001600160a01b0316816001600160a01b0316116105a457600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105e057fe5b60200260200101518a86815181106105f457fe5b60200260200101518a878151811061060857fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a2600754826001011061067e5750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff8616179055600161068d565b506001016104b0565b50600080fd5b97965050505050505056fea26469706673582212202ddf9eda76bf59c0fc65584c0b22d84ecef2c703765de60439596d6ac34c2b7264736f6c634300060b0033"
 
 // DeployCheckpointOracle deploys a new Ethereum contract, binding an instance of CheckpointOracle to it.
 func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address, _sectionSize *big.Int, _processConfirms *big.Int, _threshold *big.Int) (common.Address, *types.Transaction, *CheckpointOracle, error) {
@@ -39,6 +45,7 @@ func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBacken
 	if err != nil {
 		return common.Address{}, nil, nil, err
 	}
+
 	address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(CheckpointOracleBin), backend, _adminlist, _sectionSize, _processConfirms, _threshold)
 	if err != nil {
 		return common.Address{}, nil, nil, err
@@ -154,7 +161,7 @@ func bindCheckpointOracle(address common.Address, caller bind.ContractCaller, tr
 // sets the output to result. The result type might be a single field for simple
 // returns, a slice of interfaces for anonymous returns and a struct for named
 // returns.
-func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
 	return _CheckpointOracle.Contract.CheckpointOracleCaller.contract.Call(opts, result, method, params...)
 }
 
@@ -173,7 +180,7 @@ func (_CheckpointOracle *CheckpointOracleRaw) Transact(opts *bind.TransactOpts,
 // sets the output to result. The result type might be a single field for simple
 // returns, a slice of interfaces for anonymous returns and a struct for named
 // returns.
-func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
+func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
 	return _CheckpointOracle.Contract.contract.Call(opts, result, method, params...)
 }
 
@@ -190,58 +197,64 @@ func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transact(opts *bind.Tran
 
 // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
 //
-// Solidity: function GetAllAdmin() constant returns(address[])
+// Solidity: function GetAllAdmin() view returns(address[])
 func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) {
-	var (
-		ret0 = new([]common.Address)
-	)
-	out := ret0
-	err := _CheckpointOracle.contract.Call(opts, out, "GetAllAdmin")
-	return *ret0, err
+	var out []interface{}
+	err := _CheckpointOracle.contract.Call(opts, &out, "GetAllAdmin")
+
+	if err != nil {
+		return *new([]common.Address), err
+	}
+
+	out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address)
+
+	return out0, err
+
 }
 
 // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
 //
-// Solidity: function GetAllAdmin() constant returns(address[])
+// Solidity: function GetAllAdmin() view returns(address[])
 func (_CheckpointOracle *CheckpointOracleSession) GetAllAdmin() ([]common.Address, error) {
 	return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts)
 }
 
 // GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
 //
-// Solidity: function GetAllAdmin() constant returns(address[])
+// Solidity: function GetAllAdmin() view returns(address[])
 func (_CheckpointOracle *CheckpointOracleCallerSession) GetAllAdmin() ([]common.Address, error) {
 	return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts)
 }
 
 // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
 //
-// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256)
 func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind.CallOpts) (uint64, [32]byte, *big.Int, error) {
-	var (
-		ret0 = new(uint64)
-		ret1 = new([32]byte)
-		ret2 = new(*big.Int)
-	)
-	out := &[]interface{}{
-		ret0,
-		ret1,
-		ret2,
+	var out []interface{}
+	err := _CheckpointOracle.contract.Call(opts, &out, "GetLatestCheckpoint")
+
+	if err != nil {
+		return *new(uint64), *new([32]byte), *new(*big.Int), err
 	}
-	err := _CheckpointOracle.contract.Call(opts, out, "GetLatestCheckpoint")
-	return *ret0, *ret1, *ret2, err
+
+	out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64)
+	out1 := *abi.ConvertType(out[1], new([32]byte)).(*[32]byte)
+	out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int)
+
+	return out0, out1, out2, err
+
 }
 
 // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
 //
-// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256)
 func (_CheckpointOracle *CheckpointOracleSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) {
 	return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts)
 }
 
 // GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
 //
-// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
+// Solidity: function GetLatestCheckpoint() view returns(uint64, bytes32, uint256)
 func (_CheckpointOracle *CheckpointOracleCallerSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) {
 	return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts)
 }
diff --git a/contracts/checkpointoracle/contract/oracle.sol b/contracts/checkpointoracle/contract/oracle.sol
index 0106447273..65bac09d28 100644
--- a/contracts/checkpointoracle/contract/oracle.sol
+++ b/contracts/checkpointoracle/contract/oracle.sol
@@ -1,4 +1,4 @@
-pragma solidity ^0.5.10;
+pragma solidity ^0.6.0;
 
 /**
  * @title CheckpointOracle
diff --git a/contracts/checkpointoracle/oracle.go b/contracts/checkpointoracle/oracle.go
index 1f273272ab..7f3127d0b8 100644
--- a/contracts/checkpointoracle/oracle.go
+++ b/contracts/checkpointoracle/oracle.go
@@ -65,7 +65,7 @@ func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log,
 			if err != nil {
 				continue
 			}
-			if event.Index == section && common.Hash(event.CheckpointHash) == hash {
+			if event.Index == section && event.CheckpointHash == hash {
 				votes = append(votes, event)
 			}
 		}
diff --git a/contracts/checkpointoracle/oracle_test.go b/contracts/checkpointoracle/oracle_test.go
index 817954d11a..1218481929 100644
--- a/contracts/checkpointoracle/oracle_test.go
+++ b/contracts/checkpointoracle/oracle_test.go
@@ -178,7 +178,7 @@ func TestCheckpointRegister(t *testing.T) {
 	contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000)
 	defer contractBackend.Close()
 
-	transactOpts := bind.NewKeyedTransactor(accounts[0].key)
+	transactOpts, _ := bind.NewKeyedTransactorWithChainID(accounts[0].key, big.NewInt(1337))
 
 	// 3 trusted signers, threshold 2
 	contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2))
diff --git a/core/bench_test.go b/core/bench_test.go
index d7a5e11c2f..0c49907e64 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
 	return func(i int, gen *BlockGen) {
 		toaddr := common.Address{}
 		data := make([]byte, nbytes)
-		gas, _ := IntrinsicGas(data, false, false, false)
+		gas, _ := IntrinsicGas(data, nil, false, false, false)
 		tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey)
 		gen.AddTx(tx)
 	}
@@ -157,7 +157,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
 			b.Fatalf("cannot create temporary directory: %v", err)
 		}
 		defer os.RemoveAll(dir)
-		db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "")
+		db, err = rawdb.NewLevelDBDatabase(dir, 128, 128, "", false)
 		if err != nil {
 			b.Fatalf("cannot create temporary database: %v", err)
 		}
@@ -175,7 +175,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
 
 	// Time the insertion of the new chain.
 	// State and blocks are stored in the same DB.
-	chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	chainman, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer chainman.Stop()
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -255,7 +255,7 @@ func benchWriteChain(b *testing.B, full bool, count uint64) {
 		if err != nil {
 			b.Fatalf("cannot create temporary directory: %v", err)
 		}
-		db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "")
+		db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
 		if err != nil {
 			b.Fatalf("error opening database at %v: %v", dir, err)
 		}
@@ -272,7 +272,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
 	}
 	defer os.RemoveAll(dir)
 
-	db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "")
+	db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
 	if err != nil {
 		b.Fatalf("error opening database at %v: %v", dir, err)
 	}
@@ -283,11 +283,11 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "")
+		db, err := rawdb.NewLevelDBDatabase(dir, 128, 1024, "", false)
 		if err != nil {
 			b.Fatalf("error opening database at %v: %v", dir, err)
 		}
-		chain, err := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
+		chain, err := NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
 		if err != nil {
 			b.Fatalf("error creating chain: %v", err)
 		}
diff --git a/core/block_validator.go b/core/block_validator.go
index e4d96a14aa..6f349b0d04 100644
--- a/core/block_validator.go
+++ b/core/block_validator.go
@@ -25,6 +25,7 @@ import (
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 // BlockValidator is responsible for validating block headers, uncles and
@@ -63,7 +64,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
 	if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
 		return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash)
 	}
-	if hash := types.DeriveSha(block.Transactions()); hash != header.TxHash {
+	if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash {
 		return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
 	}
 	if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
@@ -90,8 +91,8 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
 	if rbloom != header.Bloom {
 		return fmt.Errorf("invalid bloom (remote: %x  local: %x)", header.Bloom, rbloom)
 	}
-	// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]]))
-	receiptSha := types.DeriveSha(receipts)
+	// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
+	receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
 	if receiptSha != header.ReceiptHash {
 		return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
 	}
diff --git a/core/block_validator_test.go b/core/block_validator_test.go
index 06e2ba1a4f..dfb37b88cf 100644
--- a/core/block_validator_test.go
+++ b/core/block_validator_test.go
@@ -42,7 +42,7 @@ func TestHeaderVerification(t *testing.T) {
 		headers[i] = block.Header()
 	}
 	// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
-	chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
+	chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer chain.Stop()
 
 	for i := 0; i < len(blocks); i++ {
@@ -106,11 +106,11 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
 		var results <-chan error
 
 		if valid {
-			chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil)
+			chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
 			_, results = chain.engine.VerifyHeaders(chain, headers, seals)
 			chain.Stop()
 		} else {
-			chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil)
+			chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil)
 			_, results = chain.engine.VerifyHeaders(chain, headers, seals)
 			chain.Stop()
 		}
@@ -173,7 +173,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
 	defer runtime.GOMAXPROCS(old)
 
 	// Start the verifications and immediately abort
-	chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil)
+	chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil)
 	defer chain.Stop()
 
 	abort, results := chain.engine.VerifyHeaders(chain, headers, seals)
diff --git a/core/blockchain.go b/core/blockchain.go
index ba70e1aaf6..f8fffc091b 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -70,8 +70,11 @@ var (
 	blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil)
 	blockExecutionTimer  = metrics.NewRegisteredTimer("chain/execution", nil)
 	blockWriteTimer      = metrics.NewRegisteredTimer("chain/write", nil)
-	blockReorgAddMeter   = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
-	blockReorgDropMeter  = metrics.NewRegisteredMeter("chain/reorg/add", nil)
+
+	blockReorgMeter         = metrics.NewRegisteredMeter("chain/reorg/executes", nil)
+	blockReorgAddMeter      = metrics.NewRegisteredMeter("chain/reorg/add", nil)
+	blockReorgDropMeter     = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
+	blockReorgInvalidatedTx = metrics.NewRegisteredMeter("chain/reorg/invalidTx", nil)
 
 	blockPrefetchExecuteTimer   = metrics.NewRegisteredTimer("chain/prefetch/executes", nil)
 	blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
@@ -109,22 +112,38 @@ const (
 	// - Version 7
 	//  The following incompatible database changes were added:
 	//    * Use freezer as the ancient database to maintain all ancient data
-	BlockChainVersion uint64 = 7
+	// - Version 8
+	//  The following incompatible database changes were added:
+	//    * New scheme for contract code in order to separate the codes and trie nodes
+	BlockChainVersion uint64 = 8
 )
 
 // CacheConfig contains the configuration values for the trie caching/pruning
 // that's resident in a blockchain.
 type CacheConfig struct {
 	TrieCleanLimit      int           // Memory allowance (MB) to use for caching trie nodes in memory
+	TrieCleanJournal    string        // Disk journal for saving clean cache entries.
+	TrieCleanRejournal  time.Duration // Time interval to dump clean cache to disk periodically
 	TrieCleanNoPrefetch bool          // Whether to disable heuristic state prefetching for followup blocks
 	TrieDirtyLimit      int           // Memory limit (MB) at which to start flushing dirty trie nodes to disk
 	TrieDirtyDisabled   bool          // Whether to disable trie write caching and GC altogether (archive node)
 	TrieTimeLimit       time.Duration // Time limit after which to flush the current in-memory trie to disk
 	SnapshotLimit       int           // Memory allowance (MB) to use for caching snapshot entries in memory
+	Preimages           bool          // Whether to store preimage of trie key to the disk
 
 	SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it
 }
 
+// defaultCacheConfig are the default caching values if none are specified by the
+// user (also used during testing).
+var defaultCacheConfig = &CacheConfig{
+	TrieCleanLimit: 256,
+	TrieDirtyLimit: 256,
+	TrieTimeLimit:  5 * time.Minute,
+	SnapshotLimit:  256,
+	SnapshotWait:   true,
+}
+
 // BlockChain represents the canonical chain given a database with a genesis
 // block. The Blockchain manages chain imports, reverts, chain reorganisations.
 //
@@ -148,6 +167,13 @@ type BlockChain struct {
 	triegc *prque.Prque   // Priority queue mapping block numbers to tries to gc
 	gcproc time.Duration  // Accumulates canonical block processing for trie dumping
 
+	// txLookupLimit is the maximum number of blocks from head whose tx indices
+	// are reserved:
+	//  * 0:   means no limit and regenerate any missing indexes
+	//  * N:   means N block limit [HEAD-N+1, HEAD] and delete extra indexes
+	//  * nil: disable tx reindexer/deleter, but still index new blocks
+	txLookupLimit uint64
+
 	hc            *HeaderChain
 	rmLogsFeed    event.Feed
 	chainFeed     event.Feed
@@ -171,19 +197,17 @@ type BlockChain struct {
 	txLookupCache *lru.Cache     // Cache for the most recent transaction lookup data.
 	futureBlocks  *lru.Cache     // future blocks are blocks added for later processing
 
-	quit    chan struct{} // blockchain quit channel
-	running int32         // running must be called atomically
-	// procInterrupt must be atomically called
-	procInterrupt int32          // interrupt signaler for block processing
+	quit          chan struct{}  // blockchain quit channel
 	wg            sync.WaitGroup // chain processing wait group for shutting down
+	running       int32          // 0 if chain is running, 1 when stopped
+	procInterrupt int32          // interrupt signaler for block processing
 
 	engine     consensus.Engine
-	validator  Validator  // Block and state validator interface
-	prefetcher Prefetcher // Block state prefetcher interface
-	processor  Processor  // Block transaction processor interface
+	validator  Validator // Block and state validator interface
+	prefetcher Prefetcher
+	processor  Processor // Block transaction processor interface
 	vmConfig   vm.Config
 
-	badBlocks       *lru.Cache                     // Bad block cache
 	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.
 }
@@ -191,15 +215,9 @@ type BlockChain struct {
 // NewBlockChain returns a fully initialised block chain using information
 // available in the database. It initialises the default Ethereum Validator and
 // Processor.
-func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) {
+func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool, txLookupLimit *uint64) (*BlockChain, error) {
 	if cacheConfig == nil {
-		cacheConfig = &CacheConfig{
-			TrieCleanLimit: 256,
-			TrieDirtyLimit: 256,
-			TrieTimeLimit:  5 * time.Minute,
-			SnapshotLimit:  256,
-			SnapshotWait:   true,
-		}
+		cacheConfig = defaultCacheConfig
 	}
 	bodyCache, _ := lru.New(bodyCacheLimit)
 	bodyRLPCache, _ := lru.New(bodyCacheLimit)
@@ -207,14 +225,17 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 	blockCache, _ := lru.New(blockCacheLimit)
 	txLookupCache, _ := lru.New(txLookupCacheLimit)
 	futureBlocks, _ := lru.New(maxFutureBlocks)
-	badBlocks, _ := lru.New(badBlockLimit)
 
 	bc := &BlockChain{
-		chainConfig:    chainConfig,
-		cacheConfig:    cacheConfig,
-		db:             db,
-		triegc:         prque.New(nil),
-		stateCache:     state.NewDatabaseWithCache(db, cacheConfig.TrieCleanLimit),
+		chainConfig: chainConfig,
+		cacheConfig: cacheConfig,
+		db:          db,
+		triegc:      prque.New(nil),
+		stateCache: state.NewDatabaseWithConfig(db, &trie.Config{
+			Cache:     cacheConfig.TrieCleanLimit,
+			Journal:   cacheConfig.TrieCleanJournal,
+			Preimages: cacheConfig.Preimages,
+		}),
 		quit:           make(chan struct{}),
 		shouldPreserve: shouldPreserve,
 		bodyCache:      bodyCache,
@@ -225,14 +246,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 		futureBlocks:   futureBlocks,
 		engine:         engine,
 		vmConfig:       vmConfig,
-		badBlocks:      badBlocks,
 	}
 	bc.validator = NewBlockValidator(chainConfig, bc, engine)
 	bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
 	bc.processor = NewStateProcessor(chainConfig, bc, engine)
 
 	var err error
-	bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt)
+	bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
 	if err != nil {
 		return nil, err
 	}
@@ -246,18 +266,49 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 	bc.currentFastBlock.Store(nilBlock)
 
 	// Initialize the chain with ancient data if it isn't empty.
+	var txIndexBlock uint64
+
 	if bc.empty() {
 		rawdb.InitDatabaseFromFreezer(bc.db)
+		// If ancient database is not empty, reconstruct all missing
+		// indices in the background.
+		frozen, _ := bc.db.Ancients()
+		if frozen > 0 {
+			txIndexBlock = frozen
+		}
 	}
-
 	if err := bc.loadLastState(); err != nil {
 		return nil, err
 	}
-	// The first thing the node will do is reconstruct the verification data for
-	// the head block (ethash cache or clique voting snapshot). Might as well do
-	// it in advance.
-	bc.engine.VerifyHeader(bc, bc.CurrentHeader(), true)
-
+	// Make sure the state associated with the block is available
+	head := bc.CurrentBlock()
+	if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
+		// Head state is missing, before the state recovery, find out the
+		// disk layer point of snapshot(if it's enabled). Make sure the
+		// rewound point is lower than disk layer.
+		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
 	if frozen, err := bc.db.Ancients(); err == nil && frozen > 0 {
 		var (
 			needRewind bool
@@ -267,7 +318,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 		// blockchain repair. If the head full block is even lower than the ancient
 		// chain, truncate the ancient store.
 		fullBlock := bc.CurrentBlock()
-		if fullBlock != nil && fullBlock != bc.genesisBlock && fullBlock.NumberU64() < frozen-1 {
+		if fullBlock != nil && fullBlock.Hash() != bc.genesisBlock.Hash() && fullBlock.NumberU64() < frozen-1 {
 			needRewind = true
 			low = fullBlock.NumberU64()
 		}
@@ -282,15 +333,17 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 			}
 		}
 		if needRewind {
-			var hashes []common.Hash
-			previous := bc.CurrentHeader().Number.Uint64()
-			for i := low + 1; i <= bc.CurrentHeader().Number.Uint64(); i++ {
-				hashes = append(hashes, rawdb.ReadCanonicalHash(bc.db, i))
+			log.Error("Truncating ancient chain", "from", bc.CurrentHeader().Number.Uint64(), "to", low)
+			if err := bc.SetHead(low); err != nil {
+				return nil, err
 			}
-			bc.Rollback(hashes)
-			log.Warn("Truncate ancient chain", "from", previous, "to", low)
 		}
 	}
+	// The first thing the node will do is reconstruct the verification data for
+	// the head block (ethash cache or clique voting snapshot). Might as well do
+	// it in advance.
+	bc.engine.VerifyHeader(bc, bc.CurrentHeader(), true)
+
 	// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
 	for hash := range BadHashes {
 		if header := bc.GetHeaderByHash(hash); header != nil {
@@ -299,22 +352,50 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
 			// make sure the headerByNumber (if present) is in our current canonical chain
 			if headerByNumber != nil && headerByNumber.Hash() == header.Hash() {
 				log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
-				bc.SetHead(header.Number.Uint64() - 1)
+				if err := bc.SetHead(header.Number.Uint64() - 1); err != nil {
+					return nil, err
+				}
 				log.Error("Chain rewind was successful, resuming normal operation")
 			}
 		}
 	}
 	// Load any existing snapshot, regenerating it if loading failed
 	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, true, recover)
 	}
 	// Take ownership of this particular state
 	go bc.update()
-	return bc, nil
-}
+	if txLookupLimit != nil {
+		bc.txLookupLimit = *txLookupLimit
 
-func (bc *BlockChain) getProcInterrupt() bool {
-	return atomic.LoadInt32(&bc.procInterrupt) == 1
+		bc.wg.Add(1)
+		go bc.maintainTxIndex(txIndexBlock)
+	}
+	// If periodic cache journal is required, spin it up.
+	if bc.cacheConfig.TrieCleanRejournal > 0 {
+		if bc.cacheConfig.TrieCleanRejournal < time.Minute {
+			log.Warn("Sanitizing invalid trie cache journal time", "provided", bc.cacheConfig.TrieCleanRejournal, "updated", time.Minute)
+			bc.cacheConfig.TrieCleanRejournal = time.Minute
+		}
+		triedb := bc.stateCache.TrieDB()
+		bc.wg.Add(1)
+		go func() {
+			defer bc.wg.Done()
+			triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit)
+		}()
+	}
+	return bc, nil
 }
 
 // GetVMConfig returns the block chain VM config.
@@ -357,15 +438,6 @@ func (bc *BlockChain) loadLastState() error {
 		log.Warn("Head block missing, resetting chain", "hash", head)
 		return bc.Reset()
 	}
-	// Make sure the state associated with the block is available
-	if _, err := state.New(currentBlock.Root(), bc.stateCache, bc.snaps); err != nil {
-		// Dangling block without a state associated, init from scratch
-		log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
-		if err := bc.repair(¤tBlock); err != nil {
-			return err
-		}
-		rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash())
-	}
 	// Everything seems to be fine, set as the head block
 	bc.currentBlock.Store(currentBlock)
 	headBlockGauge.Update(int64(currentBlock.NumberU64()))
@@ -399,30 +471,81 @@ func (bc *BlockChain) loadLastState() error {
 	log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(currentHeader.Time), 0)))
 	log.Info("Loaded most recent local full block", "number", currentBlock.Number(), "hash", currentBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(currentBlock.Time()), 0)))
 	log.Info("Loaded most recent local fast block", "number", currentFastBlock.Number(), "hash", currentFastBlock.Hash(), "td", fastTd, "age", common.PrettyAge(time.Unix(int64(currentFastBlock.Time()), 0)))
-
+	if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil {
+		log.Info("Loaded last fast-sync pivot marker", "number", *pivot)
+	}
 	return nil
 }
 
-// SetHead rewinds the local chain to a new head. In the case of headers, everything
-// above the new head will be deleted and the new one set. In the case of blocks
-// though, the head may be further rewound if block bodies are missing (non-archive
-// nodes after a fast sync).
+// SetHead rewinds the local chain to a new head. Depending on whether the node
+// was fast synced or full synced and in which state, the method will try to
+// delete minimal data from disk whilst retaining chain consistency.
 func (bc *BlockChain) SetHead(head uint64) error {
-	log.Warn("Rewinding blockchain", "target", head)
+	_, 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 rewinding 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()
 	defer bc.chainmu.Unlock()
 
-	updateFn := func(db ethdb.KeyValueWriter, header *types.Header) {
-		// Rewind the block chain, ensuring we don't end up with a stateless head block
-		if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() < currentBlock.NumberU64() {
+	// 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
+	// current freezer limit to start nuking id underflown
+	pivot := rawdb.ReadLastPivotNumber(bc.db)
+	frozen, _ := bc.db.Ancients()
+
+	updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (uint64, bool) {
+		// Rewind the block chain, ensuring we don't end up with a stateless head
+		// block. Note, depth equality is permitted to allow using SetHead as a
+		// chain reparation mechanism without deleting any data!
+		if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() <= currentBlock.NumberU64() {
 			newHeadBlock := bc.GetBlock(header.Hash(), header.Number.Uint64())
 			if newHeadBlock == nil {
+				log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
 				newHeadBlock = bc.genesisBlock
 			} else {
-				if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil {
-					// Rewound state missing, rolled back to before pivot, reset to genesis
-					newHeadBlock = bc.genesisBlock
+				// 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 {
+					// 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 {
+						log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
+						if pivot == nil || newHeadBlock.NumberU64() > *pivot {
+							parent := bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1)
+							if parent != nil {
+								newHeadBlock = parent
+								continue
+							}
+							log.Error("Missing block in the middle, aiming genesis", "number", newHeadBlock.NumberU64()-1, "hash", newHeadBlock.ParentHash())
+							newHeadBlock = bc.genesisBlock
+						} else {
+							log.Trace("Rewind passed pivot, aiming genesis", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "pivot", *pivot)
+							newHeadBlock = bc.genesisBlock
+						}
+					}
+					if beyondRoot || newHeadBlock.NumberU64() == 0 {
+						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())
@@ -434,7 +557,6 @@ func (bc *BlockChain) SetHead(head uint64) error {
 			bc.currentBlock.Store(newHeadBlock)
 			headBlockGauge.Update(int64(newHeadBlock.NumberU64()))
 		}
-
 		// Rewind the fast block in a simpleton way to the target head
 		if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock != nil && header.Number.Uint64() < currentFastBlock.NumberU64() {
 			newHeadFastBlock := bc.GetBlock(header.Hash(), header.Number.Uint64())
@@ -451,8 +573,17 @@ func (bc *BlockChain) SetHead(head uint64) error {
 			bc.currentFastBlock.Store(newHeadFastBlock)
 			headFastBlockGauge.Update(int64(newHeadFastBlock.NumberU64()))
 		}
-	}
+		head := bc.CurrentBlock().NumberU64()
 
+		// If setHead underflown the freezer threshold and the block processing
+		// intent afterwards is full block importing, delete the chain segment
+		// between the stateful-block and the sethead target.
+		var wipe bool
+		if head+1 < frozen {
+			wipe = pivot == nil || head >= *pivot
+		}
+		return head, wipe // Only force wipe if full synced
+	}
 	// Rewind the header chain, deleting all block bodies until then
 	delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) {
 		// Ignore the error here since light client won't hit this path
@@ -460,10 +591,9 @@ func (bc *BlockChain) SetHead(head uint64) error {
 		if num+1 <= frozen {
 			// Truncate all relative data(header, total difficulty, body, receipt
 			// and canonical hash) from ancient store.
-			if err := bc.db.TruncateAncients(num + 1); err != nil {
+			if err := bc.db.TruncateAncients(num); err != nil {
 				log.Crit("Failed to truncate ancient data", "number", num, "err", err)
 			}
-
 			// Remove the hash <-> number mapping from the active store.
 			rawdb.DeleteHeaderNumber(db, hash)
 		} else {
@@ -475,8 +605,18 @@ func (bc *BlockChain) SetHead(head uint64) error {
 		}
 		// Todo(rjl493456442) txlookup, bloombits, etc
 	}
-	bc.hc.SetHead(head, updateFn, delFn)
-
+	// If SetHead was only called as a chain reparation method, try to skip
+	// touching the header chain altogether, unless the freezer is broken
+	if block := bc.CurrentBlock(); block.NumberU64() == head {
+		if target, force := updateFn(bc.db, block.Header()); force {
+			bc.hc.SetHead(target, updateFn, delFn)
+		}
+	} else {
+		// Rewind the chain to the requested head and keep going backwards until a
+		// block with a state is found or fast sync pivot is passed
+		log.Warn("Rewinding blockchain", "target", head)
+		bc.hc.SetHead(head, updateFn, delFn)
+	}
 	// Clear out any stale content from the caches
 	bc.bodyCache.Purge()
 	bc.bodyRLPCache.Purge()
@@ -485,7 +625,7 @@ func (bc *BlockChain) SetHead(head uint64) error {
 	bc.txLookupCache.Purge()
 	bc.futureBlocks.Purge()
 
-	return bc.loadLastState()
+	return rootNumber, bc.loadLastState()
 }
 
 // FastSyncCommitHead sets the current head block to the one defined by the hash
@@ -494,7 +634,7 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
 	// Make sure that both the block as well at its state trie exists
 	block := bc.GetBlockByHash(hash)
 	if block == nil {
-		return fmt.Errorf("non existent block [%x…]", hash[:4])
+		return fmt.Errorf("non existent block [%x..]", hash[:4])
 	}
 	if _, err := trie.NewSecure(block.Root(), bc.stateCache.TrieDB()); err != nil {
 		return err
@@ -505,7 +645,8 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
 	headBlockGauge.Update(int64(block.NumberU64()))
 	bc.chainmu.Unlock()
 
-	// Destroy any existing state snapshot and regenerate it in the background
+	// Destroy any existing state snapshot and regenerate it in the background,
+	// also resuming the normal maintenance of any previously paused snapshot.
 	if bc.snaps != nil {
 		bc.snaps.Rebuild(block.Root())
 	}
@@ -524,12 +665,8 @@ func (bc *BlockChain) CurrentBlock() *types.Block {
 	return bc.currentBlock.Load().(*types.Block)
 }
 
-// Snapshot returns the blockchain snapshot tree. This method is mainly used for
-// testing, to make it possible to verify the snapshot after execution.
-//
-// Warning: There are no guarantees about the safety of using the returned 'snap' if the
-// blockchain is simultaneously importing blocks, so take care.
-func (bc *BlockChain) Snapshot() *snapshot.Tree {
+// Snapshots returns the blockchain snapshot tree.
+func (bc *BlockChain) Snapshots() *snapshot.Tree {
 	return bc.snaps
 }
 
@@ -599,28 +736,6 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
 	return nil
 }
 
-// repair tries to repair the current blockchain by rolling back the current block
-// until one with associated state is found. This is needed to fix incomplete db
-// writes caused either by crashes/power outages, or simply non-committed tries.
-//
-// This method only rolls back the current block. The current header and current
-// fast block are left intact.
-func (bc *BlockChain) repair(head **types.Block) error {
-	for {
-		// Abort if we've rewound to a head block that does have associated state
-		if _, err := state.New((*head).Root(), bc.stateCache, bc.snaps); err == nil {
-			log.Info("Rewound blockchain to past state", "number", (*head).Number(), "hash", (*head).Hash())
-			return nil
-		}
-		// Otherwise rewind one block and recheck state availability there
-		block := bc.GetBlock((*head).ParentHash(), (*head).NumberU64()-1)
-		if block == nil {
-			return fmt.Errorf("missing block %d [%x]", (*head).NumberU64()-1, (*head).ParentHash())
-		}
-		*head = block
-	}
-}
-
 // Export writes the active chain to the given writer.
 func (bc *BlockChain) Export(w io.Writer) error {
 	return bc.ExportN(w, uint64(0), bc.CurrentBlock().NumberU64())
@@ -666,7 +781,7 @@ func (bc *BlockChain) writeHeadBlock(block *types.Block) {
 	// Add the block to the canonical chain number scheme and mark as the head
 	batch := bc.db.NewBatch()
 	rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
-	rawdb.WriteTxLookupEntries(batch, block)
+	rawdb.WriteTxLookupEntriesByBlock(batch, block)
 	rawdb.WriteHeadBlockHash(batch, block.Hash())
 
 	// If the block is better than our head or is on a different chain, force update heads
@@ -852,12 +967,30 @@ func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.
 	return uncles
 }
 
-// TrieNode retrieves a blob of data associated with a trie node (or code hash)
+// TrieNode retrieves a blob of data associated with a trie node
 // either from ephemeral in-memory cache, or from persistent storage.
 func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte, error) {
 	return bc.stateCache.TrieDB().Node(hash)
 }
 
+// ContractCode retrieves a blob of data associated with a contract hash
+// either from ephemeral in-memory cache, or from persistent storage.
+func (bc *BlockChain) ContractCode(hash common.Hash) ([]byte, error) {
+	return bc.stateCache.ContractCode(common.Hash{}, hash)
+}
+
+// ContractCodeWithPrefix retrieves a blob of data associated with a contract
+// hash either from ephemeral in-memory cache, or from persistent storage.
+//
+// If the code doesn't exist in the in-memory cache, check the storage with
+// new code scheme.
+func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) {
+	type codeReader interface {
+		ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error)
+	}
+	return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash)
+}
+
 // Stop stops the blockchain service. If any imports are currently in progress
 // it will abort them using the procInterrupt.
 func (bc *BlockChain) Stop() {
@@ -867,8 +1000,7 @@ func (bc *BlockChain) Stop() {
 	// Unsubscribe all subscriptions registered from blockchain
 	bc.scope.Close()
 	close(bc.quit)
-	atomic.StoreInt32(&bc.procInterrupt, 1)
-
+	bc.StopInsert()
 	bc.wg.Wait()
 
 	// Ensure that the entirety of the state snapshot is journalled to disk.
@@ -892,14 +1024,14 @@ func (bc *BlockChain) Stop() {
 				recent := bc.GetBlockByNumber(number - offset)
 
 				log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root())
-				if err := triedb.Commit(recent.Root(), true); err != nil {
+				if err := triedb.Commit(recent.Root(), true, nil); err != nil {
 					log.Error("Failed to commit recent state trie", "err", err)
 				}
 			}
 		}
 		if snapBase != (common.Hash{}) {
 			log.Info("Writing snapshot state to disk", "root", snapBase)
-			if err := triedb.Commit(snapBase, true); err != nil {
+			if err := triedb.Commit(snapBase, true, nil); err != nil {
 				log.Error("Failed to commit recent state trie", "err", err)
 			}
 		}
@@ -910,9 +1042,27 @@ func (bc *BlockChain) Stop() {
 			log.Error("Dangling trie nodes after full cleanup")
 		}
 	}
+	// Ensure all live cached entries be saved into disk, so that we can skip
+	// cache warmup when node restarts.
+	if bc.cacheConfig.TrieCleanJournal != "" {
+		triedb := bc.stateCache.TrieDB()
+		triedb.SaveCache(bc.cacheConfig.TrieCleanJournal)
+	}
 	log.Info("Blockchain stopped")
 }
 
+// StopInsert interrupts all insertion methods, causing them to return
+// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after
+// calling this method.
+func (bc *BlockChain) StopInsert() {
+	atomic.StoreInt32(&bc.procInterrupt, 1)
+}
+
+// insertStopped returns true after StopInsert has been called.
+func (bc *BlockChain) insertStopped() bool {
+	return atomic.LoadInt32(&bc.procInterrupt) == 1
+}
+
 func (bc *BlockChain) procFutureBlocks() {
 	blocks := make([]*types.Block, 0, bc.futureBlocks.Len())
 	for _, hash := range bc.futureBlocks.Keys() {
@@ -940,52 +1090,6 @@ const (
 	SideStatTy
 )
 
-// Rollback is designed to remove a chain of links from the database that aren't
-// certain enough to be valid.
-func (bc *BlockChain) Rollback(chain []common.Hash) {
-	bc.chainmu.Lock()
-	defer bc.chainmu.Unlock()
-
-	batch := bc.db.NewBatch()
-	for i := len(chain) - 1; i >= 0; i-- {
-		hash := chain[i]
-
-		// Degrade the chain markers if they are explicitly reverted.
-		// In theory we should update all in-memory markers in the
-		// last step, however the direction of rollback is from high
-		// to low, so it's safe the update in-memory markers directly.
-		currentHeader := bc.hc.CurrentHeader()
-		if currentHeader.Hash() == hash {
-			newHeadHeader := bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1)
-			rawdb.WriteHeadHeaderHash(batch, currentHeader.ParentHash)
-			bc.hc.SetCurrentHeader(newHeadHeader)
-		}
-		if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock.Hash() == hash {
-			newFastBlock := bc.GetBlock(currentFastBlock.ParentHash(), currentFastBlock.NumberU64()-1)
-			rawdb.WriteHeadFastBlockHash(batch, currentFastBlock.ParentHash())
-			bc.currentFastBlock.Store(newFastBlock)
-			headFastBlockGauge.Update(int64(newFastBlock.NumberU64()))
-		}
-		if currentBlock := bc.CurrentBlock(); currentBlock.Hash() == hash {
-			newBlock := bc.GetBlock(currentBlock.ParentHash(), currentBlock.NumberU64()-1)
-			rawdb.WriteHeadBlockHash(batch, currentBlock.ParentHash())
-			bc.currentBlock.Store(newBlock)
-			headBlockGauge.Update(int64(newBlock.NumberU64()))
-		}
-	}
-	if err := batch.Write(); err != nil {
-		log.Crit("Failed to rollback chain markers", "err", err)
-	}
-	// Truncate ancient data which exceeds the current header.
-	//
-	// Notably, it can happen that system crashes without truncating the ancient data
-	// but the head indicator has been updated in the active store. Regarding this issue,
-	// system will self recovery by truncating the extra data during the setup phase.
-	if err := bc.truncateAncient(bc.hc.CurrentHeader().Number.Uint64()); err != nil {
-		log.Crit("Truncate ancient store failed", "err", err)
-	}
-}
-
 // truncateAncient rewinds the blockchain to the specified header and deletes all
 // data in the ancient store that exceeds the specified header.
 func (bc *BlockChain) truncateAncient(head uint64) error {
@@ -1042,7 +1146,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 			if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() {
 				log.Error("Non contiguous receipt insert", "number", blockChain[i].Number(), "hash", blockChain[i].Hash(), "parent", blockChain[i].ParentHash(),
 					"prevnumber", blockChain[i-1].Number(), "prevhash", blockChain[i-1].Hash())
-				return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, blockChain[i-1].NumberU64(),
+				return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, blockChain[i-1].NumberU64(),
 					blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4])
 			}
 		}
@@ -1059,7 +1163,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 		size  = 0
 	)
 	// updateHead updates the head fast sync block if the inserted blocks are better
-	// and returns a indicator whether the inserted blocks are canonical.
+	// and returns an indicator whether the inserted blocks are canonical.
 	updateHead := func(head *types.Block) bool {
 		bc.chainmu.Lock()
 
@@ -1098,7 +1202,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 		var deleted []*numberHash
 		for i, block := range blockChain {
 			// Short circuit insertion if shutting down or processing failed
-			if atomic.LoadInt32(&bc.procInterrupt) == 1 {
+			if bc.insertStopped() {
 				return 0, errInsertionInterrupted
 			}
 			// Short circuit insertion if it is required(used in testing only)
@@ -1107,70 +1211,36 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 			}
 			// Short circuit if the owner header is unknown
 			if !bc.HasHeader(block.Hash(), block.NumberU64()) {
-				return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
+				return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4])
 			}
-			var (
-				start  = time.Now()
-				logged = time.Now()
-				count  int
-			)
-			// Migrate all ancient blocks. This can happen if someone upgrades from Geth
-			// 1.8.x to 1.9.x mid-fast-sync. Perhaps we can get rid of this path in the
-			// long term.
-			for {
-				// We can ignore the error here since light client won't hit this code path.
-				frozen, _ := bc.db.Ancients()
-				if frozen >= block.NumberU64() {
-					break
-				}
-				h := rawdb.ReadCanonicalHash(bc.db, frozen)
-				b := rawdb.ReadBlock(bc.db, h, frozen)
-				size += rawdb.WriteAncientBlock(bc.db, b, rawdb.ReadReceipts(bc.db, h, frozen, bc.chainConfig), rawdb.ReadTd(bc.db, h, frozen))
-				count += 1
-
-				// Always keep genesis block in active database.
-				if b.NumberU64() != 0 {
-					deleted = append(deleted, &numberHash{b.NumberU64(), b.Hash()})
-				}
-				if time.Since(logged) > 8*time.Second {
-					log.Info("Migrating ancient blocks", "count", count, "elapsed", common.PrettyDuration(time.Since(start)))
-					logged = time.Now()
+			if block.NumberU64() == 1 {
+				// Make sure to write the genesis into the freezer
+				if frozen, _ := bc.db.Ancients(); frozen == 0 {
+					h := rawdb.ReadCanonicalHash(bc.db, 0)
+					b := rawdb.ReadBlock(bc.db, h, 0)
+					size += rawdb.WriteAncientBlock(bc.db, b, rawdb.ReadReceipts(bc.db, h, 0, bc.chainConfig), rawdb.ReadTd(bc.db, h, 0))
+					log.Info("Wrote genesis to ancients")
 				}
-				// Don't collect too much in-memory, write it out every 100K blocks
-				if len(deleted) > 100000 {
-					// Sync the ancient store explicitly to ensure all data has been flushed to disk.
-					if err := bc.db.Sync(); err != nil {
-						return 0, err
-					}
-					// Wipe out canonical block data.
-					for _, nh := range deleted {
-						rawdb.DeleteBlockWithoutNumber(batch, nh.hash, nh.number)
-						rawdb.DeleteCanonicalHash(batch, nh.number)
-					}
-					if err := batch.Write(); err != nil {
-						return 0, err
-					}
-					batch.Reset()
-					// Wipe out side chain too.
-					for _, nh := range deleted {
-						for _, hash := range rawdb.ReadAllHashes(bc.db, nh.number) {
-							rawdb.DeleteBlock(batch, hash, nh.number)
-						}
-					}
-					if err := batch.Write(); err != nil {
-						return 0, err
-					}
-					batch.Reset()
-					deleted = deleted[0:]
-				}
-			}
-			if count > 0 {
-				log.Info("Migrated ancient blocks", "count", count, "elapsed", common.PrettyDuration(time.Since(start)))
 			}
 			// Flush data into ancient database.
 			size += rawdb.WriteAncientBlock(bc.db, block, receiptChain[i], bc.GetTd(block.Hash(), block.NumberU64()))
-			rawdb.WriteTxLookupEntries(batch, block)
 
+			// Write tx indices if any condition is satisfied:
+			// * If user requires to reserve all tx indices(txlookuplimit=0)
+			// * If all ancient tx indices are required to be reserved(txlookuplimit is even higher than ancientlimit)
+			// * If block number is large enough to be regarded as a recent block
+			// It means blocks below the ancientLimit-txlookupLimit won't be indexed.
+			//
+			// But if the `TxIndexTail` is not nil, e.g. Geth is initialized with
+			// an external ancient database, during the setup, blockchain will start
+			// a background routine to re-indexed all indices in [ancients - txlookupLimit, ancients)
+			// range. In this case, all tx indices of newly imported blocks should be
+			// generated.
+			if bc.txLookupLimit == 0 || ancientLimit <= bc.txLookupLimit || block.NumberU64() >= ancientLimit-bc.txLookupLimit {
+				rawdb.WriteTxLookupEntriesByBlock(batch, block)
+			} else if rawdb.ReadTxIndexTail(bc.db) != nil {
+				rawdb.WriteTxLookupEntriesByBlock(batch, block)
+			}
 			stats.processed++
 		}
 		// Flush all tx-lookup index data.
@@ -1227,24 +1297,33 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 	}
 	// writeLive writes blockchain and corresponding receipt chain into active store.
 	writeLive := func(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
+		skipPresenceCheck := false
 		batch := bc.db.NewBatch()
 		for i, block := range blockChain {
 			// Short circuit insertion if shutting down or processing failed
-			if atomic.LoadInt32(&bc.procInterrupt) == 1 {
+			if bc.insertStopped() {
 				return 0, errInsertionInterrupted
 			}
 			// Short circuit if the owner header is unknown
 			if !bc.HasHeader(block.Hash(), block.NumberU64()) {
-				return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
+				return i, fmt.Errorf("containing header #%d [%x..] unknown", block.Number(), block.Hash().Bytes()[:4])
 			}
-			if bc.HasBlock(block.Hash(), block.NumberU64()) {
-				stats.ignored++
-				continue
+			if !skipPresenceCheck {
+				// Ignore if the entire data is already known
+				if bc.HasBlock(block.Hash(), block.NumberU64()) {
+					stats.ignored++
+					continue
+				} else {
+					// If block N is not present, neither are the later blocks.
+					// This should be true, but if we are mistaken, the shortcut
+					// here will only cause overwriting of some existing data
+					skipPresenceCheck = true
+				}
 			}
 			// Write all the data out into the database
 			rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body())
 			rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receiptChain[i])
-			rawdb.WriteTxLookupEntries(batch, block)
+			rawdb.WriteTxLookupEntriesByBlock(batch, block) // Always write tx indices for live blocks, we assume they are needed
 
 			// Write everything belongs to the blocks into the database. So that
 			// we can ensure all components of body is completed(body, receipts,
@@ -1270,7 +1349,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 		updateHead(blockChain[len(blockChain)-1])
 		return 0, nil
 	}
-	// Write downloaded chain data and corresponding receipt chain data.
+	// Write downloaded chain data and corresponding receipt chain data
 	if len(ancientBlocks) > 0 {
 		if n, err := writeAncient(ancientBlocks, ancientReceipts); err != nil {
 			if err == errInsertionInterrupted {
@@ -1279,6 +1358,19 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 			return n, err
 		}
 	}
+	// Write the tx index tail (block number from where we index) before write any live blocks
+	if len(liveBlocks) > 0 && liveBlocks[0].NumberU64() == ancientLimit+1 {
+		// The tx index tail can only be one of the following two options:
+		// * 0: all ancient blocks have been indexed
+		// * ancient-limit: the indices of blocks before ancient-limit are ignored
+		if tail := rawdb.ReadTxIndexTail(bc.db); tail == nil {
+			if bc.txLookupLimit == 0 || ancientLimit <= bc.txLookupLimit {
+				rawdb.WriteTxIndexTail(bc.db, 0)
+			} else {
+				rawdb.WriteTxIndexTail(bc.db, ancientLimit-bc.txLookupLimit)
+			}
+		}
+	}
 	if len(liveBlocks) > 0 {
 		if n, err := writeLive(liveBlocks, liveReceipts); err != nil {
 			if err == errInsertionInterrupted {
@@ -1302,6 +1394,18 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
 	return 0, nil
 }
 
+// SetTxLookupLimit is responsible for updating the txlookup limit to the
+// original one stored in db if the new mismatches with the old one.
+func (bc *BlockChain) SetTxLookupLimit(limit uint64) {
+	bc.txLookupLimit = limit
+}
+
+// TxLookupLimit retrieves the txlookup limit used by blockchain to prune
+// stale transaction indices.
+func (bc *BlockChain) TxLookupLimit() uint64 {
+	return bc.txLookupLimit
+}
+
 var lastWrite uint64
 
 // writeBlockWithoutState writes only the block and its metadata to the database,
@@ -1381,7 +1485,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
 
 	// If we're running an archive node, always flush
 	if bc.cacheConfig.TrieDirtyDisabled {
-		if err := triedb.Commit(root, false); err != nil {
+		if err := triedb.Commit(root, false, nil); err != nil {
 			return NonStatTy, err
 		}
 	} else {
@@ -1422,7 +1526,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
 							log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/TriesInMemory)
 						}
 						// Flush an entire trie and restart the counters
-						triedb.Commit(header.Root, true)
+						triedb.Commit(header.Root, true, nil)
 						lastWrite = chosen
 						bc.gcproc = 0
 					}
@@ -1533,7 +1637,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
 			log.Error("Non contiguous block insert", "number", block.Number(), "hash", block.Hash(),
 				"parent", block.ParentHash(), "prevnumber", prev.Number(), "prevhash", prev.Hash())
 
-			return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, prev.NumberU64(),
+			return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, prev.NumberU64(),
 				prev.Hash().Bytes()[:4], i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4])
 		}
 	}
@@ -1547,6 +1651,22 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
 	return n, err
 }
 
+// InsertChainWithoutSealVerification works exactly the same
+// except for seal verification, seal verification is omitted
+func (bc *BlockChain) InsertChainWithoutSealVerification(block *types.Block) (int, error) {
+	bc.blockProcFeed.Send(true)
+	defer bc.blockProcFeed.Send(false)
+
+	// Pre-checks passed, start the full block imports
+	bc.wg.Add(1)
+	bc.chainmu.Lock()
+	n, err := bc.insertChain(types.Blocks([]*types.Block{block}), false)
+	bc.chainmu.Unlock()
+	bc.wg.Done()
+
+	return n, err
+}
+
 // insertChain is the internal implementation of InsertChain, which assumes that
 // 1) chains are contiguous, and 2) The chain mutex is held.
 //
@@ -1632,13 +1752,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 	}
 	switch {
 	// First block is pruned, insert as sidechain and reorg only if TD grows enough
-	case err == consensus.ErrPrunedAncestor:
+	case errors.Is(err, consensus.ErrPrunedAncestor):
 		log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
 		return bc.insertSideChain(block, it)
 
 	// First block is future, shove it (and all children) to the future queue (unknown ancestor)
-	case err == consensus.ErrFutureBlock || (err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(it.first().ParentHash())):
-		for block != nil && (it.index == 0 || err == consensus.ErrUnknownAncestor) {
+	case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
+		for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) {
 			log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash())
 			if err := bc.addFutureBlock(block); err != nil {
 				return it.index, err
@@ -1659,10 +1779,21 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 		return it.index, err
 	}
 	// No validation errors for the first block (or chain prefix skipped)
+	var activeState *state.StateDB
+	defer func() {
+		// The chain importer is starting and stopping trie prefetchers. If a bad
+		// block or other error is hit however, an early return may not properly
+		// terminate the background threads. This defer ensures that we clean up
+		// and dangling prefetcher, without defering each and holding on live refs.
+		if activeState != nil {
+			activeState.StopPrefetcher()
+		}
+	}()
+
 	for ; block != nil && err == nil || err == ErrKnownBlock; block, err = it.next() {
 		// If the chain is terminating, stop processing blocks
-		if atomic.LoadInt32(&bc.procInterrupt) == 1 {
-			log.Debug("Premature abort during blocks processing")
+		if bc.insertStopped() {
+			log.Debug("Abort during block processing")
 			break
 		}
 		// If the header is a banned one, straight out abort
@@ -1684,6 +1815,20 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 				"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
 				"root", block.Root())
 
+			// Special case. Commit the empty receipt slice if we meet the known
+			// block in the middle. It can only happen in the clique chain. Whenever
+			// we insert blocks via `insertSideChain`, we only commit `td`, `header`
+			// and `body` if it's non-existent. Since we don't have receipts without
+			// reexecution, so nothing to commit. But if the sidechain will be adpoted
+			// as the canonical chain eventually, it needs to be reexecuted for missing
+			// state, but if it's this special case here(skip reexecution) we will lose
+			// the empty receipt entry.
+			if len(block.Transactions()) == 0 {
+				rawdb.WriteReceipts(bc.db, block.Hash(), block.NumberU64(), nil)
+			} else {
+				log.Error("Please file an issue, skip known block execution without receipt",
+					"hash", block.Hash(), "number", block.NumberU64())
+			}
 			if err := bc.writeKnownBlock(block); err != nil {
 				return it.index, err
 			}
@@ -1705,12 +1850,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 		if err != nil {
 			return it.index, err
 		}
+		// Enable prefetching to pull in trie node paths while processing transactions
+		statedb.StartPrefetcher("chain")
+		activeState = statedb
+
 		// If we have a followup block, run that against the current state to pre-cache
 		// transactions and probabilistically some of the account/storage trie nodes.
 		var followupInterrupt uint32
 		if !bc.cacheConfig.TrieCleanNoPrefetch {
 			if followup, err := it.peek(); followup != nil && err == nil {
 				throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps)
+
 				go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) {
 					bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt)
 
@@ -1737,8 +1887,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 		storageUpdateTimer.Update(statedb.StorageUpdates)             // Storage updates are complete, we can mark them
 		snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them
 		snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them
-
-		triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation
+		triehash := statedb.AccountHashes + statedb.StorageHashes     // Save to not double count in validation
 		trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates
 		trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates
 
@@ -1766,7 +1915,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 		if err != nil {
 			return it.index, err
 		}
-
 		// Update the metrics touched during block commit
 		accountCommitTimer.Update(statedb.AccountCommits)   // Account commits are complete, we can mark them
 		storageCommitTimer.Update(statedb.StorageCommits)   // Storage commits are complete, we can mark them
@@ -1808,13 +1956,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
 		stats.report(chain, it.index, dirty)
 	}
 	// Any blocks remaining here? The only ones we care about are the future ones
-	if block != nil && err == consensus.ErrFutureBlock {
+	if block != nil && errors.Is(err, consensus.ErrFutureBlock) {
 		if err := bc.addFutureBlock(block); err != nil {
 			return it.index, err
 		}
 		block, err = it.next()
 
-		for ; block != nil && err == consensus.ErrUnknownAncestor; block, err = it.next() {
+		for ; block != nil && errors.Is(err, consensus.ErrUnknownAncestor); block, err = it.next() {
 			if err := bc.addFutureBlock(block); err != nil {
 				return it.index, err
 			}
@@ -1842,7 +1990,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
 	// ones. Any other errors means that the block is invalid, and should not be written
 	// to disk.
 	err := consensus.ErrPrunedAncestor
-	for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() {
+	for ; block != nil && errors.Is(err, consensus.ErrPrunedAncestor); block, err = it.next() {
 		// Check the canonical state root for that number
 		if number := block.NumberU64(); current.NumberU64() >= number {
 			canonical := bc.GetBlockByNumber(number)
@@ -1936,8 +2084,8 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
 			blocks, memory = blocks[:0], 0
 
 			// If the chain is terminating, stop processing blocks
-			if atomic.LoadInt32(&bc.procInterrupt) == 1 {
-				log.Debug("Premature abort during blocks processing")
+			if bc.insertStopped() {
+				log.Debug("Abort during blocks processing")
 				return 0, nil
 			}
 		}
@@ -1980,7 +2128,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
 					l := *log
 					if removed {
 						l.Removed = true
-					} else {
 					}
 					logs = append(logs, &l)
 				}
@@ -2065,6 +2212,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
 			"drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash())
 		blockReorgAddMeter.Mark(int64(len(newChain)))
 		blockReorgDropMeter.Mark(int64(len(oldChain)))
+		blockReorgMeter.Mark(1)
 	} else {
 		log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "newnum", newBlock.Number(), "newhash", newBlock.Hash())
 	}
@@ -2129,26 +2277,95 @@ func (bc *BlockChain) update() {
 	}
 }
 
-// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network
-func (bc *BlockChain) BadBlocks() []*types.Block {
-	blocks := make([]*types.Block, 0, bc.badBlocks.Len())
-	for _, hash := range bc.badBlocks.Keys() {
-		if blk, exist := bc.badBlocks.Peek(hash); exist {
-			block := blk.(*types.Block)
-			blocks = append(blocks, block)
+// maintainTxIndex is responsible for the construction and deletion of the
+// transaction index.
+//
+// User can use flag `txlookuplimit` to specify a "recentness" block, below
+// which ancient tx indices get deleted. If `txlookuplimit` is 0, it means
+// all tx indices will be reserved.
+//
+// The user can adjust the txlookuplimit value for each launch after fast
+// sync, Geth will automatically construct the missing indices and delete
+// the extra indices.
+func (bc *BlockChain) maintainTxIndex(ancients uint64) {
+	defer bc.wg.Done()
+
+	// 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
+	// need to reindex all necessary transactions before starting to process any
+	// pruning requests.
+	if ancients > 0 {
+		var from = uint64(0)
+		if bc.txLookupLimit != 0 && ancients > bc.txLookupLimit {
+			from = ancients - bc.txLookupLimit
+		}
+		rawdb.IndexTransactions(bc.db, from, ancients, bc.quit)
+	}
+	// indexBlocks reindexes or unindexes transactions depending on user configuration
+	indexBlocks := func(tail *uint64, head uint64, done chan struct{}) {
+		defer func() { done <- struct{}{} }()
+
+		// If the user just upgraded Geth to a new version which supports transaction
+		// index pruning, write the new tail and remove anything older.
+		if tail == nil {
+			if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
+				// Nothing to delete, write the tail and return
+				rawdb.WriteTxIndexTail(bc.db, 0)
+			} else {
+				// Prune all stale tx indices and record the tx index tail
+				rawdb.UnindexTransactions(bc.db, 0, head-bc.txLookupLimit+1, bc.quit)
+			}
+			return
+		}
+		// If a previous indexing existed, make sure that we fill in any missing entries
+		if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
+			if *tail > 0 {
+				rawdb.IndexTransactions(bc.db, 0, *tail, bc.quit)
+			}
+			return
+		}
+		// Update the transaction index to the new chain state
+		if head-bc.txLookupLimit+1 < *tail {
+			// Reindex a part of missing indices and rewind index tail to HEAD-limit
+			rawdb.IndexTransactions(bc.db, head-bc.txLookupLimit+1, *tail, bc.quit)
+		} else {
+			// Unindex a part of stale indices and forward index tail to HEAD-limit
+			rawdb.UnindexTransactions(bc.db, *tail, head-bc.txLookupLimit+1, bc.quit)
 		}
 	}
-	return blocks
-}
+	// Any reindexing done, start listening to chain events and moving the index window
+	var (
+		done   chan struct{}                  // Non-nil if background unindexing or reindexing routine is active.
+		headCh = make(chan ChainHeadEvent, 1) // Buffered to avoid locking up the event feed
+	)
+	sub := bc.SubscribeChainHeadEvent(headCh)
+	if sub == nil {
+		return
+	}
+	defer sub.Unsubscribe()
 
-// addBadBlock adds a bad block to the bad-block LRU cache
-func (bc *BlockChain) addBadBlock(block *types.Block) {
-	bc.badBlocks.Add(block.Hash(), block)
+	for {
+		select {
+		case head := <-headCh:
+			if done == nil {
+				done = make(chan struct{})
+				go indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.Block.NumberU64(), done)
+			}
+		case <-done:
+			done = nil
+		case <-bc.quit:
+			if done != nil {
+				log.Info("Waiting background transaction indexer to exit")
+				<-done
+			}
+			return
+		}
+	}
 }
 
 // reportBlock logs a bad block error.
 func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) {
-	bc.addBadBlock(block)
+	rawdb.WriteBadBlock(bc.db, block)
 
 	var receiptString string
 	for i, receipt := range receipts {
@@ -2189,12 +2406,8 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
 
 	bc.wg.Add(1)
 	defer bc.wg.Done()
-
-	whFunc := func(header *types.Header) error {
-		_, err := bc.hc.WriteHeader(header)
-		return err
-	}
-	return bc.hc.InsertHeaderChain(chain, whFunc, start)
+	_, err := bc.hc.InsertHeaderChain(chain, start)
+	return 0, err
 }
 
 // CurrentHeader retrieves the current head header of the canonical chain. The
diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go
index 5685b0a4bd..cb8473c084 100644
--- a/core/blockchain_insert.go
+++ b/core/blockchain_insert.go
@@ -43,7 +43,7 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor
 	// Fetch the timings for the batch
 	var (
 		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 index == len(chain)-1 || elapsed >= statsReportLimit {
diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go
new file mode 100644
index 0000000000..8bb39d2607
--- /dev/null
+++ b/core/blockchain_repair_test.go
@@ -0,0 +1,1865 @@
+// 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 .
+
+// Tests that abnormal program termination (i.e.crash) and restart doesn't leave
+// the database in some strange state with gaps in the chain, nor with block data
+// dangling in the future.
+
+package core
+
+import (
+	"io/ioutil"
+	"math/big"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+// Tests a recovery for a short canonical chain where a recent block was already
+// 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
+// in the database for replaying.
+func TestShortRepair(t *testing.T)              { testShortRepair(t, false) }
+func TestShortRepairWithSnapshots(t *testing.T) { testShortRepair(t, true) }
+
+func testShortRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// 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     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 0,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// 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
+// chain to be rolled back to the committed block, but the chain data itself left in
+// the database for replaying.
+func TestShortFastSyncedRepair(t *testing.T)              { testShortFastSyncedRepair(t, false) }
+func TestShortFastSyncedRepairWithSnapshots(t *testing.T) { testShortFastSyncedRepair(t, true) }
+
+func testShortFastSyncedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : 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     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 0,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// 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
+// detect that it was fast syncing and not delete anything, since we can just pick
+// up directly where we left off.
+func TestShortFastSyncingRepair(t *testing.T)              { testShortFastSyncingRepair(t, false) }
+func TestShortFastSyncingRepairWithSnapshots(t *testing.T) { testShortFastSyncingRepair(t, true) }
+
+func testShortFastSyncingRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : 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
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 0,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a shorter side chain, where a
+// recent block was already committed to disk and then the process crashed. In this
+// 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
+// itself left in the database for replaying.
+func TestShortOldForkedRepair(t *testing.T)              { testShortOldForkedRepair(t, false) }
+func TestShortOldForkedRepairWithSnapshots(t *testing.T) { testShortOldForkedRepair(t, true) }
+
+func testShortOldForkedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 3,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was already committed to disk and then the process
+// crashed. In this 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 itself left in the database for replaying.
+func TestShortOldForkedFastSyncedRepair(t *testing.T) {
+	testShortOldForkedFastSyncedRepair(t, false)
+}
+func TestShortOldForkedFastSyncedRepairWithSnapshots(t *testing.T) {
+	testShortOldForkedFastSyncedRepair(t, true)
+}
+
+func testShortOldForkedFastSyncedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 3,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was not yet committed, but the process crashed. In this
+// test scenario the side chain is below the committed block. 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 left off.
+func TestShortOldForkedFastSyncingRepair(t *testing.T) {
+	testShortOldForkedFastSyncingRepair(t, false)
+}
+func TestShortOldForkedFastSyncingRepairWithSnapshots(t *testing.T) {
+	testShortOldForkedFastSyncingRepair(t, true)
+}
+
+func testShortOldForkedFastSyncingRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 3,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a shorter side chain, where a
+// recent block was already committed to disk and then the process crashed. In this
+// 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
+// chain data itself left in the database for replaying.
+func TestShortNewlyForkedRepair(t *testing.T)              { testShortNewlyForkedRepair(t, false) }
+func TestShortNewlyForkedRepairWithSnapshots(t *testing.T) { testShortNewlyForkedRepair(t, true) }
+
+func testShortNewlyForkedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3->S4->S5->S6
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    6,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 6,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was already committed to disk and then the process
+// crashed. In this 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 chain data itself left in the database for replaying.
+func TestShortNewlyForkedFastSyncedRepair(t *testing.T) {
+	testShortNewlyForkedFastSyncedRepair(t, false)
+}
+func TestShortNewlyForkedFastSyncedRepairWithSnapshots(t *testing.T) {
+	testShortNewlyForkedFastSyncedRepair(t, true)
+}
+
+func testShortNewlyForkedFastSyncedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3->S4->S5->S6
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    6,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 6,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was not yet committed, but the process crashed. In
+// this test scenario the side chain reaches above the committed block. 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 left off.
+func TestShortNewlyForkedFastSyncingRepair(t *testing.T) {
+	testShortNewlyForkedFastSyncingRepair(t, false)
+}
+func TestShortNewlyForkedFastSyncingRepairWithSnapshots(t *testing.T) {
+	testShortNewlyForkedFastSyncingRepair(t, true)
+}
+
+func testShortNewlyForkedFastSyncingRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3->S4->S5->S6
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    6,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 6,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// 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
+// 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.
+func TestShortReorgedRepair(t *testing.T)              { testShortReorgedRepair(t, false) }
+func TestShortReorgedRepairWithSnapshots(t *testing.T) { testShortReorgedRepair(t, true) }
+
+func testShortReorgedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    10,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 10,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a longer side chain, where
+// the fast sync pivot point 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 the chain data itself left in the database for replaying.
+func TestShortReorgedFastSyncedRepair(t *testing.T) {
+	testShortReorgedFastSyncedRepair(t, false)
+}
+func TestShortReorgedFastSyncedRepairWithSnapshots(t *testing.T) {
+	testShortReorgedFastSyncedRepair(t, true)
+}
+
+func testShortReorgedFastSyncedRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    10,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 10,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a short canonical chain and a longer side chain, where
+// the fast sync pivot point was 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 up directly where we left off.
+func TestShortReorgedFastSyncingRepair(t *testing.T) {
+	testShortReorgedFastSyncingRepair(t, false)
+}
+func TestShortReorgedFastSyncingRepairWithSnapshots(t *testing.T) {
+	testShortReorgedFastSyncingRepair(t, true)
+}
+
+func testShortReorgedFastSyncingRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Expected head header    : C8
+	// Expected head fast block: C8
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    10,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 8,
+		expSidechainBlocks: 10,
+		expFrozen:          0,
+		expHeadHeader:      8,
+		expHeadFastBlock:   8,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// 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
+// 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.
+func TestLongShallowRepair(t *testing.T)              { testLongShallowRepair(t, false) }
+func TestLongShallowRepairWithSnapshots(t *testing.T) { testLongShallowRepair(t, true) }
+
+func testLongShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// 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
+// the process crashed. In this case we expect the chain to be rolled back to the
+// committed block, with everything afterwads deleted.
+func TestLongDeepRepair(t *testing.T)              { testLongDeepRepair(t, false) }
+func TestLongDeepRepairWithSnapshots(t *testing.T) { testLongDeepRepair(t, true) }
+
+func testLongDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks where the fast
+// sync pivot point - newer than the ancient limit - was already committed, after
+// 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.
+func TestLongFastSyncedShallowRepair(t *testing.T) {
+	testLongFastSyncedShallowRepair(t, false)
+}
+func TestLongFastSyncedShallowRepairWithSnapshots(t *testing.T) {
+	testLongFastSyncedShallowRepair(t, true)
+}
+
+func testLongFastSyncedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// 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
+// which the process crashed. In this case we expect the chain to be rolled back
+// to the committed block, with everything afterwads deleted.
+func TestLongFastSyncedDeepRepair(t *testing.T)              { testLongFastSyncedDeepRepair(t, false) }
+func TestLongFastSyncedDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncedDeepRepair(t, true) }
+
+func testLongFastSyncedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks where the fast
+// sync pivot point - older than the ancient limit - was 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 up directly where we
+// left off.
+func TestLongFastSyncingShallowRepair(t *testing.T) {
+	testLongFastSyncingShallowRepair(t, false)
+}
+func TestLongFastSyncingShallowRepairWithSnapshots(t *testing.T) {
+	testLongFastSyncingShallowRepair(t, true)
+}
+
+func testLongFastSyncingShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks where the fast
+// sync pivot point - newer than the ancient limit - was 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 up directly where we
+// left off.
+func TestLongFastSyncingDeepRepair(t *testing.T)              { testLongFastSyncingDeepRepair(t, false) }
+func TestLongFastSyncingDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncingDeepRepair(t, true) }
+
+func testLongFastSyncingDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Expected in leveldb:
+	//   C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
+	//
+	// Expected head header    : C24
+	// Expected head fast block: C24
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 24,
+		expSidechainBlocks: 0,
+		expFrozen:          9,
+		expHeadHeader:      24,
+		expHeadFastBlock:   24,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where a recent block - newer than the ancient limit - was already
+// committed to disk and then the process crashed. In this test scenario the side
+// chain is below the committed block. In this case we expect the chain to be
+// rolled back to the committed block, with everything afterwads kept as fast
+// sync data; the side chain completely nuked by the freezer.
+func TestLongOldForkedShallowRepair(t *testing.T) {
+	testLongOldForkedShallowRepair(t, false)
+}
+func TestLongOldForkedShallowRepairWithSnapshots(t *testing.T) {
+	testLongOldForkedShallowRepair(t, true)
+}
+
+func testLongOldForkedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where a recent block - older than the ancient limit - was already
+// committed to disk and then the process crashed. In this 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, with everything afterwads deleted;
+// the side chain completely nuked by the freezer.
+func TestLongOldForkedDeepRepair(t *testing.T)              { testLongOldForkedDeepRepair(t, false) }
+func TestLongOldForkedDeepRepairWithSnapshots(t *testing.T) { testLongOldForkedDeepRepair(t, true) }
+
+func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was already committed to disk and then the process crashed. In this test scenario
+// the side chain is below the committed block. In this case we expect the chain
+// to be rolled back to the committed block, with everything afterwads kept as
+// fast sync data; the side chain completely nuked by the freezer.
+func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) {
+	testLongOldForkedFastSyncedShallowRepair(t, false)
+}
+func TestLongOldForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncedShallowRepair(t, true)
+}
+
+func testLongOldForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was already committed to disk and then the process crashed. In this 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, with everything afterwads deleted;
+// the side chain completely nuked by the freezer.
+func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) {
+	testLongOldForkedFastSyncedDeepRepair(t, false)
+}
+func TestLongOldForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncedDeepRepair(t, true)
+}
+
+func testLongOldForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but the process crashed. In this test scenario the side
+// chain is below the committed block. In this case we expect the chain to detect
+// that it was fast syncing and not delete anything. The side chain is completely
+// nuked by the freezer.
+func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) {
+	testLongOldForkedFastSyncingShallowRepair(t, false)
+}
+func TestLongOldForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncingShallowRepair(t, true)
+}
+
+func testLongOldForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but the process crashed. In this test scenario the side
+// chain is below the committed block. In this case we expect the chain to detect
+// that it was fast syncing and not delete anything. The side chain is completely
+// nuked by the freezer.
+func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) {
+	testLongOldForkedFastSyncingDeepRepair(t, false)
+}
+func TestLongOldForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncingDeepRepair(t, true)
+}
+
+func testLongOldForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Expected in leveldb:
+	//   C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
+	//
+	// Expected head header    : C24
+	// Expected head fast block: C24
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 24,
+		expSidechainBlocks: 0,
+		expFrozen:          9,
+		expHeadHeader:      24,
+		expHeadFastBlock:   24,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where a recent block - newer than the ancient limit - was already
+// committed to disk and then the process crashed. In this test scenario the side
+// chain is above the committed block. In this case we expect the chain to be
+// rolled back to the committed block, with everything afterwads kept as fast
+// sync data; the side chain completely nuked by the freezer.
+func TestLongNewerForkedShallowRepair(t *testing.T) {
+	testLongNewerForkedShallowRepair(t, false)
+}
+func TestLongNewerForkedShallowRepairWithSnapshots(t *testing.T) {
+	testLongNewerForkedShallowRepair(t, true)
+}
+
+func testLongNewerForkedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where a recent block - older than the ancient limit - was already
+// committed to disk and then the process crashed. In this test scenario the side
+// 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;
+// the side chain completely nuked by the freezer.
+func TestLongNewerForkedDeepRepair(t *testing.T)              { testLongNewerForkedDeepRepair(t, false) }
+func TestLongNewerForkedDeepRepairWithSnapshots(t *testing.T) { testLongNewerForkedDeepRepair(t, true) }
+
+func testLongNewerForkedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was already committed to disk and then the process crashed. In this test scenario
+// the side chain is above the committed block. In this case we expect the chain
+// to be rolled back to the committed block, with everything afterwads kept as fast
+// sync data; the side chain completely nuked by the freezer.
+func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) {
+	testLongNewerForkedFastSyncedShallowRepair(t, false)
+}
+func TestLongNewerForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncedShallowRepair(t, true)
+}
+
+func testLongNewerForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was already committed to disk and then the process crashed. In this test scenario
+// the side 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;
+// the side chain completely nuked by the freezer.
+func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) {
+	testLongNewerForkedFastSyncedDeepRepair(t, false)
+}
+func TestLongNewerForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncedDeepRepair(t, true)
+}
+
+func testLongNewerForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but the process crashed. In this test scenario the side
+// chain is above the committed block. In this case we expect the chain to detect
+// that it was fast syncing and not delete anything. The side chain is completely
+// nuked by the freezer.
+func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) {
+	testLongNewerForkedFastSyncingShallowRepair(t, false)
+}
+func TestLongNewerForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncingShallowRepair(t, true)
+}
+
+func testLongNewerForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but the process crashed. In this test scenario the side
+// chain is above the committed block. In this case we expect the chain to detect
+// that it was fast syncing and not delete anything. The side chain is completely
+// nuked by the freezer.
+func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) {
+	testLongNewerForkedFastSyncingDeepRepair(t, false)
+}
+func TestLongNewerForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncingDeepRepair(t, true)
+}
+
+func testLongNewerForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Expected in leveldb:
+	//   C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
+	//
+	// Expected head header    : C24
+	// Expected head fast block: C24
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 24,
+		expSidechainBlocks: 0,
+		expFrozen:          9,
+		expHeadHeader:      24,
+		expHeadFastBlock:   24,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery 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
+// 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
+// data. The side chain completely nuked by the freezer.
+func TestLongReorgedShallowRepair(t *testing.T)              { testLongReorgedShallowRepair(t, false) }
+func TestLongReorgedShallowRepairWithSnapshots(t *testing.T) { testLongReorgedShallowRepair(t, true) }
+
+func testLongReorgedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery 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
+// 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
+// side chain completely nuked by the freezer.
+func TestLongReorgedDeepRepair(t *testing.T)              { testLongReorgedDeepRepair(t, false) }
+func TestLongReorgedDeepRepairWithSnapshots(t *testing.T) { testLongReorgedDeepRepair(t, true) }
+
+func testLongReorgedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->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
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - 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 committed block, with everything
+// afterwads kept as fast sync data. The side chain completely nuked by the
+// freezer.
+func TestLongReorgedFastSyncedShallowRepair(t *testing.T) {
+	testLongReorgedFastSyncedShallowRepair(t, false)
+}
+func TestLongReorgedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncedShallowRepair(t, true)
+}
+
+func testLongReorgedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was already committed 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 side chain completely nuked by the freezer.
+func TestLongReorgedFastSyncedDeepRepair(t *testing.T) {
+	testLongReorgedFastSyncedDeepRepair(t, false)
+}
+func TestLongReorgedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncedDeepRepair(t, true)
+}
+
+func testLongReorgedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->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
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was 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 up directly where we left off.
+func TestLongReorgedFastSyncingShallowRepair(t *testing.T) {
+	testLongReorgedFastSyncingShallowRepair(t, false)
+}
+func TestLongReorgedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncingShallowRepair(t, true)
+}
+
+func testLongReorgedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18
+	//
+	// Expected head header    : C18
+	// Expected head fast block: C18
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 18,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      18,
+		expHeadFastBlock:   18,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a recovery for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was 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 up directly where we left off.
+func TestLongReorgedFastSyncingDeepRepair(t *testing.T) {
+	testLongReorgedFastSyncingDeepRepair(t, false)
+}
+func TestLongReorgedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncingDeepRepair(t, true)
+}
+
+func testLongReorgedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->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
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// CRASH
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Expected in leveldb:
+	//   C8)->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24
+	//
+	// Expected head header    : C24
+	// Expected head fast block: C24
+	// Expected head block     : G
+	testRepair(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		expCanonicalBlocks: 24,
+		expSidechainBlocks: 0,
+		expFrozen:          9,
+		expHeadHeader:      24,
+		expHeadFastBlock:   24,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
+	// 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(true))
+
+	// 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, "", false)
+	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()
+		config  = &CacheConfig{
+			TrieCleanLimit: 256,
+			TrieDirtyLimit: 256,
+			TrieTimeLimit:  5 * time.Minute,
+			SnapshotLimit:  0, // Disable snapshot by default
+		}
+	)
+	if snapshots {
+		config.SnapshotLimit = 256
+		config.SnapshotWait = true
+	}
+	chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to create chain: %v", err)
+	}
+	// If sidechain blocks are needed, make a light chain and import it
+	var sideblocks types.Blocks
+	if tt.sidechainBlocks > 0 {
+		sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
+			b.SetCoinbase(common.Address{0x01})
+		})
+		if _, err := chain.InsertChain(sideblocks); err != nil {
+			t.Fatalf("Failed to import side chain: %v", err)
+		}
+	}
+	canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
+		b.SetCoinbase(common.Address{0x02})
+		b.SetDifficulty(big.NewInt(1000000))
+	})
+	if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil {
+		t.Fatalf("Failed to import canonical chain start: %v", err)
+	}
+	if tt.commitBlock > 0 {
+		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 {
+		t.Fatalf("Failed to import canonical chain tail: %v", err)
+	}
+	// Force run a freeze cycle
+	type freezer interface {
+		Freeze(threshold uint64) error
+		Ancients() (uint64, error)
+	}
+	db.(freezer).Freeze(tt.freezeThreshold)
+
+	// Set the simulated pivot block
+	if tt.pivotBlock != nil {
+		rawdb.WriteLastPivotNumber(db, *tt.pivotBlock)
+	}
+	// Pull the plug on the database, simulating a hard crash
+	db.Close()
+
+	// Start a new blockchain back up and see where the repait leads us
+	db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
+	if err != nil {
+		t.Fatalf("Failed to reopen persistent database: %v", err)
+	}
+	defer db.Close()
+
+	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, canonblocks)
+	verifyNoGaps(t, chain, false, sideblocks)
+	verifyCutoff(t, chain, true, canonblocks, tt.expCanonicalBlocks)
+	verifyCutoff(t, chain, false, sideblocks, tt.expSidechainBlocks)
+
+	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)
+	}
+	if frozen, err := db.(freezer).Ancients(); err != nil {
+		t.Errorf("Failed to retrieve ancient count: %v\n", err)
+	} else if int(frozen) != tt.expFrozen {
+		t.Errorf("Frozen block count mismatch: have %d, want %d", frozen, tt.expFrozen)
+	}
+}
diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go
new file mode 100644
index 0000000000..e99b09cf8c
--- /dev/null
+++ b/core/blockchain_sethead_test.go
@@ -0,0 +1,2169 @@
+// 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 .
+
+// Tests that setting the chain head backwards doesn't leave the database in some
+// strange state with gaps in the chain, nor with block data dangling in the future.
+
+package core
+
+import (
+	"fmt"
+	"io/ioutil"
+	"math/big"
+	"os"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+// rewindTest is a test case for chain rollback upon user request.
+type rewindTest struct {
+	canonicalBlocks int     // Number of blocks to generate for the canonical chain (heavier)
+	sidechainBlocks int     // Number of blocks to generate for the side chain (lighter)
+	freezeThreshold uint64  // Block number until which to move things into the freezer
+	commitBlock     uint64  // Block number for which to commit the state to disk
+	pivotBlock      *uint64 // Pivot block number in case of fast sync
+
+	setheadBlock       uint64 // Block number to set head back to
+	expCanonicalBlocks int    // Number of canonical blocks expected to remain in the database (excl. genesis)
+	expSidechainBlocks int    // Number of sidechain blocks expected to remain in the database (excl. genesis)
+	expFrozen          int    // Number of canonical blocks expected to be in the freezer (incl. 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
+}
+
+func (tt *rewindTest) dump(crash bool) string {
+	buffer := new(strings.Builder)
+
+	fmt.Fprint(buffer, "Chain:\n  G")
+	for i := 0; i < tt.canonicalBlocks; i++ {
+		fmt.Fprintf(buffer, "->C%d", i+1)
+	}
+	fmt.Fprint(buffer, " (HEAD)\n")
+	if tt.sidechainBlocks > 0 {
+		fmt.Fprintf(buffer, "  └")
+		for i := 0; i < tt.sidechainBlocks; i++ {
+			fmt.Fprintf(buffer, "->S%d", i+1)
+		}
+		fmt.Fprintf(buffer, "\n")
+	}
+	fmt.Fprintf(buffer, "\n")
+
+	if tt.canonicalBlocks > int(tt.freezeThreshold) {
+		fmt.Fprint(buffer, "Frozen:\n  G")
+		for i := 0; i < tt.canonicalBlocks-int(tt.freezeThreshold); i++ {
+			fmt.Fprintf(buffer, "->C%d", i+1)
+		}
+		fmt.Fprintf(buffer, "\n\n")
+	} else {
+		fmt.Fprintf(buffer, "Frozen: none\n")
+	}
+	fmt.Fprintf(buffer, "Commit: G")
+	if tt.commitBlock > 0 {
+		fmt.Fprintf(buffer, ", C%d", tt.commitBlock)
+	}
+	fmt.Fprint(buffer, "\n")
+
+	if tt.pivotBlock == nil {
+		fmt.Fprintf(buffer, "Pivot : none\n")
+	} else {
+		fmt.Fprintf(buffer, "Pivot : C%d\n", *tt.pivotBlock)
+	}
+	if crash {
+		fmt.Fprintf(buffer, "\nCRASH\n\n")
+	} else {
+		fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", tt.setheadBlock)
+	}
+	fmt.Fprintf(buffer, "------------------------------\n\n")
+
+	if tt.expFrozen > 0 {
+		fmt.Fprint(buffer, "Expected in freezer:\n  G")
+		for i := 0; i < tt.expFrozen-1; i++ {
+			fmt.Fprintf(buffer, "->C%d", i+1)
+		}
+		fmt.Fprintf(buffer, "\n\n")
+	}
+	if tt.expFrozen > 0 {
+		if tt.expFrozen >= tt.expCanonicalBlocks {
+			fmt.Fprintf(buffer, "Expected in leveldb: none\n")
+		} else {
+			fmt.Fprintf(buffer, "Expected in leveldb:\n  C%d)", tt.expFrozen-1)
+			for i := tt.expFrozen - 1; i < tt.expCanonicalBlocks; i++ {
+				fmt.Fprintf(buffer, "->C%d", i+1)
+			}
+			fmt.Fprint(buffer, "\n")
+			if tt.expSidechainBlocks > tt.expFrozen {
+				fmt.Fprintf(buffer, "  └")
+				for i := tt.expFrozen - 1; i < tt.expSidechainBlocks; i++ {
+					fmt.Fprintf(buffer, "->S%d", i+1)
+				}
+				fmt.Fprintf(buffer, "\n")
+			}
+		}
+	} else {
+		fmt.Fprint(buffer, "Expected in leveldb:\n  G")
+		for i := tt.expFrozen; i < tt.expCanonicalBlocks; i++ {
+			fmt.Fprintf(buffer, "->C%d", i+1)
+		}
+		fmt.Fprint(buffer, "\n")
+		if tt.expSidechainBlocks > tt.expFrozen {
+			fmt.Fprintf(buffer, "  └")
+			for i := tt.expFrozen; i < tt.expSidechainBlocks; i++ {
+				fmt.Fprintf(buffer, "->S%d", i+1)
+			}
+			fmt.Fprintf(buffer, "\n")
+		}
+	}
+	fmt.Fprintf(buffer, "\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)
+	}
+	return buffer.String()
+}
+
+// Tests a sethead for a short canonical chain where a recent block was already
+// committed to disk and then the sethead called. In this case we expect the full
+// 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
+// the data can remain as "fast sync" data to avoid redownloading it.
+func TestShortSetHead(t *testing.T)              { testShortSetHead(t, false) }
+func TestShortSetHeadWithSnapshots(t *testing.T) { testShortSetHead(t, true) }
+
+func testShortSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 0,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain where the fast sync pivot point was
+// already committed, after which sethead was called. In this case we expect the
+// chain to behave like in full sync mode, rolling back 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 "fast sync" data to avoid
+// redownloading it.
+func TestShortFastSyncedSetHead(t *testing.T)              { testShortFastSyncedSetHead(t, false) }
+func TestShortFastSyncedSetHeadWithSnapshots(t *testing.T) { testShortFastSyncedSetHead(t, true) }
+
+func testShortFastSyncedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 0,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain where the fast sync pivot point was
+// not yet committed, but sethead was called. In this case we expect the chain to
+// 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
+// to the genesis.
+func TestShortFastSyncingSetHead(t *testing.T)              { testShortFastSyncingSetHead(t, false) }
+func TestShortFastSyncingSetHeadWithSnapshots(t *testing.T) { testShortFastSyncingSetHead(t, true) }
+
+func testShortFastSyncingSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 0,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a shorter side chain, where a
+// recent block was already committed to disk and then sethead was called. In this
+// test scenario the side chain is below the committed block. In this case we expect
+// the canonical full 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 the data can remain as "fast sync" data to avoid redownloading
+// it. The side chain should be left alone as it was shorter.
+func TestShortOldForkedSetHead(t *testing.T)              { testShortOldForkedSetHead(t, false) }
+func TestShortOldForkedSetHeadWithSnapshots(t *testing.T) { testShortOldForkedSetHead(t, true) }
+
+func testShortOldForkedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 3,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was already committed to disk and then sethead was
+// called. In this test scenario the side chain is below the committed block. In
+// this case we expect the canonical full 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 the data can remain as "fast sync" data
+// to avoid redownloading it. The side chain should be left alone as it was shorter.
+func TestShortOldForkedFastSyncedSetHead(t *testing.T) {
+	testShortOldForkedFastSyncedSetHead(t, false)
+}
+func TestShortOldForkedFastSyncedSetHeadWithSnapshots(t *testing.T) {
+	testShortOldForkedFastSyncedSetHead(t, true)
+}
+
+func testShortOldForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 3,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was not yet committed, but sethead was called. In this
+// test scenario the side chain is below the committed block. In this case we expect
+// the chain to 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 to the genesis.
+func TestShortOldForkedFastSyncingSetHead(t *testing.T) {
+	testShortOldForkedFastSyncingSetHead(t, false)
+}
+func TestShortOldForkedFastSyncingSetHeadWithSnapshots(t *testing.T) {
+	testShortOldForkedFastSyncingSetHead(t, true)
+}
+
+func testShortOldForkedFastSyncingSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 3,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a shorter side chain, where a
+// recent block was already committed to disk and then sethead was called. In this
+// test scenario the side chain reaches above the committed block. In this case we
+// expect the canonical full chain to be rolled back to the committed block. All
+// data 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 having
+// to redownload it. The side chain should be truncated to the head set.
+//
+// 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
+// properly handle it, so we'll trade extra work in exchange for simpler code.
+func TestShortNewlyForkedSetHead(t *testing.T)              { testShortNewlyForkedSetHead(t, false) }
+func TestShortNewlyForkedSetHeadWithSnapshots(t *testing.T) { testShortNewlyForkedSetHead(t, true) }
+
+func testShortNewlyForkedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3->S4->S5->S6->S7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    10,
+		sidechainBlocks:    8,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 7,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was already committed to disk and then sethead was
+// called. In this case we expect the canonical full chain to be rolled back to
+// between the committed block and the requested head the data can remain as
+// "fast sync" data to avoid having to redownload it. The side chain should be
+// truncated to the head set.
+//
+// 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
+// properly handle it, so we'll trade extra work in exchange for simpler code.
+func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) {
+	testShortNewlyForkedFastSyncedSetHead(t, false)
+}
+func TestShortNewlyForkedFastSyncedSetHeadWithSnapshots(t *testing.T) {
+	testShortNewlyForkedFastSyncedSetHead(t, true)
+}
+
+func testShortNewlyForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3->S4->S5->S6->S7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    10,
+		sidechainBlocks:    8,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 7,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a shorter side chain, where
+// the fast sync pivot point was not yet committed, but sethead was called. In
+// this test scenario the side chain reaches above the committed block. In this
+// case we expect the chain to detect that it was fast syncing and delete
+// everything from the new head, since we can just pick up fast syncing from
+// there.
+//
+// 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
+// properly handle it, so we'll trade extra work in exchange for simpler code.
+func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) {
+	testShortNewlyForkedFastSyncingSetHead(t, false)
+}
+func TestShortNewlyForkedFastSyncingSetHeadWithSnapshots(t *testing.T) {
+	testShortNewlyForkedFastSyncingSetHead(t, true)
+}
+
+func testShortNewlyForkedFastSyncingSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3->S4->S5->S6->S7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    10,
+		sidechainBlocks:    8,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 7,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a longer side chain, where a
+// recent block was already committed to disk and then sethead was called. In this
+// case we expect the canonical full chain to be rolled back to the committed block.
+// All data 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
+// having to redownload it. The side chain should be truncated to the head set.
+//
+// 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
+// properly handle it, so we'll trade extra work in exchange for simpler code.
+func TestShortReorgedSetHead(t *testing.T)              { testShortReorgedSetHead(t, false) }
+func TestShortReorgedSetHeadWithSnapshots(t *testing.T) { testShortReorgedSetHead(t, true) }
+
+func testShortReorgedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3->S4->S5->S6->S7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    10,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 7,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a longer side chain, where
+// the fast sync pivot point was already committed to disk and then sethead was
+// called. In this case we expect the canonical full chain to be rolled back to
+// the committed block. All data 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 having to redownload it. The side chain should be
+// truncated to the head set.
+//
+// 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
+// properly handle it, so we'll trade extra work in exchange for simpler code.
+func TestShortReorgedFastSyncedSetHead(t *testing.T) {
+	testShortReorgedFastSyncedSetHead(t, false)
+}
+func TestShortReorgedFastSyncedSetHeadWithSnapshots(t *testing.T) {
+	testShortReorgedFastSyncedSetHead(t, true)
+}
+
+func testShortReorgedFastSyncedSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Frozen: none
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3->S4->S5->S6->S7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    10,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 7,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a short canonical chain and a longer side chain, where
+// the fast sync pivot point was not yet committed, but sethead was called. In
+// this case we expect the chain to detect that it was fast syncing and delete
+// everything from the new head, since we can just pick up fast syncing from
+// there.
+//
+// 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
+// properly handle it, so we'll trade extra work in exchange for simpler code.
+func TestShortReorgedFastSyncingSetHead(t *testing.T) {
+	testShortReorgedFastSyncingSetHead(t, false)
+}
+func TestShortReorgedFastSyncingSetHeadWithSnapshots(t *testing.T) {
+	testShortReorgedFastSyncingSetHead(t, true)
+}
+
+func testShortReorgedFastSyncingSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
+	//
+	// Frozen: none
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(7)
+	//
+	// ------------------------------
+	//
+	// Expected in leveldb:
+	//   G->C1->C2->C3->C4->C5->C6->C7
+	//   └->S1->S2->S3->S4->S5->S6->S7
+	//
+	// Expected head header    : C7
+	// Expected head fast block: C7
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    8,
+		sidechainBlocks:    10,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       7,
+		expCanonicalBlocks: 7,
+		expSidechainBlocks: 7,
+		expFrozen:          0,
+		expHeadHeader:      7,
+		expHeadFastBlock:   7,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks where a recent
+// block - newer than the ancient limit - was already committed to disk and then
+// sethead was called. In this case we expect the full 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 the data can remain as
+// "fast sync" data to avoid redownloading it.
+func TestLongShallowSetHead(t *testing.T)              { testLongShallowSetHead(t, false) }
+func TestLongShallowSetHeadWithSnapshots(t *testing.T) { testLongShallowSetHead(t, true) }
+
+func testLongShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks where a recent
+// block - older than the ancient limit - was already committed to disk and then
+// 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
+// needs to be deleted onwards to avoid creating a gap.
+func TestLongDeepSetHead(t *testing.T)              { testLongDeepSetHead(t, false) }
+func TestLongDeepSetHeadWithSnapshots(t *testing.T) { testLongDeepSetHead(t, true) }
+
+func testLongDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks where the fast
+// sync pivot point - newer than the ancient limit - was already committed, after
+// which sethead was called. In this case we expect the full 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 the data can
+// remain as "fast sync" data to avoid redownloading it.
+func TestLongFastSyncedShallowSetHead(t *testing.T) {
+	testLongFastSyncedShallowSetHead(t, false)
+}
+func TestLongFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongFastSyncedShallowSetHead(t, true)
+}
+
+func testLongFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks where the fast
+// sync pivot point - older than the ancient limit - was already committed, after
+// 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
+// needs to be deleted onwards to avoid creating a gap.
+func TestLongFastSyncedDeepSetHead(t *testing.T)              { testLongFastSyncedDeepSetHead(t, false) }
+func TestLongFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { testLongFastSyncedDeepSetHead(t, true) }
+
+func testLongFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks where the fast
+// sync pivot point - newer than the ancient limit - was not yet committed, but
+// sethead was called. In this case we expect the chain to detect that it was fast
+// syncing and delete everything from the new head, since we can just pick up fast
+// syncing from there.
+func TestLongFastSyncingShallowSetHead(t *testing.T) {
+	testLongFastSyncingShallowSetHead(t, false)
+}
+func TestLongFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongFastSyncingShallowSetHead(t, true)
+}
+
+func testLongFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks where the fast
+// sync pivot point - older than the ancient limit - was not yet committed, but
+// sethead was called. In this case we expect the chain to detect that it was fast
+// syncing and delete everything from the new head, since we can just pick up fast
+// syncing from there.
+func TestLongFastSyncingDeepSetHead(t *testing.T) {
+	testLongFastSyncingDeepSetHead(t, false)
+}
+func TestLongFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongFastSyncingDeepSetHead(t, true)
+}
+
+func testLongFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    0,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          7,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter side
+// chain, where a recent block - newer than the ancient limit - was already committed
+// to disk and then sethead was called. In this case we expect the canonical full
+// 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 the data
+// can remain as "fast sync" data to avoid redownloading it. The side chain is nuked
+// by the freezer.
+func TestLongOldForkedShallowSetHead(t *testing.T) {
+	testLongOldForkedShallowSetHead(t, false)
+}
+func TestLongOldForkedShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongOldForkedShallowSetHead(t, true)
+}
+
+func testLongOldForkedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter side
+// chain, where a recent block - older than the ancient limit - was already committed
+// to disk and then sethead was called. In this case we expect the canonical full
+// 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
+// side chain is nuked by the freezer.
+func TestLongOldForkedDeepSetHead(t *testing.T)              { testLongOldForkedDeepSetHead(t, false) }
+func TestLongOldForkedDeepSetHeadWithSnapshots(t *testing.T) { testLongOldForkedDeepSetHead(t, true) }
+
+func testLongOldForkedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was already committed to disk and then sethead was called. In this test scenario
+// the side chain is below the committed block. In this case we expect the canonical
+// full 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 the data can remain as "fast sync" data to avoid redownloading
+// it. The side chain is nuked by the freezer.
+func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) {
+	testLongOldForkedFastSyncedShallowSetHead(t, false)
+}
+func TestLongOldForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncedShallowSetHead(t, true)
+}
+
+func testLongOldForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was already committed to disk and then sethead was called. In this test scenario
+// the side chain is below the committed block. In this case we expect the canonical
+// full 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
+// side chain is nuked by the freezer.
+func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) {
+	testLongOldForkedFastSyncedDeepSetHead(t, false)
+}
+func TestLongOldForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncedDeepSetHead(t, true)
+}
+
+func testLongOldForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was not yet committed, but sethead was called. In this test scenario the side
+// chain is below the committed block. In this case we expect the chain to detect
+// that it was fast syncing and delete everything from the new head, since we can
+// just pick up fast syncing from there. The side chain is completely nuked by the
+// freezer.
+func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) {
+	testLongOldForkedFastSyncingShallowSetHead(t, false)
+}
+func TestLongOldForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncingShallowSetHead(t, true)
+}
+
+func testLongOldForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but sethead was called. In this test scenario the side
+// chain is below the committed block. In this case we expect the chain to detect
+// that it was fast syncing and delete everything from the new head, since we can
+// just pick up fast syncing from there. The side chain is completely nuked by the
+// freezer.
+func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) {
+	testLongOldForkedFastSyncingDeepSetHead(t, false)
+}
+func TestLongOldForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongOldForkedFastSyncingDeepSetHead(t, true)
+}
+
+func testLongOldForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    3,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          7,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where a recent block - newer than the ancient limit - was already
+// committed to disk and then sethead was called. In this test scenario the side
+// chain is above the committed block. In this case the freezer will delete the
+// sidechain since it's dangling, reverting to TestLongShallowSetHead.
+func TestLongNewerForkedShallowSetHead(t *testing.T) {
+	testLongNewerForkedShallowSetHead(t, false)
+}
+func TestLongNewerForkedShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongNewerForkedShallowSetHead(t, true)
+}
+
+func testLongNewerForkedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where a recent block - older than the ancient limit - was already
+// committed to disk and then sethead was called. In this test scenario the side
+// chain is above the committed block. In this case the freezer will delete the
+// sidechain since it's dangling, reverting to TestLongDeepSetHead.
+func TestLongNewerForkedDeepSetHead(t *testing.T) {
+	testLongNewerForkedDeepSetHead(t, false)
+}
+func TestLongNewerForkedDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongNewerForkedDeepSetHead(t, true)
+}
+
+func testLongNewerForkedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was already committed to disk and then sethead was called. In this test scenario
+// the side chain is above the committed block. In this case the freezer will delete
+// the sidechain since it's dangling, reverting to TestLongFastSyncedShallowSetHead.
+func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) {
+	testLongNewerForkedFastSyncedShallowSetHead(t, false)
+}
+func TestLongNewerForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncedShallowSetHead(t, true)
+}
+
+func testLongNewerForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was already committed to disk and then sethead was called. In this test scenario
+// the side chain is above the committed block. In this case the freezer will delete
+// the sidechain since it's dangling, reverting to TestLongFastSyncedDeepSetHead.
+func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) {
+	testLongNewerForkedFastSyncedDeepSetHead(t, false)
+}
+func TestLongNewerForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncedDeepSetHead(t, true)
+}
+
+func testLongNewerForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was not yet committed, but sethead was called. In this test scenario the side
+// chain is above the committed block. In this case the freezer will delete the
+// sidechain since it's dangling, reverting to TestLongFastSyncinghallowSetHead.
+func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) {
+	testLongNewerForkedFastSyncingShallowSetHead(t, false)
+}
+func TestLongNewerForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncingShallowSetHead(t, true)
+}
+
+func testLongNewerForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a shorter
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but sethead was called. In this test scenario the side
+// chain is above the committed block. In this case the freezer will delete the
+// sidechain since it's dangling, reverting to TestLongFastSyncingDeepSetHead.
+func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) {
+	testLongNewerForkedFastSyncingDeepSetHead(t, false)
+}
+func TestLongNewerForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongNewerForkedFastSyncingDeepSetHead(t, true)
+}
+
+func testLongNewerForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    12,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          7,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// 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
+// to disk and then sethead was called. In this case the freezer will delete the
+// sidechain since it's dangling, reverting to TestLongShallowSetHead.
+func TestLongReorgedShallowSetHead(t *testing.T)              { testLongReorgedShallowSetHead(t, false) }
+func TestLongReorgedShallowSetHeadWithSnapshots(t *testing.T) { testLongReorgedShallowSetHead(t, true) }
+
+func testLongReorgedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// 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
+// to disk and then sethead was called. In this case the freezer will delete the
+// sidechain since it's dangling, reverting to TestLongDeepSetHead.
+func TestLongReorgedDeepSetHead(t *testing.T)              { testLongReorgedDeepSetHead(t, false) }
+func TestLongReorgedDeepSetHeadWithSnapshots(t *testing.T) { testLongReorgedDeepSetHead(t, true) }
+
+func testLongReorgedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->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
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : none
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         nil,
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was already committed to disk and then sethead was called. In this case the
+// freezer will delete the sidechain since it's dangling, reverting to
+// TestLongFastSyncedShallowSetHead.
+func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) {
+	testLongReorgedFastSyncedShallowSetHead(t, false)
+}
+func TestLongReorgedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncedShallowSetHead(t, true)
+}
+
+func testLongReorgedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was already committed to disk and then sethead was called. In this case the
+// freezer will delete the sidechain since it's dangling, reverting to
+// TestLongFastSyncedDeepSetHead.
+func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) {
+	testLongReorgedFastSyncedDeepSetHead(t, false)
+}
+func TestLongReorgedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncedDeepSetHead(t, true)
+}
+
+func testLongReorgedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->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
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G, C4
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C4
+	// Expected head fast block: C4
+	// Expected head block     : C4
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        4,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 4,
+		expSidechainBlocks: 0,
+		expFrozen:          5,
+		expHeadHeader:      4,
+		expHeadFastBlock:   4,
+		expHeadBlock:       4,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - newer than the ancient limit -
+// was not yet committed, but sethead was called. In this case we expect the
+// chain to detect that it was fast syncing and delete everything from the new
+// head, since we can just pick up fast syncing from there. The side chain is
+// completely nuked by the freezer.
+func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) {
+	testLongReorgedFastSyncingShallowSetHead(t, false)
+}
+func TestLongReorgedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncingShallowSetHead(t, true)
+}
+
+func testLongReorgedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
+	// Chain:
+	//   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
+	//
+	// Frozen:
+	//   G->C1->C2
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2
+	//
+	// Expected in leveldb:
+	//   C2)->C3->C4->C5->C6
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    18,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          3,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+// Tests a sethead for a long canonical chain with frozen blocks and a longer
+// side chain, where the fast sync pivot point - older than the ancient limit -
+// was not yet committed, but sethead was called. In this case we expect the
+// chain to detect that it was fast syncing and delete everything from the new
+// head, since we can just pick up fast syncing from there. The side chain is
+// completely nuked by the freezer.
+func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) {
+	testLongReorgedFastSyncingDeepSetHead(t, false)
+}
+func TestLongReorgedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
+	testLongReorgedFastSyncingDeepSetHead(t, true)
+}
+
+func testLongReorgedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
+	// 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)
+	//   └->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
+	//
+	// Frozen:
+	//   G->C1->C2->C3->C4->C5->C6->C7->C8
+	//
+	// Commit: G
+	// Pivot : C4
+	//
+	// SetHead(6)
+	//
+	// ------------------------------
+	//
+	// Expected in freezer:
+	//   G->C1->C2->C3->C4->C5->C6
+	//
+	// Expected in leveldb: none
+	//
+	// Expected head header    : C6
+	// Expected head fast block: C6
+	// Expected head block     : G
+	testSetHead(t, &rewindTest{
+		canonicalBlocks:    24,
+		sidechainBlocks:    26,
+		freezeThreshold:    16,
+		commitBlock:        0,
+		pivotBlock:         uint64ptr(4),
+		setheadBlock:       6,
+		expCanonicalBlocks: 6,
+		expSidechainBlocks: 0,
+		expFrozen:          7,
+		expHeadHeader:      6,
+		expHeadFastBlock:   6,
+		expHeadBlock:       0,
+	}, snapshots)
+}
+
+func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
+	// 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(false))
+
+	// 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, "", false)
+	if err != nil {
+		t.Fatalf("Failed to create persistent database: %v", err)
+	}
+	defer db.Close()
+
+	// Initialize a fresh chain
+	var (
+		genesis = new(Genesis).MustCommit(db)
+		engine  = ethash.NewFullFaker()
+		config  = &CacheConfig{
+			TrieCleanLimit: 256,
+			TrieDirtyLimit: 256,
+			TrieTimeLimit:  5 * time.Minute,
+			SnapshotLimit:  0, // Disable snapshot
+		}
+	)
+	if snapshots {
+		config.SnapshotLimit = 256
+		config.SnapshotWait = true
+	}
+	chain, err := NewBlockChain(db, config, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to create chain: %v", err)
+	}
+	// If sidechain blocks are needed, make a light chain and import it
+	var sideblocks types.Blocks
+	if tt.sidechainBlocks > 0 {
+		sideblocks, _ = GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.sidechainBlocks, func(i int, b *BlockGen) {
+			b.SetCoinbase(common.Address{0x01})
+		})
+		if _, err := chain.InsertChain(sideblocks); err != nil {
+			t.Fatalf("Failed to import side chain: %v", err)
+		}
+	}
+	canonblocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, rawdb.NewMemoryDatabase(), tt.canonicalBlocks, func(i int, b *BlockGen) {
+		b.SetCoinbase(common.Address{0x02})
+		b.SetDifficulty(big.NewInt(1000000))
+	})
+	if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil {
+		t.Fatalf("Failed to import canonical chain start: %v", err)
+	}
+	if tt.commitBlock > 0 {
+		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 {
+		t.Fatalf("Failed to import canonical chain tail: %v", err)
+	}
+	// Manually dereference anything not committed to not have to work with 128+ tries
+	for _, block := range sideblocks {
+		chain.stateCache.TrieDB().Dereference(block.Root())
+	}
+	for _, block := range canonblocks {
+		chain.stateCache.TrieDB().Dereference(block.Root())
+	}
+	// Force run a freeze cycle
+	type freezer interface {
+		Freeze(threshold uint64) error
+		Ancients() (uint64, error)
+	}
+	db.(freezer).Freeze(tt.freezeThreshold)
+
+	// Set the simulated pivot block
+	if tt.pivotBlock != nil {
+		rawdb.WriteLastPivotNumber(db, *tt.pivotBlock)
+	}
+	// Set the head of the chain back to the requested number
+	chain.SetHead(tt.setheadBlock)
+
+	// Iterate over all the remaining blocks and ensure there are no gaps
+	verifyNoGaps(t, chain, true, canonblocks)
+	verifyNoGaps(t, chain, false, sideblocks)
+	verifyCutoff(t, chain, true, canonblocks, tt.expCanonicalBlocks)
+	verifyCutoff(t, chain, false, sideblocks, tt.expSidechainBlocks)
+
+	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)
+	}
+	if frozen, err := db.(freezer).Ancients(); err != nil {
+		t.Errorf("Failed to retrieve ancient count: %v\n", err)
+	} else if int(frozen) != tt.expFrozen {
+		t.Errorf("Frozen block count mismatch: have %d, want %d", frozen, tt.expFrozen)
+	}
+}
+
+// verifyNoGaps checks that there are no gaps after the initial set of blocks in
+// the database and errors if found.
+func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) {
+	t.Helper()
+
+	var end uint64
+	for i := uint64(0); i <= uint64(len(inserted)); i++ {
+		header := chain.GetHeaderByNumber(i)
+		if header == nil && end == 0 {
+			end = i
+		}
+		if header != nil && end > 0 {
+			if canonical {
+				t.Errorf("Canonical header gap between #%d-#%d", end, i-1)
+			} else {
+				t.Errorf("Sidechain header gap between #%d-#%d", end, i-1)
+			}
+			end = 0 // Reset for further gap detection
+		}
+	}
+	end = 0
+	for i := uint64(0); i <= uint64(len(inserted)); i++ {
+		block := chain.GetBlockByNumber(i)
+		if block == nil && end == 0 {
+			end = i
+		}
+		if block != nil && end > 0 {
+			if canonical {
+				t.Errorf("Canonical block gap between #%d-#%d", end, i-1)
+			} else {
+				t.Errorf("Sidechain block gap between #%d-#%d", end, i-1)
+			}
+			end = 0 // Reset for further gap detection
+		}
+	}
+	end = 0
+	for i := uint64(1); i <= uint64(len(inserted)); i++ {
+		receipts := chain.GetReceiptsByHash(inserted[i-1].Hash())
+		if receipts == nil && end == 0 {
+			end = i
+		}
+		if receipts != nil && end > 0 {
+			if canonical {
+				t.Errorf("Canonical receipt gap between #%d-#%d", end, i-1)
+			} else {
+				t.Errorf("Sidechain receipt gap between #%d-#%d", end, i-1)
+			}
+			end = 0 // Reset for further gap detection
+		}
+	}
+}
+
+// verifyCutoff checks that there are no chain data available in the chain after
+// the specified limit, but that it is available before.
+func verifyCutoff(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks, head int) {
+	t.Helper()
+
+	for i := 1; i <= len(inserted); i++ {
+		if i <= head {
+			if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header == nil {
+				if canonical {
+					t.Errorf("Canonical header   #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				} else {
+					t.Errorf("Sidechain header   #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				}
+			}
+			if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block == nil {
+				if canonical {
+					t.Errorf("Canonical block    #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				} else {
+					t.Errorf("Sidechain block    #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				}
+			}
+			if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts == nil {
+				if canonical {
+					t.Errorf("Canonical receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				} else {
+					t.Errorf("Sidechain receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				}
+			}
+		} else {
+			if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header != nil {
+				if canonical {
+					t.Errorf("Canonical header   #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				} else {
+					t.Errorf("Sidechain header   #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				}
+			}
+			if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block != nil {
+				if canonical {
+					t.Errorf("Canonical block    #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				} else {
+					t.Errorf("Sidechain block    #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				}
+			}
+			if receipts := chain.GetReceiptsByHash(inserted[i-1].Hash()); receipts != nil {
+				if canonical {
+					t.Errorf("Canonical receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				} else {
+					t.Errorf("Sidechain receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
+				}
+			}
+		}
+	}
+}
+
+// uint64ptr is a weird helper to allow 1-line constant pointer creation.
+func uint64ptr(n uint64) *uint64 {
+	return &n
+}
diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go
new file mode 100644
index 0000000000..75c09b421d
--- /dev/null
+++ b/core/blockchain_snapshot_test.go
@@ -0,0 +1,717 @@
+// 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 .
+
+// 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"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+// snapshotTestBasic wraps the common testing fields in the snapshot tests.
+type snapshotTestBasic struct {
+	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
+
+	// share fields, set in runtime
+	datadir string
+	db      ethdb.Database
+	gendb   ethdb.Database
+	engine  consensus.Engine
+}
+
+func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) {
+	// 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, "", false)
+	if err != nil {
+		t.Fatalf("Failed to create persistent database: %v", err)
+	}
+	// 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, basic.chainBlocks, func(i int, b *BlockGen) {})
+
+	// Insert the blocks with configured settings.
+	var breakpoints []uint64
+	if basic.commitBlock > basic.snapshotBlock {
+		breakpoints = append(breakpoints, basic.snapshotBlock, basic.commitBlock)
+	} else {
+		breakpoints = append(breakpoints, basic.commitBlock, basic.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 basic.commitBlock > 0 && basic.commitBlock == point {
+			chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), true, nil)
+		}
+		if basic.snapshotBlock > 0 && basic.snapshotBlock == point {
+			// Flushing the entire snap tree into the disk, the
+			// relavant (a) snapshot root and (b) snapshot generator
+			// will be persisted atomically.
+			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 runtime fields
+	basic.datadir = datadir
+	basic.db = db
+	basic.gendb = gendb
+	basic.engine = engine
+	return chain, blocks
+}
+
+func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) {
+	// Iterate over all the remaining blocks and ensure there are no gaps
+	verifyNoGaps(t, chain, true, blocks)
+	verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks)
+
+	if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadHeader {
+		t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadHeader)
+	}
+	if head := chain.CurrentFastBlock(); head.NumberU64() != basic.expHeadFastBlock {
+		t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadFastBlock)
+	}
+	if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock {
+		t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock)
+	}
+
+	// Check the disk layer, ensure they are matched
+	block := chain.GetBlockByNumber(basic.expSnapshotBottom)
+	if block == nil {
+		t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom)
+	} else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) {
+		t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot())
+	}
+
+	// Check the snapshot, ensure it's integrated
+	if err := chain.snaps.Verify(block.Root()); err != nil {
+		t.Errorf("The disk layer is not integrated %v", err)
+	}
+}
+
+func (basic *snapshotTestBasic) dump() string {
+	buffer := new(strings.Builder)
+
+	fmt.Fprint(buffer, "Chain:\n  G")
+	for i := 0; i < basic.chainBlocks; i++ {
+		fmt.Fprintf(buffer, "->C%d", i+1)
+	}
+	fmt.Fprint(buffer, " (HEAD)\n\n")
+
+	fmt.Fprintf(buffer, "Commit:   G")
+	if basic.commitBlock > 0 {
+		fmt.Fprintf(buffer, ", C%d", basic.commitBlock)
+	}
+	fmt.Fprint(buffer, "\n")
+
+	fmt.Fprintf(buffer, "Snapshot: G")
+	if basic.snapshotBlock > 0 {
+		fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock)
+	}
+	fmt.Fprint(buffer, "\n")
+
+	//if crash {
+	//	fmt.Fprintf(buffer, "\nCRASH\n\n")
+	//} else {
+	//	fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", basic.setHead)
+	//}
+	fmt.Fprintf(buffer, "------------------------------\n\n")
+
+	fmt.Fprint(buffer, "Expected in leveldb:\n  G")
+	for i := 0; i < basic.expCanonicalBlocks; i++ {
+		fmt.Fprintf(buffer, "->C%d", i+1)
+	}
+	fmt.Fprintf(buffer, "\n\n")
+	fmt.Fprintf(buffer, "Expected head header    : C%d\n", basic.expHeadHeader)
+	fmt.Fprintf(buffer, "Expected head fast block: C%d\n", basic.expHeadFastBlock)
+	if basic.expHeadBlock == 0 {
+		fmt.Fprintf(buffer, "Expected head block     : G\n")
+	} else {
+		fmt.Fprintf(buffer, "Expected head block     : C%d\n", basic.expHeadBlock)
+	}
+	if basic.expSnapshotBottom == 0 {
+		fmt.Fprintf(buffer, "Expected snapshot disk  : G\n")
+	} else {
+		fmt.Fprintf(buffer, "Expected snapshot disk  : C%d\n", basic.expSnapshotBottom)
+	}
+	return buffer.String()
+}
+
+func (basic *snapshotTestBasic) teardown() {
+	basic.db.Close()
+	basic.gendb.Close()
+	os.RemoveAll(basic.datadir)
+}
+
+// snapshotTest is a test case type for normal snapshot recovery.
+// It can be used for testing that restart Geth normally.
+type snapshotTest struct {
+	snapshotTestBasic
+}
+
+func (snaptest *snapshotTest) test(t *testing.T) {
+	// 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())
+	chain, blocks := snaptest.prepare(t)
+
+	// Restart the chain normally
+	chain.Stop()
+	newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	defer newchain.Stop()
+
+	snaptest.verify(t, newchain, blocks)
+}
+
+// crashSnapshotTest is a test case type for innormal snapshot recovery.
+// It can be used for testing that restart Geth after the crash.
+type crashSnapshotTest struct {
+	snapshotTestBasic
+}
+
+func (snaptest *crashSnapshotTest) test(t *testing.T) {
+	// 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())
+	chain, blocks := snaptest.prepare(t)
+
+	// Pull the plug on the database, simulating a hard crash
+	db := chain.db
+	db.Close()
+
+	// Start a new blockchain back up and see where the repair leads us
+	newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false)
+	if err != nil {
+		t.Fatalf("Failed to reopen persistent database: %v", err)
+	}
+	defer newdb.Close()
+
+	// The interesting thing is: instead of starting 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.
+	newchain, err := NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	newchain.Stop()
+
+	newchain, err = NewBlockChain(newdb, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	defer newchain.Stop()
+
+	snaptest.verify(t, newchain, blocks)
+}
+
+// gappedSnapshotTest is a test type used to test this scenario:
+// - have a complete snapshot
+// - restart without enabling the snapshot
+// - insert a few blocks
+// - restart with enabling the snapshot again
+type gappedSnapshotTest struct {
+	snapshotTestBasic
+	gapped int // Number of blocks to insert without enabling snapshot
+}
+
+func (snaptest *gappedSnapshotTest) test(t *testing.T) {
+	// 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())
+	chain, blocks := snaptest.prepare(t)
+
+	// Insert blocks without enabling snapshot if gapping is required.
+	chain.Stop()
+	gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.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,
+	}
+	newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	newchain.InsertChain(gappedBlocks)
+	newchain.Stop()
+
+	// Restart the chain with enabling the snapshot
+	newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	defer newchain.Stop()
+
+	snaptest.verify(t, newchain, blocks)
+}
+
+// setHeadSnapshotTest is the test type used to test this scenario:
+// - have a complete snapshot
+// - set the head to a lower point
+// - restart
+type setHeadSnapshotTest struct {
+	snapshotTestBasic
+	setHead uint64 // Block number to set head back to
+}
+
+func (snaptest *setHeadSnapshotTest) test(t *testing.T) {
+	// 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())
+	chain, blocks := snaptest.prepare(t)
+
+	// Rewind the chain if setHead operation is required.
+	chain.SetHead(snaptest.setHead)
+	chain.Stop()
+
+	newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	defer newchain.Stop()
+
+	snaptest.verify(t, newchain, blocks)
+}
+
+// restartCrashSnapshotTest is the test type used to test this scenario:
+// - have a complete snapshot
+// - restart chain
+// - insert more blocks with enabling the snapshot
+// - commit the snapshot
+// - crash
+// - restart again
+type restartCrashSnapshotTest struct {
+	snapshotTestBasic
+	newBlocks int
+}
+
+func (snaptest *restartCrashSnapshotTest) test(t *testing.T) {
+	// 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())
+	chain, blocks := snaptest.prepare(t)
+
+	// Firstly, stop the chain properly, with all snapshot journal
+	// and state committed.
+	chain.Stop()
+
+	newchain, err := NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {})
+	newchain.InsertChain(newBlocks)
+
+	// Commit the entire snapshot into the disk if requested. Note only
+	// (a) snapshot root and (b) snapshot generator will be committed,
+	// the diff journal is not.
+	newchain.Snapshots().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
+	newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	defer newchain.Stop()
+
+	snaptest.verify(t, newchain, blocks)
+}
+
+// wipeCrashSnapshotTest is the test type used to test this scenario:
+// - have a complete snapshot
+// - restart, insert more blocks without enabling the snapshot
+// - restart again with enabling the snapshot
+// - crash
+type wipeCrashSnapshotTest struct {
+	snapshotTestBasic
+	newBlocks int
+}
+
+func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) {
+	// 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())
+	chain, blocks := snaptest.prepare(t)
+
+	// Firstly, stop the chain properly, with all snapshot journal
+	// and state committed.
+	chain.Stop()
+
+	config := &CacheConfig{
+		TrieCleanLimit: 256,
+		TrieDirtyLimit: 256,
+		TrieTimeLimit:  5 * time.Minute,
+		SnapshotLimit:  0,
+	}
+	newchain, err := NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, func(i int, b *BlockGen) {})
+	newchain.InsertChain(newBlocks)
+	newchain.Stop()
+
+	// Restart the chain, the wiper should starts working
+	config = &CacheConfig{
+		TrieCleanLimit: 256,
+		TrieDirtyLimit: 256,
+		TrieTimeLimit:  5 * time.Minute,
+		SnapshotLimit:  256,
+		SnapshotWait:   false, // Don't wait rebuild
+	}
+	newchain, err = NewBlockChain(snaptest.db, config, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	// Simulate the blockchain crash.
+
+	newchain, err = NewBlockChain(snaptest.db, nil, params.AllEthashProtocolChanges, snaptest.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to recreate chain: %v", err)
+	}
+	snaptest.verify(t, newchain, blocks)
+}
+
+// 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
+	test := &snapshotTest{
+		snapshotTestBasic{
+			chainBlocks:        8,
+			snapshotBlock:      0,
+			commitBlock:        0,
+			expCanonicalBlocks: 8,
+			expHeadHeader:      8,
+			expHeadFastBlock:   8,
+			expHeadBlock:       8,
+			expSnapshotBottom:  0, // Initial disk layer built from genesis
+		},
+	}
+	test.test(t)
+	test.teardown()
+}
+
+// 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
+	test := &crashSnapshotTest{
+		snapshotTestBasic{
+			chainBlocks:        8,
+			snapshotBlock:      4,
+			commitBlock:        0,
+			expCanonicalBlocks: 8,
+			expHeadHeader:      8,
+			expHeadFastBlock:   8,
+			expHeadBlock:       0,
+			expSnapshotBottom:  4, // Last committed disk layer, wait recovery
+		},
+	}
+	test.test(t)
+	test.teardown()
+}
+
+// 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
+	test := &crashSnapshotTest{
+		snapshotTestBasic{
+			chainBlocks:        8,
+			snapshotBlock:      4,
+			commitBlock:        2,
+			expCanonicalBlocks: 8,
+			expHeadHeader:      8,
+			expHeadFastBlock:   8,
+			expHeadBlock:       2,
+			expSnapshotBottom:  4, // Last committed disk layer, wait recovery
+		},
+	}
+	test.test(t)
+	test.teardown()
+}
+
+// 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
+	test := &crashSnapshotTest{
+		snapshotTestBasic{
+			chainBlocks:        8,
+			snapshotBlock:      4,
+			commitBlock:        6,
+			expCanonicalBlocks: 8,
+			expHeadHeader:      8,
+			expHeadFastBlock:   8,
+			expHeadBlock:       0,
+			expSnapshotBottom:  4, // Last committed disk layer, wait recovery
+		},
+	}
+	test.test(t)
+	test.teardown()
+}
+
+// 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
+	test := &gappedSnapshotTest{
+		snapshotTestBasic: snapshotTestBasic{
+			chainBlocks:        8,
+			snapshotBlock:      0,
+			commitBlock:        0,
+			expCanonicalBlocks: 10,
+			expHeadHeader:      10,
+			expHeadFastBlock:   10,
+			expHeadBlock:       10,
+			expSnapshotBottom:  10, // Rebuilt snapshot from the latest HEAD
+		},
+		gapped: 2,
+	}
+	test.test(t)
+	test.teardown()
+}
+
+// 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
+	test := &setHeadSnapshotTest{
+		snapshotTestBasic: snapshotTestBasic{
+			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
+		},
+		setHead: 4,
+	}
+	test.test(t)
+	test.teardown()
+}
+
+// Tests the Geth was running with a complete snapshot and then imports a few
+// more new blocks on top without enabling the snapshot. After the restart,
+// crash happens. Check everything is ok after the restart.
+func TestRecoverSnapshotFromWipingCrash(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
+	test := &wipeCrashSnapshotTest{
+		snapshotTestBasic: snapshotTestBasic{
+			chainBlocks:        8,
+			snapshotBlock:      4,
+			commitBlock:        0,
+			expCanonicalBlocks: 10,
+			expHeadHeader:      10,
+			expHeadFastBlock:   10,
+			expHeadBlock:       10,
+			expSnapshotBottom:  10,
+		},
+		newBlocks: 2,
+	}
+	test.test(t)
+	test.teardown()
+}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 665d20ec9c..5004abd1c7 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -17,12 +17,12 @@
 package core
 
 import (
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"math/big"
 	"math/rand"
 	"os"
-	"reflect"
 	"sync"
 	"testing"
 	"time"
@@ -37,6 +37,7 @@ import (
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 // So we can deterministically seed different blockchains
@@ -55,7 +56,7 @@ func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *B
 	)
 
 	// Initialize a fresh chain with only a genesis block
-	blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}, nil, nil)
 	// Create and inject the requested chain
 	if n == 0 {
 		return db, blockchain, nil
@@ -468,7 +469,7 @@ func testBadHashes(t *testing.T, full bool) {
 
 		_, err = blockchain.InsertHeaderChain(headers, 1)
 	}
-	if err != ErrBlacklistedHash {
+	if !errors.Is(err, ErrBlacklistedHash) {
 		t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash)
 	}
 }
@@ -510,7 +511,7 @@ func testReorgBadHashes(t *testing.T, full bool) {
 	blockchain.Stop()
 
 	// Create a new BlockChain and check that it rolled back the state.
-	ncm, err := NewBlockChain(blockchain.db, nil, blockchain.chainConfig, ethash.NewFaker(), vm.Config{}, nil)
+	ncm, err := NewBlockChain(blockchain.db, nil, blockchain.chainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create new chain manager: %v", err)
 	}
@@ -599,7 +600,7 @@ func TestFastVsFullChains(t *testing.T) {
 			Alloc:  GenesisAlloc{address: {Balance: funds}},
 		}
 		genesis = gspec.MustCommit(gendb)
-		signer  = types.NewEIP155Signer(gspec.Config.ChainID)
+		signer  = types.LatestSigner(gspec.Config)
 	)
 	blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) {
 		block.SetCoinbase(common.Address{0x00})
@@ -622,7 +623,7 @@ func TestFastVsFullChains(t *testing.T) {
 	// Import the chain as an archive node for the comparison baseline
 	archiveDb := rawdb.NewMemoryDatabase()
 	gspec.MustCommit(archiveDb)
-	archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer archive.Stop()
 
 	if n, err := archive.InsertChain(blocks); err != nil {
@@ -631,7 +632,7 @@ func TestFastVsFullChains(t *testing.T) {
 	// Fast import the chain as a non-archive node to test
 	fastDb := rawdb.NewMemoryDatabase()
 	gspec.MustCommit(fastDb)
-	fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer fast.Stop()
 
 	headers := make([]*types.Header, len(blocks))
@@ -650,12 +651,12 @@ func TestFastVsFullChains(t *testing.T) {
 		t.Fatalf("failed to create temp freezer dir: %v", err)
 	}
 	defer os.Remove(frdir)
-	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "")
+	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
 	if err != nil {
 		t.Fatalf("failed to create temp freezer db: %v", err)
 	}
 	gspec.MustCommit(ancientDb)
-	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer ancient.Stop()
 
 	if n, err := ancient.InsertHeaderChain(headers, 1); err != nil {
@@ -682,12 +683,12 @@ func TestFastVsFullChains(t *testing.T) {
 		}
 		if fblock, arblock, anblock := fast.GetBlockByHash(hash), archive.GetBlockByHash(hash), ancient.GetBlockByHash(hash); fblock.Hash() != arblock.Hash() || anblock.Hash() != arblock.Hash() {
 			t.Errorf("block #%d [%x]: block mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock, anblock, arblock)
-		} else if types.DeriveSha(fblock.Transactions()) != types.DeriveSha(arblock.Transactions()) || types.DeriveSha(anblock.Transactions()) != types.DeriveSha(arblock.Transactions()) {
+		} else if types.DeriveSha(fblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) || types.DeriveSha(anblock.Transactions(), trie.NewStackTrie(nil)) != types.DeriveSha(arblock.Transactions(), trie.NewStackTrie(nil)) {
 			t.Errorf("block #%d [%x]: transactions mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Transactions(), anblock.Transactions(), arblock.Transactions())
 		} else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) || types.CalcUncleHash(anblock.Uncles()) != types.CalcUncleHash(arblock.Uncles()) {
 			t.Errorf("block #%d [%x]: uncles mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, fblock.Uncles(), anblock, arblock.Uncles())
 		}
-		if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
+		if freceipts, anreceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(ancientDb, hash, *rawdb.ReadHeaderNumber(ancientDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), fast.Config()); types.DeriveSha(freceipts, trie.NewStackTrie(nil)) != types.DeriveSha(areceipts, trie.NewStackTrie(nil)) {
 			t.Errorf("block #%d [%x]: receipts mismatch: fastdb %v, ancientdb %v, archivedb %v", num, hash, freceipts, anreceipts, areceipts)
 		}
 	}
@@ -724,7 +725,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
 			t.Fatalf("failed to create temp freezer dir: %v", err)
 		}
 		defer os.Remove(dir)
-		db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "")
+		db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false)
 		if err != nil {
 			t.Fatalf("failed to create temp freezer db: %v", err)
 		}
@@ -732,12 +733,12 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
 		return db, func() { os.RemoveAll(dir) }
 	}
 	// Configure a subchain to roll back
-	remove := []common.Hash{}
-	for _, block := range blocks[height/2:] {
-		remove = append(remove, block.Hash())
-	}
+	remove := blocks[height/2].NumberU64()
+
 	// Create a small assertion method to check the three heads
 	assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) {
+		t.Helper()
+
 		if num := chain.CurrentBlock().NumberU64(); num != block {
 			t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block)
 		}
@@ -751,20 +752,24 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
 	// Import the chain as an archive node and ensure all pointers are updated
 	archiveDb, delfn := makeDb()
 	defer delfn()
-	archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+
+	archiveCaching := *defaultCacheConfig
+	archiveCaching.TrieDirtyDisabled = true
+
+	archive, _ := NewBlockChain(archiveDb, &archiveCaching, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	if n, err := archive.InsertChain(blocks); err != nil {
 		t.Fatalf("failed to process block %d: %v", n, err)
 	}
 	defer archive.Stop()
 
 	assert(t, "archive", archive, height, height, height)
-	archive.Rollback(remove)
+	archive.SetHead(remove - 1)
 	assert(t, "archive", archive, height/2, height/2, height/2)
 
 	// Import the chain as a non-archive node and ensure all pointers are updated
 	fastDb, delfn := makeDb()
 	defer delfn()
-	fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer fast.Stop()
 
 	headers := make([]*types.Header, len(blocks))
@@ -778,13 +783,13 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
 		t.Fatalf("failed to insert receipt %d: %v", n, err)
 	}
 	assert(t, "fast", fast, height, height, 0)
-	fast.Rollback(remove)
+	fast.SetHead(remove - 1)
 	assert(t, "fast", fast, height/2, height/2, 0)
 
 	// Import the chain as a ancient-first node and ensure all pointers are updated
 	ancientDb, delfn := makeDb()
 	defer delfn()
-	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer ancient.Stop()
 
 	if n, err := ancient.InsertHeaderChain(headers, 1); err != nil {
@@ -794,23 +799,23 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
 		t.Fatalf("failed to insert receipt %d: %v", n, err)
 	}
 	assert(t, "ancient", ancient, height, height, 0)
-	ancient.Rollback(remove)
-	assert(t, "ancient", ancient, height/2, height/2, 0)
-	if frozen, err := ancientDb.Ancients(); err != nil || frozen != height/2+1 {
-		t.Fatalf("failed to truncate ancient store, want %v, have %v", height/2+1, frozen)
-	}
+	ancient.SetHead(remove - 1)
+	assert(t, "ancient", ancient, 0, 0, 0)
 
+	if frozen, err := ancientDb.Ancients(); err != nil || frozen != 1 {
+		t.Fatalf("failed to truncate ancient store, want %v, have %v", 1, frozen)
+	}
 	// Import the chain as a light node and ensure all pointers are updated
 	lightDb, delfn := makeDb()
 	defer delfn()
-	light, _ := NewBlockChain(lightDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	light, _ := NewBlockChain(lightDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	if n, err := light.InsertHeaderChain(headers, 1); err != nil {
 		t.Fatalf("failed to insert header %d: %v", n, err)
 	}
 	defer light.Stop()
 
 	assert(t, "light", light, height, 0, 0)
-	light.Rollback(remove)
+	light.SetHead(remove - 1)
 	assert(t, "light", light, height/2, 0, 0)
 }
 
@@ -834,7 +839,7 @@ func TestChainTxReorgs(t *testing.T) {
 			},
 		}
 		genesis = gspec.MustCommit(db)
-		signer  = types.NewEIP155Signer(gspec.Config.ChainID)
+		signer  = types.LatestSigner(gspec.Config)
 	)
 
 	// Create two transactions shared between the chains:
@@ -872,7 +877,7 @@ func TestChainTxReorgs(t *testing.T) {
 		}
 	})
 	// Import the chain. This runs all block validation rules.
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	if i, err := blockchain.InsertChain(chain); err != nil {
 		t.Fatalf("failed to insert original chain[%d]: %v", i, err)
 	}
@@ -939,10 +944,10 @@ func TestLogReorgs(t *testing.T) {
 		code    = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
 		gspec   = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
 		genesis = gspec.MustCommit(db)
-		signer  = types.NewEIP155Signer(gspec.Config.ChainID)
+		signer  = types.LatestSigner(gspec.Config)
 	)
 
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer blockchain.Stop()
 
 	rmLogsCh := make(chan RemovedLogsEvent)
@@ -981,189 +986,108 @@ func TestLogReorgs(t *testing.T) {
 	}
 }
 
+// This EVM code generates a log when the contract is created.
+var logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
+
+// This test checks that log events and RemovedLogsEvent are sent
+// when the chain reorganizes.
 func TestLogRebirth(t *testing.T) {
 	var (
-		key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-		addr1   = crypto.PubkeyToAddress(key1.PublicKey)
-		db      = rawdb.NewMemoryDatabase()
-
-		// this code generates a log
-		code        = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
-		gspec       = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
-		genesis     = gspec.MustCommit(db)
-		signer      = types.NewEIP155Signer(gspec.Config.ChainID)
-		newLogCh    = make(chan bool)
-		removeLogCh = make(chan bool)
+		key1, _       = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		addr1         = crypto.PubkeyToAddress(key1.PublicKey)
+		db            = rawdb.NewMemoryDatabase()
+		gspec         = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
+		genesis       = gspec.MustCommit(db)
+		signer        = types.LatestSigner(gspec.Config)
+		engine        = ethash.NewFaker()
+		blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil)
 	)
 
-	// validateLogEvent checks whether the received logs number is equal with expected.
-	validateLogEvent := func(sink interface{}, result chan bool, expect int) {
-		chanval := reflect.ValueOf(sink)
-		chantyp := chanval.Type()
-		if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 {
-			t.Fatalf("invalid channel, given type %v", chantyp)
-		}
-		cnt := 0
-		var recv []reflect.Value
-		timeout := time.After(1 * time.Second)
-		cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}}
-		for {
-			chose, v, _ := reflect.Select(cases)
-			if chose == 1 {
-				// Not enough event received
-				result <- false
-				return
-			}
-			cnt += 1
-			recv = append(recv, v)
-			if cnt == expect {
-				break
-			}
-		}
-		done := time.After(50 * time.Millisecond)
-		cases = cases[:1]
-		cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv})
-		chose, _, _ := reflect.Select(cases)
-		// If chose equal 0, it means receiving redundant events.
-		if chose == 1 {
-			result <- true
-		} else {
-			result <- false
-		}
-	}
-
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
 	defer blockchain.Stop()
 
-	logsCh := make(chan []*types.Log)
-	blockchain.SubscribeLogsEvent(logsCh)
-
-	rmLogsCh := make(chan RemovedLogsEvent)
+	// The event channels.
+	newLogCh := make(chan []*types.Log, 10)
+	rmLogsCh := make(chan RemovedLogsEvent, 10)
+	blockchain.SubscribeLogsEvent(newLogCh)
 	blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
 
-	chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
+	// This chain contains a single log.
+	chain, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2, func(i int, gen *BlockGen) {
 		if i == 1 {
-			tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1)
+			tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), logCode), signer, key1)
 			if err != nil {
 				t.Fatalf("failed to create tx: %v", err)
 			}
 			gen.AddTx(tx)
 		}
 	})
-
-	// Spawn a goroutine to receive log events
-	go validateLogEvent(logsCh, newLogCh, 1)
 	if _, err := blockchain.InsertChain(chain); err != nil {
 		t.Fatalf("failed to insert chain: %v", err)
 	}
-	if !<-newLogCh {
-		t.Fatal("failed to receive new log event")
-	}
+	checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
 
-	// Generate long reorg chain
-	forkChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
+	// Generate long reorg chain containing another log. Inserting the
+	// chain removes one log and adds one.
+	forkChain, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2, func(i int, gen *BlockGen) {
 		if i == 1 {
-			tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1)
+			tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), logCode), signer, key1)
 			if err != nil {
 				t.Fatalf("failed to create tx: %v", err)
 			}
 			gen.AddTx(tx)
-			// Higher block difficulty
-			gen.OffsetTime(-9)
+			gen.OffsetTime(-9) // higher block difficulty
 		}
 	})
-
-	// Spawn a goroutine to receive log events
-	go validateLogEvent(logsCh, newLogCh, 1)
-	go validateLogEvent(rmLogsCh, removeLogCh, 1)
 	if _, err := blockchain.InsertChain(forkChain); err != nil {
 		t.Fatalf("failed to insert forked chain: %v", err)
 	}
-	if !<-newLogCh {
-		t.Fatal("failed to receive new log event")
-	}
-	if !<-removeLogCh {
-		t.Fatal("failed to receive removed log event")
-	}
+	checkLogEvents(t, newLogCh, rmLogsCh, 1, 1)
 
-	newBlocks, _ := GenerateChain(params.TestChainConfig, chain[len(chain)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
-	go validateLogEvent(logsCh, newLogCh, 1)
-	go validateLogEvent(rmLogsCh, removeLogCh, 1)
+	// This chain segment is rooted in the original chain, but doesn't contain any logs.
+	// When inserting it, the canonical chain switches away from forkChain and re-emits
+	// the log event for the old chain, as well as a RemovedLogsEvent for forkChain.
+	newBlocks, _ := GenerateChain(params.TestChainConfig, chain[len(chain)-1], engine, db, 1, func(i int, gen *BlockGen) {})
 	if _, err := blockchain.InsertChain(newBlocks); err != nil {
 		t.Fatalf("failed to insert forked chain: %v", err)
 	}
-	// Rebirth logs should omit a newLogEvent
-	if !<-newLogCh {
-		t.Fatal("failed to receive new log event")
-	}
-	// Ensure removedLog events received
-	if !<-removeLogCh {
-		t.Fatal("failed to receive removed log event")
-	}
+	checkLogEvents(t, newLogCh, rmLogsCh, 1, 1)
 }
 
+// This test is a variation of TestLogRebirth. It verifies that log events are emitted
+// when a side chain containing log events overtakes the canonical chain.
 func TestSideLogRebirth(t *testing.T) {
 	var (
-		key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-		addr1   = crypto.PubkeyToAddress(key1.PublicKey)
-		db      = rawdb.NewMemoryDatabase()
-
-		// this code generates a log
-		code     = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
-		gspec    = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
-		genesis  = gspec.MustCommit(db)
-		signer   = types.NewEIP155Signer(gspec.Config.ChainID)
-		newLogCh = make(chan bool)
+		key1, _       = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		addr1         = crypto.PubkeyToAddress(key1.PublicKey)
+		db            = rawdb.NewMemoryDatabase()
+		gspec         = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
+		genesis       = gspec.MustCommit(db)
+		signer        = types.LatestSigner(gspec.Config)
+		blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	)
 
-	// listenNewLog checks whether the received logs number is equal with expected.
-	listenNewLog := func(sink chan []*types.Log, expect int) {
-		cnt := 0
-		for {
-			select {
-			case logs := <-sink:
-				cnt += len(logs)
-			case <-time.NewTimer(5 * time.Second).C:
-				// new logs timeout
-				newLogCh <- false
-				return
-			}
-			if cnt == expect {
-				break
-			} else if cnt > expect {
-				// redundant logs received
-				newLogCh <- false
-				return
-			}
-		}
-		select {
-		case <-sink:
-			// redundant logs received
-			newLogCh <- false
-		case <-time.NewTimer(100 * time.Millisecond).C:
-			newLogCh <- true
-		}
-	}
-
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
 	defer blockchain.Stop()
 
-	logsCh := make(chan []*types.Log)
-	blockchain.SubscribeLogsEvent(logsCh)
+	newLogCh := make(chan []*types.Log, 10)
+	rmLogsCh := make(chan RemovedLogsEvent, 10)
+	blockchain.SubscribeLogsEvent(newLogCh)
+	blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
+
 	chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
 		if i == 1 {
-			// Higher block difficulty
-			gen.OffsetTime(-9)
+			gen.OffsetTime(-9) // higher block difficulty
+
 		}
 	})
 	if _, err := blockchain.InsertChain(chain); err != nil {
 		t.Fatalf("failed to insert forked chain: %v", err)
 	}
+	checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
 
 	// Generate side chain with lower difficulty
 	sideChain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
 		if i == 1 {
-			tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), code), signer, key1)
+			tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, new(big.Int), logCode), signer, key1)
 			if err != nil {
 				t.Fatalf("failed to create tx: %v", err)
 			}
@@ -1173,16 +1097,31 @@ func TestSideLogRebirth(t *testing.T) {
 	if _, err := blockchain.InsertChain(sideChain); err != nil {
 		t.Fatalf("failed to insert forked chain: %v", err)
 	}
+	checkLogEvents(t, newLogCh, rmLogsCh, 0, 0)
 
-	// Generate a new block based on side chain
+	// Generate a new block based on side chain.
 	newBlocks, _ := GenerateChain(params.TestChainConfig, sideChain[len(sideChain)-1], ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
-	go listenNewLog(logsCh, 1)
 	if _, err := blockchain.InsertChain(newBlocks); err != nil {
 		t.Fatalf("failed to insert forked chain: %v", err)
 	}
-	// Rebirth logs should omit a newLogEvent
-	if !<-newLogCh {
-		t.Fatalf("failed to receive new log event")
+	checkLogEvents(t, newLogCh, rmLogsCh, 1, 0)
+}
+
+func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan RemovedLogsEvent, wantNew, wantRemoved int) {
+	t.Helper()
+
+	if len(logsCh) != wantNew {
+		t.Fatalf("wrong number of log events: got %d, want %d", len(logsCh), wantNew)
+	}
+	if len(rmLogsCh) != wantRemoved {
+		t.Fatalf("wrong number of removed log events: got %d, want %d", len(rmLogsCh), wantRemoved)
+	}
+	// Drain events.
+	for i := 0; i < len(logsCh); i++ {
+		<-logsCh
+	}
+	for i := 0; i < len(rmLogsCh); i++ {
+		<-rmLogsCh
 	}
 }
 
@@ -1196,10 +1135,10 @@ func TestReorgSideEvent(t *testing.T) {
 			Alloc:  GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}},
 		}
 		genesis = gspec.MustCommit(db)
-		signer  = types.NewEIP155Signer(gspec.Config.ChainID)
+		signer  = types.LatestSigner(gspec.Config)
 	)
 
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer blockchain.Stop()
 
 	chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
@@ -1331,7 +1270,7 @@ func TestEIP155Transition(t *testing.T) {
 		genesis = gspec.MustCommit(db)
 	)
 
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer blockchain.Stop()
 
 	blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
@@ -1356,7 +1295,7 @@ func TestEIP155Transition(t *testing.T) {
 			}
 			block.AddTx(tx)
 
-			tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID))
+			tx, err = basicTx(types.LatestSigner(gspec.Config))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -1368,7 +1307,7 @@ func TestEIP155Transition(t *testing.T) {
 			}
 			block.AddTx(tx)
 
-			tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID))
+			tx, err = basicTx(types.LatestSigner(gspec.Config))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -1406,7 +1345,7 @@ func TestEIP155Transition(t *testing.T) {
 			}
 		)
 		if i == 0 {
-			tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2)))
+			tx, err = basicTx(types.LatestSigner(config))
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -1439,14 +1378,14 @@ func TestEIP161AccountRemoval(t *testing.T) {
 		}
 		genesis = gspec.MustCommit(db)
 	)
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer blockchain.Stop()
 
 	blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, block *BlockGen) {
 		var (
 			tx     *types.Transaction
 			err    error
-			signer = types.NewEIP155Signer(gspec.Config.ChainID)
+			signer = types.LatestSigner(gspec.Config)
 		)
 		switch i {
 		case 0:
@@ -1477,7 +1416,7 @@ func TestEIP161AccountRemoval(t *testing.T) {
 		t.Error("account should not exist")
 	}
 
-	// account musn't be created post eip 161
+	// account mustn't be created post eip 161
 	if _, err := blockchain.InsertChain(types.Blocks{blocks[2]}); err != nil {
 		t.Fatal(err)
 	}
@@ -1514,7 +1453,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) {
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
 
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -1523,13 +1462,13 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) {
 			t.Fatalf("block %d: failed to insert into chain: %v", i, err)
 		}
 		if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() {
-			t.Errorf("block %d: current block/header mismatch: block #%d [%x…], header #%d [%x…]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4])
+			t.Errorf("block %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4])
 		}
 		if _, err := chain.InsertChain(forks[i : i+1]); err != nil {
 			t.Fatalf(" fork %d: failed to insert into chain: %v", i, err)
 		}
 		if chain.CurrentBlock().Hash() != chain.CurrentHeader().Hash() {
-			t.Errorf(" fork %d: current block/header mismatch: block #%d [%x…], header #%d [%x…]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4])
+			t.Errorf(" fork %d: current block/header mismatch: block #%d [%x..], header #%d [%x..]", i, chain.CurrentBlock().Number(), chain.CurrentBlock().Hash().Bytes()[:4], chain.CurrentHeader().Number, chain.CurrentHeader().Hash().Bytes()[:4])
 		}
 	}
 }
@@ -1558,7 +1497,7 @@ func TestTrieForkGC(t *testing.T) {
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
 
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -1597,7 +1536,7 @@ func TestLargeReorgTrieGC(t *testing.T) {
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
 
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -1652,12 +1591,13 @@ func TestBlockchainRecovery(t *testing.T) {
 		t.Fatalf("failed to create temp freezer dir: %v", err)
 	}
 	defer os.Remove(frdir)
-	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "")
+
+	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
 	if err != nil {
 		t.Fatalf("failed to create temp freezer db: %v", err)
 	}
 	gspec.MustCommit(ancientDb)
-	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 
 	headers := make([]*types.Header, len(blocks))
 	for i, block := range blocks {
@@ -1669,6 +1609,7 @@ func TestBlockchainRecovery(t *testing.T) {
 	if n, err := ancient.InsertReceiptChain(blocks, receipts, uint64(3*len(blocks)/4)); err != nil {
 		t.Fatalf("failed to insert receipt %d: %v", n, err)
 	}
+	rawdb.WriteLastPivotNumber(ancientDb, blocks[len(blocks)-1].NumberU64()) // Force fast sync behavior
 	ancient.Stop()
 
 	// Destroy head fast block manually
@@ -1676,7 +1617,7 @@ func TestBlockchainRecovery(t *testing.T) {
 	rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash())
 
 	// Reopen broken blockchain again
-	ancient, _ = NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	ancient, _ = NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer ancient.Stop()
 	if num := ancient.CurrentBlock().NumberU64(); num != 0 {
 		t.Errorf("head block mismatch: have #%v, want #%v", num, 0)
@@ -1708,12 +1649,12 @@ func TestIncompleteAncientReceiptChainInsertion(t *testing.T) {
 		t.Fatalf("failed to create temp freezer dir: %v", err)
 	}
 	defer os.Remove(frdir)
-	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "")
+	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
 	if err != nil {
 		t.Fatalf("failed to create temp freezer db: %v", err)
 	}
 	gspec.MustCommit(ancientDb)
-	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	ancient, _ := NewBlockChain(ancientDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer ancient.Stop()
 
 	headers := make([]*types.Header, len(blocks))
@@ -1770,7 +1711,7 @@ func TestLowDiffLongChain(t *testing.T) {
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
 
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -1817,7 +1758,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
 	blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, nil)
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -1907,14 +1848,14 @@ func testInsertKnownChainData(t *testing.T, typ string) {
 		t.Fatalf("failed to create temp freezer dir: %v", err)
 	}
 	defer os.Remove(dir)
-	chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "")
+	chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false)
 	if err != nil {
 		t.Fatalf("failed to create temp freezer db: %v", err)
 	}
 	new(Genesis).MustCommit(chaindb)
 	defer os.RemoveAll(dir)
 
-	chain, err := NewBlockChain(chaindb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(chaindb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -1979,11 +1920,9 @@ func testInsertKnownChainData(t *testing.T, typ string) {
 	asserter(t, blocks[len(blocks)-1])
 
 	// Import a long canonical chain with some known data as prefix.
-	var rollback []common.Hash
-	for i := len(blocks) / 2; i < len(blocks); i++ {
-		rollback = append(rollback, blocks[i].Hash())
-	}
-	chain.Rollback(rollback)
+	rollback := blocks[len(blocks)/2].NumberU64()
+
+	chain.SetHead(rollback - 1)
 	if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
 		t.Fatalf("failed to insert chain data: %v", err)
 	}
@@ -2003,11 +1942,7 @@ func testInsertKnownChainData(t *testing.T, typ string) {
 	asserter(t, blocks3[len(blocks3)-1])
 
 	// Rollback the heavier chain and re-insert the longer chain again
-	for i := 0; i < len(blocks3); i++ {
-		rollback = append(rollback, blocks3[i].Hash())
-	}
-	chain.Rollback(rollback)
-
+	chain.SetHead(rollback - 1)
 	if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
 		t.Fatalf("failed to insert chain data: %v", err)
 	}
@@ -2030,7 +1965,7 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, error
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
 
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		return nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err)
 	}
@@ -2134,6 +2069,219 @@ func TestReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T) {
 	}
 }
 
+func TestTransactionIndices(t *testing.T) {
+	// Configure and generate a sample block chain
+	var (
+		gendb   = rawdb.NewMemoryDatabase()
+		key, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		address = crypto.PubkeyToAddress(key.PublicKey)
+		funds   = big.NewInt(1000000000)
+		gspec   = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}}
+		genesis = gspec.MustCommit(gendb)
+		signer  = types.LatestSigner(gspec.Config)
+	)
+	height := uint64(128)
+	blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), func(i int, block *BlockGen) {
+		tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil), signer, key)
+		if err != nil {
+			panic(err)
+		}
+		block.AddTx(tx)
+	})
+	blocks2, _ := GenerateChain(gspec.Config, blocks[len(blocks)-1], ethash.NewFaker(), gendb, 10, nil)
+
+	check := func(tail *uint64, chain *BlockChain) {
+		stored := rawdb.ReadTxIndexTail(chain.db)
+		if tail == nil && stored != nil {
+			t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored)
+		}
+		if tail != nil && *stored != *tail {
+			t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored)
+		}
+		if tail != nil {
+			for i := *tail; i <= chain.CurrentBlock().NumberU64(); i++ {
+				block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i)
+				if block.Transactions().Len() == 0 {
+					continue
+				}
+				for _, tx := range block.Transactions() {
+					if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil {
+						t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex())
+					}
+				}
+			}
+			for i := uint64(0); i < *tail; i++ {
+				block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i)
+				if block.Transactions().Len() == 0 {
+					continue
+				}
+				for _, tx := range block.Transactions() {
+					if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil {
+						t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex())
+					}
+				}
+			}
+		}
+	}
+	frdir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatalf("failed to create temp freezer dir: %v", err)
+	}
+	defer os.Remove(frdir)
+	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
+	if err != nil {
+		t.Fatalf("failed to create temp freezer db: %v", err)
+	}
+	gspec.MustCommit(ancientDb)
+
+	// Import all blocks into ancient db
+	l := uint64(0)
+	chain, err := NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l)
+	if err != nil {
+		t.Fatalf("failed to create tester chain: %v", err)
+	}
+	headers := make([]*types.Header, len(blocks))
+	for i, block := range blocks {
+		headers[i] = block.Header()
+	}
+	if n, err := chain.InsertHeaderChain(headers, 0); err != nil {
+		t.Fatalf("failed to insert header %d: %v", n, err)
+	}
+	if n, err := chain.InsertReceiptChain(blocks, receipts, 128); err != nil {
+		t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+	}
+	chain.Stop()
+	ancientDb.Close()
+
+	// Init block chain with external ancients, check all needed indices has been indexed.
+	limit := []uint64{0, 32, 64, 128}
+	for _, l := range limit {
+		ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
+		if err != nil {
+			t.Fatalf("failed to create temp freezer db: %v", err)
+		}
+		gspec.MustCommit(ancientDb)
+		chain, err = NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l)
+		if err != nil {
+			t.Fatalf("failed to create tester chain: %v", err)
+		}
+		time.Sleep(50 * time.Millisecond) // Wait for indices initialisation
+		var tail uint64
+		if l != 0 {
+			tail = uint64(128) - l + 1
+		}
+		check(&tail, chain)
+		chain.Stop()
+		ancientDb.Close()
+	}
+
+	// Reconstruct a block chain which only reserves HEAD-64 tx indices
+	ancientDb, err = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
+	if err != nil {
+		t.Fatalf("failed to create temp freezer db: %v", err)
+	}
+	gspec.MustCommit(ancientDb)
+
+	limit = []uint64{0, 64 /* drop stale */, 32 /* shorten history */, 64 /* extend history */, 0 /* restore all */}
+	tails := []uint64{0, 67 /* 130 - 64 + 1 */, 100 /* 131 - 32 + 1 */, 69 /* 132 - 64 + 1 */, 0}
+	for i, l := range limit {
+		chain, err = NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l)
+		if err != nil {
+			t.Fatalf("failed to create tester chain: %v", err)
+		}
+		chain.InsertChain(blocks2[i : i+1]) // Feed chain a higher block to trigger indices updater.
+		time.Sleep(50 * time.Millisecond)   // Wait for indices initialisation
+		check(&tails[i], chain)
+		chain.Stop()
+	}
+}
+
+func TestSkipStaleTxIndicesInFastSync(t *testing.T) {
+	// Configure and generate a sample block chain
+	var (
+		gendb   = rawdb.NewMemoryDatabase()
+		key, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		address = crypto.PubkeyToAddress(key.PublicKey)
+		funds   = big.NewInt(1000000000)
+		gspec   = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}}
+		genesis = gspec.MustCommit(gendb)
+		signer  = types.LatestSigner(gspec.Config)
+	)
+	height := uint64(128)
+	blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), func(i int, block *BlockGen) {
+		tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil), signer, key)
+		if err != nil {
+			panic(err)
+		}
+		block.AddTx(tx)
+	})
+
+	check := func(tail *uint64, chain *BlockChain) {
+		stored := rawdb.ReadTxIndexTail(chain.db)
+		if tail == nil && stored != nil {
+			t.Fatalf("Oldest indexded block mismatch, want nil, have %d", *stored)
+		}
+		if tail != nil && *stored != *tail {
+			t.Fatalf("Oldest indexded block mismatch, want %d, have %d", *tail, *stored)
+		}
+		if tail != nil {
+			for i := *tail; i <= chain.CurrentBlock().NumberU64(); i++ {
+				block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i)
+				if block.Transactions().Len() == 0 {
+					continue
+				}
+				for _, tx := range block.Transactions() {
+					if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index == nil {
+						t.Fatalf("Miss transaction indice, number %d hash %s", i, tx.Hash().Hex())
+					}
+				}
+			}
+			for i := uint64(0); i < *tail; i++ {
+				block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i)
+				if block.Transactions().Len() == 0 {
+					continue
+				}
+				for _, tx := range block.Transactions() {
+					if index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()); index != nil {
+						t.Fatalf("Transaction indice should be deleted, number %d hash %s", i, tx.Hash().Hex())
+					}
+				}
+			}
+		}
+	}
+
+	frdir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatalf("failed to create temp freezer dir: %v", err)
+	}
+	defer os.Remove(frdir)
+	ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
+	if err != nil {
+		t.Fatalf("failed to create temp freezer db: %v", err)
+	}
+	gspec.MustCommit(ancientDb)
+
+	// Import all blocks into ancient db, only HEAD-32 indices are kept.
+	l := uint64(32)
+	chain, err := NewBlockChain(ancientDb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, &l)
+	if err != nil {
+		t.Fatalf("failed to create tester chain: %v", err)
+	}
+	headers := make([]*types.Header, len(blocks))
+	for i, block := range blocks {
+		headers[i] = block.Header()
+	}
+	if n, err := chain.InsertHeaderChain(headers, 0); err != nil {
+		t.Fatalf("failed to insert header %d: %v", n, err)
+	}
+	// The indices before ancient-N(32) should be ignored. After that all blocks should be indexed.
+	if n, err := chain.InsertReceiptChain(blocks, receipts, 64); err != nil {
+		t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+	}
+	tail := uint64(32)
+	check(&tail, chain)
+}
+
 // Benchmarks large blocks with value transfers to non-existing accounts
 func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) {
 	var (
@@ -2179,7 +2327,7 @@ func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks in
 		diskdb := rawdb.NewMemoryDatabase()
 		gspec.MustCommit(diskdb)
 
-		chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+		chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 		if err != nil {
 			b.Fatalf("failed to create tester chain: %v", err)
 		}
@@ -2261,7 +2409,7 @@ func TestSideImportPrunedBlocks(t *testing.T) {
 	blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, nil)
 	diskdb := rawdb.NewMemoryDatabase()
 	new(Genesis).MustCommit(diskdb)
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -2355,7 +2503,7 @@ func TestDeleteCreateRevert(t *testing.T) {
 	diskdb := rawdb.NewMemoryDatabase()
 	gspec.MustCommit(diskdb)
 
-	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil)
+	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -2469,7 +2617,7 @@ func TestDeleteRecreateSlots(t *testing.T) {
 	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
 		Debug:  true,
 		Tracer: vm.NewJSONLogger(nil, os.Stdout),
-	}, nil)
+	}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -2549,7 +2697,7 @@ func TestDeleteRecreateAccount(t *testing.T) {
 	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
 		Debug:  true,
 		Tracer: vm.NewJSONLogger(nil, os.Stdout),
-	}, nil)
+	}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -2722,7 +2870,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) {
 	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
 		//Debug:  true,
 		//Tracer: vm.NewJSONLogger(nil, os.Stdout),
-	}, nil)
+	}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -2856,7 +3004,7 @@ func TestInitThenFailCreateContract(t *testing.T) {
 	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
 		//Debug:  true,
 		//Tracer: vm.NewJSONLogger(nil, os.Stdout),
-	}, nil)
+	}, nil, nil)
 	if err != nil {
 		t.Fatalf("failed to create tester chain: %v", err)
 	}
@@ -2882,3 +3030,81 @@ func TestInitThenFailCreateContract(t *testing.T) {
 		}
 	}
 }
+
+// TestEIP2718Transition tests that an EIP-2718 transaction will be accepted
+// after the fork block has passed. This is verified by sending an EIP-2930
+// access list transaction, which specifies a single slot access, and then
+// checking that the gas usage of a hot SLOAD and a cold SLOAD are calculated
+// correctly.
+func TestEIP2718Transition(t *testing.T) {
+	var (
+		aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
+
+		// Generate a canonical chain to act as the main dataset
+		engine = ethash.NewFaker()
+		db     = rawdb.NewMemoryDatabase()
+
+		// A sender who makes transactions, has some funds
+		key, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		address = crypto.PubkeyToAddress(key.PublicKey)
+		funds   = big.NewInt(1000000000)
+		gspec   = &Genesis{
+			Config: params.YoloV3ChainConfig,
+			Alloc: GenesisAlloc{
+				address: {Balance: funds},
+				// The address 0xAAAA sloads 0x00 and 0x01
+				aa: {
+					Code: []byte{
+						byte(vm.PC),
+						byte(vm.PC),
+						byte(vm.SLOAD),
+						byte(vm.SLOAD),
+					},
+					Nonce:   0,
+					Balance: big.NewInt(0),
+				},
+			},
+		}
+		genesis = gspec.MustCommit(db)
+	)
+
+	blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) {
+		b.SetCoinbase(common.Address{1})
+
+		// One transaction to 0xAAAA
+		signer := types.LatestSigner(gspec.Config)
+		tx, _ := types.SignNewTx(key, signer, &types.AccessListTx{
+			ChainID:  gspec.Config.ChainID,
+			Nonce:    0,
+			To:       &aa,
+			Gas:      30000,
+			GasPrice: big.NewInt(1),
+			AccessList: types.AccessList{{
+				Address:     aa,
+				StorageKeys: []common.Hash{{0}},
+			}},
+		})
+		b.AddTx(tx)
+	})
+
+	// Import the canonical chain
+	diskdb := rawdb.NewMemoryDatabase()
+	gspec.MustCommit(diskdb)
+
+	chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("failed to create tester chain: %v", err)
+	}
+	if n, err := chain.InsertChain(blocks); err != nil {
+		t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+	}
+
+	block := chain.GetBlockByNumber(1)
+
+	// Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list
+	expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas + vm.GasQuickStep*2 + vm.WarmStorageReadCostEIP2929 + vm.ColdSloadCostEIP2929
+	if block.GasUsed() != expected {
+		t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed())
+
+	}
+}
diff --git a/core/bloom_indexer.go b/core/bloom_indexer.go
new file mode 100644
index 0000000000..856746a1c0
--- /dev/null
+++ b/core/bloom_indexer.go
@@ -0,0 +1,92 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package core
+
+import (
+	"context"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/bitutil"
+	"github.com/ethereum/go-ethereum/core/bloombits"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/ethdb"
+)
+
+const (
+	// bloomThrottling is the time to wait between processing two consecutive index
+	// sections. It's useful during chain upgrades to prevent disk overload.
+	bloomThrottling = 100 * time.Millisecond
+)
+
+// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index
+// for the Ethereum header bloom filters, permitting blazing fast filtering.
+type BloomIndexer struct {
+	size    uint64               // section size to generate bloombits for
+	db      ethdb.Database       // database instance to write index data and metadata into
+	gen     *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
+	section uint64               // Section is the section number being processed currently
+	head    common.Hash          // Head is the hash of the last header processed
+}
+
+// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
+// canonical chain for fast logs filtering.
+func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *ChainIndexer {
+	backend := &BloomIndexer{
+		db:   db,
+		size: size,
+	}
+	table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
+
+	return NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits")
+}
+
+// Reset implements core.ChainIndexerBackend, starting a new bloombits index
+// section.
+func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
+	gen, err := bloombits.NewGenerator(uint(b.size))
+	b.gen, b.section, b.head = gen, section, common.Hash{}
+	return err
+}
+
+// Process implements core.ChainIndexerBackend, adding a new header's bloom into
+// the index.
+func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error {
+	b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom)
+	b.head = header.Hash()
+	return nil
+}
+
+// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
+// writing it out into the database.
+func (b *BloomIndexer) Commit() error {
+	batch := b.db.NewBatch()
+	for i := 0; i < types.BloomBitLength; i++ {
+		bits, err := b.gen.Bitset(uint(i))
+		if err != nil {
+			return err
+		}
+		rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits))
+	}
+	return batch.Write()
+}
+
+// Prune returns an empty error since we don't support pruning here.
+func (b *BloomIndexer) Prune(threshold uint64) error {
+	return nil
+}
diff --git a/core/bloombits/generator.go b/core/bloombits/generator.go
index ae07481ada..646151db0b 100644
--- a/core/bloombits/generator.go
+++ b/core/bloombits/generator.go
@@ -65,18 +65,23 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error {
 	}
 	// Rotate the bloom and insert into our collection
 	byteIndex := b.nextSec / 8
-	bitMask := byte(1) << byte(7-b.nextSec%8)
-
-	for i := 0; i < types.BloomBitLength; i++ {
-		bloomByteIndex := types.BloomByteLength - 1 - i/8
-		bloomBitMask := byte(1) << byte(i%8)
-
-		if (bloom[bloomByteIndex] & bloomBitMask) != 0 {
-			b.blooms[i][byteIndex] |= bitMask
+	bitIndex := byte(7 - b.nextSec%8)
+	for byt := 0; byt < types.BloomByteLength; byt++ {
+		bloomByte := bloom[types.BloomByteLength-1-byt]
+		if bloomByte == 0 {
+			continue
 		}
+		base := 8 * byt
+		b.blooms[base+7][byteIndex] |= ((bloomByte >> 7) & 1) << bitIndex
+		b.blooms[base+6][byteIndex] |= ((bloomByte >> 6) & 1) << bitIndex
+		b.blooms[base+5][byteIndex] |= ((bloomByte >> 5) & 1) << bitIndex
+		b.blooms[base+4][byteIndex] |= ((bloomByte >> 4) & 1) << bitIndex
+		b.blooms[base+3][byteIndex] |= ((bloomByte >> 3) & 1) << bitIndex
+		b.blooms[base+2][byteIndex] |= ((bloomByte >> 2) & 1) << bitIndex
+		b.blooms[base+1][byteIndex] |= ((bloomByte >> 1) & 1) << bitIndex
+		b.blooms[base][byteIndex] |= (bloomByte & 1) << bitIndex
 	}
 	b.nextSec++
-
 	return nil
 }
 
diff --git a/core/bloombits/generator_test.go b/core/bloombits/generator_test.go
index f9bcef96e0..88e3ed6b06 100644
--- a/core/bloombits/generator_test.go
+++ b/core/bloombits/generator_test.go
@@ -58,3 +58,42 @@ func TestGenerator(t *testing.T) {
 		}
 	}
 }
+
+func BenchmarkGenerator(b *testing.B) {
+	var input [types.BloomBitLength][types.BloomByteLength]byte
+	b.Run("empty", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			// Crunch the input through the generator and verify the result
+			gen, err := NewGenerator(types.BloomBitLength)
+			if err != nil {
+				b.Fatalf("failed to create bloombit generator: %v", err)
+			}
+			for j, bloom := range input {
+				if err := gen.AddBloom(uint(j), bloom); err != nil {
+					b.Fatalf("bloom %d: failed to add: %v", i, err)
+				}
+			}
+		}
+	})
+	for i := 0; i < types.BloomBitLength; i++ {
+		rand.Read(input[i][:])
+	}
+	b.Run("random", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			// Crunch the input through the generator and verify the result
+			gen, err := NewGenerator(types.BloomBitLength)
+			if err != nil {
+				b.Fatalf("failed to create bloombit generator: %v", err)
+			}
+			for j, bloom := range input {
+				if err := gen.AddBloom(uint(j), bloom); err != nil {
+					b.Fatalf("bloom %d: failed to add: %v", i, err)
+				}
+			}
+		}
+	})
+}
diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go
index 3ec0d5ae94..927232be01 100644
--- a/core/bloombits/matcher.go
+++ b/core/bloombits/matcher.go
@@ -155,7 +155,6 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin
 	session := &MatcherSession{
 		matcher: m,
 		quit:    make(chan struct{}),
-		kill:    make(chan struct{}),
 		ctx:     ctx,
 	}
 	for _, scheduler := range m.schedulers {
@@ -386,10 +385,8 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
 		requests   = make(map[uint][]uint64) // Per-bit list of section requests, ordered by section number
 		unallocs   = make(map[uint]struct{}) // Bits with pending requests but not allocated to any retriever
 		retrievers chan chan uint            // Waiting retrievers (toggled to nil if unallocs is empty)
-	)
-	var (
-		allocs   int            // Number of active allocations to handle graceful shutdown requests
-		shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests
+		allocs     int                       // Number of active allocations to handle graceful shutdown requests
+		shutdown   = session.quit            // Shutdown request channel, will gracefully wait for pending requests
 	)
 
 	// assign is a helper method fo try to assign a pending bit an actively
@@ -409,15 +406,12 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
 	for {
 		select {
 		case <-shutdown:
-			// Graceful shutdown requested, wait until all pending requests are honoured
+			// Shutdown requested. No more retrievers can be allocated,
+			// but we still need to wait until all pending requests have returned.
+			shutdown = nil
 			if allocs == 0 {
 				return
 			}
-			shutdown = nil
-
-		case <-session.kill:
-			// Pending requests not honoured in time, hard terminate
-			return
 
 		case req := <-dist:
 			// New retrieval request arrived to be distributed to some fetcher process
@@ -499,8 +493,9 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
 					assign(result.Bit)
 				}
 			}
-			// If we're in the process of shutting down, terminate
-			if allocs == 0 && shutdown == nil {
+
+			// End the session when all pending deliveries have arrived.
+			if shutdown == nil && allocs == 0 {
 				return
 			}
 		}
@@ -514,7 +509,6 @@ type MatcherSession struct {
 
 	closer sync.Once     // Sync object to ensure we only ever close once
 	quit   chan struct{} // Quit channel to request pipeline termination
-	kill   chan struct{} // Term channel to signal non-graceful forced shutdown
 
 	ctx context.Context // Context used by the light client to abort filtering
 	err atomic.Value    // Global error to track retrieval failures deep in the chain
@@ -529,7 +523,6 @@ func (s *MatcherSession) Close() {
 	s.closer.Do(func() {
 		// Signal termination and wait for all goroutines to tear down
 		close(s.quit)
-		time.AfterFunc(time.Second, func() { close(s.kill) })
 		s.pend.Wait()
 	})
 }
@@ -542,10 +535,10 @@ func (s *MatcherSession) Error() error {
 	return nil
 }
 
-// AllocateRetrieval assigns a bloom bit index to a client process that can either
+// allocateRetrieval assigns a bloom bit index to a client process that can either
 // immediately request and fetch the section contents assigned to this bit or wait
 // a little while for more sections to be requested.
-func (s *MatcherSession) AllocateRetrieval() (uint, bool) {
+func (s *MatcherSession) allocateRetrieval() (uint, bool) {
 	fetcher := make(chan uint)
 
 	select {
@@ -557,9 +550,9 @@ func (s *MatcherSession) AllocateRetrieval() (uint, bool) {
 	}
 }
 
-// PendingSections returns the number of pending section retrievals belonging to
+// pendingSections returns the number of pending section retrievals belonging to
 // the given bloom bit index.
-func (s *MatcherSession) PendingSections(bit uint) int {
+func (s *MatcherSession) pendingSections(bit uint) int {
 	fetcher := make(chan uint)
 
 	select {
@@ -571,9 +564,9 @@ func (s *MatcherSession) PendingSections(bit uint) int {
 	}
 }
 
-// AllocateSections assigns all or part of an already allocated bit-task queue
+// allocateSections assigns all or part of an already allocated bit-task queue
 // to the requesting process.
-func (s *MatcherSession) AllocateSections(bit uint, count int) []uint64 {
+func (s *MatcherSession) allocateSections(bit uint, count int) []uint64 {
 	fetcher := make(chan *Retrieval)
 
 	select {
@@ -589,14 +582,10 @@ func (s *MatcherSession) AllocateSections(bit uint, count int) []uint64 {
 	}
 }
 
-// DeliverSections delivers a batch of section bit-vectors for a specific bloom
+// deliverSections delivers a batch of section bit-vectors for a specific bloom
 // bit index to be injected into the processing pipeline.
-func (s *MatcherSession) DeliverSections(bit uint, sections []uint64, bitsets [][]byte) {
-	select {
-	case <-s.kill:
-		return
-	case s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets}:
-	}
+func (s *MatcherSession) deliverSections(bit uint, sections []uint64, bitsets [][]byte) {
+	s.matcher.deliveries <- &Retrieval{Bit: bit, Sections: sections, Bitsets: bitsets}
 }
 
 // Multiplex polls the matcher session for retrieval tasks and multiplexes it into
@@ -608,17 +597,17 @@ func (s *MatcherSession) DeliverSections(bit uint, sections []uint64, bitsets []
 func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
 	for {
 		// Allocate a new bloom bit index to retrieve data for, stopping when done
-		bit, ok := s.AllocateRetrieval()
+		bit, ok := s.allocateRetrieval()
 		if !ok {
 			return
 		}
 		// Bit allocated, throttle a bit if we're below our batch limit
-		if s.PendingSections(bit) < batch {
+		if s.pendingSections(bit) < batch {
 			select {
 			case <-s.quit:
 				// Session terminating, we can't meaningfully service, abort
-				s.AllocateSections(bit, 0)
-				s.DeliverSections(bit, []uint64{}, [][]byte{})
+				s.allocateSections(bit, 0)
+				s.deliverSections(bit, []uint64{}, [][]byte{})
 				return
 
 			case <-time.After(wait):
@@ -626,13 +615,13 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
 			}
 		}
 		// Allocate as much as we can handle and request servicing
-		sections := s.AllocateSections(bit, batch)
+		sections := s.allocateSections(bit, batch)
 		request := make(chan *Retrieval)
 
 		select {
 		case <-s.quit:
 			// Session terminating, we can't meaningfully service, abort
-			s.DeliverSections(bit, sections, make([][]byte, len(sections)))
+			s.deliverSections(bit, sections, make([][]byte, len(sections)))
 			return
 
 		case mux <- request:
@@ -644,7 +633,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
 				s.err.Store(result.Error)
 				s.Close()
 			}
-			s.DeliverSections(result.Bit, result.Sections, result.Bitsets)
+			s.deliverSections(result.Bit, result.Sections, result.Bitsets)
 		}
 	}
 }
diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go
index 91143e525e..923579221f 100644
--- a/core/bloombits/matcher_test.go
+++ b/core/bloombits/matcher_test.go
@@ -30,6 +30,7 @@ const testSectionSize = 4096
 
 // Tests that wildcard filter rules (nil) can be specified and are handled well.
 func TestMatcherWildcards(t *testing.T) {
+	t.Parallel()
 	matcher := NewMatcher(testSectionSize, [][][]byte{
 		{common.Address{}.Bytes(), common.Address{0x01}.Bytes()}, // Default address is not a wildcard
 		{common.Hash{}.Bytes(), common.Hash{0x01}.Bytes()},       // Default hash is not a wildcard
@@ -56,6 +57,7 @@ func TestMatcherWildcards(t *testing.T) {
 
 // Tests the matcher pipeline on a single continuous workflow without interrupts.
 func TestMatcherContinuous(t *testing.T) {
+	t.Parallel()
 	testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, false, 75)
 	testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, false, 81)
 	testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, false, 36)
@@ -64,6 +66,7 @@ func TestMatcherContinuous(t *testing.T) {
 // Tests the matcher pipeline on a constantly interrupted and resumed work pattern
 // with the aim of ensuring data items are requested only once.
 func TestMatcherIntermittent(t *testing.T) {
+	t.Parallel()
 	testMatcherDiffBatches(t, [][]bloomIndexes{{{10, 20, 30}}}, 0, 100000, true, 75)
 	testMatcherDiffBatches(t, [][]bloomIndexes{{{32, 3125, 100}}, {{40, 50, 10}}}, 0, 100000, true, 81)
 	testMatcherDiffBatches(t, [][]bloomIndexes{{{4, 8, 11}, {7, 8, 17}}, {{9, 9, 12}, {15, 20, 13}}, {{18, 15, 15}, {12, 10, 4}}}, 0, 10000, true, 36)
@@ -71,6 +74,7 @@ func TestMatcherIntermittent(t *testing.T) {
 
 // Tests the matcher pipeline on random input to hopefully catch anomalies.
 func TestMatcherRandom(t *testing.T) {
+	t.Parallel()
 	for i := 0; i < 10; i++ {
 		testMatcherBothModes(t, makeRandomIndexes([]int{1}, 50), 0, 10000, 0)
 		testMatcherBothModes(t, makeRandomIndexes([]int{3}, 50), 0, 10000, 0)
@@ -84,6 +88,7 @@ func TestMatcherRandom(t *testing.T) {
 // shifter from a multiple of 8. This is needed to cover an optimisation with
 // bitset matching https://github.com/ethereum/go-ethereum/issues/15309.
 func TestMatcherShifted(t *testing.T) {
+	t.Parallel()
 	// Block 0 always matches in the tests, skip ahead of first 8 blocks with the
 	// start to get a potential zero byte in the matcher bitset.
 
@@ -97,6 +102,7 @@ func TestMatcherShifted(t *testing.T) {
 
 // Tests that matching on everything doesn't crash (special case internally).
 func TestWildcardMatcher(t *testing.T) {
+	t.Parallel()
 	testMatcherBothModes(t, nil, 0, 10000, 0)
 }
 
diff --git a/core/bloombits/scheduler_test.go b/core/bloombits/scheduler_test.go
index 70772e4ab9..707e8ea11d 100644
--- a/core/bloombits/scheduler_test.go
+++ b/core/bloombits/scheduler_test.go
@@ -35,6 +35,7 @@ func TestSchedulerMultiClientSingleFetcher(t *testing.T)  { testScheduler(t, 10,
 func TestSchedulerMultiClientMultiFetcher(t *testing.T)   { testScheduler(t, 10, 10, 5000) }
 
 func testScheduler(t *testing.T, clients int, fetchers int, requests int) {
+	t.Parallel()
 	f := newScheduler(0)
 
 	// Create a batch of handler goroutines that respond to bloom bit requests and
@@ -88,10 +89,10 @@ func testScheduler(t *testing.T, clients int, fetchers int, requests int) {
 				}
 				close(in)
 			}()
-
+			b := new(big.Int)
 			for j := 0; j < requests; j++ {
 				bits := <-out
-				if want := new(big.Int).SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) {
+				if want := b.SetUint64(uint64(j)).Bytes(); !bytes.Equal(bits, want) {
 					t.Errorf("vector %d: delivered content mismatch: have %x, want %x", j, bits, want)
 				}
 			}
diff --git a/core/chain_indexer.go b/core/chain_indexer.go
index c0c2c4f7f9..95901a0eaa 100644
--- a/core/chain_indexer.go
+++ b/core/chain_indexer.go
@@ -46,6 +46,9 @@ type ChainIndexerBackend interface {
 
 	// Commit finalizes the section metadata and stores it into the database.
 	Commit() error
+
+	// Prune deletes the chain index older than the given threshold.
+	Prune(threshold uint64) error
 }
 
 // ChainIndexerChain interface is used for connecting the indexer to a blockchain
@@ -91,7 +94,7 @@ type ChainIndexer struct {
 	throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources
 
 	log  log.Logger
-	lock sync.RWMutex
+	lock sync.Mutex
 }
 
 // NewChainIndexer creates a new chain indexer to do background processing on
@@ -386,7 +389,6 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
 	c.log.Trace("Processing new chain section", "section", section)
 
 	// Reset and partial processing
-
 	if err := c.backend.Reset(c.ctx, section, lastHead); err != nil {
 		c.setValidSections(0)
 		return common.Hash{}, err
@@ -399,7 +401,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
 		}
 		header := rawdb.ReadHeader(c.chainDb, hash, number)
 		if header == nil {
-			return common.Hash{}, fmt.Errorf("block #%d [%x…] not found", number, hash[:4])
+			return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4])
 		} else if header.ParentHash != lastHead {
 			return common.Hash{}, fmt.Errorf("chain reorged during section processing")
 		}
@@ -439,6 +441,9 @@ func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) {
 
 // AddChildIndexer adds a child ChainIndexer that can use the output of this one
 func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
+	if indexer == c {
+		panic("can't add indexer as a child of itself")
+	}
 	c.lock.Lock()
 	defer c.lock.Unlock()
 
@@ -456,6 +461,11 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
 	}
 }
 
+// Prune deletes all chain data older than given threshold.
+func (c *ChainIndexer) Prune(threshold uint64) error {
+	return c.backend.Prune(threshold)
+}
+
 // loadValidSections reads the number of valid sections from the index database
 // and caches is into the local state.
 func (c *ChainIndexer) loadValidSections() {
diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go
index ff7548e7bd..f099609015 100644
--- a/core/chain_indexer_test.go
+++ b/core/chain_indexer_test.go
@@ -18,6 +18,7 @@ package core
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"math/big"
 	"math/rand"
@@ -224,7 +225,10 @@ func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Heade
 	//t.processCh <- header.Number.Uint64()
 	select {
 	case <-time.After(10 * time.Second):
-		b.t.Fatal("Unexpected call to Process")
+		b.t.Error("Unexpected call to Process")
+		// Can't use Fatal since this is not the test's goroutine.
+		// Returning error stops the chainIndexer's updateLoop
+		return errors.New("Unexpected call to Process")
 	case b.processCh <- header.Number.Uint64():
 	}
 	return nil
@@ -236,3 +240,7 @@ func (b *testChainIndexBackend) Commit() error {
 	}
 	return nil
 }
+
+func (b *testChainIndexBackend) Prune(threshold uint64) error {
+	return nil
+}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 20c2eb198e..6cb74d51be 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -112,6 +112,11 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
 	b.receipts = append(b.receipts, receipt)
 }
 
+// GetBalance returns the balance of the given address at the generated block.
+func (b *BlockGen) GetBalance(addr common.Address) *big.Int {
+	return b.statedb.GetBalance(addr)
+}
+
 // AddUncheckedTx forcefully adds a transaction to the block without any
 // validation.
 //
@@ -222,7 +227,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
 			if err != nil {
 				panic(fmt.Sprintf("state write error: %v", err))
 			}
-			if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
+			if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil {
 				panic(fmt.Sprintf("trie write error: %v", err))
 			}
 			return block, b.receipts
diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go
index 32e3888d55..85a029f7c7 100644
--- a/core/chain_makers_test.go
+++ b/core/chain_makers_test.go
@@ -79,7 +79,7 @@ func ExampleGenerateChain() {
 	})
 
 	// Import the chain. This runs all block validation rules.
-	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
+	blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer blockchain.Stop()
 
 	if i, err := blockchain.InsertChain(chain); err != nil {
diff --git a/core/dao_test.go b/core/dao_test.go
index 4e8dba9e84..b2a9f624a7 100644
--- a/core/dao_test.go
+++ b/core/dao_test.go
@@ -45,7 +45,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 	proConf.DAOForkBlock = forkBlock
 	proConf.DAOForkSupport = true
 
-	proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil)
+	proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer proBc.Stop()
 
 	conDb := rawdb.NewMemoryDatabase()
@@ -55,7 +55,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 	conConf.DAOForkBlock = forkBlock
 	conConf.DAOForkSupport = false
 
-	conBc, _ := NewBlockChain(conDb, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil)
+	conBc, _ := NewBlockChain(conDb, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer conBc.Stop()
 
 	if _, err := proBc.InsertChain(prefix); err != nil {
@@ -69,7 +69,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 		// Create a pro-fork block, and try to feed into the no-fork chain
 		db = rawdb.NewMemoryDatabase()
 		gspec.MustCommit(db)
-		bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil)
+		bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil)
 		defer bc.Stop()
 
 		blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64()))
@@ -79,7 +79,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 		if _, err := bc.InsertChain(blocks); err != nil {
 			t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
 		}
-		if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
+		if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
 			t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
 		}
 		blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
@@ -94,7 +94,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 		// Create a no-fork block, and try to feed into the pro-fork chain
 		db = rawdb.NewMemoryDatabase()
 		gspec.MustCommit(db)
-		bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil)
+		bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil)
 		defer bc.Stop()
 
 		blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64()))
@@ -104,7 +104,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 		if _, err := bc.InsertChain(blocks); err != nil {
 			t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
 		}
-		if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
+		if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
 			t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
 		}
 		blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
@@ -120,7 +120,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 	// Verify that contra-forkers accept pro-fork extra-datas after forking finishes
 	db = rawdb.NewMemoryDatabase()
 	gspec.MustCommit(db)
-	bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil)
+	bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer bc.Stop()
 
 	blocks := conBc.GetBlocksFromHash(conBc.CurrentBlock().Hash(), int(conBc.CurrentBlock().NumberU64()))
@@ -130,7 +130,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 	if _, err := bc.InsertChain(blocks); err != nil {
 		t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
 	}
-	if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
+	if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
 		t.Fatalf("failed to commit contra-fork head for expansion: %v", err)
 	}
 	blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
@@ -140,7 +140,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 	// Verify that pro-forkers accept contra-fork extra-datas after forking finishes
 	db = rawdb.NewMemoryDatabase()
 	gspec.MustCommit(db)
-	bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil)
+	bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}, nil, nil)
 	defer bc.Stop()
 
 	blocks = proBc.GetBlocksFromHash(proBc.CurrentBlock().Hash(), int(proBc.CurrentBlock().NumberU64()))
@@ -150,7 +150,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
 	if _, err := bc.InsertChain(blocks); err != nil {
 		t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
 	}
-	if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true); err != nil {
+	if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, true, nil); err != nil {
 		t.Fatalf("failed to commit pro-fork head for expansion: %v", err)
 	}
 	blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
diff --git a/core/error.go b/core/error.go
index cd4be3d705..197dd81567 100644
--- a/core/error.go
+++ b/core/error.go
@@ -16,23 +16,59 @@
 
 package core
 
-import "errors"
+import (
+	"errors"
+
+	"github.com/ethereum/go-ethereum/core/types"
+)
 
 var (
 	// ErrKnownBlock is returned when a block to import is already known locally.
 	ErrKnownBlock = errors.New("block already known")
 
-	// ErrGasLimitReached is returned by the gas pool if the amount of gas required
-	// by a transaction is higher than what's left in the block.
-	ErrGasLimitReached = errors.New("gas limit reached")
-
 	// ErrBlacklistedHash is returned if a block to import is on the blacklist.
 	ErrBlacklistedHash = errors.New("blacklisted hash")
 
+	// ErrNoGenesis is returned when there is no Genesis Block.
+	ErrNoGenesis = errors.New("genesis not found in chain")
+)
+
+// List of evm-call-message pre-checking errors. All state transition messages will
+// be pre-checked before execution. If any invalidation detected, the corresponding
+// error should be returned which is defined here.
+//
+// - If the pre-checking happens in the miner, then the transaction won't be packed.
+// - If the pre-checking happens in the block processing procedure, then a "BAD BLOCk"
+// error should be emitted.
+var (
+	// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
+	// one present in the local chain.
+	ErrNonceTooLow = errors.New("nonce too low")
+
 	// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
 	// next one expected based on the local chain.
 	ErrNonceTooHigh = errors.New("nonce too high")
 
-	// ErrNoGenesis is returned when there is no Genesis Block.
-	ErrNoGenesis = errors.New("genesis not found in chain")
+	// ErrGasLimitReached is returned by the gas pool if the amount of gas required
+	// by a transaction is higher than what's left in the block.
+	ErrGasLimitReached = errors.New("gas limit reached")
+
+	// ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't
+	// have enough funds for transfer(topmost call only).
+	ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
+
+	// ErrInsufficientFunds is returned if the total cost of executing a transaction
+	// is higher than the balance of the user's account.
+	ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
+
+	// ErrGasUintOverflow is returned when calculating gas usage.
+	ErrGasUintOverflow = errors.New("gas uint64 overflow")
+
+	// ErrIntrinsicGas is returned if the transaction is specified to use less gas
+	// than required to start the invocation.
+	ErrIntrinsicGas = errors.New("intrinsic gas too low")
+
+	// ErrTxTypeNotSupported is returned if a transaction is not supported in the
+	// current network configuration.
+	ErrTxTypeNotSupported = types.ErrTxTypeNotSupported
 )
diff --git a/core/evm.go b/core/evm.go
index 8abe5a0477..8f69d51499 100644
--- a/core/evm.go
+++ b/core/evm.go
@@ -35,8 +35,8 @@ type ChainContext interface {
 	GetHeader(common.Hash, uint64) *types.Header
 }
 
-// NewEVMContext creates a new context for use in the EVM.
-func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author *common.Address) vm.Context {
+// NewEVMBlockContext creates a new context for use in the EVM.
+func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
 	// If we don't have an explicit author (i.e. not mining), extract from the header
 	var beneficiary common.Address
 	if author == nil {
@@ -44,17 +44,23 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
 	} else {
 		beneficiary = *author
 	}
-	return vm.Context{
+	return vm.BlockContext{
 		CanTransfer: CanTransfer,
 		Transfer:    Transfer,
 		GetHash:     GetHashFn(header, chain),
-		Origin:      msg.From(),
 		Coinbase:    beneficiary,
 		BlockNumber: new(big.Int).Set(header.Number),
 		Time:        new(big.Int).SetUint64(header.Time),
 		Difficulty:  new(big.Int).Set(header.Difficulty),
 		GasLimit:    header.GasLimit,
-		GasPrice:    new(big.Int).Set(msg.GasPrice()),
+	}
+}
+
+// NewEVMTxContext creates a new transaction context for a single transaction.
+func NewEVMTxContext(msg Message) vm.TxContext {
+	return vm.TxContext{
+		Origin:   msg.From(),
+		GasPrice: new(big.Int).Set(msg.GasPrice()),
 	}
 }
 
diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go
index d37688a315..8a46485e92 100644
--- a/core/forkid/forkid.go
+++ b/core/forkid/forkid.go
@@ -27,7 +27,7 @@ import (
 	"strings"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/params"
 )
@@ -44,6 +44,18 @@ var (
 	ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update")
 )
 
+// Blockchain defines all necessary method to build a forkID.
+type Blockchain interface {
+	// Config retrieves the chain's fork configuration.
+	Config() *params.ChainConfig
+
+	// Genesis retrieves the chain's genesis block.
+	Genesis() *types.Block
+
+	// CurrentHeader retrieves the current head header of the canonical chain.
+	CurrentHeader() *types.Header
+}
+
 // ID is a fork identifier as defined by EIP-2124.
 type ID struct {
 	Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers
@@ -53,16 +65,8 @@ type ID struct {
 // Filter is a fork id filter to validate a remotely advertised ID.
 type Filter func(id ID) error
 
-// NewID calculates the Ethereum fork ID from the chain config and head.
-func NewID(chain *core.BlockChain) ID {
-	return newID(
-		chain.Config(),
-		chain.Genesis().Hash(),
-		chain.CurrentHeader().Number.Uint64(),
-	)
-}
-
-func NextForkHash(config *params.ChainConfig, genesis common.Hash, head uint64) [4]byte {
+// NewID calculates the Ethereum fork ID from the chain config, genesis hash, and head.
+func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
 	// Calculate the starting checksum from the genesis hash
 	hash := crc32.ChecksumIEEE(genesis[:])
 
@@ -77,17 +81,10 @@ func NextForkHash(config *params.ChainConfig, genesis common.Hash, head uint64)
 		next = fork
 		break
 	}
-	if next == 0 {
-		return checksumToBytes(hash)
-	} else {
-		return checksumToBytes(checksumUpdate(hash, next))
-	}
+	return ID{Hash: checksumToBytes(hash), Next: next}
 }
 
-// newID is the internal version of NewID, which takes extracted values as its
-// arguments instead of a chain. The reason is to allow testing the IDs without
-// having to simulate an entire blockchain.
-func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
+func NextForkHash(config *params.ChainConfig, genesis common.Hash, head uint64) [4]byte {
 	// Calculate the starting checksum from the genesis hash
 	hash := crc32.ChecksumIEEE(genesis[:])
 
@@ -102,12 +99,25 @@ func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID {
 		next = fork
 		break
 	}
-	return ID{Hash: checksumToBytes(hash), Next: next}
+	if next == 0 {
+		return checksumToBytes(hash)
+	} else {
+		return checksumToBytes(checksumUpdate(hash, next))
+	}
+}
+
+// NewIDWithChain calculates the Ethereum fork ID from an existing chain instance.
+func NewIDWithChain(chain Blockchain) ID {
+	return NewID(
+		chain.Config(),
+		chain.Genesis().Hash(),
+		chain.CurrentHeader().Number.Uint64(),
+	)
 }
 
 // NewFilter creates a filter that returns if a fork ID should be rejected or not
 // based on the local chain's status.
-func NewFilter(chain *core.BlockChain) Filter {
+func NewFilter(chain Blockchain) Filter {
 	return newFilter(
 		chain.Config(),
 		chain.Genesis().Hash(),
@@ -245,7 +255,7 @@ func gatherForks(config *params.ChainConfig) []uint64 {
 			forks = append(forks, rule.Uint64())
 		}
 	}
-	// Sort the fork block numbers to permit chronologival XOR
+	// Sort the fork block numbers to permit chronological XOR
 	for i := 0; i < len(forks); i++ {
 		for j := i + 1; j < len(forks); j++ {
 			if forks[i] > forks[j] {
diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go
index da56530214..a20598fa9d 100644
--- a/core/forkid/forkid_test.go
+++ b/core/forkid/forkid_test.go
@@ -43,24 +43,26 @@ func TestCreation(t *testing.T) {
 			params.MainnetChainConfig,
 			params.MainnetGenesisHash,
 			[]testcase{
-				{0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}},       // Unsynced
-				{1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
-				{1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
-				{1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
-				{1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
-				{2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
-				{2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
-				{2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
-				{2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
-				{4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
-				{4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
-				{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
-				{7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block
-				{9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block
-				{9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block
-				{9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block
-				{9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}},       // First Muir Glacier block
-				{10000000, ID{Hash: checksumToBytes(0xe029e991), Next: 0}},      // Future Muir Glacier block
+				{0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}},         // Unsynced
+				{1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}},   // Last Frontier block
+				{1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}},   // First Homestead block
+				{1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}},   // Last Homestead block
+				{1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}},   // First DAO block
+				{2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}},   // Last DAO block
+				{2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}},   // First Tangerine block
+				{2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}},   // Last Tangerine block
+				{2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}},   // First Spurious block
+				{4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}},   // Last Spurious block
+				{4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}},   // First Byzantium block
+				{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}},   // Last Byzantium block
+				{7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}},   // First and last Constantinople, first Petersburg block
+				{9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}},   // Last Petersburg block
+				{9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}},   // First Istanbul and first Muir Glacier block
+				{9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}},   // Last Istanbul and first Muir Glacier block
+				{9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}},  // First Muir Glacier block
+				{12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block
+				{12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}},        // First Berlin block
+				{20000000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 0}},        // Future Berlin block
 			},
 		},
 		// Ropsten test cases
@@ -80,8 +82,10 @@ func TestCreation(t *testing.T) {
 				{6485845, ID{Hash: checksumToBytes(0xd6e2149b), Next: 6485846}}, // Last Petersburg block
 				{6485846, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // First Istanbul block
 				{7117116, ID{Hash: checksumToBytes(0x4bc66396), Next: 7117117}}, // Last Istanbul block
-				{7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}},       // First Muir Glacier block
-				{7500000, ID{Hash: checksumToBytes(0x6727ef90), Next: 0}},       // Future
+				{7117117, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // First Muir Glacier block
+				{9812188, ID{Hash: checksumToBytes(0x6727ef90), Next: 9812189}}, // Last Muir Glacier block
+				{9812189, ID{Hash: checksumToBytes(0xa157d377), Next: 0}},       // First Berlin block
+				{10000000, ID{Hash: checksumToBytes(0xa157d377), Next: 0}},      // Future Berlin block
 			},
 		},
 		// Rinkeby test cases
@@ -100,8 +104,10 @@ func TestCreation(t *testing.T) {
 				{4321233, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block
 				{4321234, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block
 				{5435344, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block
-				{5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}},       // First Istanbul block
-				{6000000, ID{Hash: checksumToBytes(0xcbdb8838), Next: 0}},       // Future Istanbul block
+				{5435345, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block
+				{8290927, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block
+				{8290928, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}},       // First Berlin block
+				{10000000, ID{Hash: checksumToBytes(0x6910c8bd), Next: 0}},      // Future Berlin block
 			},
 		},
 		// Goerli test cases
@@ -111,14 +117,16 @@ func TestCreation(t *testing.T) {
 			[]testcase{
 				{0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}},       // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block
 				{1561650, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block
-				{1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}},       // First Istanbul block
-				{2000000, ID{Hash: checksumToBytes(0xc25efa5c), Next: 0}},       // Future Istanbul block
+				{1561651, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // First Istanbul block
+				{4460643, ID{Hash: checksumToBytes(0xc25efa5c), Next: 4460644}}, // Last Istanbul block
+				{4460644, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}},       // First Berlin block
+				{5000000, ID{Hash: checksumToBytes(0x757a1c47), Next: 0}},       // Future Berlin block
 			},
 		},
 	}
 	for i, tt := range tests {
 		for j, ttt := range tt.cases {
-			if have := newID(tt.config, tt.genesis, ttt.head); have != ttt.want {
+			if have := NewID(tt.config, tt.genesis, ttt.head); have != ttt.want {
 				t.Errorf("test %d, case %d: fork ID mismatch: have %x, want %x", i, j, have, ttt.want)
 			}
 		}
@@ -185,11 +193,11 @@ func TestValidation(t *testing.T) {
 		// Local is mainnet Petersburg, remote is Rinkeby Petersburg.
 		{7987396, ID{Hash: checksumToBytes(0xafec6b27), Next: 0}, ErrLocalIncompatibleOrStale},
 
-		// Local is mainnet Muir Glacier, far in the future. Remote announces Gopherium (non existing fork)
+		// Local is mainnet Berlin, far in the future. Remote announces Gopherium (non existing fork)
 		// at some future block 88888888, for itself, but past block for local. Local is incompatible.
 		//
 		// This case detects non-upgraded nodes with majority hash power (typical Ropsten mess).
-		{88888888, ID{Hash: checksumToBytes(0xe029e991), Next: 88888888}, ErrLocalIncompatibleOrStale},
+		{88888888, ID{Hash: checksumToBytes(0x0eb440f6), Next: 88888888}, ErrLocalIncompatibleOrStale},
 
 		// Local is mainnet Byzantium. Remote is also in Byzantium, but announces Gopherium (non existing
 		// fork) at block 7279999, before Petersburg. Local is incompatible.
diff --git a/core/genesis.go b/core/genesis.go
index 66f6315c24..75052a19b5 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -37,6 +37,7 @@ import (
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 //go:generate gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go
@@ -153,10 +154,10 @@ func (e *GenesisMismatchError) Error() string {
 //
 // The returned chain configuration is never nil.
 func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
-	return SetupGenesisBlockWithOverride(db, genesis, nil, nil)
+	return SetupGenesisBlockWithOverride(db, genesis, nil)
 }
 
-func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideIstanbul, overrideMuirGlacier *big.Int) (*params.ChainConfig, common.Hash, error) {
+func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideBerlin *big.Int) (*params.ChainConfig, common.Hash, error) {
 	if genesis != nil && genesis.Config == nil {
 		return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
 	}
@@ -176,11 +177,10 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
 		}
 		return genesis.Config, block.Hash(), nil
 	}
-
 	// We have the genesis block in database(perhaps in ancient database)
 	// but the corresponding state is missing.
 	header := rawdb.ReadHeader(db, stored, 0)
-	if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0), nil); err != nil {
+	if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil), nil); err != nil {
 		if genesis == nil {
 			genesis = DefaultGenesisBlock()
 		}
@@ -195,7 +195,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
 		}
 		return genesis.Config, block.Hash(), nil
 	}
-
 	// Check whether the genesis block is already written.
 	if genesis != nil {
 		hash := genesis.ToBlock(nil).Hash()
@@ -203,14 +202,10 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
 			return genesis.Config, hash, &GenesisMismatchError{stored, hash}
 		}
 	}
-
 	// Get the existing chain configuration.
 	newcfg := genesis.configOrDefault(stored)
-	if overrideIstanbul != nil {
-		newcfg.IstanbulBlock = overrideIstanbul
-	}
-	if overrideMuirGlacier != nil {
-		newcfg.MuirGlacierBlock = overrideMuirGlacier
+	if overrideBerlin != nil {
+		newcfg.BerlinBlock = overrideBerlin
 	}
 	if err := newcfg.CheckConfigForkOrder(); err != nil {
 		return newcfg, common.Hash{}, err
@@ -229,7 +224,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
 		stored != params.ChapelGenesisHash && stored != params.RialtoGenesisHash && stored != params.BSCGenesisHash {
 		return storedcfg, stored, nil
 	}
-
 	// Check config compatibility and write the config. Compatibility errors
 	// are returned to the caller unless we're already at block zero.
 	height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db))
@@ -262,6 +256,8 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
 		return params.ChapelChainConfig
 	case ghash == params.RialtoGenesisHash:
 		return params.RialtoChainConfig
+	case ghash == params.YoloV3GenesisHash:
+		return params.YoloV3ChainConfig
 	default:
 		return params.AllEthashProtocolChanges
 	}
@@ -303,9 +299,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
 		head.Difficulty = params.GenesisDifficulty
 	}
 	statedb.Commit(false)
-	statedb.Database().TrieDB().Commit(root, true)
+	statedb.Database().TrieDB().Commit(root, true, nil)
 
-	return types.NewBlock(head, nil, nil, nil)
+	return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
 }
 
 // Commit writes the block and state of a genesis specification to the database.
@@ -397,6 +393,18 @@ func DefaultGoerliGenesisBlock() *Genesis {
 	}
 }
 
+func DefaultYoloV3GenesisBlock() *Genesis {
+	// Full genesis: https://gist.github.com/holiman/c6ed9269dce28304ad176314caa75e97
+	return &Genesis{
+		Config:     params.YoloV3ChainConfig,
+		Timestamp:  0x6027dd2e,
+		ExtraData:  hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001041afbcb359d5a8dc58c15b2ff51354ff8a217d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
+		GasLimit:   0x47b760,
+		Difficulty: big.NewInt(1),
+		Alloc:      decodePrealloc(yoloV3AllocData),
+	}
+}
+
 // DeveloperGenesisBlock returns the 'geth --dev' genesis block.
 func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
 	// Override the default period to the user requested one
@@ -407,7 +415,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
 	return &Genesis{
 		Config:     &config,
 		ExtraData:  append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...),
-		GasLimit:   6283185,
+		GasLimit:   11500000,
 		Difficulty: big.NewInt(1),
 		Alloc: map[common.Address]GenesisAccount{
 			common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
@@ -418,6 +426,7 @@ func DeveloperGenesisBlock(period uint64, faucet common.Address) *Genesis {
 			common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
 			common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
 			common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
+			common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
 			faucet:                           {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))},
 		},
 	}
diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go
index 9d9417222c..5b0e933d7a 100644
--- a/core/genesis_alloc.go
+++ b/core/genesis_alloc.go
@@ -25,3 +25,4 @@ const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908'
 const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00"
 const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
 const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00"
+const yoloV3AllocData = "\xf9\x05o\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x94\x0e\x89\xe2\xae\xdb\x1c\xfc\u06d4$\xd4\x1a\x1f!\x8fA2s\x81r\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x10A\xaf\xbc\xb3Y\u0568\xdcX\xc1[/\xf5\x13T\xff\x8a!}\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94`\xad\xc0\xf8\x9aA\xaf#|\xe75T\xed\xe1p\xd73\xec\x14\xe0\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8a\x8e\xaf\xb1\xcfb\xbf\xbe\xb1t\x17i\xda\xe1\xa9\xddG\x99a\x92\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\x8b\xa1\xf1\tU\x1b\xd42\x800\x12dZ\xc16\xdd\xd6M\xbar\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xb0*.\xda\x1b1\u007f\xbd\x16v\x01(\x83k\n\u015bV\x0e\x9d\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x94\xdf\n\x88\xb2\xb6\x8cg7\x13\xa8\xec\x82`\x03go'.5s\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
diff --git a/core/genesis_test.go b/core/genesis_test.go
index 06a1698682..44c1ef253a 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -120,7 +120,7 @@ func TestSetupGenesis(t *testing.T) {
 				// Advance to block #4, past the homestead transition block of customg.
 				genesis := oldcustomg.MustCommit(db)
 
-				bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}, nil)
+				bc, _ := NewBlockChain(db, nil, oldcustomg.Config, ethash.NewFullFaker(), vm.Config{}, nil, nil)
 				defer bc.Stop()
 
 				blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil)
@@ -162,3 +162,38 @@ func TestSetupGenesis(t *testing.T) {
 		}
 	}
 }
+
+// TestGenesisHashes checks the congruity of default genesis data to corresponding hardcoded genesis hash values.
+func TestGenesisHashes(t *testing.T) {
+	cases := []struct {
+		genesis *Genesis
+		hash    common.Hash
+	}{
+		{
+			genesis: DefaultGenesisBlock(),
+			hash:    params.MainnetGenesisHash,
+		},
+		{
+			genesis: DefaultGoerliGenesisBlock(),
+			hash:    params.GoerliGenesisHash,
+		},
+		{
+			genesis: DefaultRopstenGenesisBlock(),
+			hash:    params.RopstenGenesisHash,
+		},
+		{
+			genesis: DefaultRinkebyGenesisBlock(),
+			hash:    params.RinkebyGenesisHash,
+		},
+		{
+			genesis: DefaultYoloV3GenesisBlock(),
+			hash:    params.YoloV3GenesisHash,
+		},
+	}
+	for i, c := range cases {
+		b := c.genesis.MustCommit(rawdb.NewMemoryDatabase())
+		if got := b.Hash(); got != c.hash {
+			t.Errorf("case: %d, want: %s, got: %s", i, c.hash.Hex(), got.Hex())
+		}
+	}
+}
diff --git a/core/headerchain.go b/core/headerchain.go
index f21dcf537e..1dbf958786 100644
--- a/core/headerchain.go
+++ b/core/headerchain.go
@@ -129,109 +129,193 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
 	return number
 }
 
-// WriteHeader writes a header into the local chain, given that its parent is
-// already known. If the total difficulty of the newly inserted header becomes
-// greater than the current known TD, the canonical chain is re-routed.
+type headerWriteResult struct {
+	status     WriteStatus
+	ignored    int
+	imported   int
+	lastHash   common.Hash
+	lastHeader *types.Header
+}
+
+// WriteHeaders writes a chain of headers into the local chain, given that the parents
+// are already known. If the total difficulty of the newly inserted chain becomes
+// greater than the current known TD, the canonical chain is reorged.
 //
 // Note: This method is not concurrent-safe with inserting blocks simultaneously
 // into the chain, as side effects caused by reorganisations cannot be emulated
 // without the real blocks. Hence, writing headers directly should only be done
 // in two scenarios: pure-header mode of operation (light clients), or properly
 // separated header/block phases (non-archive clients).
-func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, err error) {
-	// Cache some values to prevent constant recalculation
+func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWriteResult, err error) {
+	if len(headers) == 0 {
+		return &headerWriteResult{}, nil
+	}
+	ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1)
+	if ptd == nil {
+		return &headerWriteResult{}, consensus.ErrUnknownAncestor
+	}
 	var (
-		hash   = header.Hash()
-		number = header.Number.Uint64()
+		lastNumber = headers[0].Number.Uint64() - 1 // Last successfully imported number
+		lastHash   = headers[0].ParentHash          // Last imported header hash
+		newTD      = new(big.Int).Set(ptd)          // Total difficulty of inserted chain
+
+		lastHeader    *types.Header
+		inserted      []numberHash // Ephemeral lookup of number/hash for the chain
+		firstInserted = -1         // Index of the first non-ignored header
 	)
-	// Calculate the total difficulty of the header
-	ptd := hc.GetTd(header.ParentHash, number-1)
-	if ptd == nil {
-		return NonStatTy, consensus.ErrUnknownAncestor
+
+	batch := hc.chainDb.NewBatch()
+	for i, header := range headers {
+		var hash common.Hash
+		// The headers have already been validated at this point, so we already
+		// know that it's a contiguous chain, where
+		// headers[i].Hash() == headers[i+1].ParentHash
+		if i < len(headers)-1 {
+			hash = headers[i+1].ParentHash
+		} else {
+			hash = header.Hash()
+		}
+		number := header.Number.Uint64()
+		newTD.Add(newTD, header.Difficulty)
+
+		// If the header is already known, skip it, otherwise store
+		if !hc.HasHeader(hash, number) {
+			// Irrelevant of the canonical status, write the TD and header to the database.
+			rawdb.WriteTd(batch, hash, number, newTD)
+			hc.tdCache.Add(hash, new(big.Int).Set(newTD))
+
+			rawdb.WriteHeader(batch, header)
+			inserted = append(inserted, numberHash{number, hash})
+			hc.headerCache.Add(hash, header)
+			hc.numberCache.Add(hash, number)
+			if firstInserted < 0 {
+				firstInserted = i
+			}
+		}
+		lastHeader, lastHash, lastNumber = header, hash, number
 	}
-	localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64())
-	externTd := new(big.Int).Add(header.Difficulty, ptd)
 
-	// Irrelevant of the canonical status, write the td and header to the database
-	//
-	// Note all the components of header(td, hash->number index and header) should
-	// be written atomically.
-	headerBatch := hc.chainDb.NewBatch()
-	rawdb.WriteTd(headerBatch, hash, number, externTd)
-	rawdb.WriteHeader(headerBatch, header)
-	if err := headerBatch.Write(); err != nil {
-		log.Crit("Failed to write header into disk", "err", err)
+	// Skip the slow disk write of all headers if interrupted.
+	if hc.procInterrupt() {
+		log.Debug("Premature abort during headers import")
+		return &headerWriteResult{}, errors.New("aborted")
 	}
+	// Commit to disk!
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to write headers", "error", err)
+	}
+	batch.Reset()
+
+	var (
+		head    = hc.CurrentHeader().Number.Uint64()
+		localTD = hc.GetTd(hc.currentHeaderHash, head)
+		status  = SideStatTy
+	)
 	// If the total difficulty is higher than our known, add it to the canonical chain
 	// Second clause in the if statement reduces the vulnerability to selfish mining.
 	// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
-	if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) {
+	reorg := newTD.Cmp(localTD) > 0
+	if !reorg && newTD.Cmp(localTD) == 0 {
+		if lastNumber < head {
+			reorg = true
+		} else if lastNumber == head {
+			reorg = mrand.Float64() < 0.5
+		}
+	}
+	// If the parent of the (first) block is already the canon header,
+	// we don't have to go backwards to delete canon blocks, but
+	// simply pile them onto the existing chain
+	chainAlreadyCanon := headers[0].ParentHash == hc.currentHeaderHash
+	if reorg {
 		// If the header can be added into canonical chain, adjust the
 		// header chain markers(canonical indexes and head header flag).
 		//
 		// Note all markers should be written atomically.
-
-		// Delete any canonical number assignments above the new head
-		markerBatch := hc.chainDb.NewBatch()
-		for i := number + 1; ; i++ {
-			hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
-			if hash == (common.Hash{}) {
-				break
+		markerBatch := batch // we can reuse the batch to keep allocs down
+		if !chainAlreadyCanon {
+			// Delete any canonical number assignments above the new head
+			for i := lastNumber + 1; ; i++ {
+				hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
+				if hash == (common.Hash{}) {
+					break
+				}
+				rawdb.DeleteCanonicalHash(markerBatch, i)
+			}
+			// Overwrite any stale canonical number assignments, going
+			// backwards from the first header in this import
+			var (
+				headHash   = headers[0].ParentHash          // inserted[0].parent?
+				headNumber = headers[0].Number.Uint64() - 1 // inserted[0].num-1 ?
+				headHeader = hc.GetHeader(headHash, headNumber)
+			)
+			for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
+				rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber)
+				headHash = headHeader.ParentHash
+				headNumber = headHeader.Number.Uint64() - 1
+				headHeader = hc.GetHeader(headHash, headNumber)
+			}
+			// If some of the older headers were already known, but obtained canon-status
+			// during this import batch, then we need to write that now
+			// Further down, we continue writing the staus for the ones that
+			// were not already known
+			for i := 0; i < firstInserted; i++ {
+				hash := headers[i].Hash()
+				num := headers[i].Number.Uint64()
+				rawdb.WriteCanonicalHash(markerBatch, hash, num)
+				rawdb.WriteHeadHeaderHash(markerBatch, hash)
 			}
-			rawdb.DeleteCanonicalHash(markerBatch, i)
 		}
-
-		// Overwrite any stale canonical number assignments
-		var (
-			headHash   = header.ParentHash
-			headNumber = header.Number.Uint64() - 1
-			headHeader = hc.GetHeader(headHash, headNumber)
-		)
-		for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
-			rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber)
-
-			headHash = headHeader.ParentHash
-			headNumber = headHeader.Number.Uint64() - 1
-			headHeader = hc.GetHeader(headHash, headNumber)
+		// Extend the canonical chain with the new headers
+		for _, hn := range inserted {
+			rawdb.WriteCanonicalHash(markerBatch, hn.hash, hn.number)
+			rawdb.WriteHeadHeaderHash(markerBatch, hn.hash)
 		}
-		// Extend the canonical chain with the new header
-		rawdb.WriteCanonicalHash(markerBatch, hash, number)
-		rawdb.WriteHeadHeaderHash(markerBatch, hash)
 		if err := markerBatch.Write(); err != nil {
 			log.Crit("Failed to write header markers into disk", "err", err)
 		}
+		markerBatch.Reset()
 		// Last step update all in-memory head header markers
-		hc.currentHeaderHash = hash
-		hc.currentHeader.Store(types.CopyHeader(header))
-		headHeaderGauge.Update(header.Number.Int64())
+		hc.currentHeaderHash = lastHash
+		hc.currentHeader.Store(types.CopyHeader(lastHeader))
+		headHeaderGauge.Update(lastHeader.Number.Int64())
 
+		// Chain status is canonical since this insert was a reorg.
+		// Note that all inserts which have higher TD than existing are 'reorg'.
 		status = CanonStatTy
-	} else {
-		status = SideStatTy
 	}
-	hc.tdCache.Add(hash, externTd)
-	hc.headerCache.Add(hash, header)
-	hc.numberCache.Add(hash, number)
-	return
-}
 
-// WhCallback is a callback function for inserting individual headers.
-// A callback is used for two reasons: first, in a LightChain, status should be
-// processed and light chain events sent, while in a BlockChain this is not
-// necessary since chain events are sent after inserting blocks. Second, the
-// header writes should be protected by the parent chain mutex individually.
-type WhCallback func(*types.Header) error
+	if len(inserted) == 0 {
+		status = NonStatTy
+	}
+	return &headerWriteResult{
+		status:     status,
+		ignored:    len(headers) - len(inserted),
+		imported:   len(inserted),
+		lastHash:   lastHash,
+		lastHeader: lastHeader,
+	}, nil
+}
 
 func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
 	// Do a sanity check that the provided chain is actually ordered and linked
 	for i := 1; i < len(chain); i++ {
-		if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
+		if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 {
+			hash := chain[i].Hash()
+			parentHash := chain[i-1].Hash()
 			// Chain broke ancestry, log a message (programming error) and skip insertion
-			log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(),
-				"parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash())
+			log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", hash,
+				"parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", parentHash)
 
-			return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].Number,
-				chain[i-1].Hash().Bytes()[:4], i, chain[i].Number, chain[i].Hash().Bytes()[:4], chain[i].ParentHash[:4])
+			return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, chain[i-1].Number,
+				parentHash.Bytes()[:4], i, chain[i].Number, hash.Bytes()[:4], chain[i].ParentHash[:4])
+		}
+		// If the header is a banned one, straight out abort
+		if BadHashes[chain[i].ParentHash] {
+			return i - 1, ErrBlacklistedHash
+		}
+		// If it's the last header in the cunk, we need to check it too
+		if i == len(chain)-1 && BadHashes[chain[i].Hash()] {
+			return i, ErrBlacklistedHash
 		}
 	}
 
@@ -239,7 +323,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
 	seals := make([]bool, len(chain))
 	if checkFreq != 0 {
 		// In case of checkFreq == 0 all seals are left false.
-		for i := 0; i < len(seals)/checkFreq; i++ {
+		for i := 0; i <= len(seals)/checkFreq; i++ {
 			index := i*checkFreq + hc.rand.Intn(checkFreq)
 			if index >= len(seals) {
 				index = len(seals) - 1
@@ -254,16 +338,12 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
 	defer close(abort)
 
 	// Iterate over the headers and ensure they all check out
-	for i, header := range chain {
+	for i := range chain {
 		// If the chain is terminating, stop processing blocks
 		if hc.procInterrupt() {
 			log.Debug("Premature abort during headers verification")
 			return 0, errors.New("aborted")
 		}
-		// If the header is a banned one, straight out abort
-		if BadHashes[header.Hash()] {
-			return i, ErrBlacklistedHash
-		}
 		// Otherwise wait for headers checks and ensure they pass
 		if err := <-results; err != nil {
 			return i, err
@@ -273,55 +353,41 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
 	return 0, nil
 }
 
-// InsertHeaderChain attempts to insert the given header chain in to the local
-// chain, possibly creating a reorg. If an error is returned, it will return the
-// index number of the failing header as well an error describing what went wrong.
+// InsertHeaderChain inserts the given headers.
 //
-// The verify parameter can be used to fine tune whether nonce verification
-// should be done or not. The reason behind the optional check is because some
-// of the header retrieval mechanisms already need to verfy nonces, as well as
-// because nonces can be verified sparsely, not needing to check each.
-func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, writeHeader WhCallback, start time.Time) (int, error) {
-	// Collect some import statistics to report on
-	stats := struct{ processed, ignored int }{}
-	// All headers passed verification, import them into the database
-	for i, header := range chain {
-		// Short circuit insertion if shutting down
-		if hc.procInterrupt() {
-			log.Debug("Premature abort during headers import")
-			return i, errors.New("aborted")
-		}
-		// If the header's already known, skip it, otherwise store
-		hash := header.Hash()
-		if hc.HasHeader(hash, header.Number.Uint64()) {
-			externTd := hc.GetTd(hash, header.Number.Uint64())
-			localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64())
-			if externTd == nil || externTd.Cmp(localTd) <= 0 {
-				stats.ignored++
-				continue
-			}
-		}
-		if err := writeHeader(header); err != nil {
-			return i, err
-		}
-		stats.processed++
+// The validity of the headers is NOT CHECKED by this method, i.e. they need to be
+// validated by ValidateHeaderChain before calling InsertHeaderChain.
+//
+// This insert is all-or-nothing. If this returns an error, no headers were written,
+// otherwise they were all processed successfully.
+//
+// The returned 'write status' says if the inserted headers are part of the canonical chain
+// or a side chain.
+func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time) (WriteStatus, error) {
+	if hc.procInterrupt() {
+		return 0, errors.New("aborted")
 	}
-	// Report some public statistics so the user has a clue what's going on
-	last := chain[len(chain)-1]
+	res, err := hc.writeHeaders(chain)
 
+	// Report some public statistics so the user has a clue what's going on
 	context := []interface{}{
-		"count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)),
-		"number", last.Number, "hash", last.Hash(),
+		"count", res.imported,
+		"elapsed", common.PrettyDuration(time.Since(start)),
 	}
-	if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute {
-		context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
+	if err != nil {
+		context = append(context, "err", err)
+	}
+	if last := res.lastHeader; last != nil {
+		context = append(context, "number", last.Number, "hash", res.lastHash)
+		if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute {
+			context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
+		}
 	}
-	if stats.ignored > 0 {
-		context = append(context, []interface{}{"ignored", stats.ignored}...)
+	if res.ignored > 0 {
+		context = append(context, []interface{}{"ignored", res.ignored}...)
 	}
 	log.Info("Imported new block headers", context...)
-
-	return 0, nil
+	return res.status, err
 }
 
 // GetBlockHashesFromHash retrieves a number of block hashes starting at a given
@@ -360,9 +426,8 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma
 		// in this case it is cheaper to just read the header
 		if header := hc.GetHeader(hash, number); header != nil {
 			return header.ParentHash, number - 1
-		} else {
-			return common.Hash{}, 0
 		}
+		return common.Hash{}, 0
 	}
 	for ancestor != 0 {
 		if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
@@ -479,8 +544,10 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
 
 type (
 	// UpdateHeadBlocksCallback is a callback function that is called by SetHead
-	// before head header is updated.
-	UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header)
+	// before head header is updated. The method will return the actual block it
+	// updated the head to (missing state) and a flag if setHead should continue
+	// rewinding till that forcefully (exceeded ancient limits)
+	UpdateHeadBlocksCallback func(ethdb.KeyValueWriter, *types.Header) (uint64, bool)
 
 	// DeleteBlockContentCallback is a callback function that is called by SetHead
 	// before each header is deleted.
@@ -493,9 +560,10 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
 	var (
 		parentHash common.Hash
 		batch      = hc.chainDb.NewBatch()
+		origin     = true
 	)
 	for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
-		hash, num := hdr.Hash(), hdr.Number.Uint64()
+		num := hdr.Number.Uint64()
 
 		// Rewind block chain to new head.
 		parent := hc.GetHeader(hdr.ParentHash, num-1)
@@ -503,16 +571,21 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
 			parent = hc.genesisHeader
 		}
 		parentHash = hdr.ParentHash
+
 		// Notably, since geth has the possibility for setting the head to a low
 		// height which is even lower than ancient head.
 		// In order to ensure that the head is always no higher than the data in
-		// the database(ancient store or active store), we need to update head
+		// the database (ancient store or active store), we need to update head
 		// first then remove the relative data from the database.
 		//
 		// Update head first(head fast block, head full block) before deleting the data.
 		markerBatch := hc.chainDb.NewBatch()
 		if updateFn != nil {
-			updateFn(markerBatch, parent)
+			newHead, force := updateFn(markerBatch, parent)
+			if force && newHead < head {
+				log.Warn("Force rewinding till ancient limit", "head", newHead)
+				head = newHead
+			}
 		}
 		// Update head header then.
 		rawdb.WriteHeadHeaderHash(markerBatch, parentHash)
@@ -523,14 +596,34 @@ func (hc *HeaderChain) SetHead(head uint64, updateFn UpdateHeadBlocksCallback, d
 		hc.currentHeaderHash = parentHash
 		headHeaderGauge.Update(parent.Number.Int64())
 
-		// Remove the relative data from the database.
-		if delFn != nil {
-			delFn(batch, hash, num)
+		// If this is the first iteration, wipe any leftover data upwards too so
+		// we don't end up with dangling daps in the database
+		var nums []uint64
+		if origin {
+			for n := num + 1; len(rawdb.ReadAllHashes(hc.chainDb, n)) > 0; n++ {
+				nums = append([]uint64{n}, nums...) // suboptimal, but we don't really expect this path
+			}
+			origin = false
+		}
+		nums = append(nums, num)
+
+		// Remove the related data from the database on all sidechains
+		for _, num := range nums {
+			// Gather all the side fork hashes
+			hashes := rawdb.ReadAllHashes(hc.chainDb, num)
+			if len(hashes) == 0 {
+				// No hashes in the database whatsoever, probably frozen already
+				hashes = append(hashes, hdr.Hash())
+			}
+			for _, hash := range hashes {
+				if delFn != nil {
+					delFn(batch, hash, num)
+				}
+				rawdb.DeleteHeader(batch, hash, num)
+				rawdb.DeleteTd(batch, hash, num)
+			}
+			rawdb.DeleteCanonicalHash(batch, num)
 		}
-		// Rewind header chain to new head.
-		rawdb.DeleteHeader(batch, hash, num)
-		rawdb.DeleteTd(batch, hash, num)
-		rawdb.DeleteCanonicalHash(batch, num)
 	}
 	// Flush all accumulated deletions.
 	if err := batch.Write(); err != nil {
diff --git a/core/headerchain_test.go b/core/headerchain_test.go
new file mode 100644
index 0000000000..0aa25efd1f
--- /dev/null
+++ b/core/headerchain_test.go
@@ -0,0 +1,115 @@
+// 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 .
+
+package core
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+func verifyUnbrokenCanonchain(hc *HeaderChain) error {
+	h := hc.CurrentHeader()
+	for {
+		canonHash := rawdb.ReadCanonicalHash(hc.chainDb, h.Number.Uint64())
+		if exp := h.Hash(); canonHash != exp {
+			return fmt.Errorf("Canon hash chain broken, block %d got %x, expected %x",
+				h.Number, canonHash[:8], exp[:8])
+		}
+		// Verify that we have the TD
+		if td := rawdb.ReadTd(hc.chainDb, canonHash, h.Number.Uint64()); td == nil {
+			return fmt.Errorf("Canon TD missing at block %d", h.Number)
+		}
+		if h.Number.Uint64() == 0 {
+			break
+		}
+		h = hc.GetHeader(h.ParentHash, h.Number.Uint64()-1)
+	}
+	return nil
+}
+
+func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error) {
+	t.Helper()
+
+	status, err := hc.InsertHeaderChain(chain, time.Now())
+	if status != wantStatus {
+		t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus)
+	}
+	// Always verify that the header chain is unbroken
+	if err := verifyUnbrokenCanonchain(hc); err != nil {
+		t.Fatal(err)
+	}
+	if !errors.Is(err, wantErr) {
+		t.Fatalf("unexpected error from InsertHeaderChain: %v", err)
+	}
+}
+
+// This test checks status reporting of InsertHeaderChain.
+func TestHeaderInsertion(t *testing.T) {
+	var (
+		db      = rawdb.NewMemoryDatabase()
+		genesis = new(Genesis).MustCommit(db)
+	)
+
+	hc, err := NewHeaderChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), func() bool { return false })
+	if err != nil {
+		t.Fatal(err)
+	}
+	// chain A: G->A1->A2...A128
+	chainA := makeHeaderChain(genesis.Header(), 128, ethash.NewFaker(), db, 10)
+	// chain B: G->A1->B2...B128
+	chainB := makeHeaderChain(chainA[0], 128, ethash.NewFaker(), db, 10)
+	log.Root().SetHandler(log.StdoutHandler)
+
+	// Inserting 64 headers on an empty chain, expecting
+	// 1 callbacks, 1 canon-status, 0 sidestatus,
+	testInsert(t, hc, chainA[:64], CanonStatTy, nil)
+
+	// Inserting 64 identical headers, expecting
+	// 0 callbacks, 0 canon-status, 0 sidestatus,
+	testInsert(t, hc, chainA[:64], NonStatTy, nil)
+
+	// Inserting the same some old, some new headers
+	// 1 callbacks, 1 canon, 0 side
+	testInsert(t, hc, chainA[32:96], CanonStatTy, nil)
+
+	// Inserting side blocks, but not overtaking the canon chain
+	testInsert(t, hc, chainB[0:32], SideStatTy, nil)
+
+	// Inserting more side blocks, but we don't have the parent
+	testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor)
+
+	// Inserting more sideblocks, overtaking the canon chain
+	testInsert(t, hc, chainB[32:97], CanonStatTy, nil)
+
+	// Inserting more A-headers, taking back the canonicality
+	testInsert(t, hc, chainA[90:100], CanonStatTy, nil)
+
+	// And B becomes canon again
+	testInsert(t, hc, chainB[97:107], CanonStatTy, nil)
+
+	// And B becomes even longer
+	testInsert(t, hc, chainB[107:128], CanonStatTy, nil)
+}
diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go
index 9fa327062a..76132bf37e 100644
--- a/core/rawdb/accessors_chain.go
+++ b/core/rawdb/accessors_chain.go
@@ -20,6 +20,7 @@ import (
 	"bytes"
 	"encoding/binary"
 	"math/big"
+	"sort"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
@@ -80,6 +81,39 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash {
 	return hashes
 }
 
+// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the
+// certain chain range. If the accumulated entries reaches the given threshold,
+// abort the iteration and return the semi-finish result.
+func ReadAllCanonicalHashes(db ethdb.Iteratee, from uint64, to uint64, limit int) ([]uint64, []common.Hash) {
+	// Short circuit if the limit is 0.
+	if limit == 0 {
+		return nil, nil
+	}
+	var (
+		numbers []uint64
+		hashes  []common.Hash
+	)
+	// Construct the key prefix of start point.
+	start, end := headerHashKey(from), headerHashKey(to)
+	it := db.NewIterator(nil, start)
+	defer it.Release()
+
+	for it.Next() {
+		if bytes.Compare(it.Key(), end) >= 0 {
+			break
+		}
+		if key := it.Key(); len(key) == len(headerPrefix)+8+1 && bytes.Equal(key[len(key)-1:], headerHashSuffix) {
+			numbers = append(numbers, binary.BigEndian.Uint64(key[len(headerPrefix):len(headerPrefix)+8]))
+			hashes = append(hashes, common.BytesToHash(it.Value()))
+			// If the accumulated entries reaches the limit threshold, return.
+			if len(numbers) >= limit {
+				break
+			}
+		}
+	}
+	return numbers, hashes
+}
+
 // ReadHeaderNumber returns the header number assigned to a hash.
 func ReadHeaderNumber(db ethdb.KeyValueReader, hash common.Hash) *uint64 {
 	data, _ := db.Get(headerNumberKey(hash))
@@ -154,6 +188,32 @@ func WriteHeadFastBlockHash(db ethdb.KeyValueWriter, hash common.Hash) {
 	}
 }
 
+// ReadLastPivotNumber retrieves the number of the last pivot block. If the node
+// full synced, the last pivot will always be nil.
+func ReadLastPivotNumber(db ethdb.KeyValueReader) *uint64 {
+	data, _ := db.Get(lastPivotKey)
+	if len(data) == 0 {
+		return nil
+	}
+	var pivot uint64
+	if err := rlp.DecodeBytes(data, &pivot); err != nil {
+		log.Error("Invalid pivot block number in database", "err", err)
+		return nil
+	}
+	return &pivot
+}
+
+// WriteLastPivotNumber stores the number of the last pivot block.
+func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) {
+	enc, err := rlp.EncodeToBytes(pivot)
+	if err != nil {
+		log.Crit("Failed to encode pivot block number", "err", err)
+	}
+	if err := db.Put(lastPivotKey, enc); err != nil {
+		log.Crit("Failed to store pivot block number", "err", err)
+	}
+}
+
 // ReadFastTrieProgress retrieves the number of tries nodes fast synced to allow
 // reporting correct numbers across restarts.
 func ReadFastTrieProgress(db ethdb.KeyValueReader) uint64 {
@@ -172,6 +232,43 @@ func WriteFastTrieProgress(db ethdb.KeyValueWriter, count uint64) {
 	}
 }
 
+// ReadTxIndexTail retrieves the number of oldest indexed block
+// whose transaction indices has been indexed. If the corresponding entry
+// is non-existent in database it means the indexing has been finished.
+func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 {
+	data, _ := db.Get(txIndexTailKey)
+	if len(data) != 8 {
+		return nil
+	}
+	number := binary.BigEndian.Uint64(data)
+	return &number
+}
+
+// WriteTxIndexTail stores the number of oldest indexed block
+// into database.
+func WriteTxIndexTail(db ethdb.KeyValueWriter, number uint64) {
+	if err := db.Put(txIndexTailKey, encodeBlockNumber(number)); err != nil {
+		log.Crit("Failed to store the transaction index tail", "err", err)
+	}
+}
+
+// ReadFastTxLookupLimit retrieves the tx lookup limit used in fast sync.
+func ReadFastTxLookupLimit(db ethdb.KeyValueReader) *uint64 {
+	data, _ := db.Get(fastTxLookupLimitKey)
+	if len(data) != 8 {
+		return nil
+	}
+	number := binary.BigEndian.Uint64(data)
+	return &number
+}
+
+// WriteFastTxLookupLimit stores the txlookup limit used in fast sync into database.
+func WriteFastTxLookupLimit(db ethdb.KeyValueWriter, number uint64) {
+	if err := db.Put(fastTxLookupLimitKey, encodeBlockNumber(number)); err != nil {
+		log.Crit("Failed to store transaction lookup limit for fast sync", "err", err)
+	}
+}
+
 // ReadHeaderRLP retrieves a block header in its raw RLP database encoding.
 func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
 	// First try to look up the data in ancient database. Extra hash
@@ -290,6 +387,25 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue
 	return nil // Can't find the data anywhere.
 }
 
+// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical
+// block at number, in RLP encoding.
+func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue {
+	// If it's an ancient one, we don't need the canonical hash
+	data, _ := db.Ancient(freezerBodiesTable, number)
+	if len(data) == 0 {
+		// Need to get the hash
+		data, _ = db.Get(blockBodyKey(number, ReadCanonicalHash(db, number)))
+		// In the background freezer is moving data from leveldb to flatten files.
+		// So during the first check for ancient db, the data is not yet in there,
+		// but when we reach into leveldb, the data was already moved. That would
+		// result in a not found error.
+		if len(data) == 0 {
+			data, _ = db.Ancient(freezerBodiesTable, number)
+		}
+	}
+	return data
+}
+
 // WriteBodyRLP stores an RLP encoded block body into the database.
 func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp rlp.RawValue) {
 	if err := db.Put(blockBodyKey(number, hash), rlp); err != nil {
@@ -587,6 +703,102 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number
 	DeleteTd(db, hash, number)
 }
 
+const badBlockToKeep = 10
+
+type badBlock struct {
+	Header *types.Header
+	Body   *types.Body
+}
+
+// badBlockList implements the sort interface to allow sorting a list of
+// bad blocks by their number in the reverse order.
+type badBlockList []*badBlock
+
+func (s badBlockList) Len() int { return len(s) }
+func (s badBlockList) Less(i, j int) bool {
+	return s[i].Header.Number.Uint64() < s[j].Header.Number.Uint64()
+}
+func (s badBlockList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// ReadBadBlock retrieves the bad block with the corresponding block hash.
+func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
+	blob, err := db.Get(badBlockKey)
+	if err != nil {
+		return nil
+	}
+	var badBlocks badBlockList
+	if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
+		return nil
+	}
+	for _, bad := range badBlocks {
+		if bad.Header.Hash() == hash {
+			return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)
+		}
+	}
+	return nil
+}
+
+// ReadAllBadBlocks retrieves all the bad blocks in the database.
+// All returned blocks are sorted in reverse order by number.
+func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
+	blob, err := db.Get(badBlockKey)
+	if err != nil {
+		return nil
+	}
+	var badBlocks badBlockList
+	if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
+		return nil
+	}
+	var blocks []*types.Block
+	for _, bad := range badBlocks {
+		blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles))
+	}
+	return blocks
+}
+
+// WriteBadBlock serializes the bad block into the database. If the cumulated
+// bad blocks exceeds the limitation, the oldest will be dropped.
+func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) {
+	blob, err := db.Get(badBlockKey)
+	if err != nil {
+		log.Warn("Failed to load old bad blocks", "error", err)
+	}
+	var badBlocks badBlockList
+	if len(blob) > 0 {
+		if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
+			log.Crit("Failed to decode old bad blocks", "error", err)
+		}
+	}
+	for _, b := range badBlocks {
+		if b.Header.Number.Uint64() == block.NumberU64() && b.Header.Hash() == block.Hash() {
+			log.Info("Skip duplicated bad block", "number", block.NumberU64(), "hash", block.Hash())
+			return
+		}
+	}
+	badBlocks = append(badBlocks, &badBlock{
+		Header: block.Header(),
+		Body:   block.Body(),
+	})
+	sort.Sort(sort.Reverse(badBlocks))
+	if len(badBlocks) > badBlockToKeep {
+		badBlocks = badBlocks[:badBlockToKeep]
+	}
+	data, err := rlp.EncodeToBytes(badBlocks)
+	if err != nil {
+		log.Crit("Failed to encode bad blocks", "err", err)
+	}
+	if err := db.Put(badBlockKey, data); err != nil {
+		log.Crit("Failed to write bad blocks", "err", err)
+	}
+}
+
+// DeleteBadBlocks deletes all the bad blocks from the database
+func DeleteBadBlocks(db ethdb.KeyValueWriter) {
+	if err := db.Delete(badBlockKey); err != nil {
+		log.Crit("Failed to delete bad blocks", "err", err)
+	}
+}
+
 // FindCommonAncestor returns the last common ancestor of two block headers
 func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header {
 	for bn := b.Number.Uint64(); a.Number.Uint64() > bn; {
@@ -613,3 +825,29 @@ func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header {
 	}
 	return a
 }
+
+// ReadHeadHeader returns the current canonical head header.
+func ReadHeadHeader(db ethdb.Reader) *types.Header {
+	headHeaderHash := ReadHeadHeaderHash(db)
+	if headHeaderHash == (common.Hash{}) {
+		return nil
+	}
+	headHeaderNumber := ReadHeaderNumber(db, headHeaderHash)
+	if headHeaderNumber == nil {
+		return nil
+	}
+	return ReadHeader(db, headHeaderHash, *headHeaderNumber)
+}
+
+// ReadHeadBlock returns the current canonical head block.
+func ReadHeadBlock(db ethdb.Reader) *types.Block {
+	headBlockHash := ReadHeadBlockHash(db)
+	if headBlockHash == (common.Hash{}) {
+		return nil
+	}
+	headBlockNumber := ReadHeaderNumber(db, headBlockHash)
+	if headBlockNumber == nil {
+		return nil
+	}
+	return ReadBlock(db, headBlockHash, *headBlockNumber)
+}
diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go
index bb7dad5dff..ea9dc436cf 100644
--- a/core/rawdb/accessors_chain_test.go
+++ b/core/rawdb/accessors_chain_test.go
@@ -22,7 +22,9 @@ import (
 	"fmt"
 	"io/ioutil"
 	"math/big"
+	"math/rand"
 	"os"
+	"reflect"
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -83,7 +85,7 @@ func TestBodyStorage(t *testing.T) {
 	WriteBody(db, hash, 0, body)
 	if entry := ReadBody(db, hash, 0); entry == nil {
 		t.Fatalf("Stored body not found")
-	} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(types.Transactions(body.Transactions)) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
+	} else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
 		t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body)
 	}
 	if entry := ReadBodyRLP(db, hash, 0); entry == nil {
@@ -137,7 +139,7 @@ func TestBlockStorage(t *testing.T) {
 	}
 	if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil {
 		t.Fatalf("Stored body not found")
-	} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(block.Transactions()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
+	} else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(block.Transactions(), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
 		t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body())
 	}
 	// Delete the block and verify the execution
@@ -187,6 +189,75 @@ func TestPartialBlockStorage(t *testing.T) {
 	}
 }
 
+// Tests block storage and retrieval operations.
+func TestBadBlockStorage(t *testing.T) {
+	db := NewMemoryDatabase()
+
+	// Create a test block to move around the database and make sure it's really new
+	block := types.NewBlockWithHeader(&types.Header{
+		Number:      big.NewInt(1),
+		Extra:       []byte("bad block"),
+		UncleHash:   types.EmptyUncleHash,
+		TxHash:      types.EmptyRootHash,
+		ReceiptHash: types.EmptyRootHash,
+	})
+	if entry := ReadBadBlock(db, block.Hash()); entry != nil {
+		t.Fatalf("Non existent block returned: %v", entry)
+	}
+	// Write and verify the block in the database
+	WriteBadBlock(db, block)
+	if entry := ReadBadBlock(db, block.Hash()); entry == nil {
+		t.Fatalf("Stored block not found")
+	} else if entry.Hash() != block.Hash() {
+		t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block)
+	}
+	// Write one more bad block
+	blockTwo := types.NewBlockWithHeader(&types.Header{
+		Number:      big.NewInt(2),
+		Extra:       []byte("bad block two"),
+		UncleHash:   types.EmptyUncleHash,
+		TxHash:      types.EmptyRootHash,
+		ReceiptHash: types.EmptyRootHash,
+	})
+	WriteBadBlock(db, blockTwo)
+
+	// Write the block one again, should be filtered out.
+	WriteBadBlock(db, block)
+	badBlocks := ReadAllBadBlocks(db)
+	if len(badBlocks) != 2 {
+		t.Fatalf("Failed to load all bad blocks")
+	}
+
+	// Write a bunch of bad blocks, all the blocks are should sorted
+	// in reverse order. The extra blocks should be truncated.
+	for _, n := range rand.Perm(100) {
+		block := types.NewBlockWithHeader(&types.Header{
+			Number:      big.NewInt(int64(n)),
+			Extra:       []byte("bad block"),
+			UncleHash:   types.EmptyUncleHash,
+			TxHash:      types.EmptyRootHash,
+			ReceiptHash: types.EmptyRootHash,
+		})
+		WriteBadBlock(db, block)
+	}
+	badBlocks = ReadAllBadBlocks(db)
+	if len(badBlocks) != badBlockToKeep {
+		t.Fatalf("The number of persised bad blocks in incorrect %d", len(badBlocks))
+	}
+	for i := 0; i < len(badBlocks)-1; i++ {
+		if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() {
+			t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64())
+		}
+	}
+
+	// Delete all bad blocks
+	DeleteBadBlocks(db)
+	badBlocks = ReadAllBadBlocks(db)
+	if len(badBlocks) != 0 {
+		t.Fatalf("Failed to delete bad blocks")
+	}
+}
+
 // Tests block total difficulty storage and retrieval operations.
 func TestTdStorage(t *testing.T) {
 	db := NewMemoryDatabase()
@@ -369,7 +440,7 @@ func TestAncientStorage(t *testing.T) {
 	}
 	defer os.Remove(frdir)
 
-	db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "")
+	db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
 	if err != nil {
 		t.Fatalf("failed to create database with ancient backend")
 	}
@@ -424,3 +495,35 @@ func TestAncientStorage(t *testing.T) {
 		t.Fatalf("invalid td returned")
 	}
 }
+
+func TestCanonicalHashIteration(t *testing.T) {
+	var cases = []struct {
+		from, to uint64
+		limit    int
+		expect   []uint64
+	}{
+		{1, 8, 0, nil},
+		{1, 8, 1, []uint64{1}},
+		{1, 8, 10, []uint64{1, 2, 3, 4, 5, 6, 7}},
+		{1, 9, 10, []uint64{1, 2, 3, 4, 5, 6, 7, 8}},
+		{2, 9, 10, []uint64{2, 3, 4, 5, 6, 7, 8}},
+		{9, 10, 10, nil},
+	}
+	// Test empty db iteration
+	db := NewMemoryDatabase()
+	numbers, _ := ReadAllCanonicalHashes(db, 0, 10, 10)
+	if len(numbers) != 0 {
+		t.Fatalf("No entry should be returned to iterate an empty db")
+	}
+	// Fill database with testing data.
+	for i := uint64(1); i <= 8; i++ {
+		WriteCanonicalHash(db, common.Hash{}, i)
+		WriteTd(db, common.Hash{}, i, big.NewInt(10)) // Write some interferential data
+	}
+	for i, c := range cases {
+		numbers, _ := ReadAllCanonicalHashes(db, c.from, c.to, c.limit)
+		if !reflect.DeepEqual(numbers, c.expect) {
+			t.Fatalf("Case %d failed, want %v, got %v", i, c.expect, numbers)
+		}
+	}
+}
diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go
index 38f8fe10ea..d6dab6808d 100644
--- a/core/rawdb/accessors_indexes.go
+++ b/core/rawdb/accessors_indexes.go
@@ -17,6 +17,7 @@
 package rawdb
 
 import (
+	"bytes"
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -52,20 +53,44 @@ func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 {
 	return &entry.BlockIndex
 }
 
-// WriteTxLookupEntries stores a positional metadata for every transaction from
+// writeTxLookupEntry stores a positional metadata for a transaction,
+// enabling hash based transaction and receipt lookups.
+func writeTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash, numberBytes []byte) {
+	if err := db.Put(txLookupKey(hash), numberBytes); err != nil {
+		log.Crit("Failed to store transaction lookup entry", "err", err)
+	}
+}
+
+// WriteTxLookupEntries is identical to WriteTxLookupEntry, but it works on
+// a list of hashes
+func WriteTxLookupEntries(db ethdb.KeyValueWriter, number uint64, hashes []common.Hash) {
+	numberBytes := new(big.Int).SetUint64(number).Bytes()
+	for _, hash := range hashes {
+		writeTxLookupEntry(db, hash, numberBytes)
+	}
+}
+
+// WriteTxLookupEntriesByBlock stores a positional metadata for every transaction from
 // a block, enabling hash based transaction and receipt lookups.
-func WriteTxLookupEntries(db ethdb.KeyValueWriter, block *types.Block) {
-	number := block.Number().Bytes()
+func WriteTxLookupEntriesByBlock(db ethdb.KeyValueWriter, block *types.Block) {
+	numberBytes := block.Number().Bytes()
 	for _, tx := range block.Transactions() {
-		if err := db.Put(txLookupKey(tx.Hash()), number); err != nil {
-			log.Crit("Failed to store transaction lookup entry", "err", err)
-		}
+		writeTxLookupEntry(db, tx.Hash(), numberBytes)
 	}
 }
 
 // DeleteTxLookupEntry removes all transaction data associated with a hash.
 func DeleteTxLookupEntry(db ethdb.KeyValueWriter, hash common.Hash) {
-	db.Delete(txLookupKey(hash))
+	if err := db.Delete(txLookupKey(hash)); err != nil {
+		log.Crit("Failed to delete transaction lookup entry", "err", err)
+	}
+}
+
+// DeleteTxLookupEntries removes all transaction lookups for a given block.
+func DeleteTxLookupEntries(db ethdb.KeyValueWriter, hashes []common.Hash) {
+	for _, hash := range hashes {
+		DeleteTxLookupEntry(db, hash)
+	}
 }
 
 // ReadTransaction retrieves a specific transaction from the database, along with
@@ -129,3 +154,24 @@ func WriteBloomBits(db ethdb.KeyValueWriter, bit uint, section uint64, head comm
 		log.Crit("Failed to store bloom bits", "err", err)
 	}
 }
+
+// DeleteBloombits removes all compressed bloom bits vector belonging to the
+// given section range and bit index.
+func DeleteBloombits(db ethdb.Database, bit uint, from uint64, to uint64) {
+	start, end := bloomBitsKey(bit, from, common.Hash{}), bloomBitsKey(bit, to, common.Hash{})
+	it := db.NewIterator(nil, start)
+	defer it.Release()
+
+	for it.Next() {
+		if bytes.Compare(it.Key(), end) >= 0 {
+			break
+		}
+		if len(it.Key()) != len(bloomBitsPrefix)+2+8+32 {
+			continue
+		}
+		db.Delete(it.Key())
+	}
+	if it.Error() != nil {
+		log.Crit("Failed to delete bloom bits", "err", it.Error())
+	}
+}
diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go
index c09bff0101..4734e986e2 100644
--- a/core/rawdb/accessors_indexes_test.go
+++ b/core/rawdb/accessors_indexes_test.go
@@ -17,25 +17,53 @@
 package rawdb
 
 import (
+	"bytes"
+	"hash"
 	"math/big"
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
+	"golang.org/x/crypto/sha3"
 )
 
+// testHasher is the helper tool for transaction/receipt list hashing.
+// The original hasher is trie, in order to get rid of import cycle,
+// use the testing hasher instead.
+type testHasher struct {
+	hasher hash.Hash
+}
+
+func newHasher() *testHasher {
+	return &testHasher{hasher: sha3.NewLegacyKeccak256()}
+}
+
+func (h *testHasher) Reset() {
+	h.hasher.Reset()
+}
+
+func (h *testHasher) Update(key, val []byte) {
+	h.hasher.Write(key)
+	h.hasher.Write(val)
+}
+
+func (h *testHasher) Hash() common.Hash {
+	return common.BytesToHash(h.hasher.Sum(nil))
+}
+
 // Tests that positional lookup metadata can be stored and retrieved.
 func TestLookupStorage(t *testing.T) {
 	tests := []struct {
-		name                 string
-		writeTxLookupEntries func(ethdb.Writer, *types.Block)
+		name                        string
+		writeTxLookupEntriesByBlock func(ethdb.Writer, *types.Block)
 	}{
 		{
 			"DatabaseV6",
 			func(db ethdb.Writer, block *types.Block) {
-				WriteTxLookupEntries(db, block)
+				WriteTxLookupEntriesByBlock(db, block)
 			},
 		},
 		{
@@ -71,7 +99,7 @@ func TestLookupStorage(t *testing.T) {
 			tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
 			txs := []*types.Transaction{tx1, tx2, tx3}
 
-			block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil)
+			block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newHasher())
 
 			// Check that no transactions entries are in a pristine database
 			for i, tx := range txs {
@@ -82,7 +110,7 @@ func TestLookupStorage(t *testing.T) {
 			// Insert all the transactions into the database, and verify contents
 			WriteCanonicalHash(db, block.Hash(), block.NumberU64())
 			WriteBlock(db, block)
-			tc.writeTxLookupEntries(db, block)
+			tc.writeTxLookupEntriesByBlock(db, block)
 
 			for i, tx := range txs {
 				if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil {
@@ -106,3 +134,46 @@ func TestLookupStorage(t *testing.T) {
 		})
 	}
 }
+
+func TestDeleteBloomBits(t *testing.T) {
+	// Prepare testing data
+	db := NewMemoryDatabase()
+	for i := uint(0); i < 2; i++ {
+		for s := uint64(0); s < 2; s++ {
+			WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02})
+			WriteBloomBits(db, i, s, params.RinkebyGenesisHash, []byte{0x01, 0x02})
+		}
+	}
+	check := func(bit uint, section uint64, head common.Hash, exist bool) {
+		bits, _ := ReadBloomBits(db, bit, section, head)
+		if exist && !bytes.Equal(bits, []byte{0x01, 0x02}) {
+			t.Fatalf("Bloombits mismatch")
+		}
+		if !exist && len(bits) > 0 {
+			t.Fatalf("Bloombits should be removed")
+		}
+	}
+	// Check the existence of written data.
+	check(0, 0, params.MainnetGenesisHash, true)
+	check(0, 0, params.RinkebyGenesisHash, true)
+
+	// Check the existence of deleted data.
+	DeleteBloombits(db, 0, 0, 1)
+	check(0, 0, params.MainnetGenesisHash, false)
+	check(0, 0, params.RinkebyGenesisHash, false)
+	check(0, 1, params.MainnetGenesisHash, true)
+	check(0, 1, params.RinkebyGenesisHash, true)
+
+	// Check the existence of deleted data.
+	DeleteBloombits(db, 0, 0, 2)
+	check(0, 0, params.MainnetGenesisHash, false)
+	check(0, 0, params.RinkebyGenesisHash, false)
+	check(0, 1, params.MainnetGenesisHash, false)
+	check(0, 1, params.RinkebyGenesisHash, false)
+
+	// Bit1 shouldn't be affect.
+	check(1, 0, params.MainnetGenesisHash, true)
+	check(1, 0, params.RinkebyGenesisHash, true)
+	check(1, 1, params.MainnetGenesisHash, true)
+	check(1, 1, params.RinkebyGenesisHash, true)
+}
diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go
index f8d09fbddf..079e335fa6 100644
--- a/core/rawdb/accessors_metadata.go
+++ b/core/rawdb/accessors_metadata.go
@@ -18,6 +18,7 @@ package rawdb
 
 import (
 	"encoding/json"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/ethdb"
@@ -30,7 +31,7 @@ import (
 func ReadDatabaseVersion(db ethdb.KeyValueReader) *uint64 {
 	var version uint64
 
-	enc, _ := db.Get(databaseVerisionKey)
+	enc, _ := db.Get(databaseVersionKey)
 	if len(enc) == 0 {
 		return nil
 	}
@@ -47,7 +48,7 @@ func WriteDatabaseVersion(db ethdb.KeyValueWriter, version uint64) {
 	if err != nil {
 		log.Crit("Failed to encode database version", "err", err)
 	}
-	if err = db.Put(databaseVerisionKey, enc); err != nil {
+	if err = db.Put(databaseVersionKey, enc); err != nil {
 		log.Crit("Failed to store the database version", "err", err)
 	}
 }
@@ -80,19 +81,60 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
 	}
 }
 
-// ReadPreimage retrieves a single preimage of the provided hash.
-func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
-	data, _ := db.Get(preimageKey(hash))
-	return data
+// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the
+// database
+type crashList struct {
+	Discarded uint64   // how many ucs have we deleted
+	Recent    []uint64 // unix timestamps of 10 latest unclean shutdowns
 }
 
-// WritePreimages writes the provided set of preimages to the database.
-func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
-	for hash, preimage := range preimages {
-		if err := db.Put(preimageKey(hash), preimage); err != nil {
-			log.Crit("Failed to store trie preimage", "err", err)
-		}
+const crashesToKeep = 10
+
+// PushUncleanShutdownMarker appends a new unclean shutdown marker and returns
+// the previous data
+// - a list of timestamps
+// - a count of how many old unclean-shutdowns have been discarded
+func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) {
+	var uncleanShutdowns crashList
+	// Read old data
+	if data, err := db.Get(uncleanShutdownKey); err != nil {
+		log.Warn("Error reading unclean shutdown markers", "error", err)
+	} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
+		return nil, 0, err
+	}
+	var discarded = uncleanShutdowns.Discarded
+	var previous = make([]uint64, len(uncleanShutdowns.Recent))
+	copy(previous, uncleanShutdowns.Recent)
+	// Add a new (but cap it)
+	uncleanShutdowns.Recent = append(uncleanShutdowns.Recent, uint64(time.Now().Unix()))
+	if count := len(uncleanShutdowns.Recent); count > crashesToKeep+1 {
+		numDel := count - (crashesToKeep + 1)
+		uncleanShutdowns.Recent = uncleanShutdowns.Recent[numDel:]
+		uncleanShutdowns.Discarded += uint64(numDel)
+	}
+	// And save it again
+	data, _ := rlp.EncodeToBytes(uncleanShutdowns)
+	if err := db.Put(uncleanShutdownKey, data); err != nil {
+		log.Warn("Failed to write unclean-shutdown marker", "err", err)
+		return nil, 0, err
+	}
+	return previous, discarded, nil
+}
+
+// PopUncleanShutdownMarker removes the last unclean shutdown marker
+func PopUncleanShutdownMarker(db ethdb.KeyValueStore) {
+	var uncleanShutdowns crashList
+	// Read old data
+	if data, err := db.Get(uncleanShutdownKey); err != nil {
+		log.Warn("Error reading unclean shutdown markers", "error", err)
+	} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
+		log.Error("Error decoding unclean shutdown markers", "error", err) // Should mos def _not_ happen
+	}
+	if l := len(uncleanShutdowns.Recent); l > 0 {
+		uncleanShutdowns.Recent = uncleanShutdowns.Recent[:l-1]
+	}
+	data, _ := rlp.EncodeToBytes(uncleanShutdowns)
+	if err := db.Put(uncleanShutdownKey, data); err != nil {
+		log.Warn("Failed to clear unclean-shutdown marker", "err", err)
 	}
-	preimageCounter.Inc(int64(len(preimages)))
-	preimageHitCounter.Inc(int64(len(preimages)))
 }
diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go
index ecd4e65978..88446e0792 100644
--- a/core/rawdb/accessors_snapshot.go
+++ b/core/rawdb/accessors_snapshot.go
@@ -17,11 +17,33 @@
 package rawdb
 
 import (
+	"encoding/binary"
+
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 )
 
+// ReadSnapshotDisabled retrieves if the snapshot maintenance is disabled.
+func ReadSnapshotDisabled(db ethdb.KeyValueReader) bool {
+	disabled, _ := db.Has(snapshotDisabledKey)
+	return disabled
+}
+
+// WriteSnapshotDisabled stores the snapshot pause flag.
+func WriteSnapshotDisabled(db ethdb.KeyValueWriter) {
+	if err := db.Put(snapshotDisabledKey, []byte("42")); err != nil {
+		log.Crit("Failed to store snapshot disabled flag", "err", err)
+	}
+}
+
+// DeleteSnapshotDisabled deletes the flag keeping the snapshot maintenance disabled.
+func DeleteSnapshotDisabled(db ethdb.KeyValueWriter) {
+	if err := db.Delete(snapshotDisabledKey); err != nil {
+		log.Crit("Failed to remove snapshot disabled flag", "err", err)
+	}
+}
+
 // ReadSnapshotRoot retrieves the root of the block whose state is contained in
 // the persisted snapshot.
 func ReadSnapshotRoot(db ethdb.KeyValueReader) common.Hash {
@@ -118,3 +140,79 @@ func DeleteSnapshotJournal(db ethdb.KeyValueWriter) {
 		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)
+	}
+}
+
+// ReadSnapshotSyncStatus retrieves the serialized sync status saved at shutdown.
+func ReadSnapshotSyncStatus(db ethdb.KeyValueReader) []byte {
+	data, _ := db.Get(snapshotSyncStatusKey)
+	return data
+}
+
+// WriteSnapshotSyncStatus stores the serialized sync status to save at shutdown.
+func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) {
+	if err := db.Put(snapshotSyncStatusKey, status); err != nil {
+		log.Crit("Failed to store snapshot sync status", "err", err)
+	}
+}
+
+// DeleteSnapshotSyncStatus deletes the serialized sync status saved at the last
+// shutdown
+func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) {
+	if err := db.Delete(snapshotSyncStatusKey); err != nil {
+		log.Crit("Failed to remove snapshot sync status", "err", err)
+	}
+}
diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go
new file mode 100644
index 0000000000..6112de03ad
--- /dev/null
+++ b/core/rawdb/accessors_state.go
@@ -0,0 +1,96 @@
+// 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 .
+
+package rawdb
+
+import (
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+)
+
+// ReadPreimage retrieves a single preimage of the provided hash.
+func ReadPreimage(db ethdb.KeyValueReader, hash common.Hash) []byte {
+	data, _ := db.Get(preimageKey(hash))
+	return data
+}
+
+// WritePreimages writes the provided set of preimages to the database.
+func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) {
+	for hash, preimage := range preimages {
+		if err := db.Put(preimageKey(hash), preimage); err != nil {
+			log.Crit("Failed to store trie preimage", "err", err)
+		}
+	}
+	preimageCounter.Inc(int64(len(preimages)))
+	preimageHitCounter.Inc(int64(len(preimages)))
+}
+
+// ReadCode retrieves the contract code of the provided code hash.
+func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte {
+	// Try with the legacy code scheme first, if not then try with current
+	// scheme. Since most of the code will be found with legacy scheme.
+	//
+	// todo(rjl493456442) change the order when we forcibly upgrade the code
+	// scheme with snapshot.
+	data, _ := db.Get(hash[:])
+	if len(data) != 0 {
+		return data
+	}
+	return ReadCodeWithPrefix(db, hash)
+}
+
+// ReadCodeWithPrefix retrieves the contract code of the provided code hash.
+// The main difference between this function and ReadCode is this function
+// will only check the existence with latest scheme(with prefix).
+func ReadCodeWithPrefix(db ethdb.KeyValueReader, hash common.Hash) []byte {
+	data, _ := db.Get(codeKey(hash))
+	return data
+}
+
+// WriteCode writes the provided contract code database.
+func WriteCode(db ethdb.KeyValueWriter, hash common.Hash, code []byte) {
+	if err := db.Put(codeKey(hash), code); err != nil {
+		log.Crit("Failed to store contract code", "err", err)
+	}
+}
+
+// DeleteCode deletes the specified contract code from the database.
+func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) {
+	if err := db.Delete(codeKey(hash)); err != nil {
+		log.Crit("Failed to delete contract code", "err", err)
+	}
+}
+
+// ReadTrieNode retrieves the trie node of the provided hash.
+func ReadTrieNode(db ethdb.KeyValueReader, hash common.Hash) []byte {
+	data, _ := db.Get(hash.Bytes())
+	return data
+}
+
+// WriteTrieNode writes the provided trie node database.
+func WriteTrieNode(db ethdb.KeyValueWriter, hash common.Hash, node []byte) {
+	if err := db.Put(hash.Bytes(), node); err != nil {
+		log.Crit("Failed to store trie node", "err", err)
+	}
+}
+
+// DeleteTrieNode deletes the specified trie node from the database.
+func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
+	if err := db.Delete(hash.Bytes()); err != nil {
+		log.Crit("Failed to delete trie node", "err", err)
+	}
+}
diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go
new file mode 100644
index 0000000000..ad222005be
--- /dev/null
+++ b/core/rawdb/chain_iterator.go
@@ -0,0 +1,347 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package rawdb
+
+import (
+	"runtime"
+	"sync/atomic"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/prque"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// InitDatabaseFromFreezer reinitializes an empty database from a previous batch
+// of frozen ancient blocks. The method iterates over all the frozen blocks and
+// injects into the database the block hash->number mappings.
+func InitDatabaseFromFreezer(db ethdb.Database) {
+	// If we can't access the freezer or it's empty, abort
+	frozen, err := db.Ancients()
+	if err != nil || frozen == 0 {
+		return
+	}
+	var (
+		batch  = db.NewBatch()
+		start  = time.Now()
+		logged = start.Add(-7 * time.Second) // Unindex during import is fast, don't double log
+		hash   common.Hash
+	)
+	for i := uint64(0); i < frozen; i++ {
+		// Since the freezer has all data in sequential order on a file,
+		// it would be 'neat' to read more data in one go, and let the
+		// freezerdb return N items (e.g up to 1000 items per go)
+		// That would require an API change in Ancients though
+		if h, err := db.Ancient(freezerHashTable, i); err != nil {
+			log.Crit("Failed to init database from freezer", "err", err)
+		} else {
+			hash = common.BytesToHash(h)
+		}
+		WriteHeaderNumber(batch, hash, i)
+		// If enough data was accumulated in memory or we're at the last block, dump to disk
+		if batch.ValueSize() > ethdb.IdealBatchSize {
+			if err := batch.Write(); err != nil {
+				log.Crit("Failed to write data to db", "err", err)
+			}
+			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("Initializing database from freezer", "total", frozen, "number", i, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
+			logged = time.Now()
+		}
+	}
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to write data to db", "err", err)
+	}
+	batch.Reset()
+
+	WriteHeadHeaderHash(db, hash)
+	WriteHeadFastBlockHash(db, hash)
+	log.Info("Initialized database from freezer", "blocks", frozen, "elapsed", common.PrettyDuration(time.Since(start)))
+}
+
+type blockTxHashes struct {
+	number uint64
+	hashes []common.Hash
+}
+
+// iterateTransactions iterates over all transactions in the (canon) block
+// number(s) given, and yields the hashes on a channel. If there is a signal
+// 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
+	type numberRlp struct {
+		number uint64
+		rlp    rlp.RawValue
+	}
+	if to == from {
+		return nil
+	}
+	threads := to - from
+	if cpus := runtime.NumCPU(); threads > uint64(cpus) {
+		threads = uint64(cpus)
+	}
+	var (
+		rlpCh    = make(chan *numberRlp, threads*2)     // we send raw rlp over this channel
+		hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh
+	)
+	// lookup runs in one instance
+	lookup := func() {
+		n, end := from, to
+		if reverse {
+			n, end = to-1, from-1
+		}
+		defer close(rlpCh)
+		for n != end {
+			data := ReadCanonicalBodyRLP(db, n)
+			// Feed the block to the aggregator, or abort on interrupt
+			select {
+			case rlpCh <- &numberRlp{n, data}:
+			case <-interrupt:
+				return
+			}
+			if reverse {
+				n--
+			} else {
+				n++
+			}
+		}
+	}
+	// process runs in parallel
+	nThreadsAlive := int32(threads)
+	process := func() {
+		defer func() {
+			// Last processor closes the result channel
+			if atomic.AddInt32(&nThreadsAlive, -1) == 0 {
+				close(hashesCh)
+			}
+		}()
+		for data := range rlpCh {
+			var body types.Body
+			if err := rlp.DecodeBytes(data.rlp, &body); err != nil {
+				log.Warn("Failed to decode block body", "block", data.number, "error", err)
+				return
+			}
+			var hashes []common.Hash
+			for _, tx := range body.Transactions {
+				hashes = append(hashes, tx.Hash())
+			}
+			result := &blockTxHashes{
+				hashes: hashes,
+				number: data.number,
+			}
+			// Feed the block to the aggregator, or abort on interrupt
+			select {
+			case hashesCh <- result:
+			case <-interrupt:
+				return
+			}
+		}
+	}
+	go lookup() // start the sequential db accessor
+	for i := 0; i < int(threads); i++ {
+		go process()
+	}
+	return hashesCh
+}
+
+// indexTransactions creates txlookup indices of the specified block range.
+//
+// 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{}, hook func(uint64) bool) {
+	// short circuit for invalid range
+	if from >= to {
+		return
+	}
+	var (
+		hashesCh = iterateTransactions(db, from, to, true, interrupt)
+		batch    = db.NewBatch()
+		start    = time.Now()
+		logged   = start.Add(-7 * time.Second)
+		// Since we iterate in reverse, we expect the first number to come
+		// in to be [to-1]. Therefore, setting lastNum to means that the
+		// prqueue gap-evaluation will work correctly
+		lastNum = to
+		queue   = prque.New(nil)
+		// for stats reporting
+		blocks, txs = 0, 0
+	)
+	for chanDelivery := range hashesCh {
+		// Push the delivery into the queue and process contiguous ranges.
+		// Since we iterate in reverse, so lower numbers have lower prio, and
+		// we can use the number directly as prio marker
+		queue.Push(chanDelivery, int64(chanDelivery.number))
+		for !queue.Empty() {
+			// If the next available item is gapped, return
+			if _, priority := queue.Peek(); priority != int64(lastNum-1) {
+				break
+			}
+			// For testing
+			if hook != nil && !hook(lastNum-1) {
+				break
+			}
+			// Next block available, pop it off and index it
+			delivery := queue.PopItem().(*blockTxHashes)
+			lastNum = delivery.number
+			WriteTxLookupEntries(batch, delivery.number, delivery.hashes)
+			blocks++
+			txs += len(delivery.hashes)
+			// If enough data was accumulated in memory or we're at the last block, dump to disk
+			if batch.ValueSize() > ethdb.IdealBatchSize {
+				WriteTxIndexTail(batch, lastNum) // Also write the tail here
+				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("Indexing transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start)))
+				logged = time.Now()
+			}
+		}
+	}
+	// Flush the new indexing tail and the last committed data. It can also happen
+	// that the last batch is empty because nothing to index, but the tail has to
+	// be flushed anyway.
+	WriteTxIndexTail(batch, lastNum)
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed writing batch to db", "error", err)
+		return
+	}
+	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)))
+	}
+}
+
+// IndexTransactions creates txlookup indices of the specified block range.
+//
+// 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
+	if from >= to {
+		return
+	}
+	var (
+		hashesCh = iterateTransactions(db, from, to, false, interrupt)
+		batch    = db.NewBatch()
+		start    = time.Now()
+		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
+	)
+	// Otherwise spin up the concurrent iterator and unindexer
+	for delivery := range hashesCh {
+		// Push the delivery into the queue and process contiguous ranges.
+		queue.Push(delivery, -int64(delivery.number))
+		for !queue.Empty() {
+			// If the next available item is gapped, return
+			if _, priority := queue.Peek(); -priority != int64(nextNum) {
+				break
+			}
+			// For testing
+			if hook != nil && !hook(nextNum) {
+				break
+			}
+			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()
+			}
+		}
+	}
+	// Flush the new indexing tail and the last committed data. It can also happen
+	// that the last batch is empty because nothing to unindex, but the tail has to
+	// be flushed anyway.
+	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)))
+	}
+}
+
+// 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)
+}
diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go
new file mode 100644
index 0000000000..45cc6323e0
--- /dev/null
+++ b/core/rawdb/chain_iterator_test.go
@@ -0,0 +1,208 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package rawdb
+
+import (
+	"math/big"
+	"reflect"
+	"sort"
+	"sync"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+func TestChainIterator(t *testing.T) {
+	// Construct test chain db
+	chainDb := NewMemoryDatabase()
+
+	var block *types.Block
+	var txs []*types.Transaction
+	to := common.BytesToAddress([]byte{0x11})
+	block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block
+	WriteBlock(chainDb, block)
+	WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
+	for i := uint64(1); i <= 10; i++ {
+		var tx *types.Transaction
+		if i%2 == 0 {
+			tx = types.NewTx(&types.LegacyTx{
+				Nonce:    i,
+				GasPrice: big.NewInt(11111),
+				Gas:      1111,
+				To:       &to,
+				Value:    big.NewInt(111),
+				Data:     []byte{0x11, 0x11, 0x11},
+			})
+		} else {
+			tx = types.NewTx(&types.AccessListTx{
+				ChainID:  big.NewInt(1337),
+				Nonce:    i,
+				GasPrice: big.NewInt(11111),
+				Gas:      1111,
+				To:       &to,
+				Value:    big.NewInt(111),
+				Data:     []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())
+	}
+
+	var cases = []struct {
+		from, to uint64
+		reverse  bool
+		expect   []int
+	}{
+		{0, 11, true, []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}},
+		{0, 0, true, nil},
+		{0, 5, true, []int{4, 3, 2, 1, 0}},
+		{10, 11, true, []int{10}},
+		{0, 11, false, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
+		{0, 0, false, nil},
+		{10, 11, false, []int{10}},
+	}
+	for i, c := range cases {
+		var numbers []int
+		hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil)
+		if hashCh != nil {
+			for h := range hashCh {
+				numbers = append(numbers, int(h.number))
+				if len(h.hashes) > 0 {
+					if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp {
+						t.Fatalf("block %d: hash wrong, got %x exp %x", h.number, got, exp)
+					}
+				}
+			}
+		}
+		if !c.reverse {
+			sort.Ints(numbers)
+		} else {
+			sort.Sort(sort.Reverse(sort.IntSlice(numbers)))
+		}
+		if !reflect.DeepEqual(numbers, c.expect) {
+			t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, numbers)
+		}
+	}
+}
+
+func TestIndexTransactions(t *testing.T) {
+	// Construct test chain db
+	chainDb := NewMemoryDatabase()
+
+	var block *types.Block
+	var txs []*types.Transaction
+	to := common.BytesToAddress([]byte{0x11})
+
+	// Write empty genesis block
+	block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher())
+	WriteBlock(chainDb, block)
+	WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
+
+	for i := uint64(1); i <= 10; i++ {
+		var tx *types.Transaction
+		if i%2 == 0 {
+			tx = types.NewTx(&types.LegacyTx{
+				Nonce:    i,
+				GasPrice: big.NewInt(11111),
+				Gas:      1111,
+				To:       &to,
+				Value:    big.NewInt(111),
+				Data:     []byte{0x11, 0x11, 0x11},
+			})
+		} else {
+			tx = types.NewTx(&types.AccessListTx{
+				ChainID:  big.NewInt(1337),
+				Nonce:    i,
+				GasPrice: big.NewInt(11111),
+				Gas:      1111,
+				To:       &to,
+				Value:    big.NewInt(111),
+				Data:     []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 index %d missing", i)
+			}
+			if !exist && number != nil {
+				t.Fatalf("Transaction index %d is not deleted", i)
+			}
+		}
+		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)
+}
diff --git a/core/rawdb/database.go b/core/rawdb/database.go
index e4b04b33e3..725972f9ba 100644
--- a/core/rawdb/database.go
+++ b/core/rawdb/database.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"sync/atomic"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -41,10 +42,10 @@ type freezerdb struct {
 // the slow ancient tables.
 func (frdb *freezerdb) Close() error {
 	var errs []error
-	if err := frdb.KeyValueStore.Close(); err != nil {
+	if err := frdb.AncientStore.Close(); err != nil {
 		errs = append(errs, err)
 	}
-	if err := frdb.AncientStore.Close(); err != nil {
+	if err := frdb.KeyValueStore.Close(); err != nil {
 		errs = append(errs, err)
 	}
 	if len(errs) != 0 {
@@ -53,6 +54,26 @@ func (frdb *freezerdb) Close() error {
 	return nil
 }
 
+// Freeze is a helper method used for external testing to trigger and block until
+// a freeze cycle completes, without having to sleep for a minute to trigger the
+// automatic background run.
+func (frdb *freezerdb) Freeze(threshold uint64) error {
+	if frdb.AncientStore.(*freezer).readonly {
+		return errReadOnly
+	}
+	// Set the freezer threshold to a temporary value
+	defer func(old uint64) {
+		atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, old)
+	}(atomic.LoadUint64(&frdb.AncientStore.(*freezer).threshold))
+	atomic.StoreUint64(&frdb.AncientStore.(*freezer).threshold, threshold)
+
+	// Trigger a freeze cycle and block until it's done
+	trigger := make(chan struct{}, 1)
+	frdb.AncientStore.(*freezer).trigger <- trigger
+	<-trigger
+	return nil
+}
+
 // nofreezedb is a database wrapper that disables freezer data retrievals.
 type nofreezedb struct {
 	ethdb.KeyValueStore
@@ -104,9 +125,9 @@ func NewDatabase(db ethdb.KeyValueStore) ethdb.Database {
 // NewDatabaseWithFreezer creates a high level database on top of a given key-
 // value data store with a freezer moving immutable chain segments into cold
 // storage.
-func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string) (ethdb.Database, error) {
+func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace string, readonly bool) (ethdb.Database, error) {
 	// Create the idle freezer instance
-	frdb, err := newFreezer(freezer, namespace)
+	frdb, err := newFreezer(freezer, namespace, readonly)
 	if err != nil {
 		return nil, err
 	}
@@ -137,7 +158,10 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st
 			// If the freezer already contains something, ensure that the genesis blocks
 			// match, otherwise we might mix up freezers across chains and destroy both
 			// the freezer and the key-value store.
-			if frgenesis, _ := frdb.Ancient(freezerHashTable, 0); !bytes.Equal(kvgenesis, frgenesis) {
+			frgenesis, err := frdb.Ancient(freezerHashTable, 0)
+			if err != nil {
+				return nil, fmt.Errorf("failed to retrieve genesis from ancient %v", err)
+			} else if !bytes.Equal(kvgenesis, frgenesis) {
 				return nil, fmt.Errorf("genesis mismatch: %#x (leveldb) != %#x (ancients)", kvgenesis, frgenesis)
 			}
 			// Key-value store and freezer belong to the same network. Ensure that they
@@ -172,8 +196,9 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, freezer string, namespace st
 		}
 	}
 	// Freezer is consistent with the key-value database, permit combining the two
-	go frdb.freeze(db)
-
+	if !frdb.readonly {
+		go frdb.freeze(db)
+	}
 	return &freezerdb{
 		KeyValueStore: db,
 		AncientStore:  frdb,
@@ -195,8 +220,8 @@ func NewMemoryDatabaseWithCap(size int) ethdb.Database {
 
 // NewLevelDBDatabase creates a persistent key-value database without a freezer
 // moving immutable chain segments into cold storage.
-func NewLevelDBDatabase(file string, cache int, handles int, namespace string) (ethdb.Database, error) {
-	db, err := leveldb.New(file, cache, handles, namespace)
+func NewLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
+	db, err := leveldb.New(file, cache, handles, namespace, readonly)
 	if err != nil {
 		return nil, err
 	}
@@ -205,12 +230,12 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string) (
 
 // NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a
 // freezer moving immutable chain segments into cold storage.
-func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) {
-	kvdb, err := leveldb.New(file, cache, handles, namespace)
+func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer string, namespace string, readonly bool) (ethdb.Database, error) {
+	kvdb, err := leveldb.New(file, cache, handles, namespace, readonly)
 	if err != nil {
 		return nil, err
 	}
-	frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace)
+	frdb, err := NewDatabaseWithFreezer(kvdb, freezer, namespace, readonly)
 	if err != nil {
 		kvdb.Close()
 		return nil, err
@@ -218,10 +243,40 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer
 	return frdb, nil
 }
 
+type counter uint64
+
+func (c counter) String() string {
+	return fmt.Sprintf("%d", c)
+}
+
+func (c counter) Percentage(current uint64) string {
+	return fmt.Sprintf("%d", current*100/uint64(c))
+}
+
+// stat stores sizes and count for a parameter
+type stat struct {
+	size  common.StorageSize
+	count counter
+}
+
+// Add size to the stat and increase the counter by 1
+func (s *stat) Add(size common.StorageSize) {
+	s.size += size
+	s.count++
+}
+
+func (s *stat) Size() string {
+	return s.size.String()
+}
+
+func (s *stat) Count() string {
+	return s.count.String()
+}
+
 // InspectDatabase traverses the entire database and checks the size
 // of all different categories of data.
-func InspectDatabase(db ethdb.Database) error {
-	it := db.NewIterator(nil, nil)
+func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
+	it := db.NewIterator(keyPrefix, keyStart)
 	defer it.Release()
 
 	var (
@@ -230,36 +285,40 @@ func InspectDatabase(db ethdb.Database) error {
 		logged = time.Now()
 
 		// Key-value store statistics
-		total           common.StorageSize
-		headerSize      common.StorageSize
-		bodySize        common.StorageSize
-		receiptSize     common.StorageSize
-		tdSize          common.StorageSize
-		numHashPairing  common.StorageSize
-		hashNumPairing  common.StorageSize
-		trieSize        common.StorageSize
-		txlookupSize    common.StorageSize
-		accountSnapSize common.StorageSize
-		storageSnapSize common.StorageSize
-		preimageSize    common.StorageSize
-		bloomBitsSize   common.StorageSize
-		cliqueSnapsSize common.StorageSize
-		parliaSnapsSize common.StorageSize
+		headers         stat
+		bodies          stat
+		receipts        stat
+		tds             stat
+		numHashPairings stat
+		hashNumPairings stat
+		tries           stat
+		codes           stat
+		txLookups       stat
+		accountSnaps    stat
+		storageSnaps    stat
+		preimages       stat
+		bloomBits       stat
+		cliqueSnaps     stat
+		parliaSnaps     stat
 
 		// Ancient store statistics
-		ancientHeaders  common.StorageSize
-		ancientBodies   common.StorageSize
-		ancientReceipts common.StorageSize
-		ancientHashes   common.StorageSize
-		ancientTds      common.StorageSize
+		ancientHeadersSize  common.StorageSize
+		ancientBodiesSize   common.StorageSize
+		ancientReceiptsSize common.StorageSize
+		ancientTdsSize      common.StorageSize
+		ancientHashesSize   common.StorageSize
 
 		// Les statistic
-		chtTrieNodes   common.StorageSize
-		bloomTrieNodes common.StorageSize
+		chtTrieNodes   stat
+		bloomTrieNodes stat
 
 		// Meta- and unaccounted data
-		metadata    common.StorageSize
-		unaccounted common.StorageSize
+		metadata     stat
+		unaccounted  stat
+		shutdownInfo stat
+
+		// Totals
+		total common.StorageSize
 	)
 	// Inspect key-value database first.
 	for it.Next() {
@@ -269,98 +328,122 @@ func InspectDatabase(db ethdb.Database) error {
 		)
 		total += size
 		switch {
-		case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
-			tdSize += size
-		case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
-			numHashPairing += size
 		case bytes.HasPrefix(key, headerPrefix) && len(key) == (len(headerPrefix)+8+common.HashLength):
-			headerSize += size
-		case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
-			hashNumPairing += size
+			headers.Add(size)
 		case bytes.HasPrefix(key, blockBodyPrefix) && len(key) == (len(blockBodyPrefix)+8+common.HashLength):
-			bodySize += size
+			bodies.Add(size)
 		case bytes.HasPrefix(key, blockReceiptsPrefix) && len(key) == (len(blockReceiptsPrefix)+8+common.HashLength):
-			receiptSize += size
+			receipts.Add(size)
+		case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerTDSuffix):
+			tds.Add(size)
+		case bytes.HasPrefix(key, headerPrefix) && bytes.HasSuffix(key, headerHashSuffix):
+			numHashPairings.Add(size)
+		case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength):
+			hashNumPairings.Add(size)
+		case len(key) == common.HashLength:
+			tries.Add(size)
+		case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength:
+			codes.Add(size)
 		case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength):
-			txlookupSize += size
+			txLookups.Add(size)
 		case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength):
-			accountSnapSize += size
+			accountSnaps.Add(size)
 		case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength):
-			storageSnapSize += size
+			storageSnaps.Add(size)
 		case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength):
-			preimageSize += size
+			preimages.Add(size)
 		case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
-			bloomBitsSize += size
+			bloomBits.Add(size)
+		case bytes.HasPrefix(key, BloomBitsIndexPrefix):
+			bloomBits.Add(size)
 		case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength:
-			cliqueSnapsSize += size
+			cliqueSnaps.Add(size)
 		case bytes.HasPrefix(key, []byte("parlia-")) && len(key) == 7+common.HashLength:
-			parliaSnapsSize += size
-		case bytes.HasPrefix(key, []byte("cht-")) && len(key) == 4+common.HashLength:
-			chtTrieNodes += size
-		case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength:
-			bloomTrieNodes += size
-		case len(key) == common.HashLength:
-			trieSize += size
+			parliaSnaps.Add(size)
+
+		case bytes.HasPrefix(key, []byte("cht-")) ||
+			bytes.HasPrefix(key, []byte("chtIndexV2-")) ||
+			bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie
+			chtTrieNodes.Add(size)
+		case bytes.HasPrefix(key, []byte("blt-")) ||
+			bytes.HasPrefix(key, []byte("bltIndex-")) ||
+			bytes.HasPrefix(key, []byte("bltRoot-")): // Bloomtrie sub
+			bloomTrieNodes.Add(size)
+		case bytes.Equal(key, uncleanShutdownKey):
+			shutdownInfo.Add(size)
 		default:
 			var accounted bool
-			for _, meta := range [][]byte{databaseVerisionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey} {
+			for _, meta := range [][]byte{
+				databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey,
+				fastTrieProgressKey, snapshotDisabledKey, snapshotRootKey, snapshotJournalKey,
+				snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
+				uncleanShutdownKey, badBlockKey,
+			} {
 				if bytes.Equal(key, meta) {
-					metadata += size
+					metadata.Add(size)
 					accounted = true
 					break
 				}
 			}
 			if !accounted {
-				unaccounted += size
+				unaccounted.Add(size)
 			}
 		}
-		count += 1
+		count++
 		if count%1000 == 0 && time.Since(logged) > 8*time.Second {
 			log.Info("Inspecting database", "count", count, "elapsed", common.PrettyDuration(time.Since(start)))
 			logged = time.Now()
 		}
 	}
 	// Inspect append-only file store then.
-	ancients := []*common.StorageSize{&ancientHeaders, &ancientBodies, &ancientReceipts, &ancientHashes, &ancientTds}
+	ancientSizes := []*common.StorageSize{&ancientHeadersSize, &ancientBodiesSize, &ancientReceiptsSize, &ancientHashesSize, &ancientTdsSize}
 	for i, category := range []string{freezerHeaderTable, freezerBodiesTable, freezerReceiptTable, freezerHashTable, freezerDifficultyTable} {
 		if size, err := db.AncientSize(category); err == nil {
-			*ancients[i] += common.StorageSize(size)
+			*ancientSizes[i] += common.StorageSize(size)
 			total += common.StorageSize(size)
 		}
 	}
+	// Get number of ancient rows inside the freezer
+	ancients := counter(0)
+	if count, err := db.Ancients(); err == nil {
+		ancients = counter(count)
+	}
 	// Display the database statistic.
 	stats := [][]string{
-		{"Key-Value store", "Headers", headerSize.String()},
-		{"Key-Value store", "Bodies", bodySize.String()},
-		{"Key-Value store", "Receipts", receiptSize.String()},
-		{"Key-Value store", "Difficulties", tdSize.String()},
-		{"Key-Value store", "Block number->hash", numHashPairing.String()},
-		{"Key-Value store", "Block hash->number", hashNumPairing.String()},
-		{"Key-Value store", "Transaction index", txlookupSize.String()},
-		{"Key-Value store", "Bloombit index", bloomBitsSize.String()},
-		{"Key-Value store", "Trie nodes", trieSize.String()},
-		{"Key-Value store", "Trie preimages", preimageSize.String()},
-		{"Key-Value store", "Account snapshot", accountSnapSize.String()},
-		{"Key-Value store", "Storage snapshot", storageSnapSize.String()},
-		{"Key-Value store", "Clique snapshots", cliqueSnapsSize.String()},
-		{"Key-Value store", "Parlia snapshots", parliaSnapsSize.String()},
-		{"Key-Value store", "Singleton metadata", metadata.String()},
-		{"Ancient store", "Headers", ancientHeaders.String()},
-		{"Ancient store", "Bodies", ancientBodies.String()},
-		{"Ancient store", "Receipts", ancientReceipts.String()},
-		{"Ancient store", "Difficulties", ancientTds.String()},
-		{"Ancient store", "Block number->hash", ancientHashes.String()},
-		{"Light client", "CHT trie nodes", chtTrieNodes.String()},
-		{"Light client", "Bloom trie nodes", bloomTrieNodes.String()},
+		{"Key-Value store", "Headers", headers.Size(), headers.Count()},
+		{"Key-Value store", "Bodies", bodies.Size(), bodies.Count()},
+		{"Key-Value store", "Receipt lists", receipts.Size(), receipts.Count()},
+		{"Key-Value store", "Difficulties", tds.Size(), tds.Count()},
+		{"Key-Value store", "Block number->hash", numHashPairings.Size(), numHashPairings.Count()},
+		{"Key-Value store", "Block hash->number", hashNumPairings.Size(), hashNumPairings.Count()},
+		{"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()},
+		{"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()},
+		{"Key-Value store", "Contract codes", codes.Size(), codes.Count()},
+		{"Key-Value store", "Trie nodes", tries.Size(), tries.Count()},
+		{"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()},
+		{"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()},
+		{"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()},
+		{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
+		{"Key-Value store", "Parlia snapshots", parliaSnaps.Size(), parliaSnaps.Count()},
+		{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
+		{"Key-Value store", "Shutdown metadata", shutdownInfo.Size(), shutdownInfo.Count()},
+		{"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()},
+		{"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()},
+		{"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()},
+		{"Ancient store", "Difficulties", ancientTdsSize.String(), ancients.String()},
+		{"Ancient store", "Block number->hash", ancientHashesSize.String(), ancients.String()},
+		{"Light client", "CHT trie nodes", chtTrieNodes.Size(), chtTrieNodes.Count()},
+		{"Light client", "Bloom trie nodes", bloomTrieNodes.Size(), bloomTrieNodes.Count()},
 	}
 	table := tablewriter.NewWriter(os.Stdout)
-	table.SetHeader([]string{"Database", "Category", "Size"})
-	table.SetFooter([]string{"", "Total", total.String()})
+	table.SetHeader([]string{"Database", "Category", "Size", "Items"})
+	table.SetFooter([]string{"", "Total", total.String(), " "})
 	table.AppendBulk(stats)
 	table.Render()
 
-	if unaccounted > 0 {
-		log.Error("Database contains unaccounted data", "size", unaccounted)
+	if unaccounted.size > 0 {
+		log.Error("Database contains unaccounted data", "size", unaccounted.size, "count", unaccounted.count)
 	}
+
 	return nil
 }
diff --git a/core/vm/int_pool_verifier_empty.go b/core/rawdb/database_test.go
similarity index 82%
rename from core/vm/int_pool_verifier_empty.go
rename to core/rawdb/database_test.go
index a5f1dc02b7..8bf06f97d8 100644
--- a/core/vm/int_pool_verifier_empty.go
+++ b/core/rawdb/database_test.go
@@ -1,4 +1,4 @@
-// Copyright 2017 The go-ethereum Authors
+// Copyright 2019 The go-ethereum Authors
 // This file is part of the go-ethereum library.
 //
 // The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,10 +14,4 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// +build !VERIFY_EVM_INTEGER_POOL
-
-package vm
-
-const verifyPool = false
-
-func verifyIntegerPool(ip *intPool) {}
+package rawdb
diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go
index 5497c59d49..94b99a64eb 100644
--- a/core/rawdb/freezer.go
+++ b/core/rawdb/freezer.go
@@ -22,6 +22,7 @@ import (
 	"math"
 	"os"
 	"path/filepath"
+	"sync"
 	"sync/atomic"
 	"time"
 
@@ -34,6 +35,10 @@ import (
 )
 
 var (
+	// errReadOnly is returned if the freezer is opened in read only mode. All the
+	// mutations are disallowed.
+	errReadOnly = errors.New("read only")
+
 	// errUnknownTable is returned if the user attempts to read from a table that is
 	// not tracked by the freezer.
 	errUnknownTable = errors.New("unknown table")
@@ -69,15 +74,22 @@ type freezer struct {
 	// WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only
 	// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
 	// so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG).
-	frozen uint64 // Number of blocks already frozen
+	frozen    uint64 // Number of blocks already frozen
+	threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests)
 
+	readonly     bool
 	tables       map[string]*freezerTable // Data tables for storing everything
 	instanceLock fileutil.Releaser        // File-system lock to prevent double opens
+
+	trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
+
+	quit      chan struct{}
+	closeOnce sync.Once
 }
 
 // newFreezer creates a chain freezer that moves ancient chain data into
 // append-only flat file containers.
-func newFreezer(datadir string, namespace string) (*freezer, error) {
+func newFreezer(datadir string, namespace string, readonly bool) (*freezer, error) {
 	// Create the initial freezer object
 	var (
 		readMeter  = metrics.NewRegisteredMeter(namespace+"ancient/read", nil)
@@ -99,10 +111,14 @@ func newFreezer(datadir string, namespace string) (*freezer, error) {
 	}
 	// Open all the supported data tables
 	freezer := &freezer{
+		readonly:     readonly,
+		threshold:    params.FullImmutabilityThreshold,
 		tables:       make(map[string]*freezerTable),
 		instanceLock: lock,
+		trigger:      make(chan chan struct{}),
+		quit:         make(chan struct{}),
 	}
-	for name, disableSnappy := range freezerNoSnappy {
+	for name, disableSnappy := range FreezerNoSnappy {
 		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, disableSnappy)
 		if err != nil {
 			for _, table := range freezer.tables {
@@ -120,21 +136,24 @@ func newFreezer(datadir string, namespace string) (*freezer, error) {
 		lock.Release()
 		return nil, err
 	}
-	log.Info("Opened ancient database", "database", datadir)
+	log.Info("Opened ancient database", "database", datadir, "readonly", readonly)
 	return freezer, nil
 }
 
 // Close terminates the chain freezer, unmapping all the data files.
 func (f *freezer) Close() error {
 	var errs []error
-	for _, table := range f.tables {
-		if err := table.Close(); err != nil {
+	f.closeOnce.Do(func() {
+		close(f.quit)
+		for _, table := range f.tables {
+			if err := table.Close(); err != nil {
+				errs = append(errs, err)
+			}
+		}
+		if err := f.instanceLock.Release(); err != nil {
 			errs = append(errs, err)
 		}
-	}
-	if err := f.instanceLock.Release(); err != nil {
-		errs = append(errs, err)
-	}
+	})
 	if errs != nil {
 		return fmt.Errorf("%v", errs)
 	}
@@ -178,6 +197,9 @@ func (f *freezer) AncientSize(kind string) (uint64, error) {
 // injection will be rejected. But if two injections with same number happen at
 // the same time, we can get into the trouble.
 func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td []byte) (err error) {
+	if f.readonly {
+		return errReadOnly
+	}
 	// Ensure the binary blobs we are appending is continuous with freezer.
 	if atomic.LoadUint64(&f.frozen) != number {
 		return errOutOrderInsertion
@@ -218,8 +240,11 @@ func (f *freezer) AppendAncient(number uint64, hash, header, body, receipts, td
 	return nil
 }
 
-// Truncate discards any recent data above the provided threshold number.
+// TruncateAncients discards any recent data above the provided threshold number.
 func (f *freezer) TruncateAncients(items uint64) error {
+	if f.readonly {
+		return errReadOnly
+	}
 	if atomic.LoadUint64(&f.frozen) <= items {
 		return nil
 	}
@@ -232,7 +257,7 @@ func (f *freezer) TruncateAncients(items uint64) error {
 	return nil
 }
 
-// sync flushes all data tables to disk.
+// Sync flushes all data tables to disk.
 func (f *freezer) Sync() error {
 	var errs []error
 	for _, table := range f.tables {
@@ -254,48 +279,75 @@ func (f *freezer) Sync() error {
 func (f *freezer) freeze(db ethdb.KeyValueStore) {
 	nfdb := &nofreezedb{KeyValueStore: db}
 
+	var (
+		backoff   bool
+		triggered chan struct{} // Used in tests
+	)
 	for {
+		select {
+		case <-f.quit:
+			log.Info("Freezer shutting down")
+			return
+		default:
+		}
+		if backoff {
+			// If we were doing a manual trigger, notify it
+			if triggered != nil {
+				triggered <- struct{}{}
+				triggered = nil
+			}
+			select {
+			case <-time.NewTimer(freezerRecheckInterval).C:
+				backoff = false
+			case triggered = <-f.trigger:
+				backoff = false
+			case <-f.quit:
+				return
+			}
+		}
 		// Retrieve the freezing threshold.
 		hash := ReadHeadBlockHash(nfdb)
 		if hash == (common.Hash{}) {
 			log.Debug("Current full block hash unavailable") // new chain, empty database
-			time.Sleep(freezerRecheckInterval)
+			backoff = true
 			continue
 		}
 		number := ReadHeaderNumber(nfdb, hash)
+		threshold := atomic.LoadUint64(&f.threshold)
+
 		switch {
 		case number == nil:
 			log.Error("Current full block number unavailable", "hash", hash)
-			time.Sleep(freezerRecheckInterval)
+			backoff = true
 			continue
 
-		case *number < params.ImmutabilityThreshold:
-			log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", params.ImmutabilityThreshold)
-			time.Sleep(freezerRecheckInterval)
+		case *number < threshold:
+			log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
+			backoff = true
 			continue
 
-		case *number-params.ImmutabilityThreshold <= f.frozen:
+		case *number-threshold <= f.frozen:
 			log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
-			time.Sleep(freezerRecheckInterval)
+			backoff = true
 			continue
 		}
 		head := ReadHeader(nfdb, hash, *number)
 		if head == nil {
 			log.Error("Current full block unavailable", "number", *number, "hash", hash)
-			time.Sleep(freezerRecheckInterval)
+			backoff = true
 			continue
 		}
 		// Seems we have data ready to be frozen, process in usable batches
-		limit := *number - params.ImmutabilityThreshold
+		limit := *number - threshold
 		if limit-f.frozen > freezerBatchLimit {
 			limit = f.frozen + freezerBatchLimit
 		}
 		var (
 			start    = time.Now()
 			first    = f.frozen
-			ancients = make([]common.Hash, 0, limit)
+			ancients = make([]common.Hash, 0, limit-f.frozen)
 		)
-		for f.frozen < limit {
+		for f.frozen <= limit {
 			// Retrieves all the components of the canonical block
 			hash := ReadCanonicalHash(nfdb, f.frozen)
 			if hash == (common.Hash{}) {
@@ -346,11 +398,15 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
 			log.Crit("Failed to delete frozen canonical blocks", "err", err)
 		}
 		batch.Reset()
-		// Wipe out side chain also.
+
+		// Wipe out side chains also and track dangling side chians
+		var dangling []common.Hash
 		for number := first; number < f.frozen; number++ {
 			// Always keep the genesis block in active database
 			if number != 0 {
-				for _, hash := range ReadAllHashes(db, number) {
+				dangling = ReadAllHashes(db, number)
+				for _, hash := range dangling {
+					log.Trace("Deleting side chain", "number", number, "hash", hash)
 					DeleteBlock(batch, hash, number)
 				}
 			}
@@ -358,6 +414,41 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
 		if err := batch.Write(); err != nil {
 			log.Crit("Failed to delete frozen side blocks", "err", err)
 		}
+		batch.Reset()
+
+		// Step into the future and delete and dangling side chains
+		if f.frozen > 0 {
+			tip := f.frozen
+			for len(dangling) > 0 {
+				drop := make(map[common.Hash]struct{})
+				for _, hash := range dangling {
+					log.Debug("Dangling parent from freezer", "number", tip-1, "hash", hash)
+					drop[hash] = struct{}{}
+				}
+				children := ReadAllHashes(db, tip)
+				for i := 0; i < len(children); i++ {
+					// Dig up the child and ensure it's dangling
+					child := ReadHeader(nfdb, children[i], tip)
+					if child == nil {
+						log.Error("Missing dangling header", "number", tip, "hash", children[i])
+						continue
+					}
+					if _, ok := drop[child.ParentHash]; !ok {
+						children = append(children[:i], children[i+1:]...)
+						i--
+						continue
+					}
+					// Delete all block data associated with the child
+					log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash)
+					DeleteBlock(batch, children[i], tip)
+				}
+				dangling = children
+				tip++
+			}
+			if err := batch.Write(); err != nil {
+				log.Crit("Failed to delete dangling side blocks", "err", err)
+			}
+		}
 		// Log something friendly for the user
 		context := []interface{}{
 			"blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1,
@@ -369,7 +460,7 @@ func (f *freezer) freeze(db ethdb.KeyValueStore) {
 
 		// Avoid database thrashing with tiny writes
 		if f.frozen-first < freezerBatchLimit {
-			time.Sleep(freezerRecheckInterval)
+			backoff = true
 		}
 	}
 }
diff --git a/core/rawdb/freezer_reinit.go b/core/rawdb/freezer_reinit.go
deleted file mode 100644
index d6bf9ab1dd..0000000000
--- a/core/rawdb/freezer_reinit.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package rawdb
-
-import (
-	"errors"
-	"runtime"
-	"sync/atomic"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/prque"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/ethdb"
-	"github.com/ethereum/go-ethereum/log"
-)
-
-// InitDatabaseFromFreezer reinitializes an empty database from a previous batch
-// of frozen ancient blocks. The method iterates over all the frozen blocks and
-// injects into the database the block hash->number mappings and the transaction
-// lookup entries.
-func InitDatabaseFromFreezer(db ethdb.Database) error {
-	// If we can't access the freezer or it's empty, abort
-	frozen, err := db.Ancients()
-	if err != nil || frozen == 0 {
-		return err
-	}
-	// Blocks previously frozen, iterate over- and hash them concurrently
-	var (
-		number  = ^uint64(0) // -1
-		results = make(chan *types.Block, 4*runtime.NumCPU())
-	)
-	abort := make(chan struct{})
-	defer close(abort)
-
-	for i := 0; i < runtime.NumCPU(); i++ {
-		go func() {
-			for {
-				// Fetch the next task number, terminating if everything's done
-				n := atomic.AddUint64(&number, 1)
-				if n >= frozen {
-					return
-				}
-				// Retrieve the block from the freezer. If successful, pre-cache
-				// the block hash and the individual transaction hashes for storing
-				// into the database.
-				block := ReadBlock(db, ReadCanonicalHash(db, n), n)
-				if block != nil {
-					block.Hash()
-					for _, tx := range block.Transactions() {
-						tx.Hash()
-					}
-				}
-				// Feed the block to the aggregator, or abort on interrupt
-				select {
-				case results <- block:
-				case <-abort:
-					return
-				}
-			}
-		}()
-	}
-	// Reassemble the blocks into a contiguous stream and push them out to disk
-	var (
-		queue = prque.New(nil)
-		next  = int64(0)
-
-		batch  = db.NewBatch()
-		start  = time.Now()
-		logged time.Time
-	)
-	for i := uint64(0); i < frozen; i++ {
-		// Retrieve the next result and bail if it's nil
-		block := <-results
-		if block == nil {
-			return errors.New("broken ancient database")
-		}
-		// Push the block into the import queue and process contiguous ranges
-		queue.Push(block, -int64(block.NumberU64()))
-		for !queue.Empty() {
-			// If the next available item is gapped, return
-			if _, priority := queue.Peek(); -priority != next {
-				break
-			}
-			// Next block available, pop it off and index it
-			block = queue.PopItem().(*types.Block)
-			next++
-
-			// Inject hash<->number mapping and txlookup indexes
-			WriteHeaderNumber(batch, block.Hash(), block.NumberU64())
-			WriteTxLookupEntries(batch, block)
-
-			// If enough data was accumulated in memory or we're at the last block, dump to disk
-			if batch.ValueSize() > ethdb.IdealBatchSize || uint64(next) == frozen {
-				if err := batch.Write(); err != nil {
-					return err
-				}
-				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("Initializing chain from ancient data", "number", block.Number(), "hash", block.Hash(), "total", frozen-1, "elapsed", common.PrettyDuration(time.Since(start)))
-				logged = time.Now()
-			}
-		}
-	}
-	hash := ReadCanonicalHash(db, frozen-1)
-	WriteHeadHeaderHash(db, hash)
-	WriteHeadFastBlockHash(db, hash)
-
-	log.Info("Initialized chain from ancient data", "number", frozen-1, "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
-	return nil
-}
diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go
index 9cff4216ca..d7bfe18e02 100644
--- a/core/rawdb/freezer_table.go
+++ b/core/rawdb/freezer_table.go
@@ -103,6 +103,11 @@ type freezerTable struct {
 	lock   sync.RWMutex // Mutex protecting the data file descriptors
 }
 
+// NewFreezerTable opens the given path as a freezer table.
+func NewFreezerTable(path, name string, disableSnappy bool) (*freezerTable, error) {
+	return newTable(path, name, metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, disableSnappy)
+}
+
 // newTable opens a freezer table with default settings - 2G files
 func newTable(path string, name string, readMeter metrics.Meter, writeMeter metrics.Meter, sizeGauge metrics.Gauge, disableSnappy bool) (*freezerTable, error) {
 	return newCustomTable(path, name, readMeter, writeMeter, sizeGauge, 2*1000*1000*1000, disableSnappy)
@@ -232,8 +237,8 @@ func (t *freezerTable) repair() error {
 	t.index.ReadAt(buffer, 0)
 	firstIndex.unmarshalBinary(buffer)
 
-	t.tailId = firstIndex.offset
-	t.itemOffset = firstIndex.filenum
+	t.tailId = firstIndex.filenum
+	t.itemOffset = firstIndex.offset
 
 	t.index.ReadAt(buffer, offsetsSize-indexEntrySize)
 	lastIndex.unmarshalBinary(buffer)
@@ -330,7 +335,8 @@ func (t *freezerTable) truncate(items uint64) error {
 	defer t.lock.Unlock()
 
 	// If our item count is correct, don't do anything
-	if atomic.LoadUint64(&t.items) <= items {
+	existing := atomic.LoadUint64(&t.items)
+	if existing <= items {
 		return nil
 	}
 	// We need to truncate, save the old size for metrics tracking
@@ -339,7 +345,11 @@ func (t *freezerTable) truncate(items uint64) error {
 		return err
 	}
 	// Something's out of sync, truncate the table's offset index
-	t.logger.Warn("Truncating freezer table", "items", t.items, "limit", items)
+	log := t.logger.Debug
+	if existing > items+1 {
+		log = t.logger.Warn // Only loud warn if we delete multiple items
+	}
+	log("Truncating freezer table", "items", existing, "limit", items)
 	if err := truncateFreezerFile(t.index, int64(items+1)*indexEntrySize); err != nil {
 		return err
 	}
@@ -455,35 +465,59 @@ func (t *freezerTable) releaseFilesAfter(num uint32, remove bool) {
 // Note, this method will *not* flush any data to disk so be sure to explicitly
 // fsync before irreversibly deleting data from the database.
 func (t *freezerTable) Append(item uint64, blob []byte) error {
+	// Encode the blob before the lock portion
+	if !t.noCompression {
+		blob = snappy.Encode(nil, blob)
+	}
 	// Read lock prevents competition with truncate
-	t.lock.RLock()
+	retry, err := t.append(item, blob, false)
+	if err != nil {
+		return err
+	}
+	if retry {
+		// Read lock was insufficient, retry with a writelock
+		_, err = t.append(item, blob, true)
+	}
+	return err
+}
+
+// append injects a binary blob at the end of the freezer table.
+// Normally, inserts do not require holding the write-lock, so it should be invoked with 'wlock' set to
+// false.
+// However, if the data will grown the current file out of bounds, then this
+// method will return 'true, nil', indicating that the caller should retry, this time
+// with 'wlock' set to true.
+func (t *freezerTable) append(item uint64, encodedBlob []byte, wlock bool) (bool, error) {
+	if wlock {
+		t.lock.Lock()
+		defer t.lock.Unlock()
+	} else {
+		t.lock.RLock()
+		defer t.lock.RUnlock()
+	}
 	// Ensure the table is still accessible
 	if t.index == nil || t.head == nil {
-		t.lock.RUnlock()
-		return errClosed
+		return false, errClosed
 	}
 	// Ensure only the next item can be written, nothing else
 	if atomic.LoadUint64(&t.items) != item {
-		t.lock.RUnlock()
-		return fmt.Errorf("appending unexpected item: want %d, have %d", t.items, item)
+		return false, fmt.Errorf("appending unexpected item: want %d, have %d", t.items, item)
 	}
-	// Encode the blob and write it into the data file
-	if !t.noCompression {
-		blob = snappy.Encode(nil, blob)
-	}
-	bLen := uint32(len(blob))
+	bLen := uint32(len(encodedBlob))
 	if t.headBytes+bLen < bLen ||
 		t.headBytes+bLen > t.maxFileSize {
-		// we need a new file, writing would overflow
-		t.lock.RUnlock()
-		t.lock.Lock()
+		// Writing would overflow, so we need to open a new data file.
+		// If we don't already hold the writelock, abort and let the caller
+		// invoke this method a second time.
+		if !wlock {
+			return true, nil
+		}
 		nextID := atomic.LoadUint32(&t.headId) + 1
 		// We open the next file in truncated mode -- if this file already
 		// exists, we need to start over from scratch on it
 		newHead, err := t.openFile(nextID, openFreezerFileTruncated)
 		if err != nil {
-			t.lock.Unlock()
-			return err
+			return false, err
 		}
 		// Close old file, and reopen in RDONLY mode
 		t.releaseFile(t.headId)
@@ -493,13 +527,9 @@ func (t *freezerTable) Append(item uint64, blob []byte) error {
 		t.head = newHead
 		atomic.StoreUint32(&t.headBytes, 0)
 		atomic.StoreUint32(&t.headId, nextID)
-		t.lock.Unlock()
-		t.lock.RLock()
 	}
-
-	defer t.lock.RUnlock()
-	if _, err := t.head.Write(blob); err != nil {
-		return err
+	if _, err := t.head.Write(encodedBlob); err != nil {
+		return false, err
 	}
 	newOffset := atomic.AddUint32(&t.headBytes, bLen)
 	idx := indexEntry{
@@ -513,22 +543,33 @@ func (t *freezerTable) Append(item uint64, blob []byte) error {
 	t.sizeGauge.Inc(int64(bLen + indexEntrySize))
 
 	atomic.AddUint64(&t.items, 1)
-	return nil
+	return false, nil
 }
 
 // getBounds returns the indexes for the item
 // returns start, end, filenumber and error
 func (t *freezerTable) getBounds(item uint64) (uint32, uint32, uint32, error) {
-	var startIdx, endIdx indexEntry
 	buffer := make([]byte, indexEntrySize)
-	if _, err := t.index.ReadAt(buffer, int64(item*indexEntrySize)); err != nil {
-		return 0, 0, 0, err
-	}
-	startIdx.unmarshalBinary(buffer)
+	var startIdx, endIdx indexEntry
+	// Read second index
 	if _, err := t.index.ReadAt(buffer, int64((item+1)*indexEntrySize)); err != nil {
 		return 0, 0, 0, err
 	}
 	endIdx.unmarshalBinary(buffer)
+	// Read first index (unless it's the very first item)
+	if item != 0 {
+		if _, err := t.index.ReadAt(buffer, int64(item*indexEntrySize)); err != nil {
+			return 0, 0, 0, err
+		}
+		startIdx.unmarshalBinary(buffer)
+	} else {
+		// Special case if we're reading the first item in the freezer. We assume that
+		// the first item always start from zero(regarding the deletion, we
+		// only support deletion by files, so that the assumption is held).
+		// This means we can use the first item metadata to carry information about
+		// the 'global' offset, for the deletion-case
+		return 0, endIdx.offset, endIdx.filenum, nil
+	}
 	if startIdx.filenum != endIdx.filenum {
 		// If a piece of data 'crosses' a data-file,
 		// it's actually in one piece on the second data-file.
@@ -541,44 +582,48 @@ func (t *freezerTable) getBounds(item uint64) (uint32, uint32, uint32, error) {
 // Retrieve looks up the data offset of an item with the given number and retrieves
 // the raw binary blob from the data file.
 func (t *freezerTable) Retrieve(item uint64) ([]byte, error) {
+	blob, err := t.retrieve(item)
+	if err != nil {
+		return nil, err
+	}
+	if t.noCompression {
+		return blob, nil
+	}
+	return snappy.Decode(nil, blob)
+}
+
+// retrieve looks up the data offset of an item with the given number and retrieves
+// the raw binary blob from the data file. OBS! This method does not decode
+// compressed data.
+func (t *freezerTable) retrieve(item uint64) ([]byte, error) {
 	t.lock.RLock()
+	defer t.lock.RUnlock()
 	// Ensure the table and the item is accessible
 	if t.index == nil || t.head == nil {
-		t.lock.RUnlock()
 		return nil, errClosed
 	}
 	if atomic.LoadUint64(&t.items) <= item {
-		t.lock.RUnlock()
 		return nil, errOutOfBounds
 	}
 	// Ensure the item was not deleted from the tail either
 	if uint64(t.itemOffset) > item {
-		t.lock.RUnlock()
 		return nil, errOutOfBounds
 	}
 	startOffset, endOffset, filenum, err := t.getBounds(item - uint64(t.itemOffset))
 	if err != nil {
-		t.lock.RUnlock()
 		return nil, err
 	}
 	dataFile, exist := t.files[filenum]
 	if !exist {
-		t.lock.RUnlock()
 		return nil, fmt.Errorf("missing data file %d", filenum)
 	}
 	// Retrieve the data itself, decompress and return
 	blob := make([]byte, endOffset-startOffset)
 	if _, err := dataFile.ReadAt(blob, int64(startOffset)); err != nil {
-		t.lock.RUnlock()
 		return nil, err
 	}
-	t.lock.RUnlock()
 	t.readMeter.Mark(int64(len(blob) + 2*indexEntrySize))
-
-	if t.noCompression {
-		return blob, nil
-	}
-	return snappy.Decode(nil, blob)
+	return blob, nil
 }
 
 // has returns an indicator whether the specified number data
@@ -615,25 +660,24 @@ func (t *freezerTable) Sync() error {
 	return t.head.Sync()
 }
 
-// printIndex is a debug print utility function for testing
-func (t *freezerTable) printIndex() {
+// DumpIndex is a debug print utility function, mainly for testing. It can also
+// be used to analyse a live freezer table index.
+func (t *freezerTable) DumpIndex(start, stop int64) {
 	buf := make([]byte, indexEntrySize)
 
-	fmt.Printf("|-----------------|\n")
-	fmt.Printf("| fileno | offset |\n")
-	fmt.Printf("|--------+--------|\n")
+	fmt.Printf("| number | fileno | offset |\n")
+	fmt.Printf("|--------|--------|--------|\n")
 
-	for i := uint64(0); ; i++ {
+	for i := uint64(start); ; i++ {
 		if _, err := t.index.ReadAt(buf, int64(i*indexEntrySize)); err != nil {
 			break
 		}
 		var entry indexEntry
 		entry.unmarshalBinary(buf)
-		fmt.Printf("|  %03d   |  %03d   | \n", entry.filenum, entry.offset)
-		if i > 100 {
-			fmt.Printf(" ... \n")
+		fmt.Printf("|  %03d   |  %03d   |  %03d   | \n", i, entry.filenum, entry.offset)
+		if stop > 0 && i >= uint64(stop) {
 			break
 		}
 	}
-	fmt.Printf("|-----------------|\n")
+	fmt.Printf("|--------------------------|\n")
 }
diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go
index dd1ff744a6..0df28f236d 100644
--- a/core/rawdb/freezer_table_test.go
+++ b/core/rawdb/freezer_table_test.go
@@ -18,10 +18,13 @@ package rawdb
 
 import (
 	"bytes"
+	"encoding/binary"
 	"fmt"
+	"io/ioutil"
 	"math/rand"
 	"os"
 	"path/filepath"
+	"sync"
 	"testing"
 	"time"
 
@@ -525,7 +528,7 @@ func TestOffset(t *testing.T) {
 
 		f.Append(4, getChunk(20, 0xbb))
 		f.Append(5, getChunk(20, 0xaa))
-		f.printIndex()
+		f.DumpIndex(0, 100)
 		f.Close()
 	}
 	// Now crop it.
@@ -552,8 +555,8 @@ func TestOffset(t *testing.T) {
 		tailId := uint32(2)     // First file is 2
 		itemOffset := uint32(4) // We have removed four items
 		zeroIndex := indexEntry{
-			offset:  tailId,
-			filenum: itemOffset,
+			filenum: tailId,
+			offset:  itemOffset,
 		}
 		buf := zeroIndex.marshallBinary()
 		// Overwrite index zero
@@ -567,39 +570,67 @@ func TestOffset(t *testing.T) {
 
 	}
 	// Now open again
-	{
+	checkPresent := func(numDeleted uint64) {
 		f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
 		if err != nil {
 			t.Fatal(err)
 		}
-		f.printIndex()
+		f.DumpIndex(0, 100)
 		// It should allow writing item 6
-		f.Append(6, getChunk(20, 0x99))
+		f.Append(numDeleted+2, getChunk(20, 0x99))
 
 		// It should be fine to fetch 4,5,6
-		if got, err := f.Retrieve(4); err != nil {
+		if got, err := f.Retrieve(numDeleted); err != nil {
 			t.Fatal(err)
 		} else if exp := getChunk(20, 0xbb); !bytes.Equal(got, exp) {
 			t.Fatalf("expected %x got %x", exp, got)
 		}
-		if got, err := f.Retrieve(5); err != nil {
+		if got, err := f.Retrieve(numDeleted + 1); err != nil {
 			t.Fatal(err)
 		} else if exp := getChunk(20, 0xaa); !bytes.Equal(got, exp) {
 			t.Fatalf("expected %x got %x", exp, got)
 		}
-		if got, err := f.Retrieve(6); err != nil {
+		if got, err := f.Retrieve(numDeleted + 2); err != nil {
 			t.Fatal(err)
 		} else if exp := getChunk(20, 0x99); !bytes.Equal(got, exp) {
 			t.Fatalf("expected %x got %x", exp, got)
 		}
 
 		// It should error at 0, 1,2,3
-		for i := 0; i < 4; i++ {
-			if _, err := f.Retrieve(uint64(i)); err == nil {
+		for i := numDeleted - 1; i > numDeleted-10; i-- {
+			if _, err := f.Retrieve(i); err == nil {
 				t.Fatal("expected err")
 			}
 		}
 	}
+	checkPresent(4)
+	// Now, let's pretend we have deleted 1M items
+	{
+		// Read the index file
+		p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname))
+		indexFile, err := os.OpenFile(p, os.O_RDWR, 0644)
+		if err != nil {
+			t.Fatal(err)
+		}
+		indexBuf := make([]byte, 3*indexEntrySize)
+		indexFile.Read(indexBuf)
+
+		// Update the index file, so that we store
+		// [ file = 2, offset = 1M ] at index zero
+
+		tailId := uint32(2)           // First file is 2
+		itemOffset := uint32(1000000) // We have removed 1M items
+		zeroIndex := indexEntry{
+			offset:  itemOffset,
+			filenum: tailId,
+		}
+		buf := zeroIndex.marshallBinary()
+		// Overwrite index zero
+		copy(indexBuf, buf)
+		indexFile.WriteAt(indexBuf, 0)
+		indexFile.Close()
+	}
+	checkPresent(1000000)
 }
 
 // TODO (?)
@@ -609,6 +640,55 @@ func TestOffset(t *testing.T) {
 // 1. have data files d0, d1, d2, d3
 // 2. remove d2,d3
 //
-// However, all 'normal' failure modes arising due to failing to sync() or save a file should be
-// handled already, and the case described above can only (?) happen if an external process/user
-// deletes files from the filesystem.
+// However, all 'normal' failure modes arising due to failing to sync() or save a file
+// should be handled already, and the case described above can only (?) happen if an
+// external process/user deletes files from the filesystem.
+
+// TestAppendTruncateParallel is a test to check if the Append/truncate operations are
+// racy.
+//
+// The reason why it's not a regular fuzzer, within tests/fuzzers, is that it is dependent
+// on timing rather than 'clever' input -- there's no determinism.
+func TestAppendTruncateParallel(t *testing.T) {
+	dir, err := ioutil.TempDir("", "freezer")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	f, err := newCustomTable(dir, "tmp", metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, 8, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fill := func(mark uint64) []byte {
+		data := make([]byte, 8)
+		binary.LittleEndian.PutUint64(data, mark)
+		return data
+	}
+
+	for i := 0; i < 5000; i++ {
+		f.truncate(0)
+		data0 := fill(0)
+		f.Append(0, data0)
+		data1 := fill(1)
+
+		var wg sync.WaitGroup
+		wg.Add(2)
+		go func() {
+			f.truncate(0)
+			wg.Done()
+		}()
+		go func() {
+			f.Append(1, data1)
+			wg.Done()
+		}()
+		wg.Wait()
+
+		if have, err := f.Retrieve(0); err == nil {
+			if !bytes.Equal(have, data0) {
+				t.Fatalf("have %x want %x", have, data0)
+			}
+		}
+	}
+}
diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go
index 2c20df200b..2505ce90b9 100644
--- a/core/rawdb/schema.go
+++ b/core/rawdb/schema.go
@@ -18,6 +18,7 @@
 package rawdb
 
 import (
+	"bytes"
 	"encoding/binary"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -26,8 +27,8 @@ import (
 
 // The fields below define the low level database schema prefixing.
 var (
-	// databaseVerisionKey tracks the current database version.
-	databaseVerisionKey = []byte("DatabaseVersion")
+	// databaseVersionKey tracks the current database version.
+	databaseVersionKey = []byte("DatabaseVersion")
 
 	// headHeaderKey tracks the latest known header's hash.
 	headHeaderKey = []byte("LastHeader")
@@ -38,15 +39,42 @@ var (
 	// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
 	headFastBlockKey = []byte("LastFast")
 
+	// lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead).
+	lastPivotKey = []byte("LastPivot")
+
 	// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
 	fastTrieProgressKey = []byte("TrieSync")
 
+	// snapshotDisabledKey flags that the snapshot should not be maintained due to initial sync.
+	snapshotDisabledKey = []byte("SnapshotDisabled")
+
 	// snapshotRootKey tracks the hash of the last snapshot.
 	snapshotRootKey = []byte("SnapshotRoot")
 
 	// snapshotJournalKey tracks the in-memory diff layers across restarts.
 	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")
+
+	// snapshotSyncStatusKey tracks the snapshot sync status across restarts.
+	snapshotSyncStatusKey = []byte("SnapshotSyncStatus")
+
+	// txIndexTailKey tracks the oldest block whose transactions have been indexed.
+	txIndexTailKey = []byte("TransactionIndexTail")
+
+	// fastTxLookupLimitKey tracks the transaction lookup limit during fast sync.
+	fastTxLookupLimitKey = []byte("FastTransactionLookupLimit")
+
+	// badBlockKey tracks the list of bad blocks seen by local
+	badBlockKey = []byte("InvalidBlock")
+
+	// uncleanShutdownKey tracks the list of local crashes
+	uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
+
 	// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
 	headerPrefix       = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
 	headerTDSuffix     = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td
@@ -60,6 +88,7 @@ var (
 	bloomBitsPrefix       = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
 	SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
 	SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
+	CodePrefix            = []byte("c") // CodePrefix + code hash -> account code
 
 	preimagePrefix = []byte("secure-key-")      // preimagePrefix + hash -> preimage
 	configPrefix   = []byte("ethereum-config-") // config prefix for the db
@@ -88,9 +117,9 @@ const (
 	freezerDifficultyTable = "diffs"
 )
 
-// freezerNoSnappy configures whether compression is disabled for the ancient-tables.
+// FreezerNoSnappy configures whether compression is disabled for the ancient-tables.
 // Hashes and difficulties don't compress well.
-var freezerNoSnappy = map[string]bool{
+var FreezerNoSnappy = map[string]bool{
 	freezerHeaderTable:     false,
 	freezerHashTable:       true,
 	freezerBodiesTable:     false,
@@ -183,6 +212,20 @@ func preimageKey(hash common.Hash) []byte {
 	return append(preimagePrefix, hash.Bytes()...)
 }
 
+// codeKey = CodePrefix + hash
+func codeKey(hash common.Hash) []byte {
+	return append(CodePrefix, hash.Bytes()...)
+}
+
+// IsCodeKey reports whether the given byte slice is the key of contract code,
+// if so return the raw code hash as well.
+func IsCodeKey(key []byte) (bool, []byte) {
+	if bytes.HasPrefix(key, CodePrefix) && len(key) == common.HashLength+len(CodePrefix) {
+		return true, key[len(CodePrefix):]
+	}
+	return false, nil
+}
+
 // configKey = configPrefix + hash
 func configKey(hash common.Hash) []byte {
 	return append(configPrefix, hash.Bytes()...)
diff --git a/core/rlp_test.go b/core/rlp_test.go
new file mode 100644
index 0000000000..04daf2fc67
--- /dev/null
+++ b/core/rlp_test.go
@@ -0,0 +1,201 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package core
+
+import (
+	"fmt"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rlp"
+	"golang.org/x/crypto/sha3"
+)
+
+func getBlock(transactions int, uncles int, dataSize int) *types.Block {
+	var (
+		aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")
+		// Generate a canonical chain to act as the main dataset
+		engine = ethash.NewFaker()
+		db     = rawdb.NewMemoryDatabase()
+		// A sender who makes transactions, has some funds
+		key, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		address = crypto.PubkeyToAddress(key.PublicKey)
+		funds   = big.NewInt(1000000000)
+		gspec   = &Genesis{
+			Config: params.TestChainConfig,
+			Alloc:  GenesisAlloc{address: {Balance: funds}},
+		}
+		genesis = gspec.MustCommit(db)
+	)
+
+	// We need to generate as many blocks +1 as uncles
+	blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, uncles+1,
+		func(n int, b *BlockGen) {
+			if n == uncles {
+				// Add transactions and stuff on the last block
+				for i := 0; i < transactions; i++ {
+					tx, _ := types.SignTx(types.NewTransaction(uint64(i), aa,
+						big.NewInt(0), 50000, big.NewInt(1), make([]byte, dataSize)), types.HomesteadSigner{}, key)
+					b.AddTx(tx)
+				}
+				for i := 0; i < uncles; i++ {
+					b.AddUncle(&types.Header{ParentHash: b.PrevBlock(n - 1 - i).Hash(), Number: big.NewInt(int64(n - i))})
+				}
+			}
+		})
+	block := blocks[len(blocks)-1]
+	return block
+}
+
+// TestRlpIterator tests that individual transactions can be picked out
+// from blocks without full unmarshalling/marshalling
+func TestRlpIterator(t *testing.T) {
+	for _, tt := range []struct {
+		txs      int
+		uncles   int
+		datasize int
+	}{
+		{0, 0, 0},
+		{0, 2, 0},
+		{10, 0, 0},
+		{10, 2, 0},
+		{10, 2, 50},
+	} {
+		testRlpIterator(t, tt.txs, tt.uncles, tt.datasize)
+	}
+}
+
+func testRlpIterator(t *testing.T, txs, uncles, datasize int) {
+	desc := fmt.Sprintf("%d txs [%d datasize] and %d uncles", txs, datasize, uncles)
+	bodyRlp, _ := rlp.EncodeToBytes(getBlock(txs, uncles, datasize).Body())
+	it, err := rlp.NewListIterator(bodyRlp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Check that txs exist
+	if !it.Next() {
+		t.Fatal("expected two elems, got zero")
+	}
+	txdata := it.Value()
+	// Check that uncles exist
+	if !it.Next() {
+		t.Fatal("expected two elems, got one")
+	}
+	// No more after that
+	if it.Next() {
+		t.Fatal("expected only two elems, got more")
+	}
+	txIt, err := rlp.NewListIterator(txdata)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var gotHashes []common.Hash
+	var expHashes []common.Hash
+	for txIt.Next() {
+		gotHashes = append(gotHashes, crypto.Keccak256Hash(txIt.Value()))
+	}
+
+	var expBody types.Body
+	err = rlp.DecodeBytes(bodyRlp, &expBody)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, tx := range expBody.Transactions {
+		expHashes = append(expHashes, tx.Hash())
+	}
+	if gotLen, expLen := len(gotHashes), len(expHashes); gotLen != expLen {
+		t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, expLen)
+	}
+	// also sanity check against input
+	if gotLen := len(gotHashes); gotLen != txs {
+		t.Fatalf("testcase %v: length wrong, got %d exp %d", desc, gotLen, txs)
+	}
+	for i, got := range gotHashes {
+		if exp := expHashes[i]; got != exp {
+			t.Errorf("testcase %v: hash wrong, got %x, exp %x", desc, got, exp)
+		}
+	}
+}
+
+// BenchmarkHashing compares the speeds of hashing a rlp raw data directly
+// without the unmarshalling/marshalling step
+func BenchmarkHashing(b *testing.B) {
+	// Make a pretty fat block
+	var (
+		bodyRlp  []byte
+		blockRlp []byte
+	)
+	{
+		block := getBlock(200, 2, 50)
+		bodyRlp, _ = rlp.EncodeToBytes(block.Body())
+		blockRlp, _ = rlp.EncodeToBytes(block)
+	}
+	var got common.Hash
+	var hasher = sha3.NewLegacyKeccak256()
+	b.Run("iteratorhashing", func(b *testing.B) {
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			var hash common.Hash
+			it, err := rlp.NewListIterator(bodyRlp)
+			if err != nil {
+				b.Fatal(err)
+			}
+			it.Next()
+			txs := it.Value()
+			txIt, err := rlp.NewListIterator(txs)
+			if err != nil {
+				b.Fatal(err)
+			}
+			for txIt.Next() {
+				hasher.Reset()
+				hasher.Write(txIt.Value())
+				hasher.Sum(hash[:0])
+				got = hash
+			}
+		}
+	})
+	var exp common.Hash
+	b.Run("fullbodyhashing", func(b *testing.B) {
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			var body types.Body
+			rlp.DecodeBytes(bodyRlp, &body)
+			for _, tx := range body.Transactions {
+				exp = tx.Hash()
+			}
+		}
+	})
+	b.Run("fullblockhashing", func(b *testing.B) {
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			var block types.Block
+			rlp.DecodeBytes(blockRlp, &block)
+			for _, tx := range block.Transactions() {
+				tx.Hash()
+			}
+		}
+	})
+	if got != exp {
+		b.Fatalf("hash wrong, got %x exp %x", got, exp)
+	}
+}
diff --git a/core/state/access_list.go b/core/state/access_list.go
new file mode 100644
index 0000000000..4194691345
--- /dev/null
+++ b/core/state/access_list.go
@@ -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 .
+
+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)
+}
diff --git a/core/state/database.go b/core/state/database.go
index ecc2c134da..1a06e33409 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -17,9 +17,12 @@
 package state
 
 import (
+	"errors"
 	"fmt"
 
+	"github.com/VictoriaMetrics/fastcache"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/trie"
 	lru "github.com/hashicorp/golang-lru"
@@ -28,6 +31,9 @@ import (
 const (
 	// Number of codehash->size associations to keep.
 	codeSizeCacheSize = 100000
+
+	// Cache size granted for caching clean code.
+	codeCacheSize = 64 * 1024 * 1024
 )
 
 // Database wraps access to tries and contract code.
@@ -98,35 +104,45 @@ type Trie interface {
 
 // NewDatabase creates a backing store for state. The returned database is safe for
 // concurrent use, but does not retain any recent trie nodes in memory. To keep some
-// historical state in memory, use the NewDatabaseWithCache constructor.
+// historical state in memory, use the NewDatabaseWithConfig constructor.
 func NewDatabase(db ethdb.Database) Database {
-	return NewDatabaseWithCache(db, 0)
+	return NewDatabaseWithConfig(db, nil)
 }
 
-// NewDatabaseWithCache creates a backing store for state. The returned database
+// NewDatabaseWithConfig creates a backing store for state. The returned database
 // is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
 // large memory cache.
-func NewDatabaseWithCache(db ethdb.Database, cache int) Database {
+func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
 	csc, _ := lru.New(codeSizeCacheSize)
 	return &cachingDB{
-		db:            trie.NewDatabaseWithCache(db, cache),
+		db:            trie.NewDatabaseWithConfig(db, config),
 		codeSizeCache: csc,
+		codeCache:     fastcache.New(codeCacheSize),
 	}
 }
 
 type cachingDB struct {
 	db            *trie.Database
 	codeSizeCache *lru.Cache
+	codeCache     *fastcache.Cache
 }
 
 // OpenTrie opens the main account trie at a specific root hash.
 func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
-	return trie.NewSecure(root, db.db)
+	tr, err := trie.NewSecure(root, db.db)
+	if err != nil {
+		return nil, err
+	}
+	return tr, nil
 }
 
 // OpenStorageTrie opens the storage trie of an account.
 func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
-	return trie.NewSecure(root, db.db)
+	tr, err := trie.NewSecure(root, db.db)
+	if err != nil {
+		return nil, err
+	}
+	return tr, nil
 }
 
 // CopyTrie returns an independent copy of the given trie.
@@ -141,11 +157,32 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
 
 // ContractCode retrieves a particular contract's code.
 func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
-	code, err := db.db.Node(codeHash)
-	if err == nil {
+	if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
+		return code, nil
+	}
+	code := rawdb.ReadCode(db.db.DiskDB(), codeHash)
+	if len(code) > 0 {
+		db.codeCache.Set(codeHash.Bytes(), code)
+		db.codeSizeCache.Add(codeHash, len(code))
+		return code, nil
+	}
+	return nil, errors.New("not found")
+}
+
+// ContractCodeWithPrefix retrieves a particular contract's code. If the
+// code can't be found in the cache, then check the existence with **new**
+// db scheme.
+func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) {
+	if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
+		return code, nil
+	}
+	code := rawdb.ReadCodeWithPrefix(db.db.DiskDB(), codeHash)
+	if len(code) > 0 {
+		db.codeCache.Set(codeHash.Bytes(), code)
 		db.codeSizeCache.Add(codeHash, len(code))
+		return code, nil
 	}
-	return code, err
+	return nil, errors.New("not found")
 }
 
 // ContractCodeSize retrieves a particular contracts code's size.
diff --git a/core/state/dump.go b/core/state/dump.go
index ded9298ee2..b25da714fd 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -27,6 +27,14 @@ import (
 	"github.com/ethereum/go-ethereum/trie"
 )
 
+// DumpCollector interface which the state trie calls during iteration
+type DumpCollector interface {
+	// OnRoot is called with the state root
+	OnRoot(common.Hash)
+	// OnAccount is called once for each account in the trie
+	OnAccount(common.Address, DumpAccount)
+}
+
 // DumpAccount represents an account in the state.
 type DumpAccount struct {
 	Balance   string                 `json:"balance"`
@@ -46,9 +54,14 @@ type Dump struct {
 	Accounts map[common.Address]DumpAccount `json:"accounts"`
 }
 
-// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively.
-type iterativeDump struct {
-	*json.Encoder
+// OnRoot implements DumpCollector interface
+func (d *Dump) OnRoot(root common.Hash) {
+	d.Root = fmt.Sprintf("%x", root)
+}
+
+// OnAccount implements DumpCollector interface
+func (d *Dump) OnAccount(addr common.Address, account DumpAccount) {
+	d.Accounts[addr] = account
 }
 
 // IteratorDump is an implementation for iterating over data.
@@ -58,28 +71,23 @@ type IteratorDump struct {
 	Next     []byte                         `json:"next,omitempty"` // nil if no more accounts
 }
 
-// Collector interface which the state trie calls during iteration
-type collector interface {
-	onRoot(common.Hash)
-	onAccount(common.Address, DumpAccount)
-}
-
-func (d *Dump) onRoot(root common.Hash) {
+// OnRoot implements DumpCollector interface
+func (d *IteratorDump) OnRoot(root common.Hash) {
 	d.Root = fmt.Sprintf("%x", root)
 }
 
-func (d *Dump) onAccount(addr common.Address, account DumpAccount) {
+// OnAccount implements DumpCollector interface
+func (d *IteratorDump) OnAccount(addr common.Address, account DumpAccount) {
 	d.Accounts[addr] = account
 }
-func (d *IteratorDump) onRoot(root common.Hash) {
-	d.Root = fmt.Sprintf("%x", root)
-}
 
-func (d *IteratorDump) onAccount(addr common.Address, account DumpAccount) {
-	d.Accounts[addr] = account
+// iterativeDump is a DumpCollector-implementation which dumps output line-by-line iteratively.
+type iterativeDump struct {
+	*json.Encoder
 }
 
-func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) {
+// OnAccount implements DumpCollector interface
+func (d iterativeDump) OnAccount(addr common.Address, account DumpAccount) {
 	dumpAccount := &DumpAccount{
 		Balance:   account.Balance,
 		Nonce:     account.Nonce,
@@ -96,16 +104,16 @@ func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) {
 	d.Encode(dumpAccount)
 }
 
-func (d iterativeDump) onRoot(root common.Hash) {
+// OnRoot implements DumpCollector interface
+func (d iterativeDump) OnRoot(root common.Hash) {
 	d.Encode(struct {
 		Root common.Hash `json:"root"`
 	}{root})
 }
 
-func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) {
-	emptyAddress := (common.Address{})
+func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) {
 	missingPreimages := 0
-	c.onRoot(s.trie.Hash())
+	c.OnRoot(s.trie.Hash())
 
 	var count int
 	it := trie.NewIterator(s.trie.NodeIterator(start))
@@ -114,15 +122,14 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
 		if err := rlp.DecodeBytes(it.Value, &data); err != nil {
 			panic(err)
 		}
-		addr := common.BytesToAddress(s.trie.GetKey(it.Key))
-		obj := newObject(nil, addr, data)
 		account := DumpAccount{
 			Balance:  data.Balance.String(),
 			Nonce:    data.Nonce,
 			Root:     common.Bytes2Hex(data.Root[:]),
 			CodeHash: common.Bytes2Hex(data.CodeHash),
 		}
-		if emptyAddress == addr {
+		addrBytes := s.trie.GetKey(it.Key)
+		if addrBytes == nil {
 			// Preimage missing
 			missingPreimages++
 			if excludeMissingPreimages {
@@ -130,6 +137,8 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
 			}
 			account.SecureKey = it.Key
 		}
+		addr := common.BytesToAddress(addrBytes)
+		obj := newObject(s, addr, data)
 		if !excludeCode {
 			account.Code = common.Bytes2Hex(obj.Code(s.db))
 		}
@@ -145,7 +154,7 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP
 				account.Storage[common.BytesToHash(s.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(content)
 			}
 		}
-		c.onAccount(addr, account)
+		c.OnAccount(addr, account)
 		count++
 		if maxResults > 0 && count >= maxResults {
 			if it.Next() {
@@ -166,7 +175,7 @@ func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages b
 	dump := &Dump{
 		Accounts: make(map[common.Address]DumpAccount),
 	}
-	s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
+	s.DumpToCollector(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
 	return *dump
 }
 
@@ -175,14 +184,14 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool
 	dump := s.RawDump(excludeCode, excludeStorage, excludeMissingPreimages)
 	json, err := json.MarshalIndent(dump, "", "    ")
 	if err != nil {
-		fmt.Println("dump err", err)
+		fmt.Println("Dump err", err)
 	}
 	return json
 }
 
 // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout
 func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) {
-	s.dump(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
+	s.DumpToCollector(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0)
 }
 
 // IteratorDump dumps out a batch of accounts starts with the given start key
@@ -190,6 +199,6 @@ func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreima
 	iterator := &IteratorDump{
 		Accounts: make(map[common.Address]DumpAccount),
 	}
-	iterator.Next = s.dump(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults)
+	iterator.Next = s.DumpToCollector(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults)
 	return *iterator
 }
diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go
index 5060f7a651..d1afe9ca3e 100644
--- a/core/state/iterator_test.go
+++ b/core/state/iterator_test.go
@@ -28,6 +28,7 @@ import (
 func TestNodeIteratorCoverage(t *testing.T) {
 	// Create some arbitrary test state to iterate
 	db, root, _ := makeTestState()
+	db.TrieDB().Commit(root, false, nil)
 
 	state, err := New(root, db, nil)
 	if err != nil {
@@ -42,7 +43,10 @@ func TestNodeIteratorCoverage(t *testing.T) {
 	}
 	// Cross check the iterated hashes and the database/nodepool content
 	for hash := range hashes {
-		if _, err := db.TrieDB().Node(hash); err != nil {
+		if _, err = db.TrieDB().Node(hash); err != nil {
+			_, err = db.ContractCode(common.Hash{}, hash)
+		}
+		if err != nil {
 			t.Errorf("failed to retrieve reported node %x", hash)
 		}
 	}
diff --git a/core/state/journal.go b/core/state/journal.go
index f242dac5af..2070f30875 100644
--- a/core/state/journal.go
+++ b/core/state/journal.go
@@ -130,6 +130,14 @@ type (
 	touchChange struct {
 		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) {
@@ -234,3 +242,28 @@ func (ch addPreimageChange) revert(s *StateDB) {
 func (ch addPreimageChange) dirtied() *common.Address {
 	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
+}
diff --git a/core/state/pruner/bloom.go b/core/state/pruner/bloom.go
new file mode 100644
index 0000000000..4aeeb176e8
--- /dev/null
+++ b/core/state/pruner/bloom.go
@@ -0,0 +1,132 @@
+// 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 .
+
+package pruner
+
+import (
+	"encoding/binary"
+	"errors"
+	"os"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/log"
+	bloomfilter "github.com/holiman/bloomfilter/v2"
+)
+
+// stateBloomHasher is a wrapper around a byte blob to satisfy the interface API
+// requirements of the bloom library used. It's used to convert a trie hash or
+// contract code hash into a 64 bit mini hash.
+type stateBloomHasher []byte
+
+func (f stateBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") }
+func (f stateBloomHasher) Sum(b []byte) []byte               { panic("not implemented") }
+func (f stateBloomHasher) Reset()                            { panic("not implemented") }
+func (f stateBloomHasher) BlockSize() int                    { panic("not implemented") }
+func (f stateBloomHasher) Size() int                         { return 8 }
+func (f stateBloomHasher) Sum64() uint64                     { return binary.BigEndian.Uint64(f) }
+
+// stateBloom is a bloom filter used during the state convesion(snapshot->state).
+// The keys of all generated entries will be recorded here so that in the pruning
+// stage the entries belong to the specific version can be avoided for deletion.
+//
+// The false-positive is allowed here. The "false-positive" entries means they
+// actually don't belong to the specific version but they are not deleted in the
+// pruning. The downside of the false-positive allowance is we may leave some "dangling"
+// nodes in the disk. But in practice the it's very unlike the dangling node is
+// state root. So in theory this pruned state shouldn't be visited anymore. Another
+// potential issue is for fast sync. If we do another fast sync upon the pruned
+// database, it's problematic which will stop the expansion during the syncing.
+// TODO address it @rjl493456442 @holiman @karalabe.
+//
+// After the entire state is generated, the bloom filter should be persisted into
+// the disk. It indicates the whole generation procedure is finished.
+type stateBloom struct {
+	bloom *bloomfilter.Filter
+}
+
+// newStateBloomWithSize creates a brand new state bloom for state generation.
+// The bloom filter will be created by the passing bloom filter size. According
+// to the https://hur.st/bloomfilter/?n=600000000&p=&m=2048MB&k=4, the parameters
+// are picked so that the false-positive rate for mainnet is low enough.
+func newStateBloomWithSize(size uint64) (*stateBloom, error) {
+	bloom, err := bloomfilter.New(size*1024*1024*8, 4)
+	if err != nil {
+		return nil, err
+	}
+	log.Info("Initialized state bloom", "size", common.StorageSize(float64(bloom.M()/8)))
+	return &stateBloom{bloom: bloom}, nil
+}
+
+// NewStateBloomFromDisk loads the state bloom from the given file.
+// In this case the assumption is held the bloom filter is complete.
+func NewStateBloomFromDisk(filename string) (*stateBloom, error) {
+	bloom, _, err := bloomfilter.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return &stateBloom{bloom: bloom}, nil
+}
+
+// Commit flushes the bloom filter content into the disk and marks the bloom
+// as complete.
+func (bloom *stateBloom) Commit(filename, tempname string) error {
+	// Write the bloom out into a temporary file
+	_, err := bloom.bloom.WriteFile(tempname)
+	if err != nil {
+		return err
+	}
+	// Ensure the file is synced to disk
+	f, err := os.Open(tempname)
+	if err != nil {
+		return err
+	}
+	if err := f.Sync(); err != nil {
+		f.Close()
+		return err
+	}
+	f.Close()
+
+	// Move the teporary file into it's final location
+	return os.Rename(tempname, filename)
+}
+
+// Put implements the KeyValueWriter interface. But here only the key is needed.
+func (bloom *stateBloom) Put(key []byte, value []byte) error {
+	// If the key length is not 32bytes, ensure it's contract code
+	// entry with new scheme.
+	if len(key) != common.HashLength {
+		isCode, codeKey := rawdb.IsCodeKey(key)
+		if !isCode {
+			return errors.New("invalid entry")
+		}
+		bloom.bloom.Add(stateBloomHasher(codeKey))
+		return nil
+	}
+	bloom.bloom.Add(stateBloomHasher(key))
+	return nil
+}
+
+// Delete removes the key from the key-value data store.
+func (bloom *stateBloom) Delete(key []byte) error { panic("not supported") }
+
+// Contain is the wrapper of the underlying contains function which
+// reports whether the key is contained.
+// - If it says yes, the key may be contained
+// - If it says no, the key is definitely not contained.
+func (bloom *stateBloom) Contain(key []byte) (bool, error) {
+	return bloom.bloom.Contains(stateBloomHasher(key)), nil
+}
diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go
new file mode 100644
index 0000000000..9e3c531707
--- /dev/null
+++ b/core/state/pruner/pruner.go
@@ -0,0 +1,516 @@
+// 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 .
+
+package pruner
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/state/snapshot"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+const (
+	// stateBloomFilePrefix is the filename prefix of state bloom filter.
+	stateBloomFilePrefix = "statebloom"
+
+	// stateBloomFilePrefix is the filename suffix of state bloom filter.
+	stateBloomFileSuffix = "bf.gz"
+
+	// stateBloomFileTempSuffix is the filename suffix of state bloom filter
+	// while it is being written out to detect write aborts.
+	stateBloomFileTempSuffix = ".tmp"
+
+	// rangeCompactionThreshold is the minimal deleted entry number for
+	// triggering range compaction. It's a quite arbitrary number but just
+	// to avoid triggering range compaction because of small deletion.
+	rangeCompactionThreshold = 100000
+)
+
+var (
+	// emptyRoot is the known root hash of an empty trie.
+	emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+
+	// emptyCode is the known hash of the empty EVM bytecode.
+	emptyCode = crypto.Keccak256(nil)
+)
+
+// Pruner is an offline tool to prune the stale state with the
+// help of the snapshot. The workflow of pruner is very simple:
+//
+// - iterate the snapshot, reconstruct the relevant state
+// - iterate the database, delete all other state entries which
+//   don't belong to the target state and the genesis state
+//
+// It can take several hours(around 2 hours for mainnet) to finish
+// the whole pruning work. It's recommended to run this offline tool
+// periodically in order to release the disk usage and improve the
+// disk read performance to some extent.
+type Pruner struct {
+	db            ethdb.Database
+	stateBloom    *stateBloom
+	datadir       string
+	trieCachePath string
+	headHeader    *types.Header
+	snaptree      *snapshot.Tree
+}
+
+// NewPruner creates the pruner instance.
+func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint64) (*Pruner, error) {
+	headBlock := rawdb.ReadHeadBlock(db)
+	if headBlock == nil {
+		return nil, errors.New("Failed to load head block")
+	}
+	snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false)
+	if err != nil {
+		return nil, err // The relevant snapshot(s) might not exist
+	}
+	// Sanitize the bloom filter size if it's too small.
+	if bloomSize < 256 {
+		log.Warn("Sanitizing bloomfilter size", "provided(MB)", bloomSize, "updated(MB)", 256)
+		bloomSize = 256
+	}
+	stateBloom, err := newStateBloomWithSize(bloomSize)
+	if err != nil {
+		return nil, err
+	}
+	return &Pruner{
+		db:            db,
+		stateBloom:    stateBloom,
+		datadir:       datadir,
+		trieCachePath: trieCachePath,
+		headHeader:    headBlock.Header(),
+		snaptree:      snaptree,
+	}, nil
+}
+
+func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, stateBloom *stateBloom, bloomPath string, middleStateRoots map[common.Hash]struct{}, start time.Time) error {
+	// Delete all stale trie nodes in the disk. With the help of state bloom
+	// the trie nodes(and codes) belong to the active state will be filtered
+	// out. A very small part of stale tries will also be filtered because of
+	// the false-positive rate of bloom filter. But the assumption is held here
+	// that the false-positive is low enough(~0.05%). The probablity of the
+	// dangling node is the state root is super low. So the dangling nodes in
+	// theory will never ever be visited again.
+	var (
+		count  int
+		size   common.StorageSize
+		pstart = time.Now()
+		logged = time.Now()
+		batch  = maindb.NewBatch()
+		iter   = maindb.NewIterator(nil, nil)
+	)
+	for iter.Next() {
+		key := iter.Key()
+
+		// All state entries don't belong to specific state and genesis are deleted here
+		// - trie node
+		// - legacy contract code
+		// - new-scheme contract code
+		isCode, codeKey := rawdb.IsCodeKey(key)
+		if len(key) == common.HashLength || isCode {
+			checkKey := key
+			if isCode {
+				checkKey = codeKey
+			}
+			if _, exist := middleStateRoots[common.BytesToHash(checkKey)]; exist {
+				log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey))
+			} else {
+				if ok, err := stateBloom.Contain(checkKey); err != nil {
+					return err
+				} else if ok {
+					continue
+				}
+			}
+			count += 1
+			size += common.StorageSize(len(key) + len(iter.Value()))
+			batch.Delete(key)
+
+			var eta time.Duration // Realistically will never remain uninited
+			if done := binary.BigEndian.Uint64(key[:8]); done > 0 {
+				var (
+					left  = math.MaxUint64 - binary.BigEndian.Uint64(key[:8])
+					speed = done/uint64(time.Since(pstart)/time.Millisecond+1) + 1 // +1s to avoid division by zero
+				)
+				eta = time.Duration(left/speed) * time.Millisecond
+			}
+			if time.Since(logged) > 8*time.Second {
+				log.Info("Pruning state data", "nodes", count, "size", size,
+					"elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta))
+				logged = time.Now()
+			}
+			// Recreate the iterator after every batch commit in order
+			// to allow the underlying compactor to delete the entries.
+			if batch.ValueSize() >= ethdb.IdealBatchSize {
+				batch.Write()
+				batch.Reset()
+
+				iter.Release()
+				iter = maindb.NewIterator(nil, key)
+			}
+		}
+	}
+	if batch.ValueSize() > 0 {
+		batch.Write()
+		batch.Reset()
+	}
+	iter.Release()
+	log.Info("Pruned state data", "nodes", count, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart)))
+
+	// Pruning is done, now drop the "useless" layers from the snapshot.
+	// Firstly, flushing the target layer into the disk. After that all
+	// diff layers below the target will all be merged into the disk.
+	if root != snaptree.DiskRoot() {
+		if err := snaptree.Cap(root, 0); err != nil {
+			return err
+		}
+	}
+	// Secondly, flushing the snapshot journal into the disk. All diff
+	// layers upon are dropped silently. Eventually the entire snapshot
+	// tree is converted into a single disk layer with the pruning target
+	// as the root.
+	if _, err := snaptree.Journal(root); err != nil {
+		return err
+	}
+	// Delete the state bloom, it marks the entire pruning procedure is
+	// finished. If any crashes or manual exit happens before this,
+	// `RecoverPruning` will pick it up in the next restarts to redo all
+	// the things.
+	os.RemoveAll(bloomPath)
+
+	// Start compactions, will remove the deleted data from the disk immediately.
+	// Note for small pruning, the compaction is skipped.
+	if count >= rangeCompactionThreshold {
+		cstart := time.Now()
+		for b := 0x00; b <= 0xf0; b += 0x10 {
+			var (
+				start = []byte{byte(b)}
+				end   = []byte{byte(b + 0x10)}
+			)
+			if b == 0xf0 {
+				end = nil
+			}
+			log.Info("Compacting database", "range", fmt.Sprintf("%#x-%#x", start, end), "elapsed", common.PrettyDuration(time.Since(cstart)))
+			if err := maindb.Compact(start, end); err != nil {
+				log.Error("Database compaction failed", "error", err)
+				return err
+			}
+		}
+		log.Info("Database compaction finished", "elapsed", common.PrettyDuration(time.Since(cstart)))
+	}
+	log.Info("State pruning successful", "pruned", size, "elapsed", common.PrettyDuration(time.Since(start)))
+	return nil
+}
+
+// Prune deletes all historical state nodes except the nodes belong to the
+// specified state version. If user doesn't specify the state version, use
+// the bottom-most snapshot diff layer as the target.
+func (p *Pruner) Prune(root common.Hash) error {
+	// If the state bloom filter is already committed previously,
+	// reuse it for pruning instead of generating a new one. It's
+	// mandatory because a part of state may already be deleted,
+	// the recovery procedure is necessary.
+	_, stateBloomRoot, err := findBloomFilter(p.datadir)
+	if err != nil {
+		return err
+	}
+	if stateBloomRoot != (common.Hash{}) {
+		return RecoverPruning(p.datadir, p.db, p.trieCachePath)
+	}
+	// If the target state root is not specified, use the HEAD-127 as the
+	// target. The reason for picking it is:
+	// - in most of the normal cases, the related state is available
+	// - the probability of this layer being reorg is very low
+	var layers []snapshot.Snapshot
+	if root == (common.Hash{}) {
+		// Retrieve all snapshot layers from the current HEAD.
+		// In theory there are 128 difflayers + 1 disk layer present,
+		// so 128 diff layers are expected to be returned.
+		layers = p.snaptree.Snapshots(p.headHeader.Root, 128, true)
+		if len(layers) != 128 {
+			// Reject if the accumulated diff layers are less than 128. It
+			// means in most of normal cases, there is no associated state
+			// with bottom-most diff layer.
+			return fmt.Errorf("snapshot not old enough yet: need %d more blocks", 128-len(layers))
+		}
+		// Use the bottom-most diff layer as the target
+		root = layers[len(layers)-1].Root()
+	}
+	// Ensure the root is really present. The weak assumption
+	// is the presence of root can indicate the presence of the
+	// entire trie.
+	if blob := rawdb.ReadTrieNode(p.db, root); len(blob) == 0 {
+		// The special case is for clique based networks(rinkeby, goerli
+		// and some other private networks), it's possible that two
+		// consecutive blocks will have same root. In this case snapshot
+		// difflayer won't be created. So HEAD-127 may not paired with
+		// head-127 layer. Instead the paired layer is higher than the
+		// bottom-most diff layer. Try to find the bottom-most snapshot
+		// layer with state available.
+		//
+		// Note HEAD is ignored. Usually there is the associated
+		// state available, but we don't want to use the topmost state
+		// as the pruning target.
+		var found bool
+		for i := len(layers) - 2; i >= 1; i-- {
+			if blob := rawdb.ReadTrieNode(p.db, layers[i].Root()); len(blob) != 0 {
+				root = layers[i].Root()
+				found = true
+				log.Info("Selecting middle-layer as the pruning target", "root", root, "depth", i)
+				break
+			}
+		}
+		if !found {
+			if blob := rawdb.ReadTrieNode(p.db, p.snaptree.DiskRoot()); len(blob) != 0 {
+				root = p.snaptree.DiskRoot()
+				found = true
+				log.Info("Selecting disk-layer as the pruning target", "root", root)
+			}
+		}
+		if !found {
+			if len(layers) > 0 {
+				return errors.New("no snapshot paired state")
+			}
+			return fmt.Errorf("associated state[%x] is not present", root)
+		}
+	} else {
+		if len(layers) > 0 {
+			log.Info("Selecting bottom-most difflayer as the pruning target", "root", root, "height", p.headHeader.Number.Uint64()-127)
+		} else {
+			log.Info("Selecting user-specified state as the pruning target", "root", root)
+		}
+	}
+	// Before start the pruning, delete the clean trie cache first.
+	// It's necessary otherwise in the next restart we will hit the
+	// deleted state root in the "clean cache" so that the incomplete
+	// state is picked for usage.
+	deleteCleanTrieCache(p.trieCachePath)
+
+	// All the state roots of the middle layer should be forcibly pruned,
+	// otherwise the dangling state will be left.
+	middleRoots := make(map[common.Hash]struct{})
+	for _, layer := range layers {
+		if layer.Root() == root {
+			break
+		}
+		middleRoots[layer.Root()] = struct{}{}
+	}
+	// Traverse the target state, re-construct the whole state trie and
+	// commit to the given bloom filter.
+	start := time.Now()
+	if err := snapshot.GenerateTrie(p.snaptree, root, p.db, p.stateBloom); err != nil {
+		return err
+	}
+	// Traverse the genesis, put all genesis state entries into the
+	// bloom filter too.
+	if err := extractGenesis(p.db, p.stateBloom); err != nil {
+		return err
+	}
+	filterName := bloomFilterName(p.datadir, root)
+
+	log.Info("Writing state bloom to disk", "name", filterName)
+	if err := p.stateBloom.Commit(filterName, filterName+stateBloomFileTempSuffix); err != nil {
+		return err
+	}
+	log.Info("State bloom filter committed", "name", filterName)
+	return prune(p.snaptree, root, p.db, p.stateBloom, filterName, middleRoots, start)
+}
+
+// RecoverPruning will resume the pruning procedure during the system restart.
+// This function is used in this case: user tries to prune state data, but the
+// system was interrupted midway because of crash or manual-kill. In this case
+// if the bloom filter for filtering active state is already constructed, the
+// pruning can be resumed. What's more if the bloom filter is constructed, the
+// pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left
+// in the disk.
+func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) error {
+	stateBloomPath, stateBloomRoot, err := findBloomFilter(datadir)
+	if err != nil {
+		return err
+	}
+	if stateBloomPath == "" {
+		return nil // nothing to recover
+	}
+	headBlock := rawdb.ReadHeadBlock(db)
+	if headBlock == nil {
+		return errors.New("Failed to load head block")
+	}
+	// Initialize the snapshot tree in recovery mode to handle this special case:
+	// - Users run the `prune-state` command multiple times
+	// - Neither these `prune-state` running is finished(e.g. interrupted manually)
+	// - The state bloom filter is already generated, a part of state is deleted,
+	//   so that resuming the pruning here is mandatory
+	// - The state HEAD is rewound already because of multiple incomplete `prune-state`
+	// In this case, even the state HEAD is not exactly matched with snapshot, it
+	// still feasible to recover the pruning correctly.
+	snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true)
+	if err != nil {
+		return err // The relevant snapshot(s) might not exist
+	}
+	stateBloom, err := NewStateBloomFromDisk(stateBloomPath)
+	if err != nil {
+		return err
+	}
+	log.Info("Loaded state bloom filter", "path", stateBloomPath)
+
+	// Before start the pruning, delete the clean trie cache first.
+	// It's necessary otherwise in the next restart we will hit the
+	// deleted state root in the "clean cache" so that the incomplete
+	// state is picked for usage.
+	deleteCleanTrieCache(trieCachePath)
+
+	// All the state roots of the middle layers should be forcibly pruned,
+	// otherwise the dangling state will be left.
+	var (
+		found       bool
+		layers      = snaptree.Snapshots(headBlock.Root(), 128, true)
+		middleRoots = make(map[common.Hash]struct{})
+	)
+	for _, layer := range layers {
+		if layer.Root() == stateBloomRoot {
+			found = true
+			break
+		}
+		middleRoots[layer.Root()] = struct{}{}
+	}
+	if !found {
+		log.Error("Pruning target state is not existent")
+		return errors.New("non-existent target state")
+	}
+	return prune(snaptree, stateBloomRoot, db, stateBloom, stateBloomPath, middleRoots, time.Now())
+}
+
+// extractGenesis loads the genesis state and commits all the state entries
+// into the given bloomfilter.
+func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error {
+	genesisHash := rawdb.ReadCanonicalHash(db, 0)
+	if genesisHash == (common.Hash{}) {
+		return errors.New("missing genesis hash")
+	}
+	genesis := rawdb.ReadBlock(db, genesisHash, 0)
+	if genesis == nil {
+		return errors.New("missing genesis block")
+	}
+	t, err := trie.NewSecure(genesis.Root(), trie.NewDatabase(db))
+	if err != nil {
+		return err
+	}
+	accIter := t.NodeIterator(nil)
+	for accIter.Next(true) {
+		hash := accIter.Hash()
+
+		// Embedded nodes don't have hash.
+		if hash != (common.Hash{}) {
+			stateBloom.Put(hash.Bytes(), nil)
+		}
+		// If it's a leaf node, yes we are touching an account,
+		// dig into the storage trie further.
+		if accIter.Leaf() {
+			var acc state.Account
+			if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
+				return err
+			}
+			if acc.Root != emptyRoot {
+				storageTrie, err := trie.NewSecure(acc.Root, trie.NewDatabase(db))
+				if err != nil {
+					return err
+				}
+				storageIter := storageTrie.NodeIterator(nil)
+				for storageIter.Next(true) {
+					hash := storageIter.Hash()
+					if hash != (common.Hash{}) {
+						stateBloom.Put(hash.Bytes(), nil)
+					}
+				}
+				if storageIter.Error() != nil {
+					return storageIter.Error()
+				}
+			}
+			if !bytes.Equal(acc.CodeHash, emptyCode) {
+				stateBloom.Put(acc.CodeHash, nil)
+			}
+		}
+	}
+	return accIter.Error()
+}
+
+func bloomFilterName(datadir string, hash common.Hash) string {
+	return filepath.Join(datadir, fmt.Sprintf("%s.%s.%s", stateBloomFilePrefix, hash.Hex(), stateBloomFileSuffix))
+}
+
+func isBloomFilter(filename string) (bool, common.Hash) {
+	filename = filepath.Base(filename)
+	if strings.HasPrefix(filename, stateBloomFilePrefix) && strings.HasSuffix(filename, stateBloomFileSuffix) {
+		return true, common.HexToHash(filename[len(stateBloomFilePrefix)+1 : len(filename)-len(stateBloomFileSuffix)-1])
+	}
+	return false, common.Hash{}
+}
+
+func findBloomFilter(datadir string) (string, common.Hash, error) {
+	var (
+		stateBloomPath string
+		stateBloomRoot common.Hash
+	)
+	if err := filepath.Walk(datadir, func(path string, info os.FileInfo, err error) error {
+		if info != nil && !info.IsDir() {
+			ok, root := isBloomFilter(path)
+			if ok {
+				stateBloomPath = path
+				stateBloomRoot = root
+			}
+		}
+		return nil
+	}); err != nil {
+		return "", common.Hash{}, err
+	}
+	return stateBloomPath, stateBloomRoot, nil
+}
+
+const warningLog = `
+
+WARNING!
+
+The clean trie cache is not found. Please delete it by yourself after the 
+pruning. Remember don't start the Geth without deleting the clean trie cache
+otherwise the entire database may be damaged!
+
+Check the command description "geth snapshot prune-state --help" for more details.
+`
+
+func deleteCleanTrieCache(path string) {
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		log.Warn(warningLog)
+		return
+	}
+	os.RemoveAll(path)
+	log.Info("Deleted trie clean cache", "path", path)
+}
diff --git a/core/state/snapshot/account.go b/core/state/snapshot/account.go
index 1068dc2a01..b92e942950 100644
--- a/core/state/snapshot/account.go
+++ b/core/state/snapshot/account.go
@@ -24,8 +24,10 @@ import (
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
-// Account is a slim version of a state.Account, where the root and code hash
-// are replaced with a nil byte slice for empty accounts.
+// Account is a modified version of a state.Account, where the root is replaced
+// with a byte slice. This format can be used to represent full-consensus format
+// or slim-snapshot format which replaces the empty root and code hash as nil
+// byte slice.
 type Account struct {
 	Nonce    uint64
 	Balance  *big.Int
@@ -33,9 +35,8 @@ type Account struct {
 	CodeHash []byte
 }
 
-// AccountRLP converts a state.Account content into a slim snapshot version RLP
-// encoded.
-func AccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte {
+// SlimAccount converts a state.Account content into a slim snapshot account
+func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) Account {
 	slim := Account{
 		Nonce:   nonce,
 		Balance: balance,
@@ -46,9 +47,40 @@ func AccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byt
 	if !bytes.Equal(codehash, emptyCode[:]) {
 		slim.CodeHash = codehash
 	}
-	data, err := rlp.EncodeToBytes(slim)
+	return slim
+}
+
+// SlimAccountRLP converts a state.Account content into a slim snapshot
+// version RLP encoded.
+func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte {
+	data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash))
 	if err != nil {
 		panic(err)
 	}
 	return data
 }
+
+// FullAccount decodes the data on the 'slim RLP' format and return
+// the consensus format account.
+func FullAccount(data []byte) (Account, error) {
+	var account Account
+	if err := rlp.DecodeBytes(data, &account); err != nil {
+		return Account{}, err
+	}
+	if len(account.Root) == 0 {
+		account.Root = emptyRoot[:]
+	}
+	if len(account.CodeHash) == 0 {
+		account.CodeHash = emptyCode[:]
+	}
+	return account, nil
+}
+
+// FullAccountRLP converts data on the 'slim RLP' format into the full RLP-format.
+func FullAccountRLP(data []byte) ([]byte, error) {
+	account, err := FullAccount(data)
+	if err != nil {
+		return nil, err
+	}
+	return rlp.EncodeToBytes(account)
+}
diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go
index d9c86e5163..f70cbf1e68 100644
--- a/core/state/snapshot/conversion.go
+++ b/core/state/snapshot/conversion.go
@@ -17,98 +17,359 @@
 package snapshot
 
 import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math"
+	"runtime"
 	"sync"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/trie"
 )
 
-// conversionAccount is used for converting between full and slim format. When
-// doing this, we can consider 'balance' as a byte array, as it has already
-// been converted from big.Int into an rlp-byteslice.
-type conversionAccount struct {
-	Nonce    uint64
-	Balance  []byte
-	Root     []byte
-	CodeHash []byte
+// trieKV represents a trie key-value pair
+type trieKV struct {
+	key   common.Hash
+	value []byte
+}
+
+type (
+	// trieGeneratorFn is the interface of trie generation which can
+	// be implemented by different trie algorithm.
+	trieGeneratorFn func(db ethdb.KeyValueWriter, in chan (trieKV), out chan (common.Hash))
+
+	// leafCallbackFn is the callback invoked at the leaves of the trie,
+	// returns the subtrie root with the specified subtrie identifier.
+	leafCallbackFn func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error)
+)
+
+// GenerateAccountTrieRoot takes an account iterator and reproduces the root hash.
+func GenerateAccountTrieRoot(it AccountIterator) (common.Hash, error) {
+	return generateTrieRoot(nil, it, common.Hash{}, stackTrieGenerate, nil, newGenerateStats(), true)
 }
 
-// SlimToFull converts data on the 'slim RLP' format into the full RLP-format
-func SlimToFull(data []byte) ([]byte, error) {
-	acc := &conversionAccount{}
-	if err := rlp.DecodeBytes(data, acc); err != nil {
-		return nil, err
+// GenerateStorageTrieRoot takes a storage iterator and reproduces the root hash.
+func GenerateStorageTrieRoot(account common.Hash, it StorageIterator) (common.Hash, error) {
+	return generateTrieRoot(nil, it, account, stackTrieGenerate, nil, newGenerateStats(), true)
+}
+
+// GenerateTrie takes the whole snapshot tree as the input, traverses all the
+// accounts as well as the corresponding storages and regenerate the whole state
+// (account trie + all storage tries).
+func GenerateTrie(snaptree *Tree, root common.Hash, src ethdb.Database, dst ethdb.KeyValueWriter) error {
+	// Traverse all state by snapshot, re-generate the whole state trie
+	acctIt, err := snaptree.AccountIterator(root, common.Hash{})
+	if err != nil {
+		return err // The required snapshot might not exist.
 	}
-	if len(acc.Root) == 0 {
-		acc.Root = emptyRoot[:]
+	defer acctIt.Release()
+
+	got, err := generateTrieRoot(dst, acctIt, common.Hash{}, stackTrieGenerate, func(dst ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
+		// Migrate the code first, commit the contract code into the tmp db.
+		if codeHash != emptyCode {
+			code := rawdb.ReadCode(src, codeHash)
+			if len(code) == 0 {
+				return common.Hash{}, errors.New("failed to read contract code")
+			}
+			rawdb.WriteCode(dst, codeHash, code)
+		}
+		// Then migrate all storage trie nodes into the tmp db.
+		storageIt, err := snaptree.StorageIterator(root, accountHash, common.Hash{})
+		if err != nil {
+			return common.Hash{}, err
+		}
+		defer storageIt.Release()
+
+		hash, err := generateTrieRoot(dst, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
+		if err != nil {
+			return common.Hash{}, err
+		}
+		return hash, nil
+	}, newGenerateStats(), true)
+
+	if err != nil {
+		return err
 	}
-	if len(acc.CodeHash) == 0 {
-		acc.CodeHash = emptyCode[:]
+	if got != root {
+		return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root)
 	}
-	fullData, err := rlp.EncodeToBytes(acc)
-	if err != nil {
-		return nil, err
+	return nil
+}
+
+// generateStats is a collection of statistics gathered by the trie generator
+// for logging purposes.
+type generateStats struct {
+	head  common.Hash
+	start time.Time
+
+	accounts uint64 // Number of accounts done (including those being crawled)
+	slots    uint64 // Number of storage slots done (including those being crawled)
+
+	slotsStart map[common.Hash]time.Time   // Start time for account slot crawling
+	slotsHead  map[common.Hash]common.Hash // Slot head for accounts being crawled
+
+	lock sync.RWMutex
+}
+
+// newGenerateStats creates a new generator stats.
+func newGenerateStats() *generateStats {
+	return &generateStats{
+		slotsStart: make(map[common.Hash]time.Time),
+		slotsHead:  make(map[common.Hash]common.Hash),
+		start:      time.Now(),
 	}
-	return fullData, nil
 }
 
-// trieKV represents a trie key-value pair
-type trieKV struct {
-	key   common.Hash
-	value []byte
+// progressAccounts updates the generator stats for the account range.
+func (stat *generateStats) progressAccounts(account common.Hash, done uint64) {
+	stat.lock.Lock()
+	defer stat.lock.Unlock()
+
+	stat.accounts += done
+	stat.head = account
+}
+
+// finishAccounts updates the gemerator stats for the finished account range.
+func (stat *generateStats) finishAccounts(done uint64) {
+	stat.lock.Lock()
+	defer stat.lock.Unlock()
+
+	stat.accounts += done
 }
 
-type trieGeneratorFn func(in chan (trieKV), out chan (common.Hash))
+// progressContract updates the generator stats for a specific in-progress contract.
+func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) {
+	stat.lock.Lock()
+	defer stat.lock.Unlock()
 
-// GenerateTrieRoot takes an account iterator and reproduces the root hash.
-func GenerateTrieRoot(it AccountIterator) common.Hash {
-	return generateTrieRoot(it, stdGenerate)
+	stat.slots += done
+	stat.slotsHead[account] = slot
+	if _, ok := stat.slotsStart[account]; !ok {
+		stat.slotsStart[account] = time.Now()
+	}
 }
 
-func generateTrieRoot(it AccountIterator, generatorFn trieGeneratorFn) common.Hash {
+// finishContract updates the generator stats for a specific just-finished contract.
+func (stat *generateStats) finishContract(account common.Hash, done uint64) {
+	stat.lock.Lock()
+	defer stat.lock.Unlock()
+
+	stat.slots += done
+	delete(stat.slotsHead, account)
+	delete(stat.slotsStart, account)
+}
+
+// report prints the cumulative progress statistic smartly.
+func (stat *generateStats) report() {
+	stat.lock.RLock()
+	defer stat.lock.RUnlock()
+
+	ctx := []interface{}{
+		"accounts", stat.accounts,
+		"slots", stat.slots,
+		"elapsed", common.PrettyDuration(time.Since(stat.start)),
+	}
+	if stat.accounts > 0 {
+		// If there's progress on the account trie, estimate the time to finish crawling it
+		if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 {
+			var (
+				left  = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts
+				speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
+				eta   = time.Duration(left/speed) * time.Millisecond
+			)
+			// If there are large contract crawls in progress, estimate their finish time
+			for acc, head := range stat.slotsHead {
+				start := stat.slotsStart[acc]
+				if done := binary.BigEndian.Uint64(head[:8]); done > 0 {
+					var (
+						left  = math.MaxUint64 - binary.BigEndian.Uint64(head[:8])
+						speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
+					)
+					// Override the ETA if larger than the largest until now
+					if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA {
+						eta = slotETA
+					}
+				}
+			}
+			ctx = append(ctx, []interface{}{
+				"eta", common.PrettyDuration(eta),
+			}...)
+		}
+	}
+	log.Info("Iterating state snapshot", ctx...)
+}
+
+// reportDone prints the last log when the whole generation is finished.
+func (stat *generateStats) reportDone() {
+	stat.lock.RLock()
+	defer stat.lock.RUnlock()
+
+	var ctx []interface{}
+	ctx = append(ctx, []interface{}{"accounts", stat.accounts}...)
+	if stat.slots != 0 {
+		ctx = append(ctx, []interface{}{"slots", stat.slots}...)
+	}
+	ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...)
+	log.Info("Iterated snapshot", ctx...)
+}
+
+// runReport periodically prints the progress information.
+func runReport(stats *generateStats, stop chan bool) {
+	timer := time.NewTimer(0)
+	defer timer.Stop()
+
+	for {
+		select {
+		case <-timer.C:
+			stats.report()
+			timer.Reset(time.Second * 8)
+		case success := <-stop:
+			if success {
+				stats.reportDone()
+			}
+			return
+		}
+	}
+}
+
+// generateTrieRoot generates the trie hash based on the snapshot iterator.
+// It can be used for generating account trie, storage trie or even the
+// whole state which connects the accounts and the corresponding storages.
+func generateTrieRoot(db ethdb.KeyValueWriter, it Iterator, account common.Hash, generatorFn trieGeneratorFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) {
 	var (
-		in  = make(chan trieKV)      // chan to pass leaves
-		out = make(chan common.Hash) // chan to collect result
-		wg  sync.WaitGroup
+		in      = make(chan trieKV)         // chan to pass leaves
+		out     = make(chan common.Hash, 1) // chan to collect result
+		stoplog = make(chan bool, 1)        // 1-size buffer, works when logging is not enabled
+		wg      sync.WaitGroup
 	)
+	// Spin up a go-routine for trie hash re-generation
 	wg.Add(1)
 	go func() {
-		generatorFn(in, out)
-		wg.Done()
+		defer wg.Done()
+		generatorFn(db, in, out)
 	}()
-	// Feed leaves
-	start := time.Now()
-	logged := time.Now()
-	accounts := 0
+	// Spin up a go-routine for progress logging
+	if report && stats != nil {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			runReport(stats, stoplog)
+		}()
+	}
+	// Create a semaphore to assign tasks and collect results through. We'll pre-
+	// fill it with nils, thus using the same channel for both limiting concurrent
+	// processing and gathering results.
+	threads := runtime.NumCPU()
+	results := make(chan error, threads)
+	for i := 0; i < threads; i++ {
+		results <- nil // fill the semaphore
+	}
+	// stop is a helper function to shutdown the background threads
+	// and return the re-generated trie hash.
+	stop := func(fail error) (common.Hash, error) {
+		close(in)
+		result := <-out
+		for i := 0; i < threads; i++ {
+			if err := <-results; err != nil && fail == nil {
+				fail = err
+			}
+		}
+		stoplog <- fail == nil
+
+		wg.Wait()
+		return result, fail
+	}
+	var (
+		logged    = time.Now()
+		processed = uint64(0)
+		leaf      trieKV
+	)
+	// Start to feed leaves
 	for it.Next() {
-		slimData := it.Account()
-		fullData, _ := SlimToFull(slimData)
-		l := trieKV{it.Hash(), fullData}
-		in <- l
-		if time.Since(logged) > 8*time.Second {
-			log.Info("Generating trie hash from snapshot",
-				"at", l.key, "accounts", accounts, "elapsed", time.Since(start))
-			logged = time.Now()
+		if account == (common.Hash{}) {
+			var (
+				err      error
+				fullData []byte
+			)
+			if leafCallback == nil {
+				fullData, err = FullAccountRLP(it.(AccountIterator).Account())
+				if err != nil {
+					return stop(err)
+				}
+			} else {
+				// Wait until the semaphore allows us to continue, aborting if
+				// a sub-task failed
+				if err := <-results; err != nil {
+					results <- nil // stop will drain the results, add a noop back for this error we just consumed
+					return stop(err)
+				}
+				// Fetch the next account and process it concurrently
+				account, err := FullAccount(it.(AccountIterator).Account())
+				if err != nil {
+					return stop(err)
+				}
+				go func(hash common.Hash) {
+					subroot, err := leafCallback(db, hash, common.BytesToHash(account.CodeHash), stats)
+					if err != nil {
+						results <- err
+						return
+					}
+					if !bytes.Equal(account.Root, subroot.Bytes()) {
+						results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot)
+						return
+					}
+					results <- nil
+				}(it.Hash())
+				fullData, err = rlp.EncodeToBytes(account)
+				if err != nil {
+					return stop(err)
+				}
+			}
+			leaf = trieKV{it.Hash(), fullData}
+		} else {
+			leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())}
+		}
+		in <- leaf
+
+		// Accumulate the generation statistic if it's required.
+		processed++
+		if time.Since(logged) > 3*time.Second && stats != nil {
+			if account == (common.Hash{}) {
+				stats.progressAccounts(it.Hash(), processed)
+			} else {
+				stats.progressContract(account, it.Hash(), processed)
+			}
+			logged, processed = time.Now(), 0
+		}
+	}
+	// Commit the last part statistic.
+	if processed > 0 && stats != nil {
+		if account == (common.Hash{}) {
+			stats.finishAccounts(processed)
+		} else {
+			stats.finishContract(account, processed)
 		}
-		accounts++
 	}
-	close(in)
-	result := <-out
-	log.Info("Generated trie hash from snapshot", "accounts", accounts, "elapsed", time.Since(start))
-	wg.Wait()
-	return result
+	return stop(nil)
 }
 
-// stdGenerate is a very basic hexary trie builder which uses the same Trie
-// as the rest of geth, with no enhancements or optimizations
-func stdGenerate(in chan (trieKV), out chan (common.Hash)) {
-	t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New()))
+func stackTrieGenerate(db ethdb.KeyValueWriter, in chan trieKV, out chan common.Hash) {
+	t := trie.NewStackTrie(db)
 	for leaf := range in {
 		t.TryUpdate(leaf.key[:], leaf.value)
 	}
-	out <- t.Hash()
+	var root common.Hash
+	if db == nil {
+		root = t.Hash()
+	} else {
+		root, _ = t.Commit()
+	}
+	out <- root
 }
diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go
index 86ca5c8ba0..ee88938b77 100644
--- a/core/state/snapshot/difflayer.go
+++ b/core/state/snapshot/difflayer.go
@@ -28,7 +28,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/rlp"
-	"github.com/steakknife/bloomfilter"
+	bloomfilter "github.com/holiman/bloomfilter/v2"
 )
 
 var (
@@ -44,7 +44,7 @@ var (
 	// aggregatorItemLimit is an approximate number of items that will end up
 	// in the agregator layer before it's flushed out to disk. A plain account
 	// weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot
-	// 0B (+hash). Slots are mostly set/unset in lockstep, so thet average at
+	// 0B (+hash). Slots are mostly set/unset in lockstep, so that average at
 	// 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a
 	// smaller number to be on the safe side.
 	aggregatorItemLimit = aggregatorMemoryLimit / 42
@@ -105,11 +105,18 @@ type diffLayer struct {
 	root  common.Hash // Root hash to which this snapshot diff belongs to
 	stale uint32      // Signals that the layer became stale (state progressed)
 
+	// destructSet is a very special helper marker. If an account is marked as
+	// deleted, then it's recorded in this set. However it's allowed that an account
+	// is included here but still available in other sets(e.g. storageData). The
+	// reason is the diff layer includes all the changes in a *block*. It can
+	// happen that in the tx_1, account A is self-destructed while in the tx_2
+	// it's recreated. But we still need this marker to indicate the "old" A is
+	// deleted, all data in other set belongs to the "new" A.
 	destructSet map[common.Hash]struct{}               // Keyed markers for deleted (and potentially) recreated accounts
 	accountList []common.Hash                          // List of account for iteration. If it exists, it's sorted, otherwise it's nil
-	accountData map[common.Hash][]byte                 // Keyed accounts for direct retrival (nil means deleted)
+	accountData map[common.Hash][]byte                 // Keyed accounts for direct retrieval (nil means deleted)
 	storageList map[common.Hash][]common.Hash          // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil
-	storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted)
+	storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrieval. one per account (nil means deleted)
 
 	diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer
 
@@ -169,6 +176,7 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s
 		destructSet: destructs,
 		accountData: accounts,
 		storageData: storage,
+		storageList: make(map[common.Hash][]common.Hash),
 	}
 	switch parent := parent.(type) {
 	case *diskLayer:
@@ -183,30 +191,21 @@ func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]s
 		if blob == nil {
 			panic(fmt.Sprintf("account %#x nil", accountHash))
 		}
+		// Determine memory size and track the dirty writes
+		dl.memory += uint64(common.HashLength + len(blob))
+		snapshotDirtyAccountWriteMeter.Mark(int64(len(blob)))
 	}
 	for accountHash, slots := range storage {
 		if slots == nil {
 			panic(fmt.Sprintf("storage %#x nil", accountHash))
 		}
-	}
-	// Determine memory size and track the dirty writes
-	for _, data := range accounts {
-		dl.memory += uint64(common.HashLength + len(data))
-		snapshotDirtyAccountWriteMeter.Mark(int64(len(data)))
-	}
-	// Fill the storage hashes and sort them for the iterator
-	dl.storageList = make(map[common.Hash][]common.Hash)
-	for accountHash := range destructs {
-		dl.storageList[accountHash] = nil
-	}
-	// Determine memory size and track the dirty writes
-	for _, slots := range storage {
+		// Determine memory size and track the dirty writes
 		for _, data := range slots {
 			dl.memory += uint64(common.HashLength + len(data))
 			snapshotDirtyStorageWriteMeter.Mark(int64(len(data)))
 		}
 	}
-	dl.memory += uint64(len(dl.storageList) * common.HashLength)
+	dl.memory += uint64(len(destructs) * common.HashLength)
 	return dl
 }
 
@@ -287,6 +286,8 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) {
 
 // AccountRLP directly retrieves the account RLP associated with a particular
 // hash in the snapshot slim data format.
+//
+// Note the returned account is not a copy, please don't modify it.
 func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) {
 	// Check the bloom filter first whether there's even a point in reaching into
 	// all the maps in all the layers below
@@ -295,13 +296,17 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) {
 	if !hit {
 		hit = dl.diffed.Contains(destructBloomHasher(hash))
 	}
+	var origin *diskLayer
+	if !hit {
+		origin = dl.origin // extract origin while holding the lock
+	}
 	dl.lock.RUnlock()
 
 	// If the bloom filter misses, don't even bother with traversing the memory
 	// diff layers, reach straight into the bottom persistent disk layer
-	if !hit {
+	if origin != nil {
 		snapshotBloomAccountMissMeter.Mark(1)
-		return dl.origin.AccountRLP(hash)
+		return origin.AccountRLP(hash)
 	}
 	// The bloom filter hit, start poking in the internal maps
 	return dl.accountRLP(hash, 0)
@@ -347,6 +352,8 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) {
 // Storage directly retrieves the storage data associated with a particular hash,
 // within a particular account. If the slot is unknown to this diff, it's parent
 // is consulted.
+//
+// Note the returned slot is not a copy, please don't modify it.
 func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
 	// Check the bloom filter first whether there's even a point in reaching into
 	// all the maps in all the layers below
@@ -355,13 +362,17 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
 	if !hit {
 		hit = dl.diffed.Contains(destructBloomHasher(accountHash))
 	}
+	var origin *diskLayer
+	if !hit {
+		origin = dl.origin // extract origin while holding the lock
+	}
 	dl.lock.RUnlock()
 
 	// If the bloom filter misses, don't even bother with traversing the memory
 	// diff layers, reach straight into the bottom persistent disk layer
-	if !hit {
+	if origin != nil {
 		snapshotBloomStorageMissMeter.Mark(1)
-		return dl.origin.Storage(accountHash, storageHash)
+		return origin.Storage(accountHash, storageHash)
 	}
 	// The bloom filter hit, start poking in the internal maps
 	return dl.storage(accountHash, storageHash, 0)
@@ -418,7 +429,7 @@ func (dl *diffLayer) Update(blockRoot common.Hash, destructs map[common.Hash]str
 
 // flatten pushes all data from this point downwards, flattening everything into
 // a single diff at the bottom. Since usually the lowermost diff is the largest,
-// the flattening bulds up from there in reverse.
+// the flattening builds up from there in reverse.
 func (dl *diffLayer) flatten() snapshot {
 	// If the parent is not diff, we're the first in line, return unmodified
 	parent, ok := dl.parent.(*diffLayer)
@@ -475,7 +486,7 @@ func (dl *diffLayer) flatten() snapshot {
 	}
 }
 
-// AccountList returns a sorted list of all accounts in this difflayer, including
+// AccountList returns a sorted list of all accounts in this diffLayer, including
 // the deleted ones.
 //
 // Note, the returned slice is not a copy, so do not modify it.
@@ -502,22 +513,34 @@ func (dl *diffLayer) AccountList() []common.Hash {
 		}
 	}
 	sort.Sort(hashes(dl.accountList))
+	dl.memory += uint64(len(dl.accountList) * common.HashLength)
 	return dl.accountList
 }
 
-// StorageList returns a sorted list of all storage slot hashes in this difflayer
-// for the given account.
+// StorageList returns a sorted list of all storage slot hashes in this diffLayer
+// for the given account. If the whole storage is destructed in this layer, then
+// an additional flag *destructed = true* will be returned, otherwise the flag is
+// false. Besides, the returned list will include the hash of deleted storage slot.
+// Note a special case is an account is deleted in a prior tx but is recreated in
+// the following tx with some storage slots set. In this case the returned list is
+// not empty but the flag is true.
 //
 // Note, the returned slice is not a copy, so do not modify it.
-func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
-	// If an old list already exists, return it
+func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool) {
 	dl.lock.RLock()
-	list := dl.storageList[accountHash]
+	_, destructed := dl.destructSet[accountHash]
+	if _, ok := dl.storageData[accountHash]; !ok {
+		// Account not tracked by this layer
+		dl.lock.RUnlock()
+		return nil, destructed
+	}
+	// If an old list already exists, return it
+	if list, exist := dl.storageList[accountHash]; exist {
+		dl.lock.RUnlock()
+		return list, destructed // the cached list can't be nil
+	}
 	dl.lock.RUnlock()
 
-	if list != nil {
-		return list
-	}
 	// No old sorted account list exists, generate a new one
 	dl.lock.Lock()
 	defer dl.lock.Unlock()
@@ -529,5 +552,6 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
 	}
 	sort.Sort(hashes(storageList))
 	dl.storageList[accountHash] = storageList
-	return storageList
+	dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
+	return storageList, destructed
 }
diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go
index 329e0eb8e2..919af5fa86 100644
--- a/core/state/snapshot/difflayer_test.go
+++ b/core/state/snapshot/difflayer_test.go
@@ -109,7 +109,8 @@ func TestMergeBasics(t *testing.T) {
 			if have, want := len(merged.storageList), i; have != want {
 				t.Errorf("[1] storageList wrong: have %v, want %v", have, want)
 			}
-			if have, want := len(merged.StorageList(aHash)), len(sMap); have != want {
+			list, _ := merged.StorageList(aHash)
+			if have, want := len(list), len(sMap); have != want {
 				t.Errorf("[2] StorageList() wrong: have %v, want %v", have, want)
 			}
 			if have, want := len(merged.storageList[aHash]), len(sMap); have != want {
@@ -131,7 +132,7 @@ func TestMergeDelete(t *testing.T) {
 
 	flipDrops := func() map[common.Hash]struct{} {
 		return map[common.Hash]struct{}{
-			h2: struct{}{},
+			h2: {},
 		}
 	}
 	flipAccs := func() map[common.Hash][]byte {
@@ -141,7 +142,7 @@ func TestMergeDelete(t *testing.T) {
 	}
 	flopDrops := func() map[common.Hash]struct{} {
 		return map[common.Hash]struct{}{
-			h1: struct{}{},
+			h1: {},
 		}
 	}
 	flopAccs := func() map[common.Hash][]byte {
@@ -313,7 +314,7 @@ func BenchmarkSearchSlot(b *testing.B) {
 // With accountList and sorting
 // BenchmarkFlatten-6   	      50	  29890856 ns/op
 //
-// Without sorting and tracking accountlist
+// Without sorting and tracking accountList
 // BenchmarkFlatten-6   	     300	   5511511 ns/op
 func BenchmarkFlatten(b *testing.B) {
 	fill := func(parent snapshot) *diffLayer {
diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go
index e8f2bc853f..7cbf6e293d 100644
--- a/core/state/snapshot/disklayer.go
+++ b/core/state/snapshot/disklayer.go
@@ -31,7 +31,7 @@ import (
 // diskLayer is a low level persistent snapshot built on top of a key-value store.
 type diskLayer struct {
 	diskdb ethdb.KeyValueStore // Key-value store containing the base snapshot
-	triedb *trie.Database      // Trie node cache for reconstuction purposes
+	triedb *trie.Database      // Trie node cache for reconstruction purposes
 	cache  *fastcache.Cache    // Cache to avoid hitting the disk for direct access
 
 	root  common.Hash // Root hash of the base snapshot
diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go
index 15f3a6b1fa..ccde2fc094 100644
--- a/core/state/snapshot/disklayer_test.go
+++ b/core/state/snapshot/disklayer_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/ethdb/leveldb"
 	"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
@@ -121,10 +122,10 @@ func TestDiskMerge(t *testing.T) {
 
 	// Modify or delete some accounts, flatten everything onto disk
 	if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
-		accDelNoCache:  struct{}{},
-		accDelCache:    struct{}{},
-		conNukeNoCache: struct{}{},
-		conNukeCache:   struct{}{},
+		accDelNoCache:  {},
+		accDelCache:    {},
+		conNukeNoCache: {},
+		conNukeCache:   {},
 	}, map[common.Hash][]byte{
 		accModNoCache: reverse(accModNoCache[:]),
 		accModCache:   reverse(accModCache[:]),
@@ -344,10 +345,10 @@ func TestDiskPartialMerge(t *testing.T) {
 
 		// Modify or delete some accounts, flatten everything onto disk
 		if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
-			accDelNoCache:  struct{}{},
-			accDelCache:    struct{}{},
-			conNukeNoCache: struct{}{},
-			conNukeCache:   struct{}{},
+			accDelNoCache:  {},
+			accDelCache:    {},
+			conNukeNoCache: {},
+			conNukeCache:   {},
 		}, map[common.Hash][]byte{
 			accModNoCache: reverse(accModNoCache[:]),
 			accModCache:   reverse(accModCache[:]),
@@ -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 scenario 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
 // and invalidates any previously written and cached values, discarding anything
 // after the in-progress generation marker.
@@ -448,7 +524,7 @@ func TestDiskSeek(t *testing.T) {
 		t.Fatal(err)
 	} else {
 		defer os.RemoveAll(dir)
-		diskdb, err := leveldb.New(dir, 256, 0, "")
+		diskdb, err := leveldb.New(dir, 256, 0, "", false)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go
index 4b017fe69b..7e29e51b21 100644
--- a/core/state/snapshot/generate.go
+++ b/core/state/snapshot/generate.go
@@ -19,16 +19,21 @@ package snapshot
 import (
 	"bytes"
 	"encoding/binary"
+	"errors"
+	"fmt"
 	"math/big"
 	"time"
 
 	"github.com/VictoriaMetrics/fastcache"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/trie"
 )
@@ -39,24 +44,72 @@ var (
 
 	// emptyCode is the known hash of the empty EVM bytecode.
 	emptyCode = crypto.Keccak256Hash(nil)
+
+	// accountCheckRange is the upper limit of the number of accounts involved in
+	// each range check. This is a value estimated based on experience. If this
+	// value is too large, the failure rate of range prove will increase. Otherwise
+	// the the value is too small, the efficiency of the state recovery will decrease.
+	accountCheckRange = 128
+
+	// storageCheckRange is the upper limit of the number of storage slots involved
+	// in each range check. This is a value estimated based on experience. If this
+	// value is too large, the failure rate of range prove will increase. Otherwise
+	// the the value is too small, the efficiency of the state recovery will decrease.
+	storageCheckRange = 1024
+
+	// errMissingTrie is returned if the target trie is missing while the generation
+	// is running. In this case the generation is aborted and wait the new signal.
+	errMissingTrie = errors.New("missing trie")
+)
+
+// Metrics in generation
+var (
+	snapGeneratedAccountMeter     = metrics.NewRegisteredMeter("state/snapshot/generation/account/generated", nil)
+	snapRecoveredAccountMeter     = metrics.NewRegisteredMeter("state/snapshot/generation/account/recovered", nil)
+	snapWipedAccountMeter         = metrics.NewRegisteredMeter("state/snapshot/generation/account/wiped", nil)
+	snapMissallAccountMeter       = metrics.NewRegisteredMeter("state/snapshot/generation/account/missall", nil)
+	snapGeneratedStorageMeter     = metrics.NewRegisteredMeter("state/snapshot/generation/storage/generated", nil)
+	snapRecoveredStorageMeter     = metrics.NewRegisteredMeter("state/snapshot/generation/storage/recovered", nil)
+	snapWipedStorageMeter         = metrics.NewRegisteredMeter("state/snapshot/generation/storage/wiped", nil)
+	snapMissallStorageMeter       = metrics.NewRegisteredMeter("state/snapshot/generation/storage/missall", nil)
+	snapSuccessfulRangeProofMeter = metrics.NewRegisteredMeter("state/snapshot/generation/proof/success", nil)
+	snapFailedRangeProofMeter     = metrics.NewRegisteredMeter("state/snapshot/generation/proof/failure", nil)
+
+	// snapAccountProveCounter measures time spent on the account proving
+	snapAccountProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/prove", nil)
+	// snapAccountTrieReadCounter measures time spent on the account trie iteration
+	snapAccountTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/trieread", nil)
+	// snapAccountSnapReadCounter measues time spent on the snapshot account iteration
+	snapAccountSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/snapread", nil)
+	// snapAccountWriteCounter measures time spent on writing/updating/deleting accounts
+	snapAccountWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/account/write", nil)
+	// snapStorageProveCounter measures time spent on storage proving
+	snapStorageProveCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/prove", nil)
+	// snapStorageTrieReadCounter measures time spent on the storage trie iteration
+	snapStorageTrieReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/trieread", nil)
+	// snapStorageSnapReadCounter measures time spent on the snapshot storage iteration
+	snapStorageSnapReadCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/snapread", nil)
+	// snapStorageWriteCounter measures time spent on writing/updating/deleting storages
+	snapStorageWriteCounter = metrics.NewRegisteredCounter("state/snapshot/generation/duration/storage/write", nil)
 )
 
 // generatorStats is a collection of statistics gathered by the snapshot generator
-// for  logging purposes.
+// for logging purposes.
 type generatorStats struct {
-	wiping   chan struct{}      // Notification channel if wiping is in progress
 	origin   uint64             // Origin prefix where generation started
 	start    time.Time          // Timestamp when generation started
-	accounts uint64             // Number of accounts indexed
-	slots    uint64             // Number of storage slots indexed
-	storage  common.StorageSize // Account and storage slot size
+	accounts uint64             // Number of accounts indexed(generated or recovered)
+	slots    uint64             // Number of storage slots indexed(generated or recovered)
+	storage  common.StorageSize // Total account and storage slot size(generation or recovery)
 }
 
 // Log creates an contextual log with the given message and the context pulled
 // from the internally maintained statistics.
-func (gs *generatorStats) Log(msg string, marker []byte) {
+func (gs *generatorStats) Log(msg string, root common.Hash, marker []byte) {
 	var ctx []interface{}
-
+	if root != (common.Hash{}) {
+		ctx = append(ctx, []interface{}{"root", root}...)
+	}
 	// Figure out whether we're after or within an account
 	switch len(marker) {
 	case common.HashLength:
@@ -91,163 +144,598 @@ func (gs *generatorStats) Log(msg string, marker []byte) {
 // generateSnapshot regenerates a brand new snapshot based on an existing state
 // database and head block asynchronously. The snapshot is returned immediately
 // and generation is continued in the background until done.
-func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, wiper chan struct{}) *diskLayer {
-	// Wipe any previously existing snapshot from the database if no wiper is
-	// currently in progress.
-	if wiper == nil {
-		wiper = wipeSnapshot(diskdb, true)
-	}
+func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) *diskLayer {
 	// Create a new disk layer with an initialized state marker at zero
-	rawdb.WriteSnapshotRoot(diskdb, root)
-
+	var (
+		stats     = &generatorStats{start: time.Now()}
+		batch     = diskdb.NewBatch()
+		genMarker = []byte{} // Initialized but empty!
+	)
+	rawdb.WriteSnapshotRoot(batch, root)
+	journalProgress(batch, genMarker, stats)
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to write initialized state marker", "err", err)
+	}
 	base := &diskLayer{
 		diskdb:     diskdb,
 		triedb:     triedb,
 		root:       root,
 		cache:      fastcache.New(cache * 1024 * 1024),
-		genMarker:  []byte{}, // Initialized but empty!
+		genMarker:  genMarker,
 		genPending: make(chan struct{}),
 		genAbort:   make(chan chan *generatorStats),
 	}
-	go base.generate(&generatorStats{wiping: wiper, start: time.Now()})
+	go base.generate(stats)
+	log.Debug("Start snapshot generation", "root", root)
 	return base
 }
 
-// generate is a background thread that iterates over the state and storage tries,
-// constructing the state snapshot. All the arguments are purely for statistics
-// gethering and logging, since the method surfs the blocks as they arrive, often
-// being restarted.
-func (dl *diskLayer) generate(stats *generatorStats) {
-	// If a database wipe is in operation, wait until it's done
-	if stats.wiping != nil {
-		stats.Log("Wiper running, state snapshotting paused", dl.genMarker)
-		select {
-		// If wiper is done, resume normal mode of operation
-		case <-stats.wiping:
-			stats.wiping = nil
-			stats.start = time.Now()
+// journalProgress persists the generator stats into the database to resume later.
+func journalProgress(db ethdb.KeyValueWriter, marker []byte, stats *generatorStats) {
+	// Write out the generator marker. Note it's a standalone disk layer generator
+	// which is not mixed with journal. It's ok if the generator is persisted while
+	// journal is not.
+	entry := journalGenerator{
+		Done:   marker == nil,
+		Marker: marker,
+	}
+	if stats != nil {
+		entry.Accounts = stats.accounts
+		entry.Slots = stats.slots
+		entry.Storage = uint64(stats.storage)
+	}
+	blob, err := rlp.EncodeToBytes(entry)
+	if err != nil {
+		panic(err) // Cannot happen, here to catch dev errors
+	}
+	var logstr string
+	switch {
+	case marker == nil:
+		logstr = "done"
+	case bytes.Equal(marker, []byte{}):
+		logstr = "empty"
+	case len(marker) == common.HashLength:
+		logstr = fmt.Sprintf("%#x", marker)
+	default:
+		logstr = fmt.Sprintf("%#x:%#x", marker[:common.HashLength], marker[common.HashLength:])
+	}
+	log.Debug("Journalled generator progress", "progress", logstr)
+	rawdb.WriteSnapshotGenerator(db, blob)
+}
 
-		// If generator was aboted during wipe, return
-		case abort := <-dl.genAbort:
-			abort <- stats
-			return
+// proofResult contains the output of range proving which can be used
+// for further processing regardless if it is successful or not.
+type proofResult struct {
+	keys     [][]byte   // The key set of all elements being iterated, even proving is failed
+	vals     [][]byte   // The val set of all elements being iterated, even proving is failed
+	diskMore bool       // Set when the database has extra snapshot states since last iteration
+	trieMore bool       // Set when the trie has extra snapshot states(only meaningful for successful proving)
+	proofErr error      // Indicator whether the given state range is valid or not
+	tr       *trie.Trie // The trie, in case the trie was resolved by the prover (may be nil)
+}
+
+// valid returns the indicator that range proof is successful or not.
+func (result *proofResult) valid() bool {
+	return result.proofErr == nil
+}
+
+// last returns the last verified element key regardless of whether the range proof is
+// successful or not. Nil is returned if nothing involved in the proving.
+func (result *proofResult) last() []byte {
+	var last []byte
+	if len(result.keys) > 0 {
+		last = result.keys[len(result.keys)-1]
+	}
+	return last
+}
+
+// forEach iterates all the visited elements and applies the given callback on them.
+// The iteration is aborted if the callback returns non-nil error.
+func (result *proofResult) forEach(callback func(key []byte, val []byte) error) error {
+	for i := 0; i < len(result.keys); i++ {
+		key, val := result.keys[i], result.vals[i]
+		if err := callback(key, val); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// proveRange proves the snapshot segment with particular prefix is "valid".
+// The iteration start point will be assigned if the iterator is restored from
+// the last interruption. Max will be assigned in order to limit the maximum
+// amount of data involved in each iteration.
+//
+// The proof result will be returned if the range proving is finished, otherwise
+// the error will be returned to abort the entire procedure.
+func (dl *diskLayer) proveRange(stats *generatorStats, root common.Hash, prefix []byte, kind string, origin []byte, max int, valueConvertFn func([]byte) ([]byte, error)) (*proofResult, error) {
+	var (
+		keys     [][]byte
+		vals     [][]byte
+		proof    = rawdb.NewMemoryDatabase()
+		diskMore = false
+	)
+	iter := dl.diskdb.NewIterator(prefix, origin)
+	defer iter.Release()
+
+	var start = time.Now()
+	for iter.Next() {
+		key := iter.Key()
+		if len(key) != len(prefix)+common.HashLength {
+			continue
+		}
+		if len(keys) == max {
+			// Break if we've reached the max size, and signal that we're not
+			// done yet.
+			diskMore = true
+			break
 		}
+		keys = append(keys, common.CopyBytes(key[len(prefix):]))
+
+		if valueConvertFn == nil {
+			vals = append(vals, common.CopyBytes(iter.Value()))
+		} else {
+			val, err := valueConvertFn(iter.Value())
+			if err != nil {
+				// Special case, the state data is corrupted (invalid slim-format account),
+				// don't abort the entire procedure directly. Instead, let the fallback
+				// generation to heal the invalid data.
+				//
+				// Here append the original value to ensure that the number of key and
+				// value are the same.
+				vals = append(vals, common.CopyBytes(iter.Value()))
+				log.Error("Failed to convert account state data", "err", err)
+			} else {
+				vals = append(vals, val)
+			}
+		}
+	}
+	// Update metrics for database iteration and merkle proving
+	if kind == "storage" {
+		snapStorageSnapReadCounter.Inc(time.Since(start).Nanoseconds())
+	} else {
+		snapAccountSnapReadCounter.Inc(time.Since(start).Nanoseconds())
+	}
+	defer func(start time.Time) {
+		if kind == "storage" {
+			snapStorageProveCounter.Inc(time.Since(start).Nanoseconds())
+		} else {
+			snapAccountProveCounter.Inc(time.Since(start).Nanoseconds())
+		}
+	}(time.Now())
+
+	// The snap state is exhausted, pass the entire key/val set for verification
+	if origin == nil && !diskMore {
+		stackTr := trie.NewStackTrie(nil)
+		for i, key := range keys {
+			stackTr.TryUpdate(key, vals[i])
+		}
+		if gotRoot := stackTr.Hash(); gotRoot != root {
+			return &proofResult{
+				keys:     keys,
+				vals:     vals,
+				proofErr: fmt.Errorf("wrong root: have %#x want %#x", gotRoot, root),
+			}, nil
+		}
+		return &proofResult{keys: keys, vals: vals}, nil
 	}
-	// Create an account and state iterator pointing to the current generator marker
-	accTrie, err := trie.NewSecure(dl.root, dl.triedb)
+	// Snap state is chunked, generate edge proofs for verification.
+	tr, err := trie.New(root, dl.triedb)
 	if err != nil {
-		// The account trie is missing (GC), surf the chain until one becomes available
-		stats.Log("Trie missing, state snapshotting paused", dl.genMarker)
+		stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
+		return nil, errMissingTrie
+	}
+	// Firstly find out the key of last iterated element.
+	var last []byte
+	if len(keys) > 0 {
+		last = keys[len(keys)-1]
+	}
+	// Generate the Merkle proofs for the first and last element
+	if origin == nil {
+		origin = common.Hash{}.Bytes()
+	}
+	if err := tr.Prove(origin, 0, proof); err != nil {
+		log.Debug("Failed to prove range", "kind", kind, "origin", origin, "err", err)
+		return &proofResult{
+			keys:     keys,
+			vals:     vals,
+			diskMore: diskMore,
+			proofErr: err,
+			tr:       tr,
+		}, nil
+	}
+	if last != nil {
+		if err := tr.Prove(last, 0, proof); err != nil {
+			log.Debug("Failed to prove range", "kind", kind, "last", last, "err", err)
+			return &proofResult{
+				keys:     keys,
+				vals:     vals,
+				diskMore: diskMore,
+				proofErr: err,
+				tr:       tr,
+			}, nil
+		}
+	}
+	// Verify the snapshot segment with range prover, ensure that all flat states
+	// in this range correspond to merkle trie.
+	cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof)
+	return &proofResult{
+			keys:     keys,
+			vals:     vals,
+			diskMore: diskMore,
+			trieMore: cont,
+			proofErr: err,
+			tr:       tr},
+		nil
+}
 
-		abort := <-dl.genAbort
-		abort <- stats
-		return
+// onStateCallback is a function that is called by generateRange, when processing a range of
+// accounts or storage slots. For each element, the callback is invoked.
+// If 'delete' is true, then this element (and potential slots) needs to be deleted from the snapshot.
+// If 'write' is true, then this element needs to be updated with the 'val'.
+// If 'write' is false, then this element is already correct, and needs no update. However,
+// for accounts, the storage trie of the account needs to be checked.
+// The 'val' is the canonical encoding of the value (not the slim format for accounts)
+type onStateCallback func(key []byte, val []byte, write bool, delete bool) error
+
+// generateRange generates the state segment with particular prefix. Generation can
+// either verify the correctness of existing state through rangeproof and skip
+// generation, or iterate trie to regenerate state on demand.
+func (dl *diskLayer) generateRange(root common.Hash, prefix []byte, kind string, origin []byte, max int, stats *generatorStats, onState onStateCallback, valueConvertFn func([]byte) ([]byte, error)) (bool, []byte, error) {
+	// Use range prover to check the validity of the flat state in the range
+	result, err := dl.proveRange(stats, root, prefix, kind, origin, max, valueConvertFn)
+	if err != nil {
+		return false, nil, err
+	}
+	last := result.last()
+
+	// Construct contextual logger
+	logCtx := []interface{}{"kind", kind, "prefix", hexutil.Encode(prefix)}
+	if len(origin) > 0 {
+		logCtx = append(logCtx, "origin", hexutil.Encode(origin))
+	}
+	logger := log.New(logCtx...)
+
+	// The range prover says the range is correct, skip trie iteration
+	if result.valid() {
+		snapSuccessfulRangeProofMeter.Mark(1)
+		logger.Trace("Proved state range", "last", hexutil.Encode(last))
+
+		// The verification is passed, process each state with the given
+		// callback function. If this state represents a contract, the
+		// corresponding storage check will be performed in the callback
+		if err := result.forEach(func(key []byte, val []byte) error { return onState(key, val, false, false) }); err != nil {
+			return false, nil, err
+		}
+		// Only abort the iteration when both database and trie are exhausted
+		return !result.diskMore && !result.trieMore, last, nil
+	}
+	logger.Trace("Detected outdated state range", "last", hexutil.Encode(last), "err", result.proofErr)
+	snapFailedRangeProofMeter.Mark(1)
+
+	// Special case, the entire trie is missing. In the original trie scheme,
+	// all the duplicated subtries will be filter out(only one copy of data
+	// will be stored). While in the snapshot model, all the storage tries
+	// belong to different contracts will be kept even they are duplicated.
+	// Track it to a certain extent remove the noise data used for statistics.
+	if origin == nil && last == nil {
+		meter := snapMissallAccountMeter
+		if kind == "storage" {
+			meter = snapMissallStorageMeter
+		}
+		meter.Mark(1)
 	}
-	stats.Log("Resuming state snapshot generation", dl.genMarker)
 
-	var accMarker []byte
+	// We use the snap data to build up a cache which can be used by the
+	// main account trie as a primary lookup when resolving hashes
+	var snapNodeCache ethdb.KeyValueStore
+	if len(result.keys) > 0 {
+		snapNodeCache = memorydb.New()
+		snapTrieDb := trie.NewDatabase(snapNodeCache)
+		snapTrie, _ := trie.New(common.Hash{}, snapTrieDb)
+		for i, key := range result.keys {
+			snapTrie.Update(key, result.vals[i])
+		}
+		root, _ := snapTrie.Commit(nil)
+		snapTrieDb.Commit(root, false, nil)
+	}
+	tr := result.tr
+	if tr == nil {
+		tr, err = trie.New(root, dl.triedb)
+		if err != nil {
+			stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker)
+			return false, nil, errMissingTrie
+		}
+	}
+
+	var (
+		trieMore       bool
+		nodeIt         = tr.NodeIterator(origin)
+		iter           = trie.NewIterator(nodeIt)
+		kvkeys, kvvals = result.keys, result.vals
+
+		// counters
+		count     = 0 // number of states delivered by iterator
+		created   = 0 // states created from the trie
+		updated   = 0 // states updated from the trie
+		deleted   = 0 // states not in trie, but were in snapshot
+		untouched = 0 // states already correct
+
+		// timers
+		start    = time.Now()
+		internal time.Duration
+	)
+	nodeIt.AddResolver(snapNodeCache)
+	for iter.Next() {
+		if last != nil && bytes.Compare(iter.Key, last) > 0 {
+			trieMore = true
+			break
+		}
+		count++
+		write := true
+		created++
+		for len(kvkeys) > 0 {
+			if cmp := bytes.Compare(kvkeys[0], iter.Key); cmp < 0 {
+				// delete the key
+				istart := time.Now()
+				if err := onState(kvkeys[0], nil, false, true); err != nil {
+					return false, nil, err
+				}
+				kvkeys = kvkeys[1:]
+				kvvals = kvvals[1:]
+				deleted++
+				internal += time.Since(istart)
+				continue
+			} else if cmp == 0 {
+				// the snapshot key can be overwritten
+				created--
+				if write = !bytes.Equal(kvvals[0], iter.Value); write {
+					updated++
+				} else {
+					untouched++
+				}
+				kvkeys = kvkeys[1:]
+				kvvals = kvvals[1:]
+			}
+			break
+		}
+		istart := time.Now()
+		if err := onState(iter.Key, iter.Value, write, false); err != nil {
+			return false, nil, err
+		}
+		internal += time.Since(istart)
+	}
+	if iter.Err != nil {
+		return false, nil, iter.Err
+	}
+	// Delete all stale snapshot states remaining
+	istart := time.Now()
+	for _, key := range kvkeys {
+		if err := onState(key, nil, false, true); err != nil {
+			return false, nil, err
+		}
+		deleted += 1
+	}
+	internal += time.Since(istart)
+
+	// Update metrics for counting trie iteration
+	if kind == "storage" {
+		snapStorageTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
+	} else {
+		snapAccountTrieReadCounter.Inc((time.Since(start) - internal).Nanoseconds())
+	}
+	logger.Debug("Regenerated state range", "root", root, "last", hexutil.Encode(last),
+		"count", count, "created", created, "updated", updated, "untouched", untouched, "deleted", deleted)
+
+	// If there are either more trie items, or there are more snap items
+	// (in the next segment), then we need to keep working
+	return !trieMore && !result.diskMore, last, nil
+}
+
+// generate is a background thread that iterates over the state and storage tries,
+// constructing the state snapshot. All the arguments are purely for statistics
+// gathering and logging, since the method surfs the blocks as they arrive, often
+// being restarted.
+func (dl *diskLayer) generate(stats *generatorStats) {
+	var (
+		accMarker    []byte
+		accountRange = accountCheckRange
+	)
 	if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that
-		accMarker = dl.genMarker[:common.HashLength]
+		// Always reset the initial account range as 1
+		// whenever recover from the interruption.
+		accMarker, accountRange = dl.genMarker[:common.HashLength], 1
 	}
-	accIt := trie.NewIterator(accTrie.NodeIterator(accMarker))
-	batch := dl.diskdb.NewBatch()
+	var (
+		batch     = dl.diskdb.NewBatch()
+		logged    = time.Now()
+		accOrigin = common.CopyBytes(accMarker)
+		abort     chan *generatorStats
+	)
+	stats.Log("Resuming state snapshot generation", dl.root, dl.genMarker)
 
-	// Iterate from the previous marker and continue generating the state snapshot
-	logged := time.Now()
-	for accIt.Next() {
-		// Retrieve the current account and flatten it into the internal format
-		accountHash := common.BytesToHash(accIt.Key)
+	checkAndFlush := func(currentLocation []byte) error {
+		select {
+		case abort = <-dl.genAbort:
+		default:
+		}
+		if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
+			// Flush out the batch anyway no matter it's empty or not.
+			// It's possible that all the states are recovered and the
+			// generation indeed makes progress.
+			journalProgress(batch, currentLocation, stats)
+
+			if err := batch.Write(); err != nil {
+				return err
+			}
+			batch.Reset()
 
+			dl.lock.Lock()
+			dl.genMarker = currentLocation
+			dl.lock.Unlock()
+
+			if abort != nil {
+				stats.Log("Aborting state snapshot generation", dl.root, currentLocation)
+				return errors.New("aborted")
+			}
+		}
+		if time.Since(logged) > 8*time.Second {
+			stats.Log("Generating state snapshot", dl.root, currentLocation)
+			logged = time.Now()
+		}
+		return nil
+	}
+
+	onAccount := func(key []byte, val []byte, write bool, delete bool) error {
+		var (
+			start       = time.Now()
+			accountHash = common.BytesToHash(key)
+		)
+		if delete {
+			rawdb.DeleteAccountSnapshot(batch, accountHash)
+			snapWipedAccountMeter.Mark(1)
+
+			// Ensure that any previous snapshot storage values are cleared
+			prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...)
+			keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength
+			if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil {
+				return err
+			}
+			snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
+			return nil
+		}
+		// Retrieve the current account and flatten it into the internal format
 		var acc struct {
 			Nonce    uint64
 			Balance  *big.Int
 			Root     common.Hash
 			CodeHash []byte
 		}
-		if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
+		if err := rlp.DecodeBytes(val, &acc); err != nil {
 			log.Crit("Invalid account encountered during snapshot creation", "err", err)
 		}
-		data := AccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
-
 		// If the account is not yet in-progress, write it out
 		if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) {
-			rawdb.WriteAccountSnapshot(batch, accountHash, data)
-			stats.storage += common.StorageSize(1 + common.HashLength + len(data))
+			dataLen := len(val) // Approximate size, saves us a round of RLP-encoding
+			if !write {
+				if bytes.Equal(acc.CodeHash, emptyCode[:]) {
+					dataLen -= 32
+				}
+				if acc.Root == emptyRoot {
+					dataLen -= 32
+				}
+				snapRecoveredAccountMeter.Mark(1)
+			} else {
+				data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash)
+				dataLen = len(data)
+				rawdb.WriteAccountSnapshot(batch, accountHash, data)
+				snapGeneratedAccountMeter.Mark(1)
+			}
+			stats.storage += common.StorageSize(1 + common.HashLength + dataLen)
 			stats.accounts++
 		}
 		// If we've exceeded our batch allowance or termination was requested, flush to disk
-		var abort chan *generatorStats
-		select {
-		case abort = <-dl.genAbort:
-		default:
+		if err := checkAndFlush(accountHash[:]); err != nil {
+			return err
 		}
-		if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
-			// Only write and set the marker if we actually did something useful
-			if batch.ValueSize() > 0 {
-				batch.Write()
-				batch.Reset()
-
-				dl.lock.Lock()
-				dl.genMarker = accountHash[:]
-				dl.lock.Unlock()
-			}
-			if abort != nil {
-				stats.Log("Aborting state snapshot generation", accountHash[:])
-				abort <- stats
-				return
-			}
-		}
-		// If the account is in-progress, continue where we left off (otherwise iterate all)
-		if acc.Root != emptyRoot {
-			storeTrie, err := trie.NewSecure(acc.Root, dl.triedb)
-			if err != nil {
-				log.Crit("Storage trie inaccessible for snapshot generation", "err", err)
+		// If the iterated account is the contract, create a further loop to
+		// verify or regenerate the contract storage.
+		if acc.Root == emptyRoot {
+			// If the root is empty, we still need to ensure that any previous snapshot
+			// storage values are cleared
+			// TODO: investigate if this can be avoided, this will be very costly since it
+			// affects every single EOA account
+			//  - Perhaps we can avoid if where codeHash is emptyCode
+			prefix := append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...)
+			keyLen := len(rawdb.SnapshotStoragePrefix) + 2*common.HashLength
+			if err := wipeKeyRange(dl.diskdb, "storage", prefix, nil, nil, keyLen, snapWipedStorageMeter, false); err != nil {
+				return err
 			}
+			snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
+		} else {
+			snapAccountWriteCounter.Inc(time.Since(start).Nanoseconds())
+
 			var storeMarker []byte
 			if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength {
 				storeMarker = dl.genMarker[common.HashLength:]
 			}
-			storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker))
-			for storeIt.Next() {
-				rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value)
-				stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value))
+			onStorage := func(key []byte, val []byte, write bool, delete bool) error {
+				defer func(start time.Time) {
+					snapStorageWriteCounter.Inc(time.Since(start).Nanoseconds())
+				}(time.Now())
+
+				if delete {
+					rawdb.DeleteStorageSnapshot(batch, accountHash, common.BytesToHash(key))
+					snapWipedStorageMeter.Mark(1)
+					return nil
+				}
+				if write {
+					rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(key), val)
+					snapGeneratedStorageMeter.Mark(1)
+				} else {
+					snapRecoveredStorageMeter.Mark(1)
+				}
+				stats.storage += common.StorageSize(1 + 2*common.HashLength + len(val))
 				stats.slots++
 
 				// If we've exceeded our batch allowance or termination was requested, flush to disk
-				var abort chan *generatorStats
-				select {
-				case abort = <-dl.genAbort:
-				default:
+				if err := checkAndFlush(append(accountHash[:], key...)); err != nil {
+					return err
+				}
+				return nil
+			}
+			var storeOrigin = common.CopyBytes(storeMarker)
+			for {
+				exhausted, last, err := dl.generateRange(acc.Root, append(rawdb.SnapshotStoragePrefix, accountHash.Bytes()...), "storage", storeOrigin, storageCheckRange, stats, onStorage, nil)
+				if err != nil {
+					return err
+				}
+				if exhausted {
+					break
 				}
-				if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil {
-					// Only write and set the marker if we actually did something useful
-					if batch.ValueSize() > 0 {
-						batch.Write()
-						batch.Reset()
-
-						dl.lock.Lock()
-						dl.genMarker = append(accountHash[:], storeIt.Key...)
-						dl.lock.Unlock()
-					}
-					if abort != nil {
-						stats.Log("Aborting state snapshot generation", append(accountHash[:], storeIt.Key...))
-						abort <- stats
-						return
-					}
+				if storeOrigin = increaseKey(last); storeOrigin == nil {
+					break // special case, the last is 0xffffffff...fff
 				}
 			}
 		}
-		if time.Since(logged) > 8*time.Second {
-			stats.Log("Generating state snapshot", accIt.Key)
-			logged = time.Now()
-		}
 		// Some account processed, unmark the marker
 		accMarker = nil
+		return nil
 	}
-	// Snapshot fully generated, set the marker to nil
-	if batch.ValueSize() > 0 {
-		batch.Write()
+
+	// Global loop for regerating the entire state trie + all layered storage tries.
+	for {
+		exhausted, last, err := dl.generateRange(dl.root, rawdb.SnapshotAccountPrefix, "account", accOrigin, accountRange, stats, onAccount, FullAccountRLP)
+		// The procedure it aborted, either by external signal or internal error
+		if err != nil {
+			if abort == nil { // aborted by internal error, wait the signal
+				abort = <-dl.genAbort
+			}
+			abort <- stats
+			return
+		}
+		// Abort the procedure if the entire snapshot is generated
+		if exhausted {
+			break
+		}
+		if accOrigin = increaseKey(last); accOrigin == nil {
+			break // special case, the last is 0xffffffff...fff
+		}
+		accountRange = accountCheckRange
 	}
+	// Snapshot fully generated, set the marker to nil.
+	// Note even there is nothing to commit, persist the
+	// generator anyway to mark the snapshot is complete.
+	journalProgress(batch, nil, stats)
+	if err := batch.Write(); err != nil {
+		log.Error("Failed to flush batch", "err", err)
+
+		abort = <-dl.genAbort
+		abort <- stats
+		return
+	}
+	batch.Reset()
+
 	log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots,
 		"storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start)))
 
@@ -257,6 +745,18 @@ func (dl *diskLayer) generate(stats *generatorStats) {
 	dl.lock.Unlock()
 
 	// Someone will be looking for us, wait it out
-	abort := <-dl.genAbort
+	abort = <-dl.genAbort
 	abort <- nil
 }
+
+// increaseKey increase the input key by one bit. Return nil if the entire
+// addition operation overflows,
+func increaseKey(key []byte) []byte {
+	for i := len(key) - 1; i >= 0; i-- {
+		key[i]++
+		if key[i] != 0x0 {
+			return key
+		}
+	}
+	return nil
+}
diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go
new file mode 100644
index 0000000000..3a669085f7
--- /dev/null
+++ b/core/state/snapshot/generate_test.go
@@ -0,0 +1,833 @@
+// 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 .
+
+package snapshot
+
+import (
+	"fmt"
+	"math/big"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+	"golang.org/x/crypto/sha3"
+)
+
+// Tests that snapshot generation from an empty database.
+func TestGeneration(t *testing.T) {
+	// We can't use statedb to make a test trie (circular dependency), so make
+	// a fake one manually. We're going with a small account trie of 3 accounts,
+	// two of which also has the same 3-slot storage trie attached.
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
+	stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
+	stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
+	stTrie.Commit(nil)                              // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
+
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ := rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
+
+	acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
+
+	acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
+	root, _ := accTrie.Commit(nil)       // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
+	triedb.Commit(root, false, nil)
+
+	if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want {
+		t.Fatalf("have %#x want %#x", have, want)
+	}
+	snap := generateSnapshot(diskdb, triedb, 16, root)
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+func hashData(input []byte) common.Hash {
+	var hasher = sha3.NewLegacyKeccak256()
+	var hash common.Hash
+	hasher.Reset()
+	hasher.Write(input)
+	hasher.Sum(hash[:0])
+	return hash
+}
+
+// Tests that snapshot generation with existent flat state.
+func TestGenerateExistentState(t *testing.T) {
+	// We can't use statedb to make a test trie (circular dependency), so make
+	// a fake one manually. We're going with a small account trie of 3 accounts,
+	// two of which also has the same 3-slot storage trie attached.
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
+	stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
+	stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
+	stTrie.Commit(nil)                              // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
+
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ := rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
+	rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-1")), val)
+	rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-1")), []byte("val-1"))
+	rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-2")), []byte("val-2"))
+	rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-1")), hashData([]byte("key-3")), []byte("val-3"))
+
+	acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
+	diskdb.Put(hashData([]byte("acc-2")).Bytes(), val)
+	rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-2")), val)
+
+	acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
+	rawdb.WriteAccountSnapshot(diskdb, hashData([]byte("acc-3")), val)
+	rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-1")), []byte("val-1"))
+	rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-2")), []byte("val-2"))
+	rawdb.WriteStorageSnapshot(diskdb, hashData([]byte("acc-3")), hashData([]byte("key-3")), []byte("val-3"))
+
+	root, _ := accTrie.Commit(nil) // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
+	triedb.Commit(root, false, nil)
+
+	snap := generateSnapshot(diskdb, triedb, 16, root)
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+func checkSnapRoot(t *testing.T, snap *diskLayer, trieRoot common.Hash) {
+	t.Helper()
+	accIt := snap.AccountIterator(common.Hash{})
+	defer accIt.Release()
+	snapRoot, err := generateTrieRoot(nil, accIt, common.Hash{}, stackTrieGenerate,
+		func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
+			storageIt, _ := snap.StorageIterator(accountHash, common.Hash{})
+			defer storageIt.Release()
+
+			hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
+			if err != nil {
+				return common.Hash{}, err
+			}
+			return hash, nil
+		}, newGenerateStats(), true)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	if snapRoot != trieRoot {
+		t.Fatalf("snaproot: %#x != trieroot #%x", snapRoot, trieRoot)
+	}
+}
+
+type testHelper struct {
+	diskdb  *memorydb.Database
+	triedb  *trie.Database
+	accTrie *trie.SecureTrie
+}
+
+func newHelper() *testHelper {
+	diskdb := memorydb.New()
+	triedb := trie.NewDatabase(diskdb)
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	return &testHelper{
+		diskdb:  diskdb,
+		triedb:  triedb,
+		accTrie: accTrie,
+	}
+}
+
+func (t *testHelper) addTrieAccount(acckey string, acc *Account) {
+	val, _ := rlp.EncodeToBytes(acc)
+	t.accTrie.Update([]byte(acckey), val)
+}
+
+func (t *testHelper) addSnapAccount(acckey string, acc *Account) {
+	val, _ := rlp.EncodeToBytes(acc)
+	key := hashData([]byte(acckey))
+	rawdb.WriteAccountSnapshot(t.diskdb, key, val)
+}
+
+func (t *testHelper) addAccount(acckey string, acc *Account) {
+	t.addTrieAccount(acckey, acc)
+	t.addSnapAccount(acckey, acc)
+}
+
+func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) {
+	accHash := hashData([]byte(accKey))
+	for i, key := range keys {
+		rawdb.WriteStorageSnapshot(t.diskdb, accHash, hashData([]byte(key)), []byte(vals[i]))
+	}
+}
+
+func (t *testHelper) makeStorageTrie(keys []string, vals []string) []byte {
+	stTrie, _ := trie.NewSecure(common.Hash{}, t.triedb)
+	for i, k := range keys {
+		stTrie.Update([]byte(k), []byte(vals[i]))
+	}
+	root, _ := stTrie.Commit(nil)
+	return root.Bytes()
+}
+
+func (t *testHelper) Generate() (common.Hash, *diskLayer) {
+	root, _ := t.accTrie.Commit(nil)
+	t.triedb.Commit(root, false, nil)
+	snap := generateSnapshot(t.diskdb, t.triedb, 16, root)
+	return root, snap
+}
+
+// Tests that snapshot generation with existent flat state, where the flat state
+// contains some errors:
+// - the contract with empty storage root but has storage entries in the disk
+// - the contract with non empty storage root but empty storage slots
+// - the contract(non-empty storage) misses some storage slots
+//   - miss in the beginning
+//   - miss in the middle
+//   - miss in the end
+// - the contract(non-empty storage) has wrong storage slots
+//   - wrong slots in the beginning
+//   - wrong slots in the middle
+//   - wrong slots in the end
+// - the contract(non-empty storage) has extra storage slots
+//   - extra slots in the beginning
+//   - extra slots in the middle
+//   - extra slots in the end
+func TestGenerateExistentStateWithWrongStorage(t *testing.T) {
+	helper := newHelper()
+	stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
+
+	// Account one, empty root but non-empty database
+	helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
+	helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
+
+	// Account two, non empty root but empty database
+	helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+
+	// Miss slots
+	{
+		// Account three, non empty root but misses slots in the beginning
+		helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"})
+
+		// Account four, non empty root but misses slots in the middle
+		helper.addAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"})
+
+		// Account five, non empty root but misses slots in the end
+		helper.addAccount("acc-5", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"})
+	}
+
+	// Wrong storage slots
+	{
+		// Account six, non empty root but wrong slots in the beginning
+		helper.addAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"})
+
+		// Account seven, non empty root but wrong slots in the middle
+		helper.addAccount("acc-7", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"})
+
+		// Account eight, non empty root but wrong slots in the end
+		helper.addAccount("acc-8", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"})
+
+		// Account 9, non empty root but rotated slots
+		helper.addAccount("acc-9", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"})
+	}
+
+	// Extra storage slots
+	{
+		// Account 10, non empty root but extra slots in the beginning
+		helper.addAccount("acc-10", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"})
+
+		// Account 11, non empty root but extra slots in the middle
+		helper.addAccount("acc-11", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"})
+
+		// Account 12, non empty root but extra slots in the end
+		helper.addAccount("acc-12", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"})
+	}
+
+	root, snap := helper.Generate()
+	t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0
+
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// Tests that snapshot generation with existent flat state, where the flat state
+// contains some errors:
+// - miss accounts
+// - wrong accounts
+// - extra accounts
+func TestGenerateExistentStateWithWrongAccounts(t *testing.T) {
+	helper := newHelper()
+	stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
+
+	// Trie accounts [acc-1, acc-2, acc-3, acc-4, acc-6]
+	// Extra accounts [acc-0, acc-5, acc-7]
+
+	// Missing accounts, only in the trie
+	{
+		helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // Beginning
+		helper.addTrieAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // Middle
+		helper.addTrieAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()}) // End
+	}
+
+	// Wrong accounts
+	{
+		helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")})
+
+		helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()})
+	}
+
+	// Extra accounts, only in the snap
+	{
+		helper.addSnapAccount("acc-0", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyRoot.Bytes()})                     // before the beginning
+		helper.addSnapAccount("acc-5", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: common.Hex2Bytes("0x1234")}) // Middle
+		helper.addSnapAccount("acc-7", &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyRoot.Bytes()})          // after the end
+	}
+
+	root, snap := helper.Generate()
+	t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8
+
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// Tests that snapshot generation errors out correctly in case of a missing trie
+// node in the account trie.
+func TestGenerateCorruptAccountTrie(t *testing.T) {
+	// We can't use statedb to make a test trie (circular dependency), so make
+	// a fake one manually. We're going with a small account trie of 3 accounts,
+	// without any storage slots to keep the test smaller.
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	tr, _ := trie.NewSecure(common.Hash{}, triedb)
+	acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ := rlp.EncodeToBytes(acc)
+	tr.Update([]byte("acc-1"), val) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074
+
+	acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	tr.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
+
+	acc = &Account{Balance: big.NewInt(3), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	tr.Update([]byte("acc-3"), val) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4
+	tr.Commit(nil)                  // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978
+
+	// Delete an account trie leaf and ensure the generator chokes
+	triedb.Commit(common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"), false, nil)
+	diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes())
+
+	snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978"))
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+		t.Errorf("Snapshot generated against corrupt account trie")
+
+	case <-time.After(250 * time.Millisecond):
+		// Not generated fast enough, hopefully blocked inside on missing trie node fail
+	}
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// Tests that snapshot generation errors out correctly in case of a missing root
+// trie node for a storage trie. It's similar to internal corruption but it is
+// handled differently inside the generator.
+func TestGenerateMissingStorageTrie(t *testing.T) {
+	// We can't use statedb to make a test trie (circular dependency), so make
+	// a fake one manually. We're going with a small account trie of 3 accounts,
+	// two of which also has the same 3-slot storage trie attached.
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
+	stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
+	stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
+	stTrie.Commit(nil)                              // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
+
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ := rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
+
+	acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
+
+	acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
+	accTrie.Commit(nil)                  // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
+
+	// We can only corrupt the disk database, so flush the tries out
+	triedb.Reference(
+		common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
+		common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
+	)
+	triedb.Reference(
+		common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
+		common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
+	)
+	triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil)
+
+	// Delete a storage trie root and ensure the generator chokes
+	diskdb.Delete(common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67").Bytes())
+
+	snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"))
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+		t.Errorf("Snapshot generated against corrupt storage trie")
+
+	case <-time.After(250 * time.Millisecond):
+		// Not generated fast enough, hopefully blocked inside on missing trie node fail
+	}
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// Tests that snapshot generation errors out correctly in case of a missing trie
+// node in a storage trie.
+func TestGenerateCorruptStorageTrie(t *testing.T) {
+	// We can't use statedb to make a test trie (circular dependency), so make
+	// a fake one manually. We're going with a small account trie of 3 accounts,
+	// two of which also has the same 3-slot storage trie attached.
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	stTrie.Update([]byte("key-1"), []byte("val-1")) // 0x1314700b81afc49f94db3623ef1df38f3ed18b73a1b7ea2f6c095118cf6118a0
+	stTrie.Update([]byte("key-2"), []byte("val-2")) // 0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371
+	stTrie.Update([]byte("key-3"), []byte("val-3")) // 0x51c71a47af0695957647fb68766d0becee77e953df17c29b3c2f25436f055c78
+	stTrie.Commit(nil)                              // Root: 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67
+
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ := rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
+
+	acc = &Account{Balance: big.NewInt(2), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-2"), val) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7
+
+	acc = &Account{Balance: big.NewInt(3), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+	val, _ = rlp.EncodeToBytes(acc)
+	accTrie.Update([]byte("acc-3"), val) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2
+	accTrie.Commit(nil)                  // Root: 0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd
+
+	// We can only corrupt the disk database, so flush the tries out
+	triedb.Reference(
+		common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
+		common.HexToHash("0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e"),
+	)
+	triedb.Reference(
+		common.HexToHash("0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67"),
+		common.HexToHash("0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2"),
+	)
+	triedb.Commit(common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"), false, nil)
+
+	// Delete a storage trie leaf and ensure the generator chokes
+	diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes())
+
+	snap := generateSnapshot(diskdb, triedb, 16, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"))
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+		t.Errorf("Snapshot generated against corrupt storage trie")
+
+	case <-time.After(250 * time.Millisecond):
+		// Not generated fast enough, hopefully blocked inside on missing trie node fail
+	}
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+func getStorageTrie(n int, triedb *trie.Database) *trie.SecureTrie {
+	stTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	for i := 0; i < n; i++ {
+		k := fmt.Sprintf("key-%d", i)
+		v := fmt.Sprintf("val-%d", i)
+		stTrie.Update([]byte(k), []byte(v))
+	}
+	stTrie.Commit(nil)
+	return stTrie
+}
+
+// Tests that snapshot generation when an extra account with storage exists in the snap state.
+func TestGenerateWithExtraAccounts(t *testing.T) {
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+		stTrie = getStorageTrie(5, triedb)
+	)
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	{ // Account one in the trie
+		acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+		val, _ := rlp.EncodeToBytes(acc)
+		accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
+		// Identical in the snap
+		key := hashData([]byte("acc-1"))
+		rawdb.WriteAccountSnapshot(diskdb, key, val)
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-4")), []byte("val-4"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-5")), []byte("val-5"))
+	}
+	{ // Account two exists only in the snapshot
+		acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+		val, _ := rlp.EncodeToBytes(acc)
+		key := hashData([]byte("acc-2"))
+		rawdb.WriteAccountSnapshot(diskdb, key, val)
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-1")), []byte("b-val-1"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-2")), []byte("b-val-2"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("b-key-3")), []byte("b-val-3"))
+	}
+	root, _ := accTrie.Commit(nil)
+	t.Logf("root: %x", root)
+	triedb.Commit(root, false, nil)
+	// To verify the test: If we now inspect the snap db, there should exist extraneous storage items
+	if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data == nil {
+		t.Fatalf("expected snap storage to exist")
+	}
+
+	snap := generateSnapshot(diskdb, triedb, 16, root)
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+	// If we now inspect the snap db, there should exist no extraneous storage items
+	if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
+		t.Fatalf("expected slot to be removed, got %v", string(data))
+	}
+}
+
+func enableLogging() {
+	log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+}
+
+// Tests that snapshot generation when an extra account with storage exists in the snap state.
+func TestGenerateWithManyExtraAccounts(t *testing.T) {
+	if false {
+		enableLogging()
+	}
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+		stTrie = getStorageTrie(3, triedb)
+	)
+	accTrie, _ := trie.NewSecure(common.Hash{}, triedb)
+	{ // Account one in the trie
+		acc := &Account{Balance: big.NewInt(1), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+		val, _ := rlp.EncodeToBytes(acc)
+		accTrie.Update([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e
+		// Identical in the snap
+		key := hashData([]byte("acc-1"))
+		rawdb.WriteAccountSnapshot(diskdb, key, val)
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-1")), []byte("val-1"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-2")), []byte("val-2"))
+		rawdb.WriteStorageSnapshot(diskdb, key, hashData([]byte("key-3")), []byte("val-3"))
+	}
+	{ // 100 accounts exist only in snapshot
+		for i := 0; i < 1000; i++ {
+			//acc := &Account{Balance: big.NewInt(int64(i)), Root: stTrie.Hash().Bytes(), CodeHash: emptyCode.Bytes()}
+			acc := &Account{Balance: big.NewInt(int64(i)), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+			val, _ := rlp.EncodeToBytes(acc)
+			key := hashData([]byte(fmt.Sprintf("acc-%d", i)))
+			rawdb.WriteAccountSnapshot(diskdb, key, val)
+		}
+	}
+	root, _ := accTrie.Commit(nil)
+	t.Logf("root: %x", root)
+	triedb.Commit(root, false, nil)
+
+	snap := generateSnapshot(diskdb, triedb, 16, root)
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// Tests this case
+// maxAccountRange 3
+// snapshot-accounts: 01, 02, 03, 04, 05, 06, 07
+// trie-accounts:             03,             07
+//
+// We iterate three snapshot storage slots (max = 3) from the database. They are 0x01, 0x02, 0x03.
+// The trie has a lot of deletions.
+// So in trie, we iterate 2 entries 0x03, 0x07. We create the 0x07 in the database and abort the procedure, because the trie is exhausted.
+// But in the database, we still have the stale storage slots 0x04, 0x05. They are not iterated yet, but the procedure is finished.
+func TestGenerateWithExtraBeforeAndAfter(t *testing.T) {
+	accountCheckRange = 3
+	if false {
+		enableLogging()
+	}
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	accTrie, _ := trie.New(common.Hash{}, triedb)
+	{
+		acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+		val, _ := rlp.EncodeToBytes(acc)
+		accTrie.Update(common.HexToHash("0x03").Bytes(), val)
+		accTrie.Update(common.HexToHash("0x07").Bytes(), val)
+
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x01"), val)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), val)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), val)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), val)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), val)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x06"), val)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x07"), val)
+	}
+
+	root, _ := accTrie.Commit(nil)
+	t.Logf("root: %x", root)
+	triedb.Commit(root, false, nil)
+
+	snap := generateSnapshot(diskdb, triedb, 16, root)
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// TestGenerateWithMalformedSnapdata tests what happes if we have some junk
+// in the snapshot database, which cannot be parsed back to an account
+func TestGenerateWithMalformedSnapdata(t *testing.T) {
+	accountCheckRange = 3
+	if false {
+		enableLogging()
+	}
+	var (
+		diskdb = memorydb.New()
+		triedb = trie.NewDatabase(diskdb)
+	)
+	accTrie, _ := trie.New(common.Hash{}, triedb)
+	{
+		acc := &Account{Balance: big.NewInt(1), Root: emptyRoot.Bytes(), CodeHash: emptyCode.Bytes()}
+		val, _ := rlp.EncodeToBytes(acc)
+		accTrie.Update(common.HexToHash("0x03").Bytes(), val)
+
+		junk := make([]byte, 100)
+		copy(junk, []byte{0xde, 0xad})
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x02"), junk)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x03"), junk)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x04"), junk)
+		rawdb.WriteAccountSnapshot(diskdb, common.HexToHash("0x05"), junk)
+	}
+
+	root, _ := accTrie.Commit(nil)
+	t.Logf("root: %x", root)
+	triedb.Commit(root, false, nil)
+
+	snap := generateSnapshot(diskdb, triedb, 16, root)
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+	// If we now inspect the snap db, there should exist no extraneous storage items
+	if data := rawdb.ReadStorageSnapshot(diskdb, hashData([]byte("acc-2")), hashData([]byte("b-key-1"))); data != nil {
+		t.Fatalf("expected slot to be removed, got %v", string(data))
+	}
+}
+
+func TestGenerateFromEmptySnap(t *testing.T) {
+	//enableLogging()
+	accountCheckRange = 10
+	storageCheckRange = 20
+	helper := newHelper()
+	stRoot := helper.makeStorageTrie([]string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"})
+	// Add 1K accounts to the trie
+	for i := 0; i < 400; i++ {
+		helper.addTrieAccount(fmt.Sprintf("acc-%d", i),
+			&Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: emptyCode.Bytes()})
+	}
+	root, snap := helper.Generate()
+	t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4
+
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(1 * time.Second):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
+
+// Tests that snapshot generation with existent flat state, where the flat state
+// storage is correct, but incomplete.
+// The incomplete part is on the second range
+// snap: [ 0x01, 0x02, 0x03, 0x04] , [ 0x05, 0x06, 0x07, {missing}] (with storageCheck = 4)
+// trie:  0x01, 0x02, 0x03, 0x04,  0x05, 0x06, 0x07, 0x08
+// This hits a case where the snap verification passes, but there are more elements in the trie
+// which we must also add.
+func TestGenerateWithIncompleteStorage(t *testing.T) {
+	storageCheckRange = 4
+	helper := newHelper()
+	stKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8"}
+	stVals := []string{"v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"}
+	stRoot := helper.makeStorageTrie(stKeys, stVals)
+	// We add 8 accounts, each one is missing exactly one of the storage slots. This means
+	// we don't have to order the keys and figure out exactly which hash-key winds up
+	// on the sensitive spots at the boundaries
+	for i := 0; i < 8; i++ {
+		accKey := fmt.Sprintf("acc-%d", i)
+		helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: emptyCode.Bytes()})
+		var moddedKeys []string
+		var moddedVals []string
+		for ii := 0; ii < 8; ii++ {
+			if ii != i {
+				moddedKeys = append(moddedKeys, stKeys[ii])
+				moddedVals = append(moddedVals, stVals[ii])
+			}
+		}
+		helper.addSnapStorage(accKey, moddedKeys, moddedVals)
+	}
+
+	root, snap := helper.Generate()
+	t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff
+
+	select {
+	case <-snap.genPending:
+		// Snapshot generation succeeded
+
+	case <-time.After(250 * time.Millisecond):
+		t.Errorf("Snapshot generation failed")
+	}
+	checkSnapRoot(t, snap, root)
+	// Signal abortion to the generator and wait for it to tear down
+	stop := make(chan *generatorStats)
+	snap.genAbort <- stop
+	<-stop
+}
diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go
index 84cc5c3bca..1d9340bbad 100644
--- a/core/state/snapshot/iterator.go
+++ b/core/state/snapshot/iterator.go
@@ -26,9 +26,9 @@ import (
 	"github.com/ethereum/go-ethereum/ethdb"
 )
 
-// AccountIterator is an iterator to step over all the accounts in a snapshot,
-// which may or may npt be composed of multiple layers.
-type AccountIterator interface {
+// Iterator is an iterator to step over all the accounts or the specific
+// storage in a snapshot which may or may not be composed of multiple layers.
+type Iterator interface {
 	// Next steps the iterator forward one element, returning false if exhausted,
 	// or an error if iteration failed for some reason (e.g. root being iterated
 	// becomes stale and garbage collected).
@@ -38,18 +38,35 @@ type AccountIterator interface {
 	// caused a premature iteration exit (e.g. snapshot stack becoming stale).
 	Error() error
 
-	// Hash returns the hash of the account the iterator is currently at.
+	// Hash returns the hash of the account or storage slot the iterator is
+	// currently at.
 	Hash() common.Hash
 
-	// Account returns the RLP encoded slim account the iterator is currently at.
-	// An error will be returned if the iterator becomes invalid (e.g. snaph
-	Account() []byte
-
 	// Release releases associated resources. Release should always succeed and
 	// can be called multiple times without causing error.
 	Release()
 }
 
+// AccountIterator is an iterator to step over all the accounts in a snapshot,
+// which may or may not be composed of multiple layers.
+type AccountIterator interface {
+	Iterator
+
+	// Account returns the RLP encoded slim account the iterator is currently at.
+	// An error will be returned if the iterator becomes invalid
+	Account() []byte
+}
+
+// StorageIterator is an iterator to step over the specific storage in a snapshot,
+// which may or may not be composed of multiple layers.
+type StorageIterator interface {
+	Iterator
+
+	// Slot returns the storage slot the iterator is currently at. An error will
+	// be returned if the iterator becomes invalid
+	Slot() []byte
+}
+
 // diffAccountIterator is an account iterator that steps over the accounts (both
 // live and deleted) contained within a single diff layer. Higher order iterators
 // will use the deleted accounts to skip deeper iterators.
@@ -70,7 +87,7 @@ func (dl *diffLayer) AccountIterator(seek common.Hash) AccountIterator {
 	// Seek out the requested starting account
 	hashes := dl.AccountList()
 	index := sort.Search(len(hashes), func(i int) bool {
-		return bytes.Compare(seek[:], hashes[i][:]) < 0
+		return bytes.Compare(seek[:], hashes[i][:]) <= 0
 	})
 	// Assemble and returned the already seeked iterator
 	return &diffAccountIterator{
@@ -116,15 +133,18 @@ func (it *diffAccountIterator) Hash() common.Hash {
 
 // Account returns the RLP encoded slim account the iterator is currently at.
 // This method may _fail_, if the underlying layer has been flattened between
-// the call to Next and Acccount. That type of error will set it.Err.
+// the call to Next and Account. That type of error will set it.Err.
 // This method assumes that flattening does not delete elements from
 // the accountdata mapping (writing nil into it is fine though), and will panic
 // if elements have been deleted.
+//
+// Note the returned account is not a copy, please don't modify it.
 func (it *diffAccountIterator) Account() []byte {
 	it.layer.lock.RLock()
 	blob, ok := it.layer.accountData[it.curHash]
 	if !ok {
 		if _, ok := it.layer.destructSet[it.curHash]; ok {
+			it.layer.lock.RUnlock()
 			return nil
 		}
 		panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash))
@@ -163,7 +183,7 @@ func (it *diskAccountIterator) Next() bool {
 	}
 	// Try to advance the iterator and release it if we reached the end
 	for {
-		if !it.it.Next() || !bytes.HasPrefix(it.it.Key(), rawdb.SnapshotAccountPrefix) {
+		if !it.it.Next() {
 			it.it.Release()
 			it.it = nil
 			return false
@@ -181,12 +201,15 @@ func (it *diskAccountIterator) Next() bool {
 // A diff layer is immutable after creation content wise and can always be fully
 // iterated without error, so this method always returns nil.
 func (it *diskAccountIterator) Error() error {
+	if it.it == nil {
+		return nil // Iterator is exhausted and released
+	}
 	return it.it.Error()
 }
 
 // Hash returns the hash of the account the iterator is currently at.
 func (it *diskAccountIterator) Hash() common.Hash {
-	return common.BytesToHash(it.it.Key())
+	return common.BytesToHash(it.it.Key()) // The prefix will be truncated
 }
 
 // Account returns the RLP encoded slim account the iterator is currently at.
@@ -202,3 +225,176 @@ func (it *diskAccountIterator) Release() {
 		it.it = nil
 	}
 }
+
+// diffStorageIterator is a storage iterator that steps over the specific storage
+// (both live and deleted) contained within a single diff layer. Higher order
+// iterators will use the deleted slot to skip deeper iterators.
+type diffStorageIterator struct {
+	// curHash is the current hash the iterator is positioned on. The field is
+	// explicitly tracked since the referenced diff layer might go stale after
+	// the iterator was positioned and we don't want to fail accessing the old
+	// hash as long as the iterator is not touched any more.
+	curHash common.Hash
+	account common.Hash
+
+	layer *diffLayer    // Live layer to retrieve values from
+	keys  []common.Hash // Keys left in the layer to iterate
+	fail  error         // Any failures encountered (stale)
+}
+
+// StorageIterator creates a storage iterator over a single diff layer.
+// Except the storage iterator is returned, there is an additional flag
+// "destructed" returned. If it's true then it means the whole storage is
+// destructed in this layer(maybe recreated too), don't bother deeper layer
+// for storage retrieval.
+func (dl *diffLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) {
+	// Create the storage for this account even it's marked
+	// as destructed. The iterator is for the new one which
+	// just has the same address as the deleted one.
+	hashes, destructed := dl.StorageList(account)
+	index := sort.Search(len(hashes), func(i int) bool {
+		return bytes.Compare(seek[:], hashes[i][:]) <= 0
+	})
+	// Assemble and returned the already seeked iterator
+	return &diffStorageIterator{
+		layer:   dl,
+		account: account,
+		keys:    hashes[index:],
+	}, destructed
+}
+
+// Next steps the iterator forward one element, returning false if exhausted.
+func (it *diffStorageIterator) Next() bool {
+	// If the iterator was already stale, consider it a programmer error. Although
+	// we could just return false here, triggering this path would probably mean
+	// somebody forgot to check for Error, so lets blow up instead of undefined
+	// behavior that's hard to debug.
+	if it.fail != nil {
+		panic(fmt.Sprintf("called Next of failed iterator: %v", it.fail))
+	}
+	// Stop iterating if all keys were exhausted
+	if len(it.keys) == 0 {
+		return false
+	}
+	if it.layer.Stale() {
+		it.fail, it.keys = ErrSnapshotStale, nil
+		return false
+	}
+	// Iterator seems to be still alive, retrieve and cache the live hash
+	it.curHash = it.keys[0]
+	// key cached, shift the iterator and notify the user of success
+	it.keys = it.keys[1:]
+	return true
+}
+
+// Error returns any failure that occurred during iteration, which might have
+// caused a premature iteration exit (e.g. snapshot stack becoming stale).
+func (it *diffStorageIterator) Error() error {
+	return it.fail
+}
+
+// Hash returns the hash of the storage slot the iterator is currently at.
+func (it *diffStorageIterator) Hash() common.Hash {
+	return it.curHash
+}
+
+// Slot returns the raw storage slot value the iterator is currently at.
+// This method may _fail_, if the underlying layer has been flattened between
+// the call to Next and Value. That type of error will set it.Err.
+// This method assumes that flattening does not delete elements from
+// the storage mapping (writing nil into it is fine though), and will panic
+// if elements have been deleted.
+//
+// Note the returned slot is not a copy, please don't modify it.
+func (it *diffStorageIterator) Slot() []byte {
+	it.layer.lock.RLock()
+	storage, ok := it.layer.storageData[it.account]
+	if !ok {
+		panic(fmt.Sprintf("iterator referenced non-existent account storage: %x", it.account))
+	}
+	// Storage slot might be nil(deleted), but it must exist
+	blob, ok := storage[it.curHash]
+	if !ok {
+		panic(fmt.Sprintf("iterator referenced non-existent storage slot: %x", it.curHash))
+	}
+	it.layer.lock.RUnlock()
+	if it.layer.Stale() {
+		it.fail, it.keys = ErrSnapshotStale, nil
+	}
+	return blob
+}
+
+// Release is a noop for diff account iterators as there are no held resources.
+func (it *diffStorageIterator) Release() {}
+
+// diskStorageIterator is a storage iterator that steps over the live storage
+// contained within a disk layer.
+type diskStorageIterator struct {
+	layer   *diskLayer
+	account common.Hash
+	it      ethdb.Iterator
+}
+
+// StorageIterator creates a storage iterator over a disk layer.
+// If the whole storage is destructed, then all entries in the disk
+// layer are deleted already. So the "destructed" flag returned here
+// is always false.
+func (dl *diskLayer) StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool) {
+	pos := common.TrimRightZeroes(seek[:])
+	return &diskStorageIterator{
+		layer:   dl,
+		account: account,
+		it:      dl.diskdb.NewIterator(append(rawdb.SnapshotStoragePrefix, account.Bytes()...), pos),
+	}, false
+}
+
+// Next steps the iterator forward one element, returning false if exhausted.
+func (it *diskStorageIterator) Next() bool {
+	// If the iterator was already exhausted, don't bother
+	if it.it == nil {
+		return false
+	}
+	// Try to advance the iterator and release it if we reached the end
+	for {
+		if !it.it.Next() {
+			it.it.Release()
+			it.it = nil
+			return false
+		}
+		if len(it.it.Key()) == len(rawdb.SnapshotStoragePrefix)+common.HashLength+common.HashLength {
+			break
+		}
+	}
+	return true
+}
+
+// Error returns any failure that occurred during iteration, which might have
+// caused a premature iteration exit (e.g. snapshot stack becoming stale).
+//
+// A diff layer is immutable after creation content wise and can always be fully
+// iterated without error, so this method always returns nil.
+func (it *diskStorageIterator) Error() error {
+	if it.it == nil {
+		return nil // Iterator is exhausted and released
+	}
+	return it.it.Error()
+}
+
+// Hash returns the hash of the storage slot the iterator is currently at.
+func (it *diskStorageIterator) Hash() common.Hash {
+	return common.BytesToHash(it.it.Key()) // The prefix will be truncated
+}
+
+// Slot returns the raw strorage slot content the iterator is currently at.
+func (it *diskStorageIterator) Slot() []byte {
+	return it.it.Value()
+}
+
+// Release releases the database snapshot held during iteration.
+func (it *diskStorageIterator) Release() {
+	// The iterator is auto-released on exhaustion, so make sure it's still alive
+	if it.it != nil {
+		it.it.Release()
+		it.it = nil
+	}
+}
diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go
index 7d647ee7ba..22184b2545 100644
--- a/core/state/snapshot/iterator_binary.go
+++ b/core/state/snapshot/iterator_binary.go
@@ -22,29 +22,91 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 )
 
-// binaryAccountIterator is a simplistic iterator to step over the accounts in
-// a snapshot, which may or may npt be composed of multiple layers. Performance
+// binaryIterator is a simplistic iterator to step over the accounts or storage
+// in a snapshot, which may or may not be composed of multiple layers. Performance
 // wise this iterator is slow, it's meant for cross validating the fast one,
-type binaryAccountIterator struct {
-	a     *diffAccountIterator
-	b     AccountIterator
-	aDone bool
-	bDone bool
-	k     common.Hash
-	fail  error
+type binaryIterator struct {
+	a               Iterator
+	b               Iterator
+	aDone           bool
+	bDone           bool
+	accountIterator bool
+	k               common.Hash
+	account         common.Hash
+	fail            error
 }
 
-// newBinaryAccountIterator creates a simplistic account iterator to step over
-// all the accounts in a slow, but eaily verifiable way.
-func (dl *diffLayer) newBinaryAccountIterator() AccountIterator {
+// initBinaryAccountIterator creates a simplistic iterator to step over all the
+// accounts in a slow, but easily verifiable way. Note this function is used for
+// initialization, use `newBinaryAccountIterator` as the API.
+func (dl *diffLayer) initBinaryAccountIterator() Iterator {
 	parent, ok := dl.parent.(*diffLayer)
 	if !ok {
-		// parent is the disk layer
-		return dl.AccountIterator(common.Hash{})
+		l := &binaryIterator{
+			a:               dl.AccountIterator(common.Hash{}),
+			b:               dl.Parent().AccountIterator(common.Hash{}),
+			accountIterator: true,
+		}
+		l.aDone = !l.a.Next()
+		l.bDone = !l.b.Next()
+		return l
 	}
-	l := &binaryAccountIterator{
-		a: dl.AccountIterator(common.Hash{}).(*diffAccountIterator),
-		b: parent.newBinaryAccountIterator(),
+	l := &binaryIterator{
+		a:               dl.AccountIterator(common.Hash{}),
+		b:               parent.initBinaryAccountIterator(),
+		accountIterator: true,
+	}
+	l.aDone = !l.a.Next()
+	l.bDone = !l.b.Next()
+	return l
+}
+
+// initBinaryStorageIterator creates a simplistic iterator to step over all the
+// storage slots in a slow, but easily verifiable way. Note this function is used
+// for initialization, use `newBinaryStorageIterator` as the API.
+func (dl *diffLayer) initBinaryStorageIterator(account common.Hash) Iterator {
+	parent, ok := dl.parent.(*diffLayer)
+	if !ok {
+		// If the storage in this layer is already destructed, discard all
+		// deeper layers but still return an valid single-branch iterator.
+		a, destructed := dl.StorageIterator(account, common.Hash{})
+		if destructed {
+			l := &binaryIterator{
+				a:       a,
+				account: account,
+			}
+			l.aDone = !l.a.Next()
+			l.bDone = true
+			return l
+		}
+		// The parent is disk layer, don't need to take care "destructed"
+		// anymore.
+		b, _ := dl.Parent().StorageIterator(account, common.Hash{})
+		l := &binaryIterator{
+			a:       a,
+			b:       b,
+			account: account,
+		}
+		l.aDone = !l.a.Next()
+		l.bDone = !l.b.Next()
+		return l
+	}
+	// If the storage in this layer is already destructed, discard all
+	// deeper layers but still return an valid single-branch iterator.
+	a, destructed := dl.StorageIterator(account, common.Hash{})
+	if destructed {
+		l := &binaryIterator{
+			a:       a,
+			account: account,
+		}
+		l.aDone = !l.a.Next()
+		l.bDone = true
+		return l
+	}
+	l := &binaryIterator{
+		a:       a,
+		b:       parent.initBinaryStorageIterator(account),
+		account: account,
 	}
 	l.aDone = !l.a.Next()
 	l.bDone = !l.b.Next()
@@ -54,23 +116,22 @@ func (dl *diffLayer) newBinaryAccountIterator() AccountIterator {
 // Next steps the iterator forward one element, returning false if exhausted,
 // or an error if iteration failed for some reason (e.g. root being iterated
 // becomes stale and garbage collected).
-func (it *binaryAccountIterator) Next() bool {
+func (it *binaryIterator) Next() bool {
 	if it.aDone && it.bDone {
 		return false
 	}
-	nextB := it.b.Hash()
 first:
-	nextA := it.a.Hash()
 	if it.aDone {
+		it.k = it.b.Hash()
 		it.bDone = !it.b.Next()
-		it.k = nextB
 		return true
 	}
 	if it.bDone {
+		it.k = it.a.Hash()
 		it.aDone = !it.a.Next()
-		it.k = nextA
 		return true
 	}
+	nextA, nextB := it.a.Hash(), it.b.Hash()
 	if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 {
 		it.aDone = !it.a.Next()
 		it.k = nextA
@@ -87,20 +148,43 @@ first:
 
 // Error returns any failure that occurred during iteration, which might have
 // caused a premature iteration exit (e.g. snapshot stack becoming stale).
-func (it *binaryAccountIterator) Error() error {
+func (it *binaryIterator) Error() error {
 	return it.fail
 }
 
 // Hash returns the hash of the account the iterator is currently at.
-func (it *binaryAccountIterator) Hash() common.Hash {
+func (it *binaryIterator) Hash() common.Hash {
 	return it.k
 }
 
 // Account returns the RLP encoded slim account the iterator is currently at, or
 // nil if the iterated snapshot stack became stale (you can check Error after
 // to see if it failed or not).
-func (it *binaryAccountIterator) Account() []byte {
-	blob, err := it.a.layer.AccountRLP(it.k)
+//
+// Note the returned account is not a copy, please don't modify it.
+func (it *binaryIterator) Account() []byte {
+	if !it.accountIterator {
+		return nil
+	}
+	// The topmost iterator must be `diffAccountIterator`
+	blob, err := it.a.(*diffAccountIterator).layer.AccountRLP(it.k)
+	if err != nil {
+		it.fail = err
+		return nil
+	}
+	return blob
+}
+
+// Slot returns the raw storage slot data the iterator is currently at, or
+// nil if the iterated snapshot stack became stale (you can check Error after
+// to see if it failed or not).
+//
+// Note the returned slot is not a copy, please don't modify it.
+func (it *binaryIterator) Slot() []byte {
+	if it.accountIterator {
+		return nil
+	}
+	blob, err := it.a.(*diffStorageIterator).layer.Storage(it.account, it.k)
 	if err != nil {
 		it.fail = err
 		return nil
@@ -109,7 +193,21 @@ func (it *binaryAccountIterator) Account() []byte {
 }
 
 // Release recursively releases all the iterators in the stack.
-func (it *binaryAccountIterator) Release() {
+func (it *binaryIterator) Release() {
 	it.a.Release()
 	it.b.Release()
 }
+
+// newBinaryAccountIterator creates a simplistic account iterator to step over
+// all the accounts in a slow, but easily verifiable way.
+func (dl *diffLayer) newBinaryAccountIterator() AccountIterator {
+	iter := dl.initBinaryAccountIterator()
+	return iter.(AccountIterator)
+}
+
+// newBinaryStorageIterator creates a simplistic account iterator to step over
+// all the storage slots in a slow, but easily verifiable way.
+func (dl *diffLayer) newBinaryStorageIterator(account common.Hash) StorageIterator {
+	iter := dl.initBinaryStorageIterator(account)
+	return iter.(StorageIterator)
+}
diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go
index 99734ec912..48069b8fcf 100644
--- a/core/state/snapshot/iterator_fast.go
+++ b/core/state/snapshot/iterator_fast.go
@@ -24,23 +24,23 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 )
 
-// weightedAccountIterator is an account iterator with an assigned weight. It is
-// used to prioritise which account is the correct one if multiple iterators find
-// the same one (modified in multiple consecutive blocks).
-type weightedAccountIterator struct {
-	it       AccountIterator
+// weightedIterator is a iterator with an assigned weight. It is used to prioritise
+// which account or storage slot is the correct one if multiple iterators find the
+// same one (modified in multiple consecutive blocks).
+type weightedIterator struct {
+	it       Iterator
 	priority int
 }
 
-// weightedAccountIterators is a set of iterators implementing the sort.Interface.
-type weightedAccountIterators []*weightedAccountIterator
+// weightedIterators is a set of iterators implementing the sort.Interface.
+type weightedIterators []*weightedIterator
 
 // Len implements sort.Interface, returning the number of active iterators.
-func (its weightedAccountIterators) Len() int { return len(its) }
+func (its weightedIterators) Len() int { return len(its) }
 
 // Less implements sort.Interface, returning which of two iterators in the stack
 // is before the other.
-func (its weightedAccountIterators) Less(i, j int) bool {
+func (its weightedIterators) Less(i, j int) bool {
 	// Order the iterators primarily by the account hashes
 	hashI := its[i].it.Hash()
 	hashJ := its[j].it.Hash()
@@ -51,45 +51,64 @@ func (its weightedAccountIterators) Less(i, j int) bool {
 	case 1:
 		return false
 	}
-	// Same account in multiple layers, split by priority
+	// Same account/storage-slot in multiple layers, split by priority
 	return its[i].priority < its[j].priority
 }
 
 // Swap implements sort.Interface, swapping two entries in the iterator stack.
-func (its weightedAccountIterators) Swap(i, j int) {
+func (its weightedIterators) Swap(i, j int) {
 	its[i], its[j] = its[j], its[i]
 }
 
-// fastAccountIterator is a more optimized multi-layer iterator which maintains a
+// fastIterator is a more optimized multi-layer iterator which maintains a
 // direct mapping of all iterators leading down to the bottom layer.
-type fastAccountIterator struct {
-	tree       *Tree       // Snapshot tree to reinitialize stale sub-iterators with
-	root       common.Hash // Root hash to reinitialize stale sub-iterators through
+type fastIterator struct {
+	tree *Tree       // Snapshot tree to reinitialize stale sub-iterators with
+	root common.Hash // Root hash to reinitialize stale sub-iterators through
+
 	curAccount []byte
+	curSlot    []byte
 
-	iterators weightedAccountIterators
+	iterators weightedIterators
 	initiated bool
+	account   bool
 	fail      error
 }
 
-// newFastAccountIterator creates a new hierarhical account iterator with one
+// newFastIterator creates a new hierarchical account or storage iterator with one
 // element per diff layer. The returned combo iterator can be used to walk over
 // the entire snapshot diff stack simultaneously.
-func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) {
+func newFastIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash, accountIterator bool) (*fastIterator, error) {
 	snap := tree.Snapshot(root)
 	if snap == nil {
 		return nil, fmt.Errorf("unknown snapshot: %x", root)
 	}
-	fi := &fastAccountIterator{
-		tree: tree,
-		root: root,
+	fi := &fastIterator{
+		tree:    tree,
+		root:    root,
+		account: accountIterator,
 	}
 	current := snap.(snapshot)
 	for depth := 0; current != nil; depth++ {
-		fi.iterators = append(fi.iterators, &weightedAccountIterator{
-			it:       current.AccountIterator(seek),
-			priority: depth,
-		})
+		if accountIterator {
+			fi.iterators = append(fi.iterators, &weightedIterator{
+				it:       current.AccountIterator(seek),
+				priority: depth,
+			})
+		} else {
+			// If the whole storage is destructed in this layer, don't
+			// bother deeper layer anymore. But we should still keep
+			// the iterator for this layer, since the iterator can contain
+			// some valid slots which belongs to the re-created account.
+			it, destructed := current.StorageIterator(account, seek)
+			fi.iterators = append(fi.iterators, &weightedIterator{
+				it:       it,
+				priority: depth,
+			})
+			if destructed {
+				break
+			}
+		}
 		current = current.Parent()
 	}
 	fi.init()
@@ -98,7 +117,7 @@ func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (Acc
 
 // init walks over all the iterators and resolves any clashes between them, after
 // which it prepares the stack for step-by-step iteration.
-func (fi *fastAccountIterator) init() {
+func (fi *fastIterator) init() {
 	// Track which account hashes are iterators positioned on
 	var positioned = make(map[common.Hash]int)
 
@@ -153,7 +172,7 @@ func (fi *fastAccountIterator) init() {
 }
 
 // Next steps the iterator forward one element, returning false if exhausted.
-func (fi *fastAccountIterator) Next() bool {
+func (fi *fastIterator) Next() bool {
 	if len(fi.iterators) == 0 {
 		return false
 	}
@@ -161,21 +180,25 @@ func (fi *fastAccountIterator) Next() bool {
 		// Don't forward first time -- we had to 'Next' once in order to
 		// do the sorting already
 		fi.initiated = true
-		fi.curAccount = fi.iterators[0].it.Account()
+		if fi.account {
+			fi.curAccount = fi.iterators[0].it.(AccountIterator).Account()
+		} else {
+			fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot()
+		}
 		if innerErr := fi.iterators[0].it.Error(); innerErr != nil {
 			fi.fail = innerErr
 			return false
 		}
-		if fi.curAccount != nil {
+		if fi.curAccount != nil || fi.curSlot != nil {
 			return true
 		}
-		// Implicit else: we've hit a nil-account, and need to fall through to the
-		// loop below to land on something non-nil
+		// Implicit else: we've hit a nil-account or nil-slot, and need to
+		// fall through to the loop below to land on something non-nil
 	}
-	// If an account is deleted in one of the layers, the key will still be there,
-	// but the actual value will be nil. However, the iterator should not
-	// export nil-values (but instead simply omit the key), so we need to loop
-	// here until we either
+	// If an account or a slot is deleted in one of the layers, the key will
+	// still be there, but the actual value will be nil. However, the iterator
+	// should not export nil-values (but instead simply omit the key), so we
+	// need to loop here until we either
 	//  - get a non-nil value,
 	//  - hit an error,
 	//  - or exhaust the iterator
@@ -183,12 +206,16 @@ func (fi *fastAccountIterator) Next() bool {
 		if !fi.next(0) {
 			return false // exhausted
 		}
-		fi.curAccount = fi.iterators[0].it.Account()
+		if fi.account {
+			fi.curAccount = fi.iterators[0].it.(AccountIterator).Account()
+		} else {
+			fi.curSlot = fi.iterators[0].it.(StorageIterator).Slot()
+		}
 		if innerErr := fi.iterators[0].it.Error(); innerErr != nil {
 			fi.fail = innerErr
 			return false // error
 		}
-		if fi.curAccount != nil {
+		if fi.curAccount != nil || fi.curSlot != nil {
 			break // non-nil value found
 		}
 	}
@@ -201,7 +228,7 @@ func (fi *fastAccountIterator) Next() bool {
 // For example, if the iterated hashes become [2,3,5,5,8,9,10], then we should
 // invoke next(3), which will call Next on elem 3 (the second '5') and will
 // cascade along the list, applying the same operation if needed.
-func (fi *fastAccountIterator) next(idx int) bool {
+func (fi *fastIterator) next(idx int) bool {
 	// If this particular iterator got exhausted, remove it and return true (the
 	// next one is surely not exhausted yet, otherwise it would have been removed
 	// already).
@@ -211,7 +238,7 @@ func (fi *fastAccountIterator) next(idx int) bool {
 		fi.iterators = append(fi.iterators[:idx], fi.iterators[idx+1:]...)
 		return len(fi.iterators) > 0
 	}
-	// If there's noone left to cascade into, return
+	// If there's no one left to cascade into, return
 	if idx == len(fi.iterators)-1 {
 		return true
 	}
@@ -262,7 +289,7 @@ func (fi *fastAccountIterator) next(idx int) bool {
 }
 
 // move advances an iterator to another position in the list.
-func (fi *fastAccountIterator) move(index, newpos int) {
+func (fi *fastIterator) move(index, newpos int) {
 	elem := fi.iterators[index]
 	copy(fi.iterators[index:], fi.iterators[index+1:newpos+1])
 	fi.iterators[newpos] = elem
@@ -270,23 +297,30 @@ func (fi *fastAccountIterator) move(index, newpos int) {
 
 // Error returns any failure that occurred during iteration, which might have
 // caused a premature iteration exit (e.g. snapshot stack becoming stale).
-func (fi *fastAccountIterator) Error() error {
+func (fi *fastIterator) Error() error {
 	return fi.fail
 }
 
 // Hash returns the current key
-func (fi *fastAccountIterator) Hash() common.Hash {
+func (fi *fastIterator) Hash() common.Hash {
 	return fi.iterators[0].it.Hash()
 }
 
-// Account returns the current key
-func (fi *fastAccountIterator) Account() []byte {
+// Account returns the current account blob.
+// Note the returned account is not a copy, please don't modify it.
+func (fi *fastIterator) Account() []byte {
 	return fi.curAccount
 }
 
+// Slot returns the current storage slot.
+// Note the returned slot is not a copy, please don't modify it.
+func (fi *fastIterator) Slot() []byte {
+	return fi.curSlot
+}
+
 // Release iterates over all the remaining live layer iterators and releases each
 // of thme individually.
-func (fi *fastAccountIterator) Release() {
+func (fi *fastIterator) Release() {
 	for _, it := range fi.iterators {
 		it.it.Release()
 	}
@@ -294,9 +328,23 @@ func (fi *fastAccountIterator) Release() {
 }
 
 // Debug is a convencience helper during testing
-func (fi *fastAccountIterator) Debug() {
+func (fi *fastIterator) Debug() {
 	for _, it := range fi.iterators {
 		fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0])
 	}
 	fmt.Println()
 }
+
+// newFastAccountIterator creates a new hierarchical account iterator with one
+// element per diff layer. The returned combo iterator can be used to walk over
+// the entire snapshot diff stack simultaneously.
+func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) {
+	return newFastIterator(tree, root, common.Hash{}, seek, true)
+}
+
+// newFastStorageIterator creates a new hierarchical storage iterator with one
+// element per diff layer. The returned combo iterator can be used to walk over
+// the entire snapshot diff stack simultaneously.
+func newFastStorageIterator(tree *Tree, root common.Hash, account common.Hash, seek common.Hash) (StorageIterator, error) {
+	return newFastIterator(tree, root, account, seek, false)
+}
diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go
index 5468a9a589..2c7e876e08 100644
--- a/core/state/snapshot/iterator_test.go
+++ b/core/state/snapshot/iterator_test.go
@@ -28,7 +28,7 @@ import (
 	"github.com/ethereum/go-ethereum/core/rawdb"
 )
 
-// TestAccountIteratorBasics tests some simple single-layer iteration
+// TestAccountIteratorBasics tests some simple single-layer(diff and disk) iteration
 func TestAccountIteratorBasics(t *testing.T) {
 	var (
 		destructs = make(map[common.Hash]struct{})
@@ -53,9 +53,55 @@ func TestAccountIteratorBasics(t *testing.T) {
 		}
 	}
 	// Add some (identical) layers on top
-	parent := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage))
-	it := parent.AccountIterator(common.Hash{})
-	verifyIterator(t, 100, it)
+	diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage))
+	it := diffLayer.AccountIterator(common.Hash{})
+	verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator
+
+	diskLayer := diffToDisk(diffLayer)
+	it = diskLayer.AccountIterator(common.Hash{})
+	verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator
+}
+
+// TestStorageIteratorBasics tests some simple single-layer(diff and disk) iteration for storage
+func TestStorageIteratorBasics(t *testing.T) {
+	var (
+		nilStorage = make(map[common.Hash]int)
+		accounts   = make(map[common.Hash][]byte)
+		storage    = make(map[common.Hash]map[common.Hash][]byte)
+	)
+	// Fill some random data
+	for i := 0; i < 10; i++ {
+		h := randomHash()
+		accounts[h] = randomAccount()
+
+		accStorage := make(map[common.Hash][]byte)
+		value := make([]byte, 32)
+
+		var nilstorage int
+		for i := 0; i < 100; i++ {
+			rand.Read(value)
+			if rand.Intn(2) == 0 {
+				accStorage[randomHash()] = common.CopyBytes(value)
+			} else {
+				accStorage[randomHash()] = nil // delete slot
+				nilstorage += 1
+			}
+		}
+		storage[h] = accStorage
+		nilStorage[h] = nilstorage
+	}
+	// Add some (identical) layers on top
+	diffLayer := newDiffLayer(emptyLayer(), common.Hash{}, nil, copyAccounts(accounts), copyStorage(storage))
+	for account := range accounts {
+		it, _ := diffLayer.StorageIterator(account, common.Hash{})
+		verifyIterator(t, 100, it, verifyNothing) // Nil is allowed for single layer iterator
+	}
+
+	diskLayer := diffToDisk(diffLayer)
+	for account := range accounts {
+		it, _ := diskLayer.StorageIterator(account, common.Hash{})
+		verifyIterator(t, 100-nilStorage[account], it, verifyNothing) // Nil is allowed for single layer iterator
+	}
 }
 
 type testIterator struct {
@@ -87,6 +133,10 @@ func (ti *testIterator) Account() []byte {
 	return nil
 }
 
+func (ti *testIterator) Slot() []byte {
+	return nil
+}
+
 func (ti *testIterator) Release() {}
 
 func TestFastIteratorBasics(t *testing.T) {
@@ -102,13 +152,12 @@ func TestFastIteratorBasics(t *testing.T) {
 			{9, 10}, {10, 13, 15, 16}},
 			expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}},
 	} {
-		var iterators []*weightedAccountIterator
+		var iterators []*weightedIterator
 		for i, data := range tc.lists {
 			it := newTestIterator(data...)
-			iterators = append(iterators, &weightedAccountIterator{it, i})
-
+			iterators = append(iterators, &weightedIterator{it, i})
 		}
-		fi := &fastAccountIterator{
+		fi := &fastIterator{
 			iterators: iterators,
 			initiated: false,
 		}
@@ -122,7 +171,15 @@ func TestFastIteratorBasics(t *testing.T) {
 	}
 }
 
-func verifyIterator(t *testing.T, expCount int, it AccountIterator) {
+type verifyContent int
+
+const (
+	verifyNothing verifyContent = iota
+	verifyAccount
+	verifyStorage
+)
+
+func verifyIterator(t *testing.T, expCount int, it Iterator, verify verifyContent) {
 	t.Helper()
 
 	var (
@@ -134,10 +191,13 @@ func verifyIterator(t *testing.T, expCount int, it AccountIterator) {
 		if bytes.Compare(last[:], hash[:]) >= 0 {
 			t.Errorf("wrong order: %x >= %x", last, hash)
 		}
-		if it.Account() == nil {
+		count++
+		if verify == verifyAccount && len(it.(AccountIterator).Account()) == 0 {
+			t.Errorf("iterator returned nil-value for hash %x", hash)
+		} else if verify == verifyStorage && len(it.(StorageIterator).Slot()) == 0 {
 			t.Errorf("iterator returned nil-value for hash %x", hash)
 		}
-		count++
+		last = hash
 	}
 	if count != expCount {
 		t.Errorf("iterator count mismatch: have %d, want %d", count, expCount)
@@ -173,13 +233,74 @@ func TestAccountIteratorTraversal(t *testing.T) {
 	// Verify the single and multi-layer iterators
 	head := snaps.Snapshot(common.HexToHash("0x04"))
 
-	verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{}))
-	verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator())
+	verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing)
+	verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount)
 
 	it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{})
-	defer it.Release()
+	verifyIterator(t, 7, it, verifyAccount)
+	it.Release()
+
+	// Test after persist some bottom-most layers into the disk,
+	// the functionalities still work.
+	limit := aggregatorMemoryLimit
+	defer func() {
+		aggregatorMemoryLimit = limit
+	}()
+	aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk
+	snaps.Cap(common.HexToHash("0x04"), 2)
+	verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount)
+
+	it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{})
+	verifyIterator(t, 7, it, verifyAccount)
+	it.Release()
+}
+
+func TestStorageIteratorTraversal(t *testing.T) {
+	// Create an empty base layer and a snapshot tree out of it
+	base := &diskLayer{
+		diskdb: rawdb.NewMemoryDatabase(),
+		root:   common.HexToHash("0x01"),
+		cache:  fastcache.New(1024 * 500),
+	}
+	snaps := &Tree{
+		layers: map[common.Hash]snapshot{
+			base.root: base,
+		},
+	}
+	// Stack three diff layers on top with various overlaps
+	snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil))
+
+	snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x04", "0x05", "0x06"}}, nil))
+
+	snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil))
+
+	// Verify the single and multi-layer iterators
+	head := snaps.Snapshot(common.HexToHash("0x04"))
 
-	verifyIterator(t, 7, it)
+	diffIter, _ := head.(snapshot).StorageIterator(common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 3, diffIter, verifyNothing)
+	verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage)
+
+	it, _ := snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 6, it, verifyStorage)
+	it.Release()
+
+	// Test after persist some bottom-most layers into the disk,
+	// the functionalities still work.
+	limit := aggregatorMemoryLimit
+	defer func() {
+		aggregatorMemoryLimit = limit
+	}()
+	aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk
+	snaps.Cap(common.HexToHash("0x04"), 2)
+	verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage)
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 6, it, verifyStorage)
+	it.Release()
 }
 
 // TestAccountIteratorTraversalValues tests some multi-layer iteration, where we
@@ -242,8 +363,6 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
 	snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil)
 
 	it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{})
-	defer it.Release()
-
 	head := snaps.Snapshot(common.HexToHash("0x09"))
 	for it.Next() {
 		hash := it.Hash()
@@ -255,6 +374,128 @@ func TestAccountIteratorTraversalValues(t *testing.T) {
 			t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want)
 		}
 	}
+	it.Release()
+
+	// Test after persist some bottom-most layers into the disk,
+	// the functionalities still work.
+	limit := aggregatorMemoryLimit
+	defer func() {
+		aggregatorMemoryLimit = limit
+	}()
+	aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk
+	snaps.Cap(common.HexToHash("0x09"), 2)
+
+	it, _ = snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{})
+	for it.Next() {
+		hash := it.Hash()
+		want, err := head.AccountRLP(hash)
+		if err != nil {
+			t.Fatalf("failed to retrieve expected account: %v", err)
+		}
+		if have := it.Account(); !bytes.Equal(want, have) {
+			t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want)
+		}
+	}
+	it.Release()
+}
+
+func TestStorageIteratorTraversalValues(t *testing.T) {
+	// Create an empty base layer and a snapshot tree out of it
+	base := &diskLayer{
+		diskdb: rawdb.NewMemoryDatabase(),
+		root:   common.HexToHash("0x01"),
+		cache:  fastcache.New(1024 * 500),
+	}
+	snaps := &Tree{
+		layers: map[common.Hash]snapshot{
+			base.root: base,
+		},
+	}
+	wrapStorage := func(storage map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte {
+		return map[common.Hash]map[common.Hash][]byte{
+			common.HexToHash("0xaa"): storage,
+		}
+	}
+	// Create a batch of storage sets to seed subsequent layers with
+	var (
+		a = make(map[common.Hash][]byte)
+		b = make(map[common.Hash][]byte)
+		c = make(map[common.Hash][]byte)
+		d = make(map[common.Hash][]byte)
+		e = make(map[common.Hash][]byte)
+		f = make(map[common.Hash][]byte)
+		g = make(map[common.Hash][]byte)
+		h = make(map[common.Hash][]byte)
+	)
+	for i := byte(2); i < 0xff; i++ {
+		a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i))
+		if i > 20 && i%2 == 0 {
+			b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i))
+		}
+		if i%4 == 0 {
+			c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i))
+		}
+		if i%7 == 0 {
+			d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i))
+		}
+		if i%8 == 0 {
+			e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i))
+		}
+		if i > 50 || i < 85 {
+			f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i))
+		}
+		if i%64 == 0 {
+			g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i))
+		}
+		if i%128 == 0 {
+			h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i))
+		}
+	}
+	// Assemble a stack of snapshots from the account layers
+	snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa"), wrapStorage(a))
+	snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xaa"), wrapStorage(b))
+	snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xaa"), wrapStorage(c))
+	snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, randomAccountSet("0xaa"), wrapStorage(d))
+	snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, randomAccountSet("0xaa"), wrapStorage(e))
+	snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, randomAccountSet("0xaa"), wrapStorage(e))
+	snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, randomAccountSet("0xaa"), wrapStorage(g))
+	snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, randomAccountSet("0xaa"), wrapStorage(h))
+
+	it, _ := snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{})
+	head := snaps.Snapshot(common.HexToHash("0x09"))
+	for it.Next() {
+		hash := it.Hash()
+		want, err := head.Storage(common.HexToHash("0xaa"), hash)
+		if err != nil {
+			t.Fatalf("failed to retrieve expected storage slot: %v", err)
+		}
+		if have := it.Slot(); !bytes.Equal(want, have) {
+			t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want)
+		}
+	}
+	it.Release()
+
+	// Test after persist some bottom-most layers into the disk,
+	// the functionalities still work.
+	limit := aggregatorMemoryLimit
+	defer func() {
+		aggregatorMemoryLimit = limit
+	}()
+	aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk
+	snaps.Cap(common.HexToHash("0x09"), 2)
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x09"), common.HexToHash("0xaa"), common.Hash{})
+	for it.Next() {
+		hash := it.Hash()
+		want, err := head.Storage(common.HexToHash("0xaa"), hash)
+		if err != nil {
+			t.Fatalf("failed to retrieve expected slot: %v", err)
+		}
+		if have := it.Slot(); !bytes.Equal(want, have) {
+			t.Fatalf("hash %x: slot mismatch: have %x, want %x", hash, have, want)
+		}
+	}
+	it.Release()
 }
 
 // This testcase is notorious, all layers contain the exact same 200 accounts.
@@ -285,13 +526,27 @@ func TestAccountIteratorLargeTraversal(t *testing.T) {
 	}
 	// Iterate the entire stack and ensure everything is hit only once
 	head := snaps.Snapshot(common.HexToHash("0x80"))
-	verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{}))
-	verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator())
+	verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{}), verifyNothing)
+	verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount)
 
 	it, _ := snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{})
-	defer it.Release()
+	verifyIterator(t, 200, it, verifyAccount)
+	it.Release()
+
+	// Test after persist some bottom-most layers into the disk,
+	// the functionalities still work.
+	limit := aggregatorMemoryLimit
+	defer func() {
+		aggregatorMemoryLimit = limit
+	}()
+	aggregatorMemoryLimit = 0 // Force pushing the bottom-most layer into disk
+	snaps.Cap(common.HexToHash("0x80"), 2)
 
-	verifyIterator(t, 200, it)
+	verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator(), verifyAccount)
+
+	it, _ = snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{})
+	verifyIterator(t, 200, it, verifyAccount)
+	it.Release()
 }
 
 // TestAccountIteratorFlattening tests what happens when we
@@ -351,40 +606,112 @@ func TestAccountIteratorSeek(t *testing.T) {
 	snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil,
 		randomAccountSet("0xcc", "0xf0", "0xff"), nil)
 
-	// Construct various iterators and ensure their tranversal is correct
+	// Account set is now
+	// 02: aa, ee, f0, ff
+	// 03: aa, bb, dd, ee, f0 (, f0), ff
+	// 04: aa, bb, cc, dd, ee, f0 (, f0), ff (, ff)
+	// Construct various iterators and ensure their traversal is correct
 	it, _ := snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd"))
 	defer it.Release()
-	verifyIterator(t, 3, it) // expected: ee, f0, ff
+	verifyIterator(t, 3, it, verifyAccount) // expected: ee, f0, ff
 
 	it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"))
 	defer it.Release()
-	verifyIterator(t, 3, it) // expected: ee, f0, ff
+	verifyIterator(t, 4, it, verifyAccount) // expected: aa, ee, f0, ff
 
 	it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff"))
 	defer it.Release()
-	verifyIterator(t, 0, it) // expected: nothing
+	verifyIterator(t, 1, it, verifyAccount) // expected: ff
+
+	it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff1"))
+	defer it.Release()
+	verifyIterator(t, 0, it, verifyAccount) // expected: nothing
 
 	it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb"))
 	defer it.Release()
-	verifyIterator(t, 5, it) // expected: cc, dd, ee, f0, ff
+	verifyIterator(t, 6, it, verifyAccount) // expected: bb, cc, dd, ee, f0, ff
 
 	it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef"))
 	defer it.Release()
-	verifyIterator(t, 2, it) // expected: f0, ff
+	verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff
 
 	it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0"))
 	defer it.Release()
-	verifyIterator(t, 1, it) // expected: ff
+	verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff
 
 	it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff"))
 	defer it.Release()
-	verifyIterator(t, 0, it) // expected: nothing
+	verifyIterator(t, 1, it, verifyAccount) // expected: ff
+
+	it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff1"))
+	defer it.Release()
+	verifyIterator(t, 0, it, verifyAccount) // expected: nothing
+}
+
+func TestStorageIteratorSeek(t *testing.T) {
+	// Create a snapshot stack with some initial data
+	base := &diskLayer{
+		diskdb: rawdb.NewMemoryDatabase(),
+		root:   common.HexToHash("0x01"),
+		cache:  fastcache.New(1024 * 500),
+	}
+	snaps := &Tree{
+		layers: map[common.Hash]snapshot{
+			base.root: base,
+		},
+	}
+	// Stack three diff layers on top with various overlaps
+	snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil))
+
+	snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x05", "0x06"}}, nil))
+
+	snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x05", "0x08"}}, nil))
+
+	// Account set is now
+	// 02: 01, 03, 05
+	// 03: 01, 02, 03, 05 (, 05), 06
+	// 04: 01(, 01), 02, 03, 05(, 05, 05), 06, 08
+	// Construct various iterators and ensure their traversal is correct
+	it, _ := snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x01"))
+	defer it.Release()
+	verifyIterator(t, 3, it, verifyStorage) // expected: 01, 03, 05
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x02"))
+	defer it.Release()
+	verifyIterator(t, 2, it, verifyStorage) // expected: 03, 05
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x5"))
+	defer it.Release()
+	verifyIterator(t, 1, it, verifyStorage) // expected: 05
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x02"), common.HexToHash("0xaa"), common.HexToHash("0x6"))
+	defer it.Release()
+	verifyIterator(t, 0, it, verifyStorage) // expected: nothing
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x01"))
+	defer it.Release()
+	verifyIterator(t, 6, it, verifyStorage) // expected: 01, 02, 03, 05, 06, 08
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x05"))
+	defer it.Release()
+	verifyIterator(t, 3, it, verifyStorage) // expected: 05, 06, 08
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x08"))
+	defer it.Release()
+	verifyIterator(t, 1, it, verifyStorage) // expected: 08
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.HexToHash("0x09"))
+	defer it.Release()
+	verifyIterator(t, 0, it, verifyStorage) // expected: nothing
 }
 
-// TestIteratorDeletions tests that the iterator behaves correct when there are
+// TestAccountIteratorDeletions tests that the iterator behaves correct when there are
 // deleted accounts (where the Account() value is nil). The iterator
 // should not output any accounts or nil-values for those cases.
-func TestIteratorDeletions(t *testing.T) {
+func TestAccountIteratorDeletions(t *testing.T) {
 	// Create an empty base layer and a snapshot tree out of it
 	base := &diskLayer{
 		diskdb: rawdb.NewMemoryDatabase(),
@@ -402,7 +729,7 @@ func TestIteratorDeletions(t *testing.T) {
 
 	deleted := common.HexToHash("0x22")
 	destructed := map[common.Hash]struct{}{
-		deleted: struct{}{},
+		deleted: {},
 	}
 	snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"),
 		destructed, randomAccountSet("0x11", "0x33"), nil)
@@ -413,7 +740,7 @@ func TestIteratorDeletions(t *testing.T) {
 	// The output should be 11,33,44,55
 	it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{})
 	// Do a quick check
-	verifyIterator(t, 4, it)
+	verifyIterator(t, 4, it, verifyAccount)
 	it.Release()
 
 	// And a more detailed verification that we indeed do not see '0x22'
@@ -430,6 +757,63 @@ func TestIteratorDeletions(t *testing.T) {
 	}
 }
 
+func TestStorageIteratorDeletions(t *testing.T) {
+	// Create an empty base layer and a snapshot tree out of it
+	base := &diskLayer{
+		diskdb: rawdb.NewMemoryDatabase(),
+		root:   common.HexToHash("0x01"),
+		cache:  fastcache.New(1024 * 500),
+	}
+	snaps := &Tree{
+		layers: map[common.Hash]snapshot{
+			base.root: base,
+		},
+	}
+	// Stack three diff layers on top with various overlaps
+	snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x03", "0x05"}}, nil))
+
+	snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x02", "0x04", "0x06"}}, [][]string{{"0x01", "0x03"}}))
+
+	// The output should be 02,04,05,06
+	it, _ := snaps.StorageIterator(common.HexToHash("0x03"), common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 4, it, verifyStorage)
+	it.Release()
+
+	// The output should be 04,05,06
+	it, _ = snaps.StorageIterator(common.HexToHash("0x03"), common.HexToHash("0xaa"), common.HexToHash("0x03"))
+	verifyIterator(t, 3, it, verifyStorage)
+	it.Release()
+
+	// Destruct the whole storage
+	destructed := map[common.Hash]struct{}{
+		common.HexToHash("0xaa"): {},
+	}
+	snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), destructed, nil, nil)
+
+	it, _ = snaps.StorageIterator(common.HexToHash("0x04"), common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 0, it, verifyStorage)
+	it.Release()
+
+	// Re-insert the slots of the same account
+	snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil,
+		randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x07", "0x08", "0x09"}}, nil))
+
+	// The output should be 07,08,09
+	it, _ = snaps.StorageIterator(common.HexToHash("0x05"), common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 3, it, verifyStorage)
+	it.Release()
+
+	// Destruct the whole storage but re-create the account in the same layer
+	snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), destructed, randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x11", "0x12"}}, nil))
+	it, _ = snaps.StorageIterator(common.HexToHash("0x06"), common.HexToHash("0xaa"), common.Hash{})
+	verifyIterator(t, 2, it, verifyStorage) // The output should be 11,12
+	it.Release()
+
+	verifyIterator(t, 2, snaps.Snapshot(common.HexToHash("0x06")).(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa")), verifyStorage)
+}
+
 // BenchmarkAccountIteratorTraversal is a bit a bit notorious -- all layers contain the
 // exact same 200 accounts. That means that we need to process 2000 items, but
 // only spit out 200 values eventually.
diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go
index 66c7aee0ac..5cfb9a9f2a 100644
--- a/core/state/snapshot/journal.go
+++ b/core/state/snapshot/journal.go
@@ -33,9 +33,14 @@ import (
 	"github.com/ethereum/go-ethereum/trie"
 )
 
+const journalVersion uint64 = 0
+
 // journalGenerator is a disk layer entry containing the generator progress marker.
 type journalGenerator struct {
-	Wiping   bool // Whether the database was in progress of being wiped
+	// Indicator that whether the database was in progress of being wiped.
+	// It's deprecated but keep it here for background compatibility.
+	Wiping bool
+
 	Done     bool // Whether the generator finished creating the snapshot
 	Marker   []byte
 	Accounts uint64
@@ -61,13 +66,77 @@ type journalStorage struct {
 	Vals [][]byte
 }
 
+// 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.
-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, bool, error) {
+	// If snapshotting is disabled (initial sync in progress), don't do anything,
+	// wait for the chain to permit us to do something meaningful
+	if rawdb.ReadSnapshotDisabled(diskdb) {
+		return nil, true, nil
+	}
 	// Retrieve the block number and hash of the snapshot, failing if no snapshot
 	// is present in the database (or crashed mid-update).
 	baseRoot := rawdb.ReadSnapshotRoot(diskdb)
 	if baseRoot == (common.Hash{}) {
-		return nil, errors.New("missing or corrupted snapshot")
+		return nil, false, errors.New("missing or corrupted snapshot")
 	}
 	base := &diskLayer{
 		diskdb: diskdb,
@@ -75,39 +144,34 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
 		cache:  fastcache.New(cache * 1024 * 1024),
 		root:   baseRoot,
 	}
-	// Retrieve the 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(diskdb)
-	if len(journal) == 0 {
-		return nil, 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, fmt.Errorf("failed to load snapshot progress marker: %v", err)
-	}
-	// Load all the snapshot diffs from the journal
-	snapshot, err := loadDiffLayer(base, r)
+	snapshot, generator, err := loadAndParseJournal(diskdb, base)
 	if err != nil {
-		return nil, err
+		log.Warn("Failed to load new-format journal", "error", err)
+		return nil, false, err
 	}
-	// Entire snapshot journal loaded, sanity check the head and return
-	// Journal doesn't exist, don't worry if it's not supposed to
+	// Entire snapshot journal loaded, sanity check the head. If the loaded
+	// 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 {
-		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 !recovery {
+			return nil, false, 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
 	if !generator.Done {
-		// If the generator was still wiping, restart one from scratch (fine for
-		// now as it's rare and the wiper deletes the stuff it touches anyway, so
-		// restarting won't incur a lot of extra database hops.
-		var wiper chan struct{}
-		if generator.Wiping {
-			log.Info("Resuming previous snapshot wipe")
-			wiper = wipeSnapshot(diskdb, false)
-		}
 		// Whether or not wiping was in progress, load any generator progress too
 		base.genMarker = generator.Marker
 		if base.genMarker == nil {
@@ -121,7 +185,6 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
 			origin = binary.BigEndian.Uint64(generator.Marker)
 		}
 		go base.generate(&generatorStats{
-			wiping:   wiper,
 			origin:   origin,
 			start:    time.Now(),
 			accounts: generator.Accounts,
@@ -129,7 +192,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int,
 			storage:  common.StorageSize(generator.Storage),
 		})
 	}
-	return snapshot, nil
+	return snapshot, false, nil
 }
 
 // loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new
@@ -158,7 +221,11 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
 	}
 	accountData := make(map[common.Hash][]byte)
 	for _, entry := range accounts {
-		accountData[entry.Hash] = entry.Blob
+		if len(entry.Blob) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
+			accountData[entry.Hash] = entry.Blob
+		} else {
+			accountData[entry.Hash] = nil
+		}
 	}
 	var storage []journalStorage
 	if err := r.Decode(&storage); err != nil {
@@ -168,15 +235,19 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) {
 	for _, entry := range storage {
 		slots := make(map[common.Hash][]byte)
 		for i, key := range entry.Keys {
-			slots[key] = entry.Vals[i]
+			if len(entry.Vals[i]) > 0 { // RLP loses nil-ness, but `[]byte{}` is not a valid item, so reinterpret that
+				slots[key] = entry.Vals[i]
+			} else {
+				slots[key] = nil
+			}
 		}
 		storageData[entry.Hash] = slots
 	}
 	return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r)
 }
 
-// Journal writes the persistent layer generator stats into a buffer to be stored
-// in the database as the snapshot journal.
+// Journal terminates any in-progress snapshot generation, also implicitly pushing
+// the progress into the database.
 func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
 	// If the snapshot is currently being generated, abort it
 	var stats *generatorStats
@@ -185,7 +256,7 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
 		dl.genAbort <- abort
 
 		if stats = <-abort; stats != nil {
-			stats.Log("Journalling in-progress snapshot", dl.genMarker)
+			stats.Log("Journalling in-progress snapshot", dl.root, dl.genMarker)
 		}
 	}
 	// Ensure the layer didn't get stale
@@ -195,20 +266,10 @@ func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
 	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)
-	}
-	if err := rlp.Encode(buffer, entry); err != nil {
-		return common.Hash{}, err
-	}
+	// Ensure the generator stats is written even if none was ran this cycle
+	journalProgress(dl.diskdb, dl.genMarker, stats)
+
+	log.Debug("Journalled disk layer", "root", dl.root)
 	return dl.root, nil
 }
 
@@ -258,5 +319,6 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) {
 	if err := rlp.Encode(buffer, storage); err != nil {
 		return common.Hash{}, err
 	}
+	log.Debug("Journalled diff layer", "root", dl.root, "parent", dl.parent.Root())
 	return base, nil
 }
diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go
index 27a8c7f0bb..3ed534461b 100644
--- a/core/state/snapshot/snapshot.go
+++ b/core/state/snapshot/snapshot.go
@@ -29,6 +29,7 @@ import (
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/trie"
 )
 
@@ -86,6 +87,10 @@ var (
 	// range of accounts covered.
 	ErrNotCoveredYet = errors.New("not covered yet")
 
+	// ErrNotConstructed is returned if the callers want to iterate the snapshot
+	// while the generation is not finished yet.
+	ErrNotConstructed = errors.New("snapshot is not constructed")
+
 	// errSnapshotCycle is returned if a snapshot is attempted to be inserted
 	// that forms a cycle in the snapshot tree.
 	errSnapshotCycle = errors.New("snapshot cycle")
@@ -138,13 +143,16 @@ type snapshot interface {
 
 	// AccountIterator creates an account iterator over an arbitrary layer.
 	AccountIterator(seek common.Hash) AccountIterator
+
+	// StorageIterator creates a storage iterator over an arbitrary layer.
+	StorageIterator(account common.Hash, seek common.Hash) (StorageIterator, bool)
 }
 
-// SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent
-// base layer backed by a key-value store, on top of which arbitrarily many in-
-// memory diff layers are topped. The memory diffs can form a tree with branching,
-// but the disk layer is singleton and common to all. If a reorg goes deeper than
-// the disk layer, everything needs to be deleted.
+// Tree is an Ethereum state snapshot tree. It consists of one persistent base
+// layer backed by a key-value store, on top of which arbitrarily many in-memory
+// diff layers are topped. The memory diffs can form a tree with branching, but
+// the disk layer is singleton and common to all. If a reorg goes deeper than the
+// disk layer, everything needs to be deleted.
 //
 // The goal of a state snapshot is twofold: to allow direct access to account and
 // storage data to avoid expensive multi-level trie lookups; and to allow sorted,
@@ -161,10 +169,12 @@ type Tree struct {
 // store (with a number of memory layers from a journal), ensuring that the head
 // of the snapshot matches the expected one.
 //
-// If the snapshot is missing or inconsistent, the entirety is deleted and will
-// be reconstructed from scratch based on the tries in the key-value store, on a
-// background thread.
-func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool) *Tree {
+// If the snapshot is missing or the disk layer is broken, the entire is deleted
+// and will be reconstructed from scratch based on the tries in the key-value
+// store, on a background thread. If the memory layers from the journal is not
+// 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, rebuild bool, recovery bool) (*Tree, error) {
 	// Create a new, empty snapshot tree
 	snap := &Tree{
 		diskdb: diskdb,
@@ -176,22 +186,30 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm
 		defer snap.waitBuild()
 	}
 	// Attempt to load a previously persisted snapshot and rebuild one if failed
-	head, err := loadSnapshot(diskdb, triedb, cache, root)
+	head, disabled, err := loadSnapshot(diskdb, triedb, cache, root, recovery)
+	if disabled {
+		log.Warn("Snapshot maintenance disabled (syncing)")
+		return snap, nil
+	}
 	if err != nil {
-		log.Warn("Failed to load snapshot, regenerating", "err", err)
-		snap.Rebuild(root)
-		return snap
+		if rebuild {
+			log.Warn("Failed to load snapshot, regenerating", "err", err)
+			snap.Rebuild(root)
+			return snap, nil
+		}
+		return nil, err // Bail out the error, don't rebuild automatically.
 	}
 	// Existing snapshot loaded, seed all the layers
 	for head != nil {
 		snap.layers[head.Root()] = head
 		head = head.Parent()
 	}
-	return snap
+	log.Info("Snapshot loaded", "diskRoot", snap.diskRoot(), "root", root)
+	return snap, nil
 }
 
 // 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() {
 	// Find the rebuild termination channel
 	var done chan struct{}
@@ -211,6 +229,55 @@ func (t *Tree) waitBuild() {
 	}
 }
 
+// Disable interrupts any pending snapshot generator, deletes all the snapshot
+// layers in memory and marks snapshots disabled globally. In order to resume
+// the snapshot functionality, the caller must invoke Rebuild.
+func (t *Tree) Disable() {
+	// Interrupt any live snapshot layers
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	for _, layer := range t.layers {
+		switch layer := layer.(type) {
+		case *diskLayer:
+			// If the base layer is generating, abort it
+			if layer.genAbort != nil {
+				abort := make(chan *generatorStats)
+				layer.genAbort <- abort
+				<-abort
+			}
+			// Layer should be inactive now, mark it as stale
+			layer.lock.Lock()
+			layer.stale = true
+			layer.lock.Unlock()
+
+		case *diffLayer:
+			// If the layer is a simple diff, simply mark as stale
+			layer.lock.Lock()
+			atomic.StoreUint32(&layer.stale, 1)
+			layer.lock.Unlock()
+
+		default:
+			panic(fmt.Sprintf("unknown layer type: %T", layer))
+		}
+	}
+	t.layers = map[common.Hash]snapshot{}
+
+	// Delete all snapshot liveness information from the database
+	batch := t.diskdb.NewBatch()
+
+	rawdb.WriteSnapshotDisabled(batch)
+	rawdb.DeleteSnapshotRoot(batch)
+	rawdb.DeleteSnapshotJournal(batch)
+	rawdb.DeleteSnapshotGenerator(batch)
+	rawdb.DeleteSnapshotRecoveryNumber(batch)
+	// Note, we don't delete the sync progress
+
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to disable snapshots", "err", err)
+	}
+}
+
 // Snapshot retrieves a snapshot belonging to the given block root, or nil if no
 // snapshot is maintained for that block.
 func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot {
@@ -220,6 +287,39 @@ func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot {
 	return t.layers[blockRoot]
 }
 
+// Snapshots returns all visited layers from the topmost layer with specific
+// root and traverses downward. The layer amount is limited by the given number.
+// If nodisk is set, then disk layer is excluded.
+func (t *Tree) Snapshots(root common.Hash, limits int, nodisk bool) []Snapshot {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	if limits == 0 {
+		return nil
+	}
+	layer := t.layers[root]
+	if layer == nil {
+		return nil
+	}
+	var ret []Snapshot
+	for {
+		if _, isdisk := layer.(*diskLayer); isdisk && nodisk {
+			break
+		}
+		ret = append(ret, layer)
+		limits -= 1
+		if limits == 0 {
+			break
+		}
+		parent := layer.Parent()
+		if parent == nil {
+			break
+		}
+		layer = parent
+	}
+	return ret
+}
+
 // Update adds a new snapshot into the tree, if that can be linked to an existing
 // old parent. It is disallowed to insert a disk layer (the origin of all).
 func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error {
@@ -233,23 +333,30 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs m
 		return errSnapshotCycle
 	}
 	// Generate a new snapshot on top of the parent
-	parent := t.Snapshot(parentRoot).(snapshot)
+	parent := t.Snapshot(parentRoot)
 	if parent == nil {
 		return fmt.Errorf("parent [%#x] snapshot missing", parentRoot)
 	}
-	snap := parent.Update(blockRoot, destructs, accounts, storage)
+	snap := parent.(snapshot).Update(blockRoot, destructs, accounts, storage)
 
 	// Save the new snapshot for later
 	t.lock.Lock()
 	defer t.lock.Unlock()
 
 	t.layers[snap.root] = snap
+	log.Debug("Snapshot updated", "blockRoot", blockRoot)
 	return nil
 }
 
 // Cap traverses downwards the snapshot tree from a head block hash until the
 // number of allowed layers are crossed. All layers beyond the permitted number
 // are flattened downwards.
+//
+// Note, the final diff layer count in general will be one more than the amount
+// requested. This happens because the bottom-most diff layer is the accumulator
+// which may or may not overflow and cascade to disk. Since this last layer's
+// survival is only known *after* capping, we need to omit it from the count if
+// we want to ensure that *at least* the requested number of diff layers remain.
 func (t *Tree) Cap(root common.Hash, layers int) error {
 	// Retrieve the head snapshot to cap from
 	snap := t.Snapshot(root)
@@ -260,6 +367,13 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
 	if !ok {
 		return fmt.Errorf("snapshot [%#x] is disk layer", root)
 	}
+	// If the generator is still running, use a more aggressive cap
+	diff.origin.lock.RLock()
+	if diff.origin.genMarker != nil && layers > 8 {
+		layers = 8
+	}
+	diff.origin.lock.RUnlock()
+
 	// Run the internal capping and discard all stale layers
 	t.lock.Lock()
 	defer t.lock.Unlock()
@@ -267,10 +381,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
 	// Flattening the bottom-most diff layer requires special casing since there's
 	// no child to rewire to the grandparent. In that case we can fake a temporary
 	// child for the capping and then remove it.
-	var persisted *diskLayer
-
-	switch layers {
-	case 0:
+	if layers == 0 {
 		// If full commit was requested, flatten the diffs and merge onto disk
 		diff.lock.RLock()
 		base := diffToDisk(diff.flatten().(*diffLayer))
@@ -279,33 +390,9 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
 		// Replace the entire snapshot tree with the flat base
 		t.layers = map[common.Hash]snapshot{base.root: base}
 		return nil
-
-	case 1:
-		// If full flattening was requested, flatten the diffs but only merge if the
-		// memory limit was reached
-		var (
-			bottom *diffLayer
-			base   *diskLayer
-		)
-		diff.lock.RLock()
-		bottom = diff.flatten().(*diffLayer)
-		if bottom.memory >= aggregatorMemoryLimit {
-			base = diffToDisk(bottom)
-		}
-		diff.lock.RUnlock()
-
-		// If all diff layers were removed, replace the entire snapshot tree
-		if base != nil {
-			t.layers = map[common.Hash]snapshot{base.root: base}
-			return nil
-		}
-		// Merge the new aggregated layer into the snapshot tree, clean stales below
-		t.layers[bottom.root] = bottom
-
-	default:
-		// Many layers requested to be retained, cap normally
-		persisted = t.cap(diff, layers)
 	}
+	persisted := t.cap(diff, layers)
+
 	// Remove any layer that is stale or links into a stale layer
 	children := make(map[common.Hash][]common.Hash)
 	for root, snap := range t.layers {
@@ -327,7 +414,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
 			remove(root)
 		}
 	}
-	// If the disk layer was modified, regenerate all the cummulative blooms
+	// If the disk layer was modified, regenerate all the cumulative blooms
 	if persisted != nil {
 		var rebloom func(root common.Hash)
 		rebloom = func(root common.Hash) {
@@ -340,6 +427,7 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
 		}
 		rebloom(persisted.root)
 	}
+	log.Debug("Snapshot capped", "root", root)
 	return nil
 }
 
@@ -347,10 +435,16 @@ func (t *Tree) Cap(root common.Hash, layers int) error {
 // crossed. All diffs beyond the permitted number are flattened downwards. If the
 // layer limit is reached, memory cap is also enforced (but not before).
 //
-// The method returns the new disk layer if diffs were persistend into it.
+// The method returns the new disk layer if diffs were persisted into it.
+//
+// Note, the final diff layer count in general will be one more than the amount
+// requested. This happens because the bottom-most diff layer is the accumulator
+// which may or may not overflow and cascade to disk. Since this last layer's
+// survival is only known *after* capping, we need to omit it from the count if
+// we want to ensure that *at least* the requested number of diff layers remain.
 func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer {
 	// Dive until we run out of layers or reach the persistent database
-	for ; layers > 2; layers-- {
+	for i := 0; i < layers-1; i++ {
 		// If we still have diff layers below, continue down
 		if parent, ok := diff.parent.(*diffLayer); ok {
 			diff = parent
@@ -401,6 +495,9 @@ func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer {
 
 // 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.
+//
+// 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 {
 	var (
 		base  = bottom.parent.(*diskLayer)
@@ -413,8 +510,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
 		base.genAbort <- abort
 		stats = <-abort
 	}
-	// Start by temporarily deleting the current snapshot block marker. This
-	// ensures that in the case of a crash, the entire snapshot is invalidated.
+	// Put the deletion in the batch writer, flush all updates in the final step.
 	rawdb.DeleteSnapshotRoot(batch)
 
 	// Mark the original base as stale as we're going to create a new wrapper
@@ -440,8 +536,17 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
 			if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator
 				batch.Delete(key)
 				base.cache.Del(key[1:])
-
 				snapshotFlushStorageItemMeter.Mark(1)
+
+				// Ensure we don't delete too much data blindly (contract can be
+				// huge). It's ok to flush, the root will go missing in case of a
+				// crash and we'll detect and regenerate the snapshot.
+				if batch.ValueSize() > ethdb.IdealBatchSize {
+					if err := batch.Write(); err != nil {
+						log.Crit("Failed to write storage deletions", "err", err)
+					}
+					batch.Reset()
+				}
 			}
 		}
 		it.Release()
@@ -457,14 +562,18 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
 		base.cache.Set(hash[:], data)
 		snapshotCleanAccountWriteMeter.Mark(int64(len(data)))
 
+		snapshotFlushAccountItemMeter.Mark(1)
+		snapshotFlushAccountSizeMeter.Mark(int64(len(data)))
+
+		// Ensure we don't write too much data blindly. It's ok to flush, the
+		// root will go missing in case of a crash and we'll detect and regen
+		// the snapshot.
 		if batch.ValueSize() > ethdb.IdealBatchSize {
 			if err := batch.Write(); err != nil {
-				log.Crit("Failed to write account snapshot", "err", err)
+				log.Crit("Failed to write storage deletions", "err", err)
 			}
 			batch.Reset()
 		}
-		snapshotFlushAccountItemMeter.Mark(1)
-		snapshotFlushAccountSizeMeter.Mark(int64(len(data)))
 	}
 	// Push all the storage slots into the database
 	for accountHash, storage := range bottom.storageData {
@@ -491,18 +600,19 @@ func diffToDisk(bottom *diffLayer) *diskLayer {
 			snapshotFlushStorageItemMeter.Mark(1)
 			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
 	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 {
 		log.Crit("Failed to write leftover snapshot", "err", err)
 	}
+	log.Debug("Journalled disk layer", "root", bottom.root, "complete", base.genMarker == nil)
 	res := &diskLayer{
 		root:       bottom.root,
 		cache:      base.cache,
@@ -540,7 +650,21 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) {
 	t.lock.Lock()
 	defer t.lock.Unlock()
 
+	// Firstly write out the metadata of journal
 	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)
 	if err != nil {
 		return common.Hash{}, err
@@ -557,8 +681,10 @@ func (t *Tree) Rebuild(root common.Hash) {
 	t.lock.Lock()
 	defer t.lock.Unlock()
 
-	// Track whether there's a wipe currently running and keep it alive if so
-	var wiper chan struct{}
+	// Firstly delete any recovery flag in the database. Because now we are
+	// building a brand new snapshot. Also reenable the snapshot feature.
+	rawdb.DeleteSnapshotRecoveryNumber(t.diskdb)
+	rawdb.DeleteSnapshotDisabled(t.diskdb)
 
 	// Iterate over and mark all layers stale
 	for _, layer := range t.layers {
@@ -568,10 +694,7 @@ func (t *Tree) Rebuild(root common.Hash) {
 			if layer.genAbort != nil {
 				abort := make(chan *generatorStats)
 				layer.genAbort <- abort
-
-				if stats := <-abort; stats != nil {
-					wiper = stats.wiping
-				}
+				<-abort
 			}
 			// Layer should be inactive now, mark it as stale
 			layer.lock.Lock()
@@ -588,16 +711,122 @@ func (t *Tree) Rebuild(root common.Hash) {
 			panic(fmt.Sprintf("unknown layer type: %T", layer))
 		}
 	}
-	// Start generating a new snapshot from scratch on a backgroung thread. The
+	// Start generating a new snapshot from scratch on a background thread. The
 	// generator will run a wiper first if there's not one running right now.
 	log.Info("Rebuilding state snapshot")
 	t.layers = map[common.Hash]snapshot{
-		root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper),
+		root: generateSnapshot(t.diskdb, t.triedb, t.cache, root),
 	}
 }
 
 // AccountIterator creates a new account iterator for the specified root hash and
 // seeks to a starting account hash.
 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)
 }
+
+// StorageIterator creates a new storage iterator for the specified root hash and
+// 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) {
+	ok, err := t.generating()
+	if err != nil {
+		return nil, err
+	}
+	if ok {
+		return nil, ErrNotConstructed
+	}
+	return newFastStorageIterator(t, root, account, seek)
+}
+
+// Verify iterates the whole state(all the accounts as well as the corresponding storages)
+// with the specific root and compares the re-computed hash with the original one.
+func (t *Tree) Verify(root common.Hash) error {
+	acctIt, err := t.AccountIterator(root, common.Hash{})
+	if err != nil {
+		return err
+	}
+	defer acctIt.Release()
+
+	got, err := generateTrieRoot(nil, acctIt, common.Hash{}, stackTrieGenerate, func(db ethdb.KeyValueWriter, accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) {
+		storageIt, err := t.StorageIterator(root, accountHash, common.Hash{})
+		if err != nil {
+			return common.Hash{}, err
+		}
+		defer storageIt.Release()
+
+		hash, err := generateTrieRoot(nil, storageIt, accountHash, stackTrieGenerate, nil, stat, false)
+		if err != nil {
+			return common.Hash{}, err
+		}
+		return hash, nil
+	}, newGenerateStats(), true)
+
+	if err != nil {
+		return err
+	}
+	if got != root {
+		return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root)
+	}
+	return nil
+}
+
+// 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()
+}
diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go
index 9109238412..4b787cfe2e 100644
--- a/core/state/snapshot/snapshot_test.go
+++ b/core/state/snapshot/snapshot_test.go
@@ -17,6 +17,7 @@
 package snapshot
 
 import (
+	"encoding/binary"
 	"fmt"
 	"math/big"
 	"math/rand"
@@ -60,6 +61,29 @@ func randomAccountSet(hashes ...string) map[common.Hash][]byte {
 	return accounts
 }
 
+// randomStorageSet generates a set of random slots with the given strings as
+// the slot addresses.
+func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]string) map[common.Hash]map[common.Hash][]byte {
+	storages := make(map[common.Hash]map[common.Hash][]byte)
+	for index, account := range accounts {
+		storages[common.HexToHash(account)] = make(map[common.Hash][]byte)
+
+		if index < len(hashes) {
+			hashes := hashes[index]
+			for _, hash := range hashes {
+				storages[common.HexToHash(account)][common.HexToHash(hash)] = randomHash().Bytes()
+			}
+		}
+		if index < len(nilStorage) {
+			nils := nilStorage[index]
+			for _, hash := range nils {
+				storages[common.HexToHash(account)][common.HexToHash(hash)] = nil
+			}
+		}
+	}
+	return storages
+}
+
 // Tests that if a disk layer becomes stale, no active external references will
 // be returned with junk data. This version of the test flattens every diff layer
 // to check internal corner case around the bottom-most memory accumulator.
@@ -138,57 +162,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) {
 	defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit)
 	aggregatorMemoryLimit = 0
 
-	if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil {
-		t.Fatalf("failed to merge diff layer onto disk: %v", err)
-	}
-	// Since the base layer was modified, ensure that data retrievald on the external reference fail
-	if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
-		t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
-	}
-	if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale {
-		t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err)
-	}
-	if n := len(snaps.layers); n != 2 {
-		t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2)
-		fmt.Println(snaps.layers)
-	}
-}
-
-// Tests that if a diff layer becomes stale, no active external references will
-// be returned with junk data. This version of the test flattens every diff layer
-// to check internal corner case around the bottom-most memory accumulator.
-func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) {
-	// Create an empty base layer and a snapshot tree out of it
-	base := &diskLayer{
-		diskdb: rawdb.NewMemoryDatabase(),
-		root:   common.HexToHash("0x01"),
-		cache:  fastcache.New(1024 * 500),
-	}
-	snaps := &Tree{
-		layers: map[common.Hash]snapshot{
-			base.root: base,
-		},
-	}
-	// Commit two diffs on top and retrieve a reference to the bottommost
-	accounts := map[common.Hash][]byte{
-		common.HexToHash("0xa1"): randomAccount(),
-	}
-	if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil {
-		t.Fatalf("failed to create a diff layer: %v", err)
-	}
-	if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil {
-		t.Fatalf("failed to create a diff layer: %v", err)
-	}
-	if n := len(snaps.layers); n != 3 {
-		t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3)
-	}
-	ref := snaps.Snapshot(common.HexToHash("0x02"))
-
-	// Flatten the diff layer into the bottom accumulator
 	if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil {
-		t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
+		t.Fatalf("failed to merge accumulator onto disk: %v", err)
 	}
-	// Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail
+	// Since the base layer was modified, ensure that data retrievald on the external reference fail
 	if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale {
 		t.Errorf("stale reference returned account: %#x (err: %v)", acc, err)
 	}
@@ -243,7 +220,7 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) {
 		t.Errorf("layers modified, got %d exp %d", got, exp)
 	}
 	// Flatten the diff layer into the bottom accumulator
-	if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil {
+	if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil {
 		t.Fatalf("failed to flatten diff layer into accumulator: %v", err)
 	}
 	// Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail
@@ -346,3 +323,103 @@ func TestPostCapBasicDataAccess(t *testing.T) {
 		t.Error("expected error capping the disk layer, got none")
 	}
 }
+
+// TestSnaphots tests the functionality for retrieveing the snapshot
+// with given head root and the desired depth.
+func TestSnaphots(t *testing.T) {
+	// setAccount is a helper to construct a random account entry and assign it to
+	// an account slot in a snapshot
+	setAccount := func(accKey string) map[common.Hash][]byte {
+		return map[common.Hash][]byte{
+			common.HexToHash(accKey): randomAccount(),
+		}
+	}
+	makeRoot := func(height uint64) common.Hash {
+		var buffer [8]byte
+		binary.BigEndian.PutUint64(buffer[:], height)
+		return common.BytesToHash(buffer[:])
+	}
+	// Create a starting base layer and a snapshot tree out of it
+	base := &diskLayer{
+		diskdb: rawdb.NewMemoryDatabase(),
+		root:   makeRoot(1),
+		cache:  fastcache.New(1024 * 500),
+	}
+	snaps := &Tree{
+		layers: map[common.Hash]snapshot{
+			base.root: base,
+		},
+	}
+	// Construct the snapshots with 129 layers, flattening whatever's above that
+	var (
+		last = common.HexToHash("0x01")
+		head common.Hash
+	)
+	for i := 0; i < 129; i++ {
+		head = makeRoot(uint64(i + 2))
+		snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil)
+		last = head
+		snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk)
+	}
+	var cases = []struct {
+		headRoot     common.Hash
+		limit        int
+		nodisk       bool
+		expected     int
+		expectBottom common.Hash
+	}{
+		{head, 0, false, 0, common.Hash{}},
+		{head, 64, false, 64, makeRoot(129 + 2 - 64)},
+		{head, 128, false, 128, makeRoot(3)}, // Normal diff layers, no accumulator
+		{head, 129, true, 129, makeRoot(2)},  // All diff layers, including accumulator
+		{head, 130, false, 130, makeRoot(1)}, // All diff layers + disk layer
+	}
+	for i, c := range cases {
+		layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk)
+		if len(layers) != c.expected {
+			t.Errorf("non-overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers))
+		}
+		if len(layers) == 0 {
+			continue
+		}
+		bottommost := layers[len(layers)-1]
+		if bottommost.Root() != c.expectBottom {
+			t.Errorf("non-overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root())
+		}
+	}
+	// Above we've tested the normal capping, which leaves the accumulator live.
+	// Test that if the bottommost accumulator diff layer overflows the allowed
+	// memory limit, the snapshot tree gets capped to one less layer.
+	// Commit the diff layer onto the disk and ensure it's persisted
+	defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit)
+	aggregatorMemoryLimit = 0
+
+	snaps.Cap(head, 128) // 129 (128 diffs + 1 overflown accumulator + 1 disk)
+
+	cases = []struct {
+		headRoot     common.Hash
+		limit        int
+		nodisk       bool
+		expected     int
+		expectBottom common.Hash
+	}{
+		{head, 0, false, 0, common.Hash{}},
+		{head, 64, false, 64, makeRoot(129 + 2 - 64)},
+		{head, 128, false, 128, makeRoot(3)}, // All diff layers, accumulator was flattened
+		{head, 129, true, 128, makeRoot(3)},  // All diff layers, accumulator was flattened
+		{head, 130, false, 129, makeRoot(2)}, // All diff layers + disk layer
+	}
+	for i, c := range cases {
+		layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk)
+		if len(layers) != c.expected {
+			t.Errorf("overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers))
+		}
+		if len(layers) == 0 {
+			continue
+		}
+		bottommost := layers[len(layers)-1]
+		if bottommost.Root() != c.expectBottom {
+			t.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root())
+		}
+	}
+}
diff --git a/core/state/snapshot/wipe.go b/core/state/snapshot/wipe.go
index 53eb18a2d1..2cab57393b 100644
--- a/core/state/snapshot/wipe.go
+++ b/core/state/snapshot/wipe.go
@@ -24,10 +24,11 @@ import (
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
 )
 
 // wipeSnapshot starts a goroutine to iterate over the entire key-value database
-// and delete all the  data associated with the snapshot (accounts, storage,
+// and delete all the data associated with the snapshot (accounts, storage,
 // metadata). After all is done, the snapshot range of the database is compacted
 // to free up unused data blocks.
 func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} {
@@ -53,10 +54,10 @@ func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} {
 // removed in sync to avoid data races. After all is done, the snapshot range of
 // the database is compacted to free up unused data blocks.
 func wipeContent(db ethdb.KeyValueStore) error {
-	if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, len(rawdb.SnapshotAccountPrefix)+common.HashLength); err != nil {
+	if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, nil, nil, len(rawdb.SnapshotAccountPrefix)+common.HashLength, snapWipedAccountMeter, true); err != nil {
 		return err
 	}
-	if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength); err != nil {
+	if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, nil, nil, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength, snapWipedStorageMeter, true); err != nil {
 		return err
 	}
 	// Compact the snapshot section of the database to get rid of unused space
@@ -82,8 +83,11 @@ func wipeContent(db ethdb.KeyValueStore) error {
 }
 
 // wipeKeyRange deletes a range of keys from the database starting with prefix
-// and having a specific total key length.
-func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int) error {
+// and having a specific total key length. The start and limit is optional for
+// specifying a particular key range for deletion.
+//
+// Origin is included for wiping and limit is excluded if they are specified.
+func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, origin []byte, limit []byte, keylen int, meter metrics.Meter, report bool) error {
 	// Batch deletions together to avoid holding an iterator for too long
 	var (
 		batch = db.NewBatch()
@@ -92,9 +96,13 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
 	// Iterate over the key-range and delete all of them
 	start, logged := time.Now(), time.Now()
 
-	it := db.NewIterator(prefix, nil)
+	it := db.NewIterator(prefix, origin)
+	var stop []byte
+	if limit != nil {
+		stop = append(prefix, limit...)
+	}
 	for it.Next() {
-		// Skip any keys with the correct prefix but wrong lenth (trie nodes)
+		// Skip any keys with the correct prefix but wrong length (trie nodes)
 		key := it.Key()
 		if !bytes.HasPrefix(key, prefix) {
 			break
@@ -102,6 +110,9 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
 		if len(key) != keylen {
 			continue
 		}
+		if stop != nil && bytes.Compare(key, stop) >= 0 {
+			break
+		}
 		// Delete the key and periodically recreate the batch and iterator
 		batch.Delete(key)
 		items++
@@ -116,7 +127,7 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
 			seekPos := key[len(prefix):]
 			it = db.NewIterator(prefix, seekPos)
 
-			if time.Since(logged) > 8*time.Second {
+			if time.Since(logged) > 8*time.Second && report {
 				log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
 				logged = time.Now()
 			}
@@ -126,6 +137,11 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int
 	if err := batch.Write(); err != nil {
 		return err
 	}
-	log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
+	if meter != nil {
+		meter.Mark(int64(items))
+	}
+	if report {
+		log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start)))
+	}
 	return nil
 }
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 0833f2b0a8..f93f47d5f5 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -157,11 +157,20 @@ func (s *stateObject) touch() {
 
 func (s *stateObject) getTrie(db Database) Trie {
 	if s.trie == nil {
-		var err error
-		s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
-		if err != nil {
-			s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{})
-			s.setError(fmt.Errorf("can't create storage trie: %v", err))
+		// Try fetching from prefetcher first
+		// We don't prefetch empty tries
+		if s.data.Root != emptyRoot && s.db.prefetcher != nil {
+			// When the miner is creating the pending state, there is no
+			// prefetcher
+			s.trie = s.db.prefetcher.trie(s.data.Root)
+		}
+		if s.trie == nil {
+			var err error
+			s.trie, err = db.OpenStorageTrie(s.addrHash, s.data.Root)
+			if err != nil {
+				s.trie, _ = db.OpenStorageTrie(s.addrHash, common.Hash{})
+				s.setError(fmt.Errorf("can't create storage trie: %v", err))
+			}
 		}
 	}
 	return s.trie
@@ -197,12 +206,24 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
 	}
 	// If no live objects are available, attempt to use snapshots
 	var (
-		enc []byte
-		err error
+		enc   []byte
+		err   error
+		meter *time.Duration
 	)
+	readStart := time.Now()
+	if metrics.EnabledExpensive {
+		// If the snap is 'under construction', the first lookup may fail. If that
+		// happens, we don't want to double-count the time elapsed. Thus this
+		// dance with the metering.
+		defer func() {
+			if meter != nil {
+				*meter += time.Since(readStart)
+			}
+		}()
+	}
 	if s.db.snap != nil {
 		if metrics.EnabledExpensive {
-			defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now())
+			meter = &s.db.SnapshotStorageReads
 		}
 		// If the object was destructed in *this* block (and potentially resurrected),
 		// the storage has been cleared out, and we should *not* consult the previous
@@ -213,14 +234,20 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
 		if _, destructed := s.db.snapDestructs[s.addrHash]; destructed {
 			return common.Hash{}
 		}
-		enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:]))
+		enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes()))
 	}
 	// If snapshot unavailable or reading from it failed, load from the database
 	if s.db.snap == nil || err != nil {
+		if meter != nil {
+			// If we already spent time checking the snapshot, account for it
+			// and reset the readStart
+			*meter += time.Since(readStart)
+			readStart = time.Now()
+		}
 		if metrics.EnabledExpensive {
-			defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now())
+			meter = &s.db.StorageReads
 		}
-		if enc, err = s.getTrie(db).TryGet(key[:]); err != nil {
+		if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil {
 			s.setError(err)
 			return common.Hash{}
 		}
@@ -282,9 +309,16 @@ func (s *stateObject) setState(key, value common.Hash) {
 
 // finalise moves all dirty storage slots into the pending area to be hashed or
 // committed later. It is invoked at the end of every transaction.
-func (s *stateObject) finalise() {
+func (s *stateObject) finalise(prefetch bool) {
+	slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
 	for key, value := range s.dirtyStorage {
 		s.pendingStorage[key] = value
+		if value != s.originStorage[key] {
+			slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
+		}
+	}
+	if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != emptyRoot {
+		s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch)
 	}
 	if len(s.dirtyStorage) > 0 {
 		s.dirtyStorage = make(Storage)
@@ -295,26 +329,21 @@ func (s *stateObject) finalise() {
 // It will return nil if the trie has not been loaded and no changes have been made
 func (s *stateObject) updateTrie(db Database) Trie {
 	// Make sure all dirty slots are finalized into the pending storage area
-	s.finalise()
+	s.finalise(false) // Don't prefetch any more, pull directly if need be
 	if len(s.pendingStorage) == 0 {
 		return s.trie
 	}
-	// Track the amount of time wasted on updating the storge trie
+	// Track the amount of time wasted on updating the storage trie
 	if metrics.EnabledExpensive {
 		defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
 	}
-	// Retrieve the snapshot storage map for the object
+	// The snapshot storage map for the object
 	var storage map[common.Hash][]byte
-	if s.db.snap != nil {
-		// Retrieve the old storage map, if available, create a new one otherwise
-		storage = s.db.snapStorage[s.addrHash]
-		if storage == nil {
-			storage = make(map[common.Hash][]byte)
-			s.db.snapStorage[s.addrHash] = storage
-		}
-	}
 	// Insert all the pending updates into the trie
 	tr := s.getTrie(db)
+	hasher := s.db.hasher
+
+	usedStorage := make([][]byte, 0, len(s.pendingStorage))
 	for key, value := range s.pendingStorage {
 		// Skip noop changes, persist actual changes
 		if value == s.originStorage[key] {
@@ -331,9 +360,20 @@ func (s *stateObject) updateTrie(db Database) Trie {
 			s.setError(tr.TryUpdate(key[:], v))
 		}
 		// If state snapshotting is active, cache the data til commit
-		if storage != nil {
-			storage[crypto.Keccak256Hash(key[:])] = v // v will be nil if value is 0x00
+		if s.db.snap != nil {
+			if storage == nil {
+				// Retrieve the old storage map, if available, create a new one otherwise
+				if storage = s.db.snapStorage[s.addrHash]; storage == nil {
+					storage = make(map[common.Hash][]byte)
+					s.db.snapStorage[s.addrHash] = storage
+				}
+			}
+			storage[crypto.HashData(hasher, key[:])] = v // v will be nil if value is 0x00
 		}
+		usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
+	}
+	if s.db.prefetcher != nil {
+		s.db.prefetcher.used(s.data.Root, usedStorage)
 	}
 	if len(s.pendingStorage) > 0 {
 		s.pendingStorage = make(Storage)
@@ -347,7 +387,7 @@ func (s *stateObject) updateRoot(db Database) {
 	if s.updateTrie(db) == nil {
 		return
 	}
-	// Track the amount of time wasted on hashing the storge trie
+	// Track the amount of time wasted on hashing the storage trie
 	if metrics.EnabledExpensive {
 		defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
 	}
@@ -364,7 +404,7 @@ func (s *stateObject) CommitTrie(db Database) error {
 	if s.dbErr != nil {
 		return s.dbErr
 	}
-	// Track the amount of time wasted on committing the storge trie
+	// Track the amount of time wasted on committing the storage trie
 	if metrics.EnabledExpensive {
 		defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
 	}
@@ -375,22 +415,21 @@ func (s *stateObject) CommitTrie(db Database) error {
 	return err
 }
 
-// AddBalance removes amount from c's balance.
+// AddBalance adds amount to s's balance.
 // It is used to add funds to the destination account of a transfer.
 func (s *stateObject) AddBalance(amount *big.Int) {
-	// EIP158: We must check emptiness for the objects such that the account
+	// EIP161: We must check emptiness for the objects such that the account
 	// clearing (0,0,0 objects) can take effect.
 	if amount.Sign() == 0 {
 		if s.empty() {
 			s.touch()
 		}
-
 		return
 	}
 	s.SetBalance(new(big.Int).Add(s.Balance(), amount))
 }
 
-// SubBalance removes amount from c's balance.
+// SubBalance removes amount from s's balance.
 // It is used to remove funds from the origin account of a transfer.
 func (s *stateObject) SubBalance(amount *big.Int) {
 	if amount.Sign() == 0 {
@@ -454,6 +493,23 @@ func (s *stateObject) Code(db Database) []byte {
 	return code
 }
 
+// CodeSize returns the size of the contract code associated with this object,
+// or zero if none. This method is an almost mirror of Code, but uses a cache
+// inside the database to avoid loading codes seen recently.
+func (s *stateObject) CodeSize(db Database) int {
+	if s.code != nil {
+		return len(s.code)
+	}
+	if bytes.Equal(s.CodeHash(), emptyCodeHash) {
+		return 0
+	}
+	size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.CodeHash()))
+	if err != nil {
+		s.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
+	}
+	return size
+}
+
 func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
 	prevcode := s.Code(s.db.db)
 	s.db.journal.append(codeChange{
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 41d9b4655e..9566531466 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -27,8 +27,6 @@ import (
 	"github.com/ethereum/go-ethereum/ethdb"
 )
 
-var toAddr = common.BytesToAddress
-
 type stateTest struct {
 	db    ethdb.Database
 	state *StateDB
@@ -41,14 +39,16 @@ func newStateTest() *stateTest {
 }
 
 func TestDump(t *testing.T) {
-	s := newStateTest()
+	db := rawdb.NewMemoryDatabase()
+	sdb, _ := New(common.Hash{}, NewDatabaseWithConfig(db, nil), nil)
+	s := &stateTest{db: db, state: sdb}
 
 	// generate a few entries
-	obj1 := s.state.GetOrNewStateObject(toAddr([]byte{0x01}))
+	obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01}))
 	obj1.AddBalance(big.NewInt(22))
-	obj2 := s.state.GetOrNewStateObject(toAddr([]byte{0x01, 0x02}))
+	obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02}))
 	obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3})
-	obj3 := s.state.GetOrNewStateObject(toAddr([]byte{0x02}))
+	obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02}))
 	obj3.SetBalance(big.NewInt(44))
 
 	// write some of them to the trie
@@ -56,7 +56,7 @@ func TestDump(t *testing.T) {
 	s.state.updateStateObject(obj2)
 	s.state.Commit(false)
 
-	// check that dump contains the state objects that are in trie
+	// check that DumpToCollector contains the state objects that are in trie
 	got := string(s.state.Dump(false, false, true))
 	want := `{
     "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2",
@@ -83,7 +83,7 @@ func TestDump(t *testing.T) {
     }
 }`
 	if got != want {
-		t.Errorf("dump mismatch:\ngot: %s\nwant: %s\n", got, want)
+		t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want)
 	}
 }
 
@@ -106,7 +106,7 @@ func TestNull(t *testing.T) {
 }
 
 func TestSnapshot(t *testing.T) {
-	stateobjaddr := toAddr([]byte("aa"))
+	stateobjaddr := common.BytesToAddress([]byte("aa"))
 	var storageaddr common.Hash
 	data1 := common.BytesToHash([]byte{42})
 	data2 := common.BytesToHash([]byte{43})
@@ -148,8 +148,8 @@ func TestSnapshotEmpty(t *testing.T) {
 func TestSnapshot2(t *testing.T) {
 	state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil)
 
-	stateobjaddr0 := toAddr([]byte("so0"))
-	stateobjaddr1 := toAddr([]byte("so1"))
+	stateobjaddr0 := common.BytesToAddress([]byte("so0"))
+	stateobjaddr1 := common.BytesToAddress([]byte("so1"))
 	var storageaddr common.Hash
 
 	data0 := common.BytesToHash([]byte{17})
@@ -168,7 +168,7 @@ func TestSnapshot2(t *testing.T) {
 	state.setStateObject(so0)
 
 	root, _ := state.Commit(false)
-	state.Reset(root)
+	state, _ = New(root, state.db, state.snaps)
 
 	// and one with deleted == true
 	so1 := state.getStateObject(stateobjaddr1)
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 4f5c1703ed..90f4709bfc 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -25,6 +25,7 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state/snapshot"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -42,9 +43,6 @@ type revision struct {
 var (
 	// emptyRoot is the known root hash of an empty trie.
 	emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
-
-	// emptyCode is the known hash of the empty EVM bytecode.
-	emptyCode = crypto.Keccak256Hash(nil)
 )
 
 type proofList [][]byte
@@ -58,14 +56,17 @@ func (n *proofList) Delete(key []byte) error {
 	panic("not supported")
 }
 
-// StateDBs within the ethereum protocol are used to store anything
+// StateDB structs within the ethereum protocol are used to store anything
 // within the merkle trie. StateDBs take care of caching and storing
 // nested states. It's the general query interface to retrieve:
 // * Contracts
 // * Accounts
 type StateDB struct {
-	db   Database
-	trie Trie
+	db           Database
+	prefetcher   *triePrefetcher
+	originalRoot common.Hash // The pre-state root, before any changes were made
+	trie         Trie
+	hasher       crypto.KeccakState
 
 	snaps         *snapshot.Tree
 	snap          snapshot.Snapshot
@@ -95,6 +96,9 @@ type StateDB struct {
 
 	preimages map[common.Hash][]byte
 
+	// Per-transaction access list
+	accessList *accessList
+
 	// Journal of state modifications. This is the backbone of
 	// Snapshot and RevertToSnapshot.
 	journal        *journal
@@ -115,7 +119,7 @@ type StateDB struct {
 	SnapshotCommits      time.Duration
 }
 
-// Create a new state from a given trie.
+// New creates a new state from a given trie.
 func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
 	tr, err := db.OpenTrie(root)
 	if err != nil {
@@ -124,6 +128,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
 	sdb := &StateDB{
 		db:                  db,
 		trie:                tr,
+		originalRoot:        root,
 		snaps:               snaps,
 		stateObjects:        make(map[common.Address]*stateObject),
 		stateObjectsPending: make(map[common.Address]struct{}),
@@ -131,6 +136,8 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
 		logs:                make(map[common.Hash][]*types.Log),
 		preimages:           make(map[common.Hash][]byte),
 		journal:             newJournal(),
+		accessList:          newAccessList(),
+		hasher:              crypto.NewKeccakState(),
 	}
 	if sdb.snaps != nil {
 		if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
@@ -142,6 +149,28 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
 	return sdb, nil
 }
 
+// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
+// state trie concurrently while the state is mutated so that when we reach the
+// commit phase, most of the needed data is already hot.
+func (s *StateDB) StartPrefetcher(namespace string) {
+	if s.prefetcher != nil {
+		s.prefetcher.close()
+		s.prefetcher = nil
+	}
+	if s.snap != nil {
+		s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
+	}
+}
+
+// StopPrefetcher terminates a running prefetcher and reports any leftover stats
+// from the gathered metrics.
+func (s *StateDB) StopPrefetcher() {
+	if s.prefetcher != nil {
+		s.prefetcher.close()
+		s.prefetcher = nil
+	}
+}
+
 // setError remembers the first non-nil error it is called with.
 func (s *StateDB) setError(err error) {
 	if s.dbErr == nil {
@@ -153,36 +182,6 @@ func (s *StateDB) Error() error {
 	return s.dbErr
 }
 
-// Reset clears out all ephemeral state objects from the state db, but keeps
-// the underlying state trie to avoid reloading data for the next operations.
-func (s *StateDB) Reset(root common.Hash) error {
-	tr, err := s.db.OpenTrie(root)
-	if err != nil {
-		return err
-	}
-	s.trie = tr
-	s.stateObjects = make(map[common.Address]*stateObject)
-	s.stateObjectsPending = make(map[common.Address]struct{})
-	s.stateObjectsDirty = make(map[common.Address]struct{})
-	s.thash = common.Hash{}
-	s.bhash = common.Hash{}
-	s.txIndex = 0
-	s.logs = make(map[common.Hash][]*types.Log)
-	s.logSize = 0
-	s.preimages = make(map[common.Hash][]byte)
-	s.clearJournalAndRefund()
-
-	if s.snaps != nil {
-		s.snapAccounts, s.snapDestructs, s.snapStorage = nil, nil, nil
-		if s.snap = s.snaps.Snapshot(root); s.snap != nil {
-			s.snapDestructs = make(map[common.Hash]struct{})
-			s.snapAccounts = make(map[common.Hash][]byte)
-			s.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
-		}
-	}
-	return nil
-}
-
 func (s *StateDB) AddLog(log *types.Log) {
 	s.journal.append(addLogChange{txhash: s.thash})
 
@@ -250,7 +249,7 @@ func (s *StateDB) Empty(addr common.Address) bool {
 	return so == nil || so.empty()
 }
 
-// Retrieve the balance from the given address or 0 if object not found
+// GetBalance retrieves the balance from the given address or 0 if object not found
 func (s *StateDB) GetBalance(addr common.Address) *big.Int {
 	stateObject := s.getStateObject(addr)
 	if stateObject != nil {
@@ -288,17 +287,10 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
 
 func (s *StateDB) GetCodeSize(addr common.Address) int {
 	stateObject := s.getStateObject(addr)
-	if stateObject == nil {
-		return 0
-	}
-	if stateObject.code != nil {
-		return len(stateObject.code)
-	}
-	size, err := s.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash()))
-	if err != nil {
-		s.setError(err)
+	if stateObject != nil {
+		return stateObject.CodeSize(s.db)
 	}
-	return size
+	return 0
 }
 
 func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
@@ -318,14 +310,19 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
 	return common.Hash{}
 }
 
-// GetProof returns the MerkleProof for a given Account
-func (s *StateDB) GetProof(a common.Address) ([][]byte, error) {
+// GetProof returns the Merkle proof for a given account.
+func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) {
+	return s.GetProofByHash(crypto.Keccak256Hash(addr.Bytes()))
+}
+
+// GetProofByHash returns the Merkle proof for a given account.
+func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
 	var proof proofList
-	err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof)
-	return [][]byte(proof), err
+	err := s.trie.Prove(addrHash[:], 0, &proof)
+	return proof, err
 }
 
-// GetProof returns the StorageProof for given key
+// GetStorageProof returns the Merkle proof for given storage slot.
 func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
 	var proof proofList
 	trie := s.StorageTrie(a)
@@ -333,7 +330,18 @@ func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte,
 		return proof, errors.New("storage trie for requested address does not exist")
 	}
 	err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
-	return [][]byte(proof), err
+	return proof, err
+}
+
+// GetStorageProofByHash returns the Merkle proof for given storage slot.
+func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][]byte, error) {
+	var proof proofList
+	trie := s.StorageTrie(a)
+	if trie == nil {
+		return proof, errors.New("storage trie for requested address does not exist")
+	}
+	err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
+	return proof, err
 }
 
 // GetCommittedState retrieves a value from the given account's committed storage trie.
@@ -465,14 +473,16 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
 	if err != nil {
 		panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
 	}
-	s.setError(s.trie.TryUpdate(addr[:], data))
+	if err = s.trie.TryUpdate(addr[:], data); err != nil {
+		s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
+	}
 
 	// If state snapshotting is active, cache the data til commit. Note, this
 	// update mechanism is not symmetric to the deletion, because whereas it is
 	// enough to track account updates at commit time, deletions need tracking
 	// at transaction boundary level to ensure we capture state clearing.
 	if s.snap != nil {
-		s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash)
+		s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash)
 	}
 }
 
@@ -484,7 +494,9 @@ func (s *StateDB) deleteStateObject(obj *stateObject) {
 	}
 	// Delete the account from the trie
 	addr := obj.Address()
-	s.setError(s.trie.TryDelete(addr[:]))
+	if err := s.trie.TryDelete(addr[:]); err != nil {
+		s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
+	}
 }
 
 // getStateObject retrieves a state object given by the address, returning nil if
@@ -508,7 +520,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
 	}
 	// If no live objects are available, attempt to use snapshots
 	var (
-		data Account
+		data *Account
 		err  error
 	)
 	if s.snap != nil {
@@ -516,15 +528,19 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
 			defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now())
 		}
 		var acc *snapshot.Account
-		if acc, err = s.snap.Account(crypto.Keccak256Hash(addr[:])); err == nil {
+		if acc, err = s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())); err == nil {
 			if acc == nil {
 				return nil
 			}
-			data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash
+			data = &Account{
+				Nonce:    acc.Nonce,
+				Balance:  acc.Balance,
+				CodeHash: acc.CodeHash,
+				Root:     common.BytesToHash(acc.Root),
+			}
 			if len(data.CodeHash) == 0 {
 				data.CodeHash = emptyCodeHash
 			}
-			data.Root = common.BytesToHash(acc.Root)
 			if data.Root == (common.Hash{}) {
 				data.Root = emptyRoot
 			}
@@ -535,18 +551,22 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
 		if metrics.EnabledExpensive {
 			defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now())
 		}
-		enc, err := s.trie.TryGet(addr[:])
+		enc, err := s.trie.TryGet(addr.Bytes())
+		if err != nil {
+			s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err))
+			return nil
+		}
 		if len(enc) == 0 {
-			s.setError(err)
 			return nil
 		}
-		if err := rlp.DecodeBytes(enc, &data); err != nil {
+		data = new(Account)
+		if err := rlp.DecodeBytes(enc, data); err != nil {
 			log.Error("Failed to decode state object", "addr", addr, "err", err)
 			return nil
 		}
 	}
 	// Insert into the live set
-	obj := newObject(s, addr, data)
+	obj := newObject(s, addr, *data)
 	s.setStateObject(obj)
 	return obj
 }
@@ -555,7 +575,7 @@ func (s *StateDB) setStateObject(object *stateObject) {
 	s.stateObjects[object.Address()] = object
 }
 
-// Retrieve a state object or create a new state object if nil.
+// GetOrNewStateObject retrieves a state object or create a new state object if nil.
 func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
 	stateObject := s.getStateObject(addr)
 	if stateObject == nil {
@@ -584,7 +604,10 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
 		s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
 	}
 	s.setStateObject(newobj)
-	return newobj, prev
+	if prev != nil && !prev.deleted {
+		return newobj, prev
+	}
+	return newobj, nil
 }
 
 // CreateAccount explicitly creates a state object. If a state object with the address
@@ -648,6 +671,7 @@ func (s *StateDB) Copy() *StateDB {
 		logSize:             s.logSize,
 		preimages:           make(map[common.Hash][]byte, len(s.preimages)),
 		journal:             newJournal(),
+		hasher:              crypto.NewKeccakState(),
 	}
 	// Copy the dirty states, logs, and preimages
 	for addr := range s.journal.dirties {
@@ -691,6 +715,44 @@ func (s *StateDB) Copy() *StateDB {
 	for hash, preimage := range s.preimages {
 		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()
+
+	// If there's a prefetcher running, make an inactive copy of it that can
+	// only access data but does not actively preload (since the user will not
+	// know that they need to explicitly terminate an active copy).
+	if s.prefetcher != nil {
+		state.prefetcher = s.prefetcher.copy()
+	}
+	if s.snaps != nil {
+		// In order for the miner to be able to use and make additions
+		// to the snapshot tree, we need to copy that aswell.
+		// Otherwise, any block mined by ourselves will cause gaps in the tree,
+		// and force the miner to operate trie-backed only
+		state.snaps = s.snaps
+		state.snap = s.snap
+		// deep copy needed
+		state.snapDestructs = make(map[common.Hash]struct{})
+		for k, v := range s.snapDestructs {
+			state.snapDestructs[k] = v
+		}
+		state.snapAccounts = make(map[common.Hash][]byte)
+		for k, v := range s.snapAccounts {
+			state.snapAccounts[k] = v
+		}
+		state.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
+		for k, v := range s.snapStorage {
+			temp := make(map[common.Hash][]byte)
+			for kk, vv := range v {
+				temp[kk] = vv
+			}
+			state.snapStorage[k] = temp
+		}
+	}
 	return state
 }
 
@@ -727,6 +789,7 @@ func (s *StateDB) GetRefund() uint64 {
 // the journal as well as the refunds. Finalise, however, will not push any updates
 // into the tries just yet. Only IntermediateRoot or Commit will do that.
 func (s *StateDB) Finalise(deleteEmptyObjects bool) {
+	addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties))
 	for addr := range s.journal.dirties {
 		obj, exist := s.stateObjects[addr]
 		if !exist {
@@ -751,10 +814,18 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
 				delete(s.snapStorage, obj.addrHash)        // Clear out any previously updated storage data (may be recreated via a ressurrect)
 			}
 		} else {
-			obj.finalise()
+			obj.finalise(true) // Prefetch slots in the background
 		}
 		s.stateObjectsPending[addr] = struct{}{}
 		s.stateObjectsDirty[addr] = struct{}{}
+
+		// At this point, also ship the address off to the precacher. The precacher
+		// will start loading tries, and when the change is eventually committed,
+		// the commit-phase will be a lot faster
+		addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
+	}
+	if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
+		s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch)
 	}
 	// Invalidate journal because reverting across transactions is not allowed.
 	s.clearJournalAndRefund()
@@ -767,14 +838,49 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
 	// Finalise all the dirty storage states and write them into the tries
 	s.Finalise(deleteEmptyObjects)
 
+	// If there was a trie prefetcher operating, it gets aborted and irrevocably
+	// modified after we start retrieving tries. Remove it from the statedb after
+	// this round of use.
+	//
+	// This is weird pre-byzantium since the first tx runs with a prefetcher and
+	// the remainder without, but pre-byzantium even the initial prefetcher is
+	// useless, so no sleep lost.
+	prefetcher := s.prefetcher
+	if s.prefetcher != nil {
+		defer func() {
+			s.prefetcher.close()
+			s.prefetcher = nil
+		}()
+	}
+	// Although naively it makes sense to retrieve the account trie and then do
+	// the contract storage and account updates sequentially, that short circuits
+	// the account prefetcher. Instead, let's process all the storage updates
+	// first, giving the account prefeches just a few more milliseconds of time
+	// to pull useful data from disk.
 	for addr := range s.stateObjectsPending {
-		obj := s.stateObjects[addr]
-		if obj.deleted {
+		if obj := s.stateObjects[addr]; !obj.deleted {
+			obj.updateRoot(s.db)
+		}
+	}
+	// Now we're about to start to write changes to the trie. The trie is so far
+	// _untouched_. We can check with the prefetcher, if it can give us a trie
+	// which has the same root, but also has some content loaded into it.
+	if prefetcher != nil {
+		if trie := prefetcher.trie(s.originalRoot); trie != nil {
+			s.trie = trie
+		}
+	}
+	usedAddrs := make([][]byte, 0, len(s.stateObjectsPending))
+	for addr := range s.stateObjectsPending {
+		if obj := s.stateObjects[addr]; obj.deleted {
 			s.deleteStateObject(obj)
 		} else {
-			obj.updateRoot(s.db)
 			s.updateStateObject(obj)
 		}
+		usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
+	}
+	if prefetcher != nil {
+		prefetcher.used(s.originalRoot, usedAddrs)
 	}
 	if len(s.stateObjectsPending) > 0 {
 		s.stateObjectsPending = make(map[common.Address]struct{})
@@ -792,6 +898,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) {
 	s.thash = thash
 	s.bhash = bhash
 	s.txIndex = ti
+	s.accessList = newAccessList()
 }
 
 func (s *StateDB) clearJournalAndRefund() {
@@ -804,15 +911,19 @@ func (s *StateDB) clearJournalAndRefund() {
 
 // Commit writes the state to the underlying in-memory trie database.
 func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
+	if s.dbErr != nil {
+		return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
+	}
 	// Finalize any pending changes and merge everything into the tries
 	s.IntermediateRoot(deleteEmptyObjects)
 
 	// Commit objects to the trie, measuring the elapsed time
+	codeWriter := s.db.TrieDB().DiskDB().NewBatch()
 	for addr := range s.stateObjectsDirty {
 		if obj := s.stateObjects[addr]; !obj.deleted {
 			// Write any contract code associated with the state object
 			if obj.code != nil && obj.dirtyCode {
-				s.db.TrieDB().InsertBlob(common.BytesToHash(obj.CodeHash()), obj.code)
+				rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
 				obj.dirtyCode = false
 			}
 			// Write any storage changes in the state object to its storage trie
@@ -824,6 +935,11 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
 	if len(s.stateObjectsDirty) > 0 {
 		s.stateObjectsDirty = make(map[common.Address]struct{})
 	}
+	if codeWriter.ValueSize() > 0 {
+		if err := codeWriter.Write(); err != nil {
+			log.Crit("Failed to commit dirty codes", "error", err)
+		}
+	}
 	// Write the account trie changes, measuing the amount of wasted time
 	var start time.Time
 	if metrics.EnabledExpensive {
@@ -832,17 +948,13 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
 	// The onleaf func is called _serially_, so we can reuse the same account
 	// for unmarshalling every time.
 	var account Account
-	root, err := s.trie.Commit(func(leaf []byte, parent common.Hash) error {
+	root, err := s.trie.Commit(func(_ [][]byte, _ []byte, leaf []byte, parent common.Hash) error {
 		if err := rlp.DecodeBytes(leaf, &account); err != nil {
 			return nil
 		}
 		if account.Root != emptyRoot {
 			s.db.TrieDB().Reference(account.Root, parent)
 		}
-		code := common.BytesToHash(account.CodeHash)
-		if code != emptyCode {
-			s.db.TrieDB().Reference(code, parent)
-		}
 		return nil
 	})
 	if metrics.EnabledExpensive {
@@ -858,11 +970,76 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
 			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)
 			}
-			if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie
-				log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err)
+			// Keep 128 diff layers in the memory, persistent layer is 129th.
+			// - 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
 	}
 	return root, err
 }
+
+// PrepareAccessList handles the preparatory steps for executing a state transition with
+// regards to both EIP-2929 and EIP-2930:
+//
+// - Add sender to access list (2929)
+// - Add destination to access list (2929)
+// - Add precompiles to access list (2929)
+// - Add the contents of the optional tx access list (2930)
+//
+// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
+func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
+	s.AddAddressToAccessList(sender)
+	if dst != nil {
+		s.AddAddressToAccessList(*dst)
+		// If it's a create-tx, the destination will be added inside evm.create
+	}
+	for _, addr := range precompiles {
+		s.AddAddressToAccessList(addr)
+	}
+	for _, el := range list {
+		s.AddAddressToAccessList(el.Address)
+		for _, key := range el.StorageKeys {
+			s.AddSlotToAccessList(el.Address, key)
+		}
+	}
+}
+
+// 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)
+}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 1cb73f015e..9524e3730d 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -55,7 +55,7 @@ func TestUpdateLeaks(t *testing.T) {
 	}
 
 	root := state.IntermediateRoot(false)
-	if err := state.Database().TrieDB().Commit(root, false); err != nil {
+	if err := state.Database().TrieDB().Commit(root, false, nil); err != nil {
 		t.Errorf("can not commit trie %v to persistent database", root.Hex())
 	}
 
@@ -106,7 +106,7 @@ func TestIntermediateLeaks(t *testing.T) {
 	if err != nil {
 		t.Fatalf("failed to commit transition state: %v", err)
 	}
-	if err = transState.Database().TrieDB().Commit(transRoot, false); err != nil {
+	if err = transState.Database().TrieDB().Commit(transRoot, false, nil); err != nil {
 		t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
 	}
 
@@ -114,7 +114,7 @@ func TestIntermediateLeaks(t *testing.T) {
 	if err != nil {
 		t.Fatalf("failed to commit final state: %v", err)
 	}
-	if err = finalState.Database().TrieDB().Commit(finalRoot, false); err != nil {
+	if err = finalState.Database().TrieDB().Commit(finalRoot, false, nil); err != nil {
 		t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex())
 	}
 
@@ -144,7 +144,7 @@ func TestIntermediateLeaks(t *testing.T) {
 	}
 }
 
-// TestCopy tests that copying a statedb object indeed makes the original and
+// TestCopy tests that copying a StateDB object indeed makes the original and
 // the copy independent of each other. This test is a regression test against
 // https://github.com/ethereum/go-ethereum/pull/15549.
 func TestCopy(t *testing.T) {
@@ -328,6 +328,20 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
 			},
 			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))]
 	var nameargs []string
@@ -460,7 +474,7 @@ func TestTouchDelete(t *testing.T) {
 	s := newStateTest()
 	s.state.GetOrNewStateObject(common.Address{})
 	root, _ := s.state.Commit(false)
-	s.state.Reset(root)
+	s.state, _ = New(root, s.state.db, s.state.snaps)
 
 	snapshot := s.state.Snapshot()
 	s.state.AddBalance(common.Address{}, new(big.Int))
@@ -647,22 +661,22 @@ func TestCopyCopyCommitCopy(t *testing.T) {
 }
 
 // TestDeleteCreateRevert tests a weird state transition corner case that we hit
-// while changing the internals of statedb. The workflow is that a contract is
-// self destructed, then in a followup transaction (but same block) it's created
+// while changing the internals of StateDB. The workflow is that a contract is
+// self-destructed, then in a follow-up transaction (but same block) it's created
 // again and the transaction reverted.
 //
-// The original statedb implementation flushed dirty objects to the tries after
+// The original StateDB implementation flushed dirty objects to the tries after
 // each transaction, so this works ok. The rework accumulated writes in memory
 // first, but the journal wiped the entire state object on create-revert.
 func TestDeleteCreateRevert(t *testing.T) {
 	// Create an initial state with a single contract
 	state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil)
 
-	addr := toAddr([]byte("so"))
+	addr := common.BytesToAddress([]byte("so"))
 	state.SetBalance(addr, big.NewInt(1))
 
 	root, _ := state.Commit(false)
-	state.Reset(root)
+	state, _ = New(root, state.db, state.snaps)
 
 	// Simulate self-destructing in one transaction, then create-reverting in another
 	state.Suicide(addr)
@@ -674,9 +688,230 @@ func TestDeleteCreateRevert(t *testing.T) {
 
 	// Commit the entire state and make sure we don't crash and have the correct state
 	root, _ = state.Commit(true)
-	state.Reset(root)
+	state, _ = New(root, state.db, state.snaps)
 
 	if state.getStateObject(addr) != nil {
 		t.Fatalf("self-destructed contract came alive")
 	}
 }
+
+// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie,
+// the Commit operation fails with an error
+// If we are missing trie nodes, we should not continue writing to the trie
+func TestMissingTrieNodes(t *testing.T) {
+
+	// Create an initial state with a few accounts
+	memDb := rawdb.NewMemoryDatabase()
+	db := NewDatabase(memDb)
+	var root common.Hash
+	state, _ := New(common.Hash{}, db, nil)
+	addr := common.BytesToAddress([]byte("so"))
+	{
+		state.SetBalance(addr, big.NewInt(1))
+		state.SetCode(addr, []byte{1, 2, 3})
+		a2 := common.BytesToAddress([]byte("another"))
+		state.SetBalance(a2, big.NewInt(100))
+		state.SetCode(a2, []byte{1, 2, 4})
+		root, _ = state.Commit(false)
+		t.Logf("root: %x", root)
+		// force-flush
+		state.Database().TrieDB().Cap(0)
+	}
+	// Create a new state on the old root
+	state, _ = New(root, db, nil)
+	// Now we clear out the memdb
+	it := memDb.NewIterator(nil, nil)
+	for it.Next() {
+		k := it.Key()
+		// Leave the root intact
+		if !bytes.Equal(k, root[:]) {
+			t.Logf("key: %x", k)
+			memDb.Delete(k)
+		}
+	}
+	balance := state.GetBalance(addr)
+	// The removed elem should lead to it returning zero balance
+	if exp, got := uint64(0), balance.Uint64(); got != exp {
+		t.Errorf("expected %d, got %d", exp, got)
+	}
+	// Modify the state
+	state.SetBalance(addr, big.NewInt(2))
+	root, err := state.Commit(false)
+	if err == nil {
+		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)
+	}
+}
diff --git a/core/state/sync.go b/core/state/sync.go
index ef79305273..7a5852fb19 100644
--- a/core/state/sync.go
+++ b/core/state/sync.go
@@ -26,17 +26,31 @@ import (
 )
 
 // NewStateSync create a new state trie download scheduler.
-func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom) *trie.Sync {
+func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom, onLeaf func(paths [][]byte, leaf []byte) error) *trie.Sync {
+	// Register the storage slot callback if the external callback is specified.
+	var onSlot func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error
+	if onLeaf != nil {
+		onSlot = func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error {
+			return onLeaf(paths, leaf)
+		}
+	}
+	// Register the account callback to connect the state trie and the storage
+	// trie belongs to the contract.
 	var syncer *trie.Sync
-	callback := func(leaf []byte, parent common.Hash) error {
+	onAccount := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error {
+		if onLeaf != nil {
+			if err := onLeaf(paths, leaf); err != nil {
+				return err
+			}
+		}
 		var obj Account
 		if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
 			return err
 		}
-		syncer.AddSubTrie(obj.Root, 64, parent, nil)
-		syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent)
+		syncer.AddSubTrie(obj.Root, hexpath, parent, onSlot)
+		syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), hexpath, parent)
 		return nil
 	}
-	syncer = trie.NewSync(root, database, callback, bloom)
+	syncer = trie.NewSync(root, database, onAccount, bloom)
 	return syncer
 }
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index 924c8c2f90..a13fcf56a3 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/trie"
 )
 
@@ -44,7 +45,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
 	state, _ := New(common.Hash{}, db, nil)
 
 	// Fill it with some arbitrary data
-	accounts := []*testAccount{}
+	var accounts []*testAccount
 	for i := byte(0); i < 96; i++ {
 		obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
 		acc := &testAccount{address: common.BytesToAddress([]byte{i})}
@@ -59,6 +60,12 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
 			obj.SetCode(crypto.Keccak256Hash([]byte{i, i, i, i, i}), []byte{i, i, i, i, i})
 			acc.code = []byte{i, i, i, i, i}
 		}
+		if i%5 == 0 {
+			for j := byte(0); j < 5; j++ {
+				hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j})
+				obj.SetState(db, hash, hash)
+			}
+		}
 		state.updateStateObject(obj)
 		accounts = append(accounts, acc)
 	}
@@ -126,43 +133,109 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error {
 // Tests that an empty state is not scheduled for syncing.
 func TestEmptyStateSync(t *testing.T) {
 	empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
-	if req := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New())).Missing(1); len(req) != 0 {
-		t.Errorf("content requested for empty state: %v", req)
+	sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New()), nil)
+	if nodes, paths, codes := sync.Missing(1); len(nodes) != 0 || len(paths) != 0 || len(codes) != 0 {
+		t.Errorf(" content requested for empty state: %v, %v, %v", nodes, paths, codes)
 	}
 }
 
 // Tests that given a root hash, a state can sync iteratively on a single thread,
 // requesting retrieval tasks and returning all of them in one go.
-func TestIterativeStateSyncIndividual(t *testing.T) { testIterativeStateSync(t, 1) }
-func TestIterativeStateSyncBatched(t *testing.T)    { testIterativeStateSync(t, 100) }
+func TestIterativeStateSyncIndividual(t *testing.T) {
+	testIterativeStateSync(t, 1, false, false)
+}
+func TestIterativeStateSyncBatched(t *testing.T) {
+	testIterativeStateSync(t, 100, false, false)
+}
+func TestIterativeStateSyncIndividualFromDisk(t *testing.T) {
+	testIterativeStateSync(t, 1, true, false)
+}
+func TestIterativeStateSyncBatchedFromDisk(t *testing.T) {
+	testIterativeStateSync(t, 100, true, false)
+}
+func TestIterativeStateSyncIndividualByPath(t *testing.T) {
+	testIterativeStateSync(t, 1, false, true)
+}
+func TestIterativeStateSyncBatchedByPath(t *testing.T) {
+	testIterativeStateSync(t, 100, false, true)
+}
 
-func testIterativeStateSync(t *testing.T, count int) {
+func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
 	// Create a random state to copy
 	srcDb, srcRoot, srcAccounts := makeTestState()
+	if commit {
+		srcDb.TrieDB().Commit(srcRoot, false, nil)
+	}
+	srcTrie, _ := trie.New(srcRoot, srcDb.TrieDB())
 
 	// Create a destination state and sync with the scheduler
 	dstDb := rawdb.NewMemoryDatabase()
-	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
-
-	queue := append([]common.Hash{}, sched.Missing(count)...)
-	for len(queue) > 0 {
-		results := make([]trie.SyncResult, len(queue))
-		for i, hash := range queue {
+	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
+
+	nodes, paths, codes := sched.Missing(count)
+	var (
+		hashQueue []common.Hash
+		pathQueue []trie.SyncPath
+	)
+	if !bypath {
+		hashQueue = append(append(hashQueue[:0], nodes...), codes...)
+	} else {
+		hashQueue = append(hashQueue[:0], codes...)
+		pathQueue = append(pathQueue[:0], paths...)
+	}
+	for len(hashQueue)+len(pathQueue) > 0 {
+		results := make([]trie.SyncResult, len(hashQueue)+len(pathQueue))
+		for i, hash := range hashQueue {
 			data, err := srcDb.TrieDB().Node(hash)
 			if err != nil {
-				t.Fatalf("failed to retrieve node data for %x", hash)
+				data, err = srcDb.ContractCode(common.Hash{}, hash)
+			}
+			if err != nil {
+				t.Fatalf("failed to retrieve node data for hash %x", hash)
 			}
 			results[i] = trie.SyncResult{Hash: hash, Data: data}
 		}
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for i, path := range pathQueue {
+			if len(path) == 1 {
+				data, _, err := srcTrie.TryGetNode(path[0])
+				if err != nil {
+					t.Fatalf("failed to retrieve node data for path %x: %v", path, err)
+				}
+				results[len(hashQueue)+i] = trie.SyncResult{Hash: crypto.Keccak256Hash(data), Data: data}
+			} else {
+				var acc Account
+				if err := rlp.DecodeBytes(srcTrie.Get(path[0]), &acc); err != nil {
+					t.Fatalf("failed to decode account on path %x: %v", path, err)
+				}
+				stTrie, err := trie.New(acc.Root, srcDb.TrieDB())
+				if err != nil {
+					t.Fatalf("failed to retriev storage trie for path %x: %v", path, err)
+				}
+				data, _, err := stTrie.TryGetNode(path[1])
+				if err != nil {
+					t.Fatalf("failed to retrieve node data for path %x: %v", path, err)
+				}
+				results[len(hashQueue)+i] = trie.SyncResult{Hash: crypto.Keccak256Hash(data), Data: data}
+			}
+		}
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Errorf("failed to process result %v", err)
+			}
 		}
 		batch := dstDb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
-		queue = append(queue[:0], sched.Missing(count)...)
+
+		nodes, paths, codes = sched.Missing(count)
+		if !bypath {
+			hashQueue = append(append(hashQueue[:0], nodes...), codes...)
+		} else {
+			hashQueue = append(hashQueue[:0], codes...)
+			pathQueue = append(pathQueue[:0], paths...)
+		}
 	}
 	// Cross check that the two states are in sync
 	checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
@@ -176,28 +249,37 @@ func TestIterativeDelayedStateSync(t *testing.T) {
 
 	// Create a destination state and sync with the scheduler
 	dstDb := rawdb.NewMemoryDatabase()
-	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
+	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
+
+	nodes, _, codes := sched.Missing(0)
+	queue := append(append([]common.Hash{}, nodes...), codes...)
 
-	queue := append([]common.Hash{}, sched.Missing(0)...)
 	for len(queue) > 0 {
 		// Sync only half of the scheduled nodes
 		results := make([]trie.SyncResult, len(queue)/2+1)
 		for i, hash := range queue[:len(results)] {
 			data, err := srcDb.TrieDB().Node(hash)
+			if err != nil {
+				data, err = srcDb.ContractCode(common.Hash{}, hash)
+			}
 			if err != nil {
 				t.Fatalf("failed to retrieve node data for %x", hash)
 			}
 			results[i] = trie.SyncResult{Hash: hash, Data: data}
 		}
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := dstDb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
-		queue = append(queue[len(results):], sched.Missing(0)...)
+
+		nodes, _, codes = sched.Missing(0)
+		queue = append(append(queue[len(results):], nodes...), codes...)
 	}
 	// Cross check that the two states are in sync
 	checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
@@ -215,10 +297,11 @@ func testIterativeRandomStateSync(t *testing.T, count int) {
 
 	// Create a destination state and sync with the scheduler
 	dstDb := rawdb.NewMemoryDatabase()
-	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
+	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
 
 	queue := make(map[common.Hash]struct{})
-	for _, hash := range sched.Missing(count) {
+	nodes, _, codes := sched.Missing(count)
+	for _, hash := range append(nodes, codes...) {
 		queue[hash] = struct{}{}
 	}
 	for len(queue) > 0 {
@@ -226,22 +309,29 @@ func testIterativeRandomStateSync(t *testing.T, count int) {
 		results := make([]trie.SyncResult, 0, len(queue))
 		for hash := range queue {
 			data, err := srcDb.TrieDB().Node(hash)
+			if err != nil {
+				data, err = srcDb.ContractCode(common.Hash{}, hash)
+			}
 			if err != nil {
 				t.Fatalf("failed to retrieve node data for %x", hash)
 			}
 			results = append(results, trie.SyncResult{Hash: hash, Data: data})
 		}
 		// Feed the retrieved results back and queue new tasks
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := dstDb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
+
 		queue = make(map[common.Hash]struct{})
-		for _, hash := range sched.Missing(count) {
+		nodes, _, codes = sched.Missing(count)
+		for _, hash := range append(nodes, codes...) {
 			queue[hash] = struct{}{}
 		}
 	}
@@ -257,10 +347,11 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
 
 	// Create a destination state and sync with the scheduler
 	dstDb := rawdb.NewMemoryDatabase()
-	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
+	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
 
 	queue := make(map[common.Hash]struct{})
-	for _, hash := range sched.Missing(0) {
+	nodes, _, codes := sched.Missing(0)
+	for _, hash := range append(nodes, codes...) {
 		queue[hash] = struct{}{}
 	}
 	for len(queue) > 0 {
@@ -270,6 +361,9 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
 			delete(queue, hash)
 
 			data, err := srcDb.TrieDB().Node(hash)
+			if err != nil {
+				data, err = srcDb.ContractCode(common.Hash{}, hash)
+			}
 			if err != nil {
 				t.Fatalf("failed to retrieve node data for %x", hash)
 			}
@@ -280,15 +374,21 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
 			}
 		}
 		// Feed the retrieved results back and queue new tasks
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := dstDb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
-		for _, hash := range sched.Missing(0) {
+		for _, result := range results {
+			delete(queue, result.Hash)
+		}
+		nodes, _, codes = sched.Missing(0)
+		for _, hash := range append(nodes, codes...) {
 			queue[hash] = struct{}{}
 		}
 	}
@@ -302,27 +402,43 @@ func TestIncompleteStateSync(t *testing.T) {
 	// Create a random state to copy
 	srcDb, srcRoot, srcAccounts := makeTestState()
 
+	// isCodeLookup to save some hashing
+	var isCode = make(map[common.Hash]struct{})
+	for _, acc := range srcAccounts {
+		if len(acc.code) > 0 {
+			isCode[crypto.Keccak256Hash(acc.code)] = struct{}{}
+		}
+	}
+	isCode[common.BytesToHash(emptyCodeHash)] = struct{}{}
 	checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot)
 
 	// Create a destination state and sync with the scheduler
 	dstDb := rawdb.NewMemoryDatabase()
-	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb))
+	sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
+
+	var added []common.Hash
+
+	nodes, _, codes := sched.Missing(1)
+	queue := append(append([]common.Hash{}, nodes...), codes...)
 
-	added := []common.Hash{}
-	queue := append([]common.Hash{}, sched.Missing(1)...)
 	for len(queue) > 0 {
 		// Fetch a batch of state nodes
 		results := make([]trie.SyncResult, len(queue))
 		for i, hash := range queue {
 			data, err := srcDb.TrieDB().Node(hash)
+			if err != nil {
+				data, err = srcDb.ContractCode(common.Hash{}, hash)
+			}
 			if err != nil {
 				t.Fatalf("failed to retrieve node data for %x", hash)
 			}
 			results[i] = trie.SyncResult{Hash: hash, Data: data}
 		}
 		// Process each of the state nodes
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := dstDb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
@@ -331,33 +447,41 @@ func TestIncompleteStateSync(t *testing.T) {
 		batch.Write()
 		for _, result := range results {
 			added = append(added, result.Hash)
-		}
-		// Check that all known sub-tries added so far are complete or missing entirely.
-	checkSubtries:
-		for _, hash := range added {
-			for _, acc := range srcAccounts {
-				if hash == crypto.Keccak256Hash(acc.code) {
-					continue checkSubtries // skip trie check of code nodes.
-				}
+			// Check that all known sub-tries added so far are complete or missing entirely.
+			if _, ok := isCode[result.Hash]; ok {
+				continue
 			}
 			// Can't use checkStateConsistency here because subtrie keys may have odd
 			// length and crash in LeafKey.
-			if err := checkTrieConsistency(dstDb, hash); err != nil {
+			if err := checkTrieConsistency(dstDb, result.Hash); err != nil {
 				t.Fatalf("state inconsistent: %v", err)
 			}
 		}
 		// Fetch the next batch to retrieve
-		queue = append(queue[:0], sched.Missing(1)...)
+		nodes, _, codes = sched.Missing(1)
+		queue = append(append(queue[:0], nodes...), codes...)
 	}
 	// Sanity check that removing any node from the database is detected
 	for _, node := range added[1:] {
-		key := node.Bytes()
-		value, _ := dstDb.Get(key)
-
-		dstDb.Delete(key)
+		var (
+			key     = node.Bytes()
+			_, code = isCode[node]
+			val     []byte
+		)
+		if code {
+			val = rawdb.ReadCode(dstDb, node)
+			rawdb.DeleteCode(dstDb, node)
+		} else {
+			val = rawdb.ReadTrieNode(dstDb, node)
+			rawdb.DeleteTrieNode(dstDb, node)
+		}
 		if err := checkStateConsistency(dstDb, added[0]); err == nil {
 			t.Fatalf("trie inconsistency not caught, missing: %x", key)
 		}
-		dstDb.Put(key, value)
+		if code {
+			rawdb.WriteCode(dstDb, node, val)
+		} else {
+			rawdb.WriteTrieNode(dstDb, node, val)
+		}
 	}
 }
diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go
new file mode 100644
index 0000000000..ac5e95c5c2
--- /dev/null
+++ b/core/state/trie_prefetcher.go
@@ -0,0 +1,334 @@
+// 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 .
+
+package state
+
+import (
+	"sync"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
+)
+
+var (
+	// triePrefetchMetricsPrefix is the prefix under which to publis the metrics.
+	triePrefetchMetricsPrefix = "trie/prefetch/"
+)
+
+// triePrefetcher is an active prefetcher, which receives accounts or storage
+// items and does trie-loading of them. The goal is to get as much useful content
+// into the caches as possible.
+//
+// Note, the prefetcher's API is not thread safe.
+type triePrefetcher struct {
+	db       Database                    // Database to fetch trie nodes through
+	root     common.Hash                 // Root hash of theaccount trie for metrics
+	fetches  map[common.Hash]Trie        // Partially or fully fetcher tries
+	fetchers map[common.Hash]*subfetcher // Subfetchers for each trie
+
+	deliveryMissMeter metrics.Meter
+	accountLoadMeter  metrics.Meter
+	accountDupMeter   metrics.Meter
+	accountSkipMeter  metrics.Meter
+	accountWasteMeter metrics.Meter
+	storageLoadMeter  metrics.Meter
+	storageDupMeter   metrics.Meter
+	storageSkipMeter  metrics.Meter
+	storageWasteMeter metrics.Meter
+}
+
+// newTriePrefetcher
+func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
+	prefix := triePrefetchMetricsPrefix + namespace
+	p := &triePrefetcher{
+		db:       db,
+		root:     root,
+		fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map
+
+		deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
+		accountLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
+		accountDupMeter:   metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
+		accountSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
+		accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
+		storageLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
+		storageDupMeter:   metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
+		storageSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
+		storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
+	}
+	return p
+}
+
+// close iterates over all the subfetchers, aborts any that were left spinning
+// and reports the stats to the metrics subsystem.
+func (p *triePrefetcher) close() {
+	for _, fetcher := range p.fetchers {
+		fetcher.abort() // safe to do multiple times
+
+		if metrics.Enabled {
+			if fetcher.root == p.root {
+				p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
+				p.accountDupMeter.Mark(int64(fetcher.dups))
+				p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
+
+				for _, key := range fetcher.used {
+					delete(fetcher.seen, string(key))
+				}
+				p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
+			} else {
+				p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
+				p.storageDupMeter.Mark(int64(fetcher.dups))
+				p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
+
+				for _, key := range fetcher.used {
+					delete(fetcher.seen, string(key))
+				}
+				p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
+			}
+		}
+	}
+	// Clear out all fetchers (will crash on a second call, deliberate)
+	p.fetchers = nil
+}
+
+// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
+// already loaded will be copied over, but no goroutines will be started. This
+// is mostly used in the miner which creates a copy of it's actively mutated
+// state to be sealed while it may further mutate the state.
+func (p *triePrefetcher) copy() *triePrefetcher {
+	copy := &triePrefetcher{
+		db:      p.db,
+		root:    p.root,
+		fetches: make(map[common.Hash]Trie), // Active prefetchers use the fetches map
+
+		deliveryMissMeter: p.deliveryMissMeter,
+		accountLoadMeter:  p.accountLoadMeter,
+		accountDupMeter:   p.accountDupMeter,
+		accountSkipMeter:  p.accountSkipMeter,
+		accountWasteMeter: p.accountWasteMeter,
+		storageLoadMeter:  p.storageLoadMeter,
+		storageDupMeter:   p.storageDupMeter,
+		storageSkipMeter:  p.storageSkipMeter,
+		storageWasteMeter: p.storageWasteMeter,
+	}
+	// If the prefetcher is already a copy, duplicate the data
+	if p.fetches != nil {
+		for root, fetch := range p.fetches {
+			copy.fetches[root] = p.db.CopyTrie(fetch)
+		}
+		return copy
+	}
+	// Otherwise we're copying an active fetcher, retrieve the current states
+	for root, fetcher := range p.fetchers {
+		copy.fetches[root] = fetcher.peek()
+	}
+	return copy
+}
+
+// prefetch schedules a batch of trie items to prefetch.
+func (p *triePrefetcher) prefetch(root common.Hash, keys [][]byte) {
+	// If the prefetcher is an inactive one, bail out
+	if p.fetches != nil {
+		return
+	}
+	// Active fetcher, schedule the retrievals
+	fetcher := p.fetchers[root]
+	if fetcher == nil {
+		fetcher = newSubfetcher(p.db, root)
+		p.fetchers[root] = fetcher
+	}
+	fetcher.schedule(keys)
+}
+
+// trie returns the trie matching the root hash, or nil if the prefetcher doesn't
+// have it.
+func (p *triePrefetcher) trie(root common.Hash) Trie {
+	// If the prefetcher is inactive, return from existing deep copies
+	if p.fetches != nil {
+		trie := p.fetches[root]
+		if trie == nil {
+			p.deliveryMissMeter.Mark(1)
+			return nil
+		}
+		return p.db.CopyTrie(trie)
+	}
+	// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
+	fetcher := p.fetchers[root]
+	if fetcher == nil {
+		p.deliveryMissMeter.Mark(1)
+		return nil
+	}
+	// Interrupt the prefetcher if it's by any chance still running and return
+	// a copy of any pre-loaded trie.
+	fetcher.abort() // safe to do multiple times
+
+	trie := fetcher.peek()
+	if trie == nil {
+		p.deliveryMissMeter.Mark(1)
+		return nil
+	}
+	return trie
+}
+
+// used marks a batch of state items used to allow creating statistics as to
+// how useful or wasteful the prefetcher is.
+func (p *triePrefetcher) used(root common.Hash, used [][]byte) {
+	if fetcher := p.fetchers[root]; fetcher != nil {
+		fetcher.used = used
+	}
+}
+
+// subfetcher is a trie fetcher goroutine responsible for pulling entries for a
+// single trie. It is spawned when a new root is encountered and lives until the
+// main prefetcher is paused and either all requested items are processed or if
+// the trie being worked on is retrieved from the prefetcher.
+type subfetcher struct {
+	db   Database    // Database to load trie nodes through
+	root common.Hash // Root hash of the trie to prefetch
+	trie Trie        // Trie being populated with nodes
+
+	tasks [][]byte   // Items queued up for retrieval
+	lock  sync.Mutex // Lock protecting the task queue
+
+	wake chan struct{}  // Wake channel if a new task is scheduled
+	stop chan struct{}  // Channel to interrupt processing
+	term chan struct{}  // Channel to signal iterruption
+	copy chan chan Trie // Channel to request a copy of the current trie
+
+	seen map[string]struct{} // Tracks the entries already loaded
+	dups int                 // Number of duplicate preload tasks
+	used [][]byte            // Tracks the entries used in the end
+}
+
+// newSubfetcher creates a goroutine to prefetch state items belonging to a
+// particular root hash.
+func newSubfetcher(db Database, root common.Hash) *subfetcher {
+	sf := &subfetcher{
+		db:   db,
+		root: root,
+		wake: make(chan struct{}, 1),
+		stop: make(chan struct{}),
+		term: make(chan struct{}),
+		copy: make(chan chan Trie),
+		seen: make(map[string]struct{}),
+	}
+	go sf.loop()
+	return sf
+}
+
+// schedule adds a batch of trie keys to the queue to prefetch.
+func (sf *subfetcher) schedule(keys [][]byte) {
+	// Append the tasks to the current queue
+	sf.lock.Lock()
+	sf.tasks = append(sf.tasks, keys...)
+	sf.lock.Unlock()
+
+	// Notify the prefetcher, it's fine if it's already terminated
+	select {
+	case sf.wake <- struct{}{}:
+	default:
+	}
+}
+
+// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
+// is currently.
+func (sf *subfetcher) peek() Trie {
+	ch := make(chan Trie)
+	select {
+	case sf.copy <- ch:
+		// Subfetcher still alive, return copy from it
+		return <-ch
+
+	case <-sf.term:
+		// Subfetcher already terminated, return a copy directly
+		if sf.trie == nil {
+			return nil
+		}
+		return sf.db.CopyTrie(sf.trie)
+	}
+}
+
+// abort interrupts the subfetcher immediately. It is safe to call abort multiple
+// times but it is not thread safe.
+func (sf *subfetcher) abort() {
+	select {
+	case <-sf.stop:
+	default:
+		close(sf.stop)
+	}
+	<-sf.term
+}
+
+// loop waits for new tasks to be scheduled and keeps loading them until it runs
+// out of tasks or its underlying trie is retrieved for committing.
+func (sf *subfetcher) loop() {
+	// No matter how the loop stops, signal anyone waiting that it's terminated
+	defer close(sf.term)
+
+	// Start by opening the trie and stop processing if it fails
+	trie, err := sf.db.OpenTrie(sf.root)
+	if err != nil {
+		log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
+		return
+	}
+	sf.trie = trie
+
+	// Trie opened successfully, keep prefetching items
+	for {
+		select {
+		case <-sf.wake:
+			// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
+			sf.lock.Lock()
+			tasks := sf.tasks
+			sf.tasks = nil
+			sf.lock.Unlock()
+
+			// Prefetch any tasks until the loop is interrupted
+			for i, task := range tasks {
+				select {
+				case <-sf.stop:
+					// If termination is requested, add any leftover back and return
+					sf.lock.Lock()
+					sf.tasks = append(sf.tasks, tasks[i:]...)
+					sf.lock.Unlock()
+					return
+
+				case ch := <-sf.copy:
+					// Somebody wants a copy of the current trie, grant them
+					ch <- sf.db.CopyTrie(sf.trie)
+
+				default:
+					// No termination request yet, prefetch the next entry
+					taskid := string(task)
+					if _, ok := sf.seen[taskid]; ok {
+						sf.dups++
+					} else {
+						sf.trie.TryGet(task)
+						sf.seen[taskid] = struct{}{}
+					}
+				}
+			}
+
+		case ch := <-sf.copy:
+			// Somebody wants a copy of the current trie, grant them
+			ch <- sf.db.CopyTrie(sf.trie)
+
+		case <-sf.stop:
+			// Termination is requested, abort and leave remaining tasks
+			return
+		}
+	}
+}
diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go
index 2624f38db1..05394321f7 100644
--- a/core/state_prefetcher.go
+++ b/core/state_prefetcher.go
@@ -19,7 +19,6 @@ package core
 import (
 	"sync/atomic"
 
-	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
@@ -50,8 +49,11 @@ func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine conse
 // only goal is to pre-cache transaction signatures and state trie nodes.
 func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *uint32) {
 	var (
-		header  = block.Header()
-		gaspool = new(GasPool).AddGas(block.GasLimit())
+		header       = block.Header()
+		gaspool      = new(GasPool).AddGas(block.GasLimit())
+		blockContext = NewEVMBlockContext(header, p.bc, nil)
+		evm          = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
+		signer       = types.MakeSigner(p.config, header.Number)
 	)
 	// Iterate over and process the individual transactions
 	byzantium := p.config.IsByzantium(block.Number())
@@ -60,9 +62,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
 		if interrupt != nil && atomic.LoadUint32(interrupt) == 1 {
 			return
 		}
-		// Block precaching permitted to continue, execute the transaction
+		// Convert the transaction into an executable message and pre-cache its sender
+		msg, err := tx.AsMessage(signer)
+		if err != nil {
+			return // Also invalid block, bail out
+		}
 		statedb.Prepare(tx.Hash(), block.Hash(), i)
-		if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, header, tx, cfg); err != nil {
+		if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil {
 			return // Ugh, something went horribly wrong, bail out
 		}
 		// If we're pre-byzantium, pre-load trie nodes for the intermediate root
@@ -79,16 +85,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
 // precacheTransaction attempts to apply a transaction to the given state database
 // and uses the input parameters for its environment. The goal is not to execute
 // the transaction successfully, rather to warm up touched data slots.
-func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gaspool *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) error {
-	// Convert the transaction into an executable message and pre-cache its sender
-	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
-	if err != nil {
-		return err
-	}
-	// Create the EVM and execute the transaction
-	context := NewEVMContext(msg, header, bc, author)
-	vm := vm.NewEVM(context, statedb, config, cfg)
-
-	_, _, _, err = ApplyMessage(vm, msg, gaspool)
+func precacheTransaction(msg types.Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error {
+	// Update the evm with the new transaction context.
+	evm.Reset(NewEVMTxContext(msg), statedb)
+	// Add addresses to access list if applicable
+	_, err := ApplyMessage(evm, msg, gaspool)
 	return err
 }
diff --git a/core/state_processor.go b/core/state_processor.go
index c2bbd2d53a..84372082d2 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -17,6 +17,8 @@
 package core
 
 import (
+	"fmt"
+
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/consensus/misc"
@@ -68,6 +70,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
 	}
 	// Handle upgrade build-in system contract code
 	systemcontracts.UpgradeBuildInSystemContract(p.config, block.Number(), statedb)
+
+	blockContext := NewEVMBlockContext(header, p.bc, nil)
+	vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
+
 	// Iterate over and process the individual transactions
 	posa, isPoSA := p.engine.(consensus.PoSA)
 	commonTxs := make([]*types.Transaction, 0, len(block.Transactions()))
@@ -82,11 +88,17 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
 				continue
 			}
 		}
-		statedb.Prepare(tx.Hash(), block.Hash(), i)
-		receipt, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, usedGas, cfg)
+
+		msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number))
 		if err != nil {
 			return nil, nil, 0, err
 		}
+		statedb.Prepare(tx.Hash(), block.Hash(), i)
+		receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, header, tx, usedGas, vmenv)
+		if err != nil {
+			return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
+		}
+
 		commonTxs = append(commonTxs, tx)
 		receipts = append(receipts, receipt)
 	}
@@ -102,49 +114,62 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
 	return receipts, allLogs, *usedGas, nil
 }
 
-// ApplyTransaction attempts to apply a transaction to the given state database
-// and uses the input parameters for its environment. It returns the receipt
-// for the transaction, gas used and an error if the transaction failed,
-// indicating the block was invalid.
-func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
-	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
-	if err != nil {
-		return nil, err
-	}
-	// Create a new context to be used in the EVM environment
-	context := NewEVMContext(msg, header, bc, author)
-	// Create a new environment which holds all relevant information
-	// about the transaction and calling mechanisms.
-	vmenv := vm.NewEVM(context, statedb, config, cfg)
-	// Apply the transaction to the current state (included in the env)
-	_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
+func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
+	// Create a new context to be used in the EVM environment.
+	txContext := NewEVMTxContext(msg)
+	evm.Reset(txContext, statedb)
+
+	// Apply the transaction to the current state (included in the env).
+	result, err := ApplyMessage(evm, msg, gp)
 	if err != nil {
 		return nil, err
 	}
-	// Update the state with pending changes
+
+	// Update the state with pending changes.
 	var root []byte
 	if config.IsByzantium(header.Number) {
 		statedb.Finalise(true)
 	} else {
 		root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
 	}
-	*usedGas += gas
+	*usedGas += result.UsedGas
 
-	// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
-	// based on the eip phase, we're passing whether the root touch-delete accounts.
-	receipt := types.NewReceipt(root, failed, *usedGas)
+	// Create a new receipt for the transaction, storing the intermediate root and gas used
+	// by the tx.
+	receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
+	if result.Failed() {
+		receipt.Status = types.ReceiptStatusFailed
+	} else {
+		receipt.Status = types.ReceiptStatusSuccessful
+	}
 	receipt.TxHash = tx.Hash()
-	receipt.GasUsed = gas
-	// if the transaction created a contract, store the creation address in the receipt.
+	receipt.GasUsed = result.UsedGas
+
+	// If the transaction created a contract, store the creation address in the receipt.
 	if msg.To() == nil {
-		receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
+		receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
 	}
-	// Set the receipt logs and create a bloom for filtering
+
+	// Set the receipt logs and create the bloom filter.
 	receipt.Logs = statedb.GetLogs(tx.Hash())
 	receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
 	receipt.BlockHash = statedb.BlockHash()
 	receipt.BlockNumber = header.Number
 	receipt.TransactionIndex = uint(statedb.TxIndex())
-
 	return receipt, err
 }
+
+// ApplyTransaction attempts to apply a transaction to the given state database
+// and uses the input parameters for its environment. It returns the receipt
+// for the transaction, gas used and an error if the transaction failed,
+// indicating the block was invalid.
+func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
+	msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
+	if err != nil {
+		return nil, err
+	}
+	// Create a new context to be used in the EVM environment
+	blockContext := NewEVMBlockContext(header, bc, author)
+	vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
+	return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
+}
diff --git a/core/state_processor_test.go b/core/state_processor_test.go
new file mode 100644
index 0000000000..5976ecc3d4
--- /dev/null
+++ b/core/state_processor_test.go
@@ -0,0 +1,152 @@
+// 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 .
+
+package core
+
+import (
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
+	"golang.org/x/crypto/sha3"
+)
+
+// TestStateProcessorErrors tests the output from the 'core' errors
+// as defined in core/error.go. These errors are generated when the
+// blockchain imports bad blocks, meaning blocks which have valid headers but
+// contain invalid transactions
+func TestStateProcessorErrors(t *testing.T) {
+	var (
+		signer     = types.HomesteadSigner{}
+		testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		db         = rawdb.NewMemoryDatabase()
+		gspec      = &Genesis{
+			Config: params.TestChainConfig,
+		}
+		genesis       = gspec.MustCommit(db)
+		blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
+	)
+	defer blockchain.Stop()
+	var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
+		tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey)
+		return tx
+	}
+	for i, tt := range []struct {
+		txs  []*types.Transaction
+		want string
+	}{
+		{
+			txs: []*types.Transaction{
+				makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil),
+				makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil),
+			},
+			want: "could not apply tx 1 [0x36bfa6d14f1cd35a1be8cc2322982a595fabc0e799f09c1de3bad7bd5b1f7626]: nonce too low: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 0 state: 1",
+		},
+		{
+			txs: []*types.Transaction{
+				makeTx(100, common.Address{}, big.NewInt(0), params.TxGas, nil, nil),
+			},
+			want: "could not apply tx 0 [0x51cd272d41ef6011d8138e18bf4043797aca9b713c7d39a97563f9bbe6bdbe6f]: nonce too high: address 0x71562b71999873DB5b286dF957af199Ec94617F7, tx: 100 state: 0",
+		},
+		{
+			txs: []*types.Transaction{
+				makeTx(0, common.Address{}, big.NewInt(0), 21000000, nil, nil),
+			},
+			want: "could not apply tx 0 [0x54c58b530824b0bb84b7a98183f08913b5d74e1cebc368515ef3c65edf8eb56a]: gas limit reached",
+		},
+		{
+			txs: []*types.Transaction{
+				makeTx(0, common.Address{}, big.NewInt(1), params.TxGas, nil, nil),
+			},
+			want: "could not apply tx 0 [0x3094b17498940d92b13baccf356ce8bfd6f221e926abc903d642fa1466c5b50e]: insufficient funds for transfer: address 0x71562b71999873DB5b286dF957af199Ec94617F7",
+		},
+		{
+			txs: []*types.Transaction{
+				makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(0xffffff), nil),
+			},
+			want: "could not apply tx 0 [0xaa3f7d86802b1f364576d9071bf231e31d61b392d306831ac9cf706ff5371ce0]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 0 want 352321515000",
+		},
+		{
+			txs: []*types.Transaction{
+				makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, nil, nil),
+				makeTx(1, common.Address{}, big.NewInt(0), params.TxGas, nil, nil),
+				makeTx(2, common.Address{}, big.NewInt(0), params.TxGas, nil, nil),
+				makeTx(3, common.Address{}, big.NewInt(0), params.TxGas-1000, big.NewInt(0), nil),
+			},
+			want: "could not apply tx 3 [0x836fab5882205362680e49b311a20646de03b630920f18ec6ee3b111a2cf6835]: intrinsic gas too low: have 20000, want 21000",
+		},
+		// The last 'core' error is ErrGasUintOverflow: "gas uint64 overflow", but in order to
+		// trigger that one, we'd have to allocate a _huge_ chunk of data, such that the
+		// multiplication len(data) +gas_per_byte overflows uint64. Not testable at the moment
+	} {
+		block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs)
+		_, err := blockchain.InsertChain(types.Blocks{block})
+		if err == nil {
+			t.Fatal("block imported without errors")
+		}
+		if have, want := err.Error(), tt.want; have != want {
+			t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
+		}
+	}
+}
+
+// GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be
+// valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently
+// valid to be considered for import:
+// - valid pow (fake), ancestry, difficulty, gaslimit etc
+func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions) *types.Block {
+	header := &types.Header{
+		ParentHash: parent.Hash(),
+		Coinbase:   parent.Coinbase(),
+		Difficulty: engine.CalcDifficulty(&fakeChainReader{params.TestChainConfig}, parent.Time()+10, &types.Header{
+			Number:     parent.Number(),
+			Time:       parent.Time(),
+			Difficulty: parent.Difficulty(),
+			UncleHash:  parent.UncleHash(),
+		}),
+		GasLimit:  CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()),
+		Number:    new(big.Int).Add(parent.Number(), common.Big1),
+		Time:      parent.Time() + 10,
+		UncleHash: types.EmptyUncleHash,
+	}
+	var receipts []*types.Receipt
+
+	// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
+	// Preferably something unique. So let's use a combo of blocknum + txhash
+	hasher := sha3.NewLegacyKeccak256()
+	hasher.Write(header.Number.Bytes())
+	var cumulativeGas uint64
+	for _, tx := range txs {
+		txh := tx.Hash()
+		hasher.Write(txh[:])
+		receipt := types.NewReceipt(nil, false, cumulativeGas+tx.Gas())
+		receipt.TxHash = tx.Hash()
+		receipt.GasUsed = tx.Gas()
+		receipts = append(receipts, receipt)
+		cumulativeGas += tx.Gas()
+	}
+	header.Root = common.BytesToHash(hasher.Sum(nil))
+	// Assemble and return the final block for sealing
+	return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
+}
diff --git a/core/state_transition.go b/core/state_transition.go
index 360e577522..e71b980752 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -17,21 +17,17 @@
 package core
 
 import (
-	"errors"
+	"fmt"
 	"math"
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
-	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/params"
 )
 
-var (
-	errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas")
-)
-
 /*
 The State Transitioning Model
 
@@ -64,7 +60,6 @@ type StateTransition struct {
 // Message represents a message sent to a contract.
 type Message interface {
 	From() common.Address
-	//FromFrontier() (common.Address, error)
 	To() *common.Address
 
 	GasPrice() *big.Int
@@ -74,13 +69,49 @@ type Message interface {
 	Nonce() uint64
 	CheckNonce() bool
 	Data() []byte
+	AccessList() types.AccessList
+}
+
+// ExecutionResult includes all output after executing given evm
+// message no matter the execution itself is successful or not.
+type ExecutionResult struct {
+	UsedGas    uint64 // Total used gas but include the refunded gas
+	Err        error  // Any error encountered during the execution(listed in core/vm/errors.go)
+	ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
+}
+
+// Unwrap returns the internal evm error which allows us for further
+// analysis outside.
+func (result *ExecutionResult) Unwrap() error {
+	return result.Err
+}
+
+// Failed returns the indicator whether the execution is successful or not
+func (result *ExecutionResult) Failed() bool { return result.Err != nil }
+
+// Return is a helper function to help caller distinguish between revert reason
+// and function return. Return returns the data after execution if no error occurs.
+func (result *ExecutionResult) Return() []byte {
+	if result.Err != nil {
+		return nil
+	}
+	return common.CopyBytes(result.ReturnData)
+}
+
+// Revert returns the concrete revert reason if the execution is aborted by `REVERT`
+// opcode. Note the reason can be nil if no data supplied with revert opcode.
+func (result *ExecutionResult) Revert() []byte {
+	if result.Err != vm.ErrExecutionReverted {
+		return nil
+	}
+	return common.CopyBytes(result.ReturnData)
 }
 
 // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
-func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
 	// Set the starting gas for the raw transaction
 	var gas uint64
-	if contractCreation && isHomestead {
+	if isContractCreation && isHomestead {
 		gas = params.TxGasContractCreation
 	} else {
 		gas = params.TxGas
@@ -100,16 +131,20 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo
 			nonZeroGas = params.TxDataNonZeroGasEIP2028
 		}
 		if (math.MaxUint64-gas)/nonZeroGas < nz {
-			return 0, vm.ErrOutOfGas
+			return 0, ErrGasUintOverflow
 		}
 		gas += nz * nonZeroGas
 
 		z := uint64(len(data)) - nz
 		if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
-			return 0, vm.ErrOutOfGas
+			return 0, ErrGasUintOverflow
 		}
 		gas += z * params.TxDataZeroGas
 	}
+	if accessList != nil {
+		gas += uint64(len(accessList)) * params.TxAccessListAddressGas
+		gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
+	}
 	return gas, nil
 }
 
@@ -133,7 +168,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
 // the gas used (which includes gas refunds) and an error if it failed. An error always
 // indicates a core error meaning that the message would always fail for that particular
 // state and would never be accepted within a block.
-func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
+func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
 	return NewStateTransition(evm, msg, gp).TransitionDb()
 }
 
@@ -145,19 +180,10 @@ func (st *StateTransition) to() common.Address {
 	return *st.msg.To()
 }
 
-func (st *StateTransition) useGas(amount uint64) error {
-	if st.gas < amount {
-		return vm.ErrOutOfGas
-	}
-	st.gas -= amount
-
-	return nil
-}
-
 func (st *StateTransition) buyGas() error {
 	mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
-	if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
-		return errInsufficientBalanceForGas
+	if have, want := st.state.GetBalance(st.msg.From()), mgval; have.Cmp(want) < 0 {
+		return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want)
 	}
 	if err := st.gp.SubGas(st.msg.Gas()); err != nil {
 		return err
@@ -172,70 +198,96 @@ func (st *StateTransition) buyGas() error {
 func (st *StateTransition) preCheck() error {
 	// Make sure this transaction's nonce is correct.
 	if st.msg.CheckNonce() {
-		nonce := st.state.GetNonce(st.msg.From())
-		if nonce < st.msg.Nonce() {
-			return ErrNonceTooHigh
-		} else if nonce > st.msg.Nonce() {
-			return ErrNonceTooLow
+		stNonce := st.state.GetNonce(st.msg.From())
+		if msgNonce := st.msg.Nonce(); stNonce < msgNonce {
+			return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
+				st.msg.From().Hex(), msgNonce, stNonce)
+		} else if stNonce > msgNonce {
+			return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
+				st.msg.From().Hex(), msgNonce, stNonce)
 		}
 	}
 	return st.buyGas()
 }
 
 // TransitionDb will transition the state by applying the current message and
-// returning the result including the used gas. It returns an error if failed.
-// An error indicates a consensus issue.
-func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
-	if err = st.preCheck(); err != nil {
-		return
+// returning the evm execution result with following fields.
+//
+// - used gas:
+//      total gas used (including gas being refunded)
+// - returndata:
+//      the returned data from evm
+// - concrete execution error:
+//      various **EVM** error which aborts the execution,
+//      e.g. ErrOutOfGas, ErrExecutionReverted
+//
+// However if any consensus issue encountered, return the error directly with
+// nil evm execution result.
+func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
+	// First check this message satisfies all consensus rules before
+	// applying the message. The rules include these clauses
+	//
+	// 1. the nonce of the message caller is correct
+	// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
+	// 3. the amount of gas required is available in the block
+	// 4. the purchased gas is enough to cover intrinsic usage
+	// 5. there is no overflow when calculating intrinsic gas
+	// 6. caller has enough balance to cover asset transfer for **topmost** call
+
+	// Check clauses 1-3, buy gas if everything is correct
+	if err := st.preCheck(); err != nil {
+		return nil, err
 	}
 	msg := st.msg
 	sender := vm.AccountRef(msg.From())
-	homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
-	istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
+	homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
+	istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber)
 	contractCreation := msg.To() == nil
 
-	// Pay intrinsic gas
-	gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
+	// Check clauses 4-5, subtract intrinsic gas if everything is correct
+	gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul)
 	if err != nil {
-		return nil, 0, false, err
+		return nil, err
+	}
+	if st.gas < gas {
+		return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
 	}
-	if err = st.useGas(gas); err != nil {
-		return nil, 0, false, err
+	st.gas -= gas
+
+	// Check clause 6
+	if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) {
+		return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex())
 	}
 
+	// Set up the initial access list.
+	if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsBerlin {
+		st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
+	}
 	var (
-		evm = st.evm
-		// vm errors do not effect consensus and are therefor
-		// not assigned to err, except for insufficient balance
-		// error.
-		vmerr error
+		ret   []byte
+		vmerr error // vm errors do not effect consensus and are therefore not assigned to err
 	)
 	if contractCreation {
-		ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
+		ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
 	} else {
 		// Increment the nonce for the next transaction
 		st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
-		ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
-	}
-	if vmerr != nil {
-		log.Debug("VM returned with error", "err", vmerr)
-		// The only possible consensus-error would be if there wasn't
-		// sufficient balance to make the transfer happen. The first
-		// balance transfer may never fail.
-		if vmerr == vm.ErrInsufficientBalance {
-			return nil, 0, false, vmerr
-		}
+		ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
 	}
 	st.refundGas()
 
 	// consensus engine is parlia
-	if evm.ChainConfig().Parlia != nil {
+	if st.evm.ChainConfig().Parlia != nil {
 		st.state.AddBalance(consensus.SystemAddress, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
 	} else {
-		st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
+		st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
 	}
-	return ret, st.gasUsed(), vmerr != nil, err
+
+	return &ExecutionResult{
+		UsedGas:    st.gasUsed(),
+		Err:        vmerr,
+		ReturnData: ret,
+	}, nil
 }
 
 func (st *StateTransition) refundGas() {
diff --git a/core/tx_list.go b/core/tx_list.go
index 6b22cbbebe..894640d570 100644
--- a/core/tx_list.go
+++ b/core/tx_list.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/log"
 )
 
 // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for
@@ -99,7 +98,30 @@ func (m *txSortedMap) Forward(threshold uint64) types.Transactions {
 
 // Filter iterates over the list of transactions and removes all of them for which
 // the specified function evaluates to true.
+// Filter, as opposed to 'filter', re-initialises the heap after the operation is done.
+// If you want to do several consecutive filterings, it's therefore better to first
+// do a .filter(func1) followed by .Filter(func2) or reheap()
 func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transactions {
+	removed := m.filter(filter)
+	// If transactions were removed, the heap and cache are ruined
+	if len(removed) > 0 {
+		m.reheap()
+	}
+	return removed
+}
+
+func (m *txSortedMap) reheap() {
+	*m.index = make([]uint64, 0, len(m.items))
+	for nonce := range m.items {
+		*m.index = append(*m.index, nonce)
+	}
+	heap.Init(m.index)
+	m.cache = nil
+}
+
+// filter is identical to Filter, but **does not** regenerate the heap. This method
+// should only be used if followed immediately by a call to Filter or reheap()
+func (m *txSortedMap) filter(filter func(*types.Transaction) bool) types.Transactions {
 	var removed types.Transactions
 
 	// Collect all the transactions to filter out
@@ -109,14 +131,7 @@ func (m *txSortedMap) Filter(filter func(*types.Transaction) bool) types.Transac
 			delete(m.items, nonce)
 		}
 	}
-	// If transactions were removed, the heap and cache are ruined
 	if len(removed) > 0 {
-		*m.index = make([]uint64, 0, len(m.items))
-		for nonce := range m.items {
-			*m.index = append(*m.index, nonce)
-		}
-		heap.Init(m.index)
-
 		m.cache = nil
 	}
 	return removed
@@ -197,10 +212,7 @@ func (m *txSortedMap) Len() int {
 	return len(m.items)
 }
 
-// Flatten creates a nonce-sorted slice of transactions based on the loosely
-// sorted internal representation. The result of the sorting is cached in case
-// it's requested again before any modifications are made to the contents.
-func (m *txSortedMap) Flatten() types.Transactions {
+func (m *txSortedMap) flatten() types.Transactions {
 	// If the sorting was not cached yet, create and cache it
 	if m.cache == nil {
 		m.cache = make(types.Transactions, 0, len(m.items))
@@ -209,12 +221,27 @@ func (m *txSortedMap) Flatten() types.Transactions {
 		}
 		sort.Sort(types.TxByNonce(m.cache))
 	}
+	return m.cache
+}
+
+// Flatten creates a nonce-sorted slice of transactions based on the loosely
+// sorted internal representation. The result of the sorting is cached in case
+// it's requested again before any modifications are made to the contents.
+func (m *txSortedMap) Flatten() types.Transactions {
 	// Copy the cache to prevent accidental modifications
-	txs := make(types.Transactions, len(m.cache))
-	copy(txs, m.cache)
+	cache := m.flatten()
+	txs := make(types.Transactions, len(cache))
+	copy(txs, cache)
 	return txs
 }
 
+// LastElement returns the last element of a flattened list, thus, the
+// transaction with the highest nonce
+func (m *txSortedMap) LastElement() *types.Transaction {
+	cache := m.flatten()
+	return cache[len(cache)-1]
+}
+
 // txList is a "list" of transactions belonging to an account, sorted by account
 // nonce. The same type can be used both for storing contiguous transactions for
 // the executable/pending queue; and for storing gapped transactions for the non-
@@ -252,11 +279,15 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
 	// If there's an older better transaction, abort
 	old := l.txs.Get(tx.Nonce())
 	if old != nil {
-		threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
+		// threshold = oldGP * (100 + priceBump) / 100
+		a := big.NewInt(100 + int64(priceBump))
+		a = a.Mul(a, old.GasPrice())
+		b := big.NewInt(100)
+		threshold := a.Div(a, b)
 		// Have to ensure that the new gas price is higher than the old gas
 		// price as well as checking the percentage threshold to ensure that
 		// this is accurate for low (Wei-level) gas price replacements
-		if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
+		if old.GasPriceCmp(tx) >= 0 || tx.GasPriceIntCmp(threshold) < 0 {
 			return false, nil
 		}
 	}
@@ -296,20 +327,25 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions
 	l.gascap = gasLimit
 
 	// Filter out all the transactions above the account's funds
-	removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost().Cmp(costLimit) > 0 || tx.Gas() > gasLimit })
+	removed := l.txs.Filter(func(tx *types.Transaction) bool {
+		return tx.Gas() > gasLimit || tx.Cost().Cmp(costLimit) > 0
+	})
 
-	// If the list was strict, filter anything above the lowest nonce
+	if len(removed) == 0 {
+		return nil, nil
+	}
 	var invalids types.Transactions
-
-	if l.strict && len(removed) > 0 {
+	// If the list was strict, filter anything above the lowest nonce
+	if l.strict {
 		lowest := uint64(math.MaxUint64)
 		for _, tx := range removed {
 			if nonce := tx.Nonce(); lowest > nonce {
 				lowest = nonce
 			}
 		}
-		invalids = l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
+		invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
 	}
+	l.txs.reheap()
 	return removed, invalids
 }
 
@@ -363,6 +399,12 @@ func (l *txList) Flatten() types.Transactions {
 	return l.txs.Flatten()
 }
 
+// LastElement returns the last element of a flattened list, thus, the
+// transaction with the highest nonce
+func (l *txList) LastElement() *types.Transaction {
+	return l.txs.LastElement()
+}
+
 // priceHeap is a heap.Interface implementation over transactions for retrieving
 // price-sorted transactions to discard when the pool fills up.
 type priceHeap []*types.Transaction
@@ -372,7 +414,7 @@ func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
 
 func (h priceHeap) Less(i, j int) bool {
 	// Sort primarily by price, returning the cheaper one
-	switch h[i].GasPrice().Cmp(h[j].GasPrice()) {
+	switch h[i].GasPriceCmp(h[j]) {
 	case -1:
 		return true
 	case 1:
@@ -390,29 +432,35 @@ func (h *priceHeap) Pop() interface{} {
 	old := *h
 	n := len(old)
 	x := old[n-1]
+	old[n-1] = nil
 	*h = old[0 : n-1]
 	return x
 }
 
 // txPricedList is a price-sorted heap to allow operating on transactions pool
-// contents in a price-incrementing way.
+// contents in a price-incrementing way. It's built opon the all transactions
+// in txpool but only interested in the remote part. It means only remote transactions
+// will be considered for tracking, sorting, eviction, etc.
 type txPricedList struct {
-	all    *txLookup  // Pointer to the map of all transactions
-	items  *priceHeap // Heap of prices of all the stored transactions
-	stales int        // Number of stale price points to (re-heap trigger)
+	all     *txLookup  // Pointer to the map of all transactions
+	remotes *priceHeap // Heap of prices of all the stored **remote** transactions
+	stales  int        // Number of stale price points to (re-heap trigger)
 }
 
 // newTxPricedList creates a new price-sorted transaction heap.
 func newTxPricedList(all *txLookup) *txPricedList {
 	return &txPricedList{
-		all:   all,
-		items: new(priceHeap),
+		all:     all,
+		remotes: new(priceHeap),
 	}
 }
 
 // Put inserts a new transaction into the heap.
-func (l *txPricedList) Put(tx *types.Transaction) {
-	heap.Push(l.items, tx)
+func (l *txPricedList) Put(tx *types.Transaction, local bool) {
+	if local {
+		return
+	}
+	heap.Push(l.remotes, tx)
 }
 
 // Removed notifies the prices transaction list that an old transaction dropped
@@ -421,100 +469,95 @@ func (l *txPricedList) Put(tx *types.Transaction) {
 func (l *txPricedList) Removed(count int) {
 	// Bump the stale counter, but exit if still too low (< 25%)
 	l.stales += count
-	if l.stales <= len(*l.items)/4 {
+	if l.stales <= len(*l.remotes)/4 {
 		return
 	}
 	// Seems we've reached a critical number of stale transactions, reheap
-	reheap := make(priceHeap, 0, l.all.Count())
-
-	l.stales, l.items = 0, &reheap
-	l.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
-		*l.items = append(*l.items, tx)
-		return true
-	})
-	heap.Init(l.items)
+	l.Reheap()
 }
 
 // Cap finds all the transactions below the given price threshold, drops them
 // from the priced list and returns them for further removal from the entire pool.
-func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions {
+//
+// Note: only remote transactions will be considered for eviction.
+func (l *txPricedList) Cap(threshold *big.Int) types.Transactions {
 	drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
-	save := make(types.Transactions, 0, 64)  // Local underpriced transactions to keep
-
-	for len(*l.items) > 0 {
+	for len(*l.remotes) > 0 {
 		// Discard stale transactions if found during cleanup
-		tx := heap.Pop(l.items).(*types.Transaction)
-		if l.all.Get(tx.Hash()) == nil {
+		cheapest := (*l.remotes)[0]
+		if l.all.GetRemote(cheapest.Hash()) == nil { // Removed or migrated
+			heap.Pop(l.remotes)
 			l.stales--
 			continue
 		}
 		// Stop the discards if we've reached the threshold
-		if tx.GasPrice().Cmp(threshold) >= 0 {
-			save = append(save, tx)
+		if cheapest.GasPriceIntCmp(threshold) >= 0 {
 			break
 		}
-		// Non stale transaction found, discard unless local
-		if local.containsTx(tx) {
-			save = append(save, tx)
-		} else {
-			drop = append(drop, tx)
-		}
-	}
-	for _, tx := range save {
-		heap.Push(l.items, tx)
+		heap.Pop(l.remotes)
+		drop = append(drop, cheapest)
 	}
 	return drop
 }
 
 // Underpriced checks whether a transaction is cheaper than (or as cheap as) the
-// lowest priced transaction currently being tracked.
-func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) bool {
-	// Local transactions cannot be underpriced
-	if local.containsTx(tx) {
-		return false
-	}
+// lowest priced (remote) transaction currently being tracked.
+func (l *txPricedList) Underpriced(tx *types.Transaction) bool {
 	// Discard stale price points if found at the heap start
-	for len(*l.items) > 0 {
-		head := []*types.Transaction(*l.items)[0]
-		if l.all.Get(head.Hash()) == nil {
+	for len(*l.remotes) > 0 {
+		head := []*types.Transaction(*l.remotes)[0]
+		if l.all.GetRemote(head.Hash()) == nil { // Removed or migrated
 			l.stales--
-			heap.Pop(l.items)
+			heap.Pop(l.remotes)
 			continue
 		}
 		break
 	}
 	// Check if the transaction is underpriced or not
-	if len(*l.items) == 0 {
-		log.Error("Pricing query for empty pool") // This cannot happen, print to catch programming errors
-		return false
+	if len(*l.remotes) == 0 {
+		return false // There is no remote transaction at all.
 	}
-	cheapest := []*types.Transaction(*l.items)[0]
-	return cheapest.GasPrice().Cmp(tx.GasPrice()) >= 0
+	// If the remote transaction is even cheaper than the
+	// cheapest one tracked locally, reject it.
+	cheapest := []*types.Transaction(*l.remotes)[0]
+	return cheapest.GasPriceCmp(tx) >= 0
 }
 
 // Discard finds a number of most underpriced transactions, removes them from the
 // priced list and returns them for further removal from the entire pool.
-func (l *txPricedList) Discard(slots int, local *accountSet) types.Transactions {
+//
+// Note local transaction won't be considered for eviction.
+func (l *txPricedList) Discard(slots int, force bool) (types.Transactions, bool) {
 	drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop
-	save := make(types.Transactions, 0, 64)    // Local underpriced transactions to keep
-
-	for len(*l.items) > 0 && slots > 0 {
+	for len(*l.remotes) > 0 && slots > 0 {
 		// Discard stale transactions if found during cleanup
-		tx := heap.Pop(l.items).(*types.Transaction)
-		if l.all.Get(tx.Hash()) == nil {
+		tx := heap.Pop(l.remotes).(*types.Transaction)
+		if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated
 			l.stales--
 			continue
 		}
-		// Non stale transaction found, discard unless local
-		if local.containsTx(tx) {
-			save = append(save, tx)
-		} else {
-			drop = append(drop, tx)
-			slots -= numSlots(tx)
+		// Non stale transaction found, discard it
+		drop = append(drop, tx)
+		slots -= numSlots(tx)
+	}
+	// If we still can't make enough room for the new transaction
+	if slots > 0 && !force {
+		for _, tx := range drop {
+			heap.Push(l.remotes, tx)
 		}
+		return nil, false
 	}
-	for _, tx := range save {
-		heap.Push(l.items, tx)
-	}
-	return drop
+	return drop, true
+}
+
+// Reheap forcibly rebuilds the heap based on the current remote transaction set.
+func (l *txPricedList) Reheap() {
+	reheap := make(priceHeap, 0, l.all.RemoteCount())
+
+	l.stales, l.remotes = 0, &reheap
+	l.all.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
+		*l.remotes = append(*l.remotes, tx)
+		return true
+	}, false, true) // Only iterate remotes
+	heap.Init(l.remotes)
 }
diff --git a/core/tx_list_test.go b/core/tx_list_test.go
index d579f501af..3a5842d2e8 100644
--- a/core/tx_list_test.go
+++ b/core/tx_list_test.go
@@ -17,6 +17,7 @@
 package core
 
 import (
+	"math/big"
 	"math/rand"
 	"testing"
 
@@ -49,3 +50,21 @@ func TestStrictTxListAdd(t *testing.T) {
 		}
 	}
 }
+
+func BenchmarkTxListAdd(t *testing.B) {
+	// Generate a list of transactions to insert
+	key, _ := crypto.GenerateKey()
+
+	txs := make(types.Transactions, 100000)
+	for i := 0; i < len(txs); i++ {
+		txs[i] = transaction(uint64(i), 0, key)
+	}
+	// Insert the transactions in a random order
+	list := newTxList(true)
+	priceLimit := big.NewInt(int64(DefaultTxPoolConfig.PriceLimit))
+	t.ResetTimer()
+	for _, v := range rand.Perm(len(txs)) {
+		list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
+		list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
+	}
+}
diff --git a/core/tx_pool.go b/core/tx_pool.go
index f35e1d8d7c..5db1d3df32 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -59,31 +59,23 @@ var (
 	// ErrInvalidSender is returned if the transaction contains an invalid signature.
 	ErrInvalidSender = errors.New("invalid sender")
 
-	// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
-	// one present in the local chain.
-	ErrNonceTooLow = errors.New("nonce too low")
-
 	// ErrUnderpriced is returned if a transaction's gas price is below the minimum
 	// configured for the transaction pool.
 	ErrUnderpriced = errors.New("transaction underpriced")
 
+	// ErrTxPoolOverflow is returned if the transaction pool is full and can't accpet
+	// another remote transaction.
+	ErrTxPoolOverflow = errors.New("txpool is full")
+
 	// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
 	// with a different one without the required price bump.
 	ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
 
-	// ErrInsufficientFunds is returned if the total cost of executing a transaction
-	// is higher than the balance of the user's account.
-	ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
-
-	// ErrIntrinsicGas is returned if the transaction is specified to use less gas
-	// than required to start the invocation.
-	ErrIntrinsicGas = errors.New("intrinsic gas too low")
-
 	// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
 	// maximum allowance of the current block.
 	ErrGasLimit = errors.New("exceeds block gas limit")
 
-	// ErrNegativeValue is a sanity error to ensure noone is able to specify a
+	// ErrNegativeValue is a sanity error to ensure no one is able to specify a
 	// transaction with a negative value.
 	ErrNegativeValue = errors.New("negative value")
 
@@ -110,12 +102,14 @@ var (
 	queuedReplaceMeter   = metrics.NewRegisteredMeter("txpool/queued/replace", nil)
 	queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting
 	queuedNofundsMeter   = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil)   // Dropped due to out-of-funds
+	queuedEvictionMeter  = metrics.NewRegisteredMeter("txpool/queued/eviction", nil)  // Dropped due to lifetime
 
 	// General tx metrics
 	knownTxMeter       = metrics.NewRegisteredMeter("txpool/known", nil)
 	validTxMeter       = metrics.NewRegisteredMeter("txpool/valid", nil)
 	invalidTxMeter     = metrics.NewRegisteredMeter("txpool/invalid", nil)
 	underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil)
+	overflowedTxMeter  = metrics.NewRegisteredMeter("txpool/overflowed", nil)
 
 	pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil)
 	queuedGauge  = metrics.NewRegisteredGauge("txpool/queued", nil)
@@ -235,6 +229,7 @@ type TxPool struct {
 	mu          sync.RWMutex
 
 	istanbul bool // Fork indicator whether we are in the istanbul stage.
+	eip2718  bool // Fork indicator whether we are using EIP-2718 type transactions.
 
 	currentState  *state.StateDB // Current state in the blockchain head
 	pendingNonces *txNoncer      // Pending state tracking virtual nonces
@@ -274,7 +269,7 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
 		config:          config,
 		chainconfig:     chainconfig,
 		chain:           chain,
-		signer:          types.NewEIP155Signer(chainconfig.ChainID),
+		signer:          types.LatestSigner(chainconfig),
 		pending:         make(map[common.Address]*txList),
 		queue:           make(map[common.Address]*txList),
 		beats:           make(map[common.Address]time.Time),
@@ -374,9 +369,11 @@ func (pool *TxPool) loop() {
 				}
 				// Any non-locals old enough should be removed
 				if time.Since(pool.beats[addr]) > pool.config.Lifetime {
-					for _, tx := range pool.queue[addr].Flatten() {
+					list := pool.queue[addr].Flatten()
+					for _, tx := range list {
 						pool.removeTx(tx.Hash(), true)
 					}
+					queuedEvictionMeter.Mark(int64(len(list)))
 				}
 			}
 			pool.mu.Unlock()
@@ -430,7 +427,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
 	defer pool.mu.Unlock()
 
 	pool.gasPrice = price
-	for _, tx := range pool.priced.Cap(price, pool.locals) {
+	for _, tx := range pool.priced.Cap(price) {
 		pool.removeTx(tx.Hash(), false)
 	}
 	log.Info("Transaction pool price threshold updated", "price", price)
@@ -526,6 +523,10 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
 // validateTx checks whether a transaction is valid according to the consensus
 // rules and adheres to some heuristic limits of the local node (price and size).
 func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
+	// Accept only legacy transactions until EIP-2718/2930 activates.
+	if !pool.eip2718 && tx.Type() != types.LegacyTxType {
+		return ErrTxTypeNotSupported
+	}
 	// Reject transactions over defined size to prevent DOS attacks
 	if uint64(tx.Size()) > txMaxSize {
 		return ErrOversizedData
@@ -539,14 +540,13 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
 	if pool.currentMaxGas < tx.Gas() {
 		return ErrGasLimit
 	}
-	// Make sure the transaction is signed properly
+	// Make sure the transaction is signed properly.
 	from, err := types.Sender(pool.signer, tx)
 	if err != nil {
 		return ErrInvalidSender
 	}
 	// Drop non-local transactions under our own minimal accepted gas price
-	local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
-	if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
+	if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 {
 		return ErrUnderpriced
 	}
 	// Ensure the transaction adheres to nonce ordering
@@ -559,7 +559,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
 		return ErrInsufficientFunds
 	}
 	// Ensure the transaction has more gas than the basic tx fee.
-	intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul)
+	intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
 	if err != nil {
 		return err
 	}
@@ -584,22 +584,36 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
 		knownTxMeter.Mark(1)
 		return false, ErrAlreadyKnown
 	}
+	// Make the local flag. If it's from local source or it's from the network but
+	// the sender is marked as local previously, treat it as the local transaction.
+	isLocal := local || pool.locals.containsTx(tx)
+
 	// If the transaction fails basic validation, discard it
-	if err := pool.validateTx(tx, local); err != nil {
+	if err := pool.validateTx(tx, isLocal); err != nil {
 		log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
 		invalidTxMeter.Mark(1)
 		return false, err
 	}
 	// If the transaction pool is full, discard underpriced transactions
-	if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
+	if uint64(pool.all.Count()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue {
 		// If the new transaction is underpriced, don't accept it
-		if !local && pool.priced.Underpriced(tx, pool.locals) {
+		if !isLocal && pool.priced.Underpriced(tx) {
 			log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
 			underpricedTxMeter.Mark(1)
 			return false, ErrUnderpriced
 		}
-		// New transaction is better than our worse ones, make room for it
-		drop := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), pool.locals)
+		// New transaction is better than our worse ones, make room for it.
+		// If it's a local transaction, forcibly discard all available transactions.
+		// Otherwise if we can't make enough room for new one, abort the operation.
+		drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal)
+
+		// Special case, we still can't make the room for the new remote one.
+		if !isLocal && !success {
+			log.Trace("Discarding overflown transaction", "hash", hash)
+			overflowedTxMeter.Mark(1)
+			return false, ErrTxPoolOverflow
+		}
+		// Kick out the underpriced remote transactions.
 		for _, tx := range drop {
 			log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
 			underpricedTxMeter.Mark(1)
@@ -621,26 +635,28 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
 			pool.priced.Removed(1)
 			pendingReplaceMeter.Mark(1)
 		}
-		pool.all.Add(tx)
-		pool.priced.Put(tx)
+		pool.all.Add(tx, isLocal)
+		pool.priced.Put(tx, isLocal)
 		pool.journalTx(from, tx)
 		pool.queueTxEvent(tx)
 		log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
+
+		// Successful promotion, bump the heartbeat
+		pool.beats[from] = time.Now()
 		return old != nil, nil
 	}
 	// New transaction isn't replacing a pending one, push into queue
-	replaced, err = pool.enqueueTx(hash, tx)
+	replaced, err = pool.enqueueTx(hash, tx, isLocal, true)
 	if err != nil {
 		return false, err
 	}
 	// Mark local addresses and journal local transactions
-	if local {
-		if !pool.locals.contains(from) {
-			log.Info("Setting new local account", "address", from)
-			pool.locals.add(from)
-		}
+	if local && !pool.locals.contains(from) {
+		log.Info("Setting new local account", "address", from)
+		pool.locals.add(from)
+		pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time.
 	}
-	if local || pool.locals.contains(from) {
+	if isLocal {
 		localGauge.Inc(1)
 	}
 	pool.journalTx(from, tx)
@@ -652,7 +668,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
 // enqueueTx inserts a new transaction into the non-executable transaction queue.
 //
 // Note, this method assumes the pool lock is held!
-func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) {
+func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) {
 	// Try to insert the transaction into the future queue
 	from, _ := types.Sender(pool.signer, tx) // already validated
 	if pool.queue[from] == nil {
@@ -673,9 +689,18 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er
 		// Nothing was replaced, bump the queued counter
 		queuedGauge.Inc(1)
 	}
-	if pool.all.Get(hash) == nil {
-		pool.all.Add(tx)
-		pool.priced.Put(tx)
+	// If the transaction isn't in lookup set but it's expected to be there,
+	// show the error log.
+	if pool.all.Get(hash) == nil && !addAll {
+		log.Error("Missing transaction in lookup set, please report the issue", "hash", hash)
+	}
+	if addAll {
+		pool.all.Add(tx, local)
+		pool.priced.Put(tx, local)
+	}
+	// If we never record the heartbeat, do it right now.
+	if _, exist := pool.beats[from]; !exist {
+		pool.beats[from] = time.Now()
 	}
 	return old != nil, nil
 }
@@ -708,7 +733,6 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
 		// An older transaction was better, discard this
 		pool.all.Remove(hash)
 		pool.priced.Removed(1)
-
 		pendingDiscardMeter.Mark(1)
 		return false
 	}
@@ -716,21 +740,16 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
 	if old != nil {
 		pool.all.Remove(old.Hash())
 		pool.priced.Removed(1)
-
 		pendingReplaceMeter.Mark(1)
 	} else {
 		// Nothing was replaced, bump the pending counter
 		pendingGauge.Inc(1)
 	}
-	// Failsafe to work around direct pending inserts (tests)
-	if pool.all.Get(hash) == nil {
-		pool.all.Add(tx)
-		pool.priced.Put(tx)
-	}
 	// Set the potentially new pending nonce and notify any subsystems of the new tx
-	pool.beats[addr] = time.Now()
 	pool.pendingNonces.set(addr, tx.Nonce()+1)
 
+	// Successful promotion, bump the heartbeat
+	pool.beats[addr] = time.Now()
 	return true
 }
 
@@ -793,16 +812,22 @@ func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
 			knownTxMeter.Mark(1)
 			continue
 		}
+		// Exclude transactions with invalid signatures as soon as
+		// possible and cache senders in transactions before
+		// obtaining lock
+		_, err := types.Sender(pool.signer, tx)
+		if err != nil {
+			errs[i] = ErrInvalidSender
+			invalidTxMeter.Mark(1)
+			continue
+		}
 		// Accumulate all unknown transactions for deeper processing
 		news = append(news, tx)
 	}
 	if len(news) == 0 {
 		return errs
 	}
-	// Cache senders in transactions before obtaining lock (pool.signer is immutable)
-	for _, tx := range news {
-		types.Sender(pool.signer, tx)
-	}
+
 	// Process all the new transaction and merge any errors into the original slice
 	pool.mu.Lock()
 	newErrs, dirtyAddrs := pool.addTxsLocked(news, local)
@@ -898,11 +923,11 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
 			// If no more pending transactions are left, remove the list
 			if pending.Empty() {
 				delete(pool.pending, addr)
-				delete(pool.beats, addr)
 			}
 			// Postpone any invalidated transactions
 			for _, tx := range invalids {
-				pool.enqueueTx(tx.Hash(), tx)
+				// Internal shuffle shouldn't touch the lookup set.
+				pool.enqueueTx(tx.Hash(), tx, false, false)
 			}
 			// Update the account nonce if needed
 			pool.pendingNonces.setIfLower(addr, tx.Nonce())
@@ -919,11 +944,12 @@ func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
 		}
 		if future.Empty() {
 			delete(pool.queue, addr)
+			delete(pool.beats, addr)
 		}
 	}
 }
 
-// requestPromoteExecutables requests a pool reset to the new head block.
+// requestReset requests a pool reset to the new head block.
 // The returned channel is closed when the reset has occurred.
 func (pool *TxPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} {
 	select {
@@ -1030,7 +1056,10 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
 	defer close(done)
 
 	var promoteAddrs []common.Address
-	if dirtyAccounts != nil {
+	if dirtyAccounts != nil && reset == nil {
+		// Only dirty accounts need to be promoted, unless we're resetting.
+		// For resets, all addresses in the tx queue will be promoted and
+		// the flatten operation can be avoided.
 		promoteAddrs = dirtyAccounts.flatten()
 	}
 	pool.mu.Lock()
@@ -1046,20 +1075,14 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
 			}
 		}
 		// Reset needs promote for all addresses
-		promoteAddrs = promoteAddrs[:0]
+		promoteAddrs = make([]common.Address, 0, len(pool.queue))
 		for addr := range pool.queue {
 			promoteAddrs = append(promoteAddrs, addr)
 		}
 	}
 	// Check for pending transactions for every account that sent new ones
 	promoted := pool.promoteExecutables(promoteAddrs)
-	for _, tx := range promoted {
-		addr, _ := types.Sender(pool.signer, tx)
-		if _, ok := events[addr]; !ok {
-			events[addr] = newTxSortedMap()
-		}
-		events[addr].Put(tx)
-	}
+
 	// If a new block appeared, validate the pool of pending transactions. This will
 	// remove any transaction that has been included in the block or was invalidated
 	// because of another transaction (e.g. higher gas price).
@@ -1072,12 +1095,19 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
 
 	// Update all accounts to the latest known pending nonce
 	for addr, list := range pool.pending {
-		txs := list.Flatten() // Heavy but will be cached and is needed by the miner anyway
-		pool.pendingNonces.set(addr, txs[len(txs)-1].Nonce()+1)
+		highestPending := list.LastElement()
+		pool.pendingNonces.set(addr, highestPending.Nonce()+1)
 	}
 	pool.mu.Unlock()
 
 	// Notify subsystems for newly added transactions
+	for _, tx := range promoted {
+		addr, _ := types.Sender(pool.signer, tx)
+		if _, ok := events[addr]; !ok {
+			events[addr] = newTxSortedMap()
+		}
+		events[addr].Put(tx)
+	}
 	if len(events) > 0 {
 		var txs []*types.Transaction
 		for _, set := range events {
@@ -1112,44 +1142,45 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
 				// head from the chain.
 				// If that is the case, we don't have the lost transactions any more, and
 				// there's nothing to add
-				if newNum < oldNum {
-					// If the reorg ended up on a lower number, it's indicative of setHead being the cause
-					log.Debug("Skipping transaction reset caused by setHead",
-						"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
-				} else {
+				if newNum >= oldNum {
 					// If we reorged to a same or higher number, then it's not a case of setHead
 					log.Warn("Transaction pool reset with missing oldhead",
 						"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
-				}
-				return
-			}
-			for rem.NumberU64() > add.NumberU64() {
-				discarded = append(discarded, rem.Transactions()...)
-				if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
-					log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
 					return
 				}
-			}
-			for add.NumberU64() > rem.NumberU64() {
-				included = append(included, add.Transactions()...)
-				if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
-					log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
-					return
+				// If the reorg ended up on a lower number, it's indicative of setHead being the cause
+				log.Debug("Skipping transaction reset caused by setHead",
+					"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
+				// We still need to update the current state s.th. the lost transactions can be readded by the user
+			} else {
+				for rem.NumberU64() > add.NumberU64() {
+					discarded = append(discarded, rem.Transactions()...)
+					if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
+						log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
+						return
+					}
 				}
-			}
-			for rem.Hash() != add.Hash() {
-				discarded = append(discarded, rem.Transactions()...)
-				if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
-					log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
-					return
+				for add.NumberU64() > rem.NumberU64() {
+					included = append(included, add.Transactions()...)
+					if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
+						log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
+						return
+					}
 				}
-				included = append(included, add.Transactions()...)
-				if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
-					log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
-					return
+				for rem.Hash() != add.Hash() {
+					discarded = append(discarded, rem.Transactions()...)
+					if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
+						log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash())
+						return
+					}
+					included = append(included, add.Transactions()...)
+					if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
+						log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash())
+						return
+					}
 				}
+				reinject = types.TxDifference(discarded, included)
 			}
-			reinject = types.TxDifference(discarded, included)
 		}
 	}
 	// Initialize the internal state to the current head
@@ -1173,6 +1204,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
 	// Update all fork indicator by next pending block number.
 	next := new(big.Int).Add(newHead.Number, big.NewInt(1))
 	pool.istanbul = pool.chainconfig.IsIstanbul(next)
+	pool.eip2718 = pool.chainconfig.IsBerlin(next)
 }
 
 // promoteExecutables moves transactions that have become processable from the
@@ -1193,15 +1225,15 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
 		for _, tx := range forwards {
 			hash := tx.Hash()
 			pool.all.Remove(hash)
-			log.Trace("Removed old queued transaction", "hash", hash)
 		}
+		log.Trace("Removed old queued transactions", "count", len(forwards))
 		// Drop all transactions that are too costly (low balance or out of gas)
 		drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
 		for _, tx := range drops {
 			hash := tx.Hash()
 			pool.all.Remove(hash)
-			log.Trace("Removed unpayable queued transaction", "hash", hash)
 		}
+		log.Trace("Removed unpayable queued transactions", "count", len(drops))
 		queuedNofundsMeter.Mark(int64(len(drops)))
 
 		// Gather all executable transactions and promote them
@@ -1209,10 +1241,10 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
 		for _, tx := range readies {
 			hash := tx.Hash()
 			if pool.promoteTx(addr, hash, tx) {
-				log.Trace("Promoting queued transaction", "hash", hash)
 				promoted = append(promoted, tx)
 			}
 		}
+		log.Trace("Promoted queued transactions", "count", len(promoted))
 		queuedGauge.Dec(int64(len(readies)))
 
 		// Drop all transactions over the allowed limit
@@ -1235,6 +1267,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
 		// Delete the entire queue entry if it became empty.
 		if list.Empty() {
 			delete(pool.queue, addr)
+			delete(pool.beats, addr)
 		}
 	}
 	return promoted
@@ -1400,7 +1433,9 @@ func (pool *TxPool) demoteUnexecutables() {
 		for _, tx := range invalids {
 			hash := tx.Hash()
 			log.Trace("Demoting pending transaction", "hash", hash)
-			pool.enqueueTx(hash, tx)
+
+			// Internal shuffle shouldn't touch the lookup set.
+			pool.enqueueTx(hash, tx, false, false)
 		}
 		pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids)))
 		if pool.locals.contains(addr) {
@@ -1412,14 +1447,17 @@ func (pool *TxPool) demoteUnexecutables() {
 			for _, tx := range gapped {
 				hash := tx.Hash()
 				log.Error("Demoting invalidated transaction", "hash", hash)
-				pool.enqueueTx(hash, tx)
+
+				// Internal shuffle shouldn't touch the lookup set.
+				pool.enqueueTx(hash, tx, false, false)
 			}
 			pendingGauge.Dec(int64(len(gapped)))
+			// This might happen in a reorg, so log it to the metering
+			blockReorgInvalidatedTx.Mark(int64(len(gapped)))
 		}
-		// Delete the entire queue entry if it became empty.
+		// Delete the entire pending entry if it became empty.
 		if list.Empty() {
 			delete(pool.pending, addr)
-			delete(pool.beats, addr)
 		}
 	}
 }
@@ -1463,6 +1501,10 @@ func (as *accountSet) contains(addr common.Address) bool {
 	return exist
 }
 
+func (as *accountSet) empty() bool {
+	return len(as.accounts) == 0
+}
+
 // containsTx checks if the sender of a given tx is within the set. If the sender
 // cannot be derived, this method returns false.
 func (as *accountSet) containsTx(tx *types.Transaction) bool {
@@ -1506,8 +1548,8 @@ func (as *accountSet) merge(other *accountSet) {
 	as.cache = nil
 }
 
-// txLookup is used internally by TxPool to track transactions while allowing lookup without
-// mutex contention.
+// txLookup is used internally by TxPool to track transactions while allowing
+// lookup without mutex contention.
 //
 // Note, although this type is properly protected against concurrent access, it
 // is **not** a type that should ever be mutated or even exposed outside of the
@@ -1515,27 +1557,43 @@ func (as *accountSet) merge(other *accountSet) {
 // internal mechanisms. The sole purpose of the type is to permit out-of-bound
 // peeking into the pool in TxPool.Get without having to acquire the widely scoped
 // TxPool.mu mutex.
+//
+// This lookup set combines the notion of "local transactions", which is useful
+// to build upper-level structure.
 type txLookup struct {
-	all   map[common.Hash]*types.Transaction
-	slots int
-	lock  sync.RWMutex
+	slots   int
+	lock    sync.RWMutex
+	locals  map[common.Hash]*types.Transaction
+	remotes map[common.Hash]*types.Transaction
 }
 
 // newTxLookup returns a new txLookup structure.
 func newTxLookup() *txLookup {
 	return &txLookup{
-		all: make(map[common.Hash]*types.Transaction),
+		locals:  make(map[common.Hash]*types.Transaction),
+		remotes: make(map[common.Hash]*types.Transaction),
 	}
 }
 
-// Range calls f on each key and value present in the map.
-func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) {
+// Range calls f on each key and value present in the map. The callback passed
+// should return the indicator whether the iteration needs to be continued.
+// Callers need to specify which set (or both) to be iterated.
+func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) {
 	t.lock.RLock()
 	defer t.lock.RUnlock()
 
-	for key, value := range t.all {
-		if !f(key, value) {
-			break
+	if local {
+		for key, value := range t.locals {
+			if !f(key, value, true) {
+				return
+			}
+		}
+	}
+	if remote {
+		for key, value := range t.remotes {
+			if !f(key, value, false) {
+				return
+			}
 		}
 	}
 }
@@ -1545,15 +1603,50 @@ func (t *txLookup) Get(hash common.Hash) *types.Transaction {
 	t.lock.RLock()
 	defer t.lock.RUnlock()
 
-	return t.all[hash]
+	if tx := t.locals[hash]; tx != nil {
+		return tx
+	}
+	return t.remotes[hash]
+}
+
+// GetLocal returns a transaction if it exists in the lookup, or nil if not found.
+func (t *txLookup) GetLocal(hash common.Hash) *types.Transaction {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.locals[hash]
+}
+
+// GetRemote returns a transaction if it exists in the lookup, or nil if not found.
+func (t *txLookup) GetRemote(hash common.Hash) *types.Transaction {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return t.remotes[hash]
 }
 
-// Count returns the current number of items in the lookup.
+// Count returns the current number of transactions in the lookup.
 func (t *txLookup) Count() int {
 	t.lock.RLock()
 	defer t.lock.RUnlock()
 
-	return len(t.all)
+	return len(t.locals) + len(t.remotes)
+}
+
+// LocalCount returns the current number of local transactions in the lookup.
+func (t *txLookup) LocalCount() int {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return len(t.locals)
+}
+
+// RemoteCount returns the current number of remote transactions in the lookup.
+func (t *txLookup) RemoteCount() int {
+	t.lock.RLock()
+	defer t.lock.RUnlock()
+
+	return len(t.remotes)
 }
 
 // Slots returns the current number of slots used in the lookup.
@@ -1565,14 +1658,18 @@ func (t *txLookup) Slots() int {
 }
 
 // Add adds a transaction to the lookup.
-func (t *txLookup) Add(tx *types.Transaction) {
+func (t *txLookup) Add(tx *types.Transaction, local bool) {
 	t.lock.Lock()
 	defer t.lock.Unlock()
 
 	t.slots += numSlots(tx)
 	slotsGauge.Update(int64(t.slots))
 
-	t.all[tx.Hash()] = tx
+	if local {
+		t.locals[tx.Hash()] = tx
+	} else {
+		t.remotes[tx.Hash()] = tx
+	}
 }
 
 // Remove removes a transaction from the lookup.
@@ -1580,10 +1677,36 @@ func (t *txLookup) Remove(hash common.Hash) {
 	t.lock.Lock()
 	defer t.lock.Unlock()
 
-	t.slots -= numSlots(t.all[hash])
+	tx, ok := t.locals[hash]
+	if !ok {
+		tx, ok = t.remotes[hash]
+	}
+	if !ok {
+		log.Error("No transaction found to be deleted", "hash", hash)
+		return
+	}
+	t.slots -= numSlots(tx)
 	slotsGauge.Update(int64(t.slots))
 
-	delete(t.all, hash)
+	delete(t.locals, hash)
+	delete(t.remotes, hash)
+}
+
+// RemoteToLocals migrates the transactions belongs to the given locals to locals
+// set. The assumption is held the locals set is thread-safe to be used.
+func (t *txLookup) RemoteToLocals(locals *accountSet) int {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	var migrated int
+	for hash, tx := range t.remotes {
+		if locals.containsTx(tx) {
+			t.locals[hash] = tx
+			delete(t.remotes, hash)
+			migrated += 1
+		}
+	}
+	return migrated
 }
 
 // numSlots calculates the number of slots needed for a single transaction.
diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go
index 6ca60a855a..5d555f5a9c 100644
--- a/core/tx_pool_test.go
+++ b/core/tx_pool_test.go
@@ -18,6 +18,7 @@ package core
 
 import (
 	"crypto/ecdsa"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"math/big"
@@ -33,6 +34,7 @@ import (
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 // testTxPoolConfig is a transaction pool configuration without stateful disk
@@ -53,7 +55,7 @@ type testBlockChain struct {
 func (bc *testBlockChain) CurrentBlock() *types.Block {
 	return types.NewBlock(&types.Header{
 		GasLimit: bc.gasLimit,
-	}, nil, nil, nil)
+	}, nil, nil, nil, trie.NewStackTrie(nil))
 }
 
 func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
@@ -105,8 +107,10 @@ func validateTxPoolInternals(pool *TxPool) error {
 	if total := pool.all.Count(); total != pending+queued {
 		return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued)
 	}
-	if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued {
-		return fmt.Errorf("total priced transaction count %d != %d pending + %d queued", priced, pending, queued)
+	pool.priced.Reheap()
+	priced, remote := pool.priced.remotes.Len(), pool.all.RemoteCount()
+	if priced != remote {
+		return fmt.Errorf("total priced transaction count %d != %d", priced, remote)
 	}
 	// Ensure the next nonce to assign is the correct one
 	for addr, txs := range pool.pending {
@@ -117,7 +121,7 @@ func validateTxPoolInternals(pool *TxPool) error {
 				last = nonce
 			}
 		}
-		if nonce := pool.Nonce(addr); nonce != last+1 {
+		if nonce := pool.pendingNonces.get(addr); nonce != last+1 {
 			return fmt.Errorf("pending nonce mismatch: have %v, want %v", nonce, last+1)
 		}
 	}
@@ -239,20 +243,20 @@ func TestInvalidTransactions(t *testing.T) {
 	from, _ := deriveSender(tx)
 
 	pool.currentState.AddBalance(from, big.NewInt(1))
-	if err := pool.AddRemote(tx); err != ErrInsufficientFunds {
+	if err := pool.AddRemote(tx); !errors.Is(err, ErrInsufficientFunds) {
 		t.Error("expected", ErrInsufficientFunds)
 	}
 
 	balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice()))
 	pool.currentState.AddBalance(from, balance)
-	if err := pool.AddRemote(tx); err != ErrIntrinsicGas {
+	if err := pool.AddRemote(tx); !errors.Is(err, ErrIntrinsicGas) {
 		t.Error("expected", ErrIntrinsicGas, "got", err)
 	}
 
 	pool.currentState.SetNonce(from, 1)
 	pool.currentState.AddBalance(from, big.NewInt(0xffffffffffffff))
 	tx = transaction(0, 100000, key)
-	if err := pool.AddRemote(tx); err != ErrNonceTooLow {
+	if err := pool.AddRemote(tx); !errors.Is(err, ErrNonceTooLow) {
 		t.Error("expected", ErrNonceTooLow)
 	}
 
@@ -277,7 +281,7 @@ func TestTransactionQueue(t *testing.T) {
 	pool.currentState.AddBalance(from, big.NewInt(1000))
 	<-pool.requestReset(nil, nil)
 
-	pool.enqueueTx(tx.Hash(), tx)
+	pool.enqueueTx(tx.Hash(), tx, false, true)
 	<-pool.requestPromoteExecutables(newAccountSet(pool.signer, from))
 	if len(pool.pending) != 1 {
 		t.Error("expected valid txs to be 1 is", len(pool.pending))
@@ -286,7 +290,7 @@ func TestTransactionQueue(t *testing.T) {
 	tx = transaction(1, 100, key)
 	from, _ = deriveSender(tx)
 	pool.currentState.SetNonce(from, 2)
-	pool.enqueueTx(tx.Hash(), tx)
+	pool.enqueueTx(tx.Hash(), tx, false, true)
 
 	<-pool.requestPromoteExecutables(newAccountSet(pool.signer, from))
 	if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
@@ -310,9 +314,9 @@ func TestTransactionQueue2(t *testing.T) {
 	pool.currentState.AddBalance(from, big.NewInt(1000))
 	pool.reset(nil, nil)
 
-	pool.enqueueTx(tx1.Hash(), tx1)
-	pool.enqueueTx(tx2.Hash(), tx2)
-	pool.enqueueTx(tx3.Hash(), tx3)
+	pool.enqueueTx(tx1.Hash(), tx1, false, true)
+	pool.enqueueTx(tx2.Hash(), tx2, false, true)
+	pool.enqueueTx(tx3.Hash(), tx3, false, true)
 
 	pool.promoteExecutables([]common.Address{from})
 	if len(pool.pending) != 1 {
@@ -485,12 +489,21 @@ func TestTransactionDropping(t *testing.T) {
 		tx11 = transaction(11, 200, key)
 		tx12 = transaction(12, 300, key)
 	)
+	pool.all.Add(tx0, false)
+	pool.priced.Put(tx0, false)
 	pool.promoteTx(account, tx0.Hash(), tx0)
+
+	pool.all.Add(tx1, false)
+	pool.priced.Put(tx1, false)
 	pool.promoteTx(account, tx1.Hash(), tx1)
+
+	pool.all.Add(tx2, false)
+	pool.priced.Put(tx2, false)
 	pool.promoteTx(account, tx2.Hash(), tx2)
-	pool.enqueueTx(tx10.Hash(), tx10)
-	pool.enqueueTx(tx11.Hash(), tx11)
-	pool.enqueueTx(tx12.Hash(), tx12)
+
+	pool.enqueueTx(tx10.Hash(), tx10, false, true)
+	pool.enqueueTx(tx11.Hash(), tx11, false, true)
+	pool.enqueueTx(tx12.Hash(), tx12, false, true)
 
 	// Check that pre and post validations leave the pool as is
 	if pool.pending[account].Len() != 3 {
@@ -867,7 +880,7 @@ func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) {
 func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
 	// Reduce the eviction interval to a testable amount
 	defer func(old time.Duration) { evictionInterval = old }(evictionInterval)
-	evictionInterval = time.Second
+	evictionInterval = time.Millisecond * 100
 
 	// Create the pool to test the non-expiration enforcement
 	statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
@@ -904,6 +917,22 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
 	if err := validateTxPoolInternals(pool); err != nil {
 		t.Fatalf("pool internal state corrupted: %v", err)
 	}
+
+	// Allow the eviction interval to run
+	time.Sleep(2 * evictionInterval)
+
+	// Transactions should not be evicted from the queue yet since lifetime duration has not passed
+	pending, queued = pool.Stats()
+	if pending != 0 {
+		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0)
+	}
+	if queued != 2 {
+		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
+	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
+
 	// Wait a bit for eviction to run and clean up any leftovers, and ensure only the local remains
 	time.Sleep(2 * config.Lifetime)
 
@@ -923,6 +952,72 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
 	if err := validateTxPoolInternals(pool); err != nil {
 		t.Fatalf("pool internal state corrupted: %v", err)
 	}
+
+	// remove current transactions and increase nonce to prepare for a reset and cleanup
+	statedb.SetNonce(crypto.PubkeyToAddress(remote.PublicKey), 2)
+	statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2)
+	<-pool.requestReset(nil, nil)
+
+	// make sure queue, pending are cleared
+	pending, queued = pool.Stats()
+	if pending != 0 {
+		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 0)
+	}
+	if queued != 0 {
+		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
+	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
+
+	// Queue gapped transactions
+	if err := pool.AddLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil {
+		t.Fatalf("failed to add remote transaction: %v", err)
+	}
+	if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(1), remote)); err != nil {
+		t.Fatalf("failed to add remote transaction: %v", err)
+	}
+	time.Sleep(5 * evictionInterval) // A half lifetime pass
+
+	// Queue executable transactions, the life cycle should be restarted.
+	if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil {
+		t.Fatalf("failed to add remote transaction: %v", err)
+	}
+	if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), remote)); err != nil {
+		t.Fatalf("failed to add remote transaction: %v", err)
+	}
+	time.Sleep(6 * evictionInterval)
+
+	// All gapped transactions shouldn't be kicked out
+	pending, queued = pool.Stats()
+	if pending != 2 {
+		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
+	}
+	if queued != 2 {
+		t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
+	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
+
+	// The whole life time pass after last promotion, kick out stale transactions
+	time.Sleep(2 * config.Lifetime)
+	pending, queued = pool.Stats()
+	if pending != 2 {
+		t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
+	}
+	if nolocals {
+		if queued != 0 {
+			t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
+		}
+	} else {
+		if queued != 1 {
+			t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
+		}
+	}
+	if err := validateTxPoolInternals(pool); err != nil {
+		t.Fatalf("pool internal state corrupted: %v", err)
+	}
 }
 
 // Tests that even if the transaction count belonging to a single account goes
@@ -1054,7 +1149,7 @@ func TestTransactionAllowedTxSize(t *testing.T) {
 		t.Fatalf("expected rejection on slightly oversize transaction")
 	}
 	// Try adding a transaction of random not allowed size
-	if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(int(10*txMaxSize))))); err == nil {
+	if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentMaxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(10*txMaxSize)))); err == nil {
 		t.Fatalf("expected rejection on oversize transaction")
 	}
 	// Run some sanity checks on the pool internals
@@ -1879,7 +1974,7 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
 
 	for i := 0; i < size; i++ {
 		tx := transaction(uint64(1+i), 100000, key)
-		pool.enqueueTx(tx.Hash(), tx)
+		pool.enqueueTx(tx.Hash(), tx, false, true)
 	}
 	// Benchmark the speed of pool validation
 	b.ResetTimer()
@@ -1889,11 +1984,15 @@ func benchmarkFuturePromotion(b *testing.B, size int) {
 }
 
 // Benchmarks the speed of batched transaction insertion.
-func BenchmarkPoolBatchInsert100(b *testing.B)   { benchmarkPoolBatchInsert(b, 100) }
-func BenchmarkPoolBatchInsert1000(b *testing.B)  { benchmarkPoolBatchInsert(b, 1000) }
-func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000) }
+func BenchmarkPoolBatchInsert100(b *testing.B)   { benchmarkPoolBatchInsert(b, 100, false) }
+func BenchmarkPoolBatchInsert1000(b *testing.B)  { benchmarkPoolBatchInsert(b, 1000, false) }
+func BenchmarkPoolBatchInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, false) }
+
+func BenchmarkPoolBatchLocalInsert100(b *testing.B)   { benchmarkPoolBatchInsert(b, 100, true) }
+func BenchmarkPoolBatchLocalInsert1000(b *testing.B)  { benchmarkPoolBatchInsert(b, 1000, true) }
+func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert(b, 10000, true) }
 
-func benchmarkPoolBatchInsert(b *testing.B, size int) {
+func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) {
 	// Generate a batch of transactions to enqueue into the pool
 	pool, key := setupTxPool()
 	defer pool.Stop()
@@ -1911,6 +2010,45 @@ func benchmarkPoolBatchInsert(b *testing.B, size int) {
 	// Benchmark importing the transactions into the queue
 	b.ResetTimer()
 	for _, batch := range batches {
-		pool.AddRemotes(batch)
+		if local {
+			pool.AddLocals(batch)
+		} else {
+			pool.AddRemotes(batch)
+		}
+	}
+}
+
+func BenchmarkInsertRemoteWithAllLocals(b *testing.B) {
+	// Allocate keys for testing
+	key, _ := crypto.GenerateKey()
+	account := crypto.PubkeyToAddress(key.PublicKey)
+
+	remoteKey, _ := crypto.GenerateKey()
+	remoteAddr := crypto.PubkeyToAddress(remoteKey.PublicKey)
+
+	locals := make([]*types.Transaction, 4096+1024) // Occupy all slots
+	for i := 0; i < len(locals); i++ {
+		locals[i] = transaction(uint64(i), 100000, key)
+	}
+	remotes := make([]*types.Transaction, 1000)
+	for i := 0; i < len(remotes); i++ {
+		remotes[i] = pricedTransaction(uint64(i), 100000, big.NewInt(2), remoteKey) // Higher gasprice
+	}
+	// Benchmark importing the transactions into the queue
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		b.StopTimer()
+		pool, _ := setupTxPool()
+		pool.currentState.AddBalance(account, big.NewInt(100000000))
+		for _, local := range locals {
+			pool.AddLocal(local)
+		}
+		b.StartTimer()
+		// Assign a high enough balance for testing
+		pool.currentState.AddBalance(remoteAddr, big.NewInt(100000000))
+		for i := 0; i < len(remotes); i++ {
+			pool.AddRemotes([]*types.Transaction{remotes[i]})
+		}
+		pool.Stop()
 	}
 }
diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go
new file mode 100644
index 0000000000..65ee95adf6
--- /dev/null
+++ b/core/types/access_list_tx.go
@@ -0,0 +1,115 @@
+// 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 .
+
+package types
+
+import (
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+//go:generate gencodec -type AccessTuple -out gen_access_tuple.go
+
+// AccessList is an EIP-2930 access list.
+type AccessList []AccessTuple
+
+// AccessTuple is the element type of an access list.
+type AccessTuple struct {
+	Address     common.Address `json:"address"        gencodec:"required"`
+	StorageKeys []common.Hash  `json:"storageKeys"    gencodec:"required"`
+}
+
+// StorageKeys returns the total number of storage keys in the access list.
+func (al AccessList) StorageKeys() int {
+	sum := 0
+	for _, tuple := range al {
+		sum += len(tuple.StorageKeys)
+	}
+	return sum
+}
+
+// AccessListTx is the data of EIP-2930 access list transactions.
+type AccessListTx struct {
+	ChainID    *big.Int        // destination chain ID
+	Nonce      uint64          // nonce of sender account
+	GasPrice   *big.Int        // wei per gas
+	Gas        uint64          // gas limit
+	To         *common.Address `rlp:"nil"` // nil means contract creation
+	Value      *big.Int        // wei amount
+	Data       []byte          // contract invocation input data
+	AccessList AccessList      // EIP-2930 access list
+	V, R, S    *big.Int        // signature values
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *AccessListTx) copy() TxData {
+	cpy := &AccessListTx{
+		Nonce: tx.Nonce,
+		To:    tx.To, // TODO: copy pointed-to address
+		Data:  common.CopyBytes(tx.Data),
+		Gas:   tx.Gas,
+		// These are copied below.
+		AccessList: make(AccessList, len(tx.AccessList)),
+		Value:      new(big.Int),
+		ChainID:    new(big.Int),
+		GasPrice:   new(big.Int),
+		V:          new(big.Int),
+		R:          new(big.Int),
+		S:          new(big.Int),
+	}
+	copy(cpy.AccessList, tx.AccessList)
+	if tx.Value != nil {
+		cpy.Value.Set(tx.Value)
+	}
+	if tx.ChainID != nil {
+		cpy.ChainID.Set(tx.ChainID)
+	}
+	if tx.GasPrice != nil {
+		cpy.GasPrice.Set(tx.GasPrice)
+	}
+	if tx.V != nil {
+		cpy.V.Set(tx.V)
+	}
+	if tx.R != nil {
+		cpy.R.Set(tx.R)
+	}
+	if tx.S != nil {
+		cpy.S.Set(tx.S)
+	}
+	return cpy
+}
+
+// accessors for innerTx.
+
+func (tx *AccessListTx) txType() byte           { return AccessListTxType }
+func (tx *AccessListTx) chainID() *big.Int      { return tx.ChainID }
+func (tx *AccessListTx) protected() bool        { return true }
+func (tx *AccessListTx) accessList() AccessList { return tx.AccessList }
+func (tx *AccessListTx) data() []byte           { return tx.Data }
+func (tx *AccessListTx) gas() uint64            { return tx.Gas }
+func (tx *AccessListTx) gasPrice() *big.Int     { return tx.GasPrice }
+func (tx *AccessListTx) value() *big.Int        { return tx.Value }
+func (tx *AccessListTx) nonce() uint64          { return tx.Nonce }
+func (tx *AccessListTx) to() *common.Address    { return tx.To }
+
+func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) {
+	return tx.V, tx.R, tx.S
+}
+
+func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *big.Int) {
+	tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
+}
diff --git a/core/types/block.go b/core/types/block.go
index 741ff8e282..a3318f8779 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -29,11 +29,10 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/rlp"
-	"golang.org/x/crypto/sha3"
 )
 
 var (
-	EmptyRootHash  = DeriveSha(Transactions{})
+	EmptyRootHash  = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
 	EmptyUncleHash = rlpHash([]*Header(nil))
 )
 
@@ -129,11 +128,15 @@ func (h *Header) SanityCheck() error {
 	return nil
 }
 
-func rlpHash(x interface{}) (h common.Hash) {
-	hw := sha3.NewLegacyKeccak256()
-	rlp.Encode(hw, x)
-	hw.Sum(h[:0])
-	return h
+// EmptyBody returns true if there is no additional 'body' to complete the header
+// that is: no transactions and no uncles.
+func (h *Header) EmptyBody() bool {
+	return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash
+}
+
+// EmptyReceipts returns true if there are no receipts for this header/block.
+func (h *Header) EmptyReceipts() bool {
+	return h.ReceiptHash == EmptyRootHash
 }
 
 // Body is a simple (mutable, non-safe) data container for storing and moving
@@ -163,19 +166,6 @@ type Block struct {
 	ReceivedFrom interface{}
 }
 
-// DeprecatedTd is an old relic for extracting the TD of a block. It is in the
-// code solely to facilitate upgrading the database from the old format to the
-// new, after which it should be deleted. Do not use!
-func (b *Block) DeprecatedTd() *big.Int {
-	return b.td
-}
-
-// [deprecated by eth/63]
-// StorageBlock defines the RLP encoding of a Block stored in the
-// state database. The StorageBlock encoding contains fields that
-// would otherwise need to be recomputed.
-type StorageBlock Block
-
 // "external" block encoding. used for eth protocol, etc.
 type extblock struct {
 	Header *Header
@@ -183,15 +173,6 @@ type extblock struct {
 	Uncles []*Header
 }
 
-// [deprecated by eth/63]
-// "storage" block encoding. used for database.
-type storageblock struct {
-	Header *Header
-	Txs    []*Transaction
-	Uncles []*Header
-	TD     *big.Int
-}
-
 // NewBlock creates a new block. The input data is copied,
 // changes to header and to the field values will not affect the
 // block.
@@ -199,14 +180,14 @@ type storageblock struct {
 // The values of TxHash, UncleHash, ReceiptHash and Bloom in header
 // are ignored and set to values derived from the given txs, uncles
 // and receipts.
-func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt) *Block {
+func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
 	b := &Block{header: CopyHeader(header), td: new(big.Int)}
 
 	// TODO: panic if len(txs) != len(receipts)
 	if len(txs) == 0 {
 		b.header.TxHash = EmptyRootHash
 	} else {
-		b.header.TxHash = DeriveSha(Transactions(txs))
+		b.header.TxHash = DeriveSha(Transactions(txs), hasher)
 		b.transactions = make(Transactions, len(txs))
 		copy(b.transactions, txs)
 	}
@@ -214,7 +195,7 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
 	if len(receipts) == 0 {
 		b.header.ReceiptHash = EmptyRootHash
 	} else {
-		b.header.ReceiptHash = DeriveSha(Receipts(receipts))
+		b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher)
 		b.header.Bloom = CreateBloom(receipts)
 	}
 
@@ -276,16 +257,6 @@ func (b *Block) EncodeRLP(w io.Writer) error {
 	})
 }
 
-// [deprecated by eth/63]
-func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error {
-	var sb storageblock
-	if err := s.Decode(&sb); err != nil {
-		return err
-	}
-	b.header, b.uncles, b.transactions, b.td = sb.Header, sb.Uncles, sb.Txs, sb.TD
-	return nil
-}
-
 // TODO: copies
 
 func (b *Block) Uncles() []*Header          { return b.uncles }
diff --git a/core/types/block_test.go b/core/types/block_test.go
index ff0a641e5c..63904f882c 100644
--- a/core/types/block_test.go
+++ b/core/types/block_test.go
@@ -18,12 +18,17 @@ package types
 
 import (
 	"bytes"
+	"hash"
 	"math/big"
 	"reflect"
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
+	"golang.org/x/crypto/sha3"
 )
 
 // from bcValidBlockTest.json, "SimpleTx"
@@ -54,6 +59,66 @@ func TestBlockEncoding(t *testing.T) {
 	tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100"))
 	check("len(Transactions)", len(block.Transactions()), 1)
 	check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash())
+	ourBlockEnc, err := rlp.EncodeToBytes(&block)
+	if err != nil {
+		t.Fatal("encode error: ", err)
+	}
+	if !bytes.Equal(ourBlockEnc, blockEnc) {
+		t.Errorf("encoded block mismatch:\ngot:  %x\nwant: %x", ourBlockEnc, blockEnc)
+	}
+}
+
+func TestEIP2718BlockEncoding(t *testing.T) {
+	blockEnc := common.FromHex("f90319f90211a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c0")
+	var block Block
+	if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
+		t.Fatal("decode error: ", err)
+	}
+
+	check := func(f string, got, want interface{}) {
+		if !reflect.DeepEqual(got, want) {
+			t.Errorf("%s mismatch: got %v, want %v", f, got, want)
+		}
+	}
+	check("Difficulty", block.Difficulty(), big.NewInt(131072))
+	check("GasLimit", block.GasLimit(), uint64(3141592))
+	check("GasUsed", block.GasUsed(), uint64(42000))
+	check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
+	check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
+	check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
+	check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
+	check("Time", block.Time(), uint64(1426516743))
+	check("Size", block.Size(), common.StorageSize(len(blockEnc)))
+
+	// Create legacy tx.
+	to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87")
+	tx1 := NewTx(&LegacyTx{
+		Nonce:    0,
+		To:       &to,
+		Value:    big.NewInt(10),
+		Gas:      50000,
+		GasPrice: big.NewInt(10),
+	})
+	sig := common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")
+	tx1, _ = tx1.WithSignature(HomesteadSigner{}, sig)
+
+	// Create ACL tx.
+	addr := common.HexToAddress("0x0000000000000000000000000000000000000001")
+	tx2 := NewTx(&AccessListTx{
+		ChainID:    big.NewInt(1),
+		Nonce:      0,
+		To:         &to,
+		Gas:        123457,
+		GasPrice:   big.NewInt(10),
+		AccessList: AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}},
+	})
+	sig2 := common.Hex2Bytes("3dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef1401")
+	tx2, _ = tx2.WithSignature(NewEIP2930Signer(big.NewInt(1)), sig2)
+
+	check("len(Transactions)", len(block.Transactions()), 2)
+	check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash())
+	check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash())
+	check("Transactions[1].Type()", block.Transactions()[1].Type(), uint8(AccessListTxType))
 
 	ourBlockEnc, err := rlp.EncodeToBytes(&block)
 	if err != nil {
@@ -72,10 +137,82 @@ func TestUncleHash(t *testing.T) {
 		t.Fatalf("empty uncle hash is wrong, got %x != %x", h, exp)
 	}
 }
-func BenchmarkUncleHash(b *testing.B) {
-	uncles := make([]*Header, 0)
+
+var benchBuffer = bytes.NewBuffer(make([]byte, 0, 32000))
+
+func BenchmarkEncodeBlock(b *testing.B) {
+	block := makeBenchBlock()
 	b.ResetTimer()
+
 	for i := 0; i < b.N; i++ {
-		CalcUncleHash(uncles)
+		benchBuffer.Reset()
+		if err := rlp.Encode(benchBuffer, block); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+// testHasher is the helper tool for transaction/receipt list hashing.
+// The original hasher is trie, in order to get rid of import cycle,
+// use the testing hasher instead.
+type testHasher struct {
+	hasher hash.Hash
+}
+
+func newHasher() *testHasher {
+	return &testHasher{hasher: sha3.NewLegacyKeccak256()}
+}
+
+func (h *testHasher) Reset() {
+	h.hasher.Reset()
+}
+
+func (h *testHasher) Update(key, val []byte) {
+	h.hasher.Write(key)
+	h.hasher.Write(val)
+}
+
+func (h *testHasher) Hash() common.Hash {
+	return common.BytesToHash(h.hasher.Sum(nil))
+}
+
+func makeBenchBlock() *Block {
+	var (
+		key, _   = crypto.GenerateKey()
+		txs      = make([]*Transaction, 70)
+		receipts = make([]*Receipt, len(txs))
+		signer   = LatestSigner(params.TestChainConfig)
+		uncles   = make([]*Header, 3)
+	)
+	header := &Header{
+		Difficulty: math.BigPow(11, 11),
+		Number:     math.BigPow(2, 9),
+		GasLimit:   12345678,
+		GasUsed:    1476322,
+		Time:       9876543,
+		Extra:      []byte("coolest block on chain"),
+	}
+	for i := range txs {
+		amount := math.BigPow(2, int64(i))
+		price := big.NewInt(300000)
+		data := make([]byte, 100)
+		tx := NewTransaction(uint64(i), common.Address{}, amount, 123457, price, data)
+		signedTx, err := SignTx(tx, signer, key)
+		if err != nil {
+			panic(err)
+		}
+		txs[i] = signedTx
+		receipts[i] = NewReceipt(make([]byte, 32), false, tx.Gas())
+	}
+	for i := range uncles {
+		uncles[i] = &Header{
+			Difficulty: math.BigPow(11, 11),
+			Number:     math.BigPow(2, 9),
+			GasLimit:   12345678,
+			GasUsed:    1476322,
+			Time:       9876543,
+			Extra:      []byte("benchmark uncle"),
+		}
 	}
+	return NewBlock(header, txs, uncles, receipts, newHasher())
 }
diff --git a/core/types/bloom9.go b/core/types/bloom9.go
index d045c9e667..1793c2adc7 100644
--- a/core/types/bloom9.go
+++ b/core/types/bloom9.go
@@ -17,6 +17,7 @@
 package types
 
 import (
+	"encoding/binary"
 	"fmt"
 	"math/big"
 
@@ -57,28 +58,36 @@ func (b *Bloom) SetBytes(d []byte) {
 }
 
 // Add adds d to the filter. Future calls of Test(d) will return true.
-func (b *Bloom) Add(d *big.Int) {
-	bin := new(big.Int).SetBytes(b[:])
-	bin.Or(bin, bloom9(d.Bytes()))
-	b.SetBytes(bin.Bytes())
+func (b *Bloom) Add(d []byte) {
+	b.add(d, make([]byte, 6))
+}
+
+// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes)
+func (b *Bloom) add(d []byte, buf []byte) {
+	i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
+	b[i1] |= v1
+	b[i2] |= v2
+	b[i3] |= v3
 }
 
 // Big converts b to a big integer.
+// Note: Converting a bloom filter to a big.Int and then calling GetBytes
+// does not return the same bytes, since big.Int will trim leading zeroes
 func (b Bloom) Big() *big.Int {
 	return new(big.Int).SetBytes(b[:])
 }
 
+// Bytes returns the backing byte slice of the bloom
 func (b Bloom) Bytes() []byte {
 	return b[:]
 }
 
-func (b Bloom) Test(test *big.Int) bool {
-	return BloomLookup(b, test)
-}
-
-func (b Bloom) TestBytes(test []byte) bool {
-	return b.Test(new(big.Int).SetBytes(test))
-
+// Test checks if the given topic is present in the bloom filter
+func (b Bloom) Test(topic []byte) bool {
+	i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6))
+	return v1 == v1&b[i1] &&
+		v2 == v2&b[i2] &&
+		v3 == v3&b[i3]
 }
 
 // MarshalText encodes b as a hex string with 0x prefix.
@@ -91,46 +100,61 @@ func (b *Bloom) UnmarshalText(input []byte) error {
 	return hexutil.UnmarshalFixedText("Bloom", input, b[:])
 }
 
+// CreateBloom creates a bloom filter out of the give Receipts (+Logs)
 func CreateBloom(receipts Receipts) Bloom {
-	bin := new(big.Int)
+	buf := make([]byte, 6)
+	var bin Bloom
 	for _, receipt := range receipts {
-		bin.Or(bin, LogsBloom(receipt.Logs))
+		for _, log := range receipt.Logs {
+			bin.add(log.Address.Bytes(), buf)
+			for _, b := range log.Topics {
+				bin.add(b[:], buf)
+			}
+		}
 	}
-
-	return BytesToBloom(bin.Bytes())
+	return bin
 }
 
-func LogsBloom(logs []*Log) *big.Int {
-	bin := new(big.Int)
+// LogsBloom returns the bloom bytes for the given logs
+func LogsBloom(logs []*Log) []byte {
+	buf := make([]byte, 6)
+	var bin Bloom
 	for _, log := range logs {
-		bin.Or(bin, bloom9(log.Address.Bytes()))
+		bin.add(log.Address.Bytes(), buf)
 		for _, b := range log.Topics {
-			bin.Or(bin, bloom9(b[:]))
+			bin.add(b[:], buf)
 		}
 	}
-
-	return bin
+	return bin[:]
 }
 
-func bloom9(b []byte) *big.Int {
-	b = crypto.Keccak256(b)
-
-	r := new(big.Int)
-
-	for i := 0; i < 6; i += 2 {
-		t := big.NewInt(1)
-		b := (uint(b[i+1]) + (uint(b[i]) << 8)) & 2047
-		r.Or(r, t.Lsh(t, b))
-	}
-
-	return r
+// Bloom9 returns the bloom filter for the given data
+func Bloom9(data []byte) []byte {
+	var b Bloom
+	b.SetBytes(data)
+	return b.Bytes()
 }
 
-var Bloom9 = bloom9
+// bloomValues returns the bytes (index-value pairs) to set for the given data
+func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) {
+	sha := hasherPool.Get().(crypto.KeccakState)
+	sha.Reset()
+	sha.Write(data)
+	sha.Read(hashbuf)
+	hasherPool.Put(sha)
+	// The actual bits to flip
+	v1 := byte(1 << (hashbuf[1] & 0x7))
+	v2 := byte(1 << (hashbuf[3] & 0x7))
+	v3 := byte(1 << (hashbuf[5] & 0x7))
+	// The indices for the bytes to OR in
+	i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1
+	i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
+	i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
+
+	return i1, v1, i2, v2, i3, v3
+}
 
+// BloomLookup is a convenience-method to check presence int he bloom filter
 func BloomLookup(bin Bloom, topic bytesBacked) bool {
-	bloom := bin.Big()
-	cmp := bloom9(topic.Bytes())
-
-	return bloom.And(bloom, cmp).Cmp(cmp) == 0
+	return bin.Test(topic.Bytes())
 }
diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go
index a28ac0e7af..893df486dd 100644
--- a/core/types/bloom9_test.go
+++ b/core/types/bloom9_test.go
@@ -17,8 +17,12 @@
 package types
 
 import (
+	"fmt"
 	"math/big"
 	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
 )
 
 func TestBloom(t *testing.T) {
@@ -35,47 +39,118 @@ func TestBloom(t *testing.T) {
 
 	var bloom Bloom
 	for _, data := range positive {
-		bloom.Add(new(big.Int).SetBytes([]byte(data)))
+		bloom.Add([]byte(data))
 	}
 
 	for _, data := range positive {
-		if !bloom.TestBytes([]byte(data)) {
+		if !bloom.Test([]byte(data)) {
 			t.Error("expected", data, "to test true")
 		}
 	}
 	for _, data := range negative {
-		if bloom.TestBytes([]byte(data)) {
+		if bloom.Test([]byte(data)) {
 			t.Error("did not expect", data, "to test true")
 		}
 	}
 }
 
-/*
-import (
-	"testing"
-
-	"github.com/ethereum/go-ethereum/core/state"
-)
+// TestBloomExtensively does some more thorough tests
+func TestBloomExtensively(t *testing.T) {
+	var exp = common.HexToHash("c8d3ca65cdb4874300a9e39475508f23ed6da09fdbc487f89a2dcf50b09eb263")
+	var b Bloom
+	// Add 100 "random" things
+	for i := 0; i < 100; i++ {
+		data := fmt.Sprintf("xxxxxxxxxx data %d yyyyyyyyyyyyyy", i)
+		b.Add([]byte(data))
+		//b.Add(new(big.Int).SetBytes([]byte(data)))
+	}
+	got := crypto.Keccak256Hash(b.Bytes())
+	if got != exp {
+		t.Errorf("Got %x, exp %x", got, exp)
+	}
+	var b2 Bloom
+	b2.SetBytes(b.Bytes())
+	got2 := crypto.Keccak256Hash(b2.Bytes())
+	if got != got2 {
+		t.Errorf("Got %x, exp %x", got, got2)
+	}
+}
 
-func TestBloom9(t *testing.T) {
-	testCase := []byte("testtest")
-	bin := LogsBloom([]state.Log{
-		{testCase, [][]byte{[]byte("hellohello")}, nil},
-	}).Bytes()
-	res := BloomLookup(bin, testCase)
+func BenchmarkBloom9(b *testing.B) {
+	test := []byte("testestestest")
+	for i := 0; i < b.N; i++ {
+		Bloom9(test)
+	}
+}
 
-	if !res {
-		t.Errorf("Bloom lookup failed")
+func BenchmarkBloom9Lookup(b *testing.B) {
+	toTest := []byte("testtest")
+	bloom := new(Bloom)
+	for i := 0; i < b.N; i++ {
+		bloom.Test(toTest)
 	}
 }
 
+func BenchmarkCreateBloom(b *testing.B) {
 
-func TestAddress(t *testing.T) {
-	block := &Block{}
-	block.Coinbase = common.Hex2Bytes("22341ae42d6dd7384bc8584e50419ea3ac75b83f")
-	fmt.Printf("%x\n", crypto.Keccak256(block.Coinbase))
+	var txs = Transactions{
+		NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil),
+		NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil),
+	}
+	var rSmall = Receipts{
+		&Receipt{
+			Status:            ReceiptStatusFailed,
+			CumulativeGasUsed: 1,
+			Logs: []*Log{
+				{Address: common.BytesToAddress([]byte{0x11})},
+				{Address: common.BytesToAddress([]byte{0x01, 0x11})},
+			},
+			TxHash:          txs[0].Hash(),
+			ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
+			GasUsed:         1,
+		},
+		&Receipt{
+			PostState:         common.Hash{2}.Bytes(),
+			CumulativeGasUsed: 3,
+			Logs: []*Log{
+				{Address: common.BytesToAddress([]byte{0x22})},
+				{Address: common.BytesToAddress([]byte{0x02, 0x22})},
+			},
+			TxHash:          txs[1].Hash(),
+			ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
+			GasUsed:         2,
+		},
+	}
 
-	bin := CreateBloom(block)
-	fmt.Printf("bin = %x\n", common.LeftPadBytes(bin, 64))
+	var rLarge = make(Receipts, 200)
+	// Fill it with 200 receipts x 2 logs
+	for i := 0; i < 200; i += 2 {
+		copy(rLarge[i:], rSmall)
+	}
+	b.Run("small", func(b *testing.B) {
+		b.ReportAllocs()
+		var bl Bloom
+		for i := 0; i < b.N; i++ {
+			bl = CreateBloom(rSmall)
+		}
+		b.StopTimer()
+		var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
+		got := crypto.Keccak256Hash(bl.Bytes())
+		if got != exp {
+			b.Errorf("Got %x, exp %x", got, exp)
+		}
+	})
+	b.Run("large", func(b *testing.B) {
+		b.ReportAllocs()
+		var bl Bloom
+		for i := 0; i < b.N; i++ {
+			bl = CreateBloom(rLarge)
+		}
+		b.StopTimer()
+		var exp = common.HexToHash("c384c56ece49458a427c67b90fefe979ebf7104795be65dc398b280f24104949")
+		got := crypto.Keccak256Hash(bl.Bytes())
+		if got != exp {
+			b.Errorf("Got %x, exp %x", got, exp)
+		}
+	})
 }
-*/
diff --git a/core/types/gen_access_tuple.go b/core/types/gen_access_tuple.go
new file mode 100644
index 0000000000..fc48a84cc0
--- /dev/null
+++ b/core/types/gen_access_tuple.go
@@ -0,0 +1,43 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package types
+
+import (
+	"encoding/json"
+	"errors"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// MarshalJSON marshals as JSON.
+func (a AccessTuple) MarshalJSON() ([]byte, error) {
+	type AccessTuple struct {
+		Address     common.Address `json:"address"        gencodec:"required"`
+		StorageKeys []common.Hash  `json:"storageKeys"    gencodec:"required"`
+	}
+	var enc AccessTuple
+	enc.Address = a.Address
+	enc.StorageKeys = a.StorageKeys
+	return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (a *AccessTuple) UnmarshalJSON(input []byte) error {
+	type AccessTuple struct {
+		Address     *common.Address `json:"address"        gencodec:"required"`
+		StorageKeys []common.Hash   `json:"storageKeys"    gencodec:"required"`
+	}
+	var dec AccessTuple
+	if err := json.Unmarshal(input, &dec); err != nil {
+		return err
+	}
+	if dec.Address == nil {
+		return errors.New("missing required field 'address' for AccessTuple")
+	}
+	a.Address = *dec.Address
+	if dec.StorageKeys == nil {
+		return errors.New("missing required field 'storageKeys' for AccessTuple")
+	}
+	a.StorageKeys = dec.StorageKeys
+	return nil
+}
diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go
index 6e94339478..90e1c14d90 100644
--- a/core/types/gen_log_json.go
+++ b/core/types/gen_log_json.go
@@ -20,9 +20,9 @@ func (l Log) MarshalJSON() ([]byte, error) {
 		Data        hexutil.Bytes  `json:"data" gencodec:"required"`
 		BlockNumber hexutil.Uint64 `json:"blockNumber"`
 		TxHash      common.Hash    `json:"transactionHash" gencodec:"required"`
-		TxIndex     hexutil.Uint   `json:"transactionIndex" gencodec:"required"`
+		TxIndex     hexutil.Uint   `json:"transactionIndex"`
 		BlockHash   common.Hash    `json:"blockHash"`
-		Index       hexutil.Uint   `json:"logIndex" gencodec:"required"`
+		Index       hexutil.Uint   `json:"logIndex"`
 		Removed     bool           `json:"removed"`
 	}
 	var enc Log
@@ -46,9 +46,9 @@ func (l *Log) UnmarshalJSON(input []byte) error {
 		Data        *hexutil.Bytes  `json:"data" gencodec:"required"`
 		BlockNumber *hexutil.Uint64 `json:"blockNumber"`
 		TxHash      *common.Hash    `json:"transactionHash" gencodec:"required"`
-		TxIndex     *hexutil.Uint   `json:"transactionIndex" gencodec:"required"`
+		TxIndex     *hexutil.Uint   `json:"transactionIndex"`
 		BlockHash   *common.Hash    `json:"blockHash"`
-		Index       *hexutil.Uint   `json:"logIndex" gencodec:"required"`
+		Index       *hexutil.Uint   `json:"logIndex"`
 		Removed     *bool           `json:"removed"`
 	}
 	var dec Log
@@ -74,17 +74,15 @@ func (l *Log) UnmarshalJSON(input []byte) error {
 		return errors.New("missing required field 'transactionHash' for Log")
 	}
 	l.TxHash = *dec.TxHash
-	if dec.TxIndex == nil {
-		return errors.New("missing required field 'transactionIndex' for Log")
+	if dec.TxIndex != nil {
+		l.TxIndex = uint(*dec.TxIndex)
 	}
-	l.TxIndex = uint(*dec.TxIndex)
 	if dec.BlockHash != nil {
 		l.BlockHash = *dec.BlockHash
 	}
-	if dec.Index == nil {
-		return errors.New("missing required field 'logIndex' for Log")
+	if dec.Index != nil {
+		l.Index = uint(*dec.Index)
 	}
-	l.Index = uint(*dec.Index)
 	if dec.Removed != nil {
 		l.Removed = *dec.Removed
 	}
diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go
index 790ed65b58..bb892f85be 100644
--- a/core/types/gen_receipt_json.go
+++ b/core/types/gen_receipt_json.go
@@ -16,6 +16,7 @@ var _ = (*receiptMarshaling)(nil)
 // MarshalJSON marshals as JSON.
 func (r Receipt) MarshalJSON() ([]byte, error) {
 	type Receipt struct {
+		Type              hexutil.Uint64 `json:"type,omitempty"`
 		PostState         hexutil.Bytes  `json:"root"`
 		Status            hexutil.Uint64 `json:"status"`
 		CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
@@ -29,6 +30,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
 		TransactionIndex  hexutil.Uint   `json:"transactionIndex"`
 	}
 	var enc Receipt
+	enc.Type = hexutil.Uint64(r.Type)
 	enc.PostState = r.PostState
 	enc.Status = hexutil.Uint64(r.Status)
 	enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed)
@@ -46,6 +48,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
 // UnmarshalJSON unmarshals from JSON.
 func (r *Receipt) UnmarshalJSON(input []byte) error {
 	type Receipt struct {
+		Type              *hexutil.Uint64 `json:"type,omitempty"`
 		PostState         *hexutil.Bytes  `json:"root"`
 		Status            *hexutil.Uint64 `json:"status"`
 		CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
@@ -62,6 +65,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
 	if err := json.Unmarshal(input, &dec); err != nil {
 		return err
 	}
+	if dec.Type != nil {
+		r.Type = uint8(*dec.Type)
+	}
 	if dec.PostState != nil {
 		r.PostState = *dec.PostState
 	}
diff --git a/core/types/gen_tx_json.go b/core/types/gen_tx_json.go
deleted file mode 100644
index e676058ecc..0000000000
--- a/core/types/gen_tx_json.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
-
-package types
-
-import (
-	"encoding/json"
-	"errors"
-	"math/big"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-var _ = (*txdataMarshaling)(nil)
-
-// MarshalJSON marshals as JSON.
-func (t txdata) MarshalJSON() ([]byte, error) {
-	type txdata struct {
-		AccountNonce hexutil.Uint64  `json:"nonce"    gencodec:"required"`
-		Price        *hexutil.Big    `json:"gasPrice" gencodec:"required"`
-		GasLimit     hexutil.Uint64  `json:"gas"      gencodec:"required"`
-		Recipient    *common.Address `json:"to"       rlp:"nil"`
-		Amount       *hexutil.Big    `json:"value"    gencodec:"required"`
-		Payload      hexutil.Bytes   `json:"input"    gencodec:"required"`
-		V            *hexutil.Big    `json:"v" gencodec:"required"`
-		R            *hexutil.Big    `json:"r" gencodec:"required"`
-		S            *hexutil.Big    `json:"s" gencodec:"required"`
-		Hash         *common.Hash    `json:"hash" rlp:"-"`
-	}
-	var enc txdata
-	enc.AccountNonce = hexutil.Uint64(t.AccountNonce)
-	enc.Price = (*hexutil.Big)(t.Price)
-	enc.GasLimit = hexutil.Uint64(t.GasLimit)
-	enc.Recipient = t.Recipient
-	enc.Amount = (*hexutil.Big)(t.Amount)
-	enc.Payload = t.Payload
-	enc.V = (*hexutil.Big)(t.V)
-	enc.R = (*hexutil.Big)(t.R)
-	enc.S = (*hexutil.Big)(t.S)
-	enc.Hash = t.Hash
-	return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals from JSON.
-func (t *txdata) UnmarshalJSON(input []byte) error {
-	type txdata struct {
-		AccountNonce *hexutil.Uint64 `json:"nonce"    gencodec:"required"`
-		Price        *hexutil.Big    `json:"gasPrice" gencodec:"required"`
-		GasLimit     *hexutil.Uint64 `json:"gas"      gencodec:"required"`
-		Recipient    *common.Address `json:"to"       rlp:"nil"`
-		Amount       *hexutil.Big    `json:"value"    gencodec:"required"`
-		Payload      *hexutil.Bytes  `json:"input"    gencodec:"required"`
-		V            *hexutil.Big    `json:"v" gencodec:"required"`
-		R            *hexutil.Big    `json:"r" gencodec:"required"`
-		S            *hexutil.Big    `json:"s" gencodec:"required"`
-		Hash         *common.Hash    `json:"hash" rlp:"-"`
-	}
-	var dec txdata
-	if err := json.Unmarshal(input, &dec); err != nil {
-		return err
-	}
-	if dec.AccountNonce == nil {
-		return errors.New("missing required field 'nonce' for txdata")
-	}
-	t.AccountNonce = uint64(*dec.AccountNonce)
-	if dec.Price == nil {
-		return errors.New("missing required field 'gasPrice' for txdata")
-	}
-	t.Price = (*big.Int)(dec.Price)
-	if dec.GasLimit == nil {
-		return errors.New("missing required field 'gas' for txdata")
-	}
-	t.GasLimit = uint64(*dec.GasLimit)
-	if dec.Recipient != nil {
-		t.Recipient = dec.Recipient
-	}
-	if dec.Amount == nil {
-		return errors.New("missing required field 'value' for txdata")
-	}
-	t.Amount = (*big.Int)(dec.Amount)
-	if dec.Payload == nil {
-		return errors.New("missing required field 'input' for txdata")
-	}
-	t.Payload = *dec.Payload
-	if dec.V == nil {
-		return errors.New("missing required field 'v' for txdata")
-	}
-	t.V = (*big.Int)(dec.V)
-	if dec.R == nil {
-		return errors.New("missing required field 'r' for txdata")
-	}
-	t.R = (*big.Int)(dec.R)
-	if dec.S == nil {
-		return errors.New("missing required field 's' for txdata")
-	}
-	t.S = (*big.Int)(dec.S)
-	if dec.Hash != nil {
-		t.Hash = dec.Hash
-	}
-	return nil
-}
diff --git a/core/types/hashing.go b/core/types/hashing.go
new file mode 100644
index 0000000000..3227cf8a72
--- /dev/null
+++ b/core/types/hashing.go
@@ -0,0 +1,113 @@
+// Copyright 2014 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 .
+
+package types
+
+import (
+	"bytes"
+	"sync"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/rlp"
+	"golang.org/x/crypto/sha3"
+)
+
+// hasherPool holds LegacyKeccak256 hashers for rlpHash.
+var hasherPool = sync.Pool{
+	New: func() interface{} { return sha3.NewLegacyKeccak256() },
+}
+
+// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.
+var encodeBufferPool = sync.Pool{
+	New: func() interface{} { return new(bytes.Buffer) },
+}
+
+// rlpHash encodes x and hashes the encoded bytes.
+func rlpHash(x interface{}) (h common.Hash) {
+	sha := hasherPool.Get().(crypto.KeccakState)
+	defer hasherPool.Put(sha)
+	sha.Reset()
+	rlp.Encode(sha, x)
+	sha.Read(h[:])
+	return h
+}
+
+// prefixedRlpHash writes the prefix into the hasher before rlp-encoding x.
+// It's used for typed transactions.
+func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) {
+	sha := hasherPool.Get().(crypto.KeccakState)
+	defer hasherPool.Put(sha)
+	sha.Reset()
+	sha.Write([]byte{prefix})
+	rlp.Encode(sha, x)
+	sha.Read(h[:])
+	return h
+}
+
+// TrieHasher is the tool used to calculate the hash of derivable list.
+// This is internal, do not use.
+type TrieHasher interface {
+	Reset()
+	Update([]byte, []byte)
+	Hash() common.Hash
+}
+
+// DerivableList is the input to DeriveSha.
+// It is implemented by the 'Transactions' and 'Receipts' types.
+// This is internal, do not use these methods.
+type DerivableList interface {
+	Len() int
+	EncodeIndex(int, *bytes.Buffer)
+}
+
+func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte {
+	buf.Reset()
+	list.EncodeIndex(i, buf)
+	// It's really unfortunate that we need to do perform this copy.
+	// StackTrie holds onto the values until Hash is called, so the values
+	// written to it must not alias.
+	return common.CopyBytes(buf.Bytes())
+}
+
+// DeriveSha creates the tree hashes of transactions and receipts in a block header.
+func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash {
+	hasher.Reset()
+
+	valueBuf := encodeBufferPool.Get().(*bytes.Buffer)
+	defer encodeBufferPool.Put(valueBuf)
+
+	// StackTrie requires values to be inserted in increasing hash order, which is not the
+	// order that `list` provides hashes in. This insertion sequence ensures that the
+	// order is correct.
+	var indexBuf []byte
+	for i := 1; i < list.Len() && i <= 0x7f; i++ {
+		indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
+		value := encodeForDerive(list, i, valueBuf)
+		hasher.Update(indexBuf, value)
+	}
+	if list.Len() > 0 {
+		indexBuf = rlp.AppendUint64(indexBuf[:0], 0)
+		value := encodeForDerive(list, 0, valueBuf)
+		hasher.Update(indexBuf, value)
+	}
+	for i := 0x80; i < list.Len(); i++ {
+		indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i))
+		value := encodeForDerive(list, i, valueBuf)
+		hasher.Update(indexBuf, value)
+	}
+	return hasher.Hash()
+}
diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go
new file mode 100644
index 0000000000..6d1ebf897c
--- /dev/null
+++ b/core/types/hashing_test.go
@@ -0,0 +1,228 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package types_test
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math/big"
+	mrand "math/rand"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+func TestDeriveSha(t *testing.T) {
+	txs, err := genTxs(0)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for len(txs) < 1000 {
+		exp := types.DeriveSha(txs, new(trie.Trie))
+		got := types.DeriveSha(txs, trie.NewStackTrie(nil))
+		if !bytes.Equal(got[:], exp[:]) {
+			t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp)
+		}
+		newTxs, err := genTxs(uint64(len(txs) + 1))
+		if err != nil {
+			t.Fatal(err)
+		}
+		txs = append(txs, newTxs...)
+	}
+}
+
+// TestEIP2718DeriveSha tests that the input to the DeriveSha function is correct.
+func TestEIP2718DeriveSha(t *testing.T) {
+	for _, tc := range []struct {
+		rlpData string
+		exp     string
+	}{
+		{
+			rlpData: "0xb8a701f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9",
+			exp:     "01 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n80 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n",
+		},
+	} {
+		d := &hashToHumanReadable{}
+		var t1, t2 types.Transaction
+		rlp.DecodeBytes(common.FromHex(tc.rlpData), &t1)
+		rlp.DecodeBytes(common.FromHex(tc.rlpData), &t2)
+		txs := types.Transactions{&t1, &t2}
+		types.DeriveSha(txs, d)
+		if tc.exp != string(d.data) {
+			t.Fatalf("Want\n%v\nhave:\n%v", tc.exp, string(d.data))
+		}
+	}
+}
+
+func BenchmarkDeriveSha200(b *testing.B) {
+	txs, err := genTxs(200)
+	if err != nil {
+		b.Fatal(err)
+	}
+	var exp common.Hash
+	var got common.Hash
+	b.Run("std_trie", func(b *testing.B) {
+		b.ResetTimer()
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			exp = types.DeriveSha(txs, new(trie.Trie))
+		}
+	})
+
+	b.Run("stack_trie", func(b *testing.B) {
+		b.ResetTimer()
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			got = types.DeriveSha(txs, trie.NewStackTrie(nil))
+		}
+	})
+	if got != exp {
+		b.Errorf("got %x exp %x", got, exp)
+	}
+}
+
+func TestFuzzDeriveSha(t *testing.T) {
+	// increase this for longer runs -- it's set to quite low for travis
+	rndSeed := mrand.Int()
+	for i := 0; i < 10; i++ {
+		seed := rndSeed + i
+		exp := types.DeriveSha(newDummy(i), new(trie.Trie))
+		got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
+		if !bytes.Equal(got[:], exp[:]) {
+			printList(newDummy(seed))
+			t.Fatalf("seed %d: got %x exp %x", seed, got, exp)
+		}
+	}
+}
+
+// TestDerivableList contains testcases found via fuzzing
+func TestDerivableList(t *testing.T) {
+	type tcase []string
+	tcs := []tcase{
+		{
+			"0xc041",
+		},
+		{
+			"0xf04cf757812428b0763112efb33b6f4fad7deb445e",
+			"0xf04cf757812428b0763112efb33b6f4fad7deb445e",
+		},
+		{
+			"0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d",
+			"0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a",
+			"0x14abd5c47c0be87b0454596baad2",
+			"0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d",
+		},
+	}
+	for i, tc := range tcs[1:] {
+		exp := types.DeriveSha(flatList(tc), new(trie.Trie))
+		got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil))
+		if !bytes.Equal(got[:], exp[:]) {
+			t.Fatalf("case %d: got %x exp %x", i, got, exp)
+		}
+	}
+}
+
+func genTxs(num uint64) (types.Transactions, error) {
+	key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
+	if err != nil {
+		return nil, err
+	}
+	var addr = crypto.PubkeyToAddress(key.PublicKey)
+	newTx := func(i uint64) (*types.Transaction, error) {
+		signer := types.NewEIP155Signer(big.NewInt(18))
+		utx := types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil)
+		tx, err := types.SignTx(utx, signer, key)
+		return tx, err
+	}
+	var txs types.Transactions
+	for i := uint64(0); i < num; i++ {
+		tx, err := newTx(i)
+		if err != nil {
+			return nil, err
+		}
+		txs = append(txs, tx)
+	}
+	return txs, nil
+}
+
+type dummyDerivableList struct {
+	len  int
+	seed int
+}
+
+func newDummy(seed int) *dummyDerivableList {
+	d := &dummyDerivableList{}
+	src := mrand.NewSource(int64(seed))
+	// don't use lists longer than 4K items
+	d.len = int(src.Int63() & 0x0FFF)
+	d.seed = seed
+	return d
+}
+
+func (d *dummyDerivableList) Len() int {
+	return d.len
+}
+
+func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) {
+	src := mrand.NewSource(int64(d.seed + i))
+	// max item size 256, at least 1 byte per item
+	size := 1 + src.Int63()&0x00FF
+	io.CopyN(w, mrand.New(src), size)
+}
+
+func printList(l types.DerivableList) {
+	fmt.Printf("list length: %d\n", l.Len())
+	fmt.Printf("{\n")
+	for i := 0; i < l.Len(); i++ {
+		var buf bytes.Buffer
+		l.EncodeIndex(i, &buf)
+		fmt.Printf("\"0x%x\",\n", buf.Bytes())
+	}
+	fmt.Printf("},\n")
+}
+
+type flatList []string
+
+func (f flatList) Len() int {
+	return len(f)
+}
+func (f flatList) EncodeIndex(i int, w *bytes.Buffer) {
+	w.Write(hexutil.MustDecode(f[i]))
+}
+
+type hashToHumanReadable struct {
+	data []byte
+}
+
+func (d *hashToHumanReadable) Reset() {
+	d.data = make([]byte, 0)
+}
+
+func (d *hashToHumanReadable) Update(i []byte, i2 []byte) {
+	l := fmt.Sprintf("%x %x\n", i, i2)
+	d.data = append(d.data, []byte(l)...)
+}
+
+func (d *hashToHumanReadable) Hash() common.Hash {
+	return common.Hash{}
+}
diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go
new file mode 100644
index 0000000000..41ad44f379
--- /dev/null
+++ b/core/types/legacy_tx.go
@@ -0,0 +1,111 @@
+// 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 .
+
+package types
+
+import (
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// LegacyTx is the transaction data of regular Ethereum transactions.
+type LegacyTx struct {
+	Nonce    uint64          // nonce of sender account
+	GasPrice *big.Int        // wei per gas
+	Gas      uint64          // gas limit
+	To       *common.Address `rlp:"nil"` // nil means contract creation
+	Value    *big.Int        // wei amount
+	Data     []byte          // contract invocation input data
+	V, R, S  *big.Int        // signature values
+}
+
+// NewTransaction creates an unsigned legacy transaction.
+// Deprecated: use NewTx instead.
+func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
+	return NewTx(&LegacyTx{
+		Nonce:    nonce,
+		To:       &to,
+		Value:    amount,
+		Gas:      gasLimit,
+		GasPrice: gasPrice,
+		Data:     data,
+	})
+}
+
+// NewContractCreation creates an unsigned legacy transaction.
+// Deprecated: use NewTx instead.
+func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
+	return NewTx(&LegacyTx{
+		Nonce:    nonce,
+		Value:    amount,
+		Gas:      gasLimit,
+		GasPrice: gasPrice,
+		Data:     data,
+	})
+}
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *LegacyTx) copy() TxData {
+	cpy := &LegacyTx{
+		Nonce: tx.Nonce,
+		To:    tx.To, // TODO: copy pointed-to address
+		Data:  common.CopyBytes(tx.Data),
+		Gas:   tx.Gas,
+		// These are initialized below.
+		Value:    new(big.Int),
+		GasPrice: new(big.Int),
+		V:        new(big.Int),
+		R:        new(big.Int),
+		S:        new(big.Int),
+	}
+	if tx.Value != nil {
+		cpy.Value.Set(tx.Value)
+	}
+	if tx.GasPrice != nil {
+		cpy.GasPrice.Set(tx.GasPrice)
+	}
+	if tx.V != nil {
+		cpy.V.Set(tx.V)
+	}
+	if tx.R != nil {
+		cpy.R.Set(tx.R)
+	}
+	if tx.S != nil {
+		cpy.S.Set(tx.S)
+	}
+	return cpy
+}
+
+// accessors for innerTx.
+
+func (tx *LegacyTx) txType() byte           { return LegacyTxType }
+func (tx *LegacyTx) chainID() *big.Int      { return deriveChainId(tx.V) }
+func (tx *LegacyTx) accessList() AccessList { return nil }
+func (tx *LegacyTx) data() []byte           { return tx.Data }
+func (tx *LegacyTx) gas() uint64            { return tx.Gas }
+func (tx *LegacyTx) gasPrice() *big.Int     { return tx.GasPrice }
+func (tx *LegacyTx) value() *big.Int        { return tx.Value }
+func (tx *LegacyTx) nonce() uint64          { return tx.Nonce }
+func (tx *LegacyTx) to() *common.Address    { return tx.To }
+
+func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) {
+	return tx.V, tx.R, tx.S
+}
+
+func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) {
+	tx.V, tx.R, tx.S = v, r, s
+}
diff --git a/core/types/log.go b/core/types/log.go
index 006f62bbf6..88274e39da 100644
--- a/core/types/log.go
+++ b/core/types/log.go
@@ -44,11 +44,11 @@ type Log struct {
 	// hash of the transaction
 	TxHash common.Hash `json:"transactionHash" gencodec:"required"`
 	// index of the transaction in the block
-	TxIndex uint `json:"transactionIndex" gencodec:"required"`
+	TxIndex uint `json:"transactionIndex"`
 	// hash of the block in which the transaction was included
 	BlockHash common.Hash `json:"blockHash"`
 	// index of the log in the block
-	Index uint `json:"logIndex" gencodec:"required"`
+	Index uint `json:"logIndex"`
 
 	// The Removed field is true if this log was reverted due to a chain reorganisation.
 	// You must pay attention to this field if you receive logs through a filter query.
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 00a67a05c5..4a61b125f2 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -38,6 +38,9 @@ var (
 	receiptStatusSuccessfulRLP = []byte{0x01}
 )
 
+// This error is returned when a typed receipt is decoded, but the string is empty.
+var errEmptyTypedReceipt = errors.New("empty typed receipt bytes")
+
 const (
 	// ReceiptStatusFailed is the status code of a transaction if execution failed.
 	ReceiptStatusFailed = uint64(0)
@@ -55,6 +58,7 @@ type OriginalDataAndReceipt struct {
 // Receipt represents the results of a transaction.
 type Receipt struct {
 	// Consensus fields: These fields are defined by the Yellow Paper
+	Type              uint8  `json:"type,omitempty"`
 	PostState         []byte `json:"root"`
 	Status            uint64 `json:"status"`
 	CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
@@ -75,6 +79,7 @@ type Receipt struct {
 }
 
 type receiptMarshaling struct {
+	Type              hexutil.Uint64
 	PostState         hexutil.Bytes
 	Status            hexutil.Uint64
 	CumulativeGasUsed hexutil.Uint64
@@ -120,8 +125,13 @@ type v3StoredReceiptRLP struct {
 }
 
 // NewReceipt creates a barebone transaction receipt, copying the init fields.
+// Deprecated: create receipts using a struct literal instead.
 func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt {
-	r := &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: cumulativeGasUsed}
+	r := &Receipt{
+		Type:              LegacyTxType,
+		PostState:         common.CopyBytes(root),
+		CumulativeGasUsed: cumulativeGasUsed,
+	}
 	if failed {
 		r.Status = ReceiptStatusFailed
 	} else {
@@ -133,21 +143,65 @@ func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt {
 // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt
 // into an RLP stream. If no post state is present, byzantium fork is assumed.
 func (r *Receipt) EncodeRLP(w io.Writer) error {
-	return rlp.Encode(w, &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs})
+	data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
+	if r.Type == LegacyTxType {
+		return rlp.Encode(w, data)
+	}
+	// It's an EIP-2718 typed TX receipt.
+	if r.Type != AccessListTxType {
+		return ErrTxTypeNotSupported
+	}
+	buf := encodeBufferPool.Get().(*bytes.Buffer)
+	defer encodeBufferPool.Put(buf)
+	buf.Reset()
+	buf.WriteByte(r.Type)
+	if err := rlp.Encode(buf, data); err != nil {
+		return err
+	}
+	return rlp.Encode(w, buf.Bytes())
 }
 
 // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt
 // from an RLP stream.
 func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
-	var dec receiptRLP
-	if err := s.Decode(&dec); err != nil {
-		return err
-	}
-	if err := r.setStatus(dec.PostStateOrStatus); err != nil {
+	kind, _, err := s.Kind()
+	switch {
+	case err != nil:
 		return err
+	case kind == rlp.List:
+		// It's a legacy receipt.
+		var dec receiptRLP
+		if err := s.Decode(&dec); err != nil {
+			return err
+		}
+		r.Type = LegacyTxType
+		return r.setFromRLP(dec)
+	case kind == rlp.String:
+		// It's an EIP-2718 typed tx receipt.
+		b, err := s.Bytes()
+		if err != nil {
+			return err
+		}
+		if len(b) == 0 {
+			return errEmptyTypedReceipt
+		}
+		r.Type = b[0]
+		if r.Type == AccessListTxType {
+			var dec receiptRLP
+			if err := rlp.DecodeBytes(b[1:], &dec); err != nil {
+				return err
+			}
+			return r.setFromRLP(dec)
+		}
+		return ErrTxTypeNotSupported
+	default:
+		return rlp.ErrExpectedList
 	}
-	r.CumulativeGasUsed, r.Bloom, r.Logs = dec.CumulativeGasUsed, dec.Bloom, dec.Logs
-	return nil
+}
+
+func (r *Receipt) setFromRLP(data receiptRLP) error {
+	r.CumulativeGasUsed, r.Bloom, r.Logs = data.CumulativeGasUsed, data.Bloom, data.Logs
+	return r.setStatus(data.PostStateOrStatus)
 }
 
 func (r *Receipt) setStatus(postStateOrStatus []byte) error {
@@ -178,7 +232,6 @@ func (r *Receipt) statusEncoding() []byte {
 // to approximate and limit the memory consumption of various caches.
 func (r *Receipt) Size() common.StorageSize {
 	size := common.StorageSize(unsafe.Sizeof(*r)) + common.StorageSize(len(r.PostState))
-
 	size += common.StorageSize(len(r.Logs)) * common.StorageSize(unsafe.Sizeof(Log{}))
 	for _, log := range r.Logs {
 		size += common.StorageSize(len(log.Topics)*common.HashLength + len(log.Data))
@@ -283,19 +336,27 @@ func decodeV3StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
 	return nil
 }
 
-// Receipts is a wrapper around a Receipt array to implement DerivableList.
+// Receipts implements DerivableList for receipts.
 type Receipts []*Receipt
 
 // Len returns the number of receipts in this list.
-func (r Receipts) Len() int { return len(r) }
-
-// GetRlp returns the RLP encoding of one receipt from the list.
-func (r Receipts) GetRlp(i int) []byte {
-	bytes, err := rlp.EncodeToBytes(r[i])
-	if err != nil {
-		panic(err)
+func (rs Receipts) Len() int { return len(rs) }
+
+// EncodeIndex encodes the i'th receipt to w.
+func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
+	r := rs[i]
+	data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
+	switch r.Type {
+	case LegacyTxType:
+		rlp.Encode(w, data)
+	case AccessListTxType:
+		w.WriteByte(AccessListTxType)
+		rlp.Encode(w, data)
+	default:
+		// For unsupported types, write nothing. Since this is for
+		// DeriveSha, the error will be caught matching the derived hash
+		// to the block.
 	}
-	return bytes
 }
 
 // DeriveFields fills the receipts with their computed fields based on consensus
@@ -308,7 +369,8 @@ func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, num
 		return errors.New("transaction and receipt count mismatch")
 	}
 	for i := 0; i < len(r); i++ {
-		// The transaction hash can be retrieved from the transaction itself
+		// The transaction type and hash can be retrieved from the transaction itself
+		r[i].Type = txs[i].Type()
 		r[i].TxHash = txs[i].Hash()
 
 		// block location fields
diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go
index 806b3dd2ab..22a316c237 100644
--- a/core/types/receipt_test.go
+++ b/core/types/receipt_test.go
@@ -29,6 +29,15 @@ import (
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
+func TestDecodeEmptyTypedReceipt(t *testing.T) {
+	input := []byte{0x80}
+	var r Receipt
+	err := rlp.DecodeBytes(input, &r)
+	if err != errEmptyTypedReceipt {
+		t.Fatal("wrong error:", err)
+	}
+}
+
 func TestLegacyReceiptDecoding(t *testing.T) {
 	tests := []struct {
 		name   string
@@ -154,9 +163,29 @@ func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
 // Tests that receipt data can be correctly derived from the contextual infos
 func TestDeriveFields(t *testing.T) {
 	// Create a few transactions to have receipts for
+	to2 := common.HexToAddress("0x2")
+	to3 := common.HexToAddress("0x3")
 	txs := Transactions{
-		NewContractCreation(1, big.NewInt(1), 1, big.NewInt(1), nil),
-		NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil),
+		NewTx(&LegacyTx{
+			Nonce:    1,
+			Value:    big.NewInt(1),
+			Gas:      1,
+			GasPrice: big.NewInt(1),
+		}),
+		NewTx(&LegacyTx{
+			To:       &to2,
+			Nonce:    2,
+			Value:    big.NewInt(2),
+			Gas:      2,
+			GasPrice: big.NewInt(2),
+		}),
+		NewTx(&AccessListTx{
+			To:       &to3,
+			Nonce:    3,
+			Value:    big.NewInt(3),
+			Gas:      3,
+			GasPrice: big.NewInt(3),
+		}),
 	}
 	// Create the corresponding receipts
 	receipts := Receipts{
@@ -182,6 +211,18 @@ func TestDeriveFields(t *testing.T) {
 			ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
 			GasUsed:         2,
 		},
+		&Receipt{
+			Type:              AccessListTxType,
+			PostState:         common.Hash{3}.Bytes(),
+			CumulativeGasUsed: 6,
+			Logs: []*Log{
+				{Address: common.BytesToAddress([]byte{0x33})},
+				{Address: common.BytesToAddress([]byte{0x03, 0x33})},
+			},
+			TxHash:          txs[2].Hash(),
+			ContractAddress: common.BytesToAddress([]byte{0x03, 0x33, 0x33}),
+			GasUsed:         3,
+		},
 	}
 	// Clear all the computed fields and re-derive them
 	number := big.NewInt(1)
@@ -196,6 +237,9 @@ func TestDeriveFields(t *testing.T) {
 
 	logIndex := uint(0)
 	for i := range receipts {
+		if receipts[i].Type != txs[i].Type() {
+			t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type())
+		}
 		if receipts[i].TxHash != txs[i].Hash() {
 			t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String())
 		}
@@ -243,6 +287,34 @@ func TestDeriveFields(t *testing.T) {
 	}
 }
 
+// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt
+// rlp decoder, which failed due to a shadowing error.
+func TestTypedReceiptEncodingDecoding(t *testing.T) {
+	var payload = common.FromHex("f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0")
+	check := func(bundle []*Receipt) {
+		t.Helper()
+		for i, receipt := range bundle {
+			if got, want := receipt.Type, uint8(1); got != want {
+				t.Fatalf("bundle %d: got %x, want %x", i, got, want)
+			}
+		}
+	}
+	{
+		var bundle []*Receipt
+		rlp.DecodeBytes(payload, &bundle)
+		check(bundle)
+	}
+	{
+		var bundle []*Receipt
+		r := bytes.NewReader(payload)
+		s := rlp.NewStream(r, uint64(len(payload)))
+		if err := s.Decode(&bundle); err != nil {
+			t.Fatal(err)
+		}
+		check(bundle)
+	}
+}
+
 func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) {
 	t.Helper()
 
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 3eb8df0ac7..a35e07a5a3 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -17,222 +17,318 @@
 package types
 
 import (
+	"bytes"
 	"container/heap"
 	"errors"
 	"io"
 	"math/big"
 	"sync/atomic"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
-//go:generate gencodec -type txdata -field-override txdataMarshaling -out gen_tx_json.go
-
 var (
-	ErrInvalidSig = errors.New("invalid transaction v, r, s values")
+	ErrInvalidSig           = errors.New("invalid transaction v, r, s values")
+	ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures")
+	ErrInvalidTxType        = errors.New("transaction type not valid in this context")
+	ErrTxTypeNotSupported   = errors.New("transaction type not supported")
+	errEmptyTypedTx         = errors.New("empty typed transaction bytes")
+)
+
+// Transaction types.
+const (
+	LegacyTxType = iota
+	AccessListTxType
 )
 
+// Transaction is an Ethereum transaction.
 type Transaction struct {
-	data txdata
+	inner TxData    // Consensus contents of a transaction
+	time  time.Time // Time first seen locally (spam avoidance)
+
 	// caches
 	hash atomic.Value
 	size atomic.Value
 	from atomic.Value
 }
 
-type txdata struct {
-	AccountNonce uint64          `json:"nonce"    gencodec:"required"`
-	Price        *big.Int        `json:"gasPrice" gencodec:"required"`
-	GasLimit     uint64          `json:"gas"      gencodec:"required"`
-	Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
-	Amount       *big.Int        `json:"value"    gencodec:"required"`
-	Payload      []byte          `json:"input"    gencodec:"required"`
+// NewTx creates a new transaction.
+func NewTx(inner TxData) *Transaction {
+	tx := new(Transaction)
+	tx.setDecoded(inner.copy(), 0)
+	return tx
+}
 
-	// Signature values
-	V *big.Int `json:"v" gencodec:"required"`
-	R *big.Int `json:"r" gencodec:"required"`
-	S *big.Int `json:"s" gencodec:"required"`
+// TxData is the underlying data of a transaction.
+//
+// This is implemented by LegacyTx and AccessListTx.
+type TxData interface {
+	txType() byte // returns the type ID
+	copy() TxData // creates a deep copy and initializes all fields
+
+	chainID() *big.Int
+	accessList() AccessList
+	data() []byte
+	gas() uint64
+	gasPrice() *big.Int
+	value() *big.Int
+	nonce() uint64
+	to() *common.Address
+
+	rawSignatureValues() (v, r, s *big.Int)
+	setSignatureValues(chainID, v, r, s *big.Int)
+}
 
-	// This is only used when marshaling to JSON.
-	Hash *common.Hash `json:"hash" rlp:"-"`
+// EncodeRLP implements rlp.Encoder
+func (tx *Transaction) EncodeRLP(w io.Writer) error {
+	if tx.Type() == LegacyTxType {
+		return rlp.Encode(w, tx.inner)
+	}
+	// It's an EIP-2718 typed TX envelope.
+	buf := encodeBufferPool.Get().(*bytes.Buffer)
+	defer encodeBufferPool.Put(buf)
+	buf.Reset()
+	if err := tx.encodeTyped(buf); err != nil {
+		return err
+	}
+	return rlp.Encode(w, buf.Bytes())
 }
 
-type txdataMarshaling struct {
-	AccountNonce hexutil.Uint64
-	Price        *hexutil.Big
-	GasLimit     hexutil.Uint64
-	Amount       *hexutil.Big
-	Payload      hexutil.Bytes
-	V            *hexutil.Big
-	R            *hexutil.Big
-	S            *hexutil.Big
+// encodeTyped writes the canonical encoding of a typed transaction to w.
+func (tx *Transaction) encodeTyped(w *bytes.Buffer) error {
+	w.WriteByte(tx.Type())
+	return rlp.Encode(w, tx.inner)
 }
 
-func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
-	return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data)
+// MarshalBinary returns the canonical encoding of the transaction.
+// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed
+// transactions, it returns the type and payload.
+func (tx *Transaction) MarshalBinary() ([]byte, error) {
+	if tx.Type() == LegacyTxType {
+		return rlp.EncodeToBytes(tx.inner)
+	}
+	var buf bytes.Buffer
+	err := tx.encodeTyped(&buf)
+	return buf.Bytes(), err
 }
 
-func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
-	return newTransaction(nonce, nil, amount, gasLimit, gasPrice, data)
+// DecodeRLP implements rlp.Decoder
+func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
+	kind, size, err := s.Kind()
+	switch {
+	case err != nil:
+		return err
+	case kind == rlp.List:
+		// It's a legacy transaction.
+		var inner LegacyTx
+		err := s.Decode(&inner)
+		if err == nil {
+			tx.setDecoded(&inner, int(rlp.ListSize(size)))
+		}
+		return err
+	case kind == rlp.String:
+		// It's an EIP-2718 typed TX envelope.
+		var b []byte
+		if b, err = s.Bytes(); err != nil {
+			return err
+		}
+		inner, err := tx.decodeTyped(b)
+		if err == nil {
+			tx.setDecoded(inner, len(b))
+		}
+		return err
+	default:
+		return rlp.ErrExpectedList
+	}
 }
 
-func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction {
-	if len(data) > 0 {
-		data = common.CopyBytes(data)
+// UnmarshalBinary decodes the canonical encoding of transactions.
+// It supports legacy RLP transactions and EIP2718 typed transactions.
+func (tx *Transaction) UnmarshalBinary(b []byte) error {
+	if len(b) > 0 && b[0] > 0x7f {
+		// It's a legacy transaction.
+		var data LegacyTx
+		err := rlp.DecodeBytes(b, &data)
+		if err != nil {
+			return err
+		}
+		tx.setDecoded(&data, len(b))
+		return nil
 	}
-	d := txdata{
-		AccountNonce: nonce,
-		Recipient:    to,
-		Payload:      data,
-		Amount:       new(big.Int),
-		GasLimit:     gasLimit,
-		Price:        new(big.Int),
-		V:            new(big.Int),
-		R:            new(big.Int),
-		S:            new(big.Int),
+	// It's an EIP2718 typed transaction envelope.
+	inner, err := tx.decodeTyped(b)
+	if err != nil {
+		return err
 	}
-	if amount != nil {
-		d.Amount.Set(amount)
+	tx.setDecoded(inner, len(b))
+	return nil
+}
+
+// decodeTyped decodes a typed transaction from the canonical format.
+func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
+	if len(b) == 0 {
+		return nil, errEmptyTypedTx
 	}
-	if gasPrice != nil {
-		d.Price.Set(gasPrice)
+	switch b[0] {
+	case AccessListTxType:
+		var inner AccessListTx
+		err := rlp.DecodeBytes(b[1:], &inner)
+		return &inner, err
+	default:
+		return nil, ErrTxTypeNotSupported
 	}
-
-	return &Transaction{data: d}
 }
 
-// ChainId returns which chain id this transaction was signed for (if at all)
-func (tx *Transaction) ChainId() *big.Int {
-	return deriveChainId(tx.data.V)
+// setDecoded sets the inner transaction and size after decoding.
+func (tx *Transaction) setDecoded(inner TxData, size int) {
+	tx.inner = inner
+	tx.time = time.Now()
+	if size > 0 {
+		tx.size.Store(common.StorageSize(size))
+	}
 }
 
-// Protected returns whether the transaction is protected from replay protection.
-func (tx *Transaction) Protected() bool {
-	return isProtectedV(tx.data.V)
+func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error {
+	if isProtectedV(v) && !maybeProtected {
+		return ErrUnexpectedProtection
+	}
+
+	var plainV byte
+	if isProtectedV(v) {
+		chainID := deriveChainId(v).Uint64()
+		plainV = byte(v.Uint64() - 35 - 2*chainID)
+	} else if maybeProtected {
+		// Only EIP-155 signatures can be optionally protected. Since
+		// we determined this v value is not protected, it must be a
+		// raw 27 or 28.
+		plainV = byte(v.Uint64() - 27)
+	} else {
+		// If the signature is not optionally protected, we assume it
+		// must already be equal to the recovery id.
+		plainV = byte(v.Uint64())
+	}
+	if !crypto.ValidateSignatureValues(plainV, r, s, false) {
+		return ErrInvalidSig
+	}
+
+	return nil
 }
 
 func isProtectedV(V *big.Int) bool {
 	if V.BitLen() <= 8 {
 		v := V.Uint64()
-		return v != 27 && v != 28
+		return v != 27 && v != 28 && v != 1 && v != 0
 	}
 	// anything not 27 or 28 is considered protected
 	return true
 }
 
-// EncodeRLP implements rlp.Encoder
-func (tx *Transaction) EncodeRLP(w io.Writer) error {
-	return rlp.Encode(w, &tx.data)
-}
-
-// DecodeRLP implements rlp.Decoder
-func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
-	_, size, _ := s.Kind()
-	err := s.Decode(&tx.data)
-	if err == nil {
-		tx.size.Store(common.StorageSize(rlp.ListSize(size)))
+// Protected says whether the transaction is replay-protected.
+func (tx *Transaction) Protected() bool {
+	switch tx := tx.inner.(type) {
+	case *LegacyTx:
+		return tx.V != nil && isProtectedV(tx.V)
+	default:
+		return true
 	}
+}
 
-	return err
+// Type returns the transaction type.
+func (tx *Transaction) Type() uint8 {
+	return tx.inner.txType()
 }
 
-// MarshalJSON encodes the web3 RPC transaction format.
-func (tx *Transaction) MarshalJSON() ([]byte, error) {
-	hash := tx.Hash()
-	data := tx.data
-	data.Hash = &hash
-	return data.MarshalJSON()
+// ChainId returns the EIP155 chain ID of the transaction. The return value will always be
+// non-nil. For legacy transactions which are not replay-protected, the return value is
+// zero.
+func (tx *Transaction) ChainId() *big.Int {
+	return tx.inner.chainID()
 }
 
-// UnmarshalJSON decodes the web3 RPC transaction format.
-func (tx *Transaction) UnmarshalJSON(input []byte) error {
-	var dec txdata
-	if err := dec.UnmarshalJSON(input); err != nil {
-		return err
-	}
+// Data returns the input data of the transaction.
+func (tx *Transaction) Data() []byte { return tx.inner.data() }
 
-	withSignature := dec.V.Sign() != 0 || dec.R.Sign() != 0 || dec.S.Sign() != 0
-	if withSignature {
-		var V byte
-		if isProtectedV(dec.V) {
-			chainID := deriveChainId(dec.V).Uint64()
-			V = byte(dec.V.Uint64() - 35 - 2*chainID)
-		} else {
-			V = byte(dec.V.Uint64() - 27)
-		}
-		if !crypto.ValidateSignatureValues(V, dec.R, dec.S, false) {
-			return ErrInvalidSig
-		}
-	}
+// AccessList returns the access list of the transaction.
+func (tx *Transaction) AccessList() AccessList { return tx.inner.accessList() }
 
-	*tx = Transaction{data: dec}
-	return nil
-}
+// Gas returns the gas limit of the transaction.
+func (tx *Transaction) Gas() uint64 { return tx.inner.gas() }
+
+// GasPrice returns the gas price of the transaction.
+func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) }
+
+// Value returns the ether amount of the transaction.
+func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) }
 
-func (tx *Transaction) Data() []byte       { return common.CopyBytes(tx.data.Payload) }
-func (tx *Transaction) Gas() uint64        { return tx.data.GasLimit }
-func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
-func (tx *Transaction) Value() *big.Int    { return new(big.Int).Set(tx.data.Amount) }
-func (tx *Transaction) Nonce() uint64      { return tx.data.AccountNonce }
-func (tx *Transaction) CheckNonce() bool   { return true }
+// Nonce returns the sender account nonce of the transaction.
+func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() }
 
 // To returns the recipient address of the transaction.
-// It returns nil if the transaction is a contract creation.
+// For contract-creation transactions, To returns nil.
 func (tx *Transaction) To() *common.Address {
-	if tx.data.Recipient == nil {
+	// Copy the pointed-to address.
+	ito := tx.inner.to()
+	if ito == nil {
 		return nil
 	}
-	to := *tx.data.Recipient
-	return &to
+	cpy := *ito
+	return &cpy
 }
 
-// Hash hashes the RLP encoding of tx.
-// It uniquely identifies the transaction.
+// Cost returns gas * gasPrice + value.
+func (tx *Transaction) Cost() *big.Int {
+	total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
+	total.Add(total, tx.Value())
+	return total
+}
+
+// RawSignatureValues returns the V, R, S signature values of the transaction.
+// The return values should not be modified by the caller.
+func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
+	return tx.inner.rawSignatureValues()
+}
+
+// GasPriceCmp compares the gas prices of two transactions.
+func (tx *Transaction) GasPriceCmp(other *Transaction) int {
+	return tx.inner.gasPrice().Cmp(other.inner.gasPrice())
+}
+
+// GasPriceIntCmp compares the gas price of the transaction against the given price.
+func (tx *Transaction) GasPriceIntCmp(other *big.Int) int {
+	return tx.inner.gasPrice().Cmp(other)
+}
+
+// Hash returns the transaction hash.
 func (tx *Transaction) Hash() common.Hash {
 	if hash := tx.hash.Load(); hash != nil {
 		return hash.(common.Hash)
 	}
-	v := rlpHash(tx)
-	tx.hash.Store(v)
-	return v
+
+	var h common.Hash
+	if tx.Type() == LegacyTxType {
+		h = rlpHash(tx.inner)
+	} else {
+		h = prefixedRlpHash(tx.Type(), tx.inner)
+	}
+	tx.hash.Store(h)
+	return h
 }
 
 // Size returns the true RLP encoded storage size of the transaction, either by
-// encoding and returning it, or returning a previsouly cached value.
+// encoding and returning it, or returning a previously cached value.
 func (tx *Transaction) Size() common.StorageSize {
 	if size := tx.size.Load(); size != nil {
 		return size.(common.StorageSize)
 	}
 	c := writeCounter(0)
-	rlp.Encode(&c, &tx.data)
+	rlp.Encode(&c, &tx.inner)
 	tx.size.Store(common.StorageSize(c))
 	return common.StorageSize(c)
 }
 
-// AsMessage returns the transaction as a core.Message.
-//
-// AsMessage requires a signer to derive the sender.
-//
-// XXX Rename message to something less arbitrary?
-func (tx *Transaction) AsMessage(s Signer) (Message, error) {
-	msg := Message{
-		nonce:      tx.data.AccountNonce,
-		gasLimit:   tx.data.GasLimit,
-		gasPrice:   new(big.Int).Set(tx.data.Price),
-		to:         tx.data.Recipient,
-		amount:     tx.data.Amount,
-		data:       tx.data.Payload,
-		checkNonce: true,
-	}
-
-	var err error
-	msg.from, err = Sender(s, tx)
-	return msg, err
-}
-
 // WithSignature returns a new transaction with the given signature.
 // This signature needs to be in the [R || S || V] format where V is 0 or 1.
 func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) {
@@ -240,37 +336,27 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e
 	if err != nil {
 		return nil, err
 	}
-	cpy := &Transaction{data: tx.data}
-	cpy.data.R, cpy.data.S, cpy.data.V = r, s, v
-	return cpy, nil
+	cpy := tx.inner.copy()
+	cpy.setSignatureValues(signer.ChainID(), v, r, s)
+	return &Transaction{inner: cpy, time: tx.time}, nil
 }
 
-// Cost returns amount + gasprice * gaslimit.
-func (tx *Transaction) Cost() *big.Int {
-	total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit))
-	total.Add(total, tx.data.Amount)
-	return total
-}
-
-// RawSignatureValues returns the V, R, S signature values of the transaction.
-// The return values should not be modified by the caller.
-func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
-	return tx.data.V, tx.data.R, tx.data.S
-}
-
-// Transactions is a Transaction slice type for basic sorting.
+// Transactions implements DerivableList for transactions.
 type Transactions []*Transaction
 
 // Len returns the length of s.
 func (s Transactions) Len() int { return len(s) }
 
-// Swap swaps the i'th and the j'th element in s.
-func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-
-// GetRlp implements Rlpable and returns the i'th element of s in rlp.
-func (s Transactions) GetRlp(i int) []byte {
-	enc, _ := rlp.EncodeToBytes(s[i])
-	return enc
+// EncodeIndex encodes the i'th transaction to w. Note that this does not check for errors
+// because we assume that *Transaction will only ever contain valid txs that were either
+// constructed by decoding or via public API in this package.
+func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) {
+	tx := s[i]
+	if tx.Type() == LegacyTxType {
+		rlp.Encode(w, tx.inner)
+	} else {
+		tx.encodeTyped(w)
+	}
 }
 
 // TxDifference returns a new set which is the difference between a and b.
@@ -297,22 +383,30 @@ func TxDifference(a, b Transactions) Transactions {
 type TxByNonce Transactions
 
 func (s TxByNonce) Len() int           { return len(s) }
-func (s TxByNonce) Less(i, j int) bool { return s[i].data.AccountNonce < s[j].data.AccountNonce }
+func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() }
 func (s TxByNonce) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 
-// TxByPrice implements both the sort and the heap interface, making it useful
+// TxByPriceAndTime implements both the sort and the heap interface, making it useful
 // for all at once sorting as well as individually adding and removing elements.
-type TxByPrice Transactions
-
-func (s TxByPrice) Len() int           { return len(s) }
-func (s TxByPrice) Less(i, j int) bool { return s[i].data.Price.Cmp(s[j].data.Price) > 0 }
-func (s TxByPrice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+type TxByPriceAndTime Transactions
+
+func (s TxByPriceAndTime) Len() int { return len(s) }
+func (s TxByPriceAndTime) Less(i, j int) bool {
+	// If the prices are equal, use the time the transaction was first seen for
+	// deterministic sorting
+	cmp := s[i].GasPrice().Cmp(s[j].GasPrice())
+	if cmp == 0 {
+		return s[i].time.Before(s[j].time)
+	}
+	return cmp > 0
+}
+func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
 
-func (s *TxByPrice) Push(x interface{}) {
+func (s *TxByPriceAndTime) Push(x interface{}) {
 	*s = append(*s, x.(*Transaction))
 }
 
-func (s *TxByPrice) Pop() interface{} {
+func (s *TxByPriceAndTime) Pop() interface{} {
 	old := *s
 	n := len(old)
 	x := old[n-1]
@@ -325,7 +419,7 @@ func (s *TxByPrice) Pop() interface{} {
 // entire batches of transactions for non-executable accounts.
 type TransactionsByPriceAndNonce struct {
 	txs    map[common.Address]Transactions // Per account nonce-sorted list of transactions
-	heads  TxByPrice                       // Next transaction for each unique account (price heap)
+	heads  TxByPriceAndTime                // Next transaction for each unique account (price heap)
 	signer Signer                          // Signer for the set of transactions
 }
 
@@ -335,16 +429,16 @@ type TransactionsByPriceAndNonce struct {
 // Note, the input map is reowned so the caller should not interact any more with
 // if after providing it to the constructor.
 func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce {
-	// Initialize a price based heap with the head transactions
-	heads := make(TxByPrice, 0, len(txs))
+	// Initialize a price and received time based heap with the head transactions
+	heads := make(TxByPriceAndTime, 0, len(txs))
 	for from, accTxs := range txs {
-		heads = append(heads, accTxs[0])
 		// Ensure the sender address is from the signer
-		acc, _ := Sender(signer, accTxs[0])
-		txs[acc] = accTxs[1:]
-		if from != acc {
+		if acc, _ := Sender(signer, accTxs[0]); acc != from {
 			delete(txs, from)
+			continue
 		}
+		heads = append(heads, accTxs[0])
+		txs[from] = accTxs[1:]
 	}
 	heap.Init(&heads)
 
@@ -393,10 +487,11 @@ type Message struct {
 	gasLimit   uint64
 	gasPrice   *big.Int
 	data       []byte
+	accessList AccessList
 	checkNonce bool
 }
 
-func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message {
+func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, accessList AccessList, checkNonce bool) Message {
 	return Message{
 		from:       from,
 		to:         to,
@@ -405,15 +500,35 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
 		gasLimit:   gasLimit,
 		gasPrice:   gasPrice,
 		data:       data,
+		accessList: accessList,
 		checkNonce: checkNonce,
 	}
 }
 
-func (m Message) From() common.Address { return m.from }
-func (m Message) To() *common.Address  { return m.to }
-func (m Message) GasPrice() *big.Int   { return m.gasPrice }
-func (m Message) Value() *big.Int      { return m.amount }
-func (m Message) Gas() uint64          { return m.gasLimit }
-func (m Message) Nonce() uint64        { return m.nonce }
-func (m Message) Data() []byte         { return m.data }
-func (m Message) CheckNonce() bool     { return m.checkNonce }
+// AsMessage returns the transaction as a core.Message.
+func (tx *Transaction) AsMessage(s Signer) (Message, error) {
+	msg := Message{
+		nonce:      tx.Nonce(),
+		gasLimit:   tx.Gas(),
+		gasPrice:   new(big.Int).Set(tx.GasPrice()),
+		to:         tx.To(),
+		amount:     tx.Value(),
+		data:       tx.Data(),
+		accessList: tx.AccessList(),
+		checkNonce: true,
+	}
+
+	var err error
+	msg.from, err = Sender(s, tx)
+	return msg, err
+}
+
+func (m Message) From() common.Address   { return m.from }
+func (m Message) To() *common.Address    { return m.to }
+func (m Message) GasPrice() *big.Int     { return m.gasPrice }
+func (m Message) Value() *big.Int        { return m.amount }
+func (m Message) Gas() uint64            { return m.gasLimit }
+func (m Message) Nonce() uint64          { return m.nonce }
+func (m Message) Data() []byte           { return m.data }
+func (m Message) AccessList() AccessList { return m.accessList }
+func (m Message) CheckNonce() bool       { return m.checkNonce }
diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go
new file mode 100644
index 0000000000..e561485556
--- /dev/null
+++ b/core/types/transaction_marshalling.go
@@ -0,0 +1,203 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package types
+
+import (
+	"encoding/json"
+	"errors"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+// txJSON is the JSON representation of transactions.
+type txJSON struct {
+	Type hexutil.Uint64 `json:"type"`
+
+	// Common transaction fields:
+	Nonce    *hexutil.Uint64 `json:"nonce"`
+	GasPrice *hexutil.Big    `json:"gasPrice"`
+	Gas      *hexutil.Uint64 `json:"gas"`
+	Value    *hexutil.Big    `json:"value"`
+	Data     *hexutil.Bytes  `json:"input"`
+	V        *hexutil.Big    `json:"v"`
+	R        *hexutil.Big    `json:"r"`
+	S        *hexutil.Big    `json:"s"`
+	To       *common.Address `json:"to"`
+
+	// Access list transaction fields:
+	ChainID    *hexutil.Big `json:"chainId,omitempty"`
+	AccessList *AccessList  `json:"accessList,omitempty"`
+
+	// Only used for encoding:
+	Hash common.Hash `json:"hash"`
+}
+
+// MarshalJSON marshals as JSON with a hash.
+func (t *Transaction) MarshalJSON() ([]byte, error) {
+	var enc txJSON
+	// These are set for all tx types.
+	enc.Hash = t.Hash()
+	enc.Type = hexutil.Uint64(t.Type())
+
+	// Other fields are set conditionally depending on tx type.
+	switch tx := t.inner.(type) {
+	case *LegacyTx:
+		enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
+		enc.Gas = (*hexutil.Uint64)(&tx.Gas)
+		enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
+		enc.Value = (*hexutil.Big)(tx.Value)
+		enc.Data = (*hexutil.Bytes)(&tx.Data)
+		enc.To = t.To()
+		enc.V = (*hexutil.Big)(tx.V)
+		enc.R = (*hexutil.Big)(tx.R)
+		enc.S = (*hexutil.Big)(tx.S)
+	case *AccessListTx:
+		enc.ChainID = (*hexutil.Big)(tx.ChainID)
+		enc.AccessList = &tx.AccessList
+		enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
+		enc.Gas = (*hexutil.Uint64)(&tx.Gas)
+		enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
+		enc.Value = (*hexutil.Big)(tx.Value)
+		enc.Data = (*hexutil.Bytes)(&tx.Data)
+		enc.To = t.To()
+		enc.V = (*hexutil.Big)(tx.V)
+		enc.R = (*hexutil.Big)(tx.R)
+		enc.S = (*hexutil.Big)(tx.S)
+	}
+	return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (t *Transaction) UnmarshalJSON(input []byte) error {
+	var dec txJSON
+	if err := json.Unmarshal(input, &dec); err != nil {
+		return err
+	}
+
+	// Decode / verify fields according to transaction type.
+	var inner TxData
+	switch dec.Type {
+	case LegacyTxType:
+		var itx LegacyTx
+		inner = &itx
+		if dec.To != nil {
+			itx.To = dec.To
+		}
+		if dec.Nonce == nil {
+			return errors.New("missing required field 'nonce' in transaction")
+		}
+		itx.Nonce = uint64(*dec.Nonce)
+		if dec.GasPrice == nil {
+			return errors.New("missing required field 'gasPrice' in transaction")
+		}
+		itx.GasPrice = (*big.Int)(dec.GasPrice)
+		if dec.Gas == nil {
+			return errors.New("missing required field 'gas' in transaction")
+		}
+		itx.Gas = uint64(*dec.Gas)
+		if dec.Value == nil {
+			return errors.New("missing required field 'value' in transaction")
+		}
+		itx.Value = (*big.Int)(dec.Value)
+		if dec.Data == nil {
+			return errors.New("missing required field 'input' in transaction")
+		}
+		itx.Data = *dec.Data
+		if dec.V == nil {
+			return errors.New("missing required field 'v' in transaction")
+		}
+		itx.V = (*big.Int)(dec.V)
+		if dec.R == nil {
+			return errors.New("missing required field 'r' in transaction")
+		}
+		itx.R = (*big.Int)(dec.R)
+		if dec.S == nil {
+			return errors.New("missing required field 's' in transaction")
+		}
+		itx.S = (*big.Int)(dec.S)
+		withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
+		if withSignature {
+			if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil {
+				return err
+			}
+		}
+
+	case AccessListTxType:
+		var itx AccessListTx
+		inner = &itx
+		// Access list is optional for now.
+		if dec.AccessList != nil {
+			itx.AccessList = *dec.AccessList
+		}
+		if dec.ChainID == nil {
+			return errors.New("missing required field 'chainId' in transaction")
+		}
+		itx.ChainID = (*big.Int)(dec.ChainID)
+		if dec.To != nil {
+			itx.To = dec.To
+		}
+		if dec.Nonce == nil {
+			return errors.New("missing required field 'nonce' in transaction")
+		}
+		itx.Nonce = uint64(*dec.Nonce)
+		if dec.GasPrice == nil {
+			return errors.New("missing required field 'gasPrice' in transaction")
+		}
+		itx.GasPrice = (*big.Int)(dec.GasPrice)
+		if dec.Gas == nil {
+			return errors.New("missing required field 'gas' in transaction")
+		}
+		itx.Gas = uint64(*dec.Gas)
+		if dec.Value == nil {
+			return errors.New("missing required field 'value' in transaction")
+		}
+		itx.Value = (*big.Int)(dec.Value)
+		if dec.Data == nil {
+			return errors.New("missing required field 'input' in transaction")
+		}
+		itx.Data = *dec.Data
+		if dec.V == nil {
+			return errors.New("missing required field 'v' in transaction")
+		}
+		itx.V = (*big.Int)(dec.V)
+		if dec.R == nil {
+			return errors.New("missing required field 'r' in transaction")
+		}
+		itx.R = (*big.Int)(dec.R)
+		if dec.S == nil {
+			return errors.New("missing required field 's' in transaction")
+		}
+		itx.S = (*big.Int)(dec.S)
+		withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
+		if withSignature {
+			if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
+				return err
+			}
+		}
+
+	default:
+		return ErrTxTypeNotSupported
+	}
+
+	// Now set the inner transaction.
+	t.setDecoded(inner, 0)
+
+	// TODO: check hash here?
+	return nil
+}
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index 842fedbd03..126748efeb 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -27,9 +27,7 @@ import (
 	"github.com/ethereum/go-ethereum/params"
 )
 
-var (
-	ErrInvalidChainId = errors.New("invalid chain id for signer")
-)
+var ErrInvalidChainId = errors.New("invalid chain id for signer")
 
 // sigCache is used to cache the derived sender and contains
 // the signer used to derive it.
@@ -42,6 +40,8 @@ type sigCache struct {
 func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
 	var signer Signer
 	switch {
+	case config.IsBerlin(blockNumber):
+		signer = NewEIP2930Signer(config.ChainID)
 	case config.IsEIP155(blockNumber):
 		signer = NewEIP155Signer(config.ChainID)
 	case config.IsHomestead(blockNumber):
@@ -52,7 +52,40 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
 	return signer
 }
 
-// SignTx signs the transaction using the given signer and private key
+// LatestSigner returns the 'most permissive' Signer available for the given chain
+// configuration. Specifically, this enables support of EIP-155 replay protection and
+// EIP-2930 access list transactions when their respective forks are scheduled to occur at
+// any block number in the chain config.
+//
+// Use this in transaction-handling code where the current block number is unknown. If you
+// have the current block number available, use MakeSigner instead.
+func LatestSigner(config *params.ChainConfig) Signer {
+	if config.ChainID != nil {
+		if config.BerlinBlock != nil || config.YoloV3Block != nil {
+			return NewEIP2930Signer(config.ChainID)
+		}
+		if config.EIP155Block != nil {
+			return NewEIP155Signer(config.ChainID)
+		}
+	}
+	return HomesteadSigner{}
+}
+
+// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically,
+// this enables support for EIP-155 replay protection and all implemented EIP-2718
+// transaction types if chainID is non-nil.
+//
+// Use this in transaction-handling code where the current block number and fork
+// configuration are unknown. If you have a ChainConfig, use LatestSigner instead.
+// If you have a ChainConfig and know the current block number, use MakeSigner instead.
+func LatestSignerForChainID(chainID *big.Int) Signer {
+	if chainID == nil {
+		return HomesteadSigner{}
+	}
+	return NewEIP2930Signer(chainID)
+}
+
+// SignTx signs the transaction using the given signer and private key.
 func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
 	h := s.Hash(tx)
 	sig, err := crypto.Sign(h[:], prv)
@@ -62,6 +95,27 @@ func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, err
 	return tx.WithSignature(s, sig)
 }
 
+// SignNewTx creates a transaction and signs it.
+func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) {
+	tx := NewTx(txdata)
+	h := s.Hash(tx)
+	sig, err := crypto.Sign(h[:], prv)
+	if err != nil {
+		return nil, err
+	}
+	return tx.WithSignature(s, sig)
+}
+
+// MustSignNewTx creates a transaction and signs it.
+// This panics if the transaction cannot be signed.
+func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction {
+	tx, err := SignNewTx(prv, s, txdata)
+	if err != nil {
+		panic(err)
+	}
+	return tx
+}
+
 // Sender returns the address derived from the signature (V, R, S) using secp256k1
 // elliptic curve and an error if it failed deriving or upon an incorrect
 // signature.
@@ -88,21 +142,124 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) {
 	return addr, nil
 }
 
-// Signer encapsulates transaction signature handling. Note that this interface is not a
-// stable API and may change at any time to accommodate new protocol rules.
+// Signer encapsulates transaction signature handling. The name of this type is slightly
+// misleading because Signers don't actually sign, they're just for validating and
+// processing of signatures.
+//
+// Note that this interface is not a stable API and may change at any time to accommodate
+// new protocol rules.
 type Signer interface {
 	// Sender returns the sender address of the transaction.
 	Sender(tx *Transaction) (common.Address, error)
+
 	// SignatureValues returns the raw R, S, V values corresponding to the
 	// given signature.
 	SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error)
-	// Hash returns the hash to be signed.
+	ChainID() *big.Int
+
+	// Hash returns 'signature hash', i.e. the transaction hash that is signed by the
+	// private key. This hash does not uniquely identify the transaction.
 	Hash(tx *Transaction) common.Hash
+
 	// Equal returns true if the given signer is the same as the receiver.
 	Equal(Signer) bool
 }
 
-// EIP155Transaction implements Signer using the EIP155 rules.
+type eip2930Signer struct{ EIP155Signer }
+
+// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions,
+// EIP-155 replay protected transactions, and legacy Homestead transactions.
+func NewEIP2930Signer(chainId *big.Int) Signer {
+	return eip2930Signer{NewEIP155Signer(chainId)}
+}
+
+func (s eip2930Signer) ChainID() *big.Int {
+	return s.chainId
+}
+
+func (s eip2930Signer) Equal(s2 Signer) bool {
+	x, ok := s2.(eip2930Signer)
+	return ok && x.chainId.Cmp(s.chainId) == 0
+}
+
+func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) {
+	V, R, S := tx.RawSignatureValues()
+	switch tx.Type() {
+	case LegacyTxType:
+		if !tx.Protected() {
+			return HomesteadSigner{}.Sender(tx)
+		}
+		V = new(big.Int).Sub(V, s.chainIdMul)
+		V.Sub(V, big8)
+	case AccessListTxType:
+		// ACL txs are defined to use 0 and 1 as their recovery id, add
+		// 27 to become equivalent to unprotected Homestead signatures.
+		V = new(big.Int).Add(V, big.NewInt(27))
+	default:
+		return common.Address{}, ErrTxTypeNotSupported
+	}
+	if tx.ChainId().Cmp(s.chainId) != 0 {
+		return common.Address{}, ErrInvalidChainId
+	}
+	return recoverPlain(s.Hash(tx), R, S, V, true)
+}
+
+func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
+	switch txdata := tx.inner.(type) {
+	case *LegacyTx:
+		return s.EIP155Signer.SignatureValues(tx, sig)
+	case *AccessListTx:
+		// Check that chain ID of tx matches the signer. We also accept ID zero here,
+		// because it indicates that the chain ID was not specified in the tx.
+		if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
+			return nil, nil, nil, ErrInvalidChainId
+		}
+		R, S, _ = decodeSignature(sig)
+		V = big.NewInt(int64(sig[64]))
+	default:
+		return nil, nil, nil, ErrTxTypeNotSupported
+	}
+	return R, S, V, nil
+}
+
+// Hash returns the hash to be signed by the sender.
+// It does not uniquely identify the transaction.
+func (s eip2930Signer) Hash(tx *Transaction) common.Hash {
+	switch tx.Type() {
+	case LegacyTxType:
+		return rlpHash([]interface{}{
+			tx.Nonce(),
+			tx.GasPrice(),
+			tx.Gas(),
+			tx.To(),
+			tx.Value(),
+			tx.Data(),
+			s.chainId, uint(0), uint(0),
+		})
+	case AccessListTxType:
+		return prefixedRlpHash(
+			tx.Type(),
+			[]interface{}{
+				s.chainId,
+				tx.Nonce(),
+				tx.GasPrice(),
+				tx.Gas(),
+				tx.To(),
+				tx.Value(),
+				tx.Data(),
+				tx.AccessList(),
+			})
+	default:
+		// This _should_ not happen, but in case someone sends in a bad
+		// json struct via RPC, it's probably more prudent to return an
+		// empty hash instead of killing the node with a panic
+		//panic("Unsupported transaction type: %d", tx.typ)
+		return common.Hash{}
+	}
+}
+
+// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which
+// are replay-protected as well as unprotected homestead transactions.
 type EIP155Signer struct {
 	chainId, chainIdMul *big.Int
 }
@@ -117,6 +274,10 @@ func NewEIP155Signer(chainId *big.Int) EIP155Signer {
 	}
 }
 
+func (s EIP155Signer) ChainID() *big.Int {
+	return s.chainId
+}
+
 func (s EIP155Signer) Equal(s2 Signer) bool {
 	eip155, ok := s2.(EIP155Signer)
 	return ok && eip155.chainId.Cmp(s.chainId) == 0
@@ -125,24 +286,28 @@ func (s EIP155Signer) Equal(s2 Signer) bool {
 var big8 = big.NewInt(8)
 
 func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
+	if tx.Type() != LegacyTxType {
+		return common.Address{}, ErrTxTypeNotSupported
+	}
 	if !tx.Protected() {
 		return HomesteadSigner{}.Sender(tx)
 	}
 	if tx.ChainId().Cmp(s.chainId) != 0 {
 		return common.Address{}, ErrInvalidChainId
 	}
-	V := new(big.Int).Sub(tx.data.V, s.chainIdMul)
+	V, R, S := tx.RawSignatureValues()
+	V = new(big.Int).Sub(V, s.chainIdMul)
 	V.Sub(V, big8)
-	return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true)
+	return recoverPlain(s.Hash(tx), R, S, V, true)
 }
 
 // SignatureValues returns signature values. This signature
 // needs to be in the [R || S || V] format where V is 0 or 1.
 func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
-	R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig)
-	if err != nil {
-		return nil, nil, nil, err
+	if tx.Type() != LegacyTxType {
+		return nil, nil, nil, ErrTxTypeNotSupported
 	}
+	R, S, V = decodeSignature(sig)
 	if s.chainId.Sign() != 0 {
 		V = big.NewInt(int64(sig[64] + 35))
 		V.Add(V, s.chainIdMul)
@@ -154,12 +319,12 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
 // It does not uniquely identify the transaction.
 func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
 	return rlpHash([]interface{}{
-		tx.data.AccountNonce,
-		tx.data.Price,
-		tx.data.GasLimit,
-		tx.data.Recipient,
-		tx.data.Amount,
-		tx.data.Payload,
+		tx.Nonce(),
+		tx.GasPrice(),
+		tx.Gas(),
+		tx.To(),
+		tx.Value(),
+		tx.Data(),
 		s.chainId, uint(0), uint(0),
 	})
 }
@@ -168,6 +333,10 @@ func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
 // homestead rules.
 type HomesteadSigner struct{ FrontierSigner }
 
+func (s HomesteadSigner) ChainID() *big.Int {
+	return nil
+}
+
 func (s HomesteadSigner) Equal(s2 Signer) bool {
 	_, ok := s2.(HomesteadSigner)
 	return ok
@@ -180,25 +349,39 @@ func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v
 }
 
 func (hs HomesteadSigner) Sender(tx *Transaction) (common.Address, error) {
-	return recoverPlain(hs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, true)
+	if tx.Type() != LegacyTxType {
+		return common.Address{}, ErrTxTypeNotSupported
+	}
+	v, r, s := tx.RawSignatureValues()
+	return recoverPlain(hs.Hash(tx), r, s, v, true)
 }
 
 type FrontierSigner struct{}
 
+func (s FrontierSigner) ChainID() *big.Int {
+	return nil
+}
+
 func (s FrontierSigner) Equal(s2 Signer) bool {
 	_, ok := s2.(FrontierSigner)
 	return ok
 }
 
+func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) {
+	if tx.Type() != LegacyTxType {
+		return common.Address{}, ErrTxTypeNotSupported
+	}
+	v, r, s := tx.RawSignatureValues()
+	return recoverPlain(fs.Hash(tx), r, s, v, false)
+}
+
 // SignatureValues returns signature values. This signature
 // needs to be in the [R || S || V] format where V is 0 or 1.
 func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
-	if len(sig) != crypto.SignatureLength {
-		panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength))
+	if tx.Type() != LegacyTxType {
+		return nil, nil, nil, ErrTxTypeNotSupported
 	}
-	r = new(big.Int).SetBytes(sig[:32])
-	s = new(big.Int).SetBytes(sig[32:64])
-	v = new(big.Int).SetBytes([]byte{sig[64] + 27})
+	r, s, v = decodeSignature(sig)
 	return r, s, v, nil
 }
 
@@ -206,17 +389,23 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *
 // It does not uniquely identify the transaction.
 func (fs FrontierSigner) Hash(tx *Transaction) common.Hash {
 	return rlpHash([]interface{}{
-		tx.data.AccountNonce,
-		tx.data.Price,
-		tx.data.GasLimit,
-		tx.data.Recipient,
-		tx.data.Amount,
-		tx.data.Payload,
+		tx.Nonce(),
+		tx.GasPrice(),
+		tx.Gas(),
+		tx.To(),
+		tx.Value(),
+		tx.Data(),
 	})
 }
 
-func (fs FrontierSigner) Sender(tx *Transaction) (common.Address, error) {
-	return recoverPlain(fs.Hash(tx), tx.data.R, tx.data.S, tx.data.V, false)
+func decodeSignature(sig []byte) (r, s, v *big.Int) {
+	if len(sig) != crypto.SignatureLength {
+		panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength))
+	}
+	r = new(big.Int).SetBytes(sig[:32])
+	s = new(big.Int).SetBytes(sig[32:64])
+	v = new(big.Int).SetBytes([]byte{sig[64] + 27})
+	return r, s, v
 }
 
 func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool) (common.Address, error) {
diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go
index 7a1b6cd4d4..3cece9c235 100644
--- a/core/types/transaction_test.go
+++ b/core/types/transaction_test.go
@@ -20,8 +20,11 @@ import (
 	"bytes"
 	"crypto/ecdsa"
 	"encoding/json"
+	"fmt"
 	"math/big"
+	"reflect"
 	"testing"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -31,6 +34,8 @@ import (
 // The values in those tests are from the Transaction Tests
 // at github.com/ethereum/tests.
 var (
+	testAddr = common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b")
+
 	emptyTx = NewTransaction(
 		0,
 		common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
@@ -40,7 +45,7 @@ var (
 
 	rightvrsTx, _ = NewTransaction(
 		3,
-		common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"),
+		testAddr,
 		big.NewInt(10),
 		2000,
 		big.NewInt(1),
@@ -49,8 +54,32 @@ var (
 		HomesteadSigner{},
 		common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"),
 	)
+
+	emptyEip2718Tx = NewTx(&AccessListTx{
+		ChainID:  big.NewInt(1),
+		Nonce:    3,
+		To:       &testAddr,
+		Value:    big.NewInt(10),
+		Gas:      25000,
+		GasPrice: big.NewInt(1),
+		Data:     common.FromHex("5544"),
+	})
+
+	signedEip2718Tx, _ = emptyEip2718Tx.WithSignature(
+		NewEIP2930Signer(big.NewInt(1)),
+		common.Hex2Bytes("c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101"),
+	)
 )
 
+func TestDecodeEmptyTypedTx(t *testing.T) {
+	input := []byte{0x80}
+	var tx Transaction
+	err := rlp.DecodeBytes(input, &tx)
+	if err != errEmptyTypedTx {
+		t.Fatal("wrong error:", err)
+	}
+}
+
 func TestTransactionSigHash(t *testing.T) {
 	var homestead HomesteadSigner
 	if homestead.Hash(emptyTx) != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") {
@@ -72,10 +101,121 @@ func TestTransactionEncode(t *testing.T) {
 	}
 }
 
+func TestEIP2718TransactionSigHash(t *testing.T) {
+	s := NewEIP2930Signer(big.NewInt(1))
+	if s.Hash(emptyEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") {
+		t.Errorf("empty EIP-2718 transaction hash mismatch, got %x", s.Hash(emptyEip2718Tx))
+	}
+	if s.Hash(signedEip2718Tx) != common.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") {
+		t.Errorf("signed EIP-2718 transaction hash mismatch, got %x", s.Hash(signedEip2718Tx))
+	}
+}
+
+// This test checks signature operations on access list transactions.
+func TestEIP2930Signer(t *testing.T) {
+
+	var (
+		key, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		keyAddr = crypto.PubkeyToAddress(key.PublicKey)
+		signer1 = NewEIP2930Signer(big.NewInt(1))
+		signer2 = NewEIP2930Signer(big.NewInt(2))
+		tx0     = NewTx(&AccessListTx{Nonce: 1})
+		tx1     = NewTx(&AccessListTx{ChainID: big.NewInt(1), Nonce: 1})
+		tx2, _  = SignNewTx(key, signer2, &AccessListTx{ChainID: big.NewInt(2), Nonce: 1})
+	)
+
+	tests := []struct {
+		tx             *Transaction
+		signer         Signer
+		wantSignerHash common.Hash
+		wantSenderErr  error
+		wantSignErr    error
+		wantHash       common.Hash // after signing
+	}{
+		{
+			tx:             tx0,
+			signer:         signer1,
+			wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"),
+			wantSenderErr:  ErrInvalidChainId,
+			wantHash:       common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"),
+		},
+		{
+			tx:             tx1,
+			signer:         signer1,
+			wantSenderErr:  ErrInvalidSig,
+			wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"),
+			wantHash:       common.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"),
+		},
+		{
+			// This checks what happens when trying to sign an unsigned tx for the wrong chain.
+			tx:             tx1,
+			signer:         signer2,
+			wantSenderErr:  ErrInvalidChainId,
+			wantSignerHash: common.HexToHash("367967247499343401261d718ed5aa4c9486583e4d89251afce47f4a33c33362"),
+			wantSignErr:    ErrInvalidChainId,
+		},
+		{
+			// This checks what happens when trying to re-sign a signed tx for the wrong chain.
+			tx:             tx2,
+			signer:         signer1,
+			wantSenderErr:  ErrInvalidChainId,
+			wantSignerHash: common.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"),
+			wantSignErr:    ErrInvalidChainId,
+		},
+	}
+
+	for i, test := range tests {
+		sigHash := test.signer.Hash(test.tx)
+		if sigHash != test.wantSignerHash {
+			t.Errorf("test %d: wrong sig hash: got %x, want %x", i, sigHash, test.wantSignerHash)
+		}
+		sender, err := Sender(test.signer, test.tx)
+		if err != test.wantSenderErr {
+			t.Errorf("test %d: wrong Sender error %q", i, err)
+		}
+		if err == nil && sender != keyAddr {
+			t.Errorf("test %d: wrong sender address %x", i, sender)
+		}
+		signedTx, err := SignTx(test.tx, test.signer, key)
+		if err != test.wantSignErr {
+			t.Fatalf("test %d: wrong SignTx error %q", i, err)
+		}
+		if signedTx != nil {
+			if signedTx.Hash() != test.wantHash {
+				t.Errorf("test %d: wrong tx hash after signing: got %x, want %x", i, signedTx.Hash(), test.wantHash)
+			}
+		}
+	}
+}
+
+func TestEIP2718TransactionEncode(t *testing.T) {
+	// RLP representation
+	{
+		have, err := rlp.EncodeToBytes(signedEip2718Tx)
+		if err != nil {
+			t.Fatalf("encode error: %v", err)
+		}
+		want := common.FromHex("b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521")
+		if !bytes.Equal(have, want) {
+			t.Errorf("encoded RLP mismatch, got %x", have)
+		}
+	}
+	// Binary representation
+	{
+		have, err := signedEip2718Tx.MarshalBinary()
+		if err != nil {
+			t.Fatalf("encode error: %v", err)
+		}
+		want := common.FromHex("01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521")
+		if !bytes.Equal(have, want) {
+			t.Errorf("encoded RLP mismatch, got %x", have)
+		}
+	}
+}
+
 func decodeTx(data []byte) (*Transaction, error) {
 	var tx Transaction
 	t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx)
-
 	return t, err
 }
 
@@ -127,8 +267,8 @@ func TestTransactionPriceNonceSort(t *testing.T) {
 	for i := 0; i < len(keys); i++ {
 		keys[i], _ = crypto.GenerateKey()
 	}
-
 	signer := HomesteadSigner{}
+
 	// Generate a batch of transactions with overlapping values, but shifted nonces
 	groups := map[common.Address]Transactions{}
 	for start, key := range keys {
@@ -155,12 +295,10 @@ func TestTransactionPriceNonceSort(t *testing.T) {
 		// Make sure the nonce order is valid
 		for j, txj := range txs[i+1:] {
 			fromj, _ := Sender(signer, txj)
-
 			if fromi == fromj && txi.Nonce() > txj.Nonce() {
 				t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
 			}
 		}
-
 		// If the next tx has different from account, the price must be lower than the current one
 		if i+1 < len(txs) {
 			next := txs[i+1]
@@ -172,50 +310,173 @@ func TestTransactionPriceNonceSort(t *testing.T) {
 	}
 }
 
-// TestTransactionJSON tests serializing/de-serializing to/from JSON.
-func TestTransactionJSON(t *testing.T) {
+// Tests that if multiple transactions have the same price, the ones seen earlier
+// are prioritized to avoid network spam attacks aiming for a specific ordering.
+func TestTransactionTimeSort(t *testing.T) {
+	// Generate a batch of accounts to start with
+	keys := make([]*ecdsa.PrivateKey, 5)
+	for i := 0; i < len(keys); i++ {
+		keys[i], _ = crypto.GenerateKey()
+	}
+	signer := HomesteadSigner{}
+
+	// Generate a batch of transactions with overlapping prices, but different creation times
+	groups := map[common.Address]Transactions{}
+	for start, key := range keys {
+		addr := crypto.PubkeyToAddress(key.PublicKey)
+
+		tx, _ := SignTx(NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key)
+		tx.time = time.Unix(0, int64(len(keys)-start))
+
+		groups[addr] = append(groups[addr], tx)
+	}
+	// Sort the transactions and cross check the nonce ordering
+	txset := NewTransactionsByPriceAndNonce(signer, groups)
+
+	txs := Transactions{}
+	for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
+		txs = append(txs, tx)
+		txset.Shift()
+	}
+	if len(txs) != len(keys) {
+		t.Errorf("expected %d transactions, found %d", len(keys), len(txs))
+	}
+	for i, txi := range txs {
+		fromi, _ := Sender(signer, txi)
+		if i+1 < len(txs) {
+			next := txs[i+1]
+			fromNext, _ := Sender(signer, next)
+
+			if txi.GasPrice().Cmp(next.GasPrice()) < 0 {
+				t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
+			}
+			// Make sure time order is ascending if the txs have the same gas price
+			if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.time.After(next.time) {
+				t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.time, i+1, fromNext[:4], next.time)
+			}
+		}
+	}
+}
+
+// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON.
+func TestTransactionCoding(t *testing.T) {
 	key, err := crypto.GenerateKey()
 	if err != nil {
 		t.Fatalf("could not generate key: %v", err)
 	}
-	signer := NewEIP155Signer(common.Big1)
-
-	transactions := make([]*Transaction, 0, 50)
-	for i := uint64(0); i < 25; i++ {
-		var tx *Transaction
-		switch i % 2 {
+	var (
+		signer    = NewEIP2930Signer(common.Big1)
+		addr      = common.HexToAddress("0x0000000000000000000000000000000000000001")
+		recipient = common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87")
+		accesses  = AccessList{{Address: addr, StorageKeys: []common.Hash{{0}}}}
+	)
+	for i := uint64(0); i < 500; i++ {
+		var txdata TxData
+		switch i % 5 {
 		case 0:
-			tx = NewTransaction(i, common.Address{1}, common.Big0, 1, common.Big2, []byte("abcdef"))
+			// Legacy tx.
+			txdata = &LegacyTx{
+				Nonce:    i,
+				To:       &recipient,
+				Gas:      1,
+				GasPrice: big.NewInt(2),
+				Data:     []byte("abcdef"),
+			}
 		case 1:
-			tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef"))
+			// Legacy tx contract creation.
+			txdata = &LegacyTx{
+				Nonce:    i,
+				Gas:      1,
+				GasPrice: big.NewInt(2),
+				Data:     []byte("abcdef"),
+			}
+		case 2:
+			// Tx with non-zero access list.
+			txdata = &AccessListTx{
+				ChainID:    big.NewInt(1),
+				Nonce:      i,
+				To:         &recipient,
+				Gas:        123457,
+				GasPrice:   big.NewInt(10),
+				AccessList: accesses,
+				Data:       []byte("abcdef"),
+			}
+		case 3:
+			// Tx with empty access list.
+			txdata = &AccessListTx{
+				ChainID:  big.NewInt(1),
+				Nonce:    i,
+				To:       &recipient,
+				Gas:      123457,
+				GasPrice: big.NewInt(10),
+				Data:     []byte("abcdef"),
+			}
+		case 4:
+			// Contract creation with access list.
+			txdata = &AccessListTx{
+				ChainID:    big.NewInt(1),
+				Nonce:      i,
+				Gas:        123457,
+				GasPrice:   big.NewInt(10),
+				AccessList: accesses,
+			}
 		}
-		transactions = append(transactions, tx)
-
-		signedTx, err := SignTx(tx, signer, key)
+		tx, err := SignNewTx(key, signer, txdata)
 		if err != nil {
 			t.Fatalf("could not sign transaction: %v", err)
 		}
-
-		transactions = append(transactions, signedTx)
-	}
-
-	for _, tx := range transactions {
-		data, err := json.Marshal(tx)
+		// RLP
+		parsedTx, err := encodeDecodeBinary(tx)
 		if err != nil {
-			t.Fatalf("json.Marshal failed: %v", err)
+			t.Fatal(err)
 		}
+		assertEqual(parsedTx, tx)
 
-		var parsedTx *Transaction
-		if err := json.Unmarshal(data, &parsedTx); err != nil {
-			t.Fatalf("json.Unmarshal failed: %v", err)
+		// JSON
+		parsedTx, err = encodeDecodeJSON(tx)
+		if err != nil {
+			t.Fatal(err)
 		}
+		assertEqual(parsedTx, tx)
+	}
+}
 
-		// compare nonce, price, gaslimit, recipient, amount, payload, V, R, S
-		if tx.Hash() != parsedTx.Hash() {
-			t.Errorf("parsed tx differs from original tx, want %v, got %v", tx, parsedTx)
-		}
-		if tx.ChainId().Cmp(parsedTx.ChainId()) != 0 {
-			t.Errorf("invalid chain id, want %d, got %d", tx.ChainId(), parsedTx.ChainId())
+func encodeDecodeJSON(tx *Transaction) (*Transaction, error) {
+	data, err := json.Marshal(tx)
+	if err != nil {
+		return nil, fmt.Errorf("json encoding failed: %v", err)
+	}
+	var parsedTx = &Transaction{}
+	if err := json.Unmarshal(data, &parsedTx); err != nil {
+		return nil, fmt.Errorf("json decoding failed: %v", err)
+	}
+	return parsedTx, nil
+}
+
+func encodeDecodeBinary(tx *Transaction) (*Transaction, error) {
+	data, err := tx.MarshalBinary()
+	if err != nil {
+		return nil, fmt.Errorf("rlp encoding failed: %v", err)
+	}
+	var parsedTx = &Transaction{}
+	if err := parsedTx.UnmarshalBinary(data); err != nil {
+		return nil, fmt.Errorf("rlp decoding failed: %v", err)
+	}
+	return parsedTx, nil
+}
+
+func assertEqual(orig *Transaction, cpy *Transaction) error {
+	// compare nonce, price, gaslimit, recipient, amount, payload, V, R, S
+	if want, got := orig.Hash(), cpy.Hash(); want != got {
+		return fmt.Errorf("parsed tx differs from original tx, want %v, got %v", want, got)
+	}
+	if want, got := orig.ChainId(), cpy.ChainId(); want.Cmp(got) != 0 {
+		return fmt.Errorf("invalid chain id, want %d, got %d", want, got)
+	}
+	if orig.AccessList() != nil {
+		if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) {
+			return fmt.Errorf("access list wrong!")
 		}
 	}
+	return nil
 }
diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go
new file mode 100644
index 0000000000..b5bc961c84
--- /dev/null
+++ b/core/vm/access_list_tracer.go
@@ -0,0 +1,177 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vm
+
+import (
+	"math/big"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+// accessList is an accumulator for the set of accounts and storage slots an EVM
+// contract execution touches.
+type accessList map[common.Address]accessListSlots
+
+// accessListSlots is an accumulator for the set of storage slots within a single
+// contract that an EVM contract execution touches.
+type accessListSlots map[common.Hash]struct{}
+
+// newAccessList creates a new accessList.
+func newAccessList() accessList {
+	return make(map[common.Address]accessListSlots)
+}
+
+// addAddress adds an address to the accesslist.
+func (al accessList) addAddress(address common.Address) {
+	// Set address if not previously present
+	if _, present := al[address]; !present {
+		al[address] = make(map[common.Hash]struct{})
+	}
+}
+
+// addSlot adds a storage slot to the accesslist.
+func (al accessList) addSlot(address common.Address, slot common.Hash) {
+	// Set address if not previously present
+	al.addAddress(address)
+
+	// Set the slot on the surely existent storage set
+	al[address][slot] = struct{}{}
+}
+
+// equal checks if the content of the current access list is the same as the
+// content of the other one.
+func (al accessList) equal(other accessList) bool {
+	// Cross reference the accounts first
+	if len(al) != len(other) {
+		return false
+	}
+	for addr := range al {
+		if _, ok := other[addr]; !ok {
+			return false
+		}
+	}
+	for addr := range other {
+		if _, ok := al[addr]; !ok {
+			return false
+		}
+	}
+	// Accounts match, cross reference the storage slots too
+	for addr, slots := range al {
+		otherslots := other[addr]
+
+		if len(slots) != len(otherslots) {
+			return false
+		}
+		for hash := range slots {
+			if _, ok := otherslots[hash]; !ok {
+				return false
+			}
+		}
+		for hash := range otherslots {
+			if _, ok := slots[hash]; !ok {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+// accesslist converts the accesslist to a types.AccessList.
+func (al accessList) accessList() types.AccessList {
+	acl := make(types.AccessList, 0, len(al))
+	for addr, slots := range al {
+		tuple := types.AccessTuple{Address: addr}
+		for slot := range slots {
+			tuple.StorageKeys = append(tuple.StorageKeys, slot)
+		}
+		acl = append(acl, tuple)
+	}
+	return acl
+}
+
+// AccessListTracer is a tracer that accumulates touched accounts and storage
+// slots into an internal set.
+type AccessListTracer struct {
+	excl map[common.Address]struct{} // Set of account to exclude from the list
+	list accessList                  // Set of accounts and storage slots touched
+}
+
+// NewAccessListTracer creates a new tracer that can generate AccessLists.
+// An optional AccessList can be specified to occupy slots and addresses in
+// the resulting accesslist.
+func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
+	excl := map[common.Address]struct{}{
+		from: {}, to: {},
+	}
+	for _, addr := range precompiles {
+		excl[addr] = struct{}{}
+	}
+	list := newAccessList()
+	for _, al := range acl {
+		if _, ok := excl[al.Address]; !ok {
+			list.addAddress(al.Address)
+		}
+		for _, slot := range al.StorageKeys {
+			list.addSlot(al.Address, slot)
+		}
+	}
+	return &AccessListTracer{
+		excl: excl,
+		list: list,
+	}
+}
+
+func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
+func (a *AccessListTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+	stack := scope.Stack
+	if (op == SLOAD || op == SSTORE) && stack.len() >= 1 {
+		slot := common.Hash(stack.data[stack.len()-1].Bytes32())
+		a.list.addSlot(scope.Contract.Address(), slot)
+	}
+	if (op == EXTCODECOPY || op == EXTCODEHASH || op == EXTCODESIZE || op == BALANCE || op == SELFDESTRUCT) && stack.len() >= 1 {
+		addr := common.Address(stack.data[stack.len()-1].Bytes20())
+		if _, ok := a.excl[addr]; !ok {
+			a.list.addAddress(addr)
+		}
+	}
+	if (op == DELEGATECALL || op == CALL || op == STATICCALL || op == CALLCODE) && stack.len() >= 5 {
+		addr := common.Address(stack.data[stack.len()-2].Bytes20())
+		if _, ok := a.excl[addr]; !ok {
+			a.list.addAddress(addr)
+		}
+	}
+}
+
+func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
+}
+
+func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
+
+// AccessList returns the current accesslist maintained by the tracer.
+func (a *AccessListTracer) AccessList() types.AccessList {
+	return a.list.accessList()
+}
+
+// Equal returns if the content of two access list traces are equal.
+func (a *AccessListTracer) Equal(other *AccessListTracer) bool {
+	return a.list.equal(other.list)
+}
diff --git a/core/vm/common.go b/core/vm/common.go
index d592a9410d..90ba4a4ad1 100644
--- a/core/vm/common.go
+++ b/core/vm/common.go
@@ -17,15 +17,14 @@
 package vm
 
 import (
-	"math/big"
-
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/holiman/uint256"
 )
 
 // calcMemSize64 calculates the required memory size, and returns
 // the size and whether the result overflowed uint64
-func calcMemSize64(off, l *big.Int) (uint64, bool) {
+func calcMemSize64(off, l *uint256.Int) (uint64, bool) {
 	if !l.IsUint64() {
 		return 0, true
 	}
@@ -35,16 +34,16 @@ func calcMemSize64(off, l *big.Int) (uint64, bool) {
 // calcMemSize64WithUint calculates the required memory size, and returns
 // the size and whether the result overflowed uint64
 // Identical to calcMemSize64, but length is a uint64
-func calcMemSize64WithUint(off *big.Int, length64 uint64) (uint64, bool) {
+func calcMemSize64WithUint(off *uint256.Int, length64 uint64) (uint64, bool) {
 	// if length is zero, memsize is always zero, regardless of offset
 	if length64 == 0 {
 		return 0, false
 	}
 	// Check that offset doesn't overflow
-	if !off.IsUint64() {
+	offset64, overflow := off.Uint64WithOverflow()
+	if overflow {
 		return 0, true
 	}
-	offset64 := off.Uint64()
 	val := offset64 + length64
 	// if value < either of it's parts, then it overflowed
 	return val, val < offset64
@@ -64,22 +63,6 @@ func getData(data []byte, start uint64, size uint64) []byte {
 	return common.RightPadBytes(data[start:end], int(size))
 }
 
-// getDataBig returns a slice from the data based on the start and size and pads
-// up to size with zero's. This function is overflow safe.
-func getDataBig(data []byte, start *big.Int, size *big.Int) []byte {
-	dlen := big.NewInt(int64(len(data)))
-
-	s := math.BigMin(start, dlen)
-	e := math.BigMin(new(big.Int).Add(s, size), dlen)
-	return common.RightPadBytes(data[s.Uint64():e.Uint64()], int(size.Uint64()))
-}
-
-// bigUint64 returns the integer casted to a uint64 and returns whether it
-// overflowed in the process.
-func bigUint64(v *big.Int) (uint64, bool) {
-	return v.Uint64(), !v.IsUint64()
-}
-
 // toWordSize returns the ceiled word size required for memory expansion.
 func toWordSize(size uint64) uint64 {
 	if size > math.MaxUint64-31 {
diff --git a/core/vm/contract.go b/core/vm/contract.go
index 375e24bc19..61dbd5007a 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -20,6 +20,7 @@ import (
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/holiman/uint256"
 )
 
 // ContractRef is a reference to the contract's backing object
@@ -81,18 +82,30 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin
 	return c
 }
 
-func (c *Contract) validJumpdest(dest *big.Int) bool {
-	udest := dest.Uint64()
+func (c *Contract) validJumpdest(dest *uint256.Int) bool {
+	udest, overflow := dest.Uint64WithOverflow()
 	// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
 	// Don't bother checking for JUMPDEST in that case.
-	if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) {
+	if overflow || udest >= uint64(len(c.Code)) {
 		return false
 	}
 	// Only JUMPDESTs allowed for destinations
 	if OpCode(c.Code[udest]) != JUMPDEST {
 		return false
 	}
+	return c.isCode(udest)
+}
+
+// isCode returns true if the provided PC location is an actual opcode, as
+// opposed to a data-segment following a PUSHN operation.
+func (c *Contract) isCode(udest uint64) bool {
+	// Do we already have an analysis laying around?
+	if c.analysis != nil {
+		return c.analysis.codeSegment(udest)
+	}
 	// Do we have a contract hash already?
+	// If we do have a hash, that means it's a 'regular' contract. For regular
+	// contracts ( not temporary initcode), we store the analysis in a map
 	if c.CodeHash != (common.Hash{}) {
 		// Does parent context have the analysis?
 		analysis, exist := c.jumpdests[c.CodeHash]
@@ -102,6 +115,8 @@ func (c *Contract) validJumpdest(dest *big.Int) bool {
 			analysis = codeBitmap(c.Code)
 			c.jumpdests[c.CodeHash] = analysis
 		}
+		// Also stash it in current contract for faster access
+		c.analysis = analysis
 		return analysis.codeSegment(udest)
 	}
 	// We don't have the code hash, most likely a piece of initcode not already
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index f18f773521..07e125dbf7 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -26,6 +26,7 @@ import (
 	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/crypto/blake2b"
+	"github.com/ethereum/go-ethereum/crypto/bls12381"
 	"github.com/ethereum/go-ethereum/crypto/bn256"
 	"github.com/ethereum/go-ethereum/params"
 
@@ -57,7 +58,7 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
 	common.BytesToAddress([]byte{2}): &sha256hash{},
 	common.BytesToAddress([]byte{3}): &ripemd160hash{},
 	common.BytesToAddress([]byte{4}): &dataCopy{},
-	common.BytesToAddress([]byte{5}): &bigModExp{},
+	common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
 	common.BytesToAddress([]byte{6}): &bn256AddByzantium{},
 	common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{},
 	common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},
@@ -66,27 +67,97 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
 // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
 // contracts used in the Istanbul release.
 var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
-	common.BytesToAddress([]byte{1}):  &ecrecover{},
-	common.BytesToAddress([]byte{2}):  &sha256hash{},
-	common.BytesToAddress([]byte{3}):  &ripemd160hash{},
-	common.BytesToAddress([]byte{4}):  &dataCopy{},
-	common.BytesToAddress([]byte{5}):  &bigModExp{},
-	common.BytesToAddress([]byte{6}):  &bn256AddIstanbul{},
-	common.BytesToAddress([]byte{7}):  &bn256ScalarMulIstanbul{},
-	common.BytesToAddress([]byte{8}):  &bn256PairingIstanbul{},
-	common.BytesToAddress([]byte{9}):  &blake2F{},
+	common.BytesToAddress([]byte{1}): &ecrecover{},
+	common.BytesToAddress([]byte{2}): &sha256hash{},
+	common.BytesToAddress([]byte{3}): &ripemd160hash{},
+	common.BytesToAddress([]byte{4}): &dataCopy{},
+	common.BytesToAddress([]byte{5}): &bigModExp{},
+	common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
+	common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
+	common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
+	common.BytesToAddress([]byte{9}): &blake2F{},
 
 	common.BytesToAddress([]byte{100}): &tmHeaderValidate{},
 	common.BytesToAddress([]byte{101}): &iavlMerkleProofValidate{},
 }
 
+// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
+// contracts used in the Berlin release.
+var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
+	common.BytesToAddress([]byte{1}): &ecrecover{},
+	common.BytesToAddress([]byte{2}): &sha256hash{},
+	common.BytesToAddress([]byte{3}): &ripemd160hash{},
+	common.BytesToAddress([]byte{4}): &dataCopy{},
+	common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
+	common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
+	common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
+	common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
+	common.BytesToAddress([]byte{9}): &blake2F{},
+}
+
+// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
+// contracts specified in EIP-2537. These are exported for testing purposes.
+var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
+	common.BytesToAddress([]byte{10}): &bls12381G1Add{},
+	common.BytesToAddress([]byte{11}): &bls12381G1Mul{},
+	common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
+	common.BytesToAddress([]byte{13}): &bls12381G2Add{},
+	common.BytesToAddress([]byte{14}): &bls12381G2Mul{},
+	common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
+	common.BytesToAddress([]byte{16}): &bls12381Pairing{},
+	common.BytesToAddress([]byte{17}): &bls12381MapG1{},
+	common.BytesToAddress([]byte{18}): &bls12381MapG2{},
+}
+
+var (
+	PrecompiledAddressesBerlin    []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 {
+		PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k)
+	}
+	for k := range PrecompiledContractsIstanbul {
+		PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k)
+	}
+	for k := range PrecompiledContractsBerlin {
+		PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k)
+	}
+}
+
+// ActivePrecompiles returns the precompiles enabled with the current configuration.
+func ActivePrecompiles(rules params.Rules) []common.Address {
+	switch {
+	case rules.IsBerlin:
+		return PrecompiledAddressesBerlin
+	case rules.IsIstanbul:
+		return PrecompiledAddressesIstanbul
+	case rules.IsByzantium:
+		return PrecompiledAddressesByzantium
+	default:
+		return PrecompiledAddressesHomestead
+	}
+}
+
 // RunPrecompiledContract runs and evaluates the output of a precompiled contract.
-func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
-	gas := p.RequiredGas(input)
-	if contract.UseGas(gas) {
-		return p.Run(input)
+// It returns
+// - the returned bytes,
+// - the _remaining_ gas,
+// - any error that occurred
+func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
+	gasCost := p.RequiredGas(input)
+	if suppliedGas < gasCost {
+		return nil, 0, ErrOutOfGas
 	}
-	return nil, ErrOutOfGas
+	suppliedGas -= gasCost
+	output, err := p.Run(input)
+	return output, suppliedGas, err
 }
 
 // ECRECOVER implemented as a native contract.
@@ -173,13 +244,19 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
 }
 
 // bigModExp implements a native big integer exponential modular operation.
-type bigModExp struct{}
+type bigModExp struct {
+	eip2565 bool
+}
 
 var (
+	big0      = big.NewInt(0)
 	big1      = big.NewInt(1)
+	big3      = big.NewInt(3)
 	big4      = big.NewInt(4)
+	big7      = big.NewInt(7)
 	big8      = big.NewInt(8)
 	big16     = big.NewInt(16)
+	big20     = big.NewInt(20)
 	big32     = big.NewInt(32)
 	big64     = big.NewInt(64)
 	big96     = big.NewInt(96)
@@ -189,6 +266,34 @@ var (
 	big199680 = big.NewInt(199680)
 )
 
+// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
+//
+// def mult_complexity(x):
+//    if x <= 64: return x ** 2
+//    elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
+//    else: return x ** 2 // 16 + 480 * x - 199680
+//
+// where is x is max(length_of_MODULUS, length_of_BASE)
+func modexpMultComplexity(x *big.Int) *big.Int {
+	switch {
+	case x.Cmp(big64) <= 0:
+		x.Mul(x, x) // x ** 2
+	case x.Cmp(big1024) <= 0:
+		// (x ** 2 // 4 ) + ( 96 * x - 3072)
+		x = new(big.Int).Add(
+			new(big.Int).Div(new(big.Int).Mul(x, x), big4),
+			new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072),
+		)
+	default:
+		// (x ** 2 // 16) + (480 * x - 199680)
+		x = new(big.Int).Add(
+			new(big.Int).Div(new(big.Int).Mul(x, x), big16),
+			new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680),
+		)
+	}
+	return x
+}
+
 // RequiredGas returns the gas required to execute the pre-compiled contract.
 func (c *bigModExp) RequiredGas(input []byte) uint64 {
 	var (
@@ -223,25 +328,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
 		adjExpLen.Mul(big8, adjExpLen)
 	}
 	adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))
-
 	// Calculate the gas cost of the operation
 	gas := new(big.Int).Set(math.BigMax(modLen, baseLen))
-	switch {
-	case gas.Cmp(big64) <= 0:
+	if c.eip2565 {
+		// EIP-2565 has three changes
+		// 1. Different multComplexity (inlined here)
+		// in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565):
+		//
+		// def mult_complexity(x):
+		//    ceiling(x/8)^2
+		//
+		//where is x is max(length_of_MODULUS, length_of_BASE)
+		gas = gas.Add(gas, big7)
+		gas = gas.Div(gas, big8)
 		gas.Mul(gas, gas)
-	case gas.Cmp(big1024) <= 0:
-		gas = new(big.Int).Add(
-			new(big.Int).Div(new(big.Int).Mul(gas, gas), big4),
-			new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072),
-		)
-	default:
-		gas = new(big.Int).Add(
-			new(big.Int).Div(new(big.Int).Mul(gas, gas), big16),
-			new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680),
-		)
+
+		gas.Mul(gas, math.BigMax(adjExpLen, big1))
+		// 2. Different divisor (`GQUADDIVISOR`) (3)
+		gas.Div(gas, big3)
+		if gas.BitLen() > 64 {
+			return math.MaxUint64
+		}
+		// 3. Minimum price of 200 gas
+		if gas.Uint64() < 200 {
+			return 200
+		}
+		return gas.Uint64()
 	}
+	gas = modexpMultComplexity(gas)
 	gas.Mul(gas, math.BigMax(adjExpLen, big1))
-	gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv))
+	gas.Div(gas, big20)
 
 	if gas.BitLen() > 64 {
 		return math.MaxUint64
@@ -468,7 +584,7 @@ var (
 )
 
 func (c *blake2F) Run(input []byte) ([]byte, error) {
-	// Make sure the input is valid (correct lenth and final flag)
+	// Make sure the input is valid (correct length and final flag)
 	if len(input) != blake2FInputLength {
 		return nil, errBlake2FInvalidInputLength
 	}
@@ -505,3 +621,428 @@ func (c *blake2F) Run(input []byte) ([]byte, error) {
 	}
 	return output, nil
 }
+
+var (
+	errBLS12381InvalidInputLength          = errors.New("invalid input length")
+	errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes")
+	errBLS12381G1PointSubgroup             = errors.New("g1 point is not on correct subgroup")
+	errBLS12381G2PointSubgroup             = errors.New("g2 point is not on correct subgroup")
+)
+
+// bls12381G1Add implements EIP-2537 G1Add precompile.
+type bls12381G1Add struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381G1Add) RequiredGas(input []byte) uint64 {
+	return params.Bls12381G1AddGas
+}
+
+func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 G1Add precompile.
+	// > G1 addition call expects `256` bytes as an input that is interpreted as byte concatenation of two G1 points (`128` bytes each).
+	// > Output is an encoding of addition operation result - single G1 point (`128` bytes).
+	if len(input) != 256 {
+		return nil, errBLS12381InvalidInputLength
+	}
+	var err error
+	var p0, p1 *bls12381.PointG1
+
+	// Initialize G1
+	g := bls12381.NewG1()
+
+	// Decode G1 point p_0
+	if p0, err = g.DecodePoint(input[:128]); err != nil {
+		return nil, err
+	}
+	// Decode G1 point p_1
+	if p1, err = g.DecodePoint(input[128:]); err != nil {
+		return nil, err
+	}
+
+	// Compute r = p_0 + p_1
+	r := g.New()
+	g.Add(r, p0, p1)
+
+	// Encode the G1 point result into 128 bytes
+	return g.EncodePoint(r), nil
+}
+
+// bls12381G1Mul implements EIP-2537 G1Mul precompile.
+type bls12381G1Mul struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381G1Mul) RequiredGas(input []byte) uint64 {
+	return params.Bls12381G1MulGas
+}
+
+func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 G1Mul precompile.
+	// > G1 multiplication call expects `160` bytes as an input that is interpreted as byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes).
+	// > Output is an encoding of multiplication operation result - single G1 point (`128` bytes).
+	if len(input) != 160 {
+		return nil, errBLS12381InvalidInputLength
+	}
+	var err error
+	var p0 *bls12381.PointG1
+
+	// Initialize G1
+	g := bls12381.NewG1()
+
+	// Decode G1 point
+	if p0, err = g.DecodePoint(input[:128]); err != nil {
+		return nil, err
+	}
+	// Decode scalar value
+	e := new(big.Int).SetBytes(input[128:])
+
+	// Compute r = e * p_0
+	r := g.New()
+	g.MulScalar(r, p0, e)
+
+	// Encode the G1 point into 128 bytes
+	return g.EncodePoint(r), nil
+}
+
+// bls12381G1MultiExp implements EIP-2537 G1MultiExp precompile.
+type bls12381G1MultiExp struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 {
+	// Calculate G1 point, scalar value pair length
+	k := len(input) / 160
+	if k == 0 {
+		// Return 0 gas for small input length
+		return 0
+	}
+	// Lookup discount value for G1 point, scalar value pair length
+	var discount uint64
+	if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen {
+		discount = params.Bls12381MultiExpDiscountTable[k-1]
+	} else {
+		discount = params.Bls12381MultiExpDiscountTable[dLen-1]
+	}
+	// Calculate gas and return the result
+	return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000
+}
+
+func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 G1MultiExp precompile.
+	// G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes).
+	// Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes).
+	k := len(input) / 160
+	if len(input) == 0 || len(input)%160 != 0 {
+		return nil, errBLS12381InvalidInputLength
+	}
+	var err error
+	points := make([]*bls12381.PointG1, k)
+	scalars := make([]*big.Int, k)
+
+	// Initialize G1
+	g := bls12381.NewG1()
+
+	// Decode point scalar pairs
+	for i := 0; i < k; i++ {
+		off := 160 * i
+		t0, t1, t2 := off, off+128, off+160
+		// Decode G1 point
+		if points[i], err = g.DecodePoint(input[t0:t1]); err != nil {
+			return nil, err
+		}
+		// Decode scalar value
+		scalars[i] = new(big.Int).SetBytes(input[t1:t2])
+	}
+
+	// Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1)
+	r := g.New()
+	g.MultiExp(r, points, scalars)
+
+	// Encode the G1 point to 128 bytes
+	return g.EncodePoint(r), nil
+}
+
+// bls12381G2Add implements EIP-2537 G2Add precompile.
+type bls12381G2Add struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381G2Add) RequiredGas(input []byte) uint64 {
+	return params.Bls12381G2AddGas
+}
+
+func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 G2Add precompile.
+	// > G2 addition call expects `512` bytes as an input that is interpreted as byte concatenation of two G2 points (`256` bytes each).
+	// > Output is an encoding of addition operation result - single G2 point (`256` bytes).
+	if len(input) != 512 {
+		return nil, errBLS12381InvalidInputLength
+	}
+	var err error
+	var p0, p1 *bls12381.PointG2
+
+	// Initialize G2
+	g := bls12381.NewG2()
+	r := g.New()
+
+	// Decode G2 point p_0
+	if p0, err = g.DecodePoint(input[:256]); err != nil {
+		return nil, err
+	}
+	// Decode G2 point p_1
+	if p1, err = g.DecodePoint(input[256:]); err != nil {
+		return nil, err
+	}
+
+	// Compute r = p_0 + p_1
+	g.Add(r, p0, p1)
+
+	// Encode the G2 point into 256 bytes
+	return g.EncodePoint(r), nil
+}
+
+// bls12381G2Mul implements EIP-2537 G2Mul precompile.
+type bls12381G2Mul struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381G2Mul) RequiredGas(input []byte) uint64 {
+	return params.Bls12381G2MulGas
+}
+
+func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 G2MUL precompile logic.
+	// > G2 multiplication call expects `288` bytes as an input that is interpreted as byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes).
+	// > Output is an encoding of multiplication operation result - single G2 point (`256` bytes).
+	if len(input) != 288 {
+		return nil, errBLS12381InvalidInputLength
+	}
+	var err error
+	var p0 *bls12381.PointG2
+
+	// Initialize G2
+	g := bls12381.NewG2()
+
+	// Decode G2 point
+	if p0, err = g.DecodePoint(input[:256]); err != nil {
+		return nil, err
+	}
+	// Decode scalar value
+	e := new(big.Int).SetBytes(input[256:])
+
+	// Compute r = e * p_0
+	r := g.New()
+	g.MulScalar(r, p0, e)
+
+	// Encode the G2 point into 256 bytes
+	return g.EncodePoint(r), nil
+}
+
+// bls12381G2MultiExp implements EIP-2537 G2MultiExp precompile.
+type bls12381G2MultiExp struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 {
+	// Calculate G2 point, scalar value pair length
+	k := len(input) / 288
+	if k == 0 {
+		// Return 0 gas for small input length
+		return 0
+	}
+	// Lookup discount value for G2 point, scalar value pair length
+	var discount uint64
+	if dLen := len(params.Bls12381MultiExpDiscountTable); k < dLen {
+		discount = params.Bls12381MultiExpDiscountTable[k-1]
+	} else {
+		discount = params.Bls12381MultiExpDiscountTable[dLen-1]
+	}
+	// Calculate gas and return the result
+	return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000
+}
+
+func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 G2MultiExp precompile logic
+	// > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes).
+	// > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes).
+	k := len(input) / 288
+	if len(input) == 0 || len(input)%288 != 0 {
+		return nil, errBLS12381InvalidInputLength
+	}
+	var err error
+	points := make([]*bls12381.PointG2, k)
+	scalars := make([]*big.Int, k)
+
+	// Initialize G2
+	g := bls12381.NewG2()
+
+	// Decode point scalar pairs
+	for i := 0; i < k; i++ {
+		off := 288 * i
+		t0, t1, t2 := off, off+256, off+288
+		// Decode G1 point
+		if points[i], err = g.DecodePoint(input[t0:t1]); err != nil {
+			return nil, err
+		}
+		// Decode scalar value
+		scalars[i] = new(big.Int).SetBytes(input[t1:t2])
+	}
+
+	// Compute r = e_0 * p_0 + e_1 * p_1 + ... + e_(k-1) * p_(k-1)
+	r := g.New()
+	g.MultiExp(r, points, scalars)
+
+	// Encode the G2 point to 256 bytes.
+	return g.EncodePoint(r), nil
+}
+
+// bls12381Pairing implements EIP-2537 Pairing precompile.
+type bls12381Pairing struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381Pairing) RequiredGas(input []byte) uint64 {
+	return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas
+}
+
+func (c *bls12381Pairing) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 Pairing precompile logic.
+	// > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure:
+	// > - `128` bytes of G1 point encoding
+	// > - `256` bytes of G2 point encoding
+	// > Output is a `32` bytes where last single byte is `0x01` if pairing result is equal to multiplicative identity in a pairing target field and `0x00` otherwise
+	// > (which is equivalent of Big Endian encoding of Solidity values `uint256(1)` and `uin256(0)` respectively).
+	k := len(input) / 384
+	if len(input) == 0 || len(input)%384 != 0 {
+		return nil, errBLS12381InvalidInputLength
+	}
+
+	// Initialize BLS12-381 pairing engine
+	e := bls12381.NewPairingEngine()
+	g1, g2 := e.G1, e.G2
+
+	// Decode pairs
+	for i := 0; i < k; i++ {
+		off := 384 * i
+		t0, t1, t2 := off, off+128, off+384
+
+		// Decode G1 point
+		p1, err := g1.DecodePoint(input[t0:t1])
+		if err != nil {
+			return nil, err
+		}
+		// Decode G2 point
+		p2, err := g2.DecodePoint(input[t1:t2])
+		if err != nil {
+			return nil, err
+		}
+
+		// 'point is on curve' check already done,
+		// Here we need to apply subgroup checks.
+		if !g1.InCorrectSubgroup(p1) {
+			return nil, errBLS12381G1PointSubgroup
+		}
+		if !g2.InCorrectSubgroup(p2) {
+			return nil, errBLS12381G2PointSubgroup
+		}
+
+		// Update pairing engine with G1 and G2 ponits
+		e.AddPair(p1, p2)
+	}
+	// Prepare 32 byte output
+	out := make([]byte, 32)
+
+	// Compute pairing and set the result
+	if e.Check() {
+		out[31] = 1
+	}
+	return out, nil
+}
+
+// decodeBLS12381FieldElement decodes BLS12-381 elliptic curve field element.
+// Removes top 16 bytes of 64 byte input.
+func decodeBLS12381FieldElement(in []byte) ([]byte, error) {
+	if len(in) != 64 {
+		return nil, errors.New("invalid field element length")
+	}
+	// check top bytes
+	for i := 0; i < 16; i++ {
+		if in[i] != byte(0x00) {
+			return nil, errBLS12381InvalidFieldElementTopBytes
+		}
+	}
+	out := make([]byte, 48)
+	copy(out[:], in[16:])
+	return out, nil
+}
+
+// bls12381MapG1 implements EIP-2537 MapG1 precompile.
+type bls12381MapG1 struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381MapG1) RequiredGas(input []byte) uint64 {
+	return params.Bls12381MapG1Gas
+}
+
+func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 Map_To_G1 precompile.
+	// > Field-to-curve call expects `64` bytes an an input that is interpreted as a an element of the base field.
+	// > Output of this call is `128` bytes and is G1 point following respective encoding rules.
+	if len(input) != 64 {
+		return nil, errBLS12381InvalidInputLength
+	}
+
+	// Decode input field element
+	fe, err := decodeBLS12381FieldElement(input)
+	if err != nil {
+		return nil, err
+	}
+
+	// Initialize G1
+	g := bls12381.NewG1()
+
+	// Compute mapping
+	r, err := g.MapToCurve(fe)
+	if err != nil {
+		return nil, err
+	}
+
+	// Encode the G1 point to 128 bytes
+	return g.EncodePoint(r), nil
+}
+
+// bls12381MapG2 implements EIP-2537 MapG2 precompile.
+type bls12381MapG2 struct{}
+
+// RequiredGas returns the gas required to execute the pre-compiled contract.
+func (c *bls12381MapG2) RequiredGas(input []byte) uint64 {
+	return params.Bls12381MapG2Gas
+}
+
+func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
+	// Implements EIP-2537 Map_FP2_TO_G2 precompile logic.
+	// > Field-to-curve call expects `128` bytes an an input that is interpreted as a an element of the quadratic extension field.
+	// > Output of this call is `256` bytes and is G2 point following respective encoding rules.
+	if len(input) != 128 {
+		return nil, errBLS12381InvalidInputLength
+	}
+
+	// Decode input field element
+	fe := make([]byte, 96)
+	c0, err := decodeBLS12381FieldElement(input[:64])
+	if err != nil {
+		return nil, err
+	}
+	copy(fe[48:], c0)
+	c1, err := decodeBLS12381FieldElement(input[64:])
+	if err != nil {
+		return nil, err
+	}
+	copy(fe[:48], c1)
+
+	// Initialize G2
+	g := bls12381.NewG2()
+
+	// Compute mapping
+	r, err := g.MapToCurve(fe)
+	if err != nil {
+		return nil, err
+	}
+
+	// Encode the G2 point to 256 bytes
+	return g.EncodePoint(r), nil
+}
diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go
index be003a60c9..30d9b49f71 100644
--- a/core/vm/contracts_test.go
+++ b/core/vm/contracts_test.go
@@ -18,399 +18,94 @@ package vm
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
-	"math/big"
-	"reflect"
+	"io/ioutil"
 	"testing"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 )
 
 // precompiledTest defines the input/output pairs for precompiled contract tests.
 type precompiledTest struct {
-	input, expected string
-	name            string
-	noBenchmark     bool // Benchmark primarily the worst-cases
+	Input, Expected string
+	Gas             uint64
+	Name            string
+	NoBenchmark     bool // Benchmark primarily the worst-cases
 }
 
 // precompiledFailureTest defines the input/error pairs for precompiled
 // contract failure tests.
 type precompiledFailureTest struct {
-	input         string
-	expectedError error
-	name          string
+	Input         string
+	ExpectedError string
+	Name          string
 }
 
-// modexpTests are the test and benchmark data for the modexp precompiled contract.
-var modexpTests = []precompiledTest{
-	{
-		input: "0000000000000000000000000000000000000000000000000000000000000001" +
-			"0000000000000000000000000000000000000000000000000000000000000020" +
-			"0000000000000000000000000000000000000000000000000000000000000020" +
-			"03" +
-			"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e" +
-			"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "eip_example1",
-	}, {
-		input: "0000000000000000000000000000000000000000000000000000000000000000" +
-			"0000000000000000000000000000000000000000000000000000000000000020" +
-			"0000000000000000000000000000000000000000000000000000000000000020" +
-			"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e" +
-			"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
-		expected: "0000000000000000000000000000000000000000000000000000000000000000",
-		name:     "eip_example2",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
-		expected: "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc",
-		name:     "nagydani-1-square",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
-		expected: "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec",
-		name:     "nagydani-1-qube",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
-		expected: "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2",
-		name:     "nagydani-1-pow0x10001",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
-		expected: "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70",
-		name:     "nagydani-2-square",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
-		expected: "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f",
-		name:     "nagydani-2-qube",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
-		expected: "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2",
-		name:     "nagydani-2-pow0x10001",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
-		expected: "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8",
-		name:     "nagydani-3-square",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
-		expected: "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e",
-		name:     "nagydani-3-qube",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
-		expected: "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76",
-		name:     "nagydani-3-pow0x10001",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
-		expected: "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10",
-		name:     "nagydani-4-square",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
-		expected: "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc",
-		name:     "nagydani-4-qube",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
-		expected: "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963",
-		name:     "nagydani-4-pow0x10001",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
-		expected: "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb",
-		name:     "nagydani-5-square",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
-		expected: "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f",
-		name:     "nagydani-5-qube",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
-		expected: "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500",
-		name:     "nagydani-5-pow0x10001",
-	},
-}
-
-// bn256AddTests are the test and benchmark data for the bn256 addition precompiled
-// contract.
-var bn256AddTests = []precompiledTest{
-	{
-		input:    "18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7",
-		expected: "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915",
-		name:     "chfast1",
-	}, {
-		input:    "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c91518b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266",
-		expected: "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204",
-		name:     "chfast2",
-	}, {
-		input:    "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		name:     "cdetrio1",
-	}, {
-		input:    "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		name:     "cdetrio2",
-	}, {
-		input:    "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		name:     "cdetrio3",
-	}, {
-		input:    "",
-		expected: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		name:     "cdetrio4",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		name:     "cdetrio5",
-	}, {
-		input:    "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		expected: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		name:     "cdetrio6",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		name:     "cdetrio7",
-	}, {
-		input:    "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		expected: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		name:     "cdetrio8",
-	}, {
-		input:    "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		name:     "cdetrio9",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		name:     "cdetrio10",
-	}, {
-		input:    "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
-		expected: "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4",
-		name:     "cdetrio11",
-	}, {
-		input:    "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4",
-		name:     "cdetrio12",
-	}, {
-		input:    "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98",
-		expected: "15bf2bb17880144b5d1cd2b1f46eff9d617bffd1ca57c37fb5a49bd84e53cf66049c797f9ce0d17083deb32b5e36f2ea2a212ee036598dd7624c168993d1355f",
-		name:     "cdetrio13",
-	}, {
-		input:    "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		expected: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
-		name:     "cdetrio14",
-	},
-}
-
-// bn256ScalarMulTests are the test and benchmark data for the bn256 scalar
-// multipication precompiled contract.
-var bn256ScalarMulTests = []precompiledTest{
-	{
-		input:    "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2",
-		expected: "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc",
-		name:     "chfast1",
-	}, {
-		input:    "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46",
-		expected: "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e",
-		name:     "chfast2",
-	}, {
-		input:    "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3",
-		expected: "14789d0d4a730b354403b5fac948113739e276c23e0258d8596ee72f9cd9d3230af18a63153e0ec25ff9f2951dd3fa90ed0197bfef6e2a1a62b5095b9d2b4a27",
-		name:     "chfast3",
-	}, {
-		input:    "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		expected: "2cde5879ba6f13c0b5aa4ef627f159a3347df9722efce88a9afbb20b763b4c411aa7e43076f6aee272755a7f9b84832e71559ba0d2e0b17d5f9f01755e5b0d11",
-		name:     "cdetrio1",
-	}, {
-		input:       "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f630644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",
-		expected:    "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3163511ddc1c3f25d396745388200081287b3fd1472d8339d5fecb2eae0830451",
-		name:        "cdetrio2",
-		noBenchmark: true,
-	}, {
-		input:       "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000100000000000000000000000000000000",
-		expected:    "1051acb0700ec6d42a88215852d582efbaef31529b6fcbc3277b5c1b300f5cf0135b2394bb45ab04b8bd7611bd2dfe1de6a4e6e2ccea1ea1955f577cd66af85b",
-		name:        "cdetrio3",
-		noBenchmark: true,
-	}, {
-		input:       "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000009",
-		expected:    "1dbad7d39dbc56379f78fac1bca147dc8e66de1b9d183c7b167351bfe0aeab742cd757d51289cd8dbd0acf9e673ad67d0f0a89f912af47ed1be53664f5692575",
-		name:        "cdetrio4",
-		noBenchmark: true,
-	}, {
-		input:       "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000001",
-		expected:    "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6",
-		name:        "cdetrio5",
-		noBenchmark: true,
-	}, {
-		input:    "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		expected: "29e587aadd7c06722aabba753017c093f70ba7eb1f1c0104ec0564e7e3e21f6022b1143f6a41008e7755c71c3d00b6b915d386de21783ef590486d8afa8453b1",
-		name:     "cdetrio6",
-	}, {
-		input:       "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",
-		expected:    "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb",
-		name:        "cdetrio7",
-		noBenchmark: true,
-	}, {
-		input:       "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000100000000000000000000000000000000",
-		expected:    "221a3577763877920d0d14a91cd59b9479f83b87a653bb41f82a3f6f120cea7c2752c7f64cdd7f0e494bff7b60419f242210f2026ed2ec70f89f78a4c56a1f15",
-		name:        "cdetrio8",
-		noBenchmark: true,
-	}, {
-		input:       "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000009",
-		expected:    "228e687a379ba154554040f8821f4e41ee2be287c201aa9c3bc02c9dd12f1e691e0fd6ee672d04cfd924ed8fdc7ba5f2d06c53c1edc30f65f2af5a5b97f0a76a",
-		name:        "cdetrio9",
-		noBenchmark: true,
-	}, {
-		input:       "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000001",
-		expected:    "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c",
-		name:        "cdetrio10",
-		noBenchmark: true,
-	}, {
-		input:    "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-		expected: "00a1a234d08efaa2616607e31eca1980128b00b415c845ff25bba3afcb81dc00242077290ed33906aeb8e42fd98c41bcb9057ba03421af3f2d08cfc441186024",
-		name:     "cdetrio11",
-	}, {
-		input:       "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d9830644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",
-		expected:    "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b8692929ee761a352600f54921df9bf472e66217e7bb0cee9032e00acc86b3c8bfaf",
-		name:        "cdetrio12",
-		noBenchmark: true,
-	}, {
-		input:       "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000100000000000000000000000000000000",
-		expected:    "1071b63011e8c222c5a771dfa03c2e11aac9666dd097f2c620852c3951a4376a2f46fe2f73e1cf310a168d56baa5575a8319389d7bfa6b29ee2d908305791434",
-		name:        "cdetrio13",
-		noBenchmark: true,
-	}, {
-		input:       "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000009",
-		expected:    "19f75b9dd68c080a688774a6213f131e3052bd353a304a189d7a2ee367e3c2582612f545fb9fc89fde80fd81c68fc7dcb27fea5fc124eeda69433cf5c46d2d7f",
-		name:        "cdetrio14",
-		noBenchmark: true,
-	}, {
-		input:       "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000001",
-		expected:    "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98",
-		name:        "cdetrio15",
-		noBenchmark: true,
-	},
-}
-
-// bn256PairingTests are the test and benchmark data for the bn256 pairing check
-// precompiled contract.
-var bn256PairingTests = []precompiledTest{
-	{
-		input:    "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "jeff1",
-	}, {
-		input:    "2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc0203d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db841213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f06967a1237ebfeca9aaae0d6d0bab8e28c198c5a339ef8a2407e31cdac516db922160fa257a5fd5b280642ff47b65eca77e626cb685c84fa6d3b6882a283ddd1198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "jeff2",
-	}, {
-		input:    "0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd216da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb314a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee245901b9e027bd5cfc2cb5db82d4dc9677ac795ec500ecd47deee3b5da006d6d049b811d7511c78158de484232fc68daf8a45cf217d1c2fae693ff5871e8752d73b21198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "jeff3",
-	}, {
-		input:    "2f2ea0b3da1e8ef11914acf8b2e1b32d99df51f5f4f206fc6b947eae860eddb6068134ddb33dc888ef446b648d72338684d678d2eb2371c61a50734d78da4b7225f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb122acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf6806d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb186bac5188a98c45e6016873d107f5cd131f3a3e339d0375e58bd6219347b008122ae2b09e539e152ec5364e7e2204b03d11d3caa038bfc7cd499f8176aacbee1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd415794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f211b88da1679b0b64a63b7e0e7bfe52aae524f73a55be7fe70c7e9bfc94b4cf0da1213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "jeff4",
-	}, {
-		input:    "20a754d2071d4d53903e3b31a7e98ad6882d58aec240ef981fdf0a9d22c5926a29c853fcea789887315916bbeb89ca37edb355b4f980c9a12a94f30deeed30211213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f1abb4a25eb9379ae96c84fff9f0540abcfc0a0d11aeda02d4f37e4baf74cb0c11073b3ff2cdbb38755f8691ea59e9606696b3ff278acfc098fa8226470d03869217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac290a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a98552fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d70f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2198a1f162a73261f112401aa2db79c7dab1533c9935c77290a6ce3b191f2318d198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "jeff5",
-	}, {
-		input:    "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c103188585e2364128fe25c70558f1560f4f9350baf3959e603cc91486e110936198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000000",
-		name:     "jeff6",
-	}, { // ecpairing_empty_data_insufficient_gas
-		input:    "",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "empty_data",
-	}, { // ecpairing_one_point_insufficient_gas
-		input:    "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000000",
-		name:     "one_point",
-	}, { // ecpairing_two_point_match_2
-		input:    "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "two_point_match_2",
-	}, { // ecpairing_two_point_match_3
-		input:    "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "two_point_match_3",
-	}, { // ecpairing_two_point_match_4
-		input:    "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "two_point_match_4",
-	}, {
-		input:    "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "ten_point_match_1",
-	}, {
-		input:    "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "ten_point_match_2",
-	}, { // ecpairing_two_point_match_4
-		input:    "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75",
-		expected: "0000000000000000000000000000000000000000000000000000000000000001",
-		name:     "ten_point_match_3",
-	},
+// allPrecompiles does not map to the actual set of precompiles, as it also contains
+// repriced versions of precompiles at certain slots
+var allPrecompiles = map[common.Address]PrecompiledContract{
+	common.BytesToAddress([]byte{1}):    &ecrecover{},
+	common.BytesToAddress([]byte{2}):    &sha256hash{},
+	common.BytesToAddress([]byte{3}):    &ripemd160hash{},
+	common.BytesToAddress([]byte{4}):    &dataCopy{},
+	common.BytesToAddress([]byte{5}):    &bigModExp{eip2565: false},
+	common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true},
+	common.BytesToAddress([]byte{6}):    &bn256AddIstanbul{},
+	common.BytesToAddress([]byte{7}):    &bn256ScalarMulIstanbul{},
+	common.BytesToAddress([]byte{8}):    &bn256PairingIstanbul{},
+	common.BytesToAddress([]byte{9}):    &blake2F{},
+	common.BytesToAddress([]byte{10}):   &bls12381G1Add{},
+	common.BytesToAddress([]byte{11}):   &bls12381G1Mul{},
+	common.BytesToAddress([]byte{12}):   &bls12381G1MultiExp{},
+	common.BytesToAddress([]byte{13}):   &bls12381G2Add{},
+	common.BytesToAddress([]byte{14}):   &bls12381G2Mul{},
+	common.BytesToAddress([]byte{15}):   &bls12381G2MultiExp{},
+	common.BytesToAddress([]byte{16}):   &bls12381Pairing{},
+	common.BytesToAddress([]byte{17}):   &bls12381MapG1{},
+	common.BytesToAddress([]byte{18}):   &bls12381MapG2{},
 }
 
 // EIP-152 test vectors
 var blake2FMalformedInputTests = []precompiledFailureTest{
 	{
-		input:         "",
-		expectedError: errBlake2FInvalidInputLength,
-		name:          "vector 0: empty input",
+		Input:         "",
+		ExpectedError: errBlake2FInvalidInputLength.Error(),
+		Name:          "vector 0: empty input",
 	},
 	{
-		input:         "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
-		expectedError: errBlake2FInvalidInputLength,
-		name:          "vector 1: less than 213 bytes input",
+		Input:         "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+		ExpectedError: errBlake2FInvalidInputLength.Error(),
+		Name:          "vector 1: less than 213 bytes input",
 	},
 	{
-		input:         "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
-		expectedError: errBlake2FInvalidInputLength,
-		name:          "vector 2: more than 213 bytes input",
+		Input:         "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+		ExpectedError: errBlake2FInvalidInputLength.Error(),
+		Name:          "vector 2: more than 213 bytes input",
 	},
 	{
-		input:         "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002",
-		expectedError: errBlake2FInvalidFinalFlag,
-		name:          "vector 3: malformed final block indicator flag",
-	},
-}
-
-// EIP-152 test vectors
-var blake2FTests = []precompiledTest{
-	{
-		input:    "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
-		expected: "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b",
-		name:     "vector 4",
-	},
-	{ // https://tools.ietf.org/html/rfc7693#appendix-A
-		input:    "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
-		expected: "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923",
-		name:     "vector 5",
-	},
-	{
-		input:    "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000",
-		expected: "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735",
-		name:     "vector 6",
-	},
-	{
-		input:    "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
-		expected: "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421",
-		name:     "vector 7",
-	},
-	{
-		input:    "007A120048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
-		expected: "6d2ce9e534d50e18ff866ae92d70cceba79bbcd14c63819fe48752c8aca87a4bb7dcc230d22a4047f0486cfcfb50a17b24b2899eb8fca370f22240adb5170189",
-		name:     "vector 8",
+		Input:         "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002",
+		ExpectedError: errBlake2FInvalidFinalFlag.Error(),
+		Name:          "vector 3: malformed final block indicator flag",
 	},
 }
 
 func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
-	p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
-	in := common.Hex2Bytes(test.input)
-	contract := NewContract(AccountRef(common.HexToAddress("1337")),
-		nil, new(big.Int), p.RequiredGas(in))
-	t.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(t *testing.T) {
-		if res, err := RunPrecompiledContract(p, in, contract); err != nil {
+	p := allPrecompiles[common.HexToAddress(addr)]
+	in := common.Hex2Bytes(test.Input)
+	gas := p.RequiredGas(in)
+	t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
+		if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
 			t.Error(err)
-		} else if common.Bytes2Hex(res) != test.expected {
-			t.Errorf("Expected %v, got %v", test.expected, common.Bytes2Hex(res))
+		} else if common.Bytes2Hex(res) != test.Expected {
+			t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
+		}
+		if expGas := test.Gas; expGas != gas {
+			t.Errorf("%v: gas wrong, expected %d, got %d", test.Name, expGas, gas)
 		}
 		// Verify that the precompile did not touch the input buffer
-		exp := common.Hex2Bytes(test.input)
+		exp := common.Hex2Bytes(test.Input)
 		if !bytes.Equal(in, exp) {
 			t.Errorf("Precompiled %v modified input data", addr)
 		}
@@ -418,17 +113,17 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
 }
 
 func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
-	p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
-	in := common.Hex2Bytes(test.input)
-	contract := NewContract(AccountRef(common.HexToAddress("1337")),
-		nil, new(big.Int), p.RequiredGas(in)-1)
-	t.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(t *testing.T) {
-		_, err := RunPrecompiledContract(p, in, contract)
+	p := allPrecompiles[common.HexToAddress(addr)]
+	in := common.Hex2Bytes(test.Input)
+	gas := p.RequiredGas(in) - 1
+
+	t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
+		_, _, err := RunPrecompiledContract(p, in, gas)
 		if err.Error() != "out of gas" {
 			t.Errorf("Expected error [out of gas], got [%v]", err)
 		}
 		// Verify that the precompile did not touch the input buffer
-		exp := common.Hex2Bytes(test.input)
+		exp := common.Hex2Bytes(test.Input)
 		if !bytes.Equal(in, exp) {
 			t.Errorf("Precompiled %v modified input data", addr)
 		}
@@ -436,18 +131,16 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
 }
 
 func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
-	p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
-	in := common.Hex2Bytes(test.input)
-	contract := NewContract(AccountRef(common.HexToAddress("31337")),
-		nil, new(big.Int), p.RequiredGas(in))
-
-	t.Run(test.name, func(t *testing.T) {
-		_, err := RunPrecompiledContract(p, in, contract)
-		if !reflect.DeepEqual(err, test.expectedError) {
-			t.Errorf("Expected error [%v], got [%v]", test.expectedError, err)
+	p := allPrecompiles[common.HexToAddress(addr)]
+	in := common.Hex2Bytes(test.Input)
+	gas := p.RequiredGas(in)
+	t.Run(test.Name, func(t *testing.T) {
+		_, _, err := RunPrecompiledContract(p, in, gas)
+		if err.Error() != test.ExpectedError {
+			t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
 		}
 		// Verify that the precompile did not touch the input buffer
-		exp := common.Hex2Bytes(test.input)
+		exp := common.Hex2Bytes(test.Input)
 		if !bytes.Equal(in, exp) {
 			t.Errorf("Precompiled %v modified input data", addr)
 		}
@@ -455,14 +148,12 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
 }
 
 func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
-	if test.noBenchmark {
+	if test.NoBenchmark {
 		return
 	}
-	p := PrecompiledContractsIstanbul[common.HexToAddress(addr)]
-	in := common.Hex2Bytes(test.input)
+	p := allPrecompiles[common.HexToAddress(addr)]
+	in := common.Hex2Bytes(test.Input)
 	reqGas := p.RequiredGas(in)
-	contract := NewContract(AccountRef(common.HexToAddress("1337")),
-		nil, new(big.Int), reqGas)
 
 	var (
 		res  []byte
@@ -470,21 +161,31 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
 		data = make([]byte, len(in))
 	)
 
-	bench.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(bench *testing.B) {
+	bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) {
+		bench.ReportAllocs()
+		start := time.Now()
 		bench.ResetTimer()
 		for i := 0; i < bench.N; i++ {
-			contract.Gas = reqGas
 			copy(data, in)
-			res, err = RunPrecompiledContract(p, data, contract)
+			res, _, err = RunPrecompiledContract(p, data, reqGas)
 		}
 		bench.StopTimer()
+		elapsed := uint64(time.Since(start))
+		if elapsed < 1 {
+			elapsed = 1
+		}
+		gasUsed := reqGas * uint64(bench.N)
+		bench.ReportMetric(float64(reqGas), "gas/op")
+		// Keep it as uint64, multiply 100 to get two digit float later
+		mgasps := (100 * 1000 * gasUsed) / elapsed
+		bench.ReportMetric(float64(mgasps)/100, "mgas/s")
 		//Check if it is correct
 		if err != nil {
 			bench.Error(err)
 			return
 		}
-		if common.Bytes2Hex(res) != test.expected {
-			bench.Error(fmt.Sprintf("Expected %v, got %v", test.expected, common.Bytes2Hex(res)))
+		if common.Bytes2Hex(res) != test.Expected {
+			bench.Error(fmt.Sprintf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)))
 			return
 		}
 	})
@@ -493,9 +194,9 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
 // Benchmarks the sample inputs from the ECRECOVER precompile.
 func BenchmarkPrecompiledEcrecover(bench *testing.B) {
 	t := precompiledTest{
-		input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
-		expected: "000000000000000000000000ceaccac640adf55b2028469bd36ba501f28b699d",
-		name:     "",
+		Input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
+		Expected: "000000000000000000000000ceaccac640adf55b2028469bd36ba501f28b699d",
+		Name:     "",
 	}
 	benchmarkPrecompiled("01", t, bench)
 }
@@ -503,9 +204,9 @@ func BenchmarkPrecompiledEcrecover(bench *testing.B) {
 // Benchmarks the sample inputs from the SHA256 precompile.
 func BenchmarkPrecompiledSha256(bench *testing.B) {
 	t := precompiledTest{
-		input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
-		expected: "811c7003375852fabd0d362e40e68607a12bdabae61a7d068fe5fdd1dbbf2a5d",
-		name:     "128",
+		Input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
+		Expected: "811c7003375852fabd0d362e40e68607a12bdabae61a7d068fe5fdd1dbbf2a5d",
+		Name:     "128",
 	}
 	benchmarkPrecompiled("02", t, bench)
 }
@@ -513,9 +214,9 @@ func BenchmarkPrecompiledSha256(bench *testing.B) {
 // Benchmarks the sample inputs from the RIPEMD precompile.
 func BenchmarkPrecompiledRipeMD(bench *testing.B) {
 	t := precompiledTest{
-		input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
-		expected: "0000000000000000000000009215b8d9882ff46f0dfde6684d78e831467f65e6",
-		name:     "128",
+		Input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
+		Expected: "0000000000000000000000009215b8d9882ff46f0dfde6684d78e831467f65e6",
+		Name:     "128",
 	}
 	benchmarkPrecompiled("03", t, bench)
 }
@@ -523,141 +224,170 @@ func BenchmarkPrecompiledRipeMD(bench *testing.B) {
 // Benchmarks the sample inputs from the identiy precompile.
 func BenchmarkPrecompiledIdentity(bench *testing.B) {
 	t := precompiledTest{
-		input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
-		expected: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
-		name:     "128",
+		Input:    "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
+		Expected: "38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e000000000000000000000000000000000000000000000000000000000000001b38d18acb67d25c8bb9942764b62f18e17054f66a817bd4295423adf9ed98873e789d1dd423d25f0772d2748d60f7e4b81bb14d086eba8e8e8efb6dcff8a4ae02",
+		Name:     "128",
 	}
 	benchmarkPrecompiled("04", t, bench)
 }
 
 // Tests the sample inputs from the ModExp EIP 198.
-func TestPrecompiledModExp(t *testing.T) {
-	for _, test := range modexpTests {
-		testPrecompiled("05", test, t)
-	}
-}
+func TestPrecompiledModExp(t *testing.T)      { testJson("modexp", "05", t) }
+func BenchmarkPrecompiledModExp(b *testing.B) { benchJson("modexp", "05", b) }
 
-// Benchmarks the sample inputs from the ModExp EIP 198.
-func BenchmarkPrecompiledModExp(bench *testing.B) {
-	for _, test := range modexpTests {
-		benchmarkPrecompiled("05", test, bench)
-	}
-}
+func TestPrecompiledModExpEip2565(t *testing.T)      { testJson("modexp_eip2565", "f5", t) }
+func BenchmarkPrecompiledModExpEip2565(b *testing.B) { benchJson("modexp_eip2565", "f5", b) }
 
 // Tests the sample inputs from the elliptic curve addition EIP 213.
-func TestPrecompiledBn256Add(t *testing.T) {
-	for _, test := range bn256AddTests {
-		testPrecompiled("06", test, t)
-	}
-}
-
-// Benchmarks the sample inputs from the elliptic curve addition EIP 213.
-func BenchmarkPrecompiledBn256Add(bench *testing.B) {
-	for _, test := range bn256AddTests {
-		benchmarkPrecompiled("06", test, bench)
-	}
-}
+func TestPrecompiledBn256Add(t *testing.T)      { testJson("bn256Add", "06", t) }
+func BenchmarkPrecompiledBn256Add(b *testing.B) { benchJson("bn256Add", "06", b) }
 
 // Tests OOG
 func TestPrecompiledModExpOOG(t *testing.T) {
+	modexpTests, err := loadJson("modexp")
+	if err != nil {
+		t.Fatal(err)
+	}
 	for _, test := range modexpTests {
 		testPrecompiledOOG("05", test, t)
 	}
 }
 
 // Tests the sample inputs from the elliptic curve scalar multiplication EIP 213.
-func TestPrecompiledBn256ScalarMul(t *testing.T) {
-	for _, test := range bn256ScalarMulTests {
-		testPrecompiled("07", test, t)
+func TestPrecompiledBn256ScalarMul(t *testing.T)      { testJson("bn256ScalarMul", "07", t) }
+func BenchmarkPrecompiledBn256ScalarMul(b *testing.B) { benchJson("bn256ScalarMul", "07", b) }
+
+// Tests the sample inputs from the elliptic curve pairing check EIP 197.
+func TestPrecompiledBn256Pairing(t *testing.T)      { testJson("bn256Pairing", "08", t) }
+func BenchmarkPrecompiledBn256Pairing(b *testing.B) { benchJson("bn256Pairing", "08", b) }
+
+func TestPrecompiledBlake2F(t *testing.T)      { testJson("blake2F", "09", t) }
+func BenchmarkPrecompiledBlake2F(b *testing.B) { benchJson("blake2F", "09", b) }
+
+func TestPrecompileBlake2FMalformedInput(t *testing.T) {
+	for _, test := range blake2FMalformedInputTests {
+		testPrecompiledFailure("09", test, t)
 	}
 }
 
-// Benchmarks the sample inputs from the elliptic curve scalar multiplication EIP 213.
-func BenchmarkPrecompiledBn256ScalarMul(bench *testing.B) {
-	for _, test := range bn256ScalarMulTests {
-		benchmarkPrecompiled("07", test, bench)
+func TestPrecompiledEcrecover(t *testing.T) { testJson("ecRecover", "01", t) }
+
+func testJson(name, addr string, t *testing.T) {
+	tests, err := loadJson(name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, test := range tests {
+		testPrecompiled(addr, test, t)
 	}
 }
 
-// Tests the sample inputs from the elliptic curve pairing check EIP 197.
-func TestPrecompiledBn256Pairing(t *testing.T) {
-	for _, test := range bn256PairingTests {
-		testPrecompiled("08", test, t)
+func testJsonFail(name, addr string, t *testing.T) {
+	tests, err := loadJsonFail(name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, test := range tests {
+		testPrecompiledFailure(addr, test, t)
 	}
 }
 
-// Behcnmarks the sample inputs from the elliptic curve pairing check EIP 197.
-func BenchmarkPrecompiledBn256Pairing(bench *testing.B) {
-	for _, test := range bn256PairingTests {
-		benchmarkPrecompiled("08", test, bench)
+func benchJson(name, addr string, b *testing.B) {
+	tests, err := loadJson(name)
+	if err != nil {
+		b.Fatal(err)
 	}
-}
-func TestPrecompiledBlake2F(t *testing.T) {
-	for _, test := range blake2FTests {
-		testPrecompiled("09", test, t)
+	for _, test := range tests {
+		benchmarkPrecompiled(addr, test, b)
 	}
 }
 
-func BenchmarkPrecompiledBlake2F(bench *testing.B) {
-	for _, test := range blake2FTests {
-		benchmarkPrecompiled("09", test, bench)
+func TestPrecompiledBLS12381G1Add(t *testing.T)      { testJson("blsG1Add", "0a", t) }
+func TestPrecompiledBLS12381G1Mul(t *testing.T)      { testJson("blsG1Mul", "0b", t) }
+func TestPrecompiledBLS12381G1MultiExp(t *testing.T) { testJson("blsG1MultiExp", "0c", t) }
+func TestPrecompiledBLS12381G2Add(t *testing.T)      { testJson("blsG2Add", "0d", t) }
+func TestPrecompiledBLS12381G2Mul(t *testing.T)      { testJson("blsG2Mul", "0e", t) }
+func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp", "0f", t) }
+func TestPrecompiledBLS12381Pairing(t *testing.T)    { testJson("blsPairing", "10", t) }
+func TestPrecompiledBLS12381MapG1(t *testing.T)      { testJson("blsMapG1", "11", t) }
+func TestPrecompiledBLS12381MapG2(t *testing.T)      { testJson("blsMapG2", "12", t) }
+
+func BenchmarkPrecompiledBLS12381G1Add(b *testing.B)      { benchJson("blsG1Add", "0a", b) }
+func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B)      { benchJson("blsG1Mul", "0b", b) }
+func BenchmarkPrecompiledBLS12381G1MultiExp(b *testing.B) { benchJson("blsG1MultiExp", "0c", b) }
+func BenchmarkPrecompiledBLS12381G2Add(b *testing.B)      { benchJson("blsG2Add", "0d", b) }
+func BenchmarkPrecompiledBLS12381G2Mul(b *testing.B)      { benchJson("blsG2Mul", "0e", b) }
+func BenchmarkPrecompiledBLS12381G2MultiExp(b *testing.B) { benchJson("blsG2MultiExp", "0f", b) }
+func BenchmarkPrecompiledBLS12381Pairing(b *testing.B)    { benchJson("blsPairing", "10", b) }
+func BenchmarkPrecompiledBLS12381MapG1(b *testing.B)      { benchJson("blsMapG1", "11", b) }
+func BenchmarkPrecompiledBLS12381MapG2(b *testing.B)      { benchJson("blsMapG2", "12", b) }
+
+// Failure tests
+func TestPrecompiledBLS12381G1AddFail(t *testing.T)      { testJsonFail("blsG1Add", "0a", t) }
+func TestPrecompiledBLS12381G1MulFail(t *testing.T)      { testJsonFail("blsG1Mul", "0b", t) }
+func TestPrecompiledBLS12381G1MultiExpFail(t *testing.T) { testJsonFail("blsG1MultiExp", "0c", t) }
+func TestPrecompiledBLS12381G2AddFail(t *testing.T)      { testJsonFail("blsG2Add", "0d", t) }
+func TestPrecompiledBLS12381G2MulFail(t *testing.T)      { testJsonFail("blsG2Mul", "0e", t) }
+func TestPrecompiledBLS12381G2MultiExpFail(t *testing.T) { testJsonFail("blsG2MultiExp", "0f", t) }
+func TestPrecompiledBLS12381PairingFail(t *testing.T)    { testJsonFail("blsPairing", "10", t) }
+func TestPrecompiledBLS12381MapG1Fail(t *testing.T)      { testJsonFail("blsMapG1", "11", t) }
+func TestPrecompiledBLS12381MapG2Fail(t *testing.T)      { testJsonFail("blsMapG2", "12", t) }
+
+func loadJson(name string) ([]precompiledTest, error) {
+	data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name))
+	if err != nil {
+		return nil, err
 	}
+	var testcases []precompiledTest
+	err = json.Unmarshal(data, &testcases)
+	return testcases, err
 }
 
-func TestPrecompileBlake2FMalformedInput(t *testing.T) {
-	for _, test := range blake2FMalformedInputTests {
-		testPrecompiledFailure("09", test, t)
+func loadJsonFail(name string) ([]precompiledFailureTest, error) {
+	data, err := ioutil.ReadFile(fmt.Sprintf("testdata/precompiles/fail-%v.json", name))
+	if err != nil {
+		return nil, err
 	}
+	var testcases []precompiledFailureTest
+	err = json.Unmarshal(data, &testcases)
+	return testcases, err
 }
 
-// EcRecover test vectors
-var ecRecoverTests = []precompiledTest{
-	{
-		input: "a8b53bdf3306a35a7103ab5504a0c9b492295564b6202b1942a84ef300107281" +
-			"000000000000000000000000000000000000000000000000000000000000001b" +
-			"3078356531653033663533636531386237373263636230303933666637316633" +
-			"6635336635633735623734646362333161383561613862383839326234653862" +
-			"1122334455667788991011121314151617181920212223242526272829303132",
-		expected: "",
-		name:     "CallEcrecoverUnrecoverableKey",
-	},
-	{
-		input: "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c" +
-			"000000000000000000000000000000000000000000000000000000000000001c" +
-			"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f" +
-			"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
-		expected: "000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
-		name:     "ValidKey",
-	},
-	{
-		input: "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c" +
-			"100000000000000000000000000000000000000000000000000000000000001c" +
-			"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f" +
-			"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
-		expected: "",
-		name:     "InvalidHighV-bits-1",
-	},
-	{
-		input: "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c" +
-			"000000000000000000000000000000000000001000000000000000000000001c" +
-			"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f" +
-			"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
-		expected: "",
-		name:     "InvalidHighV-bits-2",
-	},
-	{
-		input: "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c" +
-			"000000000000000000000000000000000000001000000000000000000000011c" +
-			"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f" +
-			"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
-		expected: "",
-		name:     "InvalidHighV-bits-3",
-	},
+// BenchmarkPrecompiledBLS12381G1MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas.
+func BenchmarkPrecompiledBLS12381G1MultiExpWorstCase(b *testing.B) {
+	task := "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be1" +
+		"0000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9" +
+		"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+	input := task
+	for i := 0; i < 4787; i++ {
+		input = input + task
+	}
+	testcase := precompiledTest{
+		Input:       input,
+		Expected:    "0000000000000000000000000000000005a6310ea6f2a598023ae48819afc292b4dfcb40aabad24a0c2cb6c19769465691859eeb2a764342a810c5038d700f18000000000000000000000000000000001268ac944437d15923dc0aec00daa9250252e43e4b35ec7a19d01f0d6cd27f6e139d80dae16ba1c79cc7f57055a93ff5",
+		Name:        "WorstCaseG1",
+		NoBenchmark: false,
+	}
+	benchmarkPrecompiled("0c", testcase, b)
 }
 
-func TestPrecompiledEcrecover(t *testing.T) {
-	for _, test := range ecRecoverTests {
-		testPrecompiled("01", test, t)
+// BenchmarkPrecompiledBLS12381G2MultiExpWorstCase benchmarks the worst case we could find that still fits a gaslimit of 10MGas.
+func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
+	task := "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f" +
+		"000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b99" +
+		"00000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b" +
+		"000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7" +
+		"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+	input := task
+	for i := 0; i < 1040; i++ {
+		input = input + task
 	}
 
+	testcase := precompiledTest{
+		Input:       input,
+		Expected:    "0000000000000000000000000000000018f5ea0c8b086095cfe23f6bb1d90d45de929292006dba8cdedd6d3203af3c6bbfd592e93ecb2b2c81004961fdcbb46c00000000000000000000000000000000076873199175664f1b6493a43c02234f49dc66f077d3007823e0343ad92e30bd7dc209013435ca9f197aca44d88e9dac000000000000000000000000000000000e6f07f4b23b511eac1e2682a0fc224c15d80e122a3e222d00a41fab15eba645a700b9ae84f331ae4ed873678e2e6c9b000000000000000000000000000000000bcb4849e460612aaed79617255fd30c03f51cf03d2ed4163ca810c13e1954b1e8663157b957a601829bb272a4e6c7b8",
+		Name:        "WorstCaseG2",
+		NoBenchmark: false,
+	}
+	benchmarkPrecompiled("0f", testcase, b)
 }
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 8bf697e1bc..6bb941d5f9 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -18,27 +18,44 @@ package vm
 
 import (
 	"fmt"
+	"sort"
 
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/holiman/uint256"
 )
 
+var activators = map[int]func(*JumpTable){
+	2929: enable2929,
+	2200: enable2200,
+	1884: enable1884,
+	1344: enable1344,
+}
+
 // EnableEIP enables the given EIP on the config.
 // This operation writes in-place, and callers need to ensure that the globally
 // defined jump tables are not polluted.
 func EnableEIP(eipNum int, jt *JumpTable) error {
-	switch eipNum {
-	case 2200:
-		enable2200(jt)
-	case 1884:
-		enable1884(jt)
-	case 1344:
-		enable1344(jt)
-	default:
+	enablerFn, ok := activators[eipNum]
+	if !ok {
 		return fmt.Errorf("undefined eip %d", eipNum)
 	}
+	enablerFn(jt)
 	return nil
 }
 
+func ValidEip(eipNum int) bool {
+	_, ok := activators[eipNum]
+	return ok
+}
+func ActivateableEips() []string {
+	var nums []string
+	for k := range activators {
+		nums = append(nums, fmt.Sprintf("%d", k))
+	}
+	sort.Strings(nums)
+	return nums
+}
+
 // enable1884 applies EIP-1884 to the given jump table:
 // - Increase cost of BALANCE to 700
 // - Increase cost of EXTCODEHASH to 700
@@ -51,18 +68,17 @@ func enable1884(jt *JumpTable) {
 	jt[EXTCODEHASH].constantGas = params.ExtcodeHashGasEIP1884
 
 	// New opcode
-	jt[SELFBALANCE] = operation{
+	jt[SELFBALANCE] = &operation{
 		execute:     opSelfBalance,
 		constantGas: GasFastStep,
 		minStack:    minStack(0, 1),
 		maxStack:    maxStack(0, 1),
-		valid:       true,
 	}
 }
 
-func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(callContext.contract.Address()))
-	callContext.stack.push(balance)
+func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	balance, _ := uint256.FromBig(interpreter.evm.StateDB.GetBalance(scope.Contract.Address()))
+	scope.Stack.push(balance)
 	return nil, nil
 }
 
@@ -70,19 +86,18 @@ func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx
 // - Adds an opcode that returns the current chain’s EIP-155 unique identifier
 func enable1344(jt *JumpTable) {
 	// New opcode
-	jt[CHAINID] = operation{
+	jt[CHAINID] = &operation{
 		execute:     opChainID,
 		constantGas: GasQuickStep,
 		minStack:    minStack(0, 1),
 		maxStack:    maxStack(0, 1),
-		valid:       true,
 	}
 }
 
 // opChainID implements CHAINID opcode
-func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	chainId := interpreter.intPool.get().Set(interpreter.evm.chainConfig.ChainID)
-	callContext.stack.push(chainId)
+func opChainID(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	chainId, _ := uint256.FromBig(interpreter.evm.chainConfig.ChainID)
+	scope.Stack.push(chainId)
 	return nil, nil
 }
 
@@ -91,3 +106,41 @@ func enable2200(jt *JumpTable) {
 	jt[SLOAD].constantGas = params.SloadGasEIP2200
 	jt[SSTORE].dynamicGas = gasSStoreEIP2200
 }
+
+// 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
+}
diff --git a/core/vm/errors.go b/core/vm/errors.go
index 7f88f324ea..c813aa36af 100644
--- a/core/vm/errors.go
+++ b/core/vm/errors.go
@@ -16,15 +16,51 @@
 
 package vm
 
-import "errors"
+import (
+	"errors"
+	"fmt"
+)
 
-// List execution errors
+// List evm execution errors
 var (
 	ErrOutOfGas                 = errors.New("out of gas")
 	ErrCodeStoreOutOfGas        = errors.New("contract creation code storage out of gas")
 	ErrDepth                    = errors.New("max call depth exceeded")
-	ErrTraceLimitReached        = errors.New("the number of logs reached the specified limit")
 	ErrInsufficientBalance      = errors.New("insufficient balance for transfer")
 	ErrContractAddressCollision = errors.New("contract address collision")
-	ErrNoCompatibleInterpreter  = errors.New("no compatible interpreter")
+	ErrExecutionReverted        = errors.New("execution reverted")
+	ErrMaxCodeSizeExceeded      = errors.New("max code size exceeded")
+	ErrInvalidJump              = errors.New("invalid jump destination")
+	ErrWriteProtection          = errors.New("write protection")
+	ErrReturnDataOutOfBounds    = errors.New("return data out of bounds")
+	ErrGasUintOverflow          = errors.New("gas uint64 overflow")
 )
+
+// ErrStackUnderflow wraps an evm error when the items on the stack less
+// than the minimal requirement.
+type ErrStackUnderflow struct {
+	stackLen int
+	required int
+}
+
+func (e *ErrStackUnderflow) Error() string {
+	return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
+}
+
+// ErrStackOverflow wraps an evm error when the items on the stack exceeds
+// the maximum allowance.
+type ErrStackOverflow struct {
+	stackLen int
+	limit    int
+}
+
+func (e *ErrStackOverflow) Error() string {
+	return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit)
+}
+
+// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
+type ErrInvalidOpCode struct {
+	opcode OpCode
+}
+
+func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) }
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 751c1fdc1f..bd54e855c6 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -17,6 +17,7 @@
 package vm
 
 import (
+	"errors"
 	"math/big"
 	"sync/atomic"
 	"time"
@@ -24,6 +25,7 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/holiman/uint256"
 )
 
 // emptyCodeHash is used by create to ensure deployment is disallowed to already
@@ -40,20 +42,24 @@ type (
 	GetHashFunc func(uint64) common.Hash
 )
 
+func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
+	var precompiles map[common.Address]PrecompiledContract
+	switch {
+	case evm.chainRules.IsBerlin:
+		precompiles = PrecompiledContractsBerlin
+	case evm.chainRules.IsIstanbul:
+		precompiles = PrecompiledContractsIstanbul
+	case evm.chainRules.IsByzantium:
+		precompiles = PrecompiledContractsByzantium
+	default:
+		precompiles = PrecompiledContractsHomestead
+	}
+	p, ok := precompiles[addr]
+	return p, ok
+}
+
 // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
 func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
-	if contract.CodeAddr != nil {
-		precompiles := PrecompiledContractsHomestead
-		if evm.chainRules.IsByzantium {
-			precompiles = PrecompiledContractsByzantium
-		}
-		if evm.chainRules.IsIstanbul {
-			precompiles = PrecompiledContractsIstanbul
-		}
-		if p := precompiles[*contract.CodeAddr]; p != nil {
-			return RunPrecompiledContract(p, input, contract)
-		}
-	}
 	for _, interpreter := range evm.interpreters {
 		if interpreter.CanRun(contract.Code) {
 			if evm.interpreter != interpreter {
@@ -67,12 +73,12 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
 			return interpreter.Run(contract, input, readOnly)
 		}
 	}
-	return nil, ErrNoCompatibleInterpreter
+	return nil, errors.New("no compatible interpreter")
 }
 
-// Context provides the EVM with auxiliary information. Once provided
+// BlockContext provides the EVM with auxiliary information. Once provided
 // it shouldn't be modified.
-type Context struct {
+type BlockContext struct {
 	// CanTransfer returns whether the account contains
 	// sufficient ether to transfer the value
 	CanTransfer CanTransferFunc
@@ -81,10 +87,6 @@ type Context struct {
 	// GetHash returns the hash corresponding to n
 	GetHash GetHashFunc
 
-	// Message information
-	Origin   common.Address // Provides information for ORIGIN
-	GasPrice *big.Int       // Provides information for GASPRICE
-
 	// Block information
 	Coinbase    common.Address // Provides information for COINBASE
 	GasLimit    uint64         // Provides information for GASLIMIT
@@ -93,6 +95,14 @@ type Context struct {
 	Difficulty  *big.Int       // Provides information for DIFFICULTY
 }
 
+// TxContext provides the EVM with information about a transaction.
+// All fields can change between transactions.
+type TxContext struct {
+	// Message information
+	Origin   common.Address // Provides information for ORIGIN
+	GasPrice *big.Int       // Provides information for GASPRICE
+}
+
 // EVM is the Ethereum Virtual Machine base object and provides
 // the necessary tools to run a contract on the given state with
 // the provided context. It should be noted that any error
@@ -104,7 +114,8 @@ type Context struct {
 // The EVM should never be reused and is not thread safe.
 type EVM struct {
 	// Context provides auxiliary blockchain related information
-	Context
+	Context BlockContext
+	TxContext
 	// StateDB gives access to the underlying state
 	StateDB StateDB
 	// Depth is the current call stack
@@ -132,17 +143,18 @@ type EVM struct {
 
 // NewEVM returns a new EVM. The returned EVM is not thread safe and should
 // only ever be used *once*.
-func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
+func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
 	evm := &EVM{
-		Context:      ctx,
+		Context:      blockCtx,
+		TxContext:    txCtx,
 		StateDB:      statedb,
 		vmConfig:     vmConfig,
 		chainConfig:  chainConfig,
-		chainRules:   chainConfig.Rules(ctx.BlockNumber),
+		chainRules:   chainConfig.Rules(blockCtx.BlockNumber),
 		interpreters: make([]Interpreter, 0, 1),
 	}
 
-	if chainConfig.IsEWASM(ctx.BlockNumber) {
+	if chainConfig.IsEWASM(blockCtx.BlockNumber) {
 		// to be implemented by EVM-C and Wagon PRs.
 		// if vmConfig.EWASMInterpreter != "" {
 		//  extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
@@ -166,6 +178,13 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
 	return evm
 }
 
+// Reset resets the EVM with a new transaction context.Reset
+// This is not threadsafe and should only be done very cautiously.
+func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
+	evm.TxContext = txCtx
+	evm.StateDB = statedb
+}
+
 // Cancel cancels any running EVM operation. This may be called concurrently and
 // it's safe to be called multiple times.
 func (evm *EVM) Cancel() {
@@ -190,67 +209,69 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
 	if evm.vmConfig.NoRecursion && evm.depth > 0 {
 		return nil, gas, nil
 	}
-
 	// Fail if we're trying to execute above the call depth limit
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, gas, ErrDepth
 	}
 	// Fail if we're trying to transfer more than the available balance
-	if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
+	if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
 		return nil, gas, ErrInsufficientBalance
 	}
+	snapshot := evm.StateDB.Snapshot()
+	p, isPrecompile := evm.precompile(addr)
 
-	var (
-		to       = AccountRef(addr)
-		snapshot = evm.StateDB.Snapshot()
-	)
 	if !evm.StateDB.Exist(addr) {
-		precompiles := PrecompiledContractsHomestead
-		if evm.chainRules.IsByzantium {
-			precompiles = PrecompiledContractsByzantium
-		}
-		if evm.chainRules.IsIstanbul {
-			precompiles = PrecompiledContractsIstanbul
-		}
-		if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
+		if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
 			// Calling a non existing account, don't do anything, but ping the tracer
 			if evm.vmConfig.Debug && evm.depth == 0 {
-				evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
+				evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
 				evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
 			}
 			return nil, gas, nil
 		}
 		evm.StateDB.CreateAccount(addr)
 	}
-	evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
-	// Initialise a new contract and set the code that is to be used by the EVM.
-	// The contract is a scoped environment for this execution context only.
-	contract := NewContract(caller, to, value, gas)
-	contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
-
-	// Even if the account has no code, we need to continue because it might be a precompile
-	start := time.Now()
+	evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
 
 	// Capture the tracer start/end events in debug mode
 	if evm.vmConfig.Debug && evm.depth == 0 {
-		evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
-
-		defer func() { // Lazy evaluation of the parameters
-			evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
-		}()
+		evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
+		defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
+			evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
+		}(gas, time.Now())
 	}
-	ret, err = run(evm, contract, input, false)
 
+	if isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		// Initialise a new contract and set the code that is to be used by the EVM.
+		// The contract is a scoped environment for this execution context only.
+		code := evm.StateDB.GetCode(addr)
+		if len(code) == 0 {
+			ret, err = nil, nil // gas is unchanged
+		} else {
+			addrCopy := addr
+			// If the account has no code, we can abort here
+			// The depth-check is already done, and precompiles handled above
+			contract := NewContract(caller, AccountRef(addrCopy), value, gas)
+			contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
+			ret, err = run(evm, contract, input, false)
+			gas = contract.Gas
+		}
+	}
 	// When an error was returned by the EVM or when setting the creation code
 	// above we revert to the snapshot and consume any gas remaining. Additionally
 	// when we're in homestead this also counts for code storage gas errors.
 	if err != nil {
 		evm.StateDB.RevertToSnapshot(snapshot)
-		if err != errExecutionReverted {
-			contract.UseGas(contract.Gas)
+		if err != ErrExecutionReverted {
+			gas = 0
 		}
+		// TODO: consider clearing up unused snapshots:
+		//} else {
+		//	evm.StateDB.DiscardSnapshot(snapshot)
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 // CallCode executes the contract associated with the addr with the given input
@@ -264,33 +285,38 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
 	if evm.vmConfig.NoRecursion && evm.depth > 0 {
 		return nil, gas, nil
 	}
-
 	// Fail if we're trying to execute above the call depth limit
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, gas, ErrDepth
 	}
 	// Fail if we're trying to transfer more than the available balance
-	if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
+	// Note although it's noop to transfer X ether to caller itself. But
+	// if caller doesn't have enough balance, it would be an error to allow
+	// over-charging itself. So the check here is necessary.
+	if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
 		return nil, gas, ErrInsufficientBalance
 	}
-
-	var (
-		snapshot = evm.StateDB.Snapshot()
-		to       = AccountRef(caller.Address())
-	)
-	// Initialise a new contract and set the code that is to be used by the EVM.
-	// The contract is a scoped environment for this execution context only.
-	contract := NewContract(caller, to, value, gas)
-	contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
-
-	ret, err = run(evm, contract, input, false)
+	var snapshot = evm.StateDB.Snapshot()
+
+	// It is allowed to call precompiles, even via delegatecall
+	if p, isPrecompile := evm.precompile(addr); isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		addrCopy := addr
+		// Initialise a new contract and set the code that is to be used by the EVM.
+		// The contract is a scoped environment for this execution context only.
+		contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
+		contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+		ret, err = run(evm, contract, input, false)
+		gas = contract.Gas
+	}
 	if err != nil {
 		evm.StateDB.RevertToSnapshot(snapshot)
-		if err != errExecutionReverted {
-			contract.UseGas(contract.Gas)
+		if err != ErrExecutionReverted {
+			gas = 0
 		}
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 // DelegateCall executes the contract associated with the addr with the given input
@@ -306,24 +332,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, gas, ErrDepth
 	}
-
-	var (
-		snapshot = evm.StateDB.Snapshot()
-		to       = AccountRef(caller.Address())
-	)
-
-	// Initialise a new contract and make initialise the delegate values
-	contract := NewContract(caller, to, nil, gas).AsDelegate()
-	contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
-
-	ret, err = run(evm, contract, input, false)
+	var snapshot = evm.StateDB.Snapshot()
+
+	// It is allowed to call precompiles, even via delegatecall
+	if p, isPrecompile := evm.precompile(addr); isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		addrCopy := addr
+		// Initialise a new contract and make initialise the delegate values
+		contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
+		contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+		ret, err = run(evm, contract, input, false)
+		gas = contract.Gas
+	}
 	if err != nil {
 		evm.StateDB.RevertToSnapshot(snapshot)
-		if err != errExecutionReverted {
-			contract.UseGas(contract.Gas)
+		if err != ErrExecutionReverted {
+			gas = 0
 		}
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 // StaticCall executes the contract associated with the addr with the given input
@@ -338,33 +366,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, gas, ErrDepth
 	}
-
-	var (
-		to       = AccountRef(addr)
-		snapshot = evm.StateDB.Snapshot()
-	)
-	// Initialise a new contract and set the code that is to be used by the EVM.
-	// The contract is a scoped environment for this execution context only.
-	contract := NewContract(caller, to, new(big.Int), gas)
-	contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
+	// We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
+	// However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
+	// after all empty accounts were deleted, so this is not required. However, if we omit this,
+	// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
+	// We could change this, but for now it's left for legacy reasons
+	var snapshot = evm.StateDB.Snapshot()
 
 	// We do an AddBalance of zero here, just in order to trigger a touch.
 	// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
 	// but is the correct thing to do and matters on other networks, in tests, and potential
 	// future scenarios
-	evm.StateDB.AddBalance(addr, bigZero)
-
-	// When an error was returned by the EVM or when setting the creation code
-	// above we revert to the snapshot and consume any gas remaining. Additionally
-	// when we're in Homestead this also counts for code storage gas errors.
-	ret, err = run(evm, contract, input, true)
+	evm.StateDB.AddBalance(addr, big0)
+
+	if p, isPrecompile := evm.precompile(addr); isPrecompile {
+		ret, gas, err = RunPrecompiledContract(p, input, gas)
+	} else {
+		// At this point, we use a copy of address. If we don't, the go compiler will
+		// leak the 'contract' to the outer scope, and make allocation for 'contract'
+		// even if the actual execution ends on RunPrecompiled above.
+		addrCopy := addr
+		// Initialise a new contract and set the code that is to be used by the EVM.
+		// The contract is a scoped environment for this execution context only.
+		contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
+		contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
+		// When an error was returned by the EVM or when setting the creation code
+		// above we revert to the snapshot and consume any gas remaining. Additionally
+		// when we're in Homestead this also counts for code storage gas errors.
+		ret, err = run(evm, contract, input, true)
+		gas = contract.Gas
+	}
 	if err != nil {
 		evm.StateDB.RevertToSnapshot(snapshot)
-		if err != errExecutionReverted {
-			contract.UseGas(contract.Gas)
+		if err != ErrExecutionReverted {
+			gas = 0
 		}
 	}
-	return ret, contract.Gas, err
+	return ret, gas, err
 }
 
 type codeAndHash struct {
@@ -386,12 +424,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
 	if evm.depth > int(params.CallCreateDepth) {
 		return nil, common.Address{}, gas, ErrDepth
 	}
-	if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
+	if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
 		return nil, common.Address{}, gas, ErrInsufficientBalance
 	}
 	nonce := evm.StateDB.GetNonce(caller.Address())
 	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.IsBerlin {
+		evm.StateDB.AddAddressToAccessList(address)
+	}
 	// Ensure there's no existing contract already at the designated address
 	contractHash := evm.StateDB.GetCodeHash(address)
 	if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
@@ -403,7 +445,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
 	if evm.chainRules.IsEIP158 {
 		evm.StateDB.SetNonce(address, 1)
 	}
-	evm.Transfer(evm.StateDB, caller.Address(), address, value)
+	evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
 
 	// Initialise a new contract and set the code that is to be used by the EVM.
 	// The contract is a scoped environment for this execution context only.
@@ -415,19 +457,22 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
 	}
 
 	if evm.vmConfig.Debug && evm.depth == 0 {
-		evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value)
+		evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
 	}
 	start := time.Now()
 
 	ret, err := run(evm, contract, nil, false)
 
-	// check whether the max code size has been exceeded
-	maxCodeSizeExceeded := evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize
+	// Check whether the max code size has been exceeded, assign err if the case.
+	if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
+		err = ErrMaxCodeSizeExceeded
+	}
+
 	// if the contract creation ran successfully and no errors were returned
 	// calculate the gas required to store the code. If the code could not
 	// be stored due to not enough gas set an error and let it be handled
 	// by the error checking condition below.
-	if err == nil && !maxCodeSizeExceeded {
+	if err == nil {
 		createDataGas := uint64(len(ret)) * params.CreateDataGas
 		if contract.UseGas(createDataGas) {
 			evm.StateDB.SetCode(address, ret)
@@ -439,21 +484,17 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
 	// When an error was returned by the EVM or when setting the creation code
 	// above we revert to the snapshot and consume any gas remaining. Additionally
 	// when we're in homestead this also counts for code storage gas errors.
-	if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
+	if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
 		evm.StateDB.RevertToSnapshot(snapshot)
-		if err != errExecutionReverted {
+		if err != ErrExecutionReverted {
 			contract.UseGas(contract.Gas)
 		}
 	}
-	// Assign err if contract code size exceeds the max while the err is still empty.
-	if maxCodeSizeExceeded && err == nil {
-		err = errMaxCodeSizeExceeded
-	}
+
 	if evm.vmConfig.Debug && evm.depth == 0 {
 		evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
 	}
 	return ret, address, contract.Gas, err
-
 }
 
 // Create creates a new contract using code as deployment code.
@@ -466,9 +507,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
 //
 // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
 // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
-func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
+func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
 	codeAndHash := &codeAndHash{code: code}
-	contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes())
+	contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
 	return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
 }
 
diff --git a/core/vm/gas.go b/core/vm/gas.go
index 3e3052faa8..5cf1d852d2 100644
--- a/core/vm/gas.go
+++ b/core/vm/gas.go
@@ -17,7 +17,7 @@
 package vm
 
 import (
-	"math/big"
+	"github.com/holiman/uint256"
 )
 
 // Gas costs
@@ -34,19 +34,19 @@ const (
 //
 // The cost of gas was changed during the homestead price change HF.
 // As part of EIP 150 (TangerineWhistle), the returned gas is gas - base * 63 / 64.
-func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint64, error) {
+func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (uint64, error) {
 	if isEip150 {
 		availableGas = availableGas - base
 		gas := availableGas - availableGas/64
 		// If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150
-		// is smaller than the requested amount. Therefor we return the new gas instead
+		// is smaller than the requested amount. Therefore we return the new gas instead
 		// of returning an error.
 		if !callCost.IsUint64() || gas < callCost.Uint64() {
 			return gas, nil
 		}
 	}
 	if !callCost.IsUint64() {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 
 	return callCost.Uint64(), nil
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index 1d3c4f1003..944b6cf0a5 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -36,7 +36,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
 	// overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used
 	// without overflowing the gas calculation.
 	if newMemSize > 0x1FFFFFFFE0 {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	newMemSizeWords := toWordSize(newMemSize)
 	newMemSize = newMemSizeWords * 32
@@ -70,17 +70,17 @@ func memoryCopierGas(stackpos int) gasFunc {
 			return 0, err
 		}
 		// And gas for copying data, charged per word at param.CopyGas
-		words, overflow := bigUint64(stack.Back(stackpos))
+		words, overflow := stack.Back(stackpos).Uint64WithOverflow()
 		if overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 
 		if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 
 		if gas, overflow = math.SafeAdd(gas, words); overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 		return gas, nil
 	}
@@ -96,7 +96,7 @@ var (
 func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
 	var (
 		y, x    = stack.Back(1), stack.Back(0)
-		current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
+		current = evm.StateDB.GetState(contract.Address(), x.Bytes32())
 	)
 	// The legacy gas metering only takes into consideration the current state
 	// Legacy rules should be applied if we are in Petersburg (removal of EIP-1283)
@@ -131,11 +131,11 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
 	// 	  2.2.2. If original value equals new value (this storage slot is reset)
 	//       2.2.2.1. If original value is 0, add 19800 gas to refund counter.
 	// 	     2.2.2.2. Otherwise, add 4800 gas to refund counter.
-	value := common.BigToHash(y)
+	value := common.Hash(y.Bytes32())
 	if current == value { // noop (1)
 		return params.NetSstoreNoopGas, nil
 	}
-	original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x))
+	original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
 	if original == current {
 		if original == (common.Hash{}) { // create slot (2.1.1)
 			return params.NetSstoreInitGas, nil
@@ -163,18 +163,18 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
 }
 
 // 0. If *gasleft* is less than or equal to 2300, fail the current call.
-// 1. If current value equals new value (this is a no-op), SSTORE_NOOP_GAS gas is deducted.
+// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted.
 // 2. If current value does not equal new value:
 //   2.1. If original value equals current value (this storage slot has not been changed by the current execution context):
-//     2.1.1. If original value is 0, SSTORE_INIT_GAS gas is deducted.
-//     2.1.2. Otherwise, SSTORE_CLEAN_GAS gas is deducted. If new value is 0, add SSTORE_CLEAR_REFUND to refund counter.
-//   2.2. If original value does not equal current value (this storage slot is dirty), SSTORE_DIRTY_GAS gas is deducted. Apply both of the following clauses:
+//     2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted.
+//     2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter.
+//   2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses:
 //     2.2.1. If original value is not 0:
-//       2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEAR_REFUND gas from refund counter. We can prove that refund counter will never go below 0.
-//       2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEAR_REFUND gas to refund counter.
+//       2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter.
+//       2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter.
 //     2.2.2. If original value equals new value (this storage slot is reset):
-//       2.2.2.1. If original value is 0, add SSTORE_INIT_REFUND to refund counter.
-//       2.2.2.2. Otherwise, add SSTORE_CLEAN_REFUND gas to refund counter.
+//       2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter.
+//       2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter.
 func gasSStoreEIP2200(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 {
@@ -183,45 +183,45 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
 	// Gas sentry honoured, do the actual gas calculation based on the stored value
 	var (
 		y, x    = stack.Back(1), stack.Back(0)
-		current = evm.StateDB.GetState(contract.Address(), common.BigToHash(x))
+		current = evm.StateDB.GetState(contract.Address(), x.Bytes32())
 	)
-	value := common.BigToHash(y)
+	value := common.Hash(y.Bytes32())
 
 	if current == value { // noop (1)
-		return params.SstoreNoopGasEIP2200, nil
+		return params.SloadGasEIP2200, nil
 	}
-	original := evm.StateDB.GetCommittedState(contract.Address(), common.BigToHash(x))
+	original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32())
 	if original == current {
 		if original == (common.Hash{}) { // create slot (2.1.1)
-			return params.SstoreInitGasEIP2200, nil
+			return params.SstoreSetGasEIP2200, nil
 		}
 		if value == (common.Hash{}) { // delete slot (2.1.2b)
-			evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200)
+			evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
 		}
-		return params.SstoreCleanGasEIP2200, nil // write existing slot (2.1.2)
+		return params.SstoreResetGasEIP2200, 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.SstoreClearRefundEIP2200)
+			evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP2200)
 		} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
-			evm.StateDB.AddRefund(params.SstoreClearRefundEIP2200)
+			evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
 		}
 	}
 	if original == value {
 		if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
-			evm.StateDB.AddRefund(params.SstoreInitRefundEIP2200)
+			evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200)
 		} else { // reset to original existing slot (2.2.2.2)
-			evm.StateDB.AddRefund(params.SstoreCleanRefundEIP2200)
+			evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
 		}
 	}
-	return params.SstoreDirtyGasEIP2200, nil // dirty update (2.2)
+	return params.SloadGasEIP2200, nil // dirty update (2.2)
 }
 
 func makeGasLog(n uint64) gasFunc {
 	return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
-		requestedSize, overflow := bigUint64(stack.Back(1))
+		requestedSize, overflow := stack.Back(1).Uint64WithOverflow()
 		if overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 
 		gas, err := memoryGasCost(mem, memorySize)
@@ -230,18 +230,18 @@ func makeGasLog(n uint64) gasFunc {
 		}
 
 		if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 		if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 
 		var memorySizeGas uint64
 		if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 		if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow {
-			return 0, errGasUintOverflow
+			return 0, ErrGasUintOverflow
 		}
 		return gas, nil
 	}
@@ -252,15 +252,15 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
 	if err != nil {
 		return 0, err
 	}
-	wordGas, overflow := bigUint64(stack.Back(1))
+	wordGas, overflow := stack.Back(1).Uint64WithOverflow()
 	if overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -286,15 +286,15 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
 	if err != nil {
 		return 0, err
 	}
-	wordGas, overflow := bigUint64(stack.Back(2))
+	wordGas, overflow := stack.Back(2).Uint64WithOverflow()
 	if overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -307,7 +307,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
 		overflow bool
 	)
 	if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -320,7 +320,7 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
 		overflow bool
 	)
 	if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -328,8 +328,8 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
 func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
 	var (
 		gas            uint64
-		transfersValue = stack.Back(2).Sign() != 0
-		address        = common.BigToAddress(stack.Back(1))
+		transfersValue = !stack.Back(2).IsZero()
+		address        = common.Address(stack.Back(1).Bytes20())
 	)
 	if evm.chainRules.IsEIP158 {
 		if transfersValue && evm.StateDB.Empty(address) {
@@ -347,7 +347,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
 	}
 	var overflow bool
 	if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 
 	evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
@@ -355,7 +355,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
 		return 0, err
 	}
 	if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -373,14 +373,14 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
 		gas += params.CallValueTransferGas
 	}
 	if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
 	if err != nil {
 		return 0, err
 	}
 	if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -396,7 +396,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
 	}
 	var overflow bool
 	if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -412,7 +412,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
 	}
 	var overflow bool
 	if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
-		return 0, errGasUintOverflow
+		return 0, ErrGasUintOverflow
 	}
 	return gas, nil
 }
@@ -422,7 +422,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
 	// EIP150 homestead gas reprice fork:
 	if evm.chainRules.IsEIP150 {
 		gas = params.SelfdestructGasEIP150
-		var address = common.BigToAddress(stack.Back(0))
+		var address = common.Address(stack.Back(0).Bytes20())
 
 		if evm.chainRules.IsEIP158 {
 			// if empty and transfers value
diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go
index 2d8d3c6bc3..6cd126c9b4 100644
--- a/core/vm/gas_table_test.go
+++ b/core/vm/gas_table_test.go
@@ -39,8 +39,8 @@ func TestMemoryGasCost(t *testing.T) {
 	}
 	for i, tt := range tests {
 		v, err := memoryGasCost(&Memory{}, tt.size)
-		if (err == errGasUintOverflow) != tt.overflow {
-			t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == errGasUintOverflow, tt.overflow)
+		if (err == ErrGasUintOverflow) != tt.overflow {
+			t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow)
 		}
 		if v != tt.cost {
 			t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost)
@@ -87,11 +87,11 @@ func TestEIP2200(t *testing.T) {
 		statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original}))
 		statedb.Finalise(true) // Push the state into the "original" slot
 
-		vmctx := Context{
+		vmctx := BlockContext{
 			CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
 			Transfer:    func(StateDB, common.Address, common.Address, *big.Int) {},
 		}
-		vmenv := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
+		vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
 
 		_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int))
 		if err != tt.failure {
diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go
index 726012e59e..ac04afe8b7 100644
--- a/core/vm/gen_structlog.go
+++ b/core/vm/gen_structlog.go
@@ -23,6 +23,8 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
 		Memory        hexutil.Bytes               `json:"memory"`
 		MemorySize    int                         `json:"memSize"`
 		Stack         []*math.HexOrDecimal256     `json:"stack"`
+		ReturnStack   []math.HexOrDecimal64       `json:"returnStack"`
+		ReturnData    hexutil.Bytes               `json:"returnData"`
 		Storage       map[common.Hash]common.Hash `json:"-"`
 		Depth         int                         `json:"depth"`
 		RefundCounter uint64                      `json:"refund"`
@@ -43,6 +45,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
 			enc.Stack[k] = (*math.HexOrDecimal256)(v)
 		}
 	}
+	enc.ReturnData = s.ReturnData
 	enc.Storage = s.Storage
 	enc.Depth = s.Depth
 	enc.RefundCounter = s.RefundCounter
@@ -62,6 +65,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
 		Memory        *hexutil.Bytes              `json:"memory"`
 		MemorySize    *int                        `json:"memSize"`
 		Stack         []*math.HexOrDecimal256     `json:"stack"`
+		ReturnData    *hexutil.Bytes              `json:"returnData"`
 		Storage       map[common.Hash]common.Hash `json:"-"`
 		Depth         *int                        `json:"depth"`
 		RefundCounter *uint64                     `json:"refund"`
@@ -95,6 +99,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
 			s.Stack[k] = (*big.Int)(v)
 		}
 	}
+	if dec.ReturnData != nil {
+		s.ReturnData = *dec.ReturnData
+	}
 	if dec.Storage != nil {
 		s.Storage = dec.Storage
 	}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 9ac4cd47cd..3277674ee8 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -17,374 +17,223 @@
 package vm
 
 import (
-	"errors"
-	"math/big"
-
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/holiman/uint256"
 	"golang.org/x/crypto/sha3"
 )
 
-var (
-	bigZero                  = new(big.Int)
-	tt255                    = math.BigPow(2, 255)
-	errWriteProtection       = errors.New("evm: write protection")
-	errReturnDataOutOfBounds = errors.New("evm: return data out of bounds")
-	errExecutionReverted     = errors.New("evm: execution reverted")
-	errMaxCodeSizeExceeded   = errors.New("evm: max code size exceeded")
-	errInvalidJump           = errors.New("evm: invalid jump destination")
-)
-
-func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	math.U256(y.Add(x, y))
-
-	interpreter.intPool.putOne(x)
+func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Add(&x, y)
 	return nil, nil
 }
 
-func opSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	math.U256(y.Sub(x, y))
-
-	interpreter.intPool.putOne(x)
+func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Sub(&x, y)
 	return nil, nil
 }
 
-func opMul(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.pop()
-	callContext.stack.push(math.U256(x.Mul(x, y)))
-
-	interpreter.intPool.putOne(y)
-
+func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Mul(&x, y)
 	return nil, nil
 }
 
-func opDiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	if y.Sign() != 0 {
-		math.U256(y.Div(x, y))
-	} else {
-		y.SetUint64(0)
-	}
-	interpreter.intPool.putOne(x)
+func opDiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Div(&x, y)
 	return nil, nil
 }
 
-func opSdiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := math.S256(callContext.stack.pop()), math.S256(callContext.stack.pop())
-	res := interpreter.intPool.getZero()
-
-	if y.Sign() == 0 || x.Sign() == 0 {
-		callContext.stack.push(res)
-	} else {
-		if x.Sign() != y.Sign() {
-			res.Div(x.Abs(x), y.Abs(y))
-			res.Neg(res)
-		} else {
-			res.Div(x.Abs(x), y.Abs(y))
-		}
-		callContext.stack.push(math.U256(res))
-	}
-	interpreter.intPool.put(x, y)
+func opSdiv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.SDiv(&x, y)
 	return nil, nil
 }
 
-func opMod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.pop()
-	if y.Sign() == 0 {
-		callContext.stack.push(x.SetUint64(0))
-	} else {
-		callContext.stack.push(math.U256(x.Mod(x, y)))
-	}
-	interpreter.intPool.putOne(y)
+func opMod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Mod(&x, y)
 	return nil, nil
 }
 
-func opSmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := math.S256(callContext.stack.pop()), math.S256(callContext.stack.pop())
-	res := interpreter.intPool.getZero()
-
-	if y.Sign() == 0 {
-		callContext.stack.push(res)
-	} else {
-		if x.Sign() < 0 {
-			res.Mod(x.Abs(x), y.Abs(y))
-			res.Neg(res)
-		} else {
-			res.Mod(x.Abs(x), y.Abs(y))
-		}
-		callContext.stack.push(math.U256(res))
-	}
-	interpreter.intPool.put(x, y)
+func opSmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.SMod(&x, y)
 	return nil, nil
 }
 
-func opExp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	base, exponent := callContext.stack.pop(), callContext.stack.pop()
-	// some shortcuts
-	cmpToOne := exponent.Cmp(big1)
-	if cmpToOne < 0 { // Exponent is zero
-		// x ^ 0 == 1
-		callContext.stack.push(base.SetUint64(1))
-	} else if base.Sign() == 0 {
-		// 0 ^ y, if y != 0, == 0
-		callContext.stack.push(base.SetUint64(0))
-	} else if cmpToOne == 0 { // Exponent is one
-		// x ^ 1 == x
-		callContext.stack.push(base)
-	} else {
-		callContext.stack.push(math.Exp(base, exponent))
-		interpreter.intPool.putOne(base)
-	}
-	interpreter.intPool.putOne(exponent)
+func opExp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	base, exponent := scope.Stack.pop(), scope.Stack.peek()
+	exponent.Exp(&base, exponent)
 	return nil, nil
 }
 
-func opSignExtend(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	back := callContext.stack.pop()
-	if back.Cmp(big.NewInt(31)) < 0 {
-		bit := uint(back.Uint64()*8 + 7)
-		num := callContext.stack.pop()
-		mask := back.Lsh(common.Big1, bit)
-		mask.Sub(mask, common.Big1)
-		if num.Bit(int(bit)) > 0 {
-			num.Or(num, mask.Not(mask))
-		} else {
-			num.And(num, mask)
-		}
-
-		callContext.stack.push(math.U256(num))
-	}
-
-	interpreter.intPool.putOne(back)
+func opSignExtend(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	back, num := scope.Stack.pop(), scope.Stack.peek()
+	num.ExtendSign(num, &back)
 	return nil, nil
 }
 
-func opNot(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x := callContext.stack.peek()
-	math.U256(x.Not(x))
+func opNot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x := scope.Stack.peek()
+	x.Not(x)
 	return nil, nil
 }
 
-func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	if x.Cmp(y) < 0 {
-		y.SetUint64(1)
+func opLt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	if x.Lt(y) {
+		y.SetOne()
 	} else {
-		y.SetUint64(0)
+		y.Clear()
 	}
-	interpreter.intPool.putOne(x)
 	return nil, nil
 }
 
-func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	if x.Cmp(y) > 0 {
-		y.SetUint64(1)
+func opGt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	if x.Gt(y) {
+		y.SetOne()
 	} else {
-		y.SetUint64(0)
+		y.Clear()
 	}
-	interpreter.intPool.putOne(x)
 	return nil, nil
 }
 
-func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-
-	xSign := x.Cmp(tt255)
-	ySign := y.Cmp(tt255)
-
-	switch {
-	case xSign >= 0 && ySign < 0:
-		y.SetUint64(1)
-
-	case xSign < 0 && ySign >= 0:
-		y.SetUint64(0)
-
-	default:
-		if x.Cmp(y) < 0 {
-			y.SetUint64(1)
-		} else {
-			y.SetUint64(0)
-		}
+func opSlt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	if x.Slt(y) {
+		y.SetOne()
+	} else {
+		y.Clear()
 	}
-	interpreter.intPool.putOne(x)
 	return nil, nil
 }
 
-func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-
-	xSign := x.Cmp(tt255)
-	ySign := y.Cmp(tt255)
-
-	switch {
-	case xSign >= 0 && ySign < 0:
-		y.SetUint64(0)
-
-	case xSign < 0 && ySign >= 0:
-		y.SetUint64(1)
-
-	default:
-		if x.Cmp(y) > 0 {
-			y.SetUint64(1)
-		} else {
-			y.SetUint64(0)
-		}
+func opSgt(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	if x.Sgt(y) {
+		y.SetOne()
+	} else {
+		y.Clear()
 	}
-	interpreter.intPool.putOne(x)
 	return nil, nil
 }
 
-func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	if x.Cmp(y) == 0 {
-		y.SetUint64(1)
+func opEq(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	if x.Eq(y) {
+		y.SetOne()
 	} else {
-		y.SetUint64(0)
+		y.Clear()
 	}
-	interpreter.intPool.putOne(x)
 	return nil, nil
 }
 
-func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x := callContext.stack.peek()
-	if x.Sign() > 0 {
-		x.SetUint64(0)
+func opIszero(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x := scope.Stack.peek()
+	if x.IsZero() {
+		x.SetOne()
 	} else {
-		x.SetUint64(1)
+		x.Clear()
 	}
 	return nil, nil
 }
 
-func opAnd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.pop()
-	callContext.stack.push(x.And(x, y))
-
-	interpreter.intPool.putOne(y)
+func opAnd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.And(&x, y)
 	return nil, nil
 }
 
-func opOr(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	y.Or(x, y)
-
-	interpreter.intPool.putOne(x)
+func opOr(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Or(&x, y)
 	return nil, nil
 }
 
-func opXor(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y := callContext.stack.pop(), callContext.stack.peek()
-	y.Xor(x, y)
-
-	interpreter.intPool.putOne(x)
+func opXor(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y := scope.Stack.pop(), scope.Stack.peek()
+	y.Xor(&x, y)
 	return nil, nil
 }
 
-func opByte(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	th, val := callContext.stack.pop(), callContext.stack.peek()
-	if th.Cmp(common.Big32) < 0 {
-		b := math.Byte(val, 32, int(th.Int64()))
-		val.SetUint64(uint64(b))
-	} else {
-		val.SetUint64(0)
-	}
-	interpreter.intPool.putOne(th)
+func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	th, val := scope.Stack.pop(), scope.Stack.peek()
+	val.Byte(&th)
 	return nil, nil
 }
 
-func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop()
-	if z.Cmp(bigZero) > 0 {
-		x.Add(x, y)
-		x.Mod(x, z)
-		callContext.stack.push(math.U256(x))
+func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
+	if z.IsZero() {
+		z.Clear()
 	} else {
-		callContext.stack.push(x.SetUint64(0))
+		z.AddMod(&x, &y, z)
 	}
-	interpreter.intPool.put(y, z)
 	return nil, nil
 }
 
-func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop()
-	if z.Cmp(bigZero) > 0 {
-		x.Mul(x, y)
-		x.Mod(x, z)
-		callContext.stack.push(math.U256(x))
-	} else {
-		callContext.stack.push(x.SetUint64(0))
-	}
-	interpreter.intPool.put(y, z)
+func opMulmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
+	z.MulMod(&x, &y, z)
 	return nil, nil
 }
 
 // opSHL implements Shift Left
 // The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2,
 // and pushes on the stack arg2 shifted to the left by arg1 number of bits.
-func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opSHL(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
-	shift, value := math.U256(callContext.stack.pop()), math.U256(callContext.stack.peek())
-	defer interpreter.intPool.putOne(shift) // First operand back into the pool
-
-	if shift.Cmp(common.Big256) >= 0 {
-		value.SetUint64(0)
-		return nil, nil
+	shift, value := scope.Stack.pop(), scope.Stack.peek()
+	if shift.LtUint64(256) {
+		value.Lsh(value, uint(shift.Uint64()))
+	} else {
+		value.Clear()
 	}
-	n := uint(shift.Uint64())
-	math.U256(value.Lsh(value, n))
-
 	return nil, nil
 }
 
 // opSHR implements Logical Shift Right
 // The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2,
 // and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill.
-func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opSHR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	// Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards
-	shift, value := math.U256(callContext.stack.pop()), math.U256(callContext.stack.peek())
-	defer interpreter.intPool.putOne(shift) // First operand back into the pool
-
-	if shift.Cmp(common.Big256) >= 0 {
-		value.SetUint64(0)
-		return nil, nil
+	shift, value := scope.Stack.pop(), scope.Stack.peek()
+	if shift.LtUint64(256) {
+		value.Rsh(value, uint(shift.Uint64()))
+	} else {
+		value.Clear()
 	}
-	n := uint(shift.Uint64())
-	math.U256(value.Rsh(value, n))
-
 	return nil, nil
 }
 
 // opSAR implements Arithmetic Shift Right
 // The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2,
 // and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension.
-func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	// Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one
-	shift, value := math.U256(callContext.stack.pop()), math.S256(callContext.stack.pop())
-	defer interpreter.intPool.putOne(shift) // First operand back into the pool
-
-	if shift.Cmp(common.Big256) >= 0 {
+func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	shift, value := scope.Stack.pop(), scope.Stack.peek()
+	if shift.GtUint64(256) {
 		if value.Sign() >= 0 {
-			value.SetUint64(0)
+			value.Clear()
 		} else {
-			value.SetInt64(-1)
+			// Max negative shift: all bits set
+			value.SetAllOne()
 		}
-		callContext.stack.push(math.U256(value))
 		return nil, nil
 	}
 	n := uint(shift.Uint64())
-	value.Rsh(value, n)
-	callContext.stack.push(math.U256(value))
-
+	value.SRsh(value, n)
 	return nil, nil
 }
 
-func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	offset, size := callContext.stack.pop(), callContext.stack.pop()
-	data := callContext.memory.GetPtr(offset.Int64(), size.Int64())
+func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	offset, size := scope.Stack.pop(), scope.Stack.peek()
+	data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
 
 	if interpreter.hasher == nil {
 		interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState)
@@ -398,121 +247,143 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
 	if evm.vmConfig.EnablePreimageRecording {
 		evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
 	}
-	callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:]))
 
-	interpreter.intPool.put(offset, size)
+	size.SetBytes(interpreter.hasherBuf[:])
 	return nil, nil
 }
-
-func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetBytes(callContext.contract.Address().Bytes()))
+func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes()))
 	return nil, nil
 }
 
-func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	slot := callContext.stack.peek()
-	slot.Set(interpreter.evm.StateDB.GetBalance(common.BigToAddress(slot)))
+func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	slot := scope.Stack.peek()
+	address := common.Address(slot.Bytes20())
+	slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address))
 	return nil, nil
 }
 
-func opOrigin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes()))
+func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes()))
 	return nil, nil
 }
-
-func opCaller(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetBytes(callContext.contract.Caller().Bytes()))
+func opCaller(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes()))
 	return nil, nil
 }
 
-func opCallValue(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().Set(callContext.contract.value))
+func opCallValue(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	v, _ := uint256.FromBig(scope.Contract.value)
+	scope.Stack.push(v)
 	return nil, nil
 }
 
-func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetBytes(getDataBig(callContext.contract.Input, callContext.stack.pop(), big32)))
+func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	x := scope.Stack.peek()
+	if offset, overflow := x.Uint64WithOverflow(); !overflow {
+		data := getData(scope.Contract.Input, offset, 32)
+		x.SetBytes(data)
+	} else {
+		x.Clear()
+	}
 	return nil, nil
 }
 
-func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetInt64(int64(len(callContext.contract.Input))))
+func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Input))))
 	return nil, nil
 }
 
-func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		memOffset  = callContext.stack.pop()
-		dataOffset = callContext.stack.pop()
-		length     = callContext.stack.pop()
+		memOffset  = scope.Stack.pop()
+		dataOffset = scope.Stack.pop()
+		length     = scope.Stack.pop()
 	)
-	callContext.memory.Set(memOffset.Uint64(), length.Uint64(), getDataBig(callContext.contract.Input, dataOffset, length))
+	dataOffset64, overflow := dataOffset.Uint64WithOverflow()
+	if overflow {
+		dataOffset64 = 0xffffffffffffffff
+	}
+	// These values are checked for overflow during gas cost calculation
+	memOffset64 := memOffset.Uint64()
+	length64 := length.Uint64()
+	scope.Memory.Set(memOffset64, length64, getData(scope.Contract.Input, dataOffset64, length64))
 
-	interpreter.intPool.put(memOffset, dataOffset, length)
 	return nil, nil
 }
 
-func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetUint64(uint64(len(interpreter.returnData))))
+func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(interpreter.returnData))))
 	return nil, nil
 }
 
-func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		memOffset  = callContext.stack.pop()
-		dataOffset = callContext.stack.pop()
-		length     = callContext.stack.pop()
-
-		end = interpreter.intPool.get().Add(dataOffset, length)
+		memOffset  = scope.Stack.pop()
+		dataOffset = scope.Stack.pop()
+		length     = scope.Stack.pop()
 	)
-	defer interpreter.intPool.put(memOffset, dataOffset, length, end)
 
-	if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() {
-		return nil, errReturnDataOutOfBounds
+	offset64, overflow := dataOffset.Uint64WithOverflow()
+	if overflow {
+		return nil, ErrReturnDataOutOfBounds
 	}
-	callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()])
-
+	// we can reuse dataOffset now (aliasing it for clarity)
+	var end = dataOffset
+	end.Add(&dataOffset, &length)
+	end64, overflow := end.Uint64WithOverflow()
+	if overflow || uint64(len(interpreter.returnData)) < end64 {
+		return nil, ErrReturnDataOutOfBounds
+	}
+	scope.Memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64])
 	return nil, nil
 }
 
-func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	slot := callContext.stack.peek()
-	slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.BigToAddress(slot))))
-
+func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	slot := scope.Stack.peek()
+	slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
 	return nil, nil
 }
 
-func opCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	l := interpreter.intPool.get().SetInt64(int64(len(callContext.contract.Code)))
-	callContext.stack.push(l)
-
+func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	l := new(uint256.Int)
+	l.SetUint64(uint64(len(scope.Contract.Code)))
+	scope.Stack.push(l)
 	return nil, nil
 }
 
-func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		memOffset  = callContext.stack.pop()
-		codeOffset = callContext.stack.pop()
-		length     = callContext.stack.pop()
+		memOffset  = scope.Stack.pop()
+		codeOffset = scope.Stack.pop()
+		length     = scope.Stack.pop()
 	)
-	codeCopy := getDataBig(callContext.contract.Code, codeOffset, length)
-	callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+	uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
+	if overflow {
+		uint64CodeOffset = 0xffffffffffffffff
+	}
+	codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
+	scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
 
-	interpreter.intPool.put(memOffset, codeOffset, length)
 	return nil, nil
 }
 
-func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		addr       = common.BigToAddress(callContext.stack.pop())
-		memOffset  = callContext.stack.pop()
-		codeOffset = callContext.stack.pop()
-		length     = callContext.stack.pop()
+		stack      = scope.Stack
+		a          = stack.pop()
+		memOffset  = stack.pop()
+		codeOffset = stack.pop()
+		length     = stack.pop()
 	)
-	codeCopy := getDataBig(interpreter.evm.StateDB.GetCode(addr), codeOffset, length)
-	callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+	uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
+	if overflow {
+		uint64CodeOffset = 0xffffffffffffffff
+	}
+	addr := common.Address(a.Bytes20())
+	codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
+	scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
 
-	interpreter.intPool.put(memOffset, codeOffset, length)
 	return nil, nil
 }
 
@@ -542,343 +413,380 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx
 //
 //   (6) Caller tries to get the code hash for an account which is marked as deleted,
 // this account should be regarded as a non-existent account and zero should be returned.
-func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	slot := callContext.stack.peek()
-	address := common.BigToAddress(slot)
+func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	slot := scope.Stack.peek()
+	address := common.Address(slot.Bytes20())
 	if interpreter.evm.StateDB.Empty(address) {
-		slot.SetUint64(0)
+		slot.Clear()
 	} else {
 		slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
 	}
 	return nil, nil
 }
 
-func opGasprice(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().Set(interpreter.evm.GasPrice))
+func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	v, _ := uint256.FromBig(interpreter.evm.GasPrice)
+	scope.Stack.push(v)
 	return nil, nil
 }
 
-func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	num := callContext.stack.pop()
-
-	n := interpreter.intPool.get().Sub(interpreter.evm.BlockNumber, common.Big257)
-	if num.Cmp(n) > 0 && num.Cmp(interpreter.evm.BlockNumber) < 0 {
-		callContext.stack.push(interpreter.evm.GetHash(num.Uint64()).Big())
+func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	num := scope.Stack.peek()
+	num64, overflow := num.Uint64WithOverflow()
+	if overflow {
+		num.Clear()
+		return nil, nil
+	}
+	var upper, lower uint64
+	upper = interpreter.evm.Context.BlockNumber.Uint64()
+	if upper < 257 {
+		lower = 0
+	} else {
+		lower = upper - 256
+	}
+	if num64 >= lower && num64 < upper {
+		num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes())
 	} else {
-		callContext.stack.push(interpreter.intPool.getZero())
+		num.Clear()
 	}
-	interpreter.intPool.put(num, n)
 	return nil, nil
 }
 
-func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes()))
+func opCoinbase(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.Coinbase.Bytes()))
 	return nil, nil
 }
 
-func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Time)))
+func opTimestamp(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	v, _ := uint256.FromBig(interpreter.evm.Context.Time)
+	scope.Stack.push(v)
 	return nil, nil
 }
 
-func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.BlockNumber)))
+func opNumber(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	v, _ := uint256.FromBig(interpreter.evm.Context.BlockNumber)
+	scope.Stack.push(v)
 	return nil, nil
 }
 
-func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Difficulty)))
+func opDifficulty(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	v, _ := uint256.FromBig(interpreter.evm.Context.Difficulty)
+	scope.Stack.push(v)
 	return nil, nil
 }
 
-func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(math.U256(interpreter.intPool.get().SetUint64(interpreter.evm.GasLimit)))
+func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetUint64(interpreter.evm.Context.GasLimit))
 	return nil, nil
 }
 
-func opPop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	interpreter.intPool.putOne(callContext.stack.pop())
+func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.pop()
 	return nil, nil
 }
 
-func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	v := callContext.stack.peek()
-	offset := v.Int64()
-	v.SetBytes(callContext.memory.GetPtr(offset, 32))
+func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	v := scope.Stack.peek()
+	offset := int64(v.Uint64())
+	v.SetBytes(scope.Memory.GetPtr(offset, 32))
 	return nil, nil
 }
 
-func opMstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	// pop value of the stack
-	mStart, val := callContext.stack.pop(), callContext.stack.pop()
-	callContext.memory.Set32(mStart.Uint64(), val)
-
-	interpreter.intPool.put(mStart, val)
+	mStart, val := scope.Stack.pop(), scope.Stack.pop()
+	scope.Memory.Set32(mStart.Uint64(), &val)
 	return nil, nil
 }
 
-func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	off, val := callContext.stack.pop().Int64(), callContext.stack.pop().Int64()
-	callContext.memory.store[off] = byte(val & 0xff)
-
+func opMstore8(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	off, val := scope.Stack.pop(), scope.Stack.pop()
+	scope.Memory.store[off.Uint64()] = byte(val.Uint64())
 	return nil, nil
 }
 
-func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	loc := callContext.stack.peek()
-	val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), common.BigToHash(loc))
+func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	loc := scope.Stack.peek()
+	hash := common.Hash(loc.Bytes32())
+	val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash)
 	loc.SetBytes(val.Bytes())
 	return nil, nil
 }
 
-func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	loc := common.BigToHash(callContext.stack.pop())
-	val := callContext.stack.pop()
-	interpreter.evm.StateDB.SetState(callContext.contract.Address(), loc, common.BigToHash(val))
-
-	interpreter.intPool.putOne(val)
+func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	loc := scope.Stack.pop()
+	val := scope.Stack.pop()
+	interpreter.evm.StateDB.SetState(scope.Contract.Address(),
+		loc.Bytes32(), val.Bytes32())
 	return nil, nil
 }
 
-func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	pos := callContext.stack.pop()
-	if !callContext.contract.validJumpdest(pos) {
-		return nil, errInvalidJump
+func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	pos := scope.Stack.pop()
+	if !scope.Contract.validJumpdest(&pos) {
+		return nil, ErrInvalidJump
 	}
 	*pc = pos.Uint64()
-
-	interpreter.intPool.putOne(pos)
 	return nil, nil
 }
 
-func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	pos, cond := callContext.stack.pop(), callContext.stack.pop()
-	if cond.Sign() != 0 {
-		if !callContext.contract.validJumpdest(pos) {
-			return nil, errInvalidJump
+func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	pos, cond := scope.Stack.pop(), scope.Stack.pop()
+	if !cond.IsZero() {
+		if !scope.Contract.validJumpdest(&pos) {
+			return nil, ErrInvalidJump
 		}
 		*pc = pos.Uint64()
 	} else {
 		*pc++
 	}
-
-	interpreter.intPool.put(pos, cond)
 	return nil, nil
 }
 
-func opJumpdest(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opJumpdest(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	return nil, nil
 }
 
-func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetUint64(*pc))
+func opPc(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetUint64(*pc))
 	return nil, nil
 }
 
-func opMsize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetInt64(int64(callContext.memory.Len())))
+func opMsize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetUint64(uint64(scope.Memory.Len())))
 	return nil, nil
 }
 
-func opGas(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	callContext.stack.push(interpreter.intPool.get().SetUint64(callContext.contract.Gas))
+func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	scope.Stack.push(new(uint256.Int).SetUint64(scope.Contract.Gas))
 	return nil, nil
 }
 
-func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		value        = callContext.stack.pop()
-		offset, size = callContext.stack.pop(), callContext.stack.pop()
-		input        = callContext.memory.GetCopy(offset.Int64(), size.Int64())
-		gas          = callContext.contract.Gas
+		value        = scope.Stack.pop()
+		offset, size = scope.Stack.pop(), scope.Stack.pop()
+		input        = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
+		gas          = scope.Contract.Gas
 	)
 	if interpreter.evm.chainRules.IsEIP150 {
 		gas -= gas / 64
 	}
+	// reuse size int for stackvalue
+	stackvalue := size
 
-	callContext.contract.UseGas(gas)
-	res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value)
+	scope.Contract.UseGas(gas)
+	//TODO: use uint256.Int instead of converting with toBig()
+	var bigVal = big0
+	if !value.IsZero() {
+		bigVal = value.ToBig()
+	}
+
+	res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract, input, gas, bigVal)
 	// Push item on the stack based on the returned error. If the ruleset is
 	// homestead we must check for CodeStoreOutOfGasError (homestead only
 	// rule) and treat as an error, if the ruleset is frontier we must
 	// ignore this error and pretend the operation was successful.
 	if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas {
-		callContext.stack.push(interpreter.intPool.getZero())
+		stackvalue.Clear()
 	} else if suberr != nil && suberr != ErrCodeStoreOutOfGas {
-		callContext.stack.push(interpreter.intPool.getZero())
+		stackvalue.Clear()
 	} else {
-		callContext.stack.push(interpreter.intPool.get().SetBytes(addr.Bytes()))
+		stackvalue.SetBytes(addr.Bytes())
 	}
-	callContext.contract.Gas += returnGas
-	interpreter.intPool.put(value, offset, size)
+	scope.Stack.push(&stackvalue)
+	scope.Contract.Gas += returnGas
 
-	if suberr == errExecutionReverted {
+	if suberr == ErrExecutionReverted {
 		return res, nil
 	}
 	return nil, nil
 }
 
-func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		endowment    = callContext.stack.pop()
-		offset, size = callContext.stack.pop(), callContext.stack.pop()
-		salt         = callContext.stack.pop()
-		input        = callContext.memory.GetCopy(offset.Int64(), size.Int64())
-		gas          = callContext.contract.Gas
+		endowment    = scope.Stack.pop()
+		offset, size = scope.Stack.pop(), scope.Stack.pop()
+		salt         = scope.Stack.pop()
+		input        = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
+		gas          = scope.Contract.Gas
 	)
 
 	// Apply EIP150
 	gas -= gas / 64
-	callContext.contract.UseGas(gas)
-	res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, endowment, salt)
+	scope.Contract.UseGas(gas)
+	// reuse size int for stackvalue
+	stackvalue := size
+	//TODO: use uint256.Int instead of converting with toBig()
+	bigEndowment := big0
+	if !endowment.IsZero() {
+		bigEndowment = endowment.ToBig()
+	}
+	res, addr, returnGas, suberr := interpreter.evm.Create2(scope.Contract, input, gas,
+		bigEndowment, &salt)
 	// Push item on the stack based on the returned error.
 	if suberr != nil {
-		callContext.stack.push(interpreter.intPool.getZero())
+		stackvalue.Clear()
 	} else {
-		callContext.stack.push(interpreter.intPool.get().SetBytes(addr.Bytes()))
+		stackvalue.SetBytes(addr.Bytes())
 	}
-	callContext.contract.Gas += returnGas
-	interpreter.intPool.put(endowment, offset, size, salt)
+	scope.Stack.push(&stackvalue)
+	scope.Contract.Gas += returnGas
 
-	if suberr == errExecutionReverted {
+	if suberr == ErrExecutionReverted {
 		return res, nil
 	}
 	return nil, nil
 }
 
-func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	stack := scope.Stack
 	// Pop gas. The actual gas in interpreter.evm.callGasTemp.
-	interpreter.intPool.putOne(callContext.stack.pop())
+	// We can use this as a temporary value
+	temp := stack.pop()
 	gas := interpreter.evm.callGasTemp
 	// Pop other call parameters.
-	addr, value, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop()
-	toAddr := common.BigToAddress(addr)
-	value = math.U256(value)
+	addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
+	toAddr := common.Address(addr.Bytes20())
 	// Get the arguments from the memory.
-	args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64())
+	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
 
-	if value.Sign() != 0 {
+	var bigVal = big0
+	//TODO: use uint256.Int instead of converting with toBig()
+	// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
+	// but it would make more sense to extend the usage of uint256.Int
+	if !value.IsZero() {
 		gas += params.CallStipend
+		bigVal = value.ToBig()
 	}
-	ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value)
+
+	ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
+
 	if err != nil {
-		callContext.stack.push(interpreter.intPool.getZero())
+		temp.Clear()
 	} else {
-		callContext.stack.push(interpreter.intPool.get().SetUint64(1))
+		temp.SetOne()
 	}
-	if err == nil || err == errExecutionReverted {
-		callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
+	stack.push(&temp)
+	if err == nil || err == ErrExecutionReverted {
+		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
 	}
-	callContext.contract.Gas += returnGas
+	scope.Contract.Gas += returnGas
 
-	interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize)
 	return ret, nil
 }
 
-func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
-	interpreter.intPool.putOne(callContext.stack.pop())
+	stack := scope.Stack
+	// We use it as a temporary value
+	temp := stack.pop()
 	gas := interpreter.evm.callGasTemp
 	// Pop other call parameters.
-	addr, value, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop()
-	toAddr := common.BigToAddress(addr)
-	value = math.U256(value)
+	addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
+	toAddr := common.Address(addr.Bytes20())
 	// Get arguments from the memory.
-	args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64())
+	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
 
-	if value.Sign() != 0 {
+	//TODO: use uint256.Int instead of converting with toBig()
+	var bigVal = big0
+	if !value.IsZero() {
 		gas += params.CallStipend
+		bigVal = value.ToBig()
 	}
-	ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value)
+
+	ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, bigVal)
 	if err != nil {
-		callContext.stack.push(interpreter.intPool.getZero())
+		temp.Clear()
 	} else {
-		callContext.stack.push(interpreter.intPool.get().SetUint64(1))
+		temp.SetOne()
 	}
-	if err == nil || err == errExecutionReverted {
-		callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
+	stack.push(&temp)
+	if err == nil || err == ErrExecutionReverted {
+		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
 	}
-	callContext.contract.Gas += returnGas
+	scope.Contract.Gas += returnGas
 
-	interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize)
 	return ret, nil
 }
 
-func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	stack := scope.Stack
 	// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
-	interpreter.intPool.putOne(callContext.stack.pop())
+	// We use it as a temporary value
+	temp := stack.pop()
 	gas := interpreter.evm.callGasTemp
 	// Pop other call parameters.
-	addr, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop()
-	toAddr := common.BigToAddress(addr)
+	addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
+	toAddr := common.Address(addr.Bytes20())
 	// Get arguments from the memory.
-	args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64())
+	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
 
-	ret, returnGas, err := interpreter.evm.DelegateCall(callContext.contract, toAddr, args, gas)
+	ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas)
 	if err != nil {
-		callContext.stack.push(interpreter.intPool.getZero())
+		temp.Clear()
 	} else {
-		callContext.stack.push(interpreter.intPool.get().SetUint64(1))
+		temp.SetOne()
 	}
-	if err == nil || err == errExecutionReverted {
-		callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
+	stack.push(&temp)
+	if err == nil || err == ErrExecutionReverted {
+		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
 	}
-	callContext.contract.Gas += returnGas
+	scope.Contract.Gas += returnGas
 
-	interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize)
 	return ret, nil
 }
 
-func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
-	interpreter.intPool.putOne(callContext.stack.pop())
+	stack := scope.Stack
+	// We use it as a temporary value
+	temp := stack.pop()
 	gas := interpreter.evm.callGasTemp
 	// Pop other call parameters.
-	addr, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop()
-	toAddr := common.BigToAddress(addr)
+	addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
+	toAddr := common.Address(addr.Bytes20())
 	// Get arguments from the memory.
-	args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64())
+	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
 
-	ret, returnGas, err := interpreter.evm.StaticCall(callContext.contract, toAddr, args, gas)
+	ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas)
 	if err != nil {
-		callContext.stack.push(interpreter.intPool.getZero())
+		temp.Clear()
 	} else {
-		callContext.stack.push(interpreter.intPool.get().SetUint64(1))
+		temp.SetOne()
 	}
-	if err == nil || err == errExecutionReverted {
-		callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
+	stack.push(&temp)
+	if err == nil || err == ErrExecutionReverted {
+		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
 	}
-	callContext.contract.Gas += returnGas
+	scope.Contract.Gas += returnGas
 
-	interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize)
 	return ret, nil
 }
 
-func opReturn(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	offset, size := callContext.stack.pop(), callContext.stack.pop()
-	ret := callContext.memory.GetPtr(offset.Int64(), size.Int64())
+func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	offset, size := scope.Stack.pop(), scope.Stack.pop()
+	ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
 
-	interpreter.intPool.put(offset, size)
 	return ret, nil
 }
 
-func opRevert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	offset, size := callContext.stack.pop(), callContext.stack.pop()
-	ret := callContext.memory.GetPtr(offset.Int64(), size.Int64())
+func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	offset, size := scope.Stack.pop(), scope.Stack.pop()
+	ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
 
-	interpreter.intPool.put(offset, size)
 	return ret, nil
 }
 
-func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	return nil, nil
 }
 
-func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-	balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address())
-	interpreter.evm.StateDB.AddBalance(common.BigToAddress(callContext.stack.pop()), balance)
-
-	interpreter.evm.StateDB.Suicide(callContext.contract.Address())
+func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+	beneficiary := scope.Stack.pop()
+	balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
+	interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
+	interpreter.evm.StateDB.Suicide(scope.Contract.Address())
 	return nil, nil
 }
 
@@ -886,47 +794,48 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
 
 // make log instruction function
 func makeLog(size int) executionFunc {
-	return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+	return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 		topics := make([]common.Hash, size)
-		mStart, mSize := callContext.stack.pop(), callContext.stack.pop()
+		stack := scope.Stack
+		mStart, mSize := stack.pop(), stack.pop()
 		for i := 0; i < size; i++ {
-			topics[i] = common.BigToHash(callContext.stack.pop())
+			addr := stack.pop()
+			topics[i] = addr.Bytes32()
 		}
 
-		d := callContext.memory.GetCopy(mStart.Int64(), mSize.Int64())
+		d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
 		interpreter.evm.StateDB.AddLog(&types.Log{
-			Address: callContext.contract.Address(),
+			Address: scope.Contract.Address(),
 			Topics:  topics,
 			Data:    d,
 			// This is a non-consensus field, but assigned here because
 			// core/state doesn't know the current block number.
-			BlockNumber: interpreter.evm.BlockNumber.Uint64(),
+			BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(),
 		})
 
-		interpreter.intPool.put(mStart, mSize)
 		return nil, nil
 	}
 }
 
 // opPush1 is a specialized version of pushN
-func opPush1(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
+func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
 	var (
-		codeLen = uint64(len(callContext.contract.Code))
-		integer = interpreter.intPool.get()
+		codeLen = uint64(len(scope.Contract.Code))
+		integer = new(uint256.Int)
 	)
 	*pc += 1
 	if *pc < codeLen {
-		callContext.stack.push(integer.SetUint64(uint64(callContext.contract.Code[*pc])))
+		scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
 	} else {
-		callContext.stack.push(integer.SetUint64(0))
+		scope.Stack.push(integer.Clear())
 	}
 	return nil, nil
 }
 
 // make push instruction function
 func makePush(size uint64, pushByteSize int) executionFunc {
-	return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-		codeLen := len(callContext.contract.Code)
+	return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+		codeLen := len(scope.Contract.Code)
 
 		startMin := codeLen
 		if int(*pc+1) < startMin {
@@ -938,8 +847,9 @@ func makePush(size uint64, pushByteSize int) executionFunc {
 			endMin = startMin + pushByteSize
 		}
 
-		integer := interpreter.intPool.get()
-		callContext.stack.push(integer.SetBytes(common.RightPadBytes(callContext.contract.Code[startMin:endMin], pushByteSize)))
+		integer := new(uint256.Int)
+		scope.Stack.push(integer.SetBytes(common.RightPadBytes(
+			scope.Contract.Code[startMin:endMin], pushByteSize)))
 
 		*pc += size
 		return nil, nil
@@ -948,8 +858,8 @@ func makePush(size uint64, pushByteSize int) executionFunc {
 
 // make dup instruction function
 func makeDup(size int64) executionFunc {
-	return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-		callContext.stack.dup(interpreter.intPool, int(size))
+	return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+		scope.Stack.dup(int(size))
 		return nil, nil
 	}
 }
@@ -958,8 +868,8 @@ func makeDup(size int64) executionFunc {
 func makeSwap(size int64) executionFunc {
 	// switch n + 1 otherwise n would be swapped with n
 	size++
-	return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
-		callContext.stack.swap(int(size))
+	return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
+		scope.Stack.swap(int(size))
 		return nil, nil
 	}
 }
diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go
index d21dbf1423..d17ccfab89 100644
--- a/core/vm/instructions_test.go
+++ b/core/vm/instructions_test.go
@@ -21,12 +21,12 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
-	"math/big"
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/holiman/uint256"
 )
 
 type TwoOperandTestcase struct {
@@ -40,6 +40,7 @@ type twoOperandParams struct {
 	y string
 }
 
+var alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
 var commonParams []*twoOperandParams
 var twoOpMethods map[string]executionFunc
 
@@ -92,47 +93,28 @@ func init() {
 func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFunc, name string) {
 
 	var (
-		env            = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
+		env            = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
 		stack          = newstack()
 		pc             = uint64(0)
 		evmInterpreter = env.interpreter.(*EVMInterpreter)
 	)
-	// Stuff a couple of nonzero bigints into pool, to ensure that ops do not rely on pooled integers to be zero
-	evmInterpreter.intPool = poolOfIntPools.get()
-	evmInterpreter.intPool.put(big.NewInt(-1337))
-	evmInterpreter.intPool.put(big.NewInt(-1337))
-	evmInterpreter.intPool.put(big.NewInt(-1337))
 
 	for i, test := range tests {
-		x := new(big.Int).SetBytes(common.Hex2Bytes(test.X))
-		y := new(big.Int).SetBytes(common.Hex2Bytes(test.Y))
-		expected := new(big.Int).SetBytes(common.Hex2Bytes(test.Expected))
+		x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.X))
+		y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Y))
+		expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
 		stack.push(x)
 		stack.push(y)
-		opFn(&pc, evmInterpreter, &callCtx{nil, stack, nil})
+		opFn(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
+		if len(stack.data) != 1 {
+			t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
+		}
 		actual := stack.pop()
 
 		if actual.Cmp(expected) != 0 {
 			t.Errorf("Testcase %v %d, %v(%x, %x): expected  %x, got %x", name, i, name, x, y, expected, actual)
 		}
-		// Check pool usage
-		// 1.pool is not allowed to contain anything on the stack
-		// 2.pool is not allowed to contain the same pointers twice
-		if evmInterpreter.intPool.pool.len() > 0 {
-
-			poolvals := make(map[*big.Int]struct{})
-			poolvals[actual] = struct{}{}
-
-			for evmInterpreter.intPool.pool.len() > 0 {
-				key := evmInterpreter.intPool.get()
-				if _, exist := poolvals[key]; exist {
-					t.Errorf("Testcase %v %d, pool contains double-entry", name, i)
-				}
-				poolvals[key] = struct{}{}
-			}
-		}
 	}
-	poolOfIntPools.put(evmInterpreter.intPool)
 }
 
 func TestByteOp(t *testing.T) {
@@ -208,22 +190,59 @@ func TestSAR(t *testing.T) {
 	testTwoOperandOp(t, tests, opSAR, "sar")
 }
 
+func TestAddMod(t *testing.T) {
+	var (
+		env            = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
+		stack          = newstack()
+		evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
+		pc             = uint64(0)
+	)
+	tests := []struct {
+		x        string
+		y        string
+		z        string
+		expected string
+	}{
+		{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+			"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
+			"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+			"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
+		},
+	}
+	// x + y = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd
+	// in 256 bit repr, fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd
+
+	for i, test := range tests {
+		x := new(uint256.Int).SetBytes(common.Hex2Bytes(test.x))
+		y := new(uint256.Int).SetBytes(common.Hex2Bytes(test.y))
+		z := new(uint256.Int).SetBytes(common.Hex2Bytes(test.z))
+		expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.expected))
+		stack.push(z)
+		stack.push(y)
+		stack.push(x)
+		opAddmod(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
+		actual := stack.pop()
+		if actual.Cmp(expected) != 0 {
+			t.Errorf("Testcase %d, expected  %x, got %x", i, expected, actual)
+		}
+	}
+}
+
 // getResult is a convenience function to generate the expected values
 func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcase {
 	var (
-		env         = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
+		env         = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
 		stack       = newstack()
 		pc          = uint64(0)
 		interpreter = env.interpreter.(*EVMInterpreter)
 	)
-	interpreter.intPool = poolOfIntPools.get()
 	result := make([]TwoOperandTestcase, len(args))
 	for i, param := range args {
-		x := new(big.Int).SetBytes(common.Hex2Bytes(param.x))
-		y := new(big.Int).SetBytes(common.Hex2Bytes(param.y))
+		x := new(uint256.Int).SetBytes(common.Hex2Bytes(param.x))
+		y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
 		stack.push(x)
 		stack.push(y)
-		opFn(&pc, interpreter, &callCtx{nil, stack, nil})
+		opFn(&pc, interpreter, &ScopeContext{nil, stack, nil})
 		actual := stack.pop()
 		result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
 	}
@@ -262,13 +281,12 @@ func TestJsonTestcases(t *testing.T) {
 
 func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
 	var (
-		env            = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
+		env            = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
 		stack          = newstack()
 		evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
 	)
 
 	env.interpreter = evmInterpreter
-	evmInterpreter.intPool = poolOfIntPools.get()
 	// convert args
 	byteArgs := make([][]byte, len(args))
 	for i, arg := range args {
@@ -278,13 +296,13 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
 	bench.ResetTimer()
 	for i := 0; i < bench.N; i++ {
 		for _, arg := range byteArgs {
-			a := new(big.Int).SetBytes(arg)
+			a := new(uint256.Int)
+			a.SetBytes(arg)
 			stack.push(a)
 		}
-		op(&pc, evmInterpreter, &callCtx{nil, stack, nil})
+		op(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
 		stack.pop()
 	}
-	poolOfIntPools.put(evmInterpreter.intPool)
 }
 
 func BenchmarkOpAdd64(b *testing.B) {
@@ -330,8 +348,8 @@ func BenchmarkOpSub256(b *testing.B) {
 }
 
 func BenchmarkOpMul(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opMul, x, y)
 }
@@ -362,64 +380,64 @@ func BenchmarkOpSdiv(b *testing.B) {
 }
 
 func BenchmarkOpMod(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opMod, x, y)
 }
 
 func BenchmarkOpSmod(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opSmod, x, y)
 }
 
 func BenchmarkOpExp(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opExp, x, y)
 }
 
 func BenchmarkOpSignExtend(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opSignExtend, x, y)
 }
 
 func BenchmarkOpLt(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opLt, x, y)
 }
 
 func BenchmarkOpGt(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opGt, x, y)
 }
 
 func BenchmarkOpSlt(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opSlt, x, y)
 }
 
 func BenchmarkOpSgt(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opSgt, x, y)
 }
 
 func BenchmarkOpEq(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opEq, x, y)
 }
@@ -429,45 +447,45 @@ func BenchmarkOpEq2(b *testing.B) {
 	opBenchmark(b, opEq, x, y)
 }
 func BenchmarkOpAnd(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opAnd, x, y)
 }
 
 func BenchmarkOpOr(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opOr, x, y)
 }
 
 func BenchmarkOpXor(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opXor, x, y)
 }
 
 func BenchmarkOpByte(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
 
 	opBenchmark(b, opByte, x, y)
 }
 
 func BenchmarkOpAddmod(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	z := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
+	z := alphabetSoup
 
 	opBenchmark(b, opAddmod, x, y, z)
 }
 
 func BenchmarkOpMulmod(b *testing.B) {
-	x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
-	z := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
+	x := alphabetSoup
+	y := alphabetSoup
+	z := alphabetSoup
 
 	opBenchmark(b, opMulmod, x, y, z)
 }
@@ -497,72 +515,66 @@ func BenchmarkOpIsZero(b *testing.B) {
 
 func TestOpMstore(t *testing.T) {
 	var (
-		env            = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
+		env            = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
 		stack          = newstack()
 		mem            = NewMemory()
 		evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
 	)
 
 	env.interpreter = evmInterpreter
-	evmInterpreter.intPool = poolOfIntPools.get()
 	mem.Resize(64)
 	pc := uint64(0)
 	v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
-	stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0))
-	opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil})
+	stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int))
+	opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
 	if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
 		t.Fatalf("Mstore fail, got %v, expected %v", got, v)
 	}
-	stack.pushN(big.NewInt(0x1), big.NewInt(0))
-	opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil})
+	stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int))
+	opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
 	if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
 		t.Fatalf("Mstore failed to overwrite previous value")
 	}
-	poolOfIntPools.put(evmInterpreter.intPool)
 }
 
 func BenchmarkOpMstore(bench *testing.B) {
 	var (
-		env            = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
+		env            = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
 		stack          = newstack()
 		mem            = NewMemory()
 		evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
 	)
 
 	env.interpreter = evmInterpreter
-	evmInterpreter.intPool = poolOfIntPools.get()
 	mem.Resize(64)
 	pc := uint64(0)
-	memStart := big.NewInt(0)
-	value := big.NewInt(0x1337)
+	memStart := new(uint256.Int)
+	value := new(uint256.Int).SetUint64(0x1337)
 
 	bench.ResetTimer()
 	for i := 0; i < bench.N; i++ {
-		stack.pushN(value, memStart)
-		opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil})
+		stack.pushN(*value, *memStart)
+		opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
 	}
-	poolOfIntPools.put(evmInterpreter.intPool)
 }
 
 func BenchmarkOpSHA3(bench *testing.B) {
 	var (
-		env            = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
+		env            = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
 		stack          = newstack()
 		mem            = NewMemory()
 		evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
 	)
 	env.interpreter = evmInterpreter
-	evmInterpreter.intPool = poolOfIntPools.get()
 	mem.Resize(32)
 	pc := uint64(0)
-	start := big.NewInt(0)
+	start := uint256.NewInt()
 
 	bench.ResetTimer()
 	for i := 0; i < bench.N; i++ {
-		stack.pushN(big.NewInt(32), start)
-		opSha3(&pc, evmInterpreter, &callCtx{mem, stack, nil})
+		stack.pushN(*uint256.NewInt().SetUint64(32), *start)
+		opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
 	}
-	poolOfIntPools.put(evmInterpreter.intPool)
 }
 
 func TestCreate2Addreses(t *testing.T) {
@@ -636,6 +648,5 @@ func TestCreate2Addreses(t *testing.T) {
 		if !bytes.Equal(expected.Bytes(), address.Bytes()) {
 			t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String())
 		}
-
 	}
 }
diff --git a/core/vm/interface.go b/core/vm/interface.go
index dd401466ad..ad9b05d666 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -57,6 +57,16 @@ type StateDB interface {
 	// is defined according to EIP161 (balance = nonce = code = 0).
 	Empty(common.Address) bool
 
+	PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
+	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)
 	Snapshot() int
 
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 949a39dacf..08da68ea2a 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -17,7 +17,6 @@
 package vm
 
 import (
-	"fmt"
 	"hash"
 	"sync/atomic"
 
@@ -33,7 +32,7 @@ type Config struct {
 	NoRecursion             bool   // Disables call, callcode, delegate call and create
 	EnablePreimageRecording bool   // Enables recording of SHA3/keccak preimages
 
-	JumpTable [256]operation // EVM instruction table, automatically populated if unset
+	JumpTable [256]*operation // EVM instruction table, automatically populated if unset
 
 	EWASMInterpreter string // External EWASM interpreter options
 	EVMInterpreter   string // External EVM interpreter options
@@ -63,12 +62,12 @@ type Interpreter interface {
 	CanRun([]byte) bool
 }
 
-// callCtx contains the things that are per-call, such as stack and memory,
+// ScopeContext contains the things that are per-call, such as stack and memory,
 // but not transients like pc and gas
-type callCtx struct {
-	memory   *Memory
-	stack    *Stack
-	contract *Contract
+type ScopeContext struct {
+	Memory   *Memory
+	Stack    *Stack
+	Contract *Contract
 }
 
 // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
@@ -84,8 +83,6 @@ type EVMInterpreter struct {
 	evm *EVM
 	cfg Config
 
-	intPool *intPool
-
 	hasher    keccakState // Keccak256 hasher instance shared across opcodes
 	hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes
 
@@ -98,9 +95,11 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
 	// We use the STOP instruction whether to see
 	// the jump table was initialised. If it was not
 	// we'll set the default jump table.
-	if !cfg.JumpTable[STOP].valid {
+	if cfg.JumpTable[STOP] == nil {
 		var jt JumpTable
 		switch {
+		case evm.chainRules.IsBerlin:
+			jt = berlinInstructionSet
 		case evm.chainRules.IsIstanbul:
 			jt = istanbulInstructionSet
 		case evm.chainRules.IsConstantinople:
@@ -137,22 +136,15 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
 //
 // It's important to note that any errors returned by the interpreter should be
 // considered a revert-and-consume-all-gas operation except for
-// errExecutionReverted which means revert-and-keep-gas-left.
+// ErrExecutionReverted which means revert-and-keep-gas-left.
 func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
-	if in.intPool == nil {
-		in.intPool = poolOfIntPools.get()
-		defer func() {
-			poolOfIntPools.put(in.intPool)
-			in.intPool = nil
-		}()
-	}
 
 	// Increment the call depth which is restricted to 1024
 	in.evm.depth++
 	defer func() { in.evm.depth-- }()
 
 	// Make sure the readOnly is only set if we aren't in readOnly yet.
-	// This makes also sure that the readOnly flag isn't removed for child calls.
+	// This also makes sure that the readOnly flag isn't removed for child calls.
 	if readOnly && !in.readOnly {
 		in.readOnly = true
 		defer func() { in.readOnly = false }()
@@ -172,10 +164,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		op          OpCode        // current opcode
 		mem         = NewMemory() // bound memory
 		stack       = newstack()  // local stack
-		callContext = &callCtx{
-			memory:   mem,
-			stack:    stack,
-			contract: contract,
+		callContext = &ScopeContext{
+			Memory:   mem,
+			Stack:    stack,
+			Contract: contract,
 		}
 		// For optimisation reason we're using uint64 as the program counter.
 		// It's theoretically possible to go above 2^64. The YP defines the PC
@@ -188,18 +180,21 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		logged  bool   // deferred Tracer should ignore already logged steps
 		res     []byte // result of the opcode execution function
 	)
+	// Don't move this deferrred function, it's placed before the capturestate-deferred method,
+	// so that it get's executed _after_: the capturestate needs the stacks before
+	// they are returned to the pools
+	defer func() {
+		returnStack(stack)
+	}()
 	contract.Input = input
 
-	// Reclaim the stack as an int pool when the execution stops
-	defer func() { in.intPool.put(stack.data...) }()
-
 	if in.cfg.Debug {
 		defer func() {
 			if err != nil {
 				if !logged {
-					in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
+					in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
 				} else {
-					in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
+					in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
 				}
 			}
 		}()
@@ -223,16 +218,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		// enough stack items available to perform the operation.
 		op = contract.GetOp(pc)
 		operation := in.cfg.JumpTable[op]
-		if !operation.valid {
-			return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
+		if operation == nil {
+			return nil, &ErrInvalidOpCode{opcode: op}
 		}
 		// Validate stack
 		if sLen := stack.len(); sLen < operation.minStack {
-			return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack)
+			return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
 		} else if sLen > operation.maxStack {
-			return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack)
+			return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
 		}
-		// If the operation is valid, enforce and write restrictions
+		// If the operation is valid, enforce write restrictions
 		if in.readOnly && in.evm.chainRules.IsByzantium {
 			// If the interpreter is operating in readonly mode, make sure no
 			// state-modifying operation is performed. The 3rd stack item
@@ -240,7 +235,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 			// account to the others means the state is modified and should also
 			// return with an error.
 			if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
-				return nil, errWriteProtection
+				return nil, ErrWriteProtection
 			}
 		}
 		// Static portion of gas
@@ -257,12 +252,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		if operation.memorySize != nil {
 			memSize, overflow := operation.memorySize(stack)
 			if overflow {
-				return nil, errGasUintOverflow
+				return nil, ErrGasUintOverflow
 			}
 			// memory is expanded in words of 32 bytes. Gas
 			// is also calculated in words.
 			if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
-				return nil, errGasUintOverflow
+				return nil, ErrGasUintOverflow
 			}
 		}
 		// Dynamic portion of gas
@@ -281,17 +276,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		}
 
 		if in.cfg.Debug {
-			in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
+			in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
 			logged = true
 		}
 
 		// execute the operation
 		res, err = operation.execute(&pc, in, callContext)
-		// verifyPool is a build flag. Pool verification makes sure the integrity
-		// of the integer pool by comparing values to a default value.
-		if verifyPool {
-			verifyIntegerPool(in.intPool)
-		}
 		// if the operation clears the return data (e.g. it has returning data)
 		// set the last return to the result of the operation.
 		if operation.returns {
@@ -302,7 +292,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
 		case err != nil:
 			return nil, err
 		case operation.reverts:
-			return res, errExecutionReverted
+			return res, ErrExecutionReverted
 		case operation.halts:
 			return res, nil
 		case !operation.jumps:
diff --git a/core/vm/intpool.go b/core/vm/intpool.go
deleted file mode 100644
index eed074b073..0000000000
--- a/core/vm/intpool.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2017 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 .
-
-package vm
-
-import (
-	"math/big"
-	"sync"
-)
-
-var checkVal = big.NewInt(-42)
-
-const poolLimit = 256
-
-// intPool is a pool of big integers that
-// can be reused for all big.Int operations.
-type intPool struct {
-	pool *Stack
-}
-
-func newIntPool() *intPool {
-	return &intPool{pool: newstack()}
-}
-
-// get retrieves a big int from the pool, allocating one if the pool is empty.
-// Note, the returned int's value is arbitrary and will not be zeroed!
-func (p *intPool) get() *big.Int {
-	if p.pool.len() > 0 {
-		return p.pool.pop()
-	}
-	return new(big.Int)
-}
-
-// getZero retrieves a big int from the pool, setting it to zero or allocating
-// a new one if the pool is empty.
-func (p *intPool) getZero() *big.Int {
-	if p.pool.len() > 0 {
-		return p.pool.pop().SetUint64(0)
-	}
-	return new(big.Int)
-}
-
-// putOne returns an allocated big int to the pool to be later reused by get calls.
-// Note, the values as saved as is; neither put nor get zeroes the ints out!
-// As opposed to 'put' with variadic args, this method becomes inlined by the
-// go compiler
-func (p *intPool) putOne(i *big.Int) {
-	if len(p.pool.data) > poolLimit {
-		return
-	}
-	p.pool.push(i)
-}
-
-// put returns an allocated big int to the pool to be later reused by get calls.
-// Note, the values as saved as is; neither put nor get zeroes the ints out!
-func (p *intPool) put(is ...*big.Int) {
-	if len(p.pool.data) > poolLimit {
-		return
-	}
-	for _, i := range is {
-		// verifyPool is a build flag. Pool verification makes sure the integrity
-		// of the integer pool by comparing values to a default value.
-		if verifyPool {
-			i.Set(checkVal)
-		}
-		p.pool.push(i)
-	}
-}
-
-// The intPool pool's default capacity
-const poolDefaultCap = 25
-
-// intPoolPool manages a pool of intPools.
-type intPoolPool struct {
-	pools []*intPool
-	lock  sync.Mutex
-}
-
-var poolOfIntPools = &intPoolPool{
-	pools: make([]*intPool, 0, poolDefaultCap),
-}
-
-// get is looking for an available pool to return.
-func (ipp *intPoolPool) get() *intPool {
-	ipp.lock.Lock()
-	defer ipp.lock.Unlock()
-
-	if len(poolOfIntPools.pools) > 0 {
-		ip := ipp.pools[len(ipp.pools)-1]
-		ipp.pools = ipp.pools[:len(ipp.pools)-1]
-		return ip
-	}
-	return newIntPool()
-}
-
-// put a pool that has been allocated with get.
-func (ipp *intPoolPool) put(ip *intPool) {
-	ipp.lock.Lock()
-	defer ipp.lock.Unlock()
-
-	if len(ipp.pools) < cap(ipp.pools) {
-		ipp.pools = append(ipp.pools, ip)
-	}
-}
diff --git a/core/vm/intpool_test.go b/core/vm/intpool_test.go
deleted file mode 100644
index 6c0d00f3ce..0000000000
--- a/core/vm/intpool_test.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 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 .
-
-package vm
-
-import (
-	"testing"
-)
-
-func TestIntPoolPoolGet(t *testing.T) {
-	poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
-
-	nip := poolOfIntPools.get()
-	if nip == nil {
-		t.Fatalf("Invalid pool allocation")
-	}
-}
-
-func TestIntPoolPoolPut(t *testing.T) {
-	poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
-
-	nip := poolOfIntPools.get()
-	if len(poolOfIntPools.pools) != 0 {
-		t.Fatalf("Pool got added to list when none should have been")
-	}
-
-	poolOfIntPools.put(nip)
-	if len(poolOfIntPools.pools) == 0 {
-		t.Fatalf("Pool did not get added to list when one should have been")
-	}
-}
-
-func TestIntPoolPoolReUse(t *testing.T) {
-	poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
-	nip := poolOfIntPools.get()
-	poolOfIntPools.put(nip)
-	poolOfIntPools.get()
-
-	if len(poolOfIntPools.pools) != 0 {
-		t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0)
-	}
-}
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index c1e7c88dbf..bb1800ea91 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -17,20 +17,16 @@
 package vm
 
 import (
-	"errors"
-
 	"github.com/ethereum/go-ethereum/params"
 )
 
 type (
-	executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error)
+	executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)
 	gasFunc       func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
 	// memorySizeFunc returns the required size, and whether the operation overflowed a uint64
 	memorySizeFunc func(*Stack) (size uint64, overflow bool)
 )
 
-var errGasUintOverflow = errors.New("gas uint64 overflow")
-
 type operation struct {
 	// execute is the operation function
 	execute     executionFunc
@@ -48,7 +44,6 @@ type operation struct {
 	halts   bool // indicates whether the operation should halt further execution
 	jumps   bool // indicates whether the program counter should not increment
 	writes  bool // determines whether this a state modifying operation
-	valid   bool // indication whether the retrieved operation is valid and known
 	reverts bool // determines whether the operation reverts state (implicitly halts)
 	returns bool // determines whether the operations sets the return data content
 }
@@ -61,13 +56,22 @@ var (
 	byzantiumInstructionSet        = newByzantiumInstructionSet()
 	constantinopleInstructionSet   = newConstantinopleInstructionSet()
 	istanbulInstructionSet         = newIstanbulInstructionSet()
+	berlinInstructionSet           = newBerlinInstructionSet()
 )
 
 // JumpTable contains the EVM opcodes supported at a given fork.
-type JumpTable [256]operation
+type JumpTable [256]*operation
+
+// newBerlinInstructionSet returns the frontier, homestead, byzantium,
+// contantinople, istanbul, petersburg and berlin instructions.
+func newBerlinInstructionSet() JumpTable {
+	instructionSet := newIstanbulInstructionSet()
+	enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
+	return instructionSet
+}
 
-// newIstanbulInstructionSet returns the frontier, homestead
-// byzantium, contantinople and petersburg instructions.
+// newIstanbulInstructionSet returns the frontier, homestead, byzantium,
+// contantinople, istanbul and petersburg instructions.
 func newIstanbulInstructionSet() JumpTable {
 	instructionSet := newConstantinopleInstructionSet()
 
@@ -78,46 +82,41 @@ func newIstanbulInstructionSet() JumpTable {
 	return instructionSet
 }
 
-// newConstantinopleInstructionSet returns the frontier, homestead
+// newConstantinopleInstructionSet returns the frontier, homestead,
 // byzantium and contantinople instructions.
 func newConstantinopleInstructionSet() JumpTable {
 	instructionSet := newByzantiumInstructionSet()
-	instructionSet[SHL] = operation{
+	instructionSet[SHL] = &operation{
 		execute:     opSHL,
 		constantGas: GasFastestStep,
 		minStack:    minStack(2, 1),
 		maxStack:    maxStack(2, 1),
-		valid:       true,
 	}
-	instructionSet[SHR] = operation{
+	instructionSet[SHR] = &operation{
 		execute:     opSHR,
 		constantGas: GasFastestStep,
 		minStack:    minStack(2, 1),
 		maxStack:    maxStack(2, 1),
-		valid:       true,
 	}
-	instructionSet[SAR] = operation{
+	instructionSet[SAR] = &operation{
 		execute:     opSAR,
 		constantGas: GasFastestStep,
 		minStack:    minStack(2, 1),
 		maxStack:    maxStack(2, 1),
-		valid:       true,
 	}
-	instructionSet[EXTCODEHASH] = operation{
+	instructionSet[EXTCODEHASH] = &operation{
 		execute:     opExtCodeHash,
 		constantGas: params.ExtcodeHashGasConstantinople,
 		minStack:    minStack(1, 1),
 		maxStack:    maxStack(1, 1),
-		valid:       true,
 	}
-	instructionSet[CREATE2] = operation{
+	instructionSet[CREATE2] = &operation{
 		execute:     opCreate2,
 		constantGas: params.Create2Gas,
 		dynamicGas:  gasCreate2,
 		minStack:    minStack(4, 1),
 		maxStack:    maxStack(4, 1),
 		memorySize:  memoryCreate2,
-		valid:       true,
 		writes:      true,
 		returns:     true,
 	}
@@ -128,39 +127,35 @@ func newConstantinopleInstructionSet() JumpTable {
 // byzantium instructions.
 func newByzantiumInstructionSet() JumpTable {
 	instructionSet := newSpuriousDragonInstructionSet()
-	instructionSet[STATICCALL] = operation{
+	instructionSet[STATICCALL] = &operation{
 		execute:     opStaticCall,
 		constantGas: params.CallGasEIP150,
 		dynamicGas:  gasStaticCall,
 		minStack:    minStack(6, 1),
 		maxStack:    maxStack(6, 1),
 		memorySize:  memoryStaticCall,
-		valid:       true,
 		returns:     true,
 	}
-	instructionSet[RETURNDATASIZE] = operation{
+	instructionSet[RETURNDATASIZE] = &operation{
 		execute:     opReturnDataSize,
 		constantGas: GasQuickStep,
 		minStack:    minStack(0, 1),
 		maxStack:    maxStack(0, 1),
-		valid:       true,
 	}
-	instructionSet[RETURNDATACOPY] = operation{
+	instructionSet[RETURNDATACOPY] = &operation{
 		execute:     opReturnDataCopy,
 		constantGas: GasFastestStep,
 		dynamicGas:  gasReturnDataCopy,
 		minStack:    minStack(3, 0),
 		maxStack:    maxStack(3, 0),
 		memorySize:  memoryReturnDataCopy,
-		valid:       true,
 	}
-	instructionSet[REVERT] = operation{
+	instructionSet[REVERT] = &operation{
 		execute:    opRevert,
 		dynamicGas: gasRevert,
 		minStack:   minStack(2, 0),
 		maxStack:   maxStack(2, 0),
 		memorySize: memoryRevert,
-		valid:      true,
 		reverts:    true,
 		returns:    true,
 	}
@@ -192,14 +187,13 @@ func newTangerineWhistleInstructionSet() JumpTable {
 // instructions that can be executed during the homestead phase.
 func newHomesteadInstructionSet() JumpTable {
 	instructionSet := newFrontierInstructionSet()
-	instructionSet[DELEGATECALL] = operation{
+	instructionSet[DELEGATECALL] = &operation{
 		execute:     opDelegateCall,
 		dynamicGas:  gasDelegateCall,
 		constantGas: params.CallGasFrontier,
 		minStack:    minStack(6, 1),
 		maxStack:    maxStack(6, 1),
 		memorySize:  memoryDelegateCall,
-		valid:       true,
 		returns:     true,
 	}
 	return instructionSet
@@ -215,161 +209,138 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(0, 0),
 			maxStack:    maxStack(0, 0),
 			halts:       true,
-			valid:       true,
 		},
 		ADD: {
 			execute:     opAdd,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		MUL: {
 			execute:     opMul,
 			constantGas: GasFastStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		SUB: {
 			execute:     opSub,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		DIV: {
 			execute:     opDiv,
 			constantGas: GasFastStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		SDIV: {
 			execute:     opSdiv,
 			constantGas: GasFastStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		MOD: {
 			execute:     opMod,
 			constantGas: GasFastStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		SMOD: {
 			execute:     opSmod,
 			constantGas: GasFastStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		ADDMOD: {
 			execute:     opAddmod,
 			constantGas: GasMidStep,
 			minStack:    minStack(3, 1),
 			maxStack:    maxStack(3, 1),
-			valid:       true,
 		},
 		MULMOD: {
 			execute:     opMulmod,
 			constantGas: GasMidStep,
 			minStack:    minStack(3, 1),
 			maxStack:    maxStack(3, 1),
-			valid:       true,
 		},
 		EXP: {
 			execute:    opExp,
 			dynamicGas: gasExpFrontier,
 			minStack:   minStack(2, 1),
 			maxStack:   maxStack(2, 1),
-			valid:      true,
 		},
 		SIGNEXTEND: {
 			execute:     opSignExtend,
 			constantGas: GasFastStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		LT: {
 			execute:     opLt,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		GT: {
 			execute:     opGt,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		SLT: {
 			execute:     opSlt,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		SGT: {
 			execute:     opSgt,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		EQ: {
 			execute:     opEq,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		ISZERO: {
 			execute:     opIszero,
 			constantGas: GasFastestStep,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		AND: {
 			execute:     opAnd,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		XOR: {
 			execute:     opXor,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		OR: {
 			execute:     opOr,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		NOT: {
 			execute:     opNot,
 			constantGas: GasFastestStep,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		BYTE: {
 			execute:     opByte,
 			constantGas: GasFastestStep,
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
-			valid:       true,
 		},
 		SHA3: {
 			execute:     opSha3,
@@ -378,56 +349,48 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(2, 1),
 			maxStack:    maxStack(2, 1),
 			memorySize:  memorySha3,
-			valid:       true,
 		},
 		ADDRESS: {
 			execute:     opAddress,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		BALANCE: {
 			execute:     opBalance,
 			constantGas: params.BalanceGasFrontier,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		ORIGIN: {
 			execute:     opOrigin,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		CALLER: {
 			execute:     opCaller,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		CALLVALUE: {
 			execute:     opCallValue,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		CALLDATALOAD: {
 			execute:     opCallDataLoad,
 			constantGas: GasFastestStep,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		CALLDATASIZE: {
 			execute:     opCallDataSize,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		CALLDATACOPY: {
 			execute:     opCallDataCopy,
@@ -436,14 +399,12 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(3, 0),
 			maxStack:    maxStack(3, 0),
 			memorySize:  memoryCallDataCopy,
-			valid:       true,
 		},
 		CODESIZE: {
 			execute:     opCodeSize,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		CODECOPY: {
 			execute:     opCodeCopy,
@@ -452,21 +413,18 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(3, 0),
 			maxStack:    maxStack(3, 0),
 			memorySize:  memoryCodeCopy,
-			valid:       true,
 		},
 		GASPRICE: {
 			execute:     opGasprice,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		EXTCODESIZE: {
 			execute:     opExtCodeSize,
 			constantGas: params.ExtcodeSizeGasFrontier,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		EXTCODECOPY: {
 			execute:     opExtCodeCopy,
@@ -475,56 +433,48 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(4, 0),
 			maxStack:    maxStack(4, 0),
 			memorySize:  memoryExtCodeCopy,
-			valid:       true,
 		},
 		BLOCKHASH: {
 			execute:     opBlockhash,
 			constantGas: GasExtStep,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		COINBASE: {
 			execute:     opCoinbase,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		TIMESTAMP: {
 			execute:     opTimestamp,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		NUMBER: {
 			execute:     opNumber,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		DIFFICULTY: {
 			execute:     opDifficulty,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		GASLIMIT: {
 			execute:     opGasLimit,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		POP: {
 			execute:     opPop,
 			constantGas: GasQuickStep,
 			minStack:    minStack(1, 0),
 			maxStack:    maxStack(1, 0),
-			valid:       true,
 		},
 		MLOAD: {
 			execute:     opMload,
@@ -533,7 +483,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
 			memorySize:  memoryMLoad,
-			valid:       true,
 		},
 		MSTORE: {
 			execute:     opMstore,
@@ -542,7 +491,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(2, 0),
 			maxStack:    maxStack(2, 0),
 			memorySize:  memoryMStore,
-			valid:       true,
 		},
 		MSTORE8: {
 			execute:     opMstore8,
@@ -551,22 +499,18 @@ func newFrontierInstructionSet() JumpTable {
 			memorySize:  memoryMStore8,
 			minStack:    minStack(2, 0),
 			maxStack:    maxStack(2, 0),
-
-			valid: true,
 		},
 		SLOAD: {
 			execute:     opSload,
 			constantGas: params.SloadGasFrontier,
 			minStack:    minStack(1, 1),
 			maxStack:    maxStack(1, 1),
-			valid:       true,
 		},
 		SSTORE: {
 			execute:    opSstore,
 			dynamicGas: gasSStore,
 			minStack:   minStack(2, 0),
 			maxStack:   maxStack(2, 0),
-			valid:      true,
 			writes:     true,
 		},
 		JUMP: {
@@ -575,7 +519,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(1, 0),
 			maxStack:    maxStack(1, 0),
 			jumps:       true,
-			valid:       true,
 		},
 		JUMPI: {
 			execute:     opJumpi,
@@ -583,483 +526,414 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(2, 0),
 			maxStack:    maxStack(2, 0),
 			jumps:       true,
-			valid:       true,
 		},
 		PC: {
 			execute:     opPc,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		MSIZE: {
 			execute:     opMsize,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		GAS: {
 			execute:     opGas,
 			constantGas: GasQuickStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		JUMPDEST: {
 			execute:     opJumpdest,
 			constantGas: params.JumpdestGas,
 			minStack:    minStack(0, 0),
 			maxStack:    maxStack(0, 0),
-			valid:       true,
 		},
 		PUSH1: {
 			execute:     opPush1,
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH2: {
 			execute:     makePush(2, 2),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH3: {
 			execute:     makePush(3, 3),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH4: {
 			execute:     makePush(4, 4),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH5: {
 			execute:     makePush(5, 5),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH6: {
 			execute:     makePush(6, 6),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH7: {
 			execute:     makePush(7, 7),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH8: {
 			execute:     makePush(8, 8),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH9: {
 			execute:     makePush(9, 9),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH10: {
 			execute:     makePush(10, 10),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH11: {
 			execute:     makePush(11, 11),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH12: {
 			execute:     makePush(12, 12),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH13: {
 			execute:     makePush(13, 13),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH14: {
 			execute:     makePush(14, 14),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH15: {
 			execute:     makePush(15, 15),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH16: {
 			execute:     makePush(16, 16),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH17: {
 			execute:     makePush(17, 17),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH18: {
 			execute:     makePush(18, 18),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH19: {
 			execute:     makePush(19, 19),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH20: {
 			execute:     makePush(20, 20),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH21: {
 			execute:     makePush(21, 21),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH22: {
 			execute:     makePush(22, 22),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH23: {
 			execute:     makePush(23, 23),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH24: {
 			execute:     makePush(24, 24),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH25: {
 			execute:     makePush(25, 25),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH26: {
 			execute:     makePush(26, 26),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH27: {
 			execute:     makePush(27, 27),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH28: {
 			execute:     makePush(28, 28),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH29: {
 			execute:     makePush(29, 29),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH30: {
 			execute:     makePush(30, 30),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH31: {
 			execute:     makePush(31, 31),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		PUSH32: {
 			execute:     makePush(32, 32),
 			constantGas: GasFastestStep,
 			minStack:    minStack(0, 1),
 			maxStack:    maxStack(0, 1),
-			valid:       true,
 		},
 		DUP1: {
 			execute:     makeDup(1),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(1),
 			maxStack:    maxDupStack(1),
-			valid:       true,
 		},
 		DUP2: {
 			execute:     makeDup(2),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(2),
 			maxStack:    maxDupStack(2),
-			valid:       true,
 		},
 		DUP3: {
 			execute:     makeDup(3),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(3),
 			maxStack:    maxDupStack(3),
-			valid:       true,
 		},
 		DUP4: {
 			execute:     makeDup(4),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(4),
 			maxStack:    maxDupStack(4),
-			valid:       true,
 		},
 		DUP5: {
 			execute:     makeDup(5),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(5),
 			maxStack:    maxDupStack(5),
-			valid:       true,
 		},
 		DUP6: {
 			execute:     makeDup(6),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(6),
 			maxStack:    maxDupStack(6),
-			valid:       true,
 		},
 		DUP7: {
 			execute:     makeDup(7),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(7),
 			maxStack:    maxDupStack(7),
-			valid:       true,
 		},
 		DUP8: {
 			execute:     makeDup(8),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(8),
 			maxStack:    maxDupStack(8),
-			valid:       true,
 		},
 		DUP9: {
 			execute:     makeDup(9),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(9),
 			maxStack:    maxDupStack(9),
-			valid:       true,
 		},
 		DUP10: {
 			execute:     makeDup(10),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(10),
 			maxStack:    maxDupStack(10),
-			valid:       true,
 		},
 		DUP11: {
 			execute:     makeDup(11),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(11),
 			maxStack:    maxDupStack(11),
-			valid:       true,
 		},
 		DUP12: {
 			execute:     makeDup(12),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(12),
 			maxStack:    maxDupStack(12),
-			valid:       true,
 		},
 		DUP13: {
 			execute:     makeDup(13),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(13),
 			maxStack:    maxDupStack(13),
-			valid:       true,
 		},
 		DUP14: {
 			execute:     makeDup(14),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(14),
 			maxStack:    maxDupStack(14),
-			valid:       true,
 		},
 		DUP15: {
 			execute:     makeDup(15),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(15),
 			maxStack:    maxDupStack(15),
-			valid:       true,
 		},
 		DUP16: {
 			execute:     makeDup(16),
 			constantGas: GasFastestStep,
 			minStack:    minDupStack(16),
 			maxStack:    maxDupStack(16),
-			valid:       true,
 		},
 		SWAP1: {
 			execute:     makeSwap(1),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(2),
 			maxStack:    maxSwapStack(2),
-			valid:       true,
 		},
 		SWAP2: {
 			execute:     makeSwap(2),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(3),
 			maxStack:    maxSwapStack(3),
-			valid:       true,
 		},
 		SWAP3: {
 			execute:     makeSwap(3),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(4),
 			maxStack:    maxSwapStack(4),
-			valid:       true,
 		},
 		SWAP4: {
 			execute:     makeSwap(4),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(5),
 			maxStack:    maxSwapStack(5),
-			valid:       true,
 		},
 		SWAP5: {
 			execute:     makeSwap(5),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(6),
 			maxStack:    maxSwapStack(6),
-			valid:       true,
 		},
 		SWAP6: {
 			execute:     makeSwap(6),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(7),
 			maxStack:    maxSwapStack(7),
-			valid:       true,
 		},
 		SWAP7: {
 			execute:     makeSwap(7),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(8),
 			maxStack:    maxSwapStack(8),
-			valid:       true,
 		},
 		SWAP8: {
 			execute:     makeSwap(8),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(9),
 			maxStack:    maxSwapStack(9),
-			valid:       true,
 		},
 		SWAP9: {
 			execute:     makeSwap(9),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(10),
 			maxStack:    maxSwapStack(10),
-			valid:       true,
 		},
 		SWAP10: {
 			execute:     makeSwap(10),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(11),
 			maxStack:    maxSwapStack(11),
-			valid:       true,
 		},
 		SWAP11: {
 			execute:     makeSwap(11),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(12),
 			maxStack:    maxSwapStack(12),
-			valid:       true,
 		},
 		SWAP12: {
 			execute:     makeSwap(12),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(13),
 			maxStack:    maxSwapStack(13),
-			valid:       true,
 		},
 		SWAP13: {
 			execute:     makeSwap(13),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(14),
 			maxStack:    maxSwapStack(14),
-			valid:       true,
 		},
 		SWAP14: {
 			execute:     makeSwap(14),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(15),
 			maxStack:    maxSwapStack(15),
-			valid:       true,
 		},
 		SWAP15: {
 			execute:     makeSwap(15),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(16),
 			maxStack:    maxSwapStack(16),
-			valid:       true,
 		},
 		SWAP16: {
 			execute:     makeSwap(16),
 			constantGas: GasFastestStep,
 			minStack:    minSwapStack(17),
 			maxStack:    maxSwapStack(17),
-			valid:       true,
 		},
 		LOG0: {
 			execute:    makeLog(0),
@@ -1067,7 +941,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:   minStack(2, 0),
 			maxStack:   maxStack(2, 0),
 			memorySize: memoryLog,
-			valid:      true,
 			writes:     true,
 		},
 		LOG1: {
@@ -1076,7 +949,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:   minStack(3, 0),
 			maxStack:   maxStack(3, 0),
 			memorySize: memoryLog,
-			valid:      true,
 			writes:     true,
 		},
 		LOG2: {
@@ -1085,7 +957,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:   minStack(4, 0),
 			maxStack:   maxStack(4, 0),
 			memorySize: memoryLog,
-			valid:      true,
 			writes:     true,
 		},
 		LOG3: {
@@ -1094,7 +965,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:   minStack(5, 0),
 			maxStack:   maxStack(5, 0),
 			memorySize: memoryLog,
-			valid:      true,
 			writes:     true,
 		},
 		LOG4: {
@@ -1103,7 +973,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:   minStack(6, 0),
 			maxStack:   maxStack(6, 0),
 			memorySize: memoryLog,
-			valid:      true,
 			writes:     true,
 		},
 		CREATE: {
@@ -1113,7 +982,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(3, 1),
 			maxStack:    maxStack(3, 1),
 			memorySize:  memoryCreate,
-			valid:       true,
 			writes:      true,
 			returns:     true,
 		},
@@ -1124,7 +992,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(7, 1),
 			maxStack:    maxStack(7, 1),
 			memorySize:  memoryCall,
-			valid:       true,
 			returns:     true,
 		},
 		CALLCODE: {
@@ -1134,7 +1001,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:    minStack(7, 1),
 			maxStack:    maxStack(7, 1),
 			memorySize:  memoryCall,
-			valid:       true,
 			returns:     true,
 		},
 		RETURN: {
@@ -1144,7 +1010,6 @@ func newFrontierInstructionSet() JumpTable {
 			maxStack:   maxStack(2, 0),
 			memorySize: memoryReturn,
 			halts:      true,
-			valid:      true,
 		},
 		SELFDESTRUCT: {
 			execute:    opSuicide,
@@ -1152,7 +1017,6 @@ func newFrontierInstructionSet() JumpTable {
 			minStack:   minStack(1, 0),
 			maxStack:   maxStack(1, 0),
 			halts:      true,
-			valid:      true,
 			writes:     true,
 		},
 	}
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 33c58ac9da..9ccaafc772 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -21,12 +21,14 @@ import (
 	"fmt"
 	"io"
 	"math/big"
+	"strings"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/params"
 )
 
 // Storage represents a contract's storage.
@@ -38,17 +40,19 @@ func (s Storage) Copy() Storage {
 	for key, value := range s {
 		cpy[key] = value
 	}
-
 	return cpy
 }
 
 // LogConfig are the configuration options for structured logger the EVM
 type LogConfig struct {
-	DisableMemory  bool // disable memory capture
-	DisableStack   bool // disable stack capture
-	DisableStorage bool // disable storage capture
-	Debug          bool // print output during capture end
-	Limit          int  // maximum length of output, but zero means unlimited
+	DisableMemory     bool // disable memory capture
+	DisableStack      bool // disable stack capture
+	DisableStorage    bool // disable storage capture
+	DisableReturnData bool // disable return data capture
+	Debug             bool // print output during capture end
+	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
@@ -63,6 +67,7 @@ type StructLog struct {
 	Memory        []byte                      `json:"memory"`
 	MemorySize    int                         `json:"memSize"`
 	Stack         []*big.Int                  `json:"stack"`
+	ReturnData    []byte                      `json:"returnData"`
 	Storage       map[common.Hash]common.Hash `json:"-"`
 	Depth         int                         `json:"depth"`
 	RefundCounter uint64                      `json:"refund"`
@@ -75,6 +80,7 @@ type structLogMarshaling struct {
 	Gas         math.HexOrDecimal64
 	GasCost     math.HexOrDecimal64
 	Memory      hexutil.Bytes
+	ReturnData  hexutil.Bytes
 	OpName      string `json:"opName"` // adds call to OpName() in MarshalJSON
 	ErrorString string `json:"error"`  // adds call to ErrorString() in MarshalJSON
 }
@@ -98,10 +104,10 @@ func (s *StructLog) ErrorString() string {
 // Note that reference types are actual VM data structures; make copies
 // if you need to retain them beyond the current call.
 type Tracer interface {
-	CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error
-	CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
-	CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
-	CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
+	CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
+	CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
+	CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
+	CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
 }
 
 // StructLogger is an EVM state logger and implements Tracer.
@@ -112,16 +118,16 @@ type Tracer interface {
 type StructLogger struct {
 	cfg LogConfig
 
-	logs          []StructLog
-	changedValues map[common.Address]Storage
-	output        []byte
-	err           error
+	storage map[common.Address]Storage
+	logs    []StructLog
+	output  []byte
+	err     error
 }
 
 // NewStructLogger returns a new logger
 func NewStructLogger(cfg *LogConfig) *StructLogger {
 	logger := &StructLogger{
-		changedValues: make(map[common.Address]Storage),
+		storage: make(map[common.Address]Storage),
 	}
 	if cfg != nil {
 		logger.cfg = *cfg
@@ -130,33 +136,19 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
 }
 
 // CaptureStart implements the Tracer interface to initialize the tracing operation.
-func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
-	return nil
+func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
 }
 
 // CaptureState logs a new structured log message and pushes it out to the environment
 //
-// CaptureState also tracks SSTORE ops to track dirty values.
-func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
+// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
+func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+	memory := scope.Memory
+	stack := scope.Stack
+	contract := scope.Contract
 	// check if already accumulated the specified number of logs
 	if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
-		return ErrTraceLimitReached
-	}
-
-	// initialise new changed values storage container for this contract
-	// if not present.
-	if l.changedValues[contract.Address()] == nil {
-		l.changedValues[contract.Address()] = make(Storage)
-	}
-
-	// capture SSTORE opcodes and determine the changed value and store
-	// it in the local storage container.
-	if op == SSTORE && stack.len() >= 2 {
-		var (
-			value   = common.BigToHash(stack.data[stack.len()-2])
-			address = common.BigToHash(stack.data[stack.len()-1])
-		)
-		l.changedValues[contract.Address()][address] = value
+		return
 	}
 	// Copy a snapshot of the current memory state to a new buffer
 	var mem []byte
@@ -169,29 +161,52 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
 	if !l.cfg.DisableStack {
 		stck = make([]*big.Int, len(stack.Data()))
 		for i, item := range stack.Data() {
-			stck[i] = new(big.Int).Set(item)
+			stck[i] = new(big.Int).Set(item.ToBig())
 		}
 	}
 	// Copy a snapshot of the current storage to a new container
 	var storage Storage
 	if !l.cfg.DisableStorage {
-		storage = l.changedValues[contract.Address()].Copy()
+		// initialise new changed values storage container for this contract
+		// if not present.
+		if l.storage[contract.Address()] == nil {
+			l.storage[contract.Address()] = make(Storage)
+		}
+		// capture SLOAD opcodes and record the read entry in the local storage
+		if op == SLOAD && stack.len() >= 1 {
+			var (
+				address = common.Hash(stack.data[stack.len()-1].Bytes32())
+				value   = env.StateDB.GetState(contract.Address(), address)
+			)
+			l.storage[contract.Address()][address] = value
+		}
+		// capture SSTORE opcodes and record the written entry in the local storage.
+		if op == SSTORE && stack.len() >= 2 {
+			var (
+				value   = common.Hash(stack.data[stack.len()-2].Bytes32())
+				address = common.Hash(stack.data[stack.len()-1].Bytes32())
+			)
+			l.storage[contract.Address()][address] = value
+		}
+		storage = l.storage[contract.Address()].Copy()
+	}
+	var rdata []byte
+	if !l.cfg.DisableReturnData {
+		rdata = make([]byte, len(rData))
+		copy(rdata, rData)
 	}
 	// create a new snapshot of the EVM.
-	log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err}
-
+	log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, env.StateDB.GetRefund(), err}
 	l.logs = append(l.logs, log)
-	return nil
 }
 
 // CaptureFault implements the Tracer interface to trace an execution fault
 // while running an opcode.
-func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
-	return nil
+func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
 }
 
 // CaptureEnd is called after the call finishes to finalize the tracing.
-func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
+func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
 	l.output = output
 	l.err = err
 	if l.cfg.Debug {
@@ -200,7 +215,6 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration
 			fmt.Printf(" error: %v\n", err)
 		}
 	}
-	return nil
 }
 
 // StructLogs returns the captured log entries.
@@ -237,6 +251,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
 				fmt.Fprintf(writer, "%x: %x\n", h, item)
 			}
 		}
+		if len(log.ReturnData) > 0 {
+			fmt.Fprintln(writer, "ReturnData:")
+			fmt.Fprint(writer, hex.Dump(log.ReturnData))
+		}
 		fmt.Fprintln(writer)
 	}
 }
@@ -254,3 +272,65 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
 		fmt.Fprintln(writer)
 	}
 }
+
+type mdLogger struct {
+	out io.Writer
+	cfg *LogConfig
+}
+
+// NewMarkdownLogger creates a logger which outputs information in a format adapted
+// for human readability, and is also a valid markdown table
+func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
+	l := &mdLogger{writer, cfg}
+	if l.cfg == nil {
+		l.cfg = &LogConfig{}
+	}
+	return l
+}
+
+func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+	if !create {
+		fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
+			from.String(), to.String(),
+			input, gas, value)
+	} else {
+		fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
+			from.String(), to.String(),
+			input, gas, value)
+	}
+
+	fmt.Fprintf(t.out, `
+|  Pc   |      Op     | Cost |   Stack   |   RStack  |  Refund |
+|-------|-------------|------|-----------|-----------|---------|
+`)
+}
+
+// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
+func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+	stack := scope.Stack
+	fmt.Fprintf(t.out, "| %4d  | %10v  |  %3d |", pc, op, cost)
+
+	if !t.cfg.DisableStack {
+		// format stack
+		var a []string
+		for _, elem := range stack.data {
+			a = append(a, fmt.Sprintf("%v", elem.String()))
+		}
+		b := fmt.Sprintf("[%v]", strings.Join(a, ","))
+		fmt.Fprintf(t.out, "%10v |", b)
+	}
+	fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund())
+	fmt.Fprintln(t.out, "")
+	if err != nil {
+		fmt.Fprintf(t.out, "Error: %v\n", err)
+	}
+}
+
+func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
+	fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
+}
+
+func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) {
+	fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
+		output, gasUsed, err)
+}
diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go
index 04a83d0660..e54be08596 100644
--- a/core/vm/logger_json.go
+++ b/core/vm/logger_json.go
@@ -41,12 +41,16 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
 	return l
 }
 
-func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
-	return nil
+func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
 }
 
+func (l *JSONLogger) CaptureFault(*EVM, uint64, OpCode, uint64, uint64, *ScopeContext, int, error) {}
+
 // CaptureState outputs state information on the logger.
-func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
+func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
+	memory := scope.Memory
+	stack := scope.Stack
+
 	log := StructLog{
 		Pc:            pc,
 		Op:            op,
@@ -62,18 +66,21 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
 		log.Memory = memory.Data()
 	}
 	if !l.cfg.DisableStack {
-		log.Stack = stack.Data()
+		//TODO(@holiman) improve this
+		logstack := make([]*big.Int, len(stack.Data()))
+		for i, item := range stack.Data() {
+			logstack[i] = item.ToBig()
+		}
+		log.Stack = logstack
 	}
-	return l.encoder.Encode(log)
-}
-
-// CaptureFault outputs state information on the logger.
-func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
-	return nil
+	if !l.cfg.DisableReturnData {
+		log.ReturnData = rData
+	}
+	l.encoder.Encode(log)
 }
 
 // CaptureEnd is triggered at end of execution.
-func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
+func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
 	type endLog struct {
 		Output  string              `json:"output"`
 		GasUsed math.HexOrDecimal64 `json:"gasUsed"`
@@ -81,7 +88,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration,
 		Err     string              `json:"error,omitempty"`
 	}
 	if err != nil {
-		return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()})
+		l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, err.Error()})
 	}
-	return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""})
+	l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, ""})
 }
diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go
index 2ea7535a79..9c936af36a 100644
--- a/core/vm/logger_test.go
+++ b/core/vm/logger_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/holiman/uint256"
 )
 
 type dummyContractRef struct {
@@ -50,21 +51,25 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 }
 
 func TestStoreCapture(t *testing.T) {
 	var (
-		env      = NewEVM(Context{}, &dummyStatedb{}, params.TestChainConfig, Config{})
+		env      = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{})
 		logger   = NewStructLogger(nil)
-		mem      = NewMemory()
-		stack    = newstack()
 		contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0)
+		scope    = &ScopeContext{
+			Memory:   NewMemory(),
+			Stack:    newstack(),
+			Contract: contract,
+		}
 	)
-	stack.push(big.NewInt(1))
-	stack.push(big.NewInt(0))
+	scope.Stack.push(uint256.NewInt().SetUint64(1))
+	scope.Stack.push(uint256.NewInt())
 	var index common.Hash
-	logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil)
-	if len(logger.changedValues[contract.Address()]) == 0 {
-		t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()]))
+	logger.CaptureState(env, 0, SSTORE, 0, 0, scope, nil, 0, nil)
+	if len(logger.storage[contract.Address()]) == 0 {
+		t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
+			len(logger.storage[contract.Address()]))
 	}
 	exp := common.BigToHash(big.NewInt(1))
-	if logger.changedValues[contract.Address()][index] != exp {
-		t.Errorf("expected %x, got %x", exp, logger.changedValues[contract.Address()][index])
+	if logger.storage[contract.Address()][index] != exp {
+		t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index])
 	}
 }
diff --git a/core/vm/memory.go b/core/vm/memory.go
index 496a4024ba..ba5f8485dc 100644
--- a/core/vm/memory.go
+++ b/core/vm/memory.go
@@ -18,9 +18,8 @@ package vm
 
 import (
 	"fmt"
-	"math/big"
 
-	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/holiman/uint256"
 )
 
 // Memory implements a simple memory model for the ethereum virtual machine.
@@ -50,7 +49,7 @@ func (m *Memory) Set(offset, size uint64, value []byte) {
 
 // Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to
 // 32 bytes.
-func (m *Memory) Set32(offset uint64, val *big.Int) {
+func (m *Memory) Set32(offset uint64, val *uint256.Int) {
 	// length of store may never be less than offset + size.
 	// The store should be resized PRIOR to setting the memory
 	if offset+32 > uint64(len(m.store)) {
@@ -59,7 +58,7 @@ func (m *Memory) Set32(offset uint64, val *big.Int) {
 	// Zero the memory area
 	copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
 	// Fill in relevant bits
-	math.ReadBits(val, m.store[offset:offset+32])
+	val.WriteToSlice(m.store[offset:])
 }
 
 // Resize resizes the memory to size
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index 71ef0724a4..b0adf37d0c 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -107,18 +107,18 @@ const (
 
 // 0x50 range - 'storage' and execution.
 const (
-	POP OpCode = 0x50 + iota
-	MLOAD
-	MSTORE
-	MSTORE8
-	SLOAD
-	SSTORE
-	JUMP
-	JUMPI
-	PC
-	MSIZE
-	GAS
-	JUMPDEST
+	POP      OpCode = 0x50
+	MLOAD    OpCode = 0x51
+	MSTORE   OpCode = 0x52
+	MSTORE8  OpCode = 0x53
+	SLOAD    OpCode = 0x54
+	SSTORE   OpCode = 0x55
+	JUMP     OpCode = 0x56
+	JUMPI    OpCode = 0x57
+	PC       OpCode = 0x58
+	MSIZE    OpCode = 0x59
+	GAS      OpCode = 0x5a
+	JUMPDEST OpCode = 0x5b
 )
 
 // 0x60 range.
@@ -389,7 +389,7 @@ var opCodeToString = map[OpCode]string{
 func (op OpCode) String() string {
 	str := opCodeToString[op]
 	if len(str) == 0 {
-		return fmt.Sprintf("Missing opcode 0x%x", int(op))
+		return fmt.Sprintf("opcode 0x%x not defined", int(op))
 	}
 
 	return str
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
new file mode 100644
index 0000000000..c56941899e
--- /dev/null
+++ b/core/vm/operations_acl.go
@@ -0,0 +1,236 @@
+// 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 .
+
+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(), 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
+		warmAccess := evm.StateDB.AddressInAccessList(addr)
+		// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
+		// the cost to charge for cold access, if any, is Cold - Warm
+		coldCost := ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929
+		if !warmAccess {
+			evm.StateDB.AddAddressToAccessList(addr)
+			// Charge the remaining difference here already, to correctly calculate available
+			// gas for call
+			if !contract.UseGas(coldCost) {
+				return 0, ErrOutOfGas
+			}
+		}
+		// Now call the old calculator, which takes into account
+		// - create new account
+		// - transfer value
+		// - memory expansion
+		// - 63/64ths rule
+		gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
+		if warmAccess || err != nil {
+			return gas, err
+		}
+		// In case of a cold access, we temporarily add the cold charge back, and also
+		// add it to the returned gas. By adding it to the return, it will be charged
+		// outside of this function, as part of the dynamic gas, and that will make it
+		// also become correctly reported to tracers.
+		contract.Gas += coldCost
+		return gas + coldCost, nil
+	}
+}
+
+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
+
+}
diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go
index 38ee448904..6c4c72eeac 100644
--- a/core/vm/runtime/env.go
+++ b/core/vm/runtime/env.go
@@ -22,18 +22,20 @@ import (
 )
 
 func NewEnv(cfg *Config) *vm.EVM {
-	context := vm.Context{
+	txContext := vm.TxContext{
+		Origin:   cfg.Origin,
+		GasPrice: cfg.GasPrice,
+	}
+	blockContext := vm.BlockContext{
 		CanTransfer: core.CanTransfer,
 		Transfer:    core.Transfer,
 		GetHash:     cfg.GetHashFn,
-		Origin:      cfg.Origin,
 		Coinbase:    cfg.Coinbase,
 		BlockNumber: cfg.BlockNumber,
 		Time:        cfg.Time,
 		Difficulty:  cfg.Difficulty,
 		GasLimit:    cfg.GasLimit,
-		GasPrice:    cfg.GasPrice,
 	}
 
-	return vm.NewEVM(context, cfg.State, cfg.ChainConfig, cfg.EVMConfig)
+	return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig)
 }
diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go
index 9cb492786b..72601441d5 100644
--- a/core/vm/runtime/runtime.go
+++ b/core/vm/runtime/runtime.go
@@ -52,13 +52,21 @@ type Config struct {
 func setDefaults(cfg *Config) {
 	if cfg.ChainConfig == nil {
 		cfg.ChainConfig = ¶ms.ChainConfig{
-			ChainID:        big.NewInt(1),
-			HomesteadBlock: new(big.Int),
-			DAOForkBlock:   new(big.Int),
-			DAOForkSupport: false,
-			EIP150Block:    new(big.Int),
-			EIP155Block:    new(big.Int),
-			EIP158Block:    new(big.Int),
+			ChainID:             big.NewInt(1),
+			HomesteadBlock:      new(big.Int),
+			DAOForkBlock:        new(big.Int),
+			DAOForkSupport:      false,
+			EIP150Block:         new(big.Int),
+			EIP150Hash:          common.Hash{},
+			EIP155Block:         new(big.Int),
+			EIP158Block:         new(big.Int),
+			ByzantiumBlock:      new(big.Int),
+			ConstantinopleBlock: new(big.Int),
+			PetersburgBlock:     new(big.Int),
+			IstanbulBlock:       new(big.Int),
+			MuirGlacierBlock:    new(big.Int),
+			BerlinBlock:         new(big.Int),
+			YoloV3Block:         nil,
 		}
 	}
 
@@ -106,6 +114,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
 		vmenv   = NewEnv(cfg)
 		sender  = vm.AccountRef(cfg.Origin)
 	)
+	if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
+		cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
+	}
 	cfg.State.CreateAccount(address)
 	// set the receiver's (the executing contract) code for execution.
 	cfg.State.SetCode(address, code)
@@ -135,7 +146,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
 		vmenv  = NewEnv(cfg)
 		sender = vm.AccountRef(cfg.Origin)
 	)
-
+	if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
+		cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil)
+	}
 	// Call the code with the given configuration.
 	code, address, leftOverGas, err := vmenv.Create(
 		sender,
@@ -157,6 +170,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
 	vmenv := NewEnv(cfg)
 
 	sender := cfg.State.GetOrNewStateObject(cfg.Origin)
+	statedb := cfg.State
+
+	if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
+		statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
+	}
 	// Call the code with the given configuration.
 	ret, leftOverGas, err := vmenv.Call(
 		sender,
@@ -165,6 +183,5 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
 		cfg.GasLimit,
 		cfg.Value,
 	)
-
 	return ret, leftOverGas, err
 }
diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go
index decde46ea6..dcf2d0d447 100644
--- a/core/vm/runtime/runtime_test.go
+++ b/core/vm/runtime/runtime_test.go
@@ -17,14 +17,18 @@
 package runtime
 
 import (
+	"fmt"
 	"math/big"
+	"os"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/ethereum/go-ethereum/accounts/abi"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/asm"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
@@ -317,30 +321,370 @@ func TestBlockhash(t *testing.T) {
 	}
 }
 
-// BenchmarkSimpleLoop test a pretty simple loop which loops
-// 1M (1 048 575) times.
-// Takes about 200 ms
+type stepCounter struct {
+	inner *vm.JSONLogger
+	steps int
+}
+
+func (s *stepCounter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+}
+
+func (s *stepCounter) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+}
+
+func (s *stepCounter) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
+
+func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+	s.steps++
+	// Enable this for more output
+	//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
+}
+
+// benchmarkNonModifyingCode benchmarks code, but if the code modifies the
+// state, this should not be used, since it does not reset the state between runs.
+func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) {
+	cfg := new(Config)
+	setDefaults(cfg)
+	cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
+	cfg.GasLimit = gas
+	var (
+		destination = common.BytesToAddress([]byte("contract"))
+		vmenv       = NewEnv(cfg)
+		sender      = vm.AccountRef(cfg.Origin)
+	)
+	cfg.State.CreateAccount(destination)
+	eoa := common.HexToAddress("E0")
+	{
+		cfg.State.CreateAccount(eoa)
+		cfg.State.SetNonce(eoa, 100)
+	}
+	reverting := common.HexToAddress("EE")
+	{
+		cfg.State.CreateAccount(reverting)
+		cfg.State.SetCode(reverting, []byte{
+			byte(vm.PUSH1), 0x00,
+			byte(vm.PUSH1), 0x00,
+			byte(vm.REVERT),
+		})
+	}
+
+	//cfg.State.CreateAccount(cfg.Origin)
+	// set the receiver's (the executing contract) code for execution.
+	cfg.State.SetCode(destination, code)
+	vmenv.Call(sender, destination, nil, gas, cfg.Value)
+
+	b.Run(name, func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			vmenv.Call(sender, destination, nil, gas, cfg.Value)
+		}
+	})
+}
+
+// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG
+// 55 ms
 func BenchmarkSimpleLoop(b *testing.B) {
-	// 0xfffff = 1048575 loops
-	code := []byte{
-		byte(vm.PUSH3), 0x0f, 0xff, 0xff,
+
+	staticCallIdentity := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),       // out offset
+		byte(vm.DUP1),       // out insize
+		byte(vm.DUP1),       // in offset
+		byte(vm.PUSH1), 0x4, // address of identity
+		byte(vm.GAS), // gas
+		byte(vm.STATICCALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	callIdentity := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),       // out offset
+		byte(vm.DUP1),       // out insize
+		byte(vm.DUP1),       // in offset
+		byte(vm.DUP1),       // value
+		byte(vm.PUSH1), 0x4, // address of identity
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	callInexistant := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),        // out offset
+		byte(vm.DUP1),        // out insize
+		byte(vm.DUP1),        // in offset
+		byte(vm.DUP1),        // value
+		byte(vm.PUSH1), 0xff, // address of existing contract
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	callEOA := []byte{
+		byte(vm.JUMPDEST), //  [ count ]
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),        // out offset
+		byte(vm.DUP1),        // out insize
+		byte(vm.DUP1),        // in offset
+		byte(vm.DUP1),        // value
+		byte(vm.PUSH1), 0xE0, // address of EOA
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	loopingCode := []byte{
 		byte(vm.JUMPDEST), //  [ count ]
-		byte(vm.PUSH1), 1, // [count, 1]
-		byte(vm.SWAP1),    // [1, count]
-		byte(vm.SUB),      // [ count -1 ]
-		byte(vm.DUP1),     //  [ count -1 , count-1]
-		byte(vm.PUSH1), 4, // [count-1, count -1, label]
-		byte(vm.JUMPI), // [ 0 ]
-		byte(vm.STOP),
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),       // out offset
+		byte(vm.DUP1),       // out insize
+		byte(vm.DUP1),       // in offset
+		byte(vm.PUSH1), 0x4, // address of identity
+		byte(vm.GAS), // gas
+
+		byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP),
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
+	}
+
+	calllRevertingContractWithInput := []byte{
+		byte(vm.JUMPDEST), //
+		// push args for the call
+		byte(vm.PUSH1), 0, // out size
+		byte(vm.DUP1),        // out offset
+		byte(vm.PUSH1), 0x20, // in size
+		byte(vm.PUSH1), 0x00, // in offset
+		byte(vm.PUSH1), 0x00, // value
+		byte(vm.PUSH1), 0xEE, // address of reverting contract
+		byte(vm.GAS), // gas
+		byte(vm.CALL),
+		byte(vm.POP),      // pop return value
+		byte(vm.PUSH1), 0, // jumpdestination
+		byte(vm.JUMP),
 	}
+
 	//tracer := vm.NewJSONLogger(nil, os.Stdout)
-	//Execute(code, nil, &Config{
+	//Execute(loopingCode, nil, &Config{
 	//	EVMConfig: vm.Config{
 	//		Debug:  true,
 	//		Tracer: tracer,
 	//	}})
+	// 100M gas
+	benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", b)
+	benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", b)
+	benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b)
+	benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", b)
+	benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", b)
+	benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", b)
+
+	//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
+	//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
+}
 
-	for i := 0; i < b.N; i++ {
-		Execute(code, nil, nil)
+// 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)
+	}
+}
+
+// TestColdAccountAccessCost test that the cold account access cost is reported
+// correctly
+// see: https://github.com/ethereum/go-ethereum/issues/22649
+func TestColdAccountAccessCost(t *testing.T) {
+	for i, tc := range []struct {
+		code []byte
+		step int
+		want uint64
+	}{
+		{ // EXTCODEHASH(0xff)
+			code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.EXTCODEHASH), byte(vm.POP)},
+			step: 1,
+			want: 2600,
+		},
+		{ // BALANCE(0xff)
+			code: []byte{byte(vm.PUSH1), 0xFF, byte(vm.BALANCE), byte(vm.POP)},
+			step: 1,
+			want: 2600,
+		},
+		{ // CALL(0xff)
+			code: []byte{
+				byte(vm.PUSH1), 0x0,
+				byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+				byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALL), byte(vm.POP),
+			},
+			step: 7,
+			want: 2855,
+		},
+		{ // CALLCODE(0xff)
+			code: []byte{
+				byte(vm.PUSH1), 0x0,
+				byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+				byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.CALLCODE), byte(vm.POP),
+			},
+			step: 7,
+			want: 2855,
+		},
+		{ // DELEGATECALL(0xff)
+			code: []byte{
+				byte(vm.PUSH1), 0x0,
+				byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+				byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.DELEGATECALL), byte(vm.POP),
+			},
+			step: 6,
+			want: 2855,
+		},
+		{ // STATICCALL(0xff)
+			code: []byte{
+				byte(vm.PUSH1), 0x0,
+				byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
+				byte(vm.PUSH1), 0xff, byte(vm.DUP1), byte(vm.STATICCALL), byte(vm.POP),
+			},
+			step: 6,
+			want: 2855,
+		},
+		{ // SELFDESTRUCT(0xff)
+			code: []byte{
+				byte(vm.PUSH1), 0xff, byte(vm.SELFDESTRUCT),
+			},
+			step: 1,
+			want: 7600,
+		},
+	} {
+		tracer := vm.NewStructLogger(nil)
+		Execute(tc.code, nil, &Config{
+			EVMConfig: vm.Config{
+				Debug:  true,
+				Tracer: tracer,
+			},
+		})
+		have := tracer.StructLogs()[tc.step].GasCost
+		if want := tc.want; have != want {
+			for ii, op := range tracer.StructLogs() {
+				t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost)
+			}
+			t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want)
+		}
 	}
 }
diff --git a/core/vm/stack.go b/core/vm/stack.go
index c9c3d07f4b..c71d2653a5 100644
--- a/core/vm/stack.go
+++ b/core/vm/stack.go
@@ -18,36 +18,48 @@ package vm
 
 import (
 	"fmt"
-	"math/big"
+	"sync"
+
+	"github.com/holiman/uint256"
 )
 
+var stackPool = sync.Pool{
+	New: func() interface{} {
+		return &Stack{data: make([]uint256.Int, 0, 16)}
+	},
+}
+
 // Stack is an object for basic stack operations. Items popped to the stack are
 // expected to be changed and modified. stack does not take care of adding newly
 // initialised objects.
 type Stack struct {
-	data []*big.Int
+	data []uint256.Int
 }
 
 func newstack() *Stack {
-	return &Stack{data: make([]*big.Int, 0, 1024)}
+	return stackPool.Get().(*Stack)
+}
+
+func returnStack(s *Stack) {
+	s.data = s.data[:0]
+	stackPool.Put(s)
 }
 
-// Data returns the underlying big.Int array.
-func (st *Stack) Data() []*big.Int {
+// Data returns the underlying uint256.Int array.
+func (st *Stack) Data() []uint256.Int {
 	return st.data
 }
 
-func (st *Stack) push(d *big.Int) {
+func (st *Stack) push(d *uint256.Int) {
 	// NOTE push limit (1024) is checked in baseCheck
-	//stackItem := new(big.Int).Set(d)
-	//st.data = append(st.data, stackItem)
-	st.data = append(st.data, d)
+	st.data = append(st.data, *d)
 }
-func (st *Stack) pushN(ds ...*big.Int) {
+func (st *Stack) pushN(ds ...uint256.Int) {
+	// FIXME: Is there a way to pass args by pointers.
 	st.data = append(st.data, ds...)
 }
 
-func (st *Stack) pop() (ret *big.Int) {
+func (st *Stack) pop() (ret uint256.Int) {
 	ret = st.data[len(st.data)-1]
 	st.data = st.data[:len(st.data)-1]
 	return
@@ -61,17 +73,17 @@ func (st *Stack) swap(n int) {
 	st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
 }
 
-func (st *Stack) dup(pool *intPool, n int) {
-	st.push(pool.get().Set(st.data[st.len()-n]))
+func (st *Stack) dup(n int) {
+	st.push(&st.data[st.len()-n])
 }
 
-func (st *Stack) peek() *big.Int {
-	return st.data[st.len()-1]
+func (st *Stack) peek() *uint256.Int {
+	return &st.data[st.len()-1]
 }
 
 // Back returns the n'th item in stack
-func (st *Stack) Back(n int) *big.Int {
-	return st.data[st.len()-n-1]
+func (st *Stack) Back(n int) *uint256.Int {
+	return &st.data[st.len()-n-1]
 }
 
 // Print dumps the content of the stack
diff --git a/core/vm/testdata/precompiles/blake2F.json b/core/vm/testdata/precompiles/blake2F.json
new file mode 100644
index 0000000000..a25f9ae501
--- /dev/null
+++ b/core/vm/testdata/precompiles/blake2F.json
@@ -0,0 +1,37 @@
+[
+  {
+    "Input": "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+    "Expected": "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b",
+    "Name": "vector 4",
+    "Gas": 0,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+    "Expected": "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923",
+    "Name": "vector 5",
+    "Gas": 12,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000",
+    "Expected": "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735",
+    "Name": "vector 6",
+    "Gas": 12,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+    "Expected": "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421",
+    "Name": "vector 7",
+    "Gas": 1,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "007A120048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+    "Expected": "6d2ce9e534d50e18ff866ae92d70cceba79bbcd14c63819fe48752c8aca87a4bb7dcc230d22a4047f0486cfcfb50a17b24b2899eb8fca370f22240adb5170189",
+    "Name": "vector 8",
+    "Gas": 8000000,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsG1Add.json b/core/vm/testdata/precompiles/blsG1Add.json
new file mode 100644
index 0000000000..184d765aa1
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsG1Add.json
@@ -0,0 +1,730 @@
+[
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "Expected": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28",
+    "Name": "bls_g1add_(g1+g1=2*g1)",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d280000000000000000000000000000000009ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e522400000000000000000000000000000000032b80d3a6f5b09f8a84623389c5f80ca69a0cddabc3097f9d9c27310fd43be6e745256c634af45ca3473b0590ae30d1",
+    "Expected": "0000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e2",
+    "Name": "bls_g1add_(2*g1+3*g1=5*g1)",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "Name": "bls_g1add_(inf+g1=g1)",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g1add_(inf+inf=inf)",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7",
+    "Expected": "000000000000000000000000000000001466e1373ae4a7e7ba885c5f0c3ccfa48cdb50661646ac6b779952f466ac9fc92730dcaed9be831cd1f8c4fefffd5209000000000000000000000000000000000c1fb750d2285d4ca0378e1e8cdbf6044151867c34a711b73ae818aee6dbe9e886f53d7928cc6ed9c851e0422f609b11",
+    "Name": "matter_g1_add_0",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95",
+    "Expected": "0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6",
+    "Name": "matter_g1_add_1",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f00000000000000000000000000000000114c3f11ba0b47551fa28f09f148936d6b290dc9f2d0534a83c32b0b849ab921ce6bcaa4ff3c917707798d9c74f2084f00000000000000000000000000000000149dc028207fb04a7795d94ea65e21f9952e445000eb954531ee519efde6901675d3d2446614d243efb77a9cfe0ca3ae",
+    "Expected": "0000000000000000000000000000000002ce7a08719448494857102da464bc65a47c95c77819af325055a23ac50b626df4732daf63feb9a663d71b7c9b8f2c510000000000000000000000000000000016117e87e9b55bd4bd5763d69d5240d30745e014b9aef87c498f9a9e3286ec4d5927df7cd5a2e54ac4179e78645acf27",
+    "Name": "matter_g1_add_2",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000c3d564ac1fe12f18f528c3750583ab6af8973bff3eded7bb4778c32805d9b17846cc7c687af0f46bc87de7748ab72980000000000000000000000000000000002f164c131cbd5afc85692c246157d38dc4bbb2959d2edfa6daf0a8b17c7a898aad53b400e8bdc2b29bf6688ee863db7",
+    "Expected": "0000000000000000000000000000000015510826f50b88fa369caf062ecdf8b03a67e660a35b219b44437a5583b5a9adf76991dce7bff9afc50257f847299504000000000000000000000000000000000a83e879895a1b47dbd6cd25ce8b719e7490cfe021614f7539e841fc2f9c09f071e386676de60b6579aa4bf6d37b13dd",
+    "Name": "matter_g1_add_3",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d30000000000000000000000000000000019fe3a64361fea14936ff0b3e630471494d0c0b9423e6a004184a2965221c18849b5ed0eb2708a587323d8d6c6735a90000000000000000000000000000000000340823d314703e5efeb0a65c23069199d7dfff8793aaacb98cdcd6177fc8e61ab3294c57bf13b4406266715752ef3e6",
+    "Expected": "00000000000000000000000000000000010b1c96d3910f56b0bf54da5ae8c7ab674a07f8143b61fed660e7309e626dc73eaa2b11886cdb82e2b6735e7802cc860000000000000000000000000000000002dabbbedd72872c2c012e7e893d2f3df1834c43873315488d814ddd6bfcca6758a18aa6bd02a0f3aed962cb51f0a222",
+    "Name": "matter_g1_add_4",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90000000000000000000000000000000001461565b03a86df363d1854b4af74879115dffabeddfa879e2c8db9aa414fb291a076c3bdf0beee82d9c094ea8dc381a000000000000000000000000000000000e19d51ab619ee2daf25ea5bfa51eb217eabcfe0b5cb0358fd2fa105fd7cb0f5203816b990df6fda4e0e8d541be9bcf6",
+    "Expected": "000000000000000000000000000000000cb40d0bf86a627d3973f1e7846484ffd0bc4943b42a54ff9527c285fed3c056b947a9b6115824cabafe13cd1af8181c00000000000000000000000000000000076255fc12f1a9dbd232025815238baaa6a3977fd87594e8d1606caec0d37b916e1e43ee2d2953d75a40a7ba416df237",
+    "Name": "matter_g1_add_5",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b0000000000000000000000000000000019cabba3e09ad34cc3d125e0eb41b527aa48a4562c2b7637467b2dbc71c373897d50eed1bc75b2bde8904ece5626d6e400000000000000000000000000000000056b0746f820cff527358c86479dc924a10b9f7cae24cd495625a4159c8b71a8c3ad1a15ebf22d3561cd4b74e8a6e48b",
+    "Expected": "000000000000000000000000000000000e115e0b61c1f1b25cc10a7b3bd21cf696b1433a0c366c2e1bca3c26b09482c6eced8c8ecfa69ce6b9b3b4419779262e00000000000000000000000000000000077b85daf61b9f947e81633e3bc64e697bc6c1d873f2c21e5c4c3a11302d4d5ef4c3ff5519564729aaf2a50a3c9f1196",
+    "Name": "matter_g1_add_6",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a0000000000000000000000000000000011f649ee35ff8114060fc5e4df9ac828293f6212a9857ca31cb3e9ce49aa1212154a9808f1e763bc989b6d5ba7cf09390000000000000000000000000000000019af81eca7452f58c1a6e99fab50dc0d5eeebc7712153e717a14a31cffdfd0a923dbd585e652704a174905605a2e8b9d",
+    "Expected": "000000000000000000000000000000000013e37a8950a659265b285c6fb56930fb77759d9d40298acac2714b97b83ec7692a7d1c4ccb83f074384db9eedd809c0000000000000000000000000000000003215d524d6419214568ba42a31502f2a58a97d0139c66908e9d71755f5a7666567aafe30ea84d89308f06768f28a648",
+    "Name": "matter_g1_add_7",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000d713e148769fac2efd380886f8566c6d4662dd38317bb7e68744c4339efaedbab88435ce3dc289afaa7ecb37df37a5300000000000000000000000000000000129d9cd031b31c77a4e68093dcdbb585feba786207aa115d9cf120fe4f19ca31a0dca9c692bd0f53721d60a55c333129",
+    "Expected": "00000000000000000000000000000000029405b9615e14bdac8b5666bbc5f3843d4bca17c97bed66d164f1b58d2a148f0f506d645d665a40e60d53fe29375ed400000000000000000000000000000000162761f1712814e474beb2289cc50519253d680699b530c2a6477f727ccc75a19681b82e490f441f91a3c611eeb0e9e2",
+    "Name": "matter_g1_add_8",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000006d92bcb599edca426ff4ceeb154ebf133c2dea210c7db0441f74bd37c8d239149c8b5056ace0bfefb1db04b42664f530000000000000000000000000000000008522fc155eef6d5746283808091f91b427f2a96ac248850f9e3d7aadd14848101c965663fd4a63aea1153d71918435a",
+    "Expected": "000000000000000000000000000000000cfaa8df9437c0b6f344a0c8dcbc7529a07aec0d7632ace89af6796b6b960b014f78dd10e987a993fb8a95cc909822ec0000000000000000000000000000000007475f115f6eb35f78ba9a2b71a44ccb6bbc1e980b8cd369c5c469565f3fb798bc907353cf47f524ba715deaedf379cb",
+    "Name": "matter_g1_add_9",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac9430000000000000000000000000000000016380d03b7c5cc3301ffcb2cf7c28c9bde54fc22ba2b36ec293739d8eb674678c8e6461e34c1704747817c8f8341499a000000000000000000000000000000000ec6667aa5c6a769a64c180d277a341926376c39376480dc69fcad9a8d3b540238eb39d05aaa8e3ca15fc2c3ab696047",
+    "Expected": "0000000000000000000000000000000011541d798b4b5069e2541fa5410dad03fd02784332e72658c7b0fa96c586142a967addc11a7a82bfcee33bd5d07066b900000000000000000000000000000000195b3fcb94ab7beb908208283b4e5d19c0af90fca4c76268f3c703859dea7d038aca976927f48839ebc7310869c724aa",
+    "Name": "matter_g1_add_10",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e00000000000000000000000000000000065eb0770ab40199658bf87db6c6b52cd8c6c843a3e40dd60433d4d79971ff31296c9e00a5d553df7c81ade533379f4b0000000000000000000000000000000017a6f6137ddd90c15cf5e415f040260e15287d8d2254c6bfee88938caec9e5a048ff34f10607d1345ba1f09f30441ef4",
+    "Expected": "0000000000000000000000000000000006b0853b3d41fc2d7b27da0bb2d6eb76be32530b59f8f537d227a6eb78364c7c0760447494a8bba69ef4b256dbef750200000000000000000000000000000000166e55ba2d20d94da474d4a085c14245147705e252e2a76ae696c7e37d75cde6a77fea738cef045182d5e628924dc0bb",
+    "Name": "matter_g1_add_11",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000006a3f7eb0e42567210cc1ba5e6f8c42d02f1eef325b6483fef49ba186f59ab69ca2284715b736086d2a0a1f0ea224b40000000000000000000000000000000000bc08427fda31a6cfbe657a8c71c73894a33700e93e411d42f1471160c403b939b535070b68d60a4dc50e47493da63dc",
+    "Expected": "000000000000000000000000000000000c35d4cd5d43e9cf52c15d46fef521666a1e1ab9f0b4a77b8e78882e9fab40f3f988597f202c5bd176c011a56a1887d4000000000000000000000000000000000ae2b5c24928a00c02daddf03fade45344f250dcf4c12eda06c39645b4d56147cb239d95b06fd719d4dc20fe332a6fce",
+    "Name": "matter_g1_add_12",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a578768100000000000000000000000000000000068e79aea45b7199ec4b6f26e01e88ec76533743639ce76df66937fff9e7de3edf6700d227f10f43e073afcc63e2eddc00000000000000000000000000000000039c0b6d9e9681401aeb57a94cedc0709a0eff423ace9253eb00ae75e21cabeb626b52ef4368e6a4592aed9689c6fca4",
+    "Expected": "0000000000000000000000000000000013bad27dafa20f03863454c30bd5ae6b202c9c7310875da302d4693fc1c2b78cca502b1ff851b183c4b2564c5d3eb4dc0000000000000000000000000000000000552b322b3d672704382b5d8b214c225b4f7868f9c5ae0766b7cdb181f97ed90a4892235915ffbc0daf3e14ec98a606",
+    "Name": "matter_g1_add_13",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f7680000000000000000000000000000000000adac9bb98bb6f35a8f941dbff39dfd307b6a4d5756ccae103c814564e3d3993a8866ff91581ccdd7686c1dce0b19f700000000000000000000000000000000083d235e0579032ca47f65b6ae007ce8ffd2f1a890ce3bc45ebd0df6673ad530d2f42125d543cb0c51ba0c28345729d8",
+    "Expected": "000000000000000000000000000000000b5513e42f5217490f395a8cb3673a4fc35142575f770af75ecf7a4fcd97eee215c4298fc4feab51915137cbdb814839000000000000000000000000000000000e9d4db04b233b0b12a7ff620faefef906aeb2b15481ce1609dad50eb6a7d0c09a850375599c501296219fb7b288e305",
+    "Name": "matter_g1_add_14",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca",
+    "Expected": "000000000000000000000000000000000c27ef4ebf37fd629370508f4cd062b74faa355b305d2ee60c7f4d67dd741363f18a7bbd368cdb17e848f372a5e33a6f0000000000000000000000000000000000ed833df28988944115502f554636e0b436cccf845341e21191e82d5b662482f32c24df492da4c605a0f9e0f8b00604",
+    "Name": "matter_g1_add_15",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000091ee883cb9ea2c933f6645f0f4c535a826d95b6da6847b4fe2349342bd4bd496e0dd546df7a7a17a4b9fb8349e5064f000000000000000000000000000000000902d7e72242a5e6b068ca82d0cb71dc0f51335dbd302941045319f9a06777518b56a6e0b0b0c9fd8f1edf6b114ad331",
+    "Expected": "00000000000000000000000000000000122cce99f623944dfebffcdf6b0a0a3696162f35053e5952dddc2537421c60da9fe931579d1c4fc2e31082b6c25f96b500000000000000000000000000000000011366ffa91dc0b7da8b7c1839ea84d49299310f5c1ca244012eed0dd363dbcf4ad5813b8e3fb49361ef05ea8cb18ffe",
+    "Name": "matter_g1_add_16",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000000d3d4f11bc79b8425b77d25698b7e151d360ebb22c3a6afdb227de72fe432dcd6f0276b4fd3f1fcc2da5b59865053930000000000000000000000000000000015ac432071dc23148765f198ed7ea2234662745a96032c215cd9d7cf0ad8dafb8d52f209983fe98aaa2243ecc2073f1b",
+    "Expected": "000000000000000000000000000000000113ccf11264ff04448f8c58b279a6a49acb386750c2051eab2c90fa8b8e03d7c5b9e87eccf36b4b3f79446b80be7b1d0000000000000000000000000000000004358a1fabfe803f4c787a671196b593981a837ee78587225fb21d5a883b98a15b912862763b94d18b971cb7e37dbcf0",
+    "Name": "matter_g1_add_17",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb600000000000000000000000000000000034f725766897ed76394145da2f02c92c66794a51fd5ae07bd7cc60c013d7a48ebf1b07faf669dfed74d82d07e48d1150000000000000000000000000000000018f4926a3d0f740988da25379199ecb849250239ad7efcfef7ffaa43bc1373166c0448cc30dcdbd75ceb71f76f883ea7",
+    "Expected": "00000000000000000000000000000000167336aeeb9e447348156936849d518faee314c291c84d732fa3c1bd3951559230d94230e37a08e28e689e9d1fef05770000000000000000000000000000000005366535f7a68996e066ab80c55bb372a15fb0ed6634585b88fe7cafbf818fbfebbf6f6ddd9ca0ff72137594a1e84b35",
+    "Name": "matter_g1_add_18",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b00000000000000000000000000000000079e5a154cf84190b6c735bc8cd968559182166568649b813732e4fb4c5c428c8b38e8265d4ef04990c49aa1381f51c8000000000000000000000000000000000ae08e682ef92b4986a5ac5d4f094ad0919c826a97efe8d8120a96877766eae5828803804a0cae67df9822fd18622aae",
+    "Expected": "000000000000000000000000000000000a3d66cf87b1ce8c5683d71a6de4bf829d094041240f56d9071aa84ff189a06940e8e1935127e23a970c78ca73c28bf6000000000000000000000000000000000b2adda87740873c0c59e3ebde44d33834773f0fe69e2f5e7ede99c4f928978a5caaede7262e45fd22136a394b3f7858",
+    "Name": "matter_g1_add_19",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000008cefd0fd289d6964a962051c2c2ad98dab178612663548370dd5f007c5264fece368468d3ca8318a381b443c68c4cc7000000000000000000000000000000000708d118d44c1cb5609667fd51df9e58cacce8b65565ef20ad1649a3e1b9453e4fb37af67c95387de008d4c2114e5b95",
+    "Expected": "0000000000000000000000000000000004b2311897264fe08972d62872d3679225d9880a16f2f3d7dd59412226e5e3f4f2aa8a69d283a2dc5b93e022293f0ee1000000000000000000000000000000000f03e18cef3f9a86e6b842272f2c7ee48d0ad23bfc7f1d5a9a796d88e5d5ac31326db5fe90de8f0690c70ae6e0155039",
+    "Name": "matter_g1_add_20",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000008e5afc16d909eb9d8bdaaf229ad291f34f7baf5247bbd4cc938278f1349adb4b0f0aacd14799c01d0ca2ed38c937d600000000000000000000000000000000006cf972c64e20403c82fee901c90eaa5547460d57cce2565fd091ff9bc55e24584595c9182298f148882d6949c36c9d5",
+    "Expected": "000000000000000000000000000000000caf46f480ae2ea8e700f7913c505d5150c4629c9137e917357d2a4ba8a7a1c63b8f6e2978293755952fbed7f0ad8d6d0000000000000000000000000000000002e62e715b72eebbc7c366a2390318f73e69203a9533e72340aab568f65105129ffc9889a8bc00a692494d93688c7ec0",
+    "Name": "matter_g1_add_21",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0000000000000000000000000000000013a6439e0ec0fabe93f6c772e102b96b1f692971d7181c386f7f8a360daca6e5f99772e1a736f1e72a17148d90b08efe0000000000000000000000000000000010f27477f3171dcf74498e940fc324596ef5ec6792be590028c2963385d84ef8c4bbb12c6eb3f06b1afb6809a2cb0358",
+    "Expected": "000000000000000000000000000000000dea57d1fc19f994e6bdda9478a400b0ada23aed167bfe7a16ef79b6aa020403a04d554303c0b2a9c5a38f85cf6f3800000000000000000000000000000000000b8d76ccd41ba81a835775185bbf1d6bf94b031d94d5c78b3b97beb24cf246b0c25c4c309e2c06ae9896ed800169eeee",
+    "Name": "matter_g1_add_22",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff320000000000000000000000000000000005728a219d128bc0a1f851f228e2bf604a72400c393cfb0d3484456b6b28a2c5061198656f0e106bbe257d849be159040000000000000000000000000000000011f6d08baa91fb2c8b36191d5b2318e355f8964cc8112838394ba1ded84b075de58d90452601dcfc9aa8a275cfec695d",
+    "Expected": "0000000000000000000000000000000012e6d6c518c15cfd3020181ff3f829e29140b3b507b99251cc7f31795128adec817750296bce413bac18b9a80f69ca5000000000000000000000000000000000131ee9b748f6f1eb790adeb9edd0e79d89a9908368f5a6bb82ee0c913061cdfffe75d9ba411a49aa3f9194ee6d4d08a9",
+    "Name": "matter_g1_add_23",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f200000000000000000000000000000000171696781ba195f330241584e42fb112adf9b8437b54ad17d410892b45c7d334e8734e25862604d1b679097590b8ab0a000000000000000000000000000000001879328fdf0d1fb79afd920e0b0a386828be5b8e0e6024dfeea800ffcb5c65f9044061af26d639d4dcc27bcb5ba1481a",
+    "Expected": "00000000000000000000000000000000111c416d5bd018a77f3317e3fbf4b03d8e19658f2b810dc9c17863310dfb09e1c4ffdbb7c98951d357f1c3d93c5d0745000000000000000000000000000000000af0a252bff336d5eb3a406778557ef67d91776a9c788be9a76cff7727f519a70fc7809f1a50a58d29185cb9722624fd",
+    "Name": "matter_g1_add_24",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c2000000000000000000000000000000000231b0d6189a4faad082ce4a69398c1734fcf35d222b7bce22b14571033a1066b049ae3cd3bd6c8cec5bec743955cdd600000000000000000000000000000000037375237fb71536564ea693ab316ae11722aadd7cab12b17b926c8a31bd13c4565619e8c894bffb960e632896856bbe",
+    "Expected": "000000000000000000000000000000000d2b9c677417f4e9b38af6393718f55a27dbd23c730796c50472bc476ebf52172559b10f6ceb81e644ec2d0a41b3bb01000000000000000000000000000000001697f241ff6eceb05d9ada4be7d7078ecbbffa64dd4fb43ead0692eef270cb7cc31513ee4bf38a1b1154fe008a8b836a",
+    "Name": "matter_g1_add_25",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000015653d1c5184736cdc78838be953390d12b307d268b394136b917b0462d5e31b8f1b9d96cce8f7a1203c2cae93db6a4000000000000000000000000000000000060efeece033ac711d500c1156e4b6dce3243156170c94bc948fd7beae7b28a31463a44872ca22ca49dc5d4d4dd27d1c",
+    "Expected": "0000000000000000000000000000000003996050756117eeab27a5e4fa9acdde2a1161d6fbfff2601a1c7329f900e93a29f55a8073f85be8f7c2a4d0323e95cc00000000000000000000000000000000010b195a132c1cba2f1a6a73f2507baa079e9b5cb8894ea78bebc16d4151ee56fe562b16e2741f3ab1e8640cdad83180",
+    "Name": "matter_g1_add_26",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc0000000000000000000000000000000018adb42928304cbc310a229306a205e7c21cdb31b9e5daf0ff6bb9437acee80cd8cf02b35dab823155d60f8a83fde5cc0000000000000000000000000000000018b57460c81cab43235be79c8c90dcda40fafcaf69e4e767133aee56308a6df07eac71275597dd8ed6607ffb9151ed9a",
+    "Expected": "0000000000000000000000000000000003c7a7ee3d1b73cf1f0213404363bf3c0de4425ab97d679ed51448e877b7537400f148f14eba588ed241fea34e56d465000000000000000000000000000000000c581b5070e6bb8582b7ee2cd312dfeb5aaf0b0da95cf5a22a505ffba21fc204e26a5e17311d1f47113653ff13349f57",
+    "Name": "matter_g1_add_27",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da00000000000000000000000000000000001da65df8574a864ab454e5f2fa929405501bb73c3162a600979a1145586079361c89839cc0c5a07f1135c94bf059f9c0000000000000000000000000000000002560df402c0550662a2c4c463ad428ab6e60297fbc42a6484107e397ae016b58494d1c46ac4952027aa8c0896c50be3",
+    "Expected": "000000000000000000000000000000000d7a539b679e5858271a6f9cf20108410eb5d5d2b1a905e09a8aa20318efbe9175450385d78389f08f836f5634f7a2f0000000000000000000000000000000000fb624e5f6c4c814b7d73eb63b70237c5de7d90d19ac81cac776d86171a8d307d3cc8c56da14f444fe8cf329ab7e63dd",
+    "Name": "matter_g1_add_28",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e0530000000000000000000000000000000005311c11f4d0bb8542f3b60247c1441656608e5ac5c363f4d62127cecb88800a771767cf23a0e7c45f698ffa5015061f0000000000000000000000000000000018f7f1d23c8b0566a6a1fcb58d3a5c6fd422573840eb04660c3c6ba65762ed1becc756ac6300e9ce4f5bfb962e963419",
+    "Expected": "0000000000000000000000000000000000849bbc7b0226b18abbcb4c9a9e78dca2f5f75a2cbb983bd95ff3a95b427b1a01fd909ce36384c49eb88ffb8ff77bb000000000000000000000000000000000087d8d28d92305b5313ca533a6b47f454ddce1c2d0fa3574b255128ef0b145fa4158beb07e4f0d50d6b7b90ea8a8ea8a",
+    "Name": "matter_g1_add_29",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e000000000000000000000000000000000c8e293f730253128399e5c39ab18c3f040b6cd9df10d794a28d2a428a9256ea1a71cf53022bd1be11f501805e0ddda40000000000000000000000000000000003e60c2291be46900930f710969f79f27e76cf710efefc243236428db2fed93719edeeb64ada0edf6346a0411f2a4cb8",
+    "Expected": "00000000000000000000000000000000191084201608f706ea1f7c51dd5b593dda87b15d2c594b52829db66ce3beab6b30899d1d285bdb9590335949ceda5f050000000000000000000000000000000000d3460622c7f1d849658a20a7ae7b05e5afae1f01e871cad52ef632cc831b0529a3066f7b81248a7728d231e51fc4ad",
+    "Name": "matter_g1_add_30",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc80000000000000000000000000000000013267db8fdf8f488a2806fead5cffdcbb7b1b4b7681a2b67d322cd7f5985c65d088c70cdc2638e679ed678cae3cc63c80000000000000000000000000000000007757233ad6d38d488c3d9d8252b41e4ab7ee54e4ef4bbf171402df57c14f9977dd3583c6c8f9b5171b368d61f082447",
+    "Expected": "000000000000000000000000000000000c06fef6639ab7dceb44dc648ca6a7d614739e40e6486ee9fc01ecc55af580d98abc026c630a95878da7b6d5701d755c0000000000000000000000000000000007c9a7f2bc7fa1f65c9e3a1e463eb4e3283e47bb5490938edb12abf6c8f5a9b56d8ce7a81a60df67db8c399a9a1df1d4",
+    "Name": "matter_g1_add_31",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17000000000000000000000000000000001975bc52669187f27a86096ae6bf2d60178706105d15bce8fe782759f14e449bc97cb1570e87eec5f12214a9ae0e0170000000000000000000000000000000000ca6106d6e6487a3b6f00fc2af769d21cb3b83b5dc03db19e4824fc28fd9b3d9f7a986e79f05c02b3a914ff26c7a78d6",
+    "Expected": "0000000000000000000000000000000002fbf4fba68ae416b42a99f3b26916dea464d662cebce55f4545481e5ab92d3c40f3e189504b54db4c9cd51ecdd60e8d0000000000000000000000000000000008e81e094c6d4ded718ef63c5edfacb2d258f48ccfa37562950c607299bb2dca18e680a620dff8c72dedc89b4e9d4759",
+    "Name": "matter_g1_add_32",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f00000000000000000000000000000000109f6168a719add6ea1a14f9dc95345e325d6b0e56da2f4ecff8408536446894069fa61e81bdaebfc96b13b402fad865000000000000000000000000000000001806aa27c576f4c4fa8a6db49d577cd8f257a8450e89b061cbc7773c0b5434f06bacf12b479abf6847f537c4cbefcb46",
+    "Expected": "0000000000000000000000000000000014e0bd4397b90a3f96240daf835d5fb05da28a64538f4bf42d9e7925a571f831c6e663910aa37dcc265ddd7938d83045000000000000000000000000000000001695d405d4f8ba385ebf4ad25fb3f34c65977217e90d6e5ed5085b3e5b0b143194f82e6c25766d28ad6c63114ca9dcdf",
+    "Name": "matter_g1_add_33",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af0000000000000000000000000000000019d3623a7866933e2d73214ceb2e56097a1b047db5943c3ecb846890aa02250126e90fc76a729a952cef895bd154cc7d000000000000000000000000000000000e87c376bbd695a356ef72226ac7ef6a550d99e9693d8485770a686e568ae28c038ee201d3f2ea38362046236ade91cd",
+    "Expected": "000000000000000000000000000000000ffeab47985bd9b3e10ce27c6636bbda336dcf540cd37eccc3faec2adff2d97dd126633bd83a7d3c8c73c3623bdf0ba2000000000000000000000000000000001992eca4b1e924b360d57ca98b543ab496a8b55bd288d23f03bcc1b22f6bc76d95b12f47c3e305812097253c73b876dd",
+    "Name": "matter_g1_add_34",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c300000000000000000000000000000000163aaecf83d6c77a5d7417e73f5cf9d71a6aedfd194b2f3b53c608d06a228190f4f79ac57b029d77504c72744df4ecc0000000000000000000000000000000000416e6f9ca188d16daa2c28acd6a594f8fcb990eaa26e60ca2a34dfcad7ad76c425b241acedf674d48d298d0df0f824d",
+    "Expected": "000000000000000000000000000000001812bcb26fa05e0ab5176e703699ab16f5ef8917a33a9626ae6ff20f2a6f4a9d5e2afe3a11f57061cbaa992e1f30477f000000000000000000000000000000000680acf0b632cb48017cb80baa93753d030aa4b49957178d8a10d1d1a27bbdc89ac6811a91868b2c181c5c0b9b6caf86",
+    "Name": "matter_g1_add_35",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000000aba7362eee717d03ef2d4f0fef2763822115fcc8fb9e2e8243683b6c1cde799ebc78f23812e557de2cc38e2b4a2e56700000000000000000000000000000000170833db69b3f067cf5c4c4690857e6711c9e3fcad91ca7cd045e9d2f38c7b31236960e8718f5dd4c8bfb4de76c6c9b9",
+    "Expected": "00000000000000000000000000000000196ffe76a4b726fa8dd720cc1cd04c040724cb18ec10915e312eaa90d124100b08f0ce3a7fc888f46914319a3d7581f4000000000000000000000000000000000e2612357059ca6dbb64efb98ef19370560c9e83e2aad7ab2d9015e2444fe4d8c796b5577584aac9f63258beb5ae863c",
+    "Name": "matter_g1_add_36",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da8257721808000000000000000000000000000000000a98ae36c690f2e3be8100f43678be5a1064390e210328dd23f61f5a496b87398db2798580edeabc6273fb9537fa12880000000000000000000000000000000009aedf77bb969592c6552ae0121a1c74de78ba222b6cd08623c7a34708a12763b5ff7969cf761ccd25adc1b65da0f02d",
+    "Expected": "00000000000000000000000000000000072334ec8349fc38b99d6dea0b4259c03cd96c1438c90ef0da6321df2495892de031a53c23838ca2b260774fa09b5461000000000000000000000000000000000e4535767c2477c4f87c087540c836eeffcd0c45960841f9c3561a8a5f8e61ab98b183b11192b8e7ea1c9c7717336243",
+    "Name": "matter_g1_add_37",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e00000000000000000000000000000000015c3c056ec904ce865d073f8f70ef2d4b5adb5b9238deaa5e167d32f45cad4901aa6d87efa2338c633e7853ce4c19185000000000000000000000000000000000a15f1aa6e662f21d7127351a1655821c943c4cf590e3c9e60c9ab968b4a835f87fb8d87eee6331ee4e194e5f1ea91f4",
+    "Expected": "000000000000000000000000000000000140fb6dcf872d0a3bff3e32a0cb4a7fb7e60ee4fb476bb120c4ce068e169d72e1c167d7fda321280d5855983d5a9af800000000000000000000000000000000108f54a4ec3ba26dd614f4d94c5c82652583906986158ad40ffea54c17703fa4b0bd7806633e1c0318d06e8dc7d41cde",
+    "Name": "matter_g1_add_38",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb000000000000000000000000000000000307841cb33e0f188103a83334a828fa864cea09c264d5f4343246f64ab244add4610c9ccd64c001816e5074fe84013f000000000000000000000000000000000e15bbeb6fff7f1435097828f5d64c448bbc800f31a5b7428436dcffd68abc92682f2b01744d7c60540e0cd1b57ab5d4",
+    "Expected": "000000000000000000000000000000000a1b50660ed9120fff1e5c4abb401e4691a09f41780ca188cea4b1c2d77002f08ce28eb1caa41ee3fe73169e3651bb7f00000000000000000000000000000000125439ac3b45c698a98063ab911364bd3c6dd2a69435d00d6edf89fc5566b33038e960a125e5e52141abb605587942fe",
+    "Name": "matter_g1_add_39",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000013866438b089d39de5a3ca2a624d72c241a54cbdcf5b2a67ebdd2db8373b112a814e74662bd52e37748ffbfc21782a5000000000000000000000000000000000d55454a22d5c2ef82611ef9cb6533e2f08668577764afc5bb9b7dfe32abd5d333147774fb1001dd24889775de57d305",
+    "Expected": "000000000000000000000000000000000037b4e8846b423335711ac12f91e2419de772216509d6b9deb9c27fd1c1ee5851b3e032bf3bcac3dd8e93f3dce8a91b00000000000000000000000000000000113a1bf4be1103e858c3be282effafd5e2384f4d1073350f7073b0a415ecf9e7a3bfb55c951c0b2c25c6bab35454ecf0",
+    "Name": "matter_g1_add_40",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f180320000000000000000000000000000000017440fd557df23286da15f9a96bb88cfbc79589b1c157af13baf02c65227dc0a5bdec6f2f300083ff91dae395ed8cb75000000000000000000000000000000000ad09b4290842cc599d346110fdb39ededbb1d651568579564e274465f07b8f77eeaf00fece0c10db69c2125de8ab394",
+    "Expected": "0000000000000000000000000000000007c158b4e21566742f7e4e39a672bd383e27864505acef4ef8c26f8b0a9db418f9c088b555b8e9eb25acf9859b1207b40000000000000000000000000000000016e06a1ace89f992d582af0de7662ef91c0a98f574306f6f6d0d8d5e80166638d2deef70105cce2e9b20faa9d6315510",
+    "Name": "matter_g1_add_41",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0000000000000000000000000000000000d7ccc3a4efdfe1a92a88e453933b8216016091f1b9d575faf18a5b3abf90daf077813167a3f4acce7359472dee544bb00000000000000000000000000000000128008c075ab176100e755cbb8de5b9ff0e9a78114f862d26ed030d9c1d1dea1c21ec8ae4d82a84d3ff5ae4c1cd6f339",
+    "Expected": "000000000000000000000000000000000b84f9de79c748e37797c629cb78b86b4b736b199f161b30147b5dacf6eabe0b54afce40d5dacfe9a8ee8da5ef5b49de0000000000000000000000000000000010277ad094bb9a3b96379b1366dd90125b51a21ebeb4f776a81d9d9c1f37ab58c32a884a26fa32c83783ed0eef42b820",
+    "Name": "matter_g1_add_42",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e00000000000000000000000000000000008da4a93d5ffcdaa0adc736a59f0c187ae3bf11ecb5e9e6f6aedea976a47757739042200b4c4593c2dd5db555425531000000000000000000000000000000000a6fdb2d4160c6c35223daa6fa10d0b1073de07fe4f2eba28e65ed049ff8d8852ed0538b30759fe7a0d944009ddf9a6f",
+    "Expected": "000000000000000000000000000000000d740bd1effd8674250618af0358ad0b83bbc787f0264af9c2ada72fa5431be909e82155da1de0211f46fb307e9949f0000000000000000000000000000000000ddf62c91d587a14b64feef07da52c081b40fbbf9a0f2eae8b66022e0850fc94de6a467e7e4f580c7f2c806f6c6ed8cf",
+    "Name": "matter_g1_add_43",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d790000000000000000000000000000000003258d7931a1d72ab6344c7e96c0dbd435a7909fe68cc679c08ca9b62f7a6a04863082cbcfdbe9a736625d895e4f3bdb0000000000000000000000000000000009ee3e470e2b2cebc955ba3444b7e478f887138e36c13bd68490689122627269ea5e7ce22dd9c69792394a24187103d6",
+    "Expected": "000000000000000000000000000000000af674691f5d87655f0066188fac5013f31b4169a0181d3feb7ac3beae0d9a3429d4125f099ee344f644a2de8b941f9f00000000000000000000000000000000042a9603b8e4a6c37d59ede3a1398f5f80c5298da66de575a204ee28811d9f7c7c0dd40cef3769bd72a2156b9eb620c8",
+    "Name": "matter_g1_add_44",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820000000000000000000000000000000001833807f1ced52399305419450355499a63411837ee61ad681559d59561db18511eb1e8ad3161e7fe30016b560d18b8f00000000000000000000000000000000198b11b31586e17964a4a4ccdee85703163d2106481833e71f26327a589bafb43578d08d87f6cb19c7a04b4ca92392bf",
+    "Expected": "000000000000000000000000000000001081c3359a0fadfe7850ce878182859e3dd77028772da7bcac9f6451ac6455739c22627889673db626bbea70aa3648d50000000000000000000000000000000000f4e8766f976fa49a0b05ef3f06f56d92fe6452ff05c3fac455f9c16efadf1b81a44d2921bed73511dda81d6fc7478e",
+    "Name": "matter_g1_add_45",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8",
+    "Expected": "000000000000000000000000000000000f60d66fd1ed5eb04f9619d6458c522cc49f5ace111aff2b61903b112559972f80ac615591463abf2b944c4f99d4c03e000000000000000000000000000000000001a1abfa869be2cda6bd7e05454a8735e1b638db7e1b3715708539c2d14ade53069c7e68b36d3b08cff80837028b7d",
+    "Name": "matter_g1_add_46",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd0000000000000000000000000000000014b78c66c4acecdd913ba73cc4ab573c64b404a9494d29d4a2ba02393d9b8fdaba47bb7e76d32586df3a00e03ae2896700000000000000000000000000000000025c371cd8b72592a45dc521336a891202c5f96954812b1095ba2ea6bb11aad7b6941a44d68fe9b44e4e5fd06bd541d4",
+    "Expected": "0000000000000000000000000000000015b164c854a2277658f5d08e04887d896a082c6c20895c8809ed4b349da8492d6fa0333ace6059a1f0d37e92ae9bad30000000000000000000000000000000001510d176ddba09ab60bb452188c2705ef154f449bed26abf0255897673a625637b5761355b17676748f67844a61d4e9f",
+    "Name": "matter_g1_add_47",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe900000000000000000000000000000000104ee0990ba4194916f670f44e254200971b67a18ed45b25c17be49df66e4f9b934bac8c1552ecc25bdaa3af55952076000000000000000000000000000000000591094d9d89afe025ca1832d7f3e60444f83e72403a434b42216b6c4213980d29e4ef0c64ae497006de550c1faa9425",
+    "Expected": "0000000000000000000000000000000006db0cc24ffec8aa11aecc43e9b76a418daac51d51f3de437090c1bcaabace19f7f8b5ceb6277d6b32b7f3b239a90c4700000000000000000000000000000000069e01f60ca7468c6b9a247c79d18cf3d88bf5d1d62c76abf9237408edeba05dea744205ac5b501920f519bb847bb711",
+    "Name": "matter_g1_add_48",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a0000000000000000000000000000000004840d028d0c0f056aeb37b7a8505325081e9822ef26046f2da72f2155c20987dd51f4b5577c5395e24288b71d2ce5140000000000000000000000000000000015f231a233e997633c1d6492e0df358fb658ae29d0f53928c8a0578484c899a699178ca3223772210063aa08991c3fff",
+    "Expected": "000000000000000000000000000000000fa72bf2d7d564cc4982b9f2cdca743d2ac14f0f1be4218dbafb8b93a9277e55273487a5d2857fd3f731ac4ee469a6a1000000000000000000000000000000000fce44f886453c6ca5ebde9af41d2be92d1126e9897d72978a179dd7eebeed6242b6e9718604ab0c9369529a0426a575",
+    "Name": "matter_g1_add_49",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece1738220000000000000000000000000000000004877b97faa1d05d61ab65001110bf190d442cabcd6d4d1b9c1f0e513309aebd278f84a80354dfdef875769d00ec2c7500000000000000000000000000000000187066cccb5008bc2ffd0bcd1b227a5a0fe0cd4984316ba3cfd5113c4632a04c56cbda8d48993bd0dd50e9b7ce2b7ee9",
+    "Expected": "0000000000000000000000000000000019ecd38afacc6b281b2515270157328e18039d51574bae0f7e0ef16c3f6da89f55ddee9e3bbb450ad51fe11edfd9f18d00000000000000000000000000000000088a5e292761bbf7a914a9f723de099035e91bd3c1fe9cd50728a4ceaa4fd3953683f30aa8e70ba0eb23919092aa9e22",
+    "Name": "matter_g1_add_50",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000001881f5aba0603b0a256e03e5dc507598dd63682ce80a29e0fa141b2afdadf6168e98221e4ee45d378cee0416baaadc49000000000000000000000000000000000070d255101319dd3a0f8ca3a0856188428c09de15475d6b70d70a405e45ab379a5b1f2e55f84bd7fe5dd12aeedce670",
+    "Expected": "0000000000000000000000000000000011ccd455d5e3eba94567a17bcd777559b4ff1afa66fd6f05f99c69937404290a2f1c83cfd6c2c25886ebff4934332c0e0000000000000000000000000000000010920aa3d5974df25530610ef466adce3d51fd6a508d4b1111739c586dfd7ba9040836e075fd812fe111d92f25b67f51",
+    "Name": "matter_g1_add_51",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf000000000000000000000000000000000b53e5339f25bcd31afd091362874b5042c0b762ed7425341331630addbc4dccc299936e1acdf89823c36867d46c6f28000000000000000000000000000000000fc3c6b522268511dd52826dd1aee707413d925ee51aeb0e5d69c0e3eb697fabbc14783b5007e240cc0c53c299a40ada",
+    "Expected": "00000000000000000000000000000000060773b9b8f3babdba3db27089b7be3e6e287a635dbae19576039d34ae18a0e6413278bfa280570f6329ae05cdb693fd00000000000000000000000000000000075fb9527f99a8c8db41e67baaf1deafffd2c134badb1b3478a26b5501b31dca858fad6f0d52f412d5631ecfa72eece4",
+    "Name": "matter_g1_add_52",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000001693f4ebab3fed548784264196fb01cf55311399f47cdad74a9543bda5d1ca682a00ee04bb0b3954d5a0f00ceef97a750000000000000000000000000000000017f4019c23bd68e84d889857c417b17aa96c780fec3c1ed6ca75100cc70c97a8bb8272ad4c6de896d76dc2a1b09c7a61",
+    "Expected": "000000000000000000000000000000000a3ea8afdc83794f18f9a9427bcd60a355196925d38fdf74ab09d4a08279647b2da6f1fbe30948a785497d6c6dddc2a9000000000000000000000000000000001263c88f1ca3e574cafac21641432d45ee01e1b05eba95716565922abe28c7f0fb004c255afcbfa10cf7959bbe6b00d7",
+    "Name": "matter_g1_add_53",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000005d5602e05499a435effff3812744b582b0cd7c68f1c88faa3c268515c8b14f3c041b8ae322fe526b2406e7c25d84e61000000000000000000000000000000001038eaf49e74e19111e4456ebba01dc4d22c7e23a303d5dec821da832e90a1b07b1a6b8034137f1bfdcddeb58053a170",
+    "Expected": "0000000000000000000000000000000019258ea5023ce73343dcd201ec9be68ec1ee1cb4e5b9964309d801c2bc523343c8ebc4f8393a403c7881e5928f29db14000000000000000000000000000000001423bf52daefb432162ce2bd9ef78b256ff3b24d0a84766b87119489fd56ecf6156b2884c8a7e1220e493469723cd7f8",
+    "Name": "matter_g1_add_54",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000002626f28d421d9d1c28f5e1eb5a51ada9610dbdd62cd33c4078d2fdfc18dbd092e2847cf705ba5fcd8c1a60c1cc34a3b0000000000000000000000000000000001f7b8cfdb7e406c920f5fdecae45fb4be736f209480ccb455f972c6b1a1aebdd5ba116903c46ded72ce37cd8836e871",
+    "Expected": "00000000000000000000000000000000081d674f5b9c7c64673c39fe33f4f3d77271e826dcb4dfd2591062e47c931237e8539ef9c886c9e112eccc50da4f63fd00000000000000000000000000000000141b700695839110ed4ced5f8a3f4fd64a8086805358ab4a5abd2705592e616cd95ff01271212ca9014dcb68d8157ba0",
+    "Name": "matter_g1_add_55",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df91000000000000000000000000000000000259e307eacb1bc45a13811b02a7aeaaf4dc2bb405dcd88069bb6ec1c08a78905516169bd3440a36921764df0ef3a85b000000000000000000000000000000001263372b675124f6cc19ca16842ba069c5697dbf57730875fe72c864a81189d7d16fe126b5d24953a0524f96dbac5183",
+    "Expected": "000000000000000000000000000000001908aa3a640817e31a4213156fbd4fd39ab39eb931091670a0e06399def71a689e67286f90d38ce9f97cb85f6488d9c8000000000000000000000000000000000764e46b6b82aa2f8862d28e9d543a751a9de855645377b9633cc098c2110ec6ed4fd30f0044ea5868c93f950f6cfd24",
+    "Name": "matter_g1_add_56",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000a138203c916cb8425663db3bbff37f239a5745be885784b8e035a4f40c47954c48873f6d5aa06d579e213282fe789fa0000000000000000000000000000000016897b8adbc3a3a0dccd809f7311ba1f84f76e218c58af243c0aa29a1bb150ed719191d1ced802d4372e717c1c97570a",
+    "Expected": "0000000000000000000000000000000004ad79769fd10081ebaaed9e2131de5d8738d9ef143b6d0fa6e106bd82cfd53bbc9fab08c422aa03d03896a0fb2460d0000000000000000000000000000000000bb79356c2d477dfbcb1b0e417df7cb79affbe151c1f03fa60b1372d7d82fd53b2160afdd88be1bf0e9dc99596366055",
+    "Name": "matter_g1_add_57",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29460000000000000000000000000000000019f60f2cf585bdbc36947f760a15fa16c54cf46435cc5707def410202a3f4fa61b577ab2481e058b0345982d3e3d1666000000000000000000000000000000000a70b7bbc55e1f3e11e9eb7efd79d4e396742de48d911ddff8dd0a7cf10422423d5e68021948e1448e92c2e07c194776",
+    "Expected": "000000000000000000000000000000000a87e7e115ccdf3c2c1a2716491d449c3f8329e73d264088f4af444d43cf05f8be0410da273ce7eeb32969830195b7e70000000000000000000000000000000010a973d6e4bd85105bf311eb0dcfdc0a5d38dba1c099206b60f2e2df4791fd58846bf19d83769506e1561212920b4895",
+    "Name": "matter_g1_add_58",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000109bd6e0636a7f96ffe2ce8e109171efaacfcd60189c7050259ddedd15dd257e11f2585bbd84e4a3f4d8fc5fbc0289cf0000000000000000000000000000000019b420d778da53aed81b48f2c9b9eb399e771edd5e124a41577452b409ca2503e2798cd25d791f489352fc7b7268ae23",
+    "Expected": "00000000000000000000000000000000162bd29f2de10002c1c446bd9583e89751fb91703ad564e7951d41673e28d214729aa9b4b9875c397989df197c912d5f0000000000000000000000000000000004d393181871c93714afab6c33c16f68ec391fbfcad606ac65cc1d070949c099e21f710e2fe0dd4e4f50f99ea2167a7e",
+    "Name": "matter_g1_add_59",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000012bb529b45ad7875784b62a7281d025002f15e7f86cc33555e7472df60da2cb15d37c8bf628142818c0711ee9047fb4d000000000000000000000000000000000baa801623312d95e2b51ce86373fea516007e468f265d974c2327c1779830db180bed6dbe8a64f0959aad26eaafb8d9",
+    "Expected": "0000000000000000000000000000000010c4b328d264893099d89ba81b0765d0642bf36b0ac043be090c7b4f7987d21a906228c3c208c4ec5123d577efb0771f0000000000000000000000000000000016d08ce3bf755da7d4bae5f4b06b37845c17a717329c547e941be93325a04e9a5095d3f6e6c6f9ec3b1a740f59d88919",
+    "Name": "matter_g1_add_60",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000002c9e50f37ff0db2676637be8a6275fce7948ae700df1e9e6a0861a8af942b6032cca2c3be8b8d95d4b4b36171b4b0d400000000000000000000000000000000050f1a9b2416bbda35bac9c8fdd4a91c12e7ee8e035973f79bd35e418fd88fa603761e2b36736c13f1d7a582984bd15e",
+    "Expected": "000000000000000000000000000000000f798f8d5c21cbce7e9cfcbb708c3800bf5c22773ec5b44590cdbb6f720ccddf05a9f5d5e6a51f704f7c295c291df29f000000000000000000000000000000001483903fde5a968dba6924dfac3933cd39f757e2f89120f4ca9d03aaaf9e18252bdb5c5d3939471666b8a42aeb31b4ed",
+    "Name": "matter_g1_add_61",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec000000000000000000000000000000000332cdc97c1611c043dac5fd0014cfeaee4879fee3f1ad36cddf43d76162108e2dc71f181407171da0ceec4165bcd9760000000000000000000000000000000015b96a13732a726bad5860446a8f7e3f40458e865229bd924181aa671d16b2df2171669a3faa3977f0ee27920a2c5270",
+    "Expected": "0000000000000000000000000000000001c762175f885a8d7cb0be11866bd370c97fb50d4277ab15b5531dacd08da0145e037d82be3a46a4ee4116305b807de6000000000000000000000000000000000bb6c4065723eaf84d432c9fde8ce05f80de7fe3baed26cf9d1662939baac9320da69c7fe956acdd085f725178fe1b97",
+    "Name": "matter_g1_add_62",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c50000000000000000000000000000000003ebca978ea429eedad3a2c782816929724fc7529fbf78ea5738f2ca049aab56c1773f625df2698433d55db7f5fc8ca2000000000000000000000000000000000d2477f57b21ed471a40566f99b7c2d84ce6b82eaf83a6c87a7c21f3242959c8423d4113b7fd8449277b363303bb17b0",
+    "Expected": "00000000000000000000000000000000071dc0f985703bd8335093779de651b524c02faca5fc967766abd3f6f59176d2046d7a14d18c0b757b8c9802e44ebcd300000000000000000000000000000000154e5cb66be8979ee276e8e0f240557e3f7dc074c497293af589256652da21d66a6e6b00ca5bfa6f89963fbd5bc6cf48",
+    "Name": "matter_g1_add_63",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001461afe277bf0e1754c12a8aabbe60262758941281f23496c2eeb714f8c01fd3793faf15139ae173be6c3ff5d534d2bc00000000000000000000000000000000148ad14901be55baa302fa166e5d81cc741d67a98a7052618d77294c12aea56e2d04b7e497662debc714096c433e844e",
+    "Expected": "0000000000000000000000000000000012c4dd169f55dfb5634bc4866f7cbd110648b5392ace6042b5f64aba3278f24085227521b7834864f00d01ec9998dd6800000000000000000000000000000000102d7a495850195424677853da01d70caeb6c0af5270bcfffbc2d4252c0f3680518cd8d2a0a6dbbbc7b52923a5b26562",
+    "Name": "matter_g1_add_64",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002218b4498c91e0fe66417fe835e03c2896d858a10338e92a461c9d76bcecd66df209771ae02c7dcace119596018f83c000000000000000000000000000000001990233c0bae1c21ba9b0e18e09b03aeb3680539c2b2ef8c9a95a3e94cf6e7c344730bf7a499d0f9f1b77345926fef2d",
+    "Expected": "0000000000000000000000000000000010c50bd0f5169ebd65ee1f9cd2341fa18dd5254b33d2f7da0c644327677fe99b5d655dd5bfdb705b50d4df9cfce33d1400000000000000000000000000000000088e47ffbbc80c69ec3c5f2abe644a483f62df3e7c17aa2ff025553d1aaf3c884a44506eff069f4c41d622df84bbafa1",
+    "Name": "matter_g1_add_65",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7000000000000000000000000000000000160e0f540d64a3cedba9cf1e97b727be716bbfa97fbf980686c86e086833dc7a3028758be237de7be488e1c1c368fe100000000000000000000000000000000108250b265bd78f5e52f14ef11515d80af71e4d201389693a5c3ef202cf9d974628421d73666ead30481547582f7abaf",
+    "Expected": "00000000000000000000000000000000168af33c85ae6e650375ed29b91218198edd9135683f6a1428211acdcbf16bdf86f0a95575e47ee0969587a10fa9f3c90000000000000000000000000000000012d9f5d692c870b3da951b6d07797c186a8ddc89b9f08a1c0b8f0f119f10ca0b155e8df5424cf48900ad3bf09ce6872a",
+    "Name": "matter_g1_add_66",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d20000000000000000000000000000000002fa19b32a825608ab46b5c681c16ae23ebefd804bb06079059e3f2c7686fe1a74c9406f8581d29ff78f39221d995bfd000000000000000000000000000000000b41ea8a18c64de43301320eaf52d923a1f1d36812c92c6e8b34420eff031e05a037eed47b9fe701fd6a03eb045f2ca7",
+    "Expected": "000000000000000000000000000000000b99587f721a490b503a973591b2bb76152919269d80347aeba85d2912b864a3f67b868c34aee834ecc8cd82ac1373db0000000000000000000000000000000007767bb0ca3047eee40b83bf14d444e63d98e9fc6c4121bdf04ea7148bcfaf3819b70dcebd9a941134e5c649da8f8d80",
+    "Name": "matter_g1_add_67",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000002a540b681a6113a54249c0bbb47faf7c79e8da746260f71fbf83e60f18c17e5d6c8a7474badafee646fe74217a86ca4000000000000000000000000000000000fe2db7736129b35dc4958ffd0de7115359857fb9480b03a751c4fceb9ae1b2b05855398badffc517ae52c67f6394e2a",
+    "Expected": "000000000000000000000000000000000bc719a8397a035fc3587d32d7ef4b4cfd63d4a5619ab78301d59659208f86df9e247e5d12650acc51a3bca3827063a900000000000000000000000000000000150d5519380a65b1909b0d84da374484675d99b00b254d03e423e634a012b286e3fe074e9b0a7bb24ff52d327249a01b",
+    "Name": "matter_g1_add_68",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787900000000000000000000000000000000019d917eb431ce0c066f80742fe7b48f5e008cffa55ee5d02a2a585cc7a105a32bbf47bdff44f8a855ade38184a8279e0000000000000000000000000000000012ee762e29d91a4fc70bc7a2fb296a1dcdd05c90368286cca352b3d5fffc76e3b838e14ea005773c461075beddf414d8",
+    "Expected": "0000000000000000000000000000000008197403ab10f32d873974c937ef4c27fbdb0f505c4df8ac96504705d4851cf951fb0263335e477063884527b21edf160000000000000000000000000000000005396f1affa20ca8530b519a4d5d400969f0c8c8731ecc0944e8086388e89a7ff7c16d9a2a90780972c4762b88a0f0af",
+    "Name": "matter_g1_add_69",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000d280fe0b8297311751de20adf5e2d9e97f0c1bfe0cd430514cfddbafd5cdcb8c61bd8af4176cc3394f51f2de64b152400000000000000000000000000000000039f511e890187f28c7a0b2bd695ae665e89b0544c325a44b9109da52cc6908d81e1a27163a353ab275d683860c2e007",
+    "Expected": "0000000000000000000000000000000002baea63055f72646189bdd133153dd83026f95afad5ce2cffbee3f74c8d47d5480094b2b58b0936c78aa33cd9a8f72f0000000000000000000000000000000013e600456a2d76f5a760059e0ba987b881c6bc10d6161f388d7a9d8b2031921054edfec46afbd80b1364d8e8f6a5a7a2",
+    "Name": "matter_g1_add_70",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000015bad24d12b5d68558e961a17dbc3e1686e1b918e6192ebe6f3f71c925177e61d0162e018ac81126099effa0cadfa185000000000000000000000000000000000de73182569184b3d79dcfa8c27f46ec7a31fe8a3fd73fe26eec37a088461192bdbcf4d4b37b33b6177d6fde015d1631",
+    "Expected": "000000000000000000000000000000000ced641c930387432d512861eefbf2d6131017154f99a0d3d24da880dfd2aaae91c2d9634053fab8b85fc11a7884d30600000000000000000000000000000000122071c0e87fae5031c850dccc4777c3ec9d8463bbc4ed84364d4261bc9d38f696a4320d53eea926a75ed9fcc9789a07",
+    "Name": "matter_g1_add_71",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b256730000000000000000000000000000000015cdf7dafedce64aba34e1f18c57b28f297629c07ee96b732029b545cf5ea6afdf926daa6a48d1250c67aa2a8b797d370000000000000000000000000000000004867352f86267dbe8e32806e4ed02f1487e036051068f8e06d02e8dea6d3773b422e065d2db27c89ea69246d0185351",
+    "Expected": "000000000000000000000000000000000e2c633351d627a075acd1e373bec96ba41b047f0307201f4b7c9978c1a72243d0b18113604cc421b8f66d76ec9b1360000000000000000000000000000000000844e258d602bf9aaa35ce46c4c91c80dd9337053d8ab22c1163a0571fcd1488a2ef57476e2b66dd9c26963b28284d11",
+    "Name": "matter_g1_add_72",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000077eb801bcde78e9dd73b58d2429a907ea0f5600a8005093d471be373bba23ea70bf828c766ccced6a46db84b440053f00000000000000000000000000000000101af9df2939089d72e42fe2dc3de3e32be8f4526a2263ebd872d0080ed4a152107bb3d2f56176bf72d5ae8bd0c30a3f",
+    "Expected": "0000000000000000000000000000000010205c6be10a5fc5390b0e5ae47a8a822c8e9a7a96f113d081cde477ec0de7bf0e8385e61780b2335e4297edb35bcc6d000000000000000000000000000000001796af180463ed70cf330791c8201ee3f0fe52993f64819291bda33017285fcc3a515669b3d48a411276c849fa021f6f",
+    "Name": "matter_g1_add_73",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0000000000000000000000000000000019b09bb7dddd11c5d0e304dac120b920601dd3a3505e478c88850cc701c17eb02aa7bfb20e4017a62fc4fb544d4f9e8f00000000000000000000000000000000048ad536cf89576d4cce83ef065bc16c47f1a28ae27bd71d30d8f2177a9c6f8b2ed0cdf872ead71bc5a1252bccb4a7e0",
+    "Expected": "000000000000000000000000000000000fb047098a1996a625cd19021f81ea79895e038756878d8772aaee9b6bbb66930e474dcc04579ad58f4877b742a890900000000000000000000000000000000017da74a4caefc55794a36eda7938371f42265cc1f2d87d41883152db82873daeb59642e8e663afddd4f24536a1f52b3f",
+    "Name": "matter_g1_add_74",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa30000000000000000000000000000000005f84f9afa2a4a80ea1be03770cb26ac94bec65cf9cb3412a07683df41bb267c2b561b744b34779635218527484633e30000000000000000000000000000000013ce1d1764961d1b0dff236c1f64eabec2ce5a8526edf6b0bccb9ea412e5a91880db24510435cf297fcc1b774b318b65",
+    "Expected": "000000000000000000000000000000000f4ca788dc52b7c8c0cb3419ab62c26db9fb434321fc6830837333c2bb53b9f31138eecccc3c33461297f99a810e24ad0000000000000000000000000000000006785d4f9cdf42264c00fdc4452883b9050eb56e2f6e46c7b8fc8d937dfe4d3ad5072d969a47c4811b36d3887256d0b9",
+    "Name": "matter_g1_add_75",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000f0dd7a15dfc39dc2df47cf09761498b0b363157d8443356e768567f5a6d5913c2a67f12d93df2dcf50756bb686836b100000000000000000000000000000000055914dbda5b115222e738d94fbd430440c99bcc6d2c6cf7225c77756ffadf765b2d83447d395e876b5f6134563ed914",
+    "Expected": "000000000000000000000000000000000ac0f0f62202d09cede55ca77b7344b46fd831b41015eb357cac07f0fa49c2564c2e9d5c591630226677446a9100757c000000000000000000000000000000000ca21d0128ef933fc1a48c1b4967f56912513e63a416d86ad40c0a4590b2edf88e4e8a286338b8b176d8b341ea480277",
+    "Name": "matter_g1_add_76",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb211000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe",
+    "Expected": "000000000000000000000000000000000b728d4ae4b45fae9a9e242524e95e44f175356726da50f46236f690eec17fdd5edce5df1253383378dc8f9c1fee98ae00000000000000000000000000000000131d28a5eab968c45ddc86b82f220dcdeab7c009c7c61986ee4e55045c024e1bcbe76a4e35000b5699ccec5858ba427e",
+    "Name": "matter_g1_add_77",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a000000000000000000000000000000000b35fcf625cde78fba1b70904acb97d7eb449d968e8013855d44292e9c3b0df3cfbcace6f292ec3c7717e25490bb4c67000000000000000000000000000000000af57abd87df55034c32dbe68bd1c0b47139fc2c3a8887b7c151e57b57c9002070337c8dcb2ce2687f9f007d48dd68c1",
+    "Expected": "00000000000000000000000000000000178a19966b5b0fa70c138be7f5ea51d5399c7b8dcc5171cbef82ecb1451aeccbd1ed29170a27f404ebf6daa2ec99bd69000000000000000000000000000000000b1b748494806175030f6b5e2977c58982bd6ec6662d69237f0521351653c772a40035f2504ac8949fb448a901379fd6",
+    "Name": "matter_g1_add_78",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b00000000000000000000000000000000177a51fcc81580ccb7a8873fa93eaf860ca8fedde13cdf3eb53f11e66a1c1e934b82ee9251f711c5c479f33a22770c47000000000000000000000000000000000a0edc9a58f4bb414aa0aeec7bfa6076fb62bdbaee987192c18855adf4e813e7103b943e1dddc24754acfa90600a5750",
+    "Expected": "0000000000000000000000000000000019195049a2d457709e284c84c72a211224efc4d7d46d25c9a537eea94149b06506df02a2a4e0a6428263e9605eaaacb500000000000000000000000000000000061139f9a70ce7cd87ed3a701163bde247382295f557b47a3a0a880d2780f015e8ac753eb3243f9ad138f92c3a2257c5",
+    "Name": "matter_g1_add_79",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d000000000000000000000000000000001552982822e0b64a6204b27da0e192873bb5bd2997784ff0b6ed53801b402501a665c17f0a379fd946ab1adfae43c6af000000000000000000000000000000000938359655fe135dd2a390f83e27273feb68387ba94f2b6f7c15389f8272d64231ebe9c8271de90ff2358d935359ba85",
+    "Expected": "00000000000000000000000000000000168f958a40e85341d90012e134976d1a5839e807948410cc0c81a50961552c052bb784c50da4c734f6aa583777c22b28000000000000000000000000000000000d26998bac6ec11bc5fcf6fe7262c984d6500cd5b21af979048b940e20054f8d759f8a011f3e09d01d10f9cf8ab150e1",
+    "Name": "matter_g1_add_80",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d50000000000000000000000000000000000d94885dcc21b0b98821b6861a4d094e9eb5d5adcf7ca4275c5b759abbf9a9910f3b38073183d54a0569ecbbc1e9826400000000000000000000000000000000034a54b4bbb3f128608a866f5f5c554cf6ad7899f6650ca663a5bd5f1a3e4471e35a2440644c0e4e0a56080936b46d12",
+    "Expected": "000000000000000000000000000000000d4734ab1bbcf9e30cf142a7aa9e8cde1b3c88d92397b8d7d48c7a7402561feee58a810abf67776e1890489efe7f8ec20000000000000000000000000000000005be9e4af0c0c183c43601339f162345f7c013f5941167cd925057e91c4641e19091a20123a36f2e803142833c0bc1ef",
+    "Name": "matter_g1_add_81",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000014f16cbb17e7f63284d8a75968a4c8fc8ee7f37233ed656d696477c507c23e7c7eaf54001f44c93deb14c298aa6f94c00000000000000000000000000000000169bde83e861889c50b2138c76531a5866235d515a6fee4da7aaf8e8b903f2848a9fe7bbd55eac7f1c58ce3a88e7249d",
+    "Expected": "000000000000000000000000000000001400f774b2d932c6b990da6e1b3493685e8f51d429e0c53e9af1b4a2d3876781b790bca4a1bc28ce0240ea21be24a2350000000000000000000000000000000004993fcf5723b7e02095d4ba73ff3194bbe36027bc9099b57084c91c7e7d50b76331bfb06d3c678d3e401bc3f7fcc577",
+    "Name": "matter_g1_add_82",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000009acc4b4678b4b645fde47d1b75a5dda8caf6696ad2bf312dd5c12d7f3ab50b95152f5fe59842650c8a1a785f345c3ab000000000000000000000000000000000b672989004fe54f4d645e40cd29a21418151134fd2b90a68185040ceff141ced7f7ece1fdd9137c32589fa04b105a0e",
+    "Expected": "000000000000000000000000000000000fcb0ab180a69b0a230d9dba98099fdce4969f82fc7e7ad93352a7c8dd448bb0ba9c7d62f53d5dc80506bc36190d9bc700000000000000000000000000000000047b7306f4a53c21d42993c50f2365486d02dac495f2dee4f8971a4af308396fce6c90f3cfde857bf7a2c6bf5d0d8aa7",
+    "Name": "matter_g1_add_83",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6000000000000000000000000000000000198e12ade128447a240e03e024183c401d605cab1ed81f0f5bb7bc4c7cc9c889a2a01f59c0e37a0767a927719e5a95d000000000000000000000000000000001946e39fee9b76ce552108b339b9b24d11e43d3275ac19d2d4bc745c409bdc3f7c473a60c4d3a4d2cc3b598ae0d66880",
+    "Expected": "00000000000000000000000000000000050b45f896fa40099cda8b1f20ab88644915c16f926589cd709e00149b12922347fa7122175424cd44e8875f217b9ad7000000000000000000000000000000001122b7e9b1509efe5616368b14085bdd36fb7adb85cd5a7f23e327548986f5298c045a602b6ee1265d53a4432a4a3c0e",
+    "Name": "matter_g1_add_84",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac47388620000000000000000000000000000000009c48aa2681b3005b24075bb3a122ac100cbaca872f761f4398edaba9dd9da6d04d4a4925028297dfe5f77c2b0b5c821000000000000000000000000000000000ea95c646fb68aa458e69c267a6ca640a6a24d40bdca0161246e4521d13c46facfc1ac86dfc0a804cfa6665cebeec822",
+    "Expected": "0000000000000000000000000000000005325a499aec678ada9eb673d366fe0475e885d5188e2fb687a96949e8f782852fba962197976b868ec083c512bfb66b000000000000000000000000000000000c4d6fcacc8d82401882bee355b37930d83e3cea2e4a7bc133e65a3e0af919b25fc3f30c333873da9406845ce42dbb87",
+    "Name": "matter_g1_add_85",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae80000000000000000000000000000000008e8799a6cc0339e94e861692c81eee53e8a7b326523d5344b416bfbce04290585ef56018834cfd93d234bfa2943369f000000000000000000000000000000000fa1b01aab0878adad693ec769fb68640931c355b3802c51d4a3772300be5b16ceecdc8328a229b3b9f3639170db96f8",
+    "Expected": "000000000000000000000000000000000685ec14da61c48bcb697966aca9e27601db43f0fb1f32e026fb33738eecfbb7012aa1ca3acf36a21fa846730245add70000000000000000000000000000000003fc52a1c3342b12271bbc178545bb20e96e8f1fde673e51f3d27ab5cb42e60aca49c6077e0f687be59b2d25cda9718e",
+    "Name": "matter_g1_add_86",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000bb3a76287fb98fe668cb0a5de603c768340ee6b7f9f686a22da3a86926d8734d2c565c41f94f08fa3ef0e665f4ccb520000000000000000000000000000000016c02dbfb307c96d5b9c144672fe62f3e9cd78991844f246945ee484cbdef2a4c1b001a017cafb3acc57b35f7c08dc44",
+    "Expected": "00000000000000000000000000000000021796fd6ef624eed7049b8a5c50415cc86104b2367f2966eb3a9f5b7c4833b9470ef558457426f87756d526d94d8dfe000000000000000000000000000000000f492dca3f0a89102b503d7a7d5b197946348e195954d23b8ab9ab7704b3bccecaa2123b8386662f95cd4cfdbbb7a64d",
+    "Name": "matter_g1_add_87",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f00000000000000000000000000000000127420ff97df415e336cf3e24c39c161fad630c45c7ccef80f1831c4f5ed54da12f2c49a161e72bc70285fa0498e46d00000000000000000000000000000000013e605c21014f72364f8bff392ce64a10078ea537237fa282d5dd252ba1677b84b8c15d7925e54a4ab36f1feb13d3064",
+    "Expected": "000000000000000000000000000000000ae916770455b0a63717e81802f5a7fcfbcc3e260b7adeca02a61a520c338d495eea29c4f070fd6efc1b8d23eb285e4c00000000000000000000000000000000134784e092744df573ba78f7d6f3cf1ed19491a0fc7ddfa02d3ca043bcf102fd40c33ac44b03a947308e3cc7af41c2df",
+    "Name": "matter_g1_add_88",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab40000000000000000000000000000000016f41e8b098839944adc12481e5f965657a4faedd4f4cdea51a9597a6a0356989e791a686d3d2ee6232ab93683259c6b000000000000000000000000000000000d27b4a56b2cc2216e61eb41061f9a586a704652704906f7fe0eab869ba00d34205ea66f7a02d337d08b916598494e52",
+    "Expected": "0000000000000000000000000000000012842c9d7f4309f6e40124a071d317f5597de419db0d5a8e5324a517f7b61dfdeea2fb4503ad7cdd8deb8aaa5c412554000000000000000000000000000000000ace4d9f98ee6e8a4416ef14d64f26dc49e102e69eced46ef829a352e58e8c1a7e1f083e3f4fc07f24ccd1685dedf215",
+    "Name": "matter_g1_add_89",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000019e7c8d182e3b674dfa21539613f7de5d4872d4f4732307a5c6d95ada7e81a01bc25bda34e0b46634e0b0b32cd47e8ec0000000000000000000000000000000008149237de73ab46d5c20dfd85b07f593c0caf2e2e364335450e3ebb478a9f6b9ac0af89174dffd92eda2783a5271f01",
+    "Expected": "000000000000000000000000000000000875289fdaead079a283aafe4de7035c88662642b6bba389b17583f8e3b5801dada6e46bd897af961997665e6ed4a55700000000000000000000000000000000050a6b9c1db35865df0a042d27a042ff4b8d3bec2fba6a3a28a71c5a574620dc05cda0e70932ce9b8966e4592220c147",
+    "Name": "matter_g1_add_90",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6000000000000000000000000000000000c0f33f2d76366af661d6fa58a8b5aab207d35ce03899e495f7ddccedf201d9816f270468b207413a2ca70380c798fc60000000000000000000000000000000002a7dc7e2b163e65cadf93b5d682982288c8f36d08b1db8e0b1cb40cd3c7231f3f1672da42b4679f35db2076a8de5b42",
+    "Expected": "0000000000000000000000000000000019ea92820dcd442358db359146797aa82beff6154946b1ea14dccae05e8252b776b817dc044a20764e3514cd22799c0b000000000000000000000000000000000ed929fef2cb11e8b6b9b5d52bfde82080eda747f0c82f33b9cb87019476f0c128e6b918a4486172dee2884ba538ae5d",
+    "Name": "matter_g1_add_91",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000118fb45274a6b0ca9fe2654821e3b30caa46444f7c64b1921cf16dfd56a43916947d4fb6968d718a59a30ed38d65ce3000000000000000000000000000000000110e8e73e640bbea6927cd770baaf887c8e0e0c58260bca489c39b6dd7a24ab8c0c0a2495133d8ff8c7afb9790b37faa",
+    "Expected": "0000000000000000000000000000000009452bd0a167683e30c673ffd4e750c66a81edf309a8d2d6dd915c358b30b0ffc001c4165b1b17bf157a0f966bfd91d00000000000000000000000000000000015df0b1ee359dd3e35a7b2c33edbb8e92b18804ae3359a369c6a529f5561298e6be9a3498c9477f33353124af7e91968",
+    "Name": "matter_g1_add_92",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000005dcb54cdf9635db275540c16307fc9f07b4ca5cd91e3977e4b95b58e8103e40ed9fa74752b2a43d95b6acb6f5fcbf440000000000000000000000000000000007ef8457752a47864ef2698176a53990e4822421ecf83b2716251e3ce69151ab2767d4a6611a0a6e0e40a57164ffb94e",
+    "Expected": "0000000000000000000000000000000011f1ac702a06699dd64b63ebdd8b5381578f63b603c63c3a47413fe764af239ab7024712320f3ea3daefa6bd3cd3dfe9000000000000000000000000000000000918bb83a22b4fc66247e007c17155c4c2ec6326131c10fe04a5f9b82ddeca3d21c7c397a70a3949fda4d766540c85ff",
+    "Name": "matter_g1_add_93",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35850000000000000000000000000000000006d3335e092616363e94436bb68be89667c706564ba687f4a3494fcf7da62fd9ad8ae68cb76524926c261983711a14ad000000000000000000000000000000000f085a3d013592c402a380e2e8d9019864a775e7b8e8b94603c8cc1eb1def1e91075fd5675f76534397e2a7d76c2331e",
+    "Expected": "000000000000000000000000000000000344951ccb5e60d1838f7793fcf8b765f5f252b69e1cfdb4bd3c20692c8ffa01afbda6950974a65f6ac74afb9da5942e0000000000000000000000000000000014f5f0e6b99a04d1c5c2adf96c53dd41f8c01aab8db4f0e6d7fc5eab27f6c03c429632db4e1c21467c09d8a54066a4d3",
+    "Name": "matter_g1_add_94",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b0000000000000000000000000000000019e2ed6e9757e2339d013078fac91c966045f7a1416a56135d75e603c2021a8bebf4acbf6c0d5ba911f66510e9a7ad1a0000000000000000000000000000000008b8585444ffb3bd4fb6ee23e8128142aa72fd574a506151a0eea8979cbd694e03897caba63771b0490d46063bc5bb57",
+    "Expected": "000000000000000000000000000000000a449fb0da911c544887b24860bc5fcaaf054041cc80f16bbb44c796520bee454d0d06f84fd5aa179a44fd4fac9f144a000000000000000000000000000000000fca81401349089caaef9156a86c64271c77235c9efd136dcfad9894450b076cb3dd1a05bfa1e62ef904435eee5d2250",
+    "Name": "matter_g1_add_95",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65000000000000000000000000000000000f4a256b4288386545957a3ba28278c0ce69a8a412febfed1f952ca13e673822bacb6b7751ea75893b680ea363aab66400000000000000000000000000000000152379d006e74798199f83b0c6c22a98440ef653d7f0a8c5e3026bcdabec8be59a3cc291ba05860bd0639c5c5f5bee26",
+    "Expected": "000000000000000000000000000000000c427721953e139d4f12ad2a3f8f91a4caa49875a87001b619c8a6e909a7da8ddd9dd026bf56d5f85d49fd17527106a800000000000000000000000000000000018add2816914ef51a289e707ba0224fcf0b7bcfa4001487e90dbdce53f1b596e1f5872de32fcee6f63bce4484ccbef7",
+    "Name": "matter_g1_add_96",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d220000000000000000000000000000000012dae9aee13ed6ad52fe664bf7d2d0a1f134f0951d0d7ce5184e223bde164f6860967f9aaaa44fa6654d77d026c52d2a000000000000000000000000000000000f71889d64ec2f7da7319994883eb8bd1c753e6cdd3495036b630c35f07118a1bc10568c411ecbdf468a9cdaa9b4811b",
+    "Expected": "000000000000000000000000000000000275b8efb3a3e43e2a24d0cda238154520f0a2b265f168bfc502b9cd4a07b930756961ae7e4fe3f01a5473d36ce3356200000000000000000000000000000000113403d5a968f01ba127dd8ef6c8d7b783a10d039a6b69c617032eba7122e9297f3ce2360c829ae64fdc9794695bf173",
+    "Name": "matter_g1_add_97",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe54850000000000000000000000000000000004e9dd69012ab596b5d3f1f8e4593b448685fcec4ab3394008178b137b762ddf9150cbb8dbb74c8af45bd8baab9a6c4f000000000000000000000000000000001132b66a2127885774062732127951f051c9c3c9b5aba02406e3f3cd4ecfe2dbf6614ebaca3bfe9efbe4f6e5b15ba0f5",
+    "Expected": "000000000000000000000000000000000594c808954bb930bd038806500c9e3fd6460a83554e945baeeec2354a3805f046c76aea62c249080f16ae8e70f8fa6b00000000000000000000000000000000046924a32fb3f2df9a52615e45eeea2fa3ac0e2ccd38458194ada6b4d993ecdc0f441e41d0ea37599254a06aef68b9ae",
+    "Name": "matter_g1_add_98",
+    "Gas": 600,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b50000000000000000000000000000000017a81b957a12adf474a2913e8636f169ea9cd10be62c16b88f95f5caf661f158a032a9f7d249fdf2765caa1564bed0570000000000000000000000000000000017fbf2abc62dc2678b65d509e19c9c9c5d961c72565649a078da8dff98be6236ef314e9ff8022f639ff565353345c230",
+    "Expected": "00000000000000000000000000000000002c8bc5f39b2c9fea01372429e92a9c945fad152da67174f4e478fdead734d50f6e2da867c235f1f2f11bdfee67d2a7000000000000000000000000000000000c1dd27aad9f5d48c4824da3071daedf0c7a0e2a0b0ed39c50c9d25e61334a9c96765e049542ccaa00e0eccb316eec08",
+    "Name": "matter_g1_add_99",
+    "Gas": 600,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsG1Mul.json b/core/vm/testdata/precompiles/blsG1Mul.json
new file mode 100644
index 0000000000..0e166a29ca
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsG1Mul.json
@@ -0,0 +1,730 @@
+[
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g1mul_(0*g1=inf)",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g1mul_(x*inf=inf)",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g1mul_(1*g1=g1)",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000011",
+    "Expected": "000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436",
+    "Name": "bls_g1mul_(17*g1)",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e",
+    "Expected": "000000000000000000000000000000000f1f230329be03ac700ba718bc43c8ee59a4b2d1e20c7de95b22df14e7867eae4658ed2f2dfed4f775d4dcedb4235cf00000000000000000000000000000000012924104fdb82fb074cfc868bdd22012694b5bae2c0141851a5d6a97d8bc6f22ecb2f6ddec18cba6483f2e73faa5b942",
+    "Name": "matter_g1_mul_0",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d",
+    "Expected": "00000000000000000000000000000000195592b927f3f1783a0c7b5117702cb09fa4f95bb2d35aa2a70fe89ba84aa4f385bdb2bfd4e1aaffbb0bfa002ac0e51b000000000000000000000000000000000607f070f4ae567633d019a63d0411a07d767bd7b6fe258c3ba1e720279e94c31f23166b806eabdb830bb632b003ca8b",
+    "Name": "matter_g1_mul_1",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1",
+    "Expected": "0000000000000000000000000000000014f9bc24d65e3a2d046dbae935781596fb277359ba785808fd9ff7fd135ba8c1ddc27d97a16cc844427afbf4f8fc75a60000000000000000000000000000000017e3a485f84e2f2bdcf3255fe939945abe60dca5e0ae55eae9675dcc8d73e06d00b440a27ab4dc21c37f0bd492d70cf4",
+    "Name": "matter_g1_mul_2",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a",
+    "Expected": "000000000000000000000000000000000827517654873d535010e589eaf22f646cf7626144ca04738286de1f1d345342d5ae0eab9cd37ced9a3db90e569301720000000000000000000000000000000002a474c2443d71b0231d2b2b874a6aeac0452dd75da88e6f27949edafc7d094cb1577a79f4e643db42edcaecc17d66da",
+    "Name": "matter_g1_mul_3",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89",
+    "Expected": "000000000000000000000000000000000d7e5794c88c549970383454d98f9b7cebb7fdf8545256f1a5e42a61aa1d61193f02075dc6314b650da14f3776da6ead0000000000000000000000000000000002054faff236d38d2307aa6cbbc696d50f5b3ffead1be2df97a05ebbcbc9e02eaf153f311a1e141eb95d411c0ec6e981",
+    "Name": "matter_g1_mul_4",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944",
+    "Expected": "000000000000000000000000000000000ff16ff83b45eae09d858f8fe443c3f0e0b7418a87ac27bb00f7eea343d20a4a7f5c0fcc56da9b792fe12bd38d0d43c600000000000000000000000000000000042a815a4a5dca00bd1791889491c882a21f0fe0a53809d83740407455cf9c980c5547961f9ebe61871a4896dace7fbd",
+    "Name": "matter_g1_mul_5",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1",
+    "Expected": "0000000000000000000000000000000009e425f5bdc7df5c2a72303918e5a3c7d2fdeeb071179c533f83cdcf38dbbdb1ec5f4ebc85f3ed80757641ee3f8a8637000000000000000000000000000000000819a3e81e9ac2baacdc778225129e16344107517157ab2a7bc5e3480938585c55fd2dd7185f52251f5ab191f162cf5d",
+    "Name": "matter_g1_mul_6",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c",
+    "Expected": "0000000000000000000000000000000015e6bea7ecf15d91bde67231f794397502c087960fab36d905137ce2608172b5a5def065cf7ee567ca7fb08a22adecf80000000000000000000000000000000001eed472d6138fbc56e10edb62563c086fdeb9acf6de957f2367db7f1c80d2c23197c09039ed55e65cb56de9fb9be64d",
+    "Name": "matter_g1_mul_7",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108",
+    "Expected": "000000000000000000000000000000000220a71ad70fcf7e47df60381fbd1aba33c03a3f8537ba2029ad8e99b63c8677e0183f0b5bb2a5e1b23bc56693adb45c0000000000000000000000000000000017f26ac6ffc79ded7c08e08673336402f47ab48ef9ee2e46e3265e5cbb790cfc86f41bd1b578c5891eb052d11197c850",
+    "Name": "matter_g1_mul_8",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672",
+    "Expected": "0000000000000000000000000000000006b27724c4898b4f71be9727b773709a7905997d06a41ee618b7dcf864d7457bb3241046f0139c1d678b6ba6226f090f000000000000000000000000000000000b20cabf58f9c29897e20e91a9b482f5f867bef45ce0941cb8850aaa2022182298a1a24655a4b905f436520cc42a30cd",
+    "Name": "matter_g1_mul_9",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea",
+    "Expected": "0000000000000000000000000000000004745f9877b3a0851df5bb770a54c69d5355cdadddc9d961e2bfdb3d0531d3d0f780f462335289be29ad4c62cb1250a00000000000000000000000000000000011034a094f59212c29e3f91c48df670e7a4021e4586645d250ee74a90f4b7b51510a5048dba3b555511c327ed211f81f",
+    "Name": "matter_g1_mul_10",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76",
+    "Expected": "000000000000000000000000000000000841c1538c1a3b54418c1c5557a5815c9ed74f6e1c8ed70e1ad424220dc522c530e2e48affe6cb3190abb25af84b91a300000000000000000000000000000000167490a2aa6c8796736cbd364a4d18007ecfee403bde5dc13c611a214610e85af314ddddbf05ea129e027e0ae8d89b36",
+    "Name": "matter_g1_mul_11",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c",
+    "Expected": "000000000000000000000000000000000ea1f952d65dbb9a40209aa89e367d9d75e1b4c3a70a609efda5fbe7f5c5483163671da425545d3f1afb817c6d8c59a0000000000000000000000000000000000cd537dc11cc63dd15c8ff74d15961390eaee59b2d5697b18c1ea6d534d71551f5e195e8a0793140d821dde97dc77623",
+    "Name": "matter_g1_mul_12",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a",
+    "Expected": "0000000000000000000000000000000004c92b7cf9199f47008dd561e624c822a067c57fdea9d016f79e6c7956dda9df0e36b4e78715f3da1319af9f4f1fb160000000000000000000000000000000000d2851d68617567ad5308f69dc5dbbf37603c2ba48cb3759b70fc4301fdce3bdc9fca076e2ae09562396c1b8558ccdcc",
+    "Name": "matter_g1_mul_13",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054",
+    "Expected": "0000000000000000000000000000000006ed98add25d64f7488ed270e0899ee3633c84b73de26557c552017e7cda4cba1228c15e87efb5a740284dddb8cc80de000000000000000000000000000000000b363e14b0285fbd24eaacfe80b992d8df1abfe83991cc55b0484076385374bc87d9c7860177f06143c600503ac54577",
+    "Name": "matter_g1_mul_14",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746",
+    "Expected": "00000000000000000000000000000000164671460621354cd352d93ca7de51828b3e6db0a37d2894a0ac475a5facdbc3ca5909d3bd7553271dadaa68b7474e2c00000000000000000000000000000000188827c6e2f4e9796c71703ba53ba2ded71bd6e8280e047fb6ea440b8dcafa7c4252d26bee1780ac67790e0d603c8ca7",
+    "Name": "matter_g1_mul_15",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf",
+    "Expected": "00000000000000000000000000000000023b2129ac67abc79966102ba223b982d40ca83e9b1ce33dff681c751b3f0c692f8bf19fa0394eae190767899829d1d10000000000000000000000000000000015449c6b5ee2c9f8b28e9732c9ebf6ffee5048263f7b5050a5ac9a76b034931a5c034f91d24b461636f5b116e37a26a5",
+    "Name": "matter_g1_mul_16",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da",
+    "Expected": "0000000000000000000000000000000004edac7b03b5861d178bb4aa34e795c776fd95e7c0980f19d111ef208ca4854f73a3ddc219bb6bca173dec67b0e863a00000000000000000000000000000000004dbff672368f86e048c3e33cbe90aba570484b4ca2221f7f6adaa1738c369f4c02c0a10118e84ea8e53cfbaa10fa48b",
+    "Name": "matter_g1_mul_17",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833",
+    "Expected": "00000000000000000000000000000000169d637c52c31e4c62c9563a508869f7bb5adc7defedb5f4ba9f3eabe517fa8c0be2e44d656e50903dcab67a6a44984d00000000000000000000000000000000192b39d5cddac36940d896a738e25c25217768e1d0ca712968718b8fd9ad492bae63063b3cb168368c3df196306b6a1e",
+    "Name": "matter_g1_mul_18",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f",
+    "Expected": "000000000000000000000000000000001608c3bfb131eae485545b7d19b8f42de18dcea6a0db3279eac2b7c008fbead54046bf13dd63835abe9c63110e12526c000000000000000000000000000000000abb41b2f17cfcc2292c5bf559b38af3b25db40121c6a5627997f65765eee1743c204f1161abe3f71ac1fe4de6aec1d7",
+    "Name": "matter_g1_mul_19",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc",
+    "Expected": "0000000000000000000000000000000016e3125ae97a2b1184e2c6dfe5d9459ac567c686e65674f3b0513df6de5e80d1efbff3c254e509eec3f951b0835b5829000000000000000000000000000000001889481258d3e898ed4e4a43e74c0eda5ba26c0b7525973ca86b896969240ac5928ba58bc86ec17a47f2469d023682dc",
+    "Name": "matter_g1_mul_20",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52",
+    "Expected": "0000000000000000000000000000000017d8c0aa81ca6a1e4de8d0b8b3a13b1d6350f79ee8439da97a5d564d435f4d40bde99138b67284beffbb176daee92352000000000000000000000000000000000a04e0bee6b9681db56604a6dd5e41c072e84f8ee9cb4054410eb610472b96c09802a1d70e325c40c7ab7e248eb2e3e4",
+    "Name": "matter_g1_mul_21",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1",
+    "Expected": "00000000000000000000000000000000089ae9fc5cdba1a24ca87fe4f1207d1a36c494d842eed330069f988d3bc8554af1deee3a5c59b5e74729097acc1185fb00000000000000000000000000000000002fd95001da3011b48067d351ec8667c2b2390b23fa0948896725292311dbae71b51d6d5d57e173970bc992d11fdd11",
+    "Name": "matter_g1_mul_22",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9",
+    "Expected": "000000000000000000000000000000000548e7564e09c2bad9859dd63dd1045878c9b257015558b18cf5911d1763325e411c1fb8af52e8766fa7adae83eea12700000000000000000000000000000000111235351d136905fd19fa726eb6626085875c33c98067a01fde9688a5b2c289cb8e3f5d6a85d0829200a355c82f423e",
+    "Name": "matter_g1_mul_23",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b",
+    "Expected": "00000000000000000000000000000000165504769c7ab0d28b39f38f3bd09cd47c63b74c57d39935d1c03e262f9da0e8b0b9264b0d8e2908423fe5c74288c208000000000000000000000000000000001680df1d577bbbb66ffa10258bca54b74cd90a7b3f3d50472e70e18ef54b7a4412e9eb93e39b9b312e3e8e00a52e4067",
+    "Name": "matter_g1_mul_24",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6",
+    "Expected": "00000000000000000000000000000000087a52e8eadd5461e202a640024fa17e201a9f0a2984be3fecfdeef86abed72d059e8879d0be8789f2a6db0d2cf55d3400000000000000000000000000000000196fe307db05207661a5a5f8f7fb24d8fea18ef91941ea7febbc18819f49f73aef9dd1bdf4fd605e031dc04f16fa92e3",
+    "Name": "matter_g1_mul_25",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36",
+    "Expected": "000000000000000000000000000000000301caf675cd5359bcc274b6141bb6ac53ab6a86a38ad4f8c3233cc9c1a77723eb0de4a2014e556185947dc1ef6624e3000000000000000000000000000000000136d286e623637f12c8b86cd9fad2bed8479ace5189e064a4e12e6e641447dfb0399757026126ad2d169c05011f5031",
+    "Name": "matter_g1_mul_26",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc",
+    "Expected": "0000000000000000000000000000000004ce73cde58c9af5d1f76e100849b0ba3d3cc6491e76b39cf4d7b681fed0686396440f6a721f73b31fb14b4c7624c176000000000000000000000000000000000e26b15c1051d7b049e82476a30545cfa4bf0a2075681d7028797c528712c7fba7a59145c9dd9ca9f5e9b1ac8a68b126",
+    "Name": "matter_g1_mul_27",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792",
+    "Expected": "00000000000000000000000000000000028a89c904f63eb8e68096bd2001458a4b9b32556c93fab5e52ab26ed73d62f0489d6bf1906a62c8148d50d30222a65f0000000000000000000000000000000007e54f21e2ac6d5287289ed9e2a15d457b5dac22ef36c19cb28a6cf9a0d11c981bf6549ddaf7ddc0a59b3d3a4698d975",
+    "Name": "matter_g1_mul_28",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c",
+    "Expected": "0000000000000000000000000000000008c39ee7c8d86a56ad1a9dbe005b4f0d44849d6fea6bbeb0732de725ad561befd49d465a134bd1a63a39eadbb6e0bce1000000000000000000000000000000000d5c892c92817fa24afb0a0fb319ad21e309edfb6300397a215e34eb3aadf91cb41b4ab1c5273bfea6eaf33982c75eba",
+    "Name": "matter_g1_mul_29",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa",
+    "Expected": "000000000000000000000000000000000ba1650840e24c0f99ddd10a6c3341661e5c96b2e95cb6bda3340e7a0167c906e2f0ccbac6f0be2d7dbb3f9370a5ec960000000000000000000000000000000011638a3d9a81c0fe2ebb547808db758c7cfa8648b4835fb8c4931fd622da3a001fbce9a21d61f98f35b1e907913ffd25",
+    "Name": "matter_g1_mul_30",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c",
+    "Expected": "0000000000000000000000000000000000eccc25cfd8c5a58b330a74b92af0c2b932772eacfe898ff3d391fad5dfba52a3940e8edfc9bef5c4de670207c8585100000000000000000000000000000000095ae48a94c92c332915b0c07511bb0d54c316ff3a0dd2509a18a21320b506bbefa76a459260efdf4c045404f02e114d",
+    "Name": "matter_g1_mul_31",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd",
+    "Expected": "0000000000000000000000000000000017a7f3b439a98885994a6832b6394b0ec9968f665b5810da58e3ece3d8e8694c482a15d3129732b43d4b7008660f19c000000000000000000000000000000000195299086d3b9448b26fe830522d520d132ed59744e677e6eb114ba7d7045019a0d0386cf817701ca3afad2a0487a689",
+    "Name": "matter_g1_mul_32",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45",
+    "Expected": "00000000000000000000000000000000063c123a3cdb92469e7e57a18eaf3e7cab1d85d64cbcb52499d2e611e6ba71c717b0ebaf4cc9208b18c925a5ec167b78000000000000000000000000000000000fa5e78ae10ed8a4dee9440bfc7637d903404749681f85bcb62444d921c4fd809a646ffe3bb7c70dc906d07c62381415",
+    "Name": "matter_g1_mul_33",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b",
+    "Expected": "00000000000000000000000000000000192b1497c71eb894a7509bbdaf308428e4d5899edb15f9e6e45a88340f55e1b76ee0901a830b66114deccda63a913a6b0000000000000000000000000000000017d58bd474a61ca0ceb23ec392dc08abe5697b8394fd60440cf787f15cddab36aa99c2ec2341bcc06dc1771b5f0fa139",
+    "Name": "matter_g1_mul_34",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca",
+    "Expected": "0000000000000000000000000000000015f72ad769cbaa2bbce0aecef9559b825ba4ec17ec5be2d9f0dbc7184383eb3e201de5163e71f1e71655acd5ee1fb30000000000000000000000000000000000194d27d9045b9760e66b578af24b282d9aeb28eb51206d2e18dc04bcb6df90553a846736afd92b23aa004f8de90bbf9f",
+    "Name": "matter_g1_mul_35",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc",
+    "Expected": "00000000000000000000000000000000146f12001844bb0ec185e773175634f2e56bfa7190caa851ad16443b629b375ce3967b0c936d30dac2f126343722ce5e00000000000000000000000000000000080e8e90ed0d259ad803269711e511577769f7886b425f9b7857dc90ab36438cbd7435f6eecf2328f5fb6eb56f370163",
+    "Name": "matter_g1_mul_36",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447",
+    "Expected": "000000000000000000000000000000001344d2c2bc5ef45dc69597e948ed6021d84f7bf2c36119869a3f84288f3bdd6fc3a0de2b9e2564a930c2207c1ee36a0e000000000000000000000000000000000dc4d15ae09642ffa17d77510fb1ad4bf9e06084e9d352f4e234ea35f33458df4f23a209e29da42c41fb9a3cec3e8242",
+    "Name": "matter_g1_mul_37",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828",
+    "Expected": "00000000000000000000000000000000084f2ed8573d5d04e41909d5c8ed3feb88f572726fc86d17d466276342f01503f7c8552498f8a7e96c875c4928b808f2000000000000000000000000000000000b618ca81b6ee891690099459634e011b5f59fb5c96488b0205139a65c77f15af135b3528a5ca3b794e7b2991d2434d6",
+    "Name": "matter_g1_mul_38",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a",
+    "Expected": "0000000000000000000000000000000014733ee8425f42a30010366e4585cbbbdde6ed602a639bd299e63c113db3d797fa01075e24a042a060a043c9e1fa79f40000000000000000000000000000000013b44e1932681d238c52e959e1e3daa7a2e1ac67252ebea0cae90e8249f85b61812b9e09203d38d96f4916837b3693c8",
+    "Name": "matter_g1_mul_39",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb",
+    "Expected": "000000000000000000000000000000000ba15476a1346fbe9be2720721b592ce7c111b95f0b8738495e6c28487e12fcad60006314dfe68789e60f4df2db14eec000000000000000000000000000000000b44b9a9f695c94ad206717daa3128b672924d0db83ae0d47b62b3c79428f6fe151a65a39ae411e18b128d6796b67bbc",
+    "Name": "matter_g1_mul_40",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425",
+    "Expected": "00000000000000000000000000000000070dfc697f7068180a7a792604d7b8453dbd393c993be9829a263ad5864c3575d3fb235692ab12a4dfa4221bc6e0c6d600000000000000000000000000000000123a9d9b83e2ca7c95de9602116b1e14d48175073e1fe766458e3fd4b6676f120adfcc5c497febe2f7ff68b1e3508e3c",
+    "Name": "matter_g1_mul_41",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96",
+    "Expected": "000000000000000000000000000000000dcad6e29cda2332dff09377460c7a2b9d908ee53ab13f648cd892bf68a44ffcc8cd5d501f8b068f506b506d01d3f4430000000000000000000000000000000003aa625a60932474ca3f914a3e0aa8384533723f824b12c686a64863a734d96ba13670c8b355b52b0c01b49fbffb6149",
+    "Name": "matter_g1_mul_42",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc",
+    "Expected": "00000000000000000000000000000000023909bac6048bff0373d27a06dbbb8aba8ddbada93f4fea65c983598307f3c3a8cbe163462484ebb88165c6b6da41590000000000000000000000000000000002162d8a498670158c23daebb724168b5379d9124b064de871674a3ecd15e6b546366287563928a1e279fb1eb2ea0ba4",
+    "Name": "matter_g1_mul_43",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca",
+    "Expected": "000000000000000000000000000000000f79050036c4bb6c6b8e91abb300dc49a75b32faaaeb258661c905b4d936f4096d59de89b911de294603a0e3443fada5000000000000000000000000000000000985105497cd87d5ae2698479da55f6be9bc2cf5a2093b651d7305b67e36343debaf19c266ccb55c23f3de55bdae23a6",
+    "Name": "matter_g1_mul_44",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a",
+    "Expected": "0000000000000000000000000000000017901e77745a98c09d6740597c40f27df841cca6dd95653a1da6d8eb1c57d5ebffa6a7b894369b6b419c61462697080b0000000000000000000000000000000001732540a1bfa4a1a851106209ce4807d7c0a33816d3742ad5e2729229f3403940e03b93121b79bb94c24f7e60539ece",
+    "Name": "matter_g1_mul_45",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4",
+    "Expected": "000000000000000000000000000000000f990d646495fff77d090f4a69b8af0e1762982b53ef8ae9bb955ad8b894942b85c7726587c9fd956ad58eb9e3ca25630000000000000000000000000000000007b7315e1f93cfba8076cf539aae01fd3bbe1cf92daa168a6fd6a2e7c969d35c51fe7eba04f1e0dd3e2020635f2c4f09",
+    "Name": "matter_g1_mul_46",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556",
+    "Expected": "000000000000000000000000000000000de9d7e58919ba6386f32af53ccf36cb0b834855ac8dcc19af3c3c9522c3db2985e51ba36067b61181cb0fe8b47d853a0000000000000000000000000000000010ff0800ed1b4067f8c920462f7abd7361dac2371716f7b8648d64a71cc7d53265db6d80b26b9efddd572a2273ab1b17",
+    "Name": "matter_g1_mul_47",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7",
+    "Expected": "0000000000000000000000000000000011a11cc098144fe9bd42ec8845be76b6cae4b3001a79f4bbbf9f20e8ac8bca5b37ef8006c958318c3894aac7d6bf77e8000000000000000000000000000000000d5c1e6b78c40a356a35bfabfd66a81924d2eae6d428b5caacf8f3992ab980640e857e756e649ca83f5aa4bda7cd00b7",
+    "Name": "matter_g1_mul_48",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602",
+    "Expected": "0000000000000000000000000000000002e72f4568780fb41858edc3f5796f7936a30ee9ddc7b5034d9341614d301c7906238bfde3bcb77f063fe652a43b88270000000000000000000000000000000006f971f4a8ac554df7ae7ecdfab724410f1948af994d760c5f5977961f891ba4f4e76b27c3f0e5a1471ad017e91a9af7",
+    "Name": "matter_g1_mul_49",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013",
+    "Expected": "0000000000000000000000000000000014b9ef8878af80f824748389d608bc9d0ffbca96230ed590d8e351586607a614f2658e348ac172f3184c1e5fde50f550000000000000000000000000000000000630f0556407c140d0a05b10ea65de48e4866e040455ebcd54fb6ed6996a6a3ac7a94a6818ba424936fa505c2c364124",
+    "Name": "matter_g1_mul_50",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8",
+    "Expected": "000000000000000000000000000000000e66c8be115a941ef7adf4490faea39149a3d812c29d4afb36febe3f813c7390a715f838dda90cd73556f89abf3949120000000000000000000000000000000015d85c185cb86af3ca1c526ffa6e9459a9c699c5a4d57278f33b14691e980e0f86b9239e626fc4064890cb610f10e496",
+    "Name": "matter_g1_mul_51",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b",
+    "Expected": "0000000000000000000000000000000009db6ac72cdcf1f69c6593bc183aaa2b3980ff78a4417e23243f81243987ec6f2636641c9e9c738c7af2a1e9f94149d0000000000000000000000000000000000ca7537c04c06607e42403e84e7d9e55b2a06c730ec342f16d03689bb684918e85f637e7a6279d95cb7774f106139d0f",
+    "Name": "matter_g1_mul_52",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244",
+    "Expected": "000000000000000000000000000000000620b092ea8cb718ae9669da4ff2faf639fb5e657b7759fdf292e6d841b51545afbabf95a98601847f64fc7367f872ff000000000000000000000000000000000a14bfc0e328310d62f116652b1de3a18282b122e0e3965619a099466986a546b73696274e12bd395224018a48b3d80d",
+    "Name": "matter_g1_mul_53",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83",
+    "Expected": "000000000000000000000000000000000a633928be3f3bb4c94cf4d8d7a8169779f8bd4bad31ede895937e8e8b0ddea956d255776141541ef5791aa3a0bc6d360000000000000000000000000000000003dc3b703753a7b8ccf7676b04cac8021aa311233a99e8d5290655d2f84555dedff62f9f81322307b538c3f3458f6313",
+    "Name": "matter_g1_mul_54",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852",
+    "Expected": "0000000000000000000000000000000014911a8b41cb65cb7ccb940a472cfa58861f1a506a4f719888eb35d48ed9774ea0a0dc3ba38760253bedb4a1acd0963a00000000000000000000000000000000031388c90440f22cc63a1e9450256e5cfcf2f7448641ac66b43d542c4b77e9c590b957efdb1c6d75846b3faccf033276",
+    "Name": "matter_g1_mul_55",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c",
+    "Expected": "000000000000000000000000000000001968070c01f0aeeb42ab71730f5b78ec122c10ca9dac1764ff5e916fc85a5eb5ed406c03263c57858fb03b15ac0035550000000000000000000000000000000012ecfee330e1cc8006c73e9d41ac1947b67f8704d12faf8c0c05c2519dca68be7bdf88a58eb4825b35a1d270554d6ce9",
+    "Name": "matter_g1_mul_56",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0",
+    "Expected": "000000000000000000000000000000001469e7ab4c3740701927da2b0e34508a73387aea671857b042dabbc65cb849f8c8ed0b7f8c8e37f80aeee98ba953f4e4000000000000000000000000000000000674212f9f8e1419608ccf1a0447533fbd6fda87a35cb9fb39c8a7daf5d12f450c12bfac9e9f872b2643b1f8f201439a",
+    "Name": "matter_g1_mul_57",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45",
+    "Expected": "0000000000000000000000000000000009c756aec59a68832728b1133a69f0794f6a082e2f0f161e488078bec7420a0da19e812def625df9b12aa36d94d8a38600000000000000000000000000000000014aa28b18771ca07b7627446eb60d53bf4837541da661a0e5cadcfeaf58f5a650a39ac304f48e45d9b714cead9ba5d2",
+    "Name": "matter_g1_mul_58",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f",
+    "Expected": "00000000000000000000000000000000153548fb1d7f1721c7fbdfeb167e1c060a90aab8f7b6572f4a2707de91b03a7b5e68f792a18d940167ae83d1380d6653000000000000000000000000000000000113bb747eab3987cd195e9eb755735698993332d517890f4e3285bf7274f8579ffcf84908a4758f0bb932021f2c76d6",
+    "Name": "matter_g1_mul_59",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d",
+    "Expected": "0000000000000000000000000000000014ca98181489c96227f8052a77730ab446615cb7b2b00a600cdd7defe8b3ee1cd53a6d98892ffccda5fd4916e0cf5886000000000000000000000000000000001567c3207cbd42c0445ea96b464dbd9099b85f5df1932d152436c936623d92fdeb009e69919368134501fa9363a0b1c4",
+    "Name": "matter_g1_mul_60",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a",
+    "Expected": "00000000000000000000000000000000139d093364c313d400603dba5a79479d566245a397f88aae748e110e09e7ab6dd271b8c37a90b86f6b48490ec1d0d8f3000000000000000000000000000000001099d4cb400f2d786dd2dd5d162580d2113c8405f51e8a619a6894d86a7f7ceb237289808acffa274069c24ee27c860c",
+    "Name": "matter_g1_mul_61",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997",
+    "Expected": "000000000000000000000000000000001247d4d3b1625ffccd350a9fc9759295637e91d9167d9bc72bbc1b60b1abb71dc29595b49ee1edc778f5219416bcd0cf000000000000000000000000000000000dfc69cdd0e4e126208b76a4e5fb8d032ae93031dde7da9bb1358507d4480881576c5d7cb7f0b3fa3032c0151650f2da",
+    "Name": "matter_g1_mul_62",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a",
+    "Expected": "000000000000000000000000000000000150849c60273de83f9ce2016238c273359ecf486adeacc4450e1d1a6cb79fc0d0fb38974489375d5763da8a5f4e743e00000000000000000000000000000000157ec6c2dd68dc5fb3cef4e935fedb74e1f0e856f1d75890bf995a08ed6b53b52e2e0d412ae190365b139101e7fe040f",
+    "Name": "matter_g1_mul_63",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d",
+    "Expected": "00000000000000000000000000000000024b59fbec5240fbdf3fb4e565bbec20f26edbc2a1bf7ecaaeb5278ed9fe13d1e360fa298e2d3f9b2880b00aff827f620000000000000000000000000000000013ca56975d9fd667bab347ed67fb96a433d57836ca4069976e12459152e1369154bd095a15980880e21fd02b1d7e3156",
+    "Name": "matter_g1_mul_64",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616",
+    "Expected": "000000000000000000000000000000000aaff66eca5ddce81533afa27e2db1c25a2c6f0dc1dd7c2236d4c89cb9d2539e109cd1362dbfee86397156c3703d44e60000000000000000000000000000000013598d8ef4470998aec290e941576f5e94d696f7f0be40e3131b516a1679c5b0eba74dc9ae00ecb8f115e4613a50f3bb",
+    "Name": "matter_g1_mul_65",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af",
+    "Expected": "00000000000000000000000000000000163cf5475fae000c38e59754cd29f1290ab2d6550552e9186555d1ce2960b7dca5834e0347699d2869b8c9bc42f6f717000000000000000000000000000000000b21bd3bfe50e0536135a910359527f80c130a08029c24f990c82f02727def21973a20a2021c95aaa3a7c8a980b44f33",
+    "Name": "matter_g1_mul_66",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145",
+    "Expected": "000000000000000000000000000000000bc3667c38602e7e1c018cc62933c013a9e78c375b50ba06f0c3d34fead5ec8a9658702a0856625a712520ac99afde230000000000000000000000000000000015c6b5487a52b41ae1a4634c8675f7b847aa5d319ee9eec0c92fc06d8e92e1cacc90ee394f8c90ce3e2c00307f53dec6",
+    "Name": "matter_g1_mul_67",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a",
+    "Expected": "000000000000000000000000000000001358e1724cb3ec4028a63e4252eff164defaa41b21042037ea9a1e06bc1a0a1e838afc1965ee665de3da0163d22682420000000000000000000000000000000019828e11831e3e4216d843ed3446345edb357b2082b7947fe71932dfd894543928ddddd8649d32b4f1349f63f60bf095",
+    "Name": "matter_g1_mul_68",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0",
+    "Expected": "00000000000000000000000000000000130e09c096ce8ba86ae71a817426d929c7f9f8bfe00e76668b0041e935d1531d6f58e5eb743df3cf86fe88bdfda8c8a300000000000000000000000000000000187b25d8216fa3851bb6fbace998bf3f23dea80dd6e1cd94bb6a72d335702694804c6ef3d350519c5e781f941bb72f92",
+    "Name": "matter_g1_mul_69",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258",
+    "Expected": "0000000000000000000000000000000011e61e5158d9a7c59a5007732a76e27d14602e15159e8f62bd13be8b44c96736af5a77495c3da55c8244af6e60eb4f2c0000000000000000000000000000000008deda8447009898c89c6766e8add105892992585724d520c38d0d4f8c833f88d8c331e11b291b6def6847bfa9629d2b",
+    "Name": "matter_g1_mul_70",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e",
+    "Expected": "000000000000000000000000000000001182f2e45f06a729f82442ddb372f2eb8dbfccf12edd8df0764072c9f14cbe001893d932e89b948a643981ea8aa4fa41000000000000000000000000000000000910335dbdbef74b844a6f3b879d14c23c711ff2362213636ddab7eb1a44cd4b687659f8dd521c134b56bc4eed0ec5bc",
+    "Name": "matter_g1_mul_71",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83",
+    "Expected": "0000000000000000000000000000000019576d68ce66218d4c9e2e6fa9985451eea46ce60b11a74cf5ea9dbb9d0e8741d11436dfd77b0a8b490f4882cc5b416b00000000000000000000000000000000088ba5153e91738f7524034a2609848652a7e416fc68537ab2c16b6699f69695c62e5724dfda2f3b4f90277f5005bfa7",
+    "Name": "matter_g1_mul_72",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07",
+    "Expected": "0000000000000000000000000000000005720fd4bff4da704edb7e317e3d41f1d1f45e3c1f22c1b98ee0b6875af414f6f58793e8ffd5c89bcec2af711973ca1600000000000000000000000000000000051441e34eed472766186a44b2028d86eebadd597cb7e3fa4f935d30aa043f11fb18670b31f0a3b8aa23bc8f05361064",
+    "Name": "matter_g1_mul_73",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6",
+    "Expected": "00000000000000000000000000000000141a0eb238edd1cdb670737d94f658fef728691620f9c6d98e34ed8bd166b38ae6912b5bd90ea21b091766ad27d689480000000000000000000000000000000002d0e7d2584586ab2f08cbd419df3defab53a287ca467b6b081e474711a23608831c1507bac4f328750731b99a06c6da",
+    "Name": "matter_g1_mul_74",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659",
+    "Expected": "000000000000000000000000000000001227b7021e9d3dc8bcbf5b346fc503f7f8576965769c5e22bb70056eef03c84b8c80290ae9ce20345770290c55549bce00000000000000000000000000000000188ddbbfb4ad2d34a8d3dc0ec92b70b63caa73ad7dea0cc9740bac2309b4bb11107912bd086379746e9a9bcd26d4db58",
+    "Name": "matter_g1_mul_75",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d",
+    "Expected": "00000000000000000000000000000000187cb196679b6baf78a7908c37d7f31a9fcefa90b7cf165d0748a358e6dd86fc5c2d91ff1c4429a563b5962b821cbb01000000000000000000000000000000000d94711dc6efed34385579532f59964ab18b9debeac96044f3eec14cb36965f380d21d39c246e972aa2d5891ce417e9f",
+    "Name": "matter_g1_mul_76",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712",
+    "Expected": "000000000000000000000000000000001405c27eb28f58e7f66988a300df376f3536723e2ba5934d843ae629669485015c90a8da60ef5c00c63c0b08a00203a70000000000000000000000000000000000a62dc83ce27987849070a6022ab6a06186e2527f39ae94d5a23d2e4d234a465d50e03b0d7d175ed7f53ced0c3bbc8f",
+    "Name": "matter_g1_mul_77",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72",
+    "Expected": "00000000000000000000000000000000142fa228919f71f75df073927d03d9204b36a5177b4ab7bc995b59ff312034f7ff916635e27abbe775379aafc24a35c30000000000000000000000000000000014429fb137cf912995ca785902877e6675105b252a64282412798f883063824fc31cd79b356ea4e4822363b948ec27d1",
+    "Name": "matter_g1_mul_78",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6",
+    "Expected": "000000000000000000000000000000000cf0aa7969ec44cc21bc8cca97fc8a581aecb63054c4fa3b7b69d28e0e2e901fa51c42a629145d9126e63aefe7978c8b00000000000000000000000000000000199d565f26b9c6496a4115eefc75f1066480f498a50314b396685a3ade8e50ab03c7f56316be2bcc02dff8b11ad5e4d9",
+    "Name": "matter_g1_mul_79",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b",
+    "Expected": "0000000000000000000000000000000000bf4256ce2a2a976e35a9eb266d11dc53d043f6fcafb47eee06e120457ea56decab47ef22b251c6cce17df9a7d91e3300000000000000000000000000000000152c438e11fe1d661eea7c631e04e02eb9204ebe52cbceca1ab6a9b4c889a1ebdda01d7505df29fe2204ef5787749a63",
+    "Name": "matter_g1_mul_80",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035",
+    "Expected": "0000000000000000000000000000000007754a49dcdde1354412d3fe2e108675fde8a1df069c86be54c4bec46338a0952aeed50842c2486ac652202c26a1861c00000000000000000000000000000000023fe3f5e6786e339002e14ac5c9fdaac3c012526b33da9ed314cdb145f9279a71e306f5d51243a0f0dcdf59bc5d55ed",
+    "Name": "matter_g1_mul_81",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff",
+    "Expected": "00000000000000000000000000000000141464b4326b0353aa99674bbd98853b926aa580c1e03673297bcbe9094eb1d795331d16d883e0583ed0551f064d7a0f0000000000000000000000000000000002dbbfb86c4d313bdbc8ebd266c190e38645016aca22261665dc850b0d7db8b240aacebec8af097724e5291ff43e6f90",
+    "Name": "matter_g1_mul_82",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b",
+    "Expected": "00000000000000000000000000000000118ab56a65ca63becc8aea3f11b370c705f32418d51fb1b1ab64bdb8f0125de2a760cf21e7ffd4d99e9d7cde1368791c00000000000000000000000000000000047674c8f3627527dbb41f51fa52c0fe3a921d07466cb2b5484e4c8094556cae247347a0a1a98499510d1ce5067480ac",
+    "Name": "matter_g1_mul_83",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d",
+    "Expected": "0000000000000000000000000000000000d76cf9fa103355e6f5cd4baa3420e694f252249aa6171569b70cb43c906eae9b60bb79b41af8dc714bd917638bf538000000000000000000000000000000000b9272015e64f292d7b76867714a55d7223bb026f354b20109e81122fa13fd0426bb3aec705b477e7b9560c5a99c9d60",
+    "Name": "matter_g1_mul_84",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33",
+    "Expected": "0000000000000000000000000000000007c87e6d92bd41b7fa6a6ca890bf0b58304875a79af7959d9226a5be2f4ac2b4531fd09712eb6299c23d7c1c5ba3997f00000000000000000000000000000000164fb86eafac39e06c2403e315bff96faecc57474bfc964736b1850696ecfedbaa0795e537b8f541159d479ac5b52560",
+    "Name": "matter_g1_mul_85",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f",
+    "Expected": "000000000000000000000000000000000fb01ce0567f09dc44fd473009d2467c8c16da5ea7b39a1f1dba7b3656cadd6bdf2bf68f96a43252d92e428c1d2785490000000000000000000000000000000008b4fa645f3c56459a17c912c82ca36165e730807282cabeadd9c6c4a12c8a592cbac265021ef62c60eb60df3ff61061",
+    "Name": "matter_g1_mul_86",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c",
+    "Expected": "000000000000000000000000000000000b9c328c8a18113e1d1f783432c857015eaefa724fa2c441d5ef76b158ee6fe0cd1775b0c6db7600754cbf25fea528fe0000000000000000000000000000000019d30c3557af1da2ca169e70625732d9a4396b51f3b4988a9aba1be62538fd51c167c83e921f4876224d361afc90eaf8",
+    "Name": "matter_g1_mul_87",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1",
+    "Expected": "00000000000000000000000000000000041054430741e889d4cd8e7efa41547eb624bd775fd9fb64cf9e3dc2c6df27c95ffb8d76933ac4fa1952a5820ff88512000000000000000000000000000000000e8a28f5c622482b296a43ddb607e0f25635664fa849f3d6840ed7118892106a787bc07806dfd83935754d2057f2eff8",
+    "Name": "matter_g1_mul_88",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb",
+    "Expected": "000000000000000000000000000000000da2286b44e7e90e19d51c3c41bef375c54688b07afffbd7c528589dbf7f012e1fd248b9067a3faae9f1c6b626a5c90b000000000000000000000000000000000bfa0a482b0fc445f7b99c52a48116383bb70d5f2ebec5b7715796fbd0da744d0467584bfc1c8a42ace833d57c167a24",
+    "Name": "matter_g1_mul_89",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf",
+    "Expected": "00000000000000000000000000000000148a7e9b0b4fde322f1177ced0bba34abec4a3e500afb86f9ae0a71bd75004e9c631d4cb26798bf963f7aa367f74630c00000000000000000000000000000000097f4c0893f9beadd66e4cfc6976dd277e527b1e31443e07554dacca52390066a4b37a7f0824cbaf51d3a555d696881b",
+    "Name": "matter_g1_mul_90",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5",
+    "Expected": "0000000000000000000000000000000003e1d921b5e0280f7370d55967e716bdacb7521547e22190e89862dbfcce02dfe7fa7927a70e7bc33448b9321de3d8ae000000000000000000000000000000001163f78de4af8494666c64d47d68a0feb0905c42ddfa024398401202d1fe0d6672bd1bd4222a8d106668ba4617683485",
+    "Name": "matter_g1_mul_91",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e",
+    "Expected": "000000000000000000000000000000000a94a186b96acbee87f9c1745dc301229ec750c6967262e629924227c6680b1d404e4b23d998611ad0e415610dc8edd900000000000000000000000000000000014da21c0f6930a79c8afbe42f73e048236b6d9f9ef8f270733fa1cb1012377eab37ddf2b9c742fea44020caeb95beb9",
+    "Name": "matter_g1_mul_92",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5",
+    "Expected": "0000000000000000000000000000000015cc6c31dfa9482c6341f816786562481bc3a4db4a4a00807a9c7c676eb32b9dc7e002ed4971f26c1dddea00d78721b5000000000000000000000000000000001303660b6bcac611b2d41a4f7ac9ecf3f0b4292f83f2fdeba300a060131322ee3c2da3ca3539114114ec8a76dee6a5ac",
+    "Name": "matter_g1_mul_93",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0",
+    "Expected": "0000000000000000000000000000000009f1903e9a7d275487a503b9c968cd86823fe6667c09593b60ac2c88f306e20ccde32eebb5942a03fabde9195c5c500200000000000000000000000000000000179b41dbc2ede95ba7dad512329aeca9ca3bfd4da4b9620070d76d8fe8b49ad7fa92358070dd5098a2eaff490641edbb",
+    "Name": "matter_g1_mul_94",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169",
+    "Expected": "000000000000000000000000000000000f9736431073987708757d61927a45cfec471c8366776e140f62d805afd948fd132c4a5f4049de3a1474d0cb52c3c25e000000000000000000000000000000001515b057952696810a90dce1ee8464fd6370e8af5434a99333eacd1fb2884f6e8c568f887030a4957ff6d24ca02f4657",
+    "Name": "matter_g1_mul_95",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4",
+    "Expected": "00000000000000000000000000000000195460b2d59df32f9f41eaef1139d45f0cb8f35a7982c38d356a8a8412f25e600580026d2d908b0493edba5dbea85f5c0000000000000000000000000000000004b339d62b3cd4cc966c6b4038adb302f997a16d8a6dfebd153295de08e57d1513cf0f16d82dc450e4d6f52621a42fb4",
+    "Name": "matter_g1_mul_96",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125",
+    "Expected": "0000000000000000000000000000000012cf2bcb79668067b7a265672ca614405868cf189ee9789b9e1e3186d231176dab5fea86cc5865392db8c75fc5d124c900000000000000000000000000000000121bf40feea00e151b718157b8c024f126762d84cff20aac08e7f2a027ab88b33e134a410c2af279a39618f7d21482a0",
+    "Name": "matter_g1_mul_97",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b",
+    "Expected": "0000000000000000000000000000000013a530f94e7600820dbd8aabefde2acb8b3c74e833457102fbd297317eb532c0622636ef9e9376fac1637dc745fe895000000000000000000000000000000000139eb14d3b69be977413c832bfda234348186d46fe177154e34fe204f62ac79f4b0f59bbef39b0676d81ea42a0946fb3",
+    "Name": "matter_g1_mul_98",
+    "Gas": 12000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf",
+    "Expected": "000000000000000000000000000000000bf700422a382546a74376b0292f3a49ceff5597f0d2b726b1ff099bcda7ba92238a21db12eff5c314a29dd2387bec850000000000000000000000000000000005e22e3c772f3634b1ccf4e311241977eb20e7269540ef22d379de26ab80c58461dfa3b67848e0d584fb11de1917949a",
+    "Name": "matter_g1_mul_99",
+    "Gas": 12000,
+    "NoBenchmark": false
+  }
+]
diff --git a/core/vm/testdata/precompiles/blsG1MultiExp.json b/core/vm/testdata/precompiles/blsG1MultiExp.json
new file mode 100644
index 0000000000..62b91f6f4d
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsG1MultiExp.json
@@ -0,0 +1,723 @@
+[
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000011",
+    "Expected": "000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436",
+    "Name": "bls_g1multiexp_single",
+    "Gas": 14400,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000e12039459c60491672b6a6282355d8765ba6272387fb91a3e9604fa2a81450cf16b870bb446fc3a3e0a187fff6f89450000000000000000000000000000000018b6c1ed9f45d3cbc0b01b9d038dcecacbd702eb26469a0eb3905bd421461712f67f782b4735849644c1772c93fe3d09000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000147b327c8a15b39634a426af70c062b50632a744eddd41b5a4686414ef4cd9746bb11d0a53c6c2ff21bbcf331e07ac9200000000000000000000000000000000078c2e9782fa5d9ab4e728684382717aa2b8fad61b5f5e7cf3baa0bc9465f57342bb7c6d7b232e70eebcdbf70f903a450000000000000000000000000000000000000000000000000000000000000034",
+    "Expected": "000000000000000000000000000000001339b4f51923efe38905f590ba2031a2e7154f0adb34a498dfde8fb0f1ccf6862ae5e3070967056385055a666f1b6fc70000000000000000000000000000000009fb423f7e7850ef9c4c11a119bb7161fe1d11ac5527051b29fe8f73ad4262c84c37b0f1b9f0e163a9682c22c7f98c80",
+    "Name": "bls_g1multiexp_multiple",
+    "Gas": 27504,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000000000000000000000000000000020590000000000000000000000000000000009ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e522400000000000000000000000000000000032b80d3a6f5b09f8a84623389c5f80ca69a0cddabc3097f9d9c27310fd43be6e745256c634af45ca3473b0590ae30d100000000000000000000000000000000000000000000000000000000000b7fa3000000000000000000000000000000000c9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b6000000000000000000000000000000000143be6d078c2b79a7d4f1d1b21486a030ec93f56aa54e1de880db5a66dd833a652a95bee27c824084006cb5644cbd43f0000000000000000000000000000000000000000000000000000000004165ef10000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e20000000000000000000000000000000000000000000000000000000173f3bfab0000000000000000000000000000000006e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb9090000000000000000000000000000000017d81038f7d60bee9110d9c0d6d1102fe2d998c957f28e31ec284cc04134df8e47e8f82ff3af2e60a6d9688a4563477c0000000000000000000000000000000000000000000000000000008437a521c9000000000000000000000000000000001928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb700000000000000000000000000000000108dadbaa4b636445639d5ae3089b3c43a8a1d47818edd1839d7383959a41c10fdc66849cfa1b08c5a11ec7e28981a1c00000000000000000000000000000000000000000000000000002effc7b3027300000000000000000000000000000000085ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf0000000000000000000000000000000019e7dfab8a794b6abb9f84e57739de172a63415273f460d1607fa6a74f0acd97d9671b801dd1fd4f18232dd1259359a10000000000000000000000000000000000000000000000000010b4ebfca1dee10000000000000000000000000000000019cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a577930000000000000000000000000000000007481b1f261aabacf45c6e4fc278055441bfaf99f604d1f835c0752ac9742b4522c9f5c77db40989e7da608505d4861600000000000000000000000000000000000000000000000005f04fe2cd8a39fb000000000000000000000000000000000f81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed0000000000000000000000000000000011af629591ec86916d6ce37877b743fe209a3af61147996c1df7fd1c47b03181cd806fd31c3071b739e4deb234bd9e190000000000000000000000000000000000000000000000021c6c659f10229c390000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a900000000000000000000000000000000000000000000000c01a881f8abc4d8843000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf10000000000000000000000000000000000000000000044496e633650ef8f6fd100000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000000000000018461a3d444ec527fcbf4b0000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb54117000000000000000000000000000000000000000008a0eb53c748001536d7ffa9000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a700000000000000000000000000000000000000031133a6c7d698078a7ec7e11300000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb300000000000000000000000000000000000001171d5c4909480aae3b110d01c1000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc75843600000000000000000000000000000000000063376fcdf64c9bcbeeff0f9f9f9b000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f535800000000000000000000000000000000002344b4be368d3b617df4aa8dbdbc19000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000c896c3f9d64341ba7c5f8a06271dce3000000000000000000000000000000000272e9d1d50a4aea7d8f0583948090d0888be5777f2846800b8281139cd4aa9eee05f89b069857a3e77ccfaae1615f9c0000000000000000000000000000000016ab25d6a997bcac8999d481633caa41606894aae9770cdb54aac65ac0a454dd0346b3428fefd837b1e3f654f8217f4a0000000000000000000000000000000474d97a9cf29e85d4a35f6102fe7984b1000000000000000000000000000000001780e853f8ce7eda772c6691d25e220ca1d2ab0db51a7824b700620f7ac94c06639e91c98bb6abd78128f0ec845df8ef00000000000000000000000000000000095bc13d5a05c686e20d7b904db4931272d84d051a516fbb23acf7981d39bffa3943d08a9be01fc48e5241cd8b775ddd00000000000000000000000000000195894e95ca3e59929612e77c1075322aeb000000000000000000000000000000000b48aa2cc6f4a0bb63b5d67be54ac3aed10326dda304c5aeb9e942b40d6e7610478377680ab90e092ef1895e62786008000000000000000000000000000000000f6fc00c0697119a34363c0294acf608eca3c680d80183a59c89b45a66dc750f818a27e3a6e136d69e7580a8afca001b00000000000000000000000000009027ceef3ee429d71b58b84919d9a8d54189000000000000000000000000000000000c8b694b04d98a749a0763c72fc020ef61b2bb3f63ebb182cb2e568f6a8b9ca3ae013ae78317599e7e7ba2a528ec754a000000000000000000000000000000000951b70c206350e1edc2aefdfaa95318368c151e01e468b9fb1cf7c3c6575e4f06c135715cc5e51e1b492d19adf9bee000000000000000000000000000333e268f0b5b1adf76b88981fc305f03ce4bb3000000000000000000000000000000001717182463fbe215168e6762abcbb55c5c65290f2b5a2af616f8a6f50d625b46164178a11622d21913efdfa4b800648d0000000000000000000000000000000008531aa42aa092a91e0894d84ff0bcec0d37cede43dec85cca80ffad335d6f69da18335869ba1174f73bb37501404d6f000000000000000000000000123717b4d909628d6f3398e134a531c65a54e8a1000000000000000000000000000000000cb58c81ae0cae2e9d4d446b730922239923c345744eee58efaadb36e9a0925545b18a987acf0bad469035b291e37269000000000000000000000000000000001678cefdd942f60480b5f69738a6a4cea5e1a9239d1bd5f701ad96c2dd1fd252f0aeea219bddcda4bc8f83983a282aff00000000000000000000000679956d49265608468757580db6b8b1821c2eb13b",
+    "Expected": "0000000000000000000000000000000005548dad0613ef8804a347152e8267acdbbcab98a795fc0da2d9df5c8ec37e0eb32e82950fbe5f8ec330b8bffafe13e40000000000000000000000000000000014e94dbbf60d89b3f68a5a076fcbd7cc0b683eae228f5d5036ee61012996ae2d347cec19dbd4eab547fadecdb31c078a",
+    "Name": "bls_g1multiexp_larger",
+    "Gas": 89400,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746",
+    "Expected": "000000000000000000000000000000000b370fc4ca67fb0c3c270b1b4c4816ef953cd9f7cf6ad20e88099c40aace9c4bb3f4cd215e5796f65080c69c9f4d2a0f0000000000000000000000000000000007203220935ddc0190e2d7a99ec3f9231da550768373f9a5933dffd366f48146f8ea5fe5dee6539d925288083bb5a8f1",
+    "Name": "matter_g1_multiexp_0",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d507f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154bd411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af6bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719292a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b10000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff327064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e90000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c23176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a600000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd1d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc9915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da05061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b2087920000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053f396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265ef0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c",
+    "Expected": "0000000000000000000000000000000017479d99909c144a5a5fdfd71721f4a2ee90b2b9654e069a38b460945b9291fc74e6922a7dbab9bb12b4bff9e2d0175b0000000000000000000000000000000015cfff11afe08d76944c9f810017ecf78b8ed54096078195d65a5418f660cf9b2024646a8532e349eac5d32d59c829db",
+    "Name": "matter_g1_multiexp_1",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3fc00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9aff661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c3346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db39a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218082c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcbd4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6341776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0e7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5ec26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79bba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b81158f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa40000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556",
+    "Expected": "0000000000000000000000000000000001c143e5d7bba56a959b94955f8eaab82a92a2e2b355baac7da0b57281645c689486059fb590ef2576a7a03a7c57e85d00000000000000000000000000000000182b1e16004c7e6f55923dd0b1dfa7346d1243996070db78f45c4c0a2cef95e93c6373903b5e0dc63f171c8164c2fb5a",
+    "Name": "matter_g1_multiexp_2",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe9a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac700000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193ab473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c0130000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10ca9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bff228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b29431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b2051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751ab96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f8520000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df9178176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c049c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29462ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d3184fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae928a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b170d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ecdbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad9970000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a",
+    "Expected": "000000000000000000000000000000000b2997ce4cb01abbb0ae6d28099d20e1f08c33351a6f0dce417a279789d6c581d4bc5a4a261e37e6df31a6928040d1f60000000000000000000000000000000003068e73dbbab6fddfd3c1e4fbf58bab58f15e1630c8c236faf3048be840abe316084aad7dd4ca6ee9d353ea8db536d6",
+    "Name": "matter_g1_multiexp_3",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d245111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d1450000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531fc07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787929b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a63d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf542580000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f57a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567381b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc3ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de0700000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa3ac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c88586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb2116e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a85cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6",
+    "Expected": "000000000000000000000000000000000ed96265e66875001ebbe888571ded16799d0bf5a6bad0abaca75b94bebf3023487a29fbe26a68f1cc90485df379845d0000000000000000000000000000000001be40cb29d8b722f91515f7e18372f7a0f77bc3ef2852c59e7533aeb67cc4cc4aab0b8e87f9a4982806124462ae94ec",
+    "Name": "matter_g1_multiexp_4",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d506e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad51a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a9251bd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6a300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886233e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8c48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f594228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82fa417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab446561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c2cf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e40ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefbae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be50000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35851268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728bf9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169",
+    "Expected": "0000000000000000000000000000000008644b6d6adf9d5b6b50d4759363901ea94218881fac2006ea391c41fed2a94645eeb3359df803d740710f0f7842b985000000000000000000000000000000001168ff1897eb699e475b8ca2930ae9ccff139d534c7cc606c7bafec0ed23a6e55c6ddb1efbb1b5f75044d0a7e122d204",
+    "Name": "matter_g1_multiexp_5",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d22d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe5485041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000107e1fea76b5f2be2d12e11082fe690f01bfe6cefe22ce67de968e410ef51a6192b5b28a89f222db7e5b5fd5b8bc7c4000000000000000000000000000000000014028a700cbde8bce295c564dfbd73294f9bb65db3db9d38312cdc31410ceaf7151ff5d9420de2a5bc8f0d609893c0612adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c6000000000000000000000000000000000a57058bc228914bbb3e3e8f6a73842533432e0cf226cc02990b9b99a74b0acbad498036d8fb72a163590c75b6041d060000000000000000000000000000000016d275fe8c7e37058f287e1646c28ad1b4a675c0eef9671cf95dfa25617e2f2d515b2fbc04cfdffd5d487b255dfca245d1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000016b6ecca57c78d6595e6b55b9360bd946b2f0061b98d931d82b03ed747998285e093c978015f0b775867ad0d8b4a1f82000000000000000000000000000000000b584f6f00bbcb2432b6cfbd4f6c88e228658887b5278e461ede804fc8a65dc6c997de30efc65b4f43e3d96717b938644c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d3000000000000000000000000000000000d1eac060ddc0a327396051c8c4dcccb77d11da05678d0720dec020d8aa29cb8ac959184417267cd7386feb1c81146a70000000000000000000000000000000003f8b5667ee4707958ecb93a1772849d5d8a4d42a2367ca058b160dbafa8ac0b98d5ea216fd18130237a1f17ce905feb3c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd00000000000000000000000000000000180152247144900b015c3db2d8b26d45a57930a5ca988c1fbf74b63b48afa149347a343f3fc6b1f31ddd6de079391efa000000000000000000000000000000000b6f3ae16d2a580ae06634455302db85fa94d71def14c84cbacc5ef98335d6d87faacff7a9bc14dea342a6a80d9bdfd661301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000001918c4f95a0d0931ac3f254cd61c10cadce5cb9e1ef352edc8e5944c8aa8ecd90c403ed764ef42f646c7ec5e3126a140000000000000000000000000000000000ed644cd065411c63c7d054a57344e7a909e1d0a6b414bccbd356f15d16fc1b42c681cb6b36b143e91b31866387fa94395cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c00000000000000000000000000000000070dfa1dda5ba02e94b29a63f8eb571ed7e8b0d037a0203af9a8350dacec092be1bfe33f4134b2afac77b9a36f95208000000000000000000000000000000000019e11a80ce3f9b3321cc6fd1ea2b314bf0c71d0dde80cd5b4de5f0d974597f57036613829dc777a6f6ecd6f9bef2f85fb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e000000000000000000000000000000000584b7ea99ce0398473167289d34314c60ba913338b0bb690cfbb013496d24854863237a4d716437dc6ae33326240bd800000000000000000000000000000000065964a064e4da56471c9aed383e6eb38b58b9110a2cbf991d6dee869d2f1307cf7273d203d941ead90ed67c923dfcd5bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b860000000000000000000000000000000007d6061bdf40745ef7573917e0e19f240b6e917f7cd4c47e01969b9afdc6af4e3c93e0f1dc2d15790bc2e6f182c01f680000000000000000000000000000000014625d3f2825121a907b570e9aeececcf81137f40ca6d0c00d709ba9931e403c0c2ed262a8f4c2b24305dcd3185b81b0e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d2500000000000000000000000000000000087b5d6595554184fee36be472a0ddb9ac7f9beb20817647fa9978b2e0c3549ece4f061b58054e9191ff3f120c12077b00000000000000000000000000000000168d8d995c1fd032ca7b0aef2ad5c37ef7c7cef8b61ab8fcb5ea2d449455bc75b1b85631fd2ff8f5ca4e5880f36905ded4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b048000000000000000000000000000000000f44cda026dc5e30eb06f12307bd582b271ee695fa68fbff48674c0499dcc875d617471830958e31bcd2c883e97a9e590000000000000000000000000000000002977682ca8ca450df2ac3c3880b1235e0ad8436a36364d319903fe2ca2664e05a70840aaf2d62531cd8c4ba4bfae9124c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d300000000000000000000000000000000107dd39f779801f608cceb4784134894d2d9aee37cf328bb764d8afcb6d1e0f1387b36bf5b7b335099849278eac44e8200000000000000000000000000000000045c985714b519061a9c8d8c9665b582abdc4116a48a70e0d3c4a7709568aaf011aa8ecb893ca483878404b3f8b22e41a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae",
+    "Expected": "0000000000000000000000000000000005605f40a1116742ed53aaf7092715355ba8e41d5d49909f79ec9580955c4a9e696fa30df37e4044de4d23fa1c4121530000000000000000000000000000000015acbfdf914883d16918f980aedc2dfddc428ef8e9897776443ec3cb091f6cbeea31634ef5ed061792710d5d2c87b8b1",
+    "Name": "matter_g1_multiexp_6",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001696e814de192623543f822a22344258c8ef9b92c178c4bf2404b38f39151828bf518a868cde3db5cffb745f1bf0023900000000000000000000000000000000125bd1df30f4799271a9db03842f98ccb5a5c3acbe813b8324fefd06e3c8ec4e2c0be8c2d24328a2d1169bf6ea0ce46fb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff00000000000000000000000000000000138dc5c034135105c8899eeb61ab54a84cf02919e5650f34518f941aea5bbb6f9df3ee6bb2056d0b9e060158140f990a000000000000000000000000000000000eec8442c8656ebc4696ee13273b12f3e362862acc3b8ec6f2b53f58f74ea23b33c79fbbd2058ad205f4932db2e87ae9064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb334610000000000000000000000000000000008f7f45310b5638221cfc9dece18010034c00b3cf170535008d60dc7ed6ff90bfe87e03d94e9a38201699f0742d8973500000000000000000000000000000000185b62a19864e21e1bef19fbbc21863c75f558bcbfa1b948bf939876188f4fbff5d5f4f756e0ec5348e194bb4f70706ca3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca0000000000000000000000000000000019269dcdf3772ae4969b68a0b4f87c5097aa8bcc9e6155638c3de94fc22b4965386be28e059bcea69f993cc388ea9a79000000000000000000000000000000000b95f44ad9f14cb5e3b9a338d0e4345153c4ad0d42aa00a4c12df117b89d9cf8bb556041d49f94b8f63108f03c56a449bd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000011c86d420b6d8820af8a3ef511d79aed7c82ee08df993a5ba479b29ef2f968919444a7c48a24ec33522e1206bb9ab784000000000000000000000000000000000f4a47f3f14a25108c2c9262466d14e3a8d1f21bd2d3d6f28f03f35bf23a4b5b494a7cafe6ed5f39195e07b1692bb6da562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000091d9fb6493f4441c6e57a5a58210a6b78e86f1a9d204094ba6fecf2c146522cf842219c900d7cb95366cf7e411ff4a00000000000000000000000000000000015254260fb67e88d0067ba7006a49814c74a5369837dc5279c0fd19c8826813c922793c96e0f708092158ac297a368ddaf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f00000000000000000000000000000000027910712cefec94f0fd4de6aa70ccc408e64d5de6b473086009c525fb6d058ea03bc99f7ab49cdbad3a42bc8ec0999d000000000000000000000000000000000c0b0bedbad83ebf6af4f5757035b8292fadae4bfbef9f3bfcadd21dd796d7e3ecdf9685ca6d4d649b2f0702a3280d40e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e88000000000000000000000000000000000b0ae8987464ea0b77201d468db7256b135a5cebea92dddb3aff10e451568e714f1c418b6d53903b89bc71109180b8c20000000000000000000000000000000003050becb4625f8e3ab2cf13dd1eb8f7eefc7e14c16934b87661adbf0139631108d241bcb1fb24c5b989f6d424cac883396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000dba43568347a96f26f2633d9fc0fb4610428a8d4992c2734b20928bf974bf642a5122995884cf11b76126ba66522c8c000000000000000000000000000000000b9bb25b0db32149736b671ceed44df71f36a33c15ed821f591098ecd873355cfb8a39fc7c7378a19d84a5b232227ab92087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000018d46d1a9ec91cc7983b29ac83fe9101c0ca36276d40743d2a6010d574fe1c16ebd9d7f0c83cad5ec2b2f652d3e6cfa500000000000000000000000000000000185f6367fcfa70e7a005c1739c0d0a19b5ec8de766037ec92840e66e2e9db18ba2356676899763183222f9957f48f300f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000016677511c781b2b97456c3059c19b3e12a865cc21ad71cf06979bee1a3128682a4a86f3e07cdbc9ff7b5aa7a9899653600000000000000000000000000000000006307c89ac36a88c6921c020d32530fb69338afbb33929e231fa704f0454d642c47a3b8d320b4266283a8571944d0558624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b600000000000000000000000000000000015a9b215eaed682e4704cd3b1265962ae0e24555a16612ac762040e1fb9b412eacec5130a6f6a31eb670806be7ed775f000000000000000000000000000000000f60035910c438c177a27e6141d0ddae706d3e852d61e37cf8bb2f03550feeefa7213545e3af5ea614c91b51bc2fb378b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb3832000000000000000000000000000000000077b7a4c4644b21ac3ef56db1163f7b2e07a817cfd9d4c6830a97d0ae0b620e0b235376d590162c378899ba12eadb5900000000000000000000000000000000022beafe4b4ab44434c9dabae45a395b5b8da15da2fc2e723c1b30b5efc95e880846844f27eb47dfae8657fa27ab64ef88ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb000000000000000000000000000000000324928100db98f5a1af039a8e1b63099214982f1729ba633b51166da97e861426bb91283b386ed4b465d791e63928ce00000000000000000000000000000000178823756c0facbd4b1cab22f346ea7d1dce0ab687263265350c9939d52abcb5a5000b3395f8268a38027410675e8baf951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f440000000000000000000000000000000008828eea92c3245eea4d60ee385734d3237e4e346e52c5de8b24c82325819be6984da4f0c1ecfc6ded5d0539a6f1f1490000000000000000000000000000000017169bab8970f47a303d2487e3af707eddaf7c4453e9d2d6bbaf953e74947b5fd40663173edd55c0d6aad7884f69a0967056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000787474664b2803e78489de6c5d5f1938e784e552bca4c32196cfe121380aad591c9fe4d9230dbe976b3ed3b3044b8630000000000000000000000000000000000c026547c94cea37793fee439c359cbeb2b985a33559ab25d1b55773c24faaf4fe511fbf7df085bf5c1715c79469cc28aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a",
+    "Expected": "000000000000000000000000000000000a7153c6173dc27b6a241c046a2a9bc25b85c4621667d0a10550cf7a090a8fb914725201876a5bd2af09d4fefdede3890000000000000000000000000000000007aeec94a64ac629673f3be3cf660f471e3e928de0884300ca8271d42c92b965c86bfe76df61d0645e955f40cbe3751e",
+    "Name": "matter_g1_multiexp_7",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015fec2d82f5286d2067b07d83cd1c131d3fe18628101c3e45caab07f3c775c97e1533836830959cd7e434fc3fc392203000000000000000000000000000000001050e1396a5053c902441cb33003d9c54e6b631a80e3c132dfd37805bfe87cc2ddc495200268fba0376c5fa071fad230a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c4000000000000000000000000000000000fbcd07180f265688329d72ca68cde644a580cc9d698e40f69380065110ff5a61149e4aa9f67056e0e1603bfb9b5b3ce0000000000000000000000000000000006f363a9addd63a59035cad90cd52213665069f540b6c6cb41cfff5711376885e3242b596d051a59f681941bafeca53eb54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000001164abfa75cb4d711ad811c4df430ecbd6329968ab003fa680d235ab34a9565e5c08add76cf412f132b54812671da7a900000000000000000000000000000000141c9858dd17dbb027dde22dd65f6a7cd38a1999eb7977cde87ad762425e364e1395851b1cdb41094551e530d891b0d15849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b95d221c628a77bb75ee5942c9df4b700268c90c4e6330ab5533d13d59826c81aeef7621ef6145f48bef9607d280ad2000000000000000000000000000000000b2ae1b6f916d77c31e4421f8d0241201bdab5339f95eae0e9491b4da5e226f8eb3f754d40be3b446ad6d18f28158b08aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000128989e92641f3c3a914c13e986aea1bad2c87a8c28cf156bbc68bcbb134b25cd672832f2a988f60d2ecaaa1b83159e50000000000000000000000000000000000106dc95373dfcc85d9de6b5b609554b67e8683f90ea13156c8318aa8de0a2355a721b3bd77a6329264ae671c05af4a52c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b2020000000000000000000000000000000009fd9fc9ecc0d1521696bfe7624360d11111523a4ee0e30432a468dbaf1c101691fa527aac5ab531be822ae914b0afad0000000000000000000000000000000016b317ad68ec471b0ad67be2c489c9f5bb0d8bb6b5ef909ea975cb17f5964564d5f1a61d32d60c457923e4680a218b9bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd9000000000000000000000000000000000b982f33980dea4d89b577c9f849f8b8d9cb0c7efec7e17284d45c855638fe9ab2e5bdc52ba79d06a9133f66bf0ea2b5000000000000000000000000000000000c252a2e2769d3250479091050133808a1b0fd20af2b41cdeebe7cfcf7e3a92b9ab17cdf4d370f9fc391981db76de39c915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000001769e8b5fda96ef205750826f34fdda3587efddc86f69d37001c62938a90efc23a3ae150d223ef4bf3766ab7d86d80df0000000000000000000000000000000009ee24ab483300764bccba33b55b8889b084288ffda23d157f650df34125fd803624d88f2bd0c3c3ca51bcb57b9f4dcb58fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000146001b68cd902fbb4548c3e7cfae9cf3c8916e462f1becb9918c8de42483ef65f418d6e93200e8ec95528928916bdb10000000000000000000000000000000008bef4996b8120613292dc76dcc77b07b24d4498d6bd35f5dfb80ad241ad97bd161cb2c5c96fb250b70f8aec1aee5b56c29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea00000000000000000000000000000000019d02e168efb5769416132b0457ee1ca74bd5737f9364623bb270e8218c96e71dc49403584aa0a7e6c15bf6948ddb956000000000000000000000000000000000510c0917796c7ef2e100c7656591d04c3c5968d688b36b93dd690b0a8ea55694157fead964b85a5eef1815cd5932819dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e9000000000000000000000000000000000dd05e53ee40f051037c88fd28364aba276c793047007a20f893d13222c35b24e14f6c74004c3d8070405621380553af00000000000000000000000000000000191d7f1863ab7bc4ad1ebab359499f4df75b8c7a58fae8fe7cca530c7a56e5ee1617b343765960ca4bdc0245ff997a9221c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea470000000000000000000000000000000013320367c29a4f1527e8c0f3047f776d7c892d08988c402c55e90e84b07ed7f0932c3b5fd19f8d133aa839ebd90f6428000000000000000000000000000000000f8396d819d7aabefda680c8ad51c7f907911dc4da7c5fbb7e599e7f3b758c5e7c9e9ab4de1700f72f109d7206c1be0ee1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c96000000000000000000000000000000001413f6a4ec8b21a459a4aa33ea9d92614857df629ec16990939fbb8ab11fcc919a25a10423ded219ca5b94f71377dc2c0000000000000000000000000000000014a3320275a64ede5e1221c78b421c1e4474bd499263aa21e97af103d7cb62335faf4b85b5983c5865599b709e95efc4a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000057c3c7e4cf799d716483f1e8bd4e6ec91ad9566379683c54204ce46a0e5635fd9852b0a83328386643b2017b9b551f90000000000000000000000000000000010e3d5725beabfa7e4843eeb5bcbf6e7a54b4b82fd1768a3c276bba8fb7dd25dcca7e20e74231e2f7cdf0ff50cb9cf7c2c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b60000000000000000000000000000000009d85ce8e918ddbcc47494c4b194649fdbc8de31f5f3299ea4bec7c68ff56c7f6ae916c85118553b6a6634ef9b8820f50000000000000000000000000000000000c9a680e6389d447a4884b4e134a3e025f8679edcba56bf8ea2061a00e34d38c325319a8a5efb556fc2536886e225912908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f000000000000000000000000000000000f34c8793a9ec6c34c704159d18e385dc9a127e0a9b5f95667f58e68f5ddaa272f68f5fb55e105010fb656954f25927c000000000000000000000000000000000fa1d9379fbd273b05aaa8ef5397eae24cc14f83118b2584085312986c192d2c5e3a0fd8fe5c2d82be2ee5b006413a2be8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399",
+    "Expected": "000000000000000000000000000000001223d94bca6cb3225511b4e28797ddbf1b1e2d059c6f7c5e852331f381c578016671b5390dff3898f9048e5868b253de00000000000000000000000000000000093eb1c50064251cf043e7c7af7518b883a8f760deac99f6e10c3dc941fed718f2293ec2cecaba6d58060374bce11a8f",
+    "Name": "matter_g1_multiexp_8",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001429e7011c17bff6df1b3237a06bae78d427720af641d2614f32cfef8c537d5ae9315c0b179af0a114a486e2eff7bc470000000000000000000000000000000003b9caa69b5495dd33139d14146919f9344efe2416b665dc262bd09ab91f3f07d1fb5eaa3c3a94606e74ee747114f347e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a80000000000000000000000000000000012b481abfcf8ecfcaed39a4277492641c420acb65ec809a7d55892091c7f76f82c02e7baf2a648cdd5cdac45113b11e90000000000000000000000000000000015d32649850a5c99a787ceb894a66b58066c9257dafc4a6cfad2887e7a19f8af69f8d1fa69258289e417954d064e63eb5307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af0000000000000000000000000000000006038134150b97e785f33b0accd0d1991c7b97aee1acf9bf671188f61a846a9603f2d3f56d2edc0564d1ea7967e112460000000000000000000000000000000019434ad4fe571da11e2de03c891d19ea2729f4bb7b7863ae0bb8f18b53852ad4dbbbe682da2c8568fbe96c6c9a7236dc7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e2260000000000000000000000000000000013786032ab493b5026cf23fdcc468ecc486cc8179c9510d99a503031d1fe39f9caedb2d42dcdfa17173e693e2344bd05000000000000000000000000000000000f1deaaefeedfac7f708092bbe3005be7c4b56499bdeb8fc742b72be7ffe4d8ca90e605502f1863d89a41ed794e06586eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df000000000000000000000000000000000aff14b235c3569586e67cf5113ac0ab32d442a1c07cd9e249149d719dbd64f8ec1b07c4241af135d3869eae05ddc0a40000000000000000000000000000000013d960e93447cf6df8bb48db45532d567dd2b0756dd674625657e5364f81b4bb94bf436b54bfe9afe8eb5f4bd1be90732c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000f85e9736fd9d3f9a839f701b6d8a6724af55ea74d28f101f97229f4b406016e50f54a0b3d2087117f352bcc28b53d5e000000000000000000000000000000000b2717e98f9fca574ad9202bd76ff6e53c74c342d1b6049fe66310040217563a4e5df460f264769418cfdc443dc31e008dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a02538000000000000000000000000000000000ed8e6113d657b2d3283e50e9d054e612793fcdebfc31c53ef4f417e63c76234900c627b7e8c433addbeb6a79bcc5d380000000000000000000000000000000012f0a3095ae16b5535192a932f188c62c3cf01d2184f8e299794bcba86d4573e423a0eda4e17b4b512c5e06367e470f6146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000002fa5630b261e07326fb51aa2bd897ab49e0b960f769e3207906a530fd759a53db8ae17fa79c8e8c889a923fb38888770000000000000000000000000000000013d49d032b888aeba7e652b200c91042f409a6a824d1aaa04bc402f94233385254a2d1f8605d15d04013ab0de9e40a94de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb9497368000000000000000000000000000000001495234b14a93a24881f3b4425dfd82b49aa1828746b06822097c8338da57db37ddc836a9abc46f7a0cd17ec08d36fef0000000000000000000000000000000013b868cdd5ed7bf90018873ae2ec84e4bc71d002483831ab7a4a19bf18feabaa210a729ebae606ea18ce16458e997497c2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf000000000000000000000000000000000f223490fde3ae0d7b94412b3aa86030e5d9dca805f6ab5b025ce8e9648aa02067fd29ab9a1915c2df7b2186f35a2c74000000000000000000000000000000000aa747ff7e24cf6d1dd2c4fe9db8c031b78830e98cab27cf765fd874fe6b7731c13af69559748c81f3915f9f3a6c63bac1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b00000000000000000000000000000000134f8ec87b5572c062f6f3b43ee896c2e019356214ad397f703a839d39215bec954f02d3f81e3442586ba9762bb9690e000000000000000000000000000000000218735ec0b5bf9b59dee7cfc70ec4c6f21aa129d604fffe824b7ed6b6346dc242757abbe98c19c02d5235da448e331d6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa2000000000000000000000000000000001510f39616d7f576980055d0547c603d882dbe85dd0b634577fae134f210736007345d035d815306db660de4a07fc24300000000000000000000000000000000064d356ad7bd2edcd3622b1fc225fe319f86b5f7da875cd57fe5adc5bdb6443c5b09d676950e2d069bd4303b8f9206928e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca10000000000000000000000000000000014d4184d69d34b8e509f3fc7e7033d76b10ba913d6109bdf842be4c49cc0c29576adae2f75e6fa054bd989e26bda58170000000000000000000000000000000019d0b70eb45a353166bfaabcb661b46eb1b7d8a59a903cbf9e43ceb6ece492e78d7f1765922e981903153072a08bde098efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c00000000000000000000000000000000087bc015b995ff8a840fbbf23db2cdaa8bb2dcbc38e12b588bdc4186a77409fa2a4cd74347f568c5b516879b70552df9000000000000000000000000000000000b15f04955dc27d19ad2a97a99e0890e6d3ad17d29f6b30f866f8cb3ee7789038abcc24c63d4525860e64593af02e39f47f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d530000000000000000000000000000000013eb2ed1d78059beb34c3fce731d42ba28c485dbc74916e373424917d60bc8c402e331e8aa2fdf70360049740e670da7000000000000000000000000000000000eaf5b5e47a2312410035d87aba7196f3f0b65abfaac28ac80accc9d87a1115b7f175e59ea2394198a2876568986fbebfb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b5580000000000000000000000000000000012d7a2e92adfff3d37ad21dd26299188e25b628a9e9d7b54d2eb8a886e80de812a32db9816964f2c0ad25d9f0aa6ae9e000000000000000000000000000000000c7084afff475bdc0a4ec265a3cb3f87d862270b6263a47d869786495abdd4316f6f154b997224d3a895010ce04151c34b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a",
+    "Expected": "0000000000000000000000000000000019c9d9833332c6dd40c200d53b30e92f1595c22568b31882e24b8fb17be41f4d2f9692ca1e63106c59ba574f8b0764c9000000000000000000000000000000001414165b510abdf0d4a32236cdbe51fe2b5c9a31942710a07bb5664592a3f1b5c821edea44bd7fe185cb53b11d6407df",
+    "Name": "matter_g1_multiexp_9",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000038e60d2dae22dee4dad0d9e0658741c13d165d3718c763270292602852625ac83c5ebc1a6d86c181686cd01a1891b520000000000000000000000000000000007299913b59e2d245fa489d92873b7d2bc8921191a34a0d7f6c5774757ea4eb3d667ff8f3e9293f0d2354ef03cb6592b68ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000b07454ff91e3f9707880c1713c69f8a44d70040b44d96ac74d196853c62f264ccbe6d9c8945905092d9bef665e45bf9000000000000000000000000000000000405c965e2e8cb5e85ef9e18927c7e86e63e7aeb49f45b3428089010f34eef9ff37eb005e6b86e20236dc870661dd68c61529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f81000000000000000000000000000000000557c7f55246759b901e4e8478aac7b80d37edd5d6be057e5aeafe3d8da008e48c96c17ab1093a6a4fb39cbe9364fdff00000000000000000000000000000000158908f112d7cdcf867f1a5b05062b92972c2947be213ede3a7fed7a477fd57e69e1de82164f7cbd53a3f4f4bad551d744d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c0000000000000000000000000000000001701edcc472ffbbf157b1f239968924bb91825754bd4fda9f13450162e82932b8f5f39e54ec5975dbe7dc744d6d676a0000000000000000000000000000000017d13c1f6d64af2a808c3ba20792af9ee9c626235ceb9ced3c7acb4bc864ba47e55e0945a430da47da1e87f015dc024724872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000011231262d0fcf5a4b92cc1ed62aa66a55be739eab1316219ed2bb8d3e939e25b840b75f914cdd3f07b3f57bbc07c23e0000000000000000000000000000000001eabe4a5782244ceaa57ea0b58ed1334dcb94e449b7fb905805cefb786e83af66ded006cadc651a7b2cb07c3e3fceb401a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc000000000000000000000000000000000045bb823151b691e26b0e706b8abb248ecd87107a88c728e7a627a962aca7f85d4c88df949b3c53e2d32ef18f60675350000000000000000000000000000000003342b2d1a75300ae9ffbae66326936b19c7e59fc6f597ff09f2e5d50c1942f161dcbcbba00e4a46d87ab51074320132c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000043d72d26ee669ae8e47eaa74199feb37d51f5c99151a8f854362469e5acb2c5f6d2c208e7d674efa189fb90275b835e0000000000000000000000000000000019e6f1b3137bdb49c534902abbf42893fd576a211b93c831dee90723c7daeecdceccd3eb981537d4fe729d6e48d70d6ae62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000837b6a981e486865dc4d6d0c123230ced707e2518277cbfd0be747a8c9c76be6aff8b06df76f7c801fa34d11141354900000000000000000000000000000000011d745300b20c5ff1e607ef3a42ac31cc55e8be979b091aac0396748e607f00f30ff579321f2e660e90e8e5f9efd4f77a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000740837b02d2923815914ee9cfad663eb7246ec8c56e632cdc2dce25b6e475dbb6a75ed2ca6790f5f83fd1a274832e8d00000000000000000000000000000000188034daa9801ea182b712da519f7524cbb9f641146bc0fbf77e72ecd066bd577672c1ccf28a2c4d3cb9854cb2b9e7c80f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce888000000000000000000000000000000000014ddfffbffd0317ba7e248f648cbc98fac2be9f0cc31d6476f41527c25fe8d078207965eb2382ee1e0f08a38fbff7c10000000000000000000000000000000003e492f3667da69d44b35899f425af2ba51130aa6341bcc0d4d9646cc96b090061acece81ed16c7e75fa452818748b119b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c0000000000000000000000000000000005107fd2b5b483173992b0f2f51dc24bdba94b5174c063b52c33a8cf84ce3adefe0efe08e6bf4de3e68189e495b39c6d000000000000000000000000000000000605e8540f1c7f5790c306643a68606581a16a60d33607064dad5572947c93f3846f66afae10a66cd33621c6a2dae30c6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000012eb811b231a07e27e997900be274f73720afe3b0626104a9d5aed39a3931595f2ad57cf6e8f12d5110cf38fc8e7f244000000000000000000000000000000000abf1b8abe848b91333b4bb226b81a33aff5b8f7af70108538a3c706da182476a42e0e5c2fcdf694c8a12f62a996c86c030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d4050000000000000000000000000000000009d2b5044a8fe22a957b6d1eb20454db2cff51e7ebb6357b3c6b95387b1fd810b94eab4aef4f0a0aec4e6a693903dab60000000000000000000000000000000012ccb794eb1174735b5f7700ef95ccb67691cd3673d601dbf6b2e2469521f1b2ed283f2f98a9cd601867de4640c9517988e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000003eb6e7ab6dbf66614ff5b55ed36243e1d9baa317f01aacbd7f3a015bddfd818c6764c0802e97a42063a18edd9dd091d0000000000000000000000000000000018571d50a947e56f63b26a4377678c838de7b315e655104eeee48b7d5e6f5ee5d876b3ebdebcbde4080e022cc88c995326989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000906d5a1691dcb7dfd5d78f0688e95de2e2f06cdc70f8760e43a562365939d3fa23ddaaddfd1ddfbd3bc9777783a7ab600000000000000000000000000000000168422a6171f5ae44b645b6b6e73011494dc75e98793db2424bab311990eb7730a9a45234afb78aeff7778503cf4e5a03d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b10000000000000000000000000000000011c20d0c6140e0e11d3ffb8c28c6bd80ec495d057775f6dc331c98b0b0aba17568e1ba773771c703068dcc6747187767000000000000000000000000000000000f88fde780460bd75f46f593cf6fd0aa25ad14cccc061d9ae2cd8c20398f24e76ef614008efc9ffe1d1884df1122111b5639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef",
+    "Expected": "00000000000000000000000000000000018ed322f140a351069f18d039ebded8630fd019e8c6c401dc760ec5cc6536bc2f52a6cd5400dca0caae95e4b9282967000000000000000000000000000000000b9666fbbe91ec8bd670b2a64571a34d689eac44c5b63a43f60da08df51b003d85c03f0eab3e2042b1175af3501de8b5",
+    "Name": "matter_g1_multiexp_10",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b42381a83d4472a3a7a18d2ba5266bcca254fade1170c6f55d442aa2a7674008fb35c58d5a638280e0ff7531617a768000000000000000000000000000000000eb5de05b5cdf9f95c5a3ad30ce068d5491006640be4c7f02b7498963b5769d516efb9a117c60c1c5fb71617d42c977142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000163ee62f1ea9219b921ae7ed0f121426fe9fb8fc0056916c81ea9e713f1a16e3f2bec6ed0e3e552a7173f8dffcde82bb000000000000000000000000000000000f5fa0e4980d3d2b92e98e76e5d67815ce55858852f03ec7b8809b02d4b1e9e1a6c8b06bd481d9d153acc68378e779fa253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b7000000000000000000000000000000000cfbefb41304008b0e7341451f13d65681f0726544f14fd1c0d02433d3c34a4769f1456960cfdb11b6bcf016b906228d0000000000000000000000000000000001adf387f4feeb3845b12449fd5294802ed30ae211d0837eff1b22c3fedf538ec7119c1fe69ed7d595f7c0fdcd54f684acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000d736d3b8b586e09d6ecb1ee2d7eb28bd68aec60234e90611da8f1e1aedebd9c74718d41a89186a4a5dcc3f7cc81e99e00000000000000000000000000000000000ec0e89da57affa4686494e8e0f5517f11532f6e294215bd060c370fc64c26e34ee1e2d77cf341226daf84791f5e3cd9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df0000000000000000000000000000000008fef795f8bfb6de5feee020a9363adb1c26fb521439e405570b4e997f55af5783968b24d2e95144bcb6b38e4ef9497c0000000000000000000000000000000004d4e31720644e8828faeaeff38985ffa4fa2f7bdaa476b5c4d7eee81c89491eedd3f4262effe118a4c204eb555abfbd05fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c3400000000000000000000000000000000195f8fc4b1ca0c7041810b02bbc38b8bcd0711dccfd80de2b2f357f4a732e65492d57f455e99fc810d6f86eeab0ac101000000000000000000000000000000000e3010ed298656b91b5aa342f6be7250cf5504fc3aa26a2c7f46f90e852fd7799d96a85b25e6066b7d24794648a81331d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee11909900000000000000000000000000000000192cc18dff89d9a94e6f0498419ceb9f21d70e42a1b9b64bea093d67075d499184d7b2106f74d31ccd1863beeb7be0a9000000000000000000000000000000000b80e940dce71be82106640d99c121dd21e99ba459f0dc8b1f11cdffaa0d8ab295b9711c23de1f4bc35120a89948b91a44c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f6000000000000000000000000000000000b53f55edb182dd08e2c9d0ee43aa3d734143b54686295410f80086d3aebf6fc681d1150e808d684f47b0eb23fcaf629000000000000000000000000000000000d73442636f4d5dd1374cfc7ab29b995420995bee9808aec29ef7d1aac08c0ee51a0390330a863295af6129b7e8171d82583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000002bd8316507e6eded2034cf268b2b4660211e6bea2e82b3e3a0902bcda0f9ae9980b401f36178f681691ee7c10dc4ecf000000000000000000000000000000000e9af98fdbd02ef62ae90f1e87c4e7a8eb2089204b1c58dc6e59fa32d001c97f22740d8a13ccab23b5a8842b693504a8506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000aef5a5d5b46d340fccdfad359b0499a5c62ff4e5d9b9d6f7a5fd6a97e96820b7fd226e7a2aabaea392869a40cd38e1d000000000000000000000000000000000865d32d825149d26b60969ca567ca85af5e280b835cf541b20b0a4db83309dd2b5700f802ed9106af73b912dcf9630b7f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca600000000000000000000000000000000153310de30b7a485753dd8443f8638c12b21083f6133a1c093648bcb566b33f73631c6fc558f32abeb0d6df8430e61a900000000000000000000000000000000005be397e9f77556ad952dba0540f46cbc7db910d5203cb976e168a7be3a3b8557c5f08d51cca9379552694a291d67fb4429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c000000000000000000000000000000000cc75210c78f2e7903b7c33379a6ab412e92f35de51a152cfd2f4a5d122f9e558b617d8a09670990b7f056e95eb058ab0000000000000000000000000000000000aee8eda7c1bedd39f97efc60af110e64662b9990257beff15ef5e7856e5ea388df058ed8aa6dd93cf5a81ba48cb88854a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a000000000000000000000000000000000f396976e55dc0c46fc4543a8dbf690b8da7b6010a03e04c9010f01abe1b3beab8870be0b6a2c6d6afdf85c6fd38d8b70000000000000000000000000000000006c60eeaa2d94b571df8a6291c2b12b2ce9f17f414264e4af2a006d6aef2d70436ef0978139751d4ccafce200f16f06113814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa04000000000000000000000000000000000543f8d9faa2b3cac2518f1462c297595ca10d8415143c8ff3feecfa58b648d0dd0c25156287b2f29f3b6f9a60f02701000000000000000000000000000000000be673141c496cdeab5ba8604e081ed3006828c7c877d8990efd29798c1ceae3093e052f1f928fac0c5cf84174283844aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000001342f79c96ba0a29de9a77cc2e10314bf2e15a7d192a90af9c025e2f23ff30fe49cf239b180cfb6f8c35f95c115777390000000000000000000000000000000011f0bfb11be253b3680817af2b929de9ccf06dc574d17cf6680643b87e5fadd06b54224f155c1393c870c2dd01d6bb07c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e00000000000000000000000000000000183eab3c2a127818862c6cb42bfbc9d59c51043dcc28c68d3fea08331323c9dd50cc34a4ef66a97f98684a5d9a982a1d000000000000000000000000000000000228f8f774bb68f966f3ffab5d0928a59707d6fb4f6ca84fed831a8212f71085cdc27b1d52909bdc005b3250f26cff3b9680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd",
+    "Expected": "0000000000000000000000000000000017ddd94df17d52e842abacf3467f4461e345cbb680ae624f98c6885e40b17940bc240da17ed0a1a45f13f2ce4ab8dc100000000000000000000000000000000005ea5144aa5d5393b98db4d23477011638dba465a542dc28691ee2807ffc08413938ffb74e16c7afc507f85d76acbcd1",
+    "Name": "matter_g1_multiexp_11",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000023977b65312306b1a746b94bebbe79ccef0342ce833684a273d8baf74e0ee71104d6c453acf02d0c4f3909144b1a3b700000000000000000000000000000000050494df74705eddbf97da56a21bd673e2b0d3a9cc157168b8b413a89359c9c48f09e756f8e6ecc67811d4bd8043bba91ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e0000000000000000000000000000000015862e2e3cb73ed2ba6b0b69dd9fc4c308c0a79e5cca2d2a42fe94e9b029b22b5b6aefe0503798d78d4599dd5c201cd0000000000000000000000000000000000c49723dfa37fb1592722b14e6c75110cf2252ad5170131bb72fa35bc359470bbda292fc2a459dab89900eb251e848e12943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b1000000000000000000000000000000000af2d03791884033b8293fb636b0c569d9b008b075c6c71ddd7b0c3f5e139a17e1fbb18144d1ecf491d2fc40b7369c0d000000000000000000000000000000000d680b707e32626219fba862cbb18e39e03a8b9ac78f7bde619049748f7f0e49cc0223f1111dfc1f5c851229e62a9cdc1551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b442117cecac25834a442ef457061634d863875c10e1809a3b9464eef6760f074e06c046a74bfb34f4d16255cd4f62a0000000000000000000000000000000000febea79eb8102b2632b6fe3151d9d972d5dded2893a117a6cd7e2bb662f042131cf06d04ca5c88c8535155910f9e008eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f000000000000000000000000000000000d355c97dcf055181b8c523bbdf7eabbf064159c15532bef1e1be56146d72c08eb5d6994a3be7d6f4a4ef204f0e6d8dd000000000000000000000000000000000cd6d4e6df1ef7cd5fcd360e8aac511a3aea1f3e29536c193f4c3a2ff0f3ca16ebec620cecddfa8f27732eacbea75500f72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000072ff95f5cd9416eac2cd83781acf856a0bfa567a079bd3cc909eeaf5a3fb31090e3e2ccc3acd44b6b04b47b5b8609a7000000000000000000000000000000000b7a39ab3ec7de26c86eee5d8737c7ae7e5969b03457b7b7b5720e3492ce254a63e031fc477361606a24821830d27271331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000886babc1acee93b5f96e4a0700805982657d15170c77468c77000f21978f0cc154a265de2f766d6f7f8600f378b219c0000000000000000000000000000000013cc47f0a1e5f7315e6ddb9003dbf901824e419854d234676e4a8593bc5ad4c15e8c59ee6985d0b729e7d095e9b7642416d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000567f08c96b8431a133cb284144f6ec8f7c68722f18ec257b4def0a18a754507eb477f405b8c256adb797f45ed2755050000000000000000000000000000000004945b59bc84df7b793dc759bc2a3352b3eecc5cd59bea7a9560c06ef25828ad2e9ccdc6b3beab7a71a702b829208b8556be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef4000000000000000000000000000000000584ae62e22e0c2fd733cf2093f7a1f3c763453cc34a7a7a4548d8fd43c95f13be06da4e41f257f6d38e6e6921ad0f6e000000000000000000000000000000000dc803ba6a45298075a8cf45939a61760de44d22407da6ac0d63939918daa6f78e8d0b7cd794256f992cc89b8622e737aea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef00000000000000000000000000000000090848e332eec39e026eac0e6416d1ecd5aee8b4d82712b6c113da1e7d38901470743af43bae951d4141592f6057caec00000000000000000000000000000000140f8aa557213d49097ef315a18ae7e62924a97c71139555baf08c70674031934b629a457f75bd801af579f9fe9395579ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea000000000000000000000000000000000e29d6fd73f56b4546358967d7f0080e6cad97531e3d672a91a6dd121f35cdf0f452dfee1ad98b7c832c2878b495f3c100000000000000000000000000000000050fe9818b36baa8ccef166247bc673baa8424e19a19b199ea5e9d0baf56fd68cb339fdf5d041b31545e28bb2b8fe32c773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000c13e5062ec580886d09c87c7cc72f7f19227eca99b0092a7e9759672ed1405d21fbdc8985847fa1b57129ac40bb036b0000000000000000000000000000000007d6407d32f846088759be5369c5ab66d2f512f00c93eefaca86a86bf7b1e3ef39ab85fb6c317c28c4e331a19b927650f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba6800000000000000000000000000000000112839aa4daa7b0d614dc6a555731cd4b595a0495f2a2f0f1a3b3fa1b603c36348e265145583e8bdfa8a2a26c1f822f1000000000000000000000000000000000383bcca42f2513ce42342f4bab5377ec276bf0f1910718c7203d450f15c5b6a3648a82e4cd1222109171030eaf05292d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a0000000000000000000000000000000010aa255df04dde054fc069473dbbcde9c68dbd71048b195df2b23e5471e5cd39eab5658ce689ca09db80c72e099907120000000000000000000000000000000013cfb46746c9bd13aa88a24ef3097b35ee2302e76b19ed001baee8cbe5b19c2620043efeaf81697ce48af0717a1066eec629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b000000000000000000000000000000001888735aecb7125b08f2a840957887fb5be0517788a8931fdb8d280579776c5ad70e6454303ba23908bc6fb864a4ea290000000000000000000000000000000019479631b9c711f700ff2353aac97cd0ddbf14669cc046e686ef19ff0bea0aa74b4bf771882f7226de0d4fe356301912c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d50000000000000000000000000000000003233c1edded239fd465f7f7833251b98ffed6180b56676bcbe2ed361438d26db671c03a6454a4fda34111e358eb2cb10000000000000000000000000000000003cc9768ad0576a34550b913a895e2687481c6adb3371bad5cc8f9792c61aec555a52bcb267c337649fa00293c9b4af3c685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7",
+    "Expected": "00000000000000000000000000000000117879988edc3cc57fe19ab04eee6f9e94a30811b809afe922d63bc3d785a3a581b69512180beb89402410a4d8abf6620000000000000000000000000000000000beda376a5f1499882c560961f8b0cfc44c712d6691677ea24db18b138af8a21a5a4fcb9cf5814473b0ef7c00719700",
+    "Name": "matter_g1_multiexp_12",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bc83829ec8e98081abac2fa8e0572e819b570b2499d4cd1e6748f48c350c392f5d52c672dd0bbcdf1469414d7ce929c00000000000000000000000000000000007d1574eb65b391475b49857766c808fa95ac2a78755d8d740d2df90bfa9aab3dd5c850d536c9794f6cfa2f004b4550c067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000022e4ed74f98d69a9bb1037a307eed57210d3ca92648ca9c54546c7b57102558ab12f5d2bb46502ba3c07529f64b72b30000000000000000000000000000000005ea660c44a9d36696a899ed1bbef1d951656f2eae553f4f124ac9cee3d7de13556a7884ffc07e20d5afb7bdb9c6f1638b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d000000000000000000000000000000000f6f9411caaf7bbed9b05368ed8bbc35a0439a5c1ae417215d10adaab203aa0a607642aa8b94f4846add8f5f8db755530000000000000000000000000000000012eba1de04ecff3405596452a4f5830bc6c8af2ab0e84115a8a04a2cf60400eb741e8eda78ef733338494fd4e7b16f812db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b0000000000000000000000000000000009898acf9cacee1f5750d54798a4c31796fc471a17c9d2ddbd00262f5a82e3ca968c3e02334c29aeae9b16d8916def1600000000000000000000000000000000017f5a3907bc14b6cf182af2778c88704fc6b02d2b47bbbd6e40a448a89ad1455f868dba330452112973ab69489534ece2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000e782486684a6c3fd7f5977fa40038e8a9ac0a8611e79c18ea5328248be9ad4d95c63ba9ce41d3b4d85701283369063f000000000000000000000000000000000a98e9f649d2431991dbad1cc7f4ea0c89a58bd7e75e4a5bf7d9a728943363777c1cf84bdb1853a976e4e66a6d3fa8cbaa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a000000000000000000000000000000001171bd468b4d40e77b8264e082cf7a168d88ec3c21adb6c33f215e82f5ff3d0d2314e0fb12d7ec93aca92532debde74500000000000000000000000000000000099bc823a44c54fd379798eed2559d95275b324481c248d452a02755e1b5a48a7b0694b637dce4c21ad7d73a63cef2a3e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa00000000000000000000000000000000190f9d82f079757ad752b17b419c63ca09e3c8a23d0f56b1e738dc8ff4d588a4a2360687679e51bd75615c18c49103c400000000000000000000000000000000191b91de53dc0807b537540e81d9219daee48ad27de9e5ab2980dcc09062b80dad2a0a9024c5b0465e04e6ea2b225d0249510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e4000000000000000000000000000000000c8f48d3dacefba0e1719f74867b539a65d640d2372ad38bcfc43548f7ad3d8a04337878529119b9175068b511efb04c0000000000000000000000000000000003c7b5c11985fd7ff7c75e2cdd8670f75de655aa81f6b99206ed8a344f86ae85d2fb14bce434a25a5ee25c903c238341713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000055e115a8a7edec3a443354b381f584ba13a5802520c54b51ade1bfc7c93c96c7cd66254738929aea2e88edf2895d82f0000000000000000000000000000000001bdf3f4b489cc22c6f57a1eba23d3348c5567d0dd1cc82924873813b92a0d0b2b90727589028b9844d351e13c6e3868c040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae400000000000000000000000000000000041fd1625afa48a446454d6613c17cc6a65b3ec8b8f2125c0eb7b8e5d07968397d43969a6579226f496d9b24dbb71b820000000000000000000000000000000006131c506f243b5ac40354f826ac1838839eee9f61301aabd88e499d40e57df3122edc8b36f0a8b16b72f9ac783efd3e17b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a000000000000000000000000000000000113a08cd0728cb3bab3886681d8cd4e5f14b3a4a7979f9929ed4d8dc77de6a65f7bbbf8a282818ea3f21e6ea59ab1f5100000000000000000000000000000000032e95b26193c9768cc9967c9710c7695f57fce8a4e089f290526842963504cc8c99981bed3cc7d827eedcf686c813c3bd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf25752000000000000000000000000000000000ac19ea5cb7169ffa2741bbef922e0ba307e2bff5eb67fbd2c1545bcfebb79948489605f3c6c072444093e996594c95700000000000000000000000000000000111c277e16440fc3f0cfe16bb81b927cf76553fad040c1825210fa145240abb0bfc8a40a016db15844b8830d4d725da3f221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000906df246466ac720b1db9445902aeba8ff5c747133b037f29b33880b3f511621a0241fcc46adb0532682feb4e8819bb00000000000000000000000000000000145b356e384183788358353a69c49332ca137e9faf30bbcd7a67434a980c27630c3f21781a36fe73e82459318b59331bba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e0000000000000000000000000000000003f2f02b7ab2d2165836349ef8f53e42d223f4f6a892e7b72db93362de3929fcbda5edc4606766fe26ddfda9d09b283b000000000000000000000000000000000feb10a6ba91dddb0829cd6b95a78958fd55cdb120a7237a2842df1a2007530775848c3976804824698a4370fb022bdc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001433eeb265f1d57027a80189806d071edb1f5ccb97da0b5e00dc75eb88304ef2eed287f5d74264245684a1677a23b3f5000000000000000000000000000000000be2d2b5fd307192ef8a0b2b4dc9970c112a236a71ee899a0a5147012a206a0274d34901594f54bdaae26f2552da481b87eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000011b50012e0d92c0f74e3b6e83d60bf77e710dc03baeedc949c1af218bcb87ca1528a745aa819a5b615ac355dec360eed0000000000000000000000000000000013cd46e3cbe008dcec36e64285173b7d545359c23fea32d3a1fa2918c5c5d671a87d90791b70a740564c0f731fbb32013bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded",
+    "Expected": "000000000000000000000000000000000cd7a2b89d286a421808305db523aca962c3f253f6defcfee9b74bd7f00b3ca8521b520376659d584b03fc5dd736d3f800000000000000000000000000000000117b8b8a8e299cb0fe3eee45651069a21988a2421b1d349529cbaf7362425349148fa043581e5fd275cc94a4fce44732",
+    "Name": "matter_g1_multiexp_13",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016b98dda34f703f90438f5c2624c1ccc870b18cf8eb964800ec97179f67f82c521b1cccb1b81ebd3484da1349e4c0cc3000000000000000000000000000000000c743850f15041ed9023ce296570036f96db4a510903a0e7971592348651b44afc0091c8f0d6e86bbed8bd3f6b28072af44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000007df1648d65d140c775f729e7739a807a7f430de0711671807a7154a8e5723a2b9137175d47bc319ca659faca10af23d00000000000000000000000000000000199ebb99b555fa438587b9badcf5d7858029e905b97229f1de4ecc1940ccac59503e0e1a99c9571d50ba39ac3619699bde25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea0000000000000000000000000000000000027b64caa979063b420cff77cd259e54bf86498f87e7297651f9bbad6087a8b4b704b27746db53f8869d080a22363c90000000000000000000000000000000003239455ad4ab885727a25b0cae5115d87ac9ccfd93120ffded5130ac683b3b2485fb358e3aca3b6cac4bb3da5b4210d2e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b210000000000000000000000000000000002f14454852a72159581b8a931d863c65170fa9280cb811c697fd067a505910d17fcb71b27963c2a6a02264aa0e1fa04000000000000000000000000000000000303f0857d990e90e19a076d2d331f5eb7fbcf102dbf8d4cb29f159fa2277eb413c0c10c3b501cefd9ca581ca62876c5e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a91300000000000000000000000000000000192b3fcf7dd2534f226ad51f40e7256064eb788e7c91b1155908fb752ed4e854fda44af13f0c681fcb818eb4202eb64100000000000000000000000000000000125b51b4cf8e9427db9baeee0417b02c2d296ec4adfd437667238ffe5137b85b40fca4fa705f81d0b4b6d788a8456f1fe6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c400000000000000000000000000000000130798c851758638c03f90f9181814eba97c5f93de85a71bbcc360bc53e4491e8fea38ff8c94061cd5008b0333ff26af0000000000000000000000000000000014758dbfcbbf0e1c78fb3ad4945bd300a74f2555338a009d807e2cf0e5fe582729556bd3ecb79db131ed9a72c3362c37e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f243500000000000000000000000000000000093318a1c189c8957c9736a56a4b3e8da13bc8a303303bbc106148a0a7f319e30f5dcd11787dcd3424255c7a02cd3e760000000000000000000000000000000015f0767a3a1e3c448ecbd4ac8c4c70db6daec95a1e4b3a69cb5dc10fb43f8ad030e360832f7726cb166e0fe5fad0c860c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000c7143093aea0143c58e2c459472f44b6b759a3f036aefced481eef6fb3a1b2af72ae4cc4de06af2a8a99e27cf9cae140000000000000000000000000000000019f44d1120d82e50f7da3c1e87a47d3433152b7141e9085eb54e04f30f5931d067f9ad559cf5d092dbaece723e6a724138cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d14740000000000000000000000000000000012a171d46d2bbfab83d02e023f5edb18e353ea82174d1a1653952bbba234c7de4fd5ed212c81f795e8c7a0b81e37087a0000000000000000000000000000000015dd85eecde306a845917187c404cee066038a764beaca9a58b859873b06652800291506b4c995581866a3c2bd7f19618a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f800000000000000000000000000000000176e512a4122ef10ca1fe6626cd2c839d4c573bede92092e5ca55b0bb936de9b62297b2a598a033e9a7e49ba9aabb9190000000000000000000000000000000013bf0f4c0dee3c9298192748497803a906e4192333b1ca61deff010a63eb8e4cbd63c7bd5b5546540e71bcac6000eb5380030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f600000000000000000000000000000000066bb65bbc3f8ed9cdd5cfcdb121274427ab7dff904551a60be48f8197c84400d54ec27ed25c2a09687f1067c10edae5000000000000000000000000000000000afe1e97e1dcee30959a6411328f0d69134bb4c3a0d5ac53b87f254593f7cecf3070eaa9e19de76ebc6e1052a41ccca00d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000001439b3031d7272f92c7072c6b44dd3a1c328251d34e1fcafc5f864b7072086168fa6f398d6334fe7fc56d6fc0e776eb600000000000000000000000000000000090885199f56df470628357ad224e19c29dc435ac54b8c17a7df5cdd24c3fdfb136952063dcb446ffe271ab5775bbc51969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000011a0c8f7d76a36e605f193efdb5f7899d7db5b89ab0603dd6184e69a7e51f0d7e12f466fbc917cc5b6dd6d4a0bac16c30000000000000000000000000000000015dfa17cdd22984bec570d2ca24a5ac373f6f174b66aed70a15ec892caaf92c73ad3d7ef11b2f4a0104df8ec5397f5e9957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000004bfe701f6645589925b34c1117cf62752b4e242e38bf056ef36515338a5c3698f561d65b237123677d926c1616618ec0000000000000000000000000000000011892535443daffffce0867dee36b7bc711006bc0963e6a061066b889adcde877a8dd3661250b6bc48064ed9dea304168e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d50000000000000000000000000000000017666cd38f1e7139fd032a79776301e4eef7fc22c144900c711f1568634d9712b2e3566bcfdd152faeef20b47cf6cf7100000000000000000000000000000000150c30df0eb5945ab96603b0f36120a4f697b6958a9929f6dd8d1b8a34a1d1d3f1a34bddf9ff7f1e105ca23ac34b6f7671a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000107f9378f695524614ba000d6fd1b72c5eafc4ee60c5ba36ddb72814936403fded547f8d15083186f7f5f5d94c1ce18300000000000000000000000000000000140bc17d86038d4fed0580582f55d90259b460ddaeb37a70063d09d83f5fb6c803f8b467927758cb7cc52a2a6f8a84ba2d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641",
+    "Expected": "00000000000000000000000000000000179ba87e1596396fb5cf88f91f56e3fd6f01dda6f95363be3365bd42960ed620b7648863524c22663bad20d128d22f4c0000000000000000000000000000000001ad84c4e62b0494bab4f2ec2931d2331d88502674a1bf58b765eeb022f3d6fbe8f8546818d965a8be79a94be86292c8",
+    "Name": "matter_g1_multiexp_14",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007eefedc0360b258ca2bc9add8e23b9d535f35332e7a35952fd832d7fe3d448aac08a01073876a21914a501dbca513850000000000000000000000000000000016188049abc44154b244c6af4e115caa14a977efcdd524ad78e5dce010f2f48259708d14454630eabf2318bb271315007740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000010a19a7cae27e432b77c77d26653c6f17507413a5037621bdb096fa4f33e68dd86d5aa3b52fa54655730fd88415c3eea00000000000000000000000000000000031925aae4540280dd6d08fc53478fbf05b0ec784d04abd04c3a8dadb04ad9adebe87101c6401ebb4a808104b3d7e88fd226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000000447e249cb49d64494fb1f1b18c94a44791fd8d4957bac13df1f992480f72a14c3aec517184700d87200092e866d60ee0000000000000000000000000000000018a12284086bf2f64297a65f6c8b55b4ff3b791372b88aed9085152e24b1214655a74a182e131d7023f949c8cd9602dbc133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000780d3f5c10ab7932e3e3b45c942d1ee2a12f28070674d9c666016d084613f3ffbcccfb576fb7779feb2d0e614106c990000000000000000000000000000000000ea320730367c89cf162305c69ad594d8730d71a910f53143770f50024bdbc40b7d2486b1eec63b1ac7dbaeb51ef9640fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f00000000000000000000000000000000049ff517593107482da6805fe4ab49cfe9cf71c9a95eba00091511719eb76db98f71f089a701c6c136b398a40dccfee700000000000000000000000000000000038d1566f1057bb2da7813c39374b79149e598e1651dc3541a445264693495dea35a6515dd2173f7de43964dd5e8257d70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d00000000000000000000000000000000016baae36e71ce87a6dd7136f7572788c256ef88cb73e550641f14a557828e06ad64f001fe78d69465fed92b67e8dec3000000000000000000000000000000000613a6b87249bfdfd01016ce920aaf902de85c066c2d64c866ca0a93950a1a971cc561560a4122d9a766e38f9dca9239ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d721000000000000000000000000000000001921d310700ff4e2868a28dd29ae6e0216bc27ee9463cc8dd2823a1b4670abe973859e86719142525ae5c76e2df0bae0000000000000000000000000000000000b4b4952e96be92ba6c78037e529c197c9404cfb67af04f39d24045c742b34a700057b2cedb3193dad70e64944642c01fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000001dae69033cf21e6e1618efba143426df1501250c82f214ecc9ccbf957e685d9831533cf7f747fc22309227aca1d1a2200000000000000000000000000000000114abe65155656679b89a11c7961435ea9f77fe2f957833dfb61b8538695e2569e509f0ee2c0bfff75f83d9399a3d49b5af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d700000000000000000000000000000000128e019ff92e7171d3c791bd4cf75b0f47c2a9d8722b4a8279f1178db6dddf8a4c00083a935168518a1c26a56b23624f0000000000000000000000000000000008d0c5f3300e73682f4756e6ff1d6722dde576beb587301ded34427d6935e59e76cc8a8cb0ea5f659db9ad5435851e53c7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f1000000000000000000000000000000001110168c2dc1c2f0df0dc645970c0feb03bd644fdbe1576d5e5a8090282bcb81ac9be738d18e72a31ceeb5ba826b40290000000000000000000000000000000013fccd2429da394be698812af6c3288e89a26f0244327cd38bc85d5c3bb934004bfe24449534b7d271add7a279bdc8512d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a000000000000000000000000000000000199b9c9772a8c1bb0c015c467098bd38b5f73e5d0b3f627c8279b8dc853fa2952faad01e7be353a2762b8144cc1614c000000000000000000000000000000000f781597005df947eaccca59939253b936d1ae84805ec27dde0dc707a4583af408672addb2eea607a14faec9dabe61ae3549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab710000000000000000000000000000000014bd5d22e4bd2f7b8df4add90446650fd83d72d531395fb35dfcff72eca0886ded935e7a0e3fc99a7dd07efa1ed60c3f00000000000000000000000000000000122cfac9ae5c98dd162576c92e9acb4582b9eb67117bfbf4074654fc8bc473793a7139995666447a7663f3af1446dc35c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000f67ef1eff6875abb96378e5a7b1602b5dc553554987589b9953c4401fefdcc5cd7b196a1a65cb3daaa13f9fdd703835000000000000000000000000000000000f58ef60be74af52c23662e6b405f1d5c359b2ce9d15b5e139460e10da0e31161fb52f529c7b406e52c6f600d5670f3c9ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000000b7dc680fbfff55bf0cf276a864f448d5a9feef303d2416e7d87d6d669456b951a8769026bbba545685e1f92277b182000000000000000000000000000000000c36a14d5693b0d9d91d831c0581d1f4ee801f86e5c32f10cc400f66b58f247594c30f0059b4ea79995d6f9d90b0009ebd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b0000000000000000000000000000000014dd947a01add8294f97a84850e6dd11ed4a513e7656daac5b725cff501446e95e3b966492e028ec23fe1238b53d99ea0000000000000000000000000000000003d9726342018f802df12fc867998b6016743739a2a4f47e1f6f50992e4fe23a6bacfea0e7ed5be570eb8242ec4101ec10bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000014182228dbd223cb5b601521608bd7f87659f86a7a01233d4158484024730925e3d841e05e07f2a330b9495fb028db6d0000000000000000000000000000000002e0ad163d40a56215a774751434d19ea17341f41701d41e521983ff753ed76c435c6e2b543510e47060edaaa06d29f665c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d",
+    "Expected": "000000000000000000000000000000001364f0b671bbcf69b3a101dd758ce54434d59fd053591cb744841ba2061bbe2be91cc9f2cbe1ec058119ec8a5e909024000000000000000000000000000000000cf5be1c16fd10ff97a154826067ab7cfd7b78ca5ad0a2e2117155f1ee126286019f8a1049b86095887e79ba67216a33",
+    "Name": "matter_g1_multiexp_15",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010040f531866c4e6fdc255e2a7ebcea89ffc36d44e265d5129f8be44b07f00646a7810662723546ed158b2cef146c7120000000000000000000000000000000016d6a5e46b2067c29e11d00b6b6ae9f0987afb4e9357c1d223fb2962589c3527f94d4e01f2ce6a7c57f971756163e48108e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b790000000000000000000000000000000002b6e3a234119f0f06a2b049d952230da40590a84d241ff76483169428e787093ae88c4040c64f2f1e3aa5be2c37db3b000000000000000000000000000000000732aea9a2ac5612ac350b474d9d267dd1ffa822cead992d3eb411efcb6992d196d66868a0e1f89dd47da584d075d4f55e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d0000000000000000000000000000000014c3187e04024d719560e36b5a63228a685f085aa080c82244a3a704aa2ed68b219d1c699e49dc1fd648e904ae638e3d000000000000000000000000000000001911df5a9f709b8434856c14fe4111935156a984a5e8cc27081059840167c3daf468a290461bf6cbd2ea4fa21255d7c11362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000216ba7fa8afa06136b054c11bbd978209017dc4d8c8a2b05fa717a97f4d88abd9efc1e9879de709b87d7de65c859b65000000000000000000000000000000001797c34bdde358ba5533d5bb531915545e3ba359ea1fd66d9dc2ce06f7cdb64684bf11e5bc02097f3b957957c986de1074d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de0000000000000000000000000000000013343f0b79485528b8a5ca5e0780e8925ea7277970843ce3699046673a41c977dd0cbbc97273ed47a1a105a0017853340000000000000000000000000000000010f3232b511b8d529f91f1ab613af1e2443947fb2e29c4f98d1dbab1aeb965079f64281d0b10e58e26a4bc0577943873389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000005aa017b9381423c9d00982fffb93a7cf9bceceaaf31895a17ce3a9bc42bc5b6f5c69679ebc91c9e5cdaf7651cf78621000000000000000000000000000000000c77e86d84377ceab757a0da9bcea401b3db29e8e577da793da0d5338eb471315315171ec4bab4e9dab36f4ec6d907a85f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000001971e5758027516443fb373a8ba8cb98b78fd5d16b42a83becd2a9b06e8ca7d255fd687cdf10de7dfc6bee5cfd199b1f0000000000000000000000000000000013465b45ed2469c2dc6ef4b4b8ac90b9b30c793425093898203d3b13d76cf4b8e0836c6fe57e637a6eb08bffa3bb55250dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b6000000000000000000000000000000000c14833dd82daba173eeb40c29912c0edacff741bc3ab03ae4911c334cf91d5832a8847d7e175934f61089f523b77fdc0000000000000000000000000000000013820819e27a27009ee44a5cf02e995bb317ee49b6068d2e9f4c5f072d233a6808d0feb61958e047f70b2bb1a5426319c579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b53600000000000000000000000000000000105a1323577a38bc9495090b4d023a9dfed8b510a9a6d755f7ad6af72eedf1c92e6a5172cf68608d8dac34242d1e0eb200000000000000000000000000000000147d889d919a58de8aad3b4735359201c47d8961a1dbd321061a81c67b1a05c6732782975445d9c1f2aed12b0b7306f469f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f1000000000000000000000000000000001335049a2ed3629ca83f041e4ccedede286445e4b79f3afe225bbee6273e0cc84b32b91c54991dd072c54ecf0d6c538e00000000000000000000000000000000098220fab5661a40cf34782efcd62ede159c82dba8c6e9f032f7216b888ad85fca1031c4622547a03f14185b3eb6d0d576618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000018799254b6fe847f53e2892343dc77efa3717bccb3589b776584fcc9e934deb3b8fa4c1ac0709ce505ca4d1504ed822c0000000000000000000000000000000017b98c35564c9d67b77bfec8ce23310c93167a5f75a4680420e8d71d8851f4061d897fd86b52d4a8cdde391c5b21a63afbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a0787617788000000000000000000000000000000000499468c8da336124bb89285a81eb76fb05e4ac2bde68d2f78f1de8926109631ee3e33eeebf686c7f6b7b4d68d13d2fc0000000000000000000000000000000001ac43e7c6d46e88d88a195180df6a3a91b3aabbe54f88c8b39168ead4b9847a031561828b0076b9b94c8fc7cc0c4636a0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000006690b59efd7c3e7f9477cc35fc5e13a5dc7f485100ecde7771e7bbd9f79f72719cd45cc9e0e791b7b5dee6f0252c53d0000000000000000000000000000000008b6f82c8514f7804a1d75f347f08334064b81ff95765355550c53098e19a4a5fe59c6a9611f4795981047754a6304792c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b0000000000000000000000000000000000ca51cd2fbe8d015a2e80bb4a24f52abfe6b99b1fbf1b656d4398f76e8e73e7a441dcacb43a4bd0a1dd45df2ed03a4e0000000000000000000000000000000006269d0e0f77f3ac5af8f70905ddb323362ec5de91a1eb90bf3773457a2bc2d018942e58c04013b83a7764b6639ea87c141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c59200000000000000000000000000000000122f6c35f7b1456952b56a5f90ef9066a191a4164d4b2f81965bf7318d485c725141576e5a1164c3c17a8bc387c9262800000000000000000000000000000000086bcc20a2f0f0afd4ce845243061e1c12eb238f2d3fd711000f259c31d826c2bb56617479139cd611d35b6548a438101d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c00000000000000000000000000000000008ee124fb457671b65c0f9f550ce1ef196c3bf13a5403a3a21a801cb1a335012b43cbdab33a1ace7f84a998a4322ae20000000000000000000000000000000005b0067f853d9dec4dee3b2834679b9145bba170f22b7e1dbbb6ca3dd98abe4f41673b283f9c43f2cc7ee2305b874a0e1b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4",
+    "Expected": "0000000000000000000000000000000016dbe06af92533e96177581a7a349811e0a3d3826241c4ca047411d5b24c76dcb6849df7f4ca050286b2d84afd71ec9f0000000000000000000000000000000012dc4fc05526d6dd6da27b117294da4e9473a34699871b7bc9f02937a8308c57b5387a6fde0dd09e8a01702a8b97c4cd",
+    "Name": "matter_g1_multiexp_16",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001394f8d94cccdaf982b1c6a8080be6bbb65c9352a961cd5daf2f817a17bd8d5e3e086c6f54f6068691f3edc4378215350000000000000000000000000000000013560d0482e6ef2fc19cf274f85bc3d14236273dd8af86107839882dc26dbe897a7de90ab5457ca440498265bb59e59358fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000bfc36885481f9ea9aa275c1b4a774fd01476c6f956fe75b5f6e73199928b1928108658e35dad50b298307598582443a00000000000000000000000000000000161f833b58de4db4de0af0fd17ddf81ba20e4b6ca21dd80852cb992afce9857e6cf99cc580664a970e9c6928d13dffba73b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad00000000000000000000000000000000042489a05aecc0fc139c0ef0c703860ed36f8bdf50e4c772487c0d27b46b395f6417ae34ee98290a40b3b765d5a41d430000000000000000000000000000000018fdc2c8ac7aa01ae6dbf84412de8a47c3c504f2abed060c63190265babf779384dd6e3330e91198f5bce5a103bdcd701ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000005a44b3af7b95c7869d74c7084d0e556a67b39090b7a62fe51fa833cee316044a26d4e383695ecd3bb1715d0693f2f1a00000000000000000000000000000000112fafd6d6f1da250d12817711bc999217d16d7a6a923b5e11cb91a333898fb27f7b89885567d33b39923d7a664960eca691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000197317f509ddb9d536845443d7966314eca15f20cfcbf3ff2f8701d94974e35cc0957855e0085b3f85c7da512ea882910000000000000000000000000000000018b1ddc196607122be575ebc923dee96823fb4f8ed05fd8639b1af06ddff25398e67709809b642d4d9c21dd8ab6e65470d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000000c7ed49a60aa90f074af9f7fb19f6e27ec4a83ce2ed77a44c70c8e0bec02318bbe44a212c505efed3550ab6a1ea2c6d50000000000000000000000000000000013c0a772ce2c97522607b1b05cd9a89e930b6371202b69eddd108237f1495eb1c6ca65549c5ab030cc4f7e3ff4492fe9c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000fd64797f2bdd429e6f5217858cb14d78b7054b178b74696b8bc8ec9f9ede70bd03c36c824a3f775ee2f8cd6be7e2ca2000000000000000000000000000000000f675a8a43da599a09ae2367240870636ed385eb280cc199fb7c4ee575f5e3c5fe0b302566cde70b956f3c2b20fdf09c6fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec500000000000000000000000000000000065856fe1dcbef934cef47b177ecb7df76cc8796624400d5c0518aa9438bcadf397234808d099bed89ab674560ffbb1800000000000000000000000000000000071b2ff64379ed3e20cda000602c3504616dd673aebbe7690e797d6428ecfbdb29f11138169f3462dffd319cad68b96ebffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf00000000000000000000000000000000094d6e0bae02b4e7541a27111092737e7b27fe742fd0400672953d8fd787482195a2cb59a91e8584be002976c3c3e9b8000000000000000000000000000000000c2146b68ef535ed9efbed7fd02ea5cf6ba8cc20ad8bce17c06e5d595282f6e7453e2cd267181e477f511cd4fd56e8b157f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f40000000000000000000000000000000003925e9f1e24531f9f26547108671a6a0fcf58aa6ef2bcf9f4f64b659782b93187bdf2988029de9f51e5d41cbbc4744d000000000000000000000000000000001975210e2c8bbd2431288a42f9cf5d6bd6c6afa2eb05caebe740c0a1f680b9cced0f32f8f84e368563183b97aeb6e7ef2d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba8400000000000000000000000000000000004a2ac3d53c193265889f6c3802d7c68b938ebb6298dbfa14d1a9f515647482c84ebbb3855686b544d4299554473f1d60000000000000000000000000000000003283688bec2b8ff2e34565f8e254d579f57f9c0fe0e8521129088099a5005dfa9d565d52a75a2b26148205dae83aa6a614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000001b82af64f984294882fef7e5ba880ed8b0a36a90a5e9680ddfc5d86e65aafc3899a7d63e2a420113ba29412a025a0970000000000000000000000000000000012b11a5bf0f7895e329c2c6bb3d1737aeb5fe9f32a96262d8268c74687a460c47a89e252e607032576e7b67f5ad655b87c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef72840000000000000000000000000000000003ad0dbf936f79659ccab765a61633ebb648503a774e92b24967aa8f8e45c5e26f03acbc7984a45e089ce68c5566664c0000000000000000000000000000000011686f58262dca9399d95cf2828b50b216e1df251b61c77f952c21374bcdacd99d26891fe5f335afb7ec76ce7d95b43f64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f000000000000000000000000000000000ddfea60c169079c0fb4b9c3ca539e43b7f184f31cfa2eeb942acd2a84b472597c83fb52544479f326bd1207b4e872f000000000000000000000000000000000102108e827cf4473ba1382a2fa8f3b904f20a40657784d54e3a91fcf2703dc6fbcfb7f4b0e04c3a53a24a6e14b5735f435bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf000000000000000000000000000000001286a578ce3829f289cb98aa41cb6bd7274aecbe15b5087d8c16d575fd991878b06c88f17fd4bd905c4576494ca9f8fe0000000000000000000000000000000018e3cffb0746cf70aa79053ac579c1adbb09ed5b6a8b5e7b84951460e551e9bb62f2c1968e37ba34f7633e60a5f1f2a97980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b28000000000000000000000000000000000ad648d5e0a45c8208fb9b6adcb3c47cf0e20ca906c4fdb31e5c2f0678fa3ddb6e27848a39e8035cfd9eb91aeea824200000000000000000000000000000000005ea40be38d82e2b256bd5e26e71dc642e06145d94c1ca4fcfd6e63e2bbbd7b7aa153b498793e94ed1d89691195b4aa3a256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca",
+    "Expected": "0000000000000000000000000000000013a9e1e306a5cfd461a1df4f2097f0beb079a845a959ca3a6d3e229198c75d29daeb5e7b65603397353cf800e0a10d820000000000000000000000000000000016532afaf2b6d884a5b593cb8dbc38b4e2bbe522ac69b745fe9938834d66e2393653e31b052a803f3f901abdcb352eae",
+    "Name": "matter_g1_multiexp_17",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a187eff5afd944ea8731afffb4aefde4046b812b47e7cd99687ce40a5af90d6a4a2c7e2c9ce515a229e6c99ce46933a000000000000000000000000000000000121183879453793d954c99cbb007ff428c721d0e0b9cef192dbb177696ab9d575d3ade2cd56964428adfbdfbafba7505805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c00000000000000000000000000000000196b029b6a808602b09dd4597db611f19bb911b3acb5dce08bad8676cae9910865355cca0a109bb8d7b60359da6d0544000000000000000000000000000000000cf045d01c1a6d6ae397b39833243ad3cc310be9220f71a248032e9325c7186ce3ea40fbcdae5258410e1a513b69713e502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000000c6578ed0ccdfea63fe924d0a30c4aa7d65d9f85ea832733013c0ac225f039bd6f94b4acf634a01ac67b7165a810db8000000000000000000000000000000000624981245bedf55b95217691d9dfbc16d0d83476f8c09a46f9541d77c29ff978ded7fb7fed7272701e385e016647463e7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a74000000000000000000000000000000000a406d8da1910d9ae8e52ac70f1fbb85954ff7590863ba9f6e00861160f83defd24e99be31ec63489a483fa77d84ffaf00000000000000000000000000000000170bac083f0f6f4ff5edbacc5cedbdfa314de364e86486cac0e656d27e6a4880ea3f76ebe0f69927299bbe4a734e0482e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba000000000000000000000000000000000b8a583c24eba7a27a05bcc606a10a306ec07401ddb8de8e9bf206250ab7cc720903bd81a2c17a9e075ecf0ef99ad65a0000000000000000000000000000000006d5c7e9faf830ebd0154dc1c366b84445a85f0ebfc13b5339294752f4d1dc352e0e4204d9d64abed83e8297325de2556e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc500000000000000000000000000000000122735cbd1927c40688662c740db5cb78425942985ea69c729061a6ba749c78d4fc3d894d07c285aea9ee104f59581690000000000000000000000000000000007c18425af769864f403c39ce3df4f07d4b7319342457d0dee30ce4bab013b754e2ab7492f2dbcd5bac2ec1ca2e0220f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed00000000000000000000000000000000039c8c0453627d13ca0e174f5a27525f8a0054ced2b9e7d92c0ba7bcf06c78c1e1632db35abe2a81f72b986934ade66300000000000000000000000000000000134876b42096d986e6004364e176e23f81637f8ffd3dd86097f480d25aca9ce3a96c9dc73b651106b4de307c002dad95586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000032e727809658a52f60a973d32bf47bff5fc45404e6652facc1801d52188dc7db79ac1bff415a6c3e49e417f205422c7000000000000000000000000000000000c83d3e5ed78c1304f42afcc0143f959ca24348510e241c3e79ed5eff8742a89b4ce131e63544b9497c2a1712999a18cefaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e4000000000000000000000000000000000c2bb8dd01510ffe473715d7714e47dc8fff0f24af49405e55a9537a617dbf425950ca3df681f1fb2a3721afdc5a4d730000000000000000000000000000000019fcf0bdc8cf0f14c4b8eff44ce2646feecb3ab0555f530f579cb2764badb6445166598824f7b0c46a857865ade1278239d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca80000000000000000000000000000000011a463b5221e4c3abd9652917379564ed2830907464235fb6f9038f1d3a4f0f8cf9f6ccbbf66c86e216975b2d372400d000000000000000000000000000000000f0e9d5050d539f9211ff7d3cf3f0e7108c5580b634b122810c78d8fe047ac709bbb06ab1501e08f0e58093ba8208e0d4c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a0000000000000000000000000000000010b293dd411de6a5cc464e919d290d6bdb51952a7a68cc27aee3ec80808bf05a50763fd4c17f25e94e655997bc948860000000000000000000000000000000000f18c7ab95bd74d9095ea9ea66b2b14987157162b8b8a313a781ce58b05d2307db4e853733a45344923488ae9dce1a459af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000013ca27fdf920f901634156567835601ac0b84efdc79d7d979c2156041bac04f3297c1799d3b0641df33da9647e604b87000000000000000000000000000000001527cf040f6c84496ceb57df9c9ebda89c394eef034e40f5e6b540e931775ab91a4aebbf6078922da479ff397cc5271ac72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b00000000000000000000000000000000197c0e4474e27fcaf203819563b86e725778409c7d6792fe41820c495e412382fefda97b7df560885082c70f9d522024000000000000000000000000000000000b14b9d40bf866d933a15e16f06ec16b502ea8e7084d68c74418414fd281a6da50bc443647fdba348b26b4a3490d0ac4b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000a254b07ca0f2c9219fc0dfb49bdd7901999cc85161f741500a3ae8be566e64f8a5fb3e59985444206a2cd02ed4ee99d000000000000000000000000000000001726739e92da7bf5a6d2dfbf96fee6028fc7022cb1be2f838ec1b9bd08ef141f4b24e093fcbd99080721063f8b7c98dc986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000001133389c12bf1d2e232cfef1a8303a733edb0dc4fa26acedbb288166fd232b79f67cbe76227ab2eb517f1c3126b929a30000000000000000000000000000000001ca6bf5c18255bb3c533ece833964320bee7c3da4af56d876058edd15f89b0ef147fba11e1d539d528e4bc452e17df8979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000000d0caaa05d3a01c89d6efad96f5957f1f9338136119e8530853a58c0698583d834fb0f045e212e6889d8baaa49815c790000000000000000000000000000000009e7fd124160f6ba3afa752b2557f1c4b5f4010a6d4a3c8a8bfe350c6b6e198b9e3d11f2ec7dc6a02dad4c07bcd4bb1d25ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990",
+    "Expected": "00000000000000000000000000000000138cea47ce2ea638f06c26d24ce87073f98b023b800245b6fc74fc2851d79a402b49c54e5df4e1aa98e33801d3fbb965000000000000000000000000000000001558e37121ec3710ff5e6c2a4e418c803a5b83cdeec98c8216b8dac7890ce17bff08a95ca2aacb40eccc761c8a31e8c0",
+    "Name": "matter_g1_multiexp_18",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001920ce210ffc78b2c053eb2106acf1e238ac5160b50187fe816010e8a95ec632a7fd29565aefa4bec90d87701c2610dd000000000000000000000000000000000322ce646a20e23a1a68361806cf072ae3d6310f4055f5289ace0036a90b5c7ada537e614780156f6a103ed726e15b4fbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee967000000000000000000000000000000000a1ee4319282f43ab9cecccf2c7f5e08f35a6c7e7bdc8dd2f4d642e8968aff377791a5d1e2b2152c59a8f36d9bbe04ed0000000000000000000000000000000012e60ad9f99f55859f2529ce02b8b41f8565705455cdfeef3cb315903ffbf29fabffc2546359007a36ba579b6dd06c2043cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000000c2aa9516360c840b7f88ce0cfaa0ebec502bc9cb9304c1a4d895089a2344bdb6623638e730cf30c66d977e077423a000000000000000000000000000000001163f60b32213940c9cfdeb2c86d5ccf61c0a714436b3d0923ec338ce7bd35542726a87a1311c8072fd589499c26521d733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b600000000000000000000000000000000116aa02028755dd5195ce0b2d3234d31b07b557a52330fdb50064a18015ae630f427a4512dff06f93ae67c4fd0c1e10f00000000000000000000000000000000117d4a68064b3f11d88ce976ed43ceeb742ba6f473645995a2773121b2b8edb8fa2715f51c8be109f8d91c44e8943e7c5112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea7100000000000000000000000000000000166cbdb131fadd6c4e7a94af82ce4fc4805dc34aacb0d6cd89e69cef0b9071b112ea4a7d9d03e3dd961b5d833b84195c0000000000000000000000000000000010736a73e2283849595569db9a5b0b9cabf2182c3d8c40a39fa32abe52dd6038edfb8176f64ec12671e3411dd69397585683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000ec2405173e541945011d09092cc3a71d9dd1ff54451127181bb2d5b50876a148e59f298ee30ec5473c520be0a53d61f000000000000000000000000000000001239198a5b1f6f57bce914583c3bac476a922e56d2bb30da4912acd31cbf307bc258f22fd9f6a0073ec48dfdaa4799bb5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000000232940188006769a382a4958383aa6702b2cbfb9c2907a989938ac618f23e241767b021e8ae11c23617ab393d4f85a0000000000000000000000000000000016a672061fe76ed943e36b2d6fa4aadf579db96eba5e4c60cda2884ddcbb0f37668638a3167d8858cd296917eaeff8e0f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da00000000000000000000000000000000066fe1f7cb3d67c20a1ba29a52c0c86d6a2aca176630ff20d45632398a39404619e55b8ade69e0cb0b7a6f363c3b2d4d000000000000000000000000000000000aa25dbff2a8c1f1d0982a709fbe88563813e918c8f25f0da9c5d0dcf3abc257f7e32def4efbf74035aee1ee350cd4fa57a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000001e54b0e8f34cbfbc20c9feffc555036857c31f453a1bbcffe67bb71d0d6b2b278b2ec5d6ab6648b397c9255a1139993000000000000000000000000000000000bb6d6c1a41675b3394f5b9cf14ddfe73c188592916f24240edcf0940fdab1d1fc04a11bea4af90d0d9f6734a743b38086ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb00000000000000000000000000000000099f521ecae704ed5a37ac90dd4beb4fa21ac197d467185c8329ad7b87c02943a228285b109178bbc2606e89699403ce000000000000000000000000000000000a95a85f84e76ebace78bbedbd13c6b79a6339dba246596e0695aac18d2b14b370c033e62a01caf8484dced0ebe8a76a03fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac377321360000000000000000000000000000000005b8ba4c7d3c83fbe9bcbcbf60b0b3ce42b52ca19a5a322fb18bc20f81c2fcac23e1f62b9fd6edde5ffa2e37f685e06a0000000000000000000000000000000008c03604012e4dff47923a2a43382edde86c76754a1073ba51fa3a2ec7011268ffcd1452d46786682ab2ee4848210cc635ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc62791000000000000000000000000000000000869a2ec19afbe70ad0a15532f776f56da5d7a7dd5b75194d0c65d0304c69a6d0363c0ff3b549e8d15171fae18ea13f8000000000000000000000000000000000389d0e6c9d73bd98202191b5b213fbe77bcf527faf98f4d25c9dd3ea2cec8f3b1e8f261d9fc8baf7b1c21dfd102f99104a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa0084527000000000000000000000000000000000138a4f53b8fcaea11869a6208e7498238dd80be79cde96885e6e5226315deedc98a17f8d75df733ab6f15dc24efb5c5b000000000000000000000000000000000d25d69d6d5a9c597fbec8aa7fbbe579dd86c5fd3747378e984c20b34e018b83f889bef3069c693a91ff552fff1fb8a403f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c98162100000000000000000000000000000000192e8e186cc9159d2207b0af2dca495e9d0c82fb376041360ea80562e470168b52a3326553902fd6f5a43ead32eb968e000000000000000000000000000000000fcac12d18fdfb661a12d112fc3414839bd34aa244ce0cb40be79718ec37a014b43856e5e4b003f4816e04ce612e63ca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c942342000000000000000000000000000000000b860984aed11a63656e3390f5e94695d8cd9367ad7961c65d714637c68ad88a3602699ed3f627f0fbc5782ff18775af000000000000000000000000000000000ed00636e74e8163645c43b8b31f05228da7c42aa332ca250270e5f14b3660fbadb8e8957f52592d942b1cc1bd2eb0a50033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000b162c0897755fa47053e45ee1b298404818ca282a7b5818364c292a6052703502656e536f2dfb470730e9bef0d7cbf6000000000000000000000000000000001924ea42eddcddda067126534e8b862f0e16dc0cc296ea892115a9ca9734fa03d019e90263be2c909528129a12a68d874c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87",
+    "Expected": "00000000000000000000000000000000056604e75c1069b4a061ea72cae2cfcba90130f6db6d9c268649089ce4ae1cbd80120a086e3f066f2584f5b2233b468c0000000000000000000000000000000018c5e93590b89119ad7d44f80cce5ecd97d4a4d3010a19fd02a58342752b166a1b82dbbad30d6f3404b330dba6a57454",
+    "Name": "matter_g1_multiexp_19",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015f9de55b3b45c16d59adda55d9f5059e765ddc06d22d6e68c099358d8df0229c6fe368384a0486af1cc9e532f78817a0000000000000000000000000000000018b992d73dd4c602afd82ad0845ee2c6662c860c5b7be197c62a8a20e91764004b5293ea40602574e91c313e8103e7a1dbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc8000000000000000000000000000000000157a9795cf9a45d2ea5e0312783829cddce176c63eb16195e7994b0688f9f30a4f2b2113e955bc66dcf05b5441521889000000000000000000000000000000000dd9365359ce805327b8f627f02ef5458cdc806bba246dbb21065c89e7ac6093004d214145cf3dec605195f14f1a49d357df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec0000000000000000000000000000000000066d9a54dbb5fe64835523e8ae05bb70b1934e389db0ee7547da60e4af965c7eee14a148f2e3269f01e8a545480db610000000000000000000000000000000017d6a22dffc3eac4366d0d35bfdd053d73d7b3392e7f52fe04e7e481783db3232f85687d2341358d2148fb3af7e9315de2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e0000000000000000000000000000000008be91d2752203afba19d8f3660118f83dbf851a6d2c54af389ef979121c55426d0761812de72a79d46c66dfcd00d5cb000000000000000000000000000000000269b050e36718ef4ebbc89bd88106a4043b267d974439855b6027f7fc3441518c39af6d3fee46e87d399d3ef03c63c82897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a94000000000000000000000000000000000dc976bbec5c5791688499da28c1d120e8a68eb5511ddf54525c047378016f68e8590b95f05cfeffba56c3daeb0729dd000000000000000000000000000000000af6e02afcbb707fd4d8bdcb5e73e1db56d7a2eb02258b91ec4a5c46c4627525220c11e6e379077677e1b733e2df60e02f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000119b86eced2222d203b6428907269b950bcbc1519859c013349b1c7acf486d3da5c4b35319e6b1ba8ae815e4ea14a6900000000000000000000000000000000015c342be097ba679319b83a68164f6820e2ceece3a90d1ec296514f0ccab6e454a0fc444d599a812bb4d78e656e8897fce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a610000000000000000000000000000000002da8de95ee2ee1be2f3ba8afd8f52a4fd0e352c295e92aa8fe9a08a03b6170222f5d6cabc9b9d9bf2835128c6ece3e9000000000000000000000000000000000fddd2b5faaff49cec261eaa8d093b410e024e1620863b6b9bd882088b59afdd4445a4971f31738e2afeafb36900b2d47b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000012060c8cab190beadf40a2e3d927d7cff21c475dad04d64c718d02ead9e351a27be81a3c5a71c6c95aa7d7e287070356000000000000000000000000000000000233ee868716db87f46d546aa1a7e4d3e70b2592efa0104d9f4fab1680c627484a33346406f61499e3971157a6dfbf972b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000cb9c4b59eb8bbbfb8aa2e9ed72eab69735a0154645d68428f0bda762d3b061b0659b31a907f531a55c0906532c539e6000000000000000000000000000000001806c7e8a8d95a34403ec78b43dbfe0bb09014fbe0e019f8c3b6ffd91a75d5e361a6794996e975309fa716b6c6a933784f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000131a492451e5c0ff787a233f72766339d7dae09f2e17c6bec9faeb08e4e48d6407b12adf2dffa3911395d5f25980c9650000000000000000000000000000000001f14d5268c422f94657a20ca02be7d007ea88e1a352753b2fdcceca5275a7ac101c0ecfc075735eec82b8fa6bd61c980ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000d988d419d559b1b487297cec19386f28659fbc5f121750b6bbe941794954e82e67c15a9a00334527d85e9be706bc2960000000000000000000000000000000004c222c037fedce38f42da2b08f06614ec9b166cc6428e3c4cad8ffa440af3d8fca7b9e4aff727eb0890effbc2b88060002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000fc9786ef5291943cfd885238090be47632c10cc46df48f6bb5250a7a85690f1c90f5f5bae03a71d7c52634cd0deff340000000000000000000000000000000019b4ec13ad67e058906a3559cc683511715b25e52f39a591b22177e2dd235e042832f740269544de112d9100c1ae49d9aa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000570b5e7b74c04db066d0aa751c9f763f59c6121e4e2ca4eec222277049143fb2e5fa39ac0fb41cd85310e4504f662ef000000000000000000000000000000000b522af535ca2b9db0cff08bf8ba19862e8f964b6210ee19f0cfccae8972150ae41ae1b8ddce4b1d2733c7dd47bc4c87df9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d860000000000000000000000000000000000892cc60eeaa0ab6584ef2731538a84c6a1e8dcc2efa9591ef1321442684ca9fd953553268ac4ed44bf50004683793550000000000000000000000000000000010234542eb7231f4356c34e11e7b4f08b4cb405a31aa87f961d4eaddbdaf5ba6227b2764e7c7c9ba76bac7da3b19f6014005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000005f3ff7ed08cfc6bfc8f5b55e2b368cd7e9f4a508ab46c7a383b2123b0346b81c39ba1304d628448c65d8c86bec682760000000000000000000000000000000001cbd3457f6925d5b8db7a785587d0dc6e2ad2ff5a6683dd11c8946e953dee72bd52760cc977987cd06a2679c74f9b64893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd15157000000000000000000000000000000000aa17bed794d72f8ac77989ce1b78550da54b4920ef6ac4ee0e83bb3cac5431cc7fb5c300c021045d4d391c67963feab000000000000000000000000000000001300e87daa3c36d87138628ad9aac5ec7d62e979c83c5ee4ce9a375fdabc745fc5874578945395ae128022eb98c6d8e4f685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b",
+    "Expected": "0000000000000000000000000000000010a773006edb1a84341f2971f9226841142b26bcc4af52bc8b000f165f6c59d094aa2eab1b83b3623c6c09145b5bf6120000000000000000000000000000000000130a0242c95fb2b858256de2fe27df7f5736765f880673300e3506a5e756b3b563e52b481c51a003bac76d04f56c5a",
+    "Name": "matter_g1_multiexp_20",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000090ef8b0781c66698848215b3aa84f7be47f86a9d95bf5a1ebe9c3dd6615d4fb4c6425f9e0029fa3d7b94052ef8bb252000000000000000000000000000000000cd1927ed1bfac35325d69fc924f4045c5af9fa5b0a18fbf6c658a3a6d405ac1159d1c82934aa116a98cceb382dde2ee94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000dfb10a6b4e5980400bc275ba5cd8211b8a6bb6cce026546b9459805ba48f46a429ba683ad3f96ace4a4ffd6cfdecafd0000000000000000000000000000000001f643a6d83f235edd9dea19f0f2ecb98a82ba295d8ad45f75be5c0d5b1a1522c5d9f5ed812d47da6e5fe8d7924648fc6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed7000000000000000000000000000000001824ddc80e263475b6ae3b73ef5613c7334b2f71c95d64cbb84dd489851580e767be29e7c7b47d53668a0ee3e6bcb03e00000000000000000000000000000000073f6ee13c3b05c466d35ac49c33e5ffebe5e8325f8f06b893042734bcaa4a1bc76da272602664c2aff48e731cea0304e5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000000abe490a12162aa01307e814684261566302501f589c655b3cb840876259112a1924b1ee723e0c81d6cc6b19535d52f20000000000000000000000000000000006a2205d02f58dff40715070cfd635aa5e68553eea8718090e5f6a96dfb0a2f5a23e11ba92d38a7cee16ce67aaf5de194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000180b5eb4201b4f10f605b4a7f5f5e25783bbd7c9e354238dacbd29563cdf119c832b4ca5c908329d5087d5c8c6786d68000000000000000000000000000000000ac5f56013acf364ce736c455a88a4b2615ca40fc67251039eb99df3cf6423fb85695cc035b6a9b47ef15db7406880bcde4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef1000000000000000000000000000000001963e29f92f6f72be2afa4635221b0d2f6afe9ada4582bd7ca4b77eb77fc4503578f38fb49aa1838751db8cf1ca0b0cd0000000000000000000000000000000009856a48f12966554afbcde1971499ee3ae40c9c5c3aef13bc415fddb97545ed84d5f50d2a26b9c16c4403a487dca614bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006cb805ab137fc56763f73867a7ee5635448a8a66bbeaa9ff07554db3d07aa38542884006744f6719f4cfab1392039820000000000000000000000000000000005e6f6f14f7aedc757cc458ba363fb5d97ee0dc092cf6866083722d4535e1b852c1d99d0c7c57e96a644de4b431c7f9bc3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced4329000000000000000000000000000000001045bd19d4fba8380467df25a777b1ed2850b7f5c5ff5501c048339c2f71278b2c97e4815973303e9eef283378cd8f470000000000000000000000000000000003278c7c8aa02c15275cbbdfc49f6286d6e7fb208a71a4da390c0c853684d7b4d8a6ab24953075a6a45f79fe0c9b910b70fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000007af4aafeee0372e88786c6025a710fad46252a8df870b56bc1d8a39497c2422bc01aebfb567b5b68273ac59b5cc8d6f000000000000000000000000000000000dfe4a8471e42dceabb609b983b59dfd9869f29fdde01a168c07247252a9be6555a823a61487778597e0ae305da4205fe538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000005f847129487acc07fffe21e2d0aa6275a586f051c06e2575f3bf8549ad9f6c2678c541d0dc7bdf909b7cff683ecc5bc00000000000000000000000000000000163451ea5122e16ee62d58d6ccaf8cd981a29aa820d77967e69478127a76092e9bd0dc9f24a27ddca5b40b1fe8ce18b130b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000faa1804b1f65a6ca75d032186b5dda63799a5fff3ffcf1f53eeb04bb5ce08be40fac13295937f34666e0f0be3bdfd9c0000000000000000000000000000000016a9086134daa2a1374fd8eb74ea65858ebe8b2990bb92972121ac68bd6bd77916203a1033ac4b163d863d9120bea0a33a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb4605000000000000000000000000000000000a2ad31568d9778b306525e275bc4f525d86c04dbb98f48e72adae813ce9d02dc6d826a813ffa5b9f9d014e92de42c520000000000000000000000000000000014e928d48c4ca7640a5f5c55c8ae756fb6f03bc1a8e4e907ba89865ce610fbd919a024e86969c52a4216d84b37673cb5c7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d5600000000000000000000000000000000041fc07f8759995530350fdb8712304083da882a5e4df8188cdad48a3df91a5f1bcc1b2a25fb3c9b59e2c935d579a9d1000000000000000000000000000000001925153fa12217d98007963237a665e56570cc666651c29729445adab3963d599a4eab996b192be1d49c7429d9f0cfe43563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe00000000000000000000000000000000006c45218eaa27435aff594c2601276950bb99fb3c1756dbec76e609d163b2593933b5ecd5fd8544d4bd2d145821831c000000000000000000000000000000000a43ab2ea73a8e1131e184fbe9004aaea198a3dab575d3516b422c275f20c7a6e5d41bca0aa3dfe7ec761dca0ba6687d833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b5000000000000000000000000000000000d17ec8ed30bbca5766def9fa375219503bf2f7322d2cc36a38fcc8471fd9d11d2a30ef004e39cac4d1ed2d33a66f7d200000000000000000000000000000000108e6c9ef3a5a41662fa16488243af3419e2d8e78c0311446186c96f20d9c15a60b5470eb95e0e58143a3c71a7565b05d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000017f498e192905962fdaf41120027d49267523bee9de8e412161cec69c62d2586752d1da3d15e89446b5941a2f321beb60000000000000000000000000000000015e9e4eb30296ca3355ba9c5eee343fe7edcbf5bd110ca5be12f55191d0f07b563881f52e65588a8f4b3e03dfce6566e3ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0",
+    "Expected": "0000000000000000000000000000000008b5f4f55def15b4590e978384fa0aa87e088916de20ff0fbd688ab3a13138f32d1e89cddc72acdf69fd86aaed6cbc4200000000000000000000000000000000022a02016f38156fcff83fceed881f96fe14e5d3410b4fc50e607d8a23ca986351ce48d6af970590f68aa2ad7181c9e8",
+    "Name": "matter_g1_multiexp_21",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001155a7d2cf81ee4f8d65c835ef422075a9453bb85b3566ec0545c1198b93749beffffbad14ededaa5bc6443736f77bb800000000000000000000000000000000073e4df0ea06345dba9fe772710ab71153e57152c74bd05d8cd4229c5ba1301f7e654f3fbb2a45526f1bc3b09c72366f16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f51000000000000000000000000000000001238e5a46f24e0f00d2b45bfad87f96140ce10d774f4a17c3df224b58693afa7cd0655e5ab202998f4f8b4b5e22cb82d0000000000000000000000000000000012628d85d982086640b09f046c5bf07b1cf718b5b4b20bd99d64382bbd8bd0112230609d78ecdc742cf1ebd24f1750ef8c02014d5392d30863a12102d1c9315839b5611dccfdb489207f918662513850000000000000000000000000000000001363b85a95432193800bdf353de1a5764cc2333b0369ca7dd539f230bffe81dce11288a289e0842f2db62a89e6f6af1a0000000000000000000000000000000003dc043b958167a900cbca116b097724e64d49897f8fb6a31df99e100be837e873328f5113a28c9fb510017d28d90d30d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea24900000000000000000000000000000000175aef023d9375ae90e9f562f88e0a4affdd399c3755c1b22494445d4e7d96899aa4d5f77ab9392051de4cb7e400ca830000000000000000000000000000000018e3eab56eae429c09f9eed67492181279704d947cff0f1c9a4919dff5e6fe07fedcaf5dae854dba6719194f9fccde1704753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000018d7001b1d4a67d22399c5f9b3262183a47b6fc81786f8f7b78e80fdafb3c0c175756e602c92855e8ff9d99d4116e3a40000000000000000000000000000000018451928599da4a14442910a5bf125d97f0b67af4194797b3f54ecc9ef0be840a1e0ede13e1415391f57044d71fae2efd1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000013ca19bea2e93c748cd2adf682a123416823a2473148e59d87da33cabba8e0ff2516e5b2bc9a8fcea9dc4240b20133ad000000000000000000000000000000000433fa5475709a7b70044f88a5949064e32014f1d64826abbf60789380db6d5ccfa750a868d9902e4646bae766e241acab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da000000000000000000000000000000001236e6ebf0b704a18f85281b09a9552e8a478c66e59c9f5d53eb6ff1f606fd667a6f0bfe239970892c9c295a378fe389000000000000000000000000000000000cc5c1039850f3333981b1cd6457a466dde93e2355c2052cc325e18604f59cb22588b6d892685fd7843938fc1b5b8d8a1d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a00000000000000000000000000000000003f86a5cabfe7792de25b9d8c58a283c5cef56e23dbf713851b42fc0d66481ce1946d1c632e38b9de1a55ffa0bd7f5a000000000000000000000000000000000f548b05782ebe160d487c622f8378786712cb5b68545ede95b34b08698f600e02e918fa2253a8be2c1b773cc74c41042cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d40000000000000000000000000000000015a4bfb53e57dcf53483fca1b4dad7f788e48fedf8bbd7ac40b1707c35a57011a0c7f77ce6626821221e59d8185b9ca40000000000000000000000000000000005618adc16eb9771bfe731dea180e7e2b3b0c9537806349e653a586dea4633aaff7fa7e7ff165fa16ae0013c9672a783214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e000000000000000000000000000000000039895bd3ef87c094c9cb1ec77229d615e76dbf0f3bbd399948a70714d6835b570e54f46f94197657dc94d36c4a49093000000000000000000000000000000000f1c6f8b06ea4378234e99d16fcc439a64cad45a7f8ec567755febdeeeaea4f4b133af18a4c00b3778090c5857739b66c1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e20000000000000000000000000000000003007275e93f828b96d060e775f2b92d191d6da44b1441bd0aaeccc5abcfc7d2b5e9cfaf7b8497016ec992b13455af2c0000000000000000000000000000000015c1320efcddd0709a12a75049633dd871747e51f099e40908542a3e426d7a29b6633f5e69a4c0b5c32ad0269a969bbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f7400000000000000000000000000000000068dbddbfea897bc2b20b6f967aeafb0ef759082f55a180b3eda87174d0e036761f1be1c682d1a4c33f5113a6ff4e2240000000000000000000000000000000004ad9da407bd80ef365df2eb763ee35ae06074dae0eec7e2a36e57df4b3e5ac333e373cc60c1986543c0c23f3124253561ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a0000000000000000000000000000000007f506a54adb1f763d55278419d4c18ca581b28ee369f33b848be495dbcce72c76533b809d70e26dda71316cfc3a1c73000000000000000000000000000000000a6c574799ba920ac58d6cea6d0f8ae249ef5310609904965bf86fbf88269530badbeededfcaa03892f1ad6b76818ec4681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da000000000000000000000000000000001424ab1e7a30035c4ee7d5bdcd8ef87a0aac284a36259742b68a5997e7dd3f2e5065e2238f2e29a23ac5ae9bce3bedc1000000000000000000000000000000001530257b63872851431a0bf5397dff45d6c201da58d7b779318beb70a5ee2a93142e4c5c43c3d65ddc65fe2df1af18906f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000bdc58489ffec3668363be0a3e45ca2115bd5cd1745f86f1842ab82ae31b08a1f285e88dd4e0c7b94778f42d495b1f9c0000000000000000000000000000000006f4d2a07ebc588a8f9993ec6048092b6dad82c25275c922b2842253a8fe24e191cad4fab51621198147c6d1bfabeb0ba8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe6000000000000000000000000000000000096e94b43a1dae483b49c1a616c010c25b660ec3566fb7d9c295d3b43c60ba4967b3f0abcc0634de5cf3fba14169fea00000000000000000000000000000000026146a58d55ba4cef1cfbc1db6efd46400b78f508ecc0b2eede8834eeb741b68ade43ef2300fdfae18c02b86e3386768823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b",
+    "Expected": "00000000000000000000000000000000143fd63e2576a606ec59d017e6582711718a542dd0a4c84566fa4312b2d2bbb2d71c1e0ab8e4e214ef2376706a20e3130000000000000000000000000000000001e97699fd2e0badc3a97f6cc55bcf729142aaa13c51f9b155e5904382ed0d94fbe1d2553d602a71ac7ff39189848a52",
+    "Name": "matter_g1_multiexp_22",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000023a2a3e6e1c1cc57b2295c664ac26abd0f5bbecc0ed8e9850f90b04484c0cf048a76477ddde84e90cc452429e28b78e00000000000000000000000000000000194aa1d8332fd8120ed518f27fd827e3c955c2cbb2cae8d5e677f55963565dfdd232c83a38826621e8e66565f8e200b39f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c540700000000000000000000000000000000124adb0352af8f18a631cb0078ec7daf00c2186e04d3ee47882d557b0e9e7fda0e0d258393ded20288789085583a97dd00000000000000000000000000000000053f94d0889a5122b6dfb1da2d7f13a836b9be039f127a011991c360c941e5dab8cb3c7ff3d7e128e52dfeb776aeedafc8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000009dee1a168c00632903b93fcf330b28ec7dcb8d6fba65f369237ef873ecaddd60a2d1af6e5b087f07a103f096aeb5e600000000000000000000000000000000006f90048b72dc28cf4cb40585925e62275d44df95fcbf1206e2bc762a455dea5fc6b830420d49b2415d259f8d5ed3ab7e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c000000000000000000000000000000001750fc7241cee9d71d95f0023dbc4b1f41ce794e9e7822a29a84c93b9374ccf0f11f931795fb824bb5c9fdb4f9e7bd9c000000000000000000000000000000000a0e6e6c76088200a345531f589ed883203e35c8ad8413575bf961b1e8d6716829f632e72fe90947dfa46745c9ffdefdf7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe2200000000000000000000000000000000141d878adfaa6a3982cd0de93b4d64ba840a07c026ca443d6d4c2b6c36cf882e109d80df63b1626c112f9a89809788080000000000000000000000000000000005a5888d22a2f654a58d9a03c68d59cde9ab5e5356b2288033ba58fe2dbacf533e59344bdf30eed07698261d6269fc70557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000afb5e198ea80997e7cace2d5b271e3907525b6383e9d45d8a7717317655a79bec3a48800149d6bbb11a838b1338079200000000000000000000000000000000060dee81112b7e0bde192c9d382b1eb695f3a1b0b9ef7ae33b1c5ef8ad9134c23b4f473103df15a97bd6de007b828fe63313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb75000000000000000000000000000000000a9327207fa94bdffaac0a8741955968ee2278dc0fd17e99c6f4717e8b0db2ce7915b1b028c81d48380cdef05ecd5a7e0000000000000000000000000000000006c24bd6aa5f9c41bd4551afaa6baf5bab1729b7012951fd0ddaf2c6dd03ddc2030d49dc92073540503718a44260fb028faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000195d0a7f5a351dff02a805fa08b2a793d9e0c74ae95fbf2f42bfefae8aeb0deccadeb9a2dbad7285c015ce14724879ba000000000000000000000000000000000e177a86f6aebee8bad62d77703d1d34a1b708e84216437c02e0694fe722414f2ef2577c1d39a45b4cfe6c73f411b1b413994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000bc7fbda14f76ed98e78eb84033b65f286527ef76ba56dae43a094a23067e10798065674daa14f912ee13dece4f36b17000000000000000000000000000000000f69104995530de05660aa048993c4e08576488deaa177520676c9cd53034ef101fa3911e40933975aa958efbb1b931f81eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000001c3bfedaa15025440c6cd32115555fbbec439a9a2fbf706ef21e06a534af3f43baf46897158e211ea8821a5e32f932e000000000000000000000000000000000fe08cc9ff0fc601e5609ca139ae0ebe58faf8d2e2f4f3d0a1231382a15ebdc8f67271b556cc24fc5408daf3c7f74f875bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d00000000000000000000000000000000032c376b26551a064cace577ef53077cde48c284af5633152c89ee109e880b511c0b90db1b30d6d9700037489f6984af00000000000000000000000000000000059c013cde62f10f39175335b76adc5cf7330ffa75d770d908ac7e0fba6faa7b9453e8d0215f0589af872b2e648ec1d0a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d20000000000000000000000000000000009cb943167f21d9399b184f0bc0c2aca58dcf8e702614ffaf5407644ffa9eda85efa12dd23e756c5ccb5bbb25abe57e9000000000000000000000000000000000d4f59115321181962452c6f3c1e086cbfbc155f2c3019e51e73fd193e9b11ec891b2dfbd95198b318e4513c62cd51bc2c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b2000000000000000000000000000000000637e1dae04d31282c2278e087eac9ba8506d3c1349c6b98485cf32805bcad002e37d55667f1cc8e5e11f35b4d228cba000000000000000000000000000000000778c3a40e79d6288d3a93580c8f8bef7591acfac2c734018d61aea5dac020360ad4c69b4422f7320b87ff22e30d9a6a5e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf04227500000000000000000000000000000000069a54448ac1c9ee754fc28c9b671e84a67e884492f8e84e09e49cbcbcaf07fffed42820b1de61cdd0bf6314a2f4a1e20000000000000000000000000000000008f5512a1a70d3a61ee7fd6750813a29c47410b7ddd62db0426b3caf9cd7c31029638499c2e27e5922810cb9bb130723606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000078356cf80bc64c0e03da2198da5971b01341024a620ef4a455291b7a694ac3d91fe6f19299d725cdf7506e0485485da0000000000000000000000000000000015af5f875422c1e3ec6bfc5e57ed793f368799c2e068669656294be0de25eb772aebbc61358b410fa9ef79c72f309c84c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b8510000000000000000000000000000000019699fb3c6af71eae16b8ee123870888d646ac71dd31d0bb3ca365f728a6687540851c8539dee5c34f16871ca244ac6b000000000000000000000000000000000e68a278bee81ea53d4a52e84c8f534a0fb8c065bbcad9f3727917402746b4d1f611ba5064f0c3cea6f4d7fe84948dfd47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455",
+    "Expected": "0000000000000000000000000000000009057b093eae9c7ab2455b447a681857d588819c94b1cdffc0e315987b095edba1ca727043667749c56591429f9173b900000000000000000000000000000000157bac2835d2f972fd1269039a7b6159b7a81a1bf4327cfbd3be8b7c779631e8beea634ffefd9771c910c612d6925384",
+    "Name": "matter_g1_multiexp_23",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015b6687a34084292423eb600bacc585b4e686251892b16a52d0783b1490a82f68f4eba5eefd36d147c4ec442d2eddf8b00000000000000000000000000000000151f59108d7383351b426ba8bebcf2a04976550aa2d10d5f89d5ed7c3bbd3473ebfa29c1706560866c8596f7549085cc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a00000000000000000000000000000000064e3333f828b1e54d201c043bb0f327d8c9af2cb96fbf587dcfbd55547d76784de0981a0ac86b65f4b8e45b19abc66f00000000000000000000000000000000172b76a242fb2bd9070ad26497a5c190d08472d3fbffa83dafc53d2bf612bf805691bc8f850da8c230ca0b8bd4fab818a59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000158e81d92b789696efcdbd6e3e7c16386d6e5259a247991118dfbb3674643fb97a82fe404832cdbcbb58156c9548e59000000000000000000000000000000000fa0d18e57d64db246ee52980218c3eda5fb7b1029e1c76c9894548df52f69725fb7ff090417ae05957a652029d0a37019ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c500000000000000000000000000000000027a07cd6b7cf0219b57110edf07d758ea40b1cca42270b341b2bc33c78fb9cf52acc31676811032d3f618898a0d13330000000000000000000000000000000000e1212938244e425860646cd0258b65556360e832d4f2262984f4e307023896714731a2db10004e5509a1dc25f49ab7b8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc1000000000000000000000000000000000bd589682a8510471ab1be8c348ed0d242548f0a5b85ee9eaab5af164367be21684ce2329a64a6afdc6a30ecc5bbb51b0000000000000000000000000000000008c8af9dd0e06a08f2da0ab7cdfc20100b94c04c7e6773a0351bc0e0ea503a69e5f25f250f0bbc5c7685795b279ae151edf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d0000000000000000000000000000000015865d51ca8131cd5d2b0cb11c2f06e39b7e167ddf504d5772d478d48463668c4f7dabed00cbaca414b6ba96224c95cc00000000000000000000000000000000042fee2fb44ab45d310ab00896170a638940edb2df9a0f06c077bd00d203966d49694c82cd59c378445ae0577471221c0dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd0000000000000000000000000000000017257c7d5c733cb6e9ea1bc93bda4f36b98375147a119c376996beb6f0bd030c997ac52b1556d01152991738dc640788000000000000000000000000000000001155b29f473d9abd15514a0ae1cbd0b6a4ef394aa65f4fadfd3e9551c1d8420fac28acd5337fc5d114c092bd45e9e30d109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d370000000000000000000000000000000009802bef3feb5688df77c86c74214451e4613d0260fdc5ed6e763226d3eea8a583c7dcf29eaf4c0bf16c907ceda76db9000000000000000000000000000000001447b1f7ac05cf8dce7e81de516d7303b310316f49ed5ef3f40f03db17926ff5f6656d859367805c889e07919224a6436326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a0000000000000000000000000000000000057b59f849f0237ad511a75b66a77e79ae062025e5019eb71b7b7ad94a96c2905e25afe4357506b2472f99bc71a8ca000000000000000000000000000000000f10b6ad9fdb4f346c5b4a499722e377c7649a800bb95306dd7e2ab7542e59455ea5541f2d75e7cfb1da5dd03bf037a1e005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d000000000000000000000000000000000e0725ff4149698aa757e794590ce446a1589d9a574587575ef64d6a3c935fbd78fb60c7c840d7ef42eee8d72a5ce341000000000000000000000000000000000f0478a776be354e29bf8bd2710a8529cd01da31853d04ea722225bde560f2d9da302ce4f2634c9385ffeae379324b743917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000003103b0553facf8f3cd18967a758b73111a4a9987b0ceca3a20d6657a7e365be3925f63bd09990e33e1162bbffb63278000000000000000000000000000000000998a34ba445dbefe6023e737f3e35cc6416289185a26611301721db3a24f80dd784b001a2f2a745ffc3d0da5a9e6204f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000041e13fb55bc9ed069c6d625ee08122efb0212f525b319b88197450ed1a60fc7283f61083ff263e4df10499b689498670000000000000000000000000000000010d931f006adaf737afd1ed2d1a631f519e6d1e9e22166c24830e92e3571e9f138ba901f5ac2f03192c9701067e7906b3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb442000000000000000000000000000000000bc0d401197ce816b692c5ac3ea539cc9658de56e48b4c3ac78631f3c529d4fa2a656f66098a702b4307fc56e147f962000000000000000000000000000000000d89fa2bbf3ad409a9ee7b7097662113b94fab95c98bd47a70fc2707a6aff23bf39944aad5509aba34930d7343762f6e5ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e9918800000000000000000000000000000000103cc442deeb800c14c9b3071c13d354d8c36d187e580073d150f4936ff178817dce67ee276d1633e003e66985c038cd00000000000000000000000000000000188b34fb0a4fc2408d8c70eab6df4c6c42d92ac5e43827044db526d4208acad4561c1310115448bc00feb9ee7cfdc40a877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000145220a2f8fc61b2973d219042580a0edfcbd73a6bb6feea3655dd33bde8a25e0fb841a3b038049e554315100e6724c50000000000000000000000000000000018bf41cf4ce164819a8b00e630401f0332f5caa08b03bda27c205e8fcc5ea7a3374b591a4adc581f492cb07445c8995f145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f607000000000000000000000000000000001416a39ffccdb10f65e5f06c8d7af68fbe894a0778e7270ab167ae2a5e917fb0eef1ef1b9fd45c991a45dc92a223ceaa000000000000000000000000000000000755c58a0692f8ff860430c5f75fa35366391f7e5313936e04230a1fcf1142c81b01e68fb3c888effddc0a498f264da9de4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88",
+    "Expected": "000000000000000000000000000000000d73a7edcbb7163795dbb5a5b4daca733e07f6498d336a5dea1a61c9edee346f74676afe0d6d39c39caa1fa7660ab311000000000000000000000000000000000f3d573970077a17967ecc0fc5e2e7dd4b6ce910f1891f444e36761e2ee3a72fce399993405761de29f9563f74d8b1c7",
+    "Name": "matter_g1_multiexp_24",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008c2f928feb8b65e521b7218b029a4f54022a28a18845614b3b2de93035228c282c73ce172997e6af93a402e35158ce3000000000000000000000000000000000ca2dd2c06221058a4a7a06438f035ddbd96f6b39fe80c0029f41246a2c8a4410961555e43d9b3d5d87dceb8d0be1ef42576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e00448390000000000000000000000000000000010d919a48f588429918f1b2f05ba6e897c45b12d905615e045c1969ee8a7d9ae262551f546b7de764266d3ab656c3137000000000000000000000000000000000a40d6f247315e0440b0b8195fe5f7a7dfdb2e1be9e593f7933691fd22789ae94bcb6bfebf3b84afaef7cae9fd539b5379f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000005eaa990ca9d57885e6ee3eee10b6e2dde6e1652a743c62ebce4871ebd2d3c8e4915418aea4f4285ba375ad1923b70a200000000000000000000000000000000159919c720eefd062ba8d72fd3befd953e1272695471315ff500830c9b5b60ce5f94bd6e966828d69f7f268bb423dfd7300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f65715000000000000000000000000000000000b7244995b7819857f716288dc59eee9ba5ac7bfe010937ea0b67ee71388a3792e5b7feb6890a436db4f1b26df18b38c0000000000000000000000000000000009a0b73360bc0ca3b632c0116f21ffdaecf37e4d6c904c98d6225a08d7caadf5024ad6b457cf31b924118ea147ff10fb0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000005c2bd45375084cbc4bfebf41709a87c2a8d52256a5e4bc162501bc119394186fd624c5d3d6749708be2811da2c84c15000000000000000000000000000000001626cfa6e87e41c2f0960d6d2b8e303ff8de00c78d1e788f32cdf548a5ca00db1f3a3c082f051b4bca93788243d9b0973685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae8206000000000000000000000000000000000f736c8cab0794e3751a9e13027a8e4ded1308c23be3d75b373780eb69f130654121435c53b62a929cad39c605637ce10000000000000000000000000000000015b1edb73501789811fc09fe0156344a7a4eab1f04d1fabc24f36e2ddef7c2ccf9643699cfc654b7169d8e371c14e8c660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc2540000000000000000000000000000000018dbc414f9e1c66af803b0c228a3fe77c94c29239e529cee652099d80795c460a507538eea6c94e99b78779fc0f3f33400000000000000000000000000000000151bf39a8e3e85b9361a9472e95cafc3ae11f7d0b952714d2836b903910a8c701e0c3832b8c88592bb8507694d9109b5addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001241319f49e1bcc2d3f3eaca51d2e4c395241e2c5d8f32749a168e4af17570793fe086610432db1f93fcbbb95ced8b49000000000000000000000000000000000d90602dfcefc3860a78a8f51432a7608a7c483fcd86c0ee6a70f8ac723537825c14736240cbcf903c94d04e24e8ecc928416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae800000000000000000000000000000000024f26ba0c3295002418f7839b774cd305cecc3c2cfe20974343dafbfa6677c2fa6be5c546a1fe81458678c3548d8d6a000000000000000000000000000000000fc8ac2bf4585e8ac8454e3e424e858e1d67cb6b9a7181e26af803d8895717796f20abdfce0dfb390bbc0c7b16c70ffb78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000005f24bd878cf5832ebcf008835f12f9dfbc78b2f6e46ee384b419928aae0e754d86809d360b0afc01bd8f2f8d79a685d0000000000000000000000000000000004aafc9a20f52d1c78a17e7824062a1e7165362ff265dddd4c3458c7810a8e59104d36035c93284988eb708ba196d6a2871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce000000000000000000000000000000000375313e7ab999d174735b5290bf9ea333a62387996bf4df3dc33d9a5212ac0645789ef4153223d488aa2fbbcfe808f00000000000000000000000000000000014b792fb5bc39dbfe409356bd75b195d7023bf6f715a4102cf36ef05b52fb2284cc0739fe5ad628a760049c3624a3f2876ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000006ae2c85b2b267c86320c4cdc56b1a09e25f0f68dd208e898ac5b1c0645aca3dd8000eb544eb666f4256806123480800000000000000000000000000000000006670390bd47829d3c31cf2da8fdbbb64b92b47c78d3ab638727ea834ea6203e45a9a023060056c69c1fb567c35b671795ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c40000000000000000000000000000000011c78f1b6d0ecc5523dc089852d95dee641222c743dfd09ff2e56d008ce523762bbd9c7bec6c18e9885b7022131ad30b00000000000000000000000000000000066a1aa8af751eac5dbaf2d3ae285e0cc7a975c1787178f550a42e8ba89fa74a1b18f27716eb7ccc4f21b7957cffd8e806d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db000000000000000000000000000000000cbc0fe6b4956c0f7b9fdd36ea14a4d8284468c280605a31536636114759ece1339f06e050260bbf936b560586e7d12c000000000000000000000000000000001213bfe642bf78554d91820c362b73b7059cf20a0aefa5855f9e61a0490d165f6f61416e135473e2de54bf97cc14b8f6257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f0980000000000000000000000000000000007cac206b2d123cbe9375f5c913939b25886a51c857271a59cc2fae2e9d669af0ada833c72366f78be265ff9db049d0e0000000000000000000000000000000002db3f65b6fe7c6688f8d3741e448ac6ff322b8769277572f0198dd6ee8a99397aaeb9addd0892286a9ec6028bf9678863d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc3000000000000000000000000000000000a60331f8e8b26e97366c0e4cfea158e78ac72d63f219e1abbb670675bea008609f7154752438d9c7758b2a2e076da7b000000000000000000000000000000000d40d90f498a2855ba35f1c4bb3c5409b87062d7857bd97dd37d6e5fa53c94c78319c6b16bdcbf2610ba379d50d131e47a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c",
+    "Expected": "000000000000000000000000000000000ddd3c7964bf51207485b0575afb6430cf801bae388ff78a69b8173c27431e0593584f9e755b99a5b2ed3113b3fc0082000000000000000000000000000000001735fb40978d364be3521ada17c3ae74b2a738b412906fdf425bdf13ec09e5acdf29013b03fbabe889fa261302a7ca42",
+    "Name": "matter_g1_multiexp_25",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c52993730e412fec923e33f3da42adadb5d87290ac4448d7df9b401e28b3c7fe7f49c7b7e4bad5412c815931416303e000000000000000000000000000000000db71c91975e41b3f12e303bd8ad15f7c9836b146073946129ba3815bc3217b6116a2a03137608cdab8807d5834eb12026a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c0000000000000000000000000000000009657686875d82eaf4f93f3e710c467ced1348b60aa47658992771195660c4b96798cfec584ace3bc64040666de71f8f000000000000000000000000000000001375f7e985d987df508321c3d0aa7e7a06cdb78117248e19c3344dc443da319f49c00ff605c057d1ecd942e8b04a5e4ef1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b6000000000000000000000000000000000da69e098b5e2c8be2ba699f20fa38cd27b9c78025e071ecb2d9fba3bc84b1e673eed79f1887fcad9bfd5b0516236a1f0000000000000000000000000000000016c4ca4d9f15716b7efe6f9e61aaad880423243b2d5ffc96804fc70f29b633dc16474f7194b5e3ca12ab5a1627da580f97fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e0000000000000000000000000000000005370f5c60fb3bc36ee208e8c185613390748452cf6191bfad06c9bcb52501873bff63892066e0afcb01a0204cbc951b0000000000000000000000000000000003c7a2a97cf7be433864541082bd04467bbb42b2ab708866c8520a6582cce5225af13acb887b6b6a8d627c90e43f6e7b640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d67000000000000000000000000000000001820666eb1abd6144df2f21f2d46096410274e346ba862aca0e62d293fc64a6fd213dca4ddc1a4e414796f59db4d6104000000000000000000000000000000000a2521c021f2fb7beb76a2ff4c7ce96cf1d05823ad8edd9b2021eb39c08e0c7caff505ea76bcff8f6afb6e8c2e81d2f68bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af4000000000000000000000000000000000e756ad1ccf0404e110a778f66ade3d10464bf8902f646f7d7ff38d15ef890bbc6d61d48122ba6edb799630a62ae084a0000000000000000000000000000000005b322f44f07d3db292c43f9ddf9ac9e44e8d16c07537bf563c98e02c2705eefc1013e627567ac2a03698268707cd84e8da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000000420b819a63b7ff7ce541661c5fa8cb107cf00ae678981b3fc1b568174ae3864a8241f1e9b656cadeeba232156e66feb00000000000000000000000000000000136fe878b886bc14fed061cd8ff1fa2d85f05bab922bf18a1f09b55c331e7cc9bf0f9860e9112c2f6242b6d1124851dbd6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000b73d3549a6b2f76741aa39ee9bc2bda8cd55759bbedaa9ecc5802310b054b01670dc803938aaea547389d7b0ceda469000000000000000000000000000000000227fc49bdf53bc4f916714ea9789b526aa53efa1eb032c4030519608c62434443847cac82a13e2dd2eb48f73473d8e1e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b100000000000000000000000000000000167cdb86301937bff18287eb0b00f5224e674953d70258065e5e8370016cac8194ec8c2f44330adaea44426aaefac7d70000000000000000000000000000000007e9128bb015f01aa725796d7b7851f9c2819a8a578bc7d3af02f7328c922c26335ae9f87756f52409c446852bc710ada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000018bd46832b101d12f95b21332b7259719c1f94c056118d877324656d285f73a4fe2cf637cc62a45647db92ba9d6c7d18000000000000000000000000000000000fe58fe2c19ee903d82da6da8713863423f10edb954606b6c56326eb8eea6c66cab63b0c816479f8107612391072c634b929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf00000000000000000000000000000000012db7fda36505d19a2c6ba5072044154f444eaaf3e12cce81ea74f28e691e4b7a730095667a71308db5e8322e80fc66a000000000000000000000000000000000fd0f22b05bf82688ac72e9ede526bf806695ff430ff3c750c2946d58ef90c778e4c5693d152e39fb1837bb10cf5f3be36999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f00000000000000000000000000000000116ba7b6faedd465fd4d1e5f42ae80c133a1d158614894ba663f87137f6108ae03b8e80bf32852ccce78b776dc224c760000000000000000000000000000000004c3702ff7fd9c74169ea76c00efb7b475d45efb12e1b5b700d47a970ed9f95f46e4c0ac66cd12fe79d62898b24b54a0fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000016254d89b0e2a8315253434d5444000d9b56b8f43d3c20d17fd26da4c8e7432d6e463b71a5b2a1a7f559a908d73abf6a000000000000000000000000000000000170c490fe3962fbfaaea1707bd28ecdd46ba29b5d8a0a35baf7fea4eaa47694e680e47e8a9f07d25078274074e232dcc36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000007a7fd283d64efef7094fbd6162da2fd56399765b559674c18d1cf6df51036007ad6c9af62bee534388ea093d3cdc3c90000000000000000000000000000000012fcf920eeec2c1728f3e620fdab1f8a0b99c6219f44b0fd19d0f7f4a15d1636fce7b4701f9c3963cff9b030c3759fb20f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e40000000000000000000000000000000009bcf0b2d49ce38914ea877832eaa3f1034cec429cd9fe0d06ef36691ac8ac6b69a712792e31afc700872d08c2e0fa48000000000000000000000000000000000f5fd9d2d4710d1cc6c13c88ae602f584a7b671df91cd544697070eff3342d80d750e15e09358125d15fbf8a1ae8df93c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba00000000000000000000000000000000053fc59a0b84028cbb3a97dc3124927d6a0eab1c58d4c6d143462bf73c0c847712bf22557a1181750146fe63e9c9668b000000000000000000000000000000000c1fa8c1539ae702bc9441085a89790a5dcac9b18925cdb1e21b95c9f7286795e8f36e7a8b4c3f4dcfa12454624911675398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc",
+    "Expected": "00000000000000000000000000000000085e4232f0daeddb9e1ec8731855cf855d7dbc05d4b82d10b77a53306ee7a38ebf45bdeef1981325a61ecd754944c84d00000000000000000000000000000000061e32056ac411c3917684356a6ab3c7068f55d30ebcf8cfe446c68267923e4fb98596aded9740dc7944847a2e617fea",
+    "Name": "matter_g1_multiexp_26",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000070bcf49d6d066afa9b008fa22fd52f63b68a648bfbb5cb3eefd6feae666f3fd0b9a8447f427d5a9db52ba49854db7cc000000000000000000000000000000000947d708a02cd0a18342bc04639e8d126fc4c97acb497aa507e1c4c3912b04bdca886b75b9b9e1c5ca745acd090433119f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000001023942a16150e6497289627dbb0205a7c34afc704232ef214a6609125e90260d68b7c60600cd6f4859ddcad46c015580000000000000000000000000000000002da96265b7460ea6a8d51122bbd2442c6784d4f5bcf6d8b0b6eee6ec82e4d03c9265887f88c106792795837c02ed76e4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000d5be6f99bb9a2379d1e542ece048164fa5d14e0c6c459180717b3da46e8446e9def576635ac1124e1390196fe97f39e000000000000000000000000000000001482d8339b402e3bffe61aaa298c8bae4286f1fbfc877a66e21cfe239bbee383d701d95a6c2b8193d67df5a551bb7aba7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000001969dbab76e6a158506b9dd38c647d4a670a21458a9552d903ac686855fe021a7dcabc91e712aa252de369c9234fdb59000000000000000000000000000000000b60179a6fa6146aa6e57b097f20944c123916c6722fd7e606aa34b8da579f6c126dcbb251da7917076a83e2e4b02d32a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000e5fa0feaca8dca2a6b4a42e4a291383ec867f12b85593360f8caec45d31109373dc16d985a4702e3b5684774699e6b5000000000000000000000000000000000ae96f4a4ac0d0a6fe6aabcf902eb0765aee9ac81ad09e7e097d649b0c0165de6ad7e5ffd4ae7d8a272034f28c85ad6f97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d00000000000000000000000000000000006391a93eb14641ff145f690c626ca412af266d50b903f7465d9a9b678025a35a68bf1962bb5ffe76ea07989a7d807920000000000000000000000000000000001a90846cba7c708bf8b4bcdb3415e17e80ffc9b48820d3307362327b29eca0d1bb7fcac9c09d09fa309829679080b36efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000000c9cc9547fd49cb22986f7a1dc1da89b05f5e7c0d3cf2179f22002df9fa2c586bb3f1496c0c60f8ba36b631fe74c8fcf0000000000000000000000000000000014f8e4e8c5a12b61caf4325d1e4a8505409d722e4eb16d51be5f01f863e5dc1ca68df1b83f546d22fc116f1654a3b30e9a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d45200000000000000000000000000000000123292cef01012c3723b4713a345ea7648bdd8b8edaf76f149f1afb993f196f57b3315d86a374fb78a34486ea10e0c26000000000000000000000000000000000ee2389f669431df6697d79ba16d3e4d9bb4264c9ac146a772de6a9a8ac94760cdde7f613a4ae6592509b04b1f8233cce9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000001284787a11e0164bb197f69702d0d746975bd96a3b9221841c7193676861e97e11077b74e69f744c521ddb40689f9685000000000000000000000000000000000eb6c4c25fa1322f7c829691d938f87ba6bcce850404bab57cc3be8c3d0abcf123be8922af9967b83789fe64e2cb35f40a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d400000000000000000000000000000000069f8970964efa22facc786291d6ffe860929121595fa713f4a12f9e99d8508d7d20f7d19c51514538d1ce89d2adb78500000000000000000000000000000000122bc9405ccae4e409c1aa22b36db314a19ef6e67a572f7ea67c247085205302ad12ef7f83d3616279892ccd3c456980cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb0000000000000000000000000000000017b92fbdb00429846fb30633a2c3f383d32d0bd433d5a46e27d3c7bd6880948f89bf70b3f1639a18d308ba80b7209df00000000000000000000000000000000012374e8e7c1fdaa4ad4a2d8607afb62ce939bed23ea42a51fbac995e2c3026c2daaa338be160dbec2602a0fdaa1e9897e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac576800000000000000000000000000000000004850419631e3de2617bb6b51ef19bf14dcc9f4c7b24ae817cb239342081947f1799080cafaf51ed687b9dabb2f3581000000000000000000000000000000001108a0463d38d617d0a778bf9478ab44050ec290e442ac41e23b526089ab5aabd5819a8f08f903343e93177ce4042c82f26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac57946000000000000000000000000000000000c9ef168fadf7a056e6cceef0430f65a57b0f2c3372a5d3c533871c91cf81c40d3459cfdb5f1f66f53b2d8d50124ed15000000000000000000000000000000000483ebcdc219c4c361735aa0ea96c00c4908b9db62ed8cb565d25a7fa664829bcacc37a5608a3c3ea3a42ecf74708ee9ccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f94000000000000000000000000000000000f7232dfd8367af413dd078f9d5f47b8c76c38b3ccc4110fd59764265e6a368fd4609b52c21f8e6db2c73908d4ac0b3d0000000000000000000000000000000018433b00ede4de21cd6a1c78c5d280af98b814f0a60c625f0a8f355be43d8d99346282b6d9911c4d4074fe827b55d726fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb3400000000000000000000000000000000015b0ef81908ae275b2d5c3cbc563b8424ee0be0e1f2fb77f67749a79b7730d33028a136d133825da14448b05bda1409d000000000000000000000000000000000461c575bf65c6c5754a214c2e72d6d24df2cc228ae1c9f99d75eebf9cf48f20945a6483185337aa7c0096543dc0a527d94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000006e7385d061bafef2c731ffedd01758f153e2635c7f2bc42ea2efe29931697a1c50e4a13ac420572afc523b7316190cf0000000000000000000000000000000018ad5dac1577c9cc1e9ed30ab277dd381a6babc17e86538570abac44573a8c2439d97cbc370cd2b5d2c6509a18dbc96f122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2",
+    "Expected": "00000000000000000000000000000000020da97236c2405d3f1bf4e937d8285014a190bbc59a17b7163a292a2b825f086db5d371776988d1aa2d7529a64d2a4e0000000000000000000000000000000016cf6d7b831a81d0c487bfc3380a1dc8a1bdada61426a457993f7d6c9c8fee9ee4959324bf7a2425b070aeace3cdaff6",
+    "Name": "matter_g1_multiexp_27",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017df783852d1f1f9c6dcf1975ed2dfacf3dc0cf942cbd7243a0cea7907ddb289f378ae59b30661d06d0702792ea9e9e2000000000000000000000000000000001717bc4192402e587400b4e7243db7e79fead2f878079c3af998b3a683a0539aad5d6c1e5da6e0a00ffbd10a2d891ff2b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb0000000000000000000000000000000018c9cc123fd18d50a7c878b31622a3727864fa61d784285b990fd116567c69dbc7ed872866db2166c7af1812157af9040000000000000000000000000000000000f38e55466a6d1cc2512c1282f74f5c0c19777365819e48606c0a86d2c6aab8938475d15a74f24db868802fe935f6107e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d310000000000000000000000000000000000816580c761a2f54c386cf60b1417d51a310bb7569a50b475f8d45f13ed6c1f11640079b5d6119270d616e77a489069d000000000000000000000000000000000d9af7b25803b611351f00daa88464e49b277de8d8fe22284a9001a13ed63ff931937d27ee19ba4000ebc212fe03a0390a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000032fbde9d988ef200df573dc99b087a8ffbec95349256989774194dabea55d970ba303657837bdcdce3b59eb54669c86000000000000000000000000000000000d65e89d8df2a189761e04d35c9f4d3a5292d1dc0d083bc9a982a131b07df6250cc969a3534808959b583923bf02125cb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a000000000000000000000000000000000ed3f47ea234f8fdc16e97eec7f4521941c37acccdfc422fefc6df9c1127ed293998945fb1bdce89ea18b9ec2b6e5175000000000000000000000000000000000a066fb6f1d69b88495bcb0f0eeaad2a41d5c6764e2dcac2ddb4ac340cda72d7b51b7901c758df15ea16e4e46c7053298902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf20628000000000000000000000000000000000ca7faa768ce5ddb6d668436e2e1692893d07afdf7466c00bc8c963b80cf0d44f6eb9a2070a7bd889ef692a81f9d76d8000000000000000000000000000000000f86fb53e3f061cbe777c7aeb63402616c428216a0c65d5d5a13cce1dc31567a4051420d54b4fc93c6bf263601046712426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000011113946d8ed7e5e545ecd0ef30de293206f3ac50e6010fa7a1cb0371f47aab2d8775c51172c4dbacb05414e65fdae10000000000000000000000000000000000022a7b8af616e4076f625f8151d748f4f49e6dbe439ec695b854544f8a498c7e261c366a4c81be5b9cad85a4eb07c36de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad000000000000000000000000000000000d863702db9f9e43ea311fdd7e0d87495ed0bbbddaedd3333108704417521b3da4b8ff0bf904710b0200453ecb2948620000000000000000000000000000000016a520d1162c7070030fea7702420de2a6e0f255c28a89bbcaf663c0d6761d201f07d86adf5ea6589e27bf844abf85a57431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb6000000000000000000000000000000001357cc987a4ee7c7bc063ec8cbaecbea0ace4b80e3af01f74d23801d5d37326ab5732222f60ad864cdc8c5dfd3edb37f000000000000000000000000000000000094fbbc2936e1730a1abeb42e58818ffe6dd97bed27a1e4fc090388d943763b055301852222503a2d2a9dedf69b3da26745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e000000000000000000000000000000000672e9a4eb4e8be8efab0595bcb7a6fdf269db71dcb585c12f9d7c1a8414b6e11d91373959d47a4c64a8890766f68671000000000000000000000000000000000203f3804abe330bca60b7bf9925a626eeae79d58ce7c71658b2fceb8cc93da9d455b6d59bb58bdc23b58238d4f01948ed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000c4e95c27fd983c31fcacb578a688c2fe055516735b5f1ea1415c5cd29592e7720eb2f548071fa3ac642b70e339757dd00000000000000000000000000000000067ab19ad1c97a773164e812771aac69fd5d199e4f60eb28c7aa5f09dd9b3adea959ab4ad47683d27394714eab4a40d281633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000003da3fcadcafc5eff08a736e4cacb1d6617c3f0850ffe33ff1648f783a4467163d1ddda082ba0b54e678b171b1f79618000000000000000000000000000000000a273fbd5fe99df4f724fb20ae0fee994823d374979ec7ff23dbe148f6977145de9a1f20eda777cbfe0fa4cf8c2a8949c6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000015b74087be4a98f4c5cb442e4e893d4d92602b1ad36d0f038f232ce25b53e19816f44122e8f5c821b40a0cb36897fef0000000000000000000000000000000017e50b1e84c7e767171edbddc397653c35b34141bd69ca7123792d6f20532f6daa5ed18615bb364b72744f96d4a730be6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000131517851372c44894bf433d5162d0da394b87a9554e9d4f6174d5712dbf69f756c5da1534eed80f8596f906f36799a100000000000000000000000000000000130a4583c7529129831ad621cd1e04a8fcfeed67ea96db4932809ac140a089e6252bcd101f17d4653555b1bdd9ea3a9b5906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000002b8d72148ed7076656128040e7dec82ecfc2d5ed05050b27361a85d0fae6d90de6dc32dbeaebac039187e3883ab238d0000000000000000000000000000000004021fbb748bdffca854bfc5de8f69a9bec478181477d3c6e41a7da2fab3100f7e2737ce958c046d6447370d47e373ad94ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000015d1c5bda34c6fafa52dd3801d94a04c53a3acbe43cdd128de3a346739df5afc6dba58d63c7cc09d18589c41d9679cff0000000000000000000000000000000014367ab7f03febf90be2279a87890527935725880ae3d418ec055004f312fa0c42c8f6fbc9c319117f6ce600d86910f16feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438",
+    "Expected": "00000000000000000000000000000000130db02ba2d24a3d70439503b089e6da4cde7b5c51b1d69774b38ae0f265aeb8996e50ef077ec12199ffa3d000adbf38000000000000000000000000000000000de25ad8eb2142051fb2c97175cb4cb2984ddcab65dcfacb76cfe60f6a47083a22dac4f6e06e357a194249b7363210be",
+    "Name": "matter_g1_multiexp_28",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016785db77cadde48a4ed0d2f8aa9f91bed9387a4766c3566217afec80b180461c8e1017297888e9c5896e509a26137b000000000000000000000000000000000025b26ffb3fa42b1a9e974eb23ada4b9329d670e38970e7abc937463e522887d777934895be0cfbf13d213b3b737a5f6cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000d90bd38049f2a8de869d8a748c9ff3120542f38fca6e8d5fbbff86baaabf0f19dbf449cf23c043dfea322d99837f7110000000000000000000000000000000000ede89c8bb8299726ec685765f10167c5b844e427d3c15da6ec2c1d97de174819d52caa96d5cc938e93dd09bbd1e0d813a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b0000000000000000000000000000000019e1e2706e878e60bf6fada47a4d4028750cb27749bcf8fff531ec75d1ff9b3a1b5e0bf19e2758899c3d8bc96a18a0540000000000000000000000000000000004b5f00109eb4832ffc9108740f0728ac059c613654a771beaaa028fef06b6cadb9dd182cc573d7ada1dcaf307a8bca4ace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b77400000000000000000000000000000000013844937de287b98db2b9631d8e36bc36ded8bbb3ebb2005ea5ab39a4844fa354b62feb7433b8fd3e72aa89ac8e4ff50000000000000000000000000000000005603183a5fb09ffcf6faabcb5042328496f8b0f83e8fe9031f9dddfefef43ee4525d1afe859177d4b9f966599005bdb8d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f5360000000000000000000000000000000008ad6b2bb88897a2e53d4fb9910b6244faaa045ef32a2fd223adbe6e0b1a5c1683dca69c0e9515dccf7e4589f1e69bff0000000000000000000000000000000013564245d53366d8468b51f88becc288b695879a70c3c753933092904b9fa5e64e39be30edf1f5e9de7eb29c4b3cdfebabeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000003b587bba9173011da620ff930befccb7b43093052636d6632fb6e9b59b8d127ffa0b7829b59873ae347eccf0e6c86c5000000000000000000000000000000000363be6dee6dd9a1271b24ff84c6557adc62738805b31714c9f7208c320aff220c02b222b96c62af96f1eb42b5299a63adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000009b403c5fe094f6ec4e4b9b7d098c3ca6fcd838e46a885506ebe8cb3d8b29849a8f3d8f9550f6d33315e69f6c1a6654a000000000000000000000000000000000714a7aee8bd6d754b9bf0292be50836e13ae886f7952c61afb1b45a02a2c378d6d22eb3eb882206a3141e43658a068c06e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f00000000000000000000000000000000115f7928ee8b8e47af2739dd70bbccbbd8c4c4f9b92868b981e407887b448745514b67164df86126a7aa53af9ea7a0ab000000000000000000000000000000000772b21e2bdc688f0b883a2ec5accd48a13ff3917d1c5ca8896faffca7e4097021ae3c348bfc2e8174db93e079979967b3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c60000000000000000000000000000000011bbf90f59d646617a6d074f5938f64232550e189c6d8105bcb67a3607e13b4668701f64933de602e5daf7b0f4f50c8300000000000000000000000000000000153ff6cb6a6dc6b6ec086e2ea8122d23e2c6abb8d59c7535fcbdfa721ba505d7e9113cfac69e1d81611c72e872071bdd29b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000001897421ca9a740a1f03d67ed31b3922d7f6067287b4addef6689303571b49bae574c343e967dc0f270aa4f91381609520000000000000000000000000000000007ab14771a4e256ec4009aa03af8caedbec4b3ab21d6499041ec58afe17175a656a7600c4bdac42c92efc9d2d21b48bb6b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e0000000000000000000000000000000005e4061b14fa76d4c02d77adc7e07881dbcb023dca9dbfd1301cb3252410d54db87816a6403d18c2ea8c18027674133600000000000000000000000000000000079d3ca06d0878a569a3984858cac6daf967bacb3fd540187e47dc2c0790d6cfffd1ae1f377c75910f0b9a17d2cde2bb3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f7897000000000000000000000000000000000e2814ce8e1011c37f6f7c38ee9543c65d0d40282793dec81b195b2d4f4b55f2d2b68416eedc6aba6e31b2234c3f08b90000000000000000000000000000000006ddeccda49ae15e5574bce201589758d7ab8baaf1348c30111e997154b6ba413c03e939e288fd95d808017387f1882947944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd000000000000000000000000000000000b78dc15a4f413ea9c8b347cd82c278cec530a28d239694d051812c4af08b5be888064f54d2fa2278ca4734549cdd41b000000000000000000000000000000000a8c5ecc1541fd79771037e247357599146fc46b852536529b841bf4b21978a85dd09c01baf8878bc2b6bd8e36bb93c030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac00000000000000000000000000000000172140620e46db480b2a9f1b7f9d0b374c0fa19145e3349906aba351686e0b75305db408fca3465fd263d06157ea471d000000000000000000000000000000000c20ddfb4502ad34e0934812913e222fd9aa201b9e10b4af688031d2202663e9c044cf3374ede037ef0c7aaa82428ccc4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd97860000000000000000000000000000000009d75caf6ffb593ff15d5635502abd9ef88675210aaf98a73bfea25888c90b63de14501459a038f07ca502b2b0eb98ea00000000000000000000000000000000091c4826870da1d2d7da43fabda1311384f24bc6d7693ab92f59cb76a06ea129911abdc22addd72181c3ecaa15dffc884d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e0000000000000000000000000000000011e977de99564d61c5e0d1654ceca0d0d63dc09a6dadf6baac980bbb97f38513459b391e40c09329d22be015fcdafa6700000000000000000000000000000000119164ddb3240c59428f11ef8c7e0469d219a591b926296f394048dd59a62a21ee2dbcca55f79df5cac6b784a2e06bc5e140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b",
+    "Expected": "00000000000000000000000000000000073edf80ee80c7d1675d05f8bed28da759098f44730bcde3ca1a9a8e286ff1791fbf22bc36de06d88b20f7f1422dbe38000000000000000000000000000000000d52fe400f41b902f8801063c0f3e793bf643c027676e0a1ad3860e5455bdde58d988b929582823e5d7ee0af8987c551",
+    "Name": "matter_g1_multiexp_29",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004663e332c105837eebfb9ecaf524a8f7f4d651f3eeae6909824eaaa6250c9f7fc212f98c6b3d4c08c5198477f240a8300000000000000000000000000000000057144a8578437c9a10a7801fb179e417e9bbe1b85e9dd8e2208943978cdd77a8345d682ba83950e174c6cd39c9eb936a57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b1530000000000000000000000000000000017c44ab586ecd185de616da02f99ee799487b32baf2470871865baa2b2e3ca20f61e6c82d741853b71c5578199d46afb000000000000000000000000000000000c77154ab5f0ba817b30672367bf1e19f9e53a95d7fcc4565f82f604a07d5eedba2182cf1bcca2371af4d1bd09146cb98fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000002df334ee40a5aa144d3727ec6c19d8dac476c01935e7ddbfc164112e35cca9180ffdae5e56f1fb31741c327b5733d6b0000000000000000000000000000000006c1721530a765ce427eacc4e5679c42591d5d1443f0a1bca8a87dd19d6a33b731db6561c50a35511735324c5f402858b061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d6100000000000000000000000000000000016682e225b46618ff794f2da02a82e40193289c9df4ed6985b4daca3e9ce9ac6e8ce84a3fd6776119ae1a2e84f62e73000000000000000000000000000000000e383f55e44fa8528e80fdf391f2804f7b7f3367e0db07b78647e9ceeba5fb151a5b867bafb2d9c07a6a572ee71c2714355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a00000000000000000000000000000000176de8a3ee21e803ec6fd42f7f297daeaf1541c08c5c359e286ba65b78d7c31a0a630a2c73d2e886cfcb289783f30cf20000000000000000000000000000000010645db8d7d42e004c4f76bb2fe8b99a3177624ce0c1f465e67f3767bb57ca80ebadb12fba65bd021106e17adcd8553430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a77200000000000000000000000000000000006c151767d1066f9567ed86f7759a6f425a9a130a4530a2dec0913e4efe2485dd4b0105f453e90bf27cbeee5d0482af40000000000000000000000000000000019a081fb1fe2893f1919628cb8a3b332ef072971fe6ea7fbaf79d327440274a589045db5d3f06d6dc32d6bc7038c528b89a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000d19d55d1fa04f886078bba50e09ece3a394f3413745785c16d17c5936941345e42e4ac50cba055d79f2d813c69e0b20000000000000000000000000000000000ba513864132f44be3056d3d3d1fe8d10b8be954e785e3d07f816875a3454fb6d44c1a6da8c9644648b46dc7d8a0b67120b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d5620000000000000000000000000000000019cb4ac7844effff88b242db9908bd8773d91cbd8e076127493c548350bb9f8230d57a3e9c4e4b212e5686bee925d80a00000000000000000000000000000000021e94fbe9881b2f5ce2e8d777a33336fa21c24818cc1b6b699f0bf5cf1f22d7b9fe85be05d09509b88391f78eadf14e3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe000000000000000000000000000000000c6429ad7548acf43bd9e7fd9ccbb09b5b9b4474937bcca985a2d00c62cc8b72e07e725a5d447e2a92a6bb9fff0c50c100000000000000000000000000000000135ae562ac2225bdfcbed36817c8deadf892da1f8982f4bf53271320bb4e702022128dfbf9e48fc6623648878020c1a67fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000004a813c60a1988f7983f6ac644a66369153319e3bceda90fcef6fdf3e53ceb04b2c5d240cc65aaeb2530e8931f1a962b00000000000000000000000000000000141411938210cef5576dacba6d521bc46b13ce9c1f2a9aa41a0e9b56639995b69b6198f2a406ca5e471cb0a48233985ff0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000041855bc5957b8649451b7d91ef58fe8e0770b113ea3009815e60cb36c9b7ab797b4448d3747fa9b64b7fb50af906b6d00000000000000000000000000000000048f78b763a88fb7122e117ea4946a631be83b5ae456f0c77a16f3f2b546802bea7117eb27e23a5db65d616966bf2630f8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000003ca70d52cbfe2c097c17bd300f4baba1d03951c6dae613bfbbd53f68598a71d80a285af1a16365b5b82991599ae8fd0000000000000000000000000000000000ff454d717d8518415f23ced167ad7ad1ec76c437e29fef81b5604e8bc628b320fa39c192f32aa6201c2b5b4035cfddc285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d3258200000000000000000000000000000000098363ac967c6800b28c28afe92c1379574ec11e0585a0319273aaa6b92322563ad56144437569f3b9cd70ba9e7f9e030000000000000000000000000000000006e4aa226ef031c07150bb231046f36b8ced6b795b3e3f25f707435abc214f14e0c420c699f9c880e8d647ba85d467ef35bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e440000000000000000000000000000000001ced5366374fd923b3196d8f6e35900b80d01eeaa6ac41bf7d05d1fb7d47810eb8cd2d1ab793126edbe863be4c1224200000000000000000000000000000000010b27a94ae8413494e0560a10ac71554ff502be7e86cd9760b0d4ea7d1df926cf7ff1661b7902fb93ebcfd1542619caa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c800000000000000000000000000000000120ffc413256888669dce253043ace9a8c924f2996d73ef3a64d76d88dab415c870071a22b97da222361dc02d91cb25e000000000000000000000000000000000940f2259f4fadc3bfbed20ed2b80bdd86f30a846d6167661339e15548f6e57030fcd0be99496fa406a2d025077a4a4e1155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a640000000000000000000000000000000003ea26434b5bc703c242cc5e84e17be5c7777758f0b232feccef6d200db9a03f10df46cf0eead48064f8dbbccccc3369000000000000000000000000000000000649df5d665a64565079201123e954e78f07177739d082c2bd0aabddcc13f9fec6ef082a1348a369e446b82181e52aadc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045",
+    "Expected": "00000000000000000000000000000000110fac33d46271daf3924995a4798b3f62c79562d3b44f736b91add9f2af779a614d4b12a9c0d7c60bcb1f104b35474c000000000000000000000000000000001592121fbb147085613d1b647cb0e4a7b895bfd4e5391b45bcb287975bbf0e5218078d3e88f8383a506550ae07c9d167",
+    "Name": "matter_g1_multiexp_30",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000033f3c31337bc48622d27a9a3224a2acdb5c538a59b497a4a85840c81cff667ed0a0e4e3f4bb23a9ae53c1e79ea54cbb000000000000000000000000000000000cf0dc22af4530260cde26aa0eedc83a0ec3ae87d024e6907f3d22070e1054b3d4f24d5ace7218ed44763af6ec3f25ee32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000935dce5baf85335575af5a6801b36647727c3e28f224cf25227bfaa52fd646d6fdf0f24466631a93506a58b5f2df9b70000000000000000000000000000000007e032c51e2d9aa53a3120e5777a14963af8a9fc65dadf5da779c5ade6aa043ff496cf4f33e2672dc5e10c4a06dad86a6583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e86300000000000000000000000000000000178e7b4d05c4b7762b474649b38a5ce999c67ea677fee77115ce7e55207d87a82b6d05516ab41c2bac294fc382c0e12400000000000000000000000000000000126e5aef1a9729c73278b805cf102934239d1f706bb3fc3a81f3726feb4b3d2fd8de69fff2f20d5e5217edabb645e8df5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000014760b82d3b4949c67d38c6d9172e12bacd52ed49f442d781aeccb7c0444407629e3b7d5d5e1be996940966785940e46000000000000000000000000000000000aa2d6391e40e50ab9ece25786a42e8dc657e9112683279b143be5665bca43746244c27352d3600dc62c2c1c7776924339c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b0000000000000000000000000000000003f7c65aeca3fe6e67c91e1f284be35149276a9d9c0c1907010d8ce26d5c88f2a68b632530a31e41388cfc97529485f40000000000000000000000000000000012b9322902ed50ae50e3bb3e07eddec3245df27f193fa88a7685795990a5fecfa4be4b5bf8b0702897cfa369d614eb942196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c5650000000000000000000000000000000013995f89bc17b99384e389c9a768fa4bc37526606966a74a370c9f964cd9d3a7dff9d6be2319d2c8c9d5ac1b6f5140b20000000000000000000000000000000017b32d8800e21a4553a1a15ddbee029788f58023164e65b25086e0dbe2ee0c16e519dcc4753c322b50c24edc305cc26d8df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000017601971d5328ca817108dc9899c9c3b88aeca2ac5c03f70662c9bf6bf3e06d25fa4b7150e0838c21c9b089c7102a17700000000000000000000000000000000198db85ed42c61e1137fa50c8b2a3ad2eca4e9dfde3553b8ff7ee3aa6389d73c80d500c883e52be5cb9fe8f828bba84f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef0000000000000000000000000000000000c830262d029435b1b857e7e3cd118e8a6825e3e413f5a5f67b37da686f442577c0beca3e86c13ef6924472305ab54b10000000000000000000000000000000003d35dcd36ea7352d453041e821dea655422ae01a50731698af020234e3ddd38140c24ba2af296a964f4f5896bc0af8c8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000009830bb211c58fdb25fb97a4ba226ab03516911e7b7d98f25b94c827774592b5d5c56edfe3c3040454def1429f81c4fb0000000000000000000000000000000003f34873ad16852f435cec18f977db00f786b7860c580ae0dcff8f03a8a1edbb417f01e0dbeaf035b6f60b733f38a564cd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e50667000000000000000000000000000000001718cef19fe02a179385ba031f23d28e20e7f57ee82db31e632cc3530d17291e54e8a01564963835c724056c53f9853b0000000000000000000000000000000016c44ed6c85628341789e80e1d95a10399b6ac126319bba3c66bdfe6a40f2b06b721a0867c30be1356656cd36e6370aa2a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000011267a6e9adc4b547ea0f42ff6cc9b35a40c3cdfd7ea3c4169fe1efdf533341969cc591f26fe9a48a44e544c515339310000000000000000000000000000000013d878f761efaacf28677577c93d825336698772044266d469b934332412bde9ad5deeee4c1f534a9fd89e799584d3394a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee280000000000000000000000000000000004ca71357762ac2e9bc1f53919ee2c19d071fbd3918f5948f32ecc78be1e65672d12afb4d4a8df41a038bd5448bb0a04000000000000000000000000000000000b80b54ce782afbdad1cfbd57a852f629c0452346d5b898062a8abf12c73bf79296564d3fdb867ddd81156697a00f03ba62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000009cc641fda19b0e33065a35e74a7ac28ca1bd3bb8a7fd350244ad0cd5dc89d91e7b2865e78ba24e112589e298e6c5cb40000000000000000000000000000000009c3ce4324dacb1e2ca82f4ce6a7ed1292f204f4f7b2c5e0086843546c5c00d16be4e7bd9c979ecd3af590b40b0d70a4a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a60000000000000000000000000000000016ed972bad2d24d80332c4aeb1dc012ae4fc30a11597df1ca73114945c20e337d1c424e636d403141c737103a4dc02470000000000000000000000000000000009ab2d22c0161247a3c4eee341027a97009ea95bfd45fd186e15feaaabcfc09fd39dfeddb2d3631b943958620555fed81f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b40000000000000000000000000000000001721e0fe2ebc0be63df10f4b9db3faa5c5fc3ada0bfea176c4fcd1cbb696779c03602cbcc1da3917dfc09af72fa3cee200000000000000000000000000000000192e3e3b5b9b087aba72b852319c200451a4976a4e7cd817eec04c007c8a2f800fe0bf7834d22a21c1989ad8c6ef73973bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000159c5a01e76ee666e8e22aafc77e27705a633bd3d1dbaca92117e4b80f917a3bfe80b36d3fc7721ed2fb8434558c780000000000000000000000000000000000c8c356e19c759e1eaacab45b4fd2e0b42dadf6aa2ee8c051b8ef4de0c4e583fadfd86ff6bbfca1eed42a29afa470c8c1b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d",
+    "Expected": "0000000000000000000000000000000010f2b9ae629ef12f213e6408c94601482c4c3cd0ee33e3418c86f0b8092d1a1ab7d2600140f6e1231297c3bee4a48a9400000000000000000000000000000000018446e6fc72ffb3c6c25d6aee2d9a8bfafec7b4f63dd3f98fde09c088876c7f4d30cc0ee31985526712228766ad91d9",
+    "Name": "matter_g1_multiexp_31",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000de77471af6d857548f26f2ccea9c33f50db361c59b097fa481887b5a5deb4fcbaa25ec1008b131fedd3711d4d3ba029000000000000000000000000000000001037ee7b2005032974767d672e14be86177621db0ad5d7df5faa966b0e7db6319ead334358142feb370f60cec698f3d1bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc0000000000000000000000000000000000ae57db1c0d1575c49f8b049667e1c8ba0ca863fa56ee58a34ac1ae780c92418ec50294b666a0f99e0efcb2686a4d27000000000000000000000000000000000aa08900fcc4f9b551229b7a8a59aa9b337100c68703ef60597f6acaaa7c1ce910e643549dd0c328a7fa17e44b68de1cf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda994380000000000000000000000000000000016cf186f3a0ee77c7e990ec0784d99510320114793fd7a672d5f739e9b0f1186faaa9d5914860d66173696c603173b3000000000000000000000000000000000124f5c20e988b460c261d274251841cadf5c99a12c9ae8b4b3baa7fea8b592192dac3506860b15289df704cdba1dfdbfe45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a67000000000000000000000000000000000166434c1551befa708de9201c02cfe18020d18ed881ac4d154f5e560995f302b57b1694740f76232307ec0ff729b2709000000000000000000000000000000000a961fa3c19068590b4c252c0429414ef393ee071b02a4ef15f6a5c722a73d145c8e058ebe1997058b38ce7961860da954868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001618c78e4962162f253729c4cbe326e7ea7dfd6d5cdac1b17353135485d434fe7c4d857df673793e9d12ee65dcad4bb50000000000000000000000000000000016921790d30423d878255c44966b316f9c29dde6695d66a97139fbc6fba9c4df9e291c308effc424e5e2134680846fc37068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb00000000000000000000000000000000128a8a8584726a4aa2cab71853f843f49efa79071a8ed0a6ed2c7913fbb85e254184d457163fe647d0ad719d04e6857100000000000000000000000000000000158d36271e87ac2879fdd3f1fe8ff306126adb340ed93406951e372a7f7f3deb1c347ccf598f2e007d92f502038bd4960042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e00000000000000000000000000000000047e63ded02c49b7126a1023f1ed4a0af20c2d5e95718f474e4171c0fa888d7fb53b6a2bfcd47893aef6657f31071167000000000000000000000000000000001404e16f51ea45098d5bfa00ece3df841a3a6630bff2b02a8063ff9af5c3f149e504f04e1fc9d9bf35324569e8b2e1730a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da000000000000000000000000000000001866eb045ddc4e29fa612a31a34355ecaaa8482cd0885bbfbc5cc0b3870a86a2b4c3f15da23638dc03619cae6b721f1800000000000000000000000000000000086aeb6a413db889a86bb3fe036486b4e26dd614aabf575f8d63614a300df8a528c9f6d47d59daad59d840f591063b22ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000001765c386f85f7282251b6054f03a3941d44f9a8ea2814a49f75519f9fc985133937e2c9e06b59441a6d9a95c806d6b10000000000000000000000000000000011db74b6bd144f9a0d48185a3e9f4adbc79131764b6e82f11823f1bec92245a55d82e6d949f3378ea6605ec84f0613285f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000000bc9a01aee9eb527491f7334959b0f4275492afa38044f0e6dd222a3704f440b5ae2120e8e2798179634c65f3d674413000000000000000000000000000000000ff19f94b6802a4788c4fd84f66b9be03fb1417544d56d0e473caae0f9b9124c622e6298624fa1d53886fb5ba8b470fdcc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed2165000000000000000000000000000000000df5ceaa6ca501d1869b51f035c19c0f3f9db39c739f882a380930cbde7737790b25a2c01e65ed477755c2beb16e97f300000000000000000000000000000000148458f4ff4fcf8559b9f8a2ee4e486febff21d91fe4bc3c77988007cf700186894f1c1fa18ee3c4595a462712750d3097b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba591672700000000000000000000000000000000029b16c9578701febf6662da833091deee23e647a15f16895fc057a37c153fa738efb1742c4bfcf27eda953a07aa01c3000000000000000000000000000000000196d74cfb1e6472b7ab67a664a7c46ad0377c2b465e12d94b035b4b79c7e358475339e09690557e4b280cc84391eb84752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000e005ebdde060ed0233d1b1d6344b8d21f8cc1ceb6d4fcca389303e1c44c5964a4521dac8ce225e2e4909c4b2a47f622000000000000000000000000000000000fb3185aca9683a81d41a17b3a6048e75549d589354d4652756a4663cb25b9fbca1bcb9158e2ed73765d03be4e2b570f2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee300000000000000000000000000000000004df18eeff223e3a255e6652c3d14a6dad17c76e0597b43a6679a85f78d4bbaac1e2fc0ccf6a89149dc18045169345860000000000000000000000000000000019d60ee8b23308fdcfbb26ed30fda1dda5c6841b46fcd902e6c34dd268fdb1426e215d21bf650a340b284d5c7516efd3f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db44580000000000000000000000000000000006b084e91066f299e44a0c37cf65c30009006ddda34d4151b0c18a5545d67f2bc76df0bf9a78fd2b771795c8d041655d000000000000000000000000000000000262ba1d9dbb009f779e2a584ed313d78e4ac69a811e071c10e21027138234a32deceab16a33767fdc4a78062cd23ec71c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc0000000000000000000000000000000009aafc73979c000236c08e089828880f54645b5ff4c1dcfea0ff41ffe8e3fce8ba0dbcebf0d4205bb6616a737b6d3542000000000000000000000000000000001399a2072604d50f92ee186924ce32c4e887803dc258b7495aa2f3d2187571045db7f360d2614b198f83bc8024b06559b0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4",
+    "Expected": "000000000000000000000000000000000365ffdbc48aabd8f0e786634b9a853cb8312bf295543bd280c1a0a9f7d0f8ba95b3aebe31987ffab1f69a504edeac2400000000000000000000000000000000150af5ab7e9b1bc60cda3ceeada36abf9bb43f1182659d8d72281c1f1cdba73fe7d6e52abaa7506b89ef43f092f25bba",
+    "Name": "matter_g1_multiexp_32",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000012a651f467e9a1c1cc99c82e16cab2cef53b77268d968dcc73c5008e103d2e4d19aef4cdffa24b9474fcb393a48d6a70000000000000000000000000000000005d202cf9bc8c0124c0f817465eee7d4b1219071cfde50ce2cf8951efcc21fa19c762a1a8630eb7b8dd90cd03b8bbb0484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd00000000000000000000000000000000060650b71c97950ce5cd6b6bfdad46d66df454c5aae1ea313a70e7fc841e06f64a31edaaced17d8de56f1ee75f5263540000000000000000000000000000000018a211f44acc52e92ab5eb1ce304d80532fd4dacce60370dc62d9ffdebbf749689620798429b5ad1d8293c1967a43c12bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd10000000000000000000000000000000000f51ac340d512becf5d7a515111f63123e9bc940242ba42be9f464b89847a8cca9d93360851e3d047de4ee667a6baf0000000000000000000000000000000000dd7e71b516b3752c5be5ee5f3908c17e3e019b46422f24659596a42e569ba9e8711b1e8f8329cfbb990942f258cce103717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000015d542246cc0b46bbf5571c3173abfcf10ba447e5ec962b5f712ea7de3974c2873df1979c9d6432bc88d02588a3730f00000000000000000000000000000000005e1611597c12a4c7aaa25bd9ab1b6d30c58bd1fce3d87d66a03f25d6ed110c84c3e902ff5475795b5159126debf6cb522788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d9400000000000000000000000000000000018f565b38ce775e6b40581f757935efca255311b872fea3bfafa0662620ad5a02a7e8ce48c17daf45668c95ab0487c4e0000000000000000000000000000000010686971b402783c1e7d60126cf484fd01b871944179adc4b28de5d72e5b8823b48d382a8b69f6b4681c74961ca2a3843c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd3840000000000000000000000000000000008d42e31cb4c514e450f56488208444481db0beb5807c6f1c2d82ee09c9413cd6726dccd72e0b8ab6f6ce6492921b14f0000000000000000000000000000000012143ca6dcc3bc9edb5b10c3a47a5130e393986dc5e83d1eb61d9b193ca28193101eadf00916a3cdcf7b6c1369b17038ccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a00000000000000000000000000000000101eb8b48df43c3e01c1508aa9d3dbfe168e7458cef2ff61c15d5b4e8dd11be6b9a76966c01682fb07368f22362f355a0000000000000000000000000000000000babbb820a5a8e0bbbae1e2455d54b97f6771ff914fe33a007734d5072a993df31c6a2726c8b03a8c2dcf48a73959a8ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f0000000000000000000000000000000002fe8c461de25f5e6c5a082fbc4ecab5a37dbba9255ebaa0b5d245735edd27550968c2558ed24f7bee99092228e37c8a0000000000000000000000000000000012513b2fb62725aaf948403c13f11a6d7461c70cce3e4f912c8d2cc9f2a8676d9bb37face3770e7c0121bad6af6302d121cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c91000000000000000000000000000000000175c93838001f4c67a3e0e5dd7eded26a8818b2e492eab2e0e6f8b421e3d3611561c8b933010a3c5ff96128631f4e88700000000000000000000000000000000136292092a366a73a5609cb1e7fa403c59825e99c8c91a37b289ed779c4a3db71370a4bda2cf8509cc9d4b4731b4f52d2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a05000000000000000000000000000000000d03e1d6dc4bf59262fe3bc3e163565110b751c534e57c621b4be59bac28d6e8bb379cd4afa3740797dadf32194fde310000000000000000000000000000000014ee46a0cf13e795c8a46399ae63e1b812f237eea725539265e13d3ad1a663374dd566df450fc1191512ba978736e5b779cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000003cefffd8fa01842c36dd9fe1c57efef3278eebe5d1020582c3d13ced75d24177127da37eb59e9b46b4a0a19421a5aef0000000000000000000000000000000016c258ffb2edb299fcc04ad309ee5d8a8f186db5f3af8011d42b22b23687c2e814e2a8d366f3cc61d7c89bd9619523b31973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e400000000000000000000000000000000097b6535843436f879ce659b6ac9563d81ac0262b9a861bbb367bf8244a35a5de51f3060d05cb2174cb41c8c3dbd8dfb0000000000000000000000000000000012dc9607e0ebf73e3577ba1ab39437b03215e366cf1ecffeae4ad4c7919a63f62e45103db65de4c9e3281d7604b07f24a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000004479ec5d5ba2f1c661df8e4f85320d0e754372e0c463098b0ad7477f7373f309c674dfd31c7f08cccbbf4bbd17c23d7000000000000000000000000000000000470cabd9f5c4bb8b1a370888d8f0f486387a89efb92912072fb0907a1e64f3327e9beaddeaff44c502414632243d6fb99ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b00000000000000000000000000000000038101da3c35dff20a878300bcf69e393b77873a971838581daa9d096b00bd6fec3dceca882a02d397a90c816fb415a4000000000000000000000000000000001184246344c03be6103acd745b3ed37d8f67ebf0caecb00cb2528e0da9aa3f352a4677dd6b832c042d6e1235da7521fbfdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e90000000000000000000000000000000000cf99121ecf9b02cbd006348b16f9d80f64ae3c946c4802ec6bc056bf6e95e01b80cf3fd10ab1d30260a402b7c46f880000000000000000000000000000000015f35fe1ec8c258095394ab2b021d63ce54ed4bfe14cc5666f5ea4d5a0461d535b8bce3263913c1b4e6db6996cdc037d786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e83f4b1d3eb8d45ec0fd9a4ef001e5bfdcfb9c99a6d1dd4b4e8043b4d11f5c6fd65296a33c7fd26a4e30dbbe1869090000000000000000000000000000000001197b11d6747280b37769946549ad9d4a1ff1006ac726d7cd322cdb4e3cf86906c7ed371e770fd95ab4fbaa1b7b514d985d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6",
+    "Expected": "0000000000000000000000000000000012f496f031f5c1b594256e272520ab98f3733fc9c481e7ec8de8ba70f493065eb25b681a3959994d37aec979c22c6c3b00000000000000000000000000000000015dbaf471eeef9307d8dccceaee179d8c9072b052af66fbf049ad1d346e08bb555238a763e903541fc72d9edc30ec30",
+    "Name": "matter_g1_multiexp_33",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b632afb8deb955e64fb4ff5aac396152e23b11a3f326df0d77b3ec078934cfd5e486244aebb44cbd1599f594991a26d000000000000000000000000000000000519f9de5a5b1623e4524be68b5ba0f997addb4da78adcc9c3d5910009a261fdf8b0efbb6e2a085e74112ac4e2106ef319582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d370000000000000000000000000000000005456d9312825dcfe5501b2c38aa610a767bd38f46cdc8acd92f0c8206a9c2f9b8f65c8baedffdec5e69f03fd3adc4c40000000000000000000000000000000009b2dab21ba4e4b4c284a623994b92ed5fff0fc198bd154fcfac9abe5f05b830066b44894ac6f92bb2f61bc88a7867a8ac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d10000000000000000000000000000000009f55a987011dcfc796df284c7bd758c3024d4f09edb3884dc087de26fc1df0f71067d44fde07fab9334971b4a0bace000000000000000000000000000000000003a4ee3e9ac2632cc81cbd4ba397d44f738ee390a4af6ecd65079f412bdd8c4a37d5413d0d9a7dbeda8a1267d6d843b069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000173c7db310b54a4a720074dee01dc0e5f84b606c9c3ea0962bd4610b569f478d7a5221feaa944054cf7395e578d730d8000000000000000000000000000000001697f0e16c49b223dec9e0fa429e68dbaf96b004a561aa3e37158064ceb9232c1cd21156c053fb89ddb230deaa7f8336be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c000000000000000000000000000000000ab7f085b711171f999d0c4a46cc7c8cd8a429f6bd90d1b860c01066bd0d193f1c1441ae5aa97d690569807749ed69e1000000000000000000000000000000000824841eab90d56a1810c129b8f27d0068fbb7e3536d6e56cdfdd9eb553e283c5d0ab1c418869e886fafce53697520859b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000bbc8c5b5e4373e76457fa45acfd3f1151735457b0fae06e1d3e6e5dfeb35815aed44bbe6395039481ce02d2aa2c502900000000000000000000000000000000089ac22ebc582bb71a60c88638747e2243096e8d193fa1863089698fbf6805128f9e32636d6f954ff03bfb6c5bcb0060d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f00000000000000000000000000000000001fea43c3029447965718c8e76100875acc8fb4da66f7a4f7fc5260de3844aa9e9a89ae4d9baa11c118b9f851fd63de000000000000000000000000000000000844aecf4a3ebfd8b711dfa9efaf1a57d635f46fb980903e362d4ad55d48c4289a3fb1f439e6b7d8f88cc51867d6b462cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c4000000000000000000000000000000001384e33086ebe795cde3c951de9b48f3f0fa2f627524cf0c4e3691599b62d4611c6a84897298c287d162825c3f153a75000000000000000000000000000000000a04af7cc41c2d3663444c8aaabeaf70dd146dec114458b3d1dbc95cee99ba89a4c5a38f2974622292e3236fe2aede6d17f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b400000000000000000000000000000000158820954aaf8e6387cc0e8e528723e0875f5f719a46ae5cd9d967674815a2d9679aea9b5736f882d37e2dd26b7db17f00000000000000000000000000000000058cb933f8dbac61a22477cdb3f52c9e3de6f060dd51aada35b6f8480a53e8eec8f82800e89ccaa2d2eb1dfb4352f16561ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb50000000000000000000000000000000000a7c9c1bf574503a884ecde5e921da80b299c4efe674a2d5c841e6036adaf7c1156393116c2c0b9827978d43f1e3e440000000000000000000000000000000005cf22e56bf4a46504ecedb072fe5e18096f9da550065612a1d00cf79c65384dea1bf59cb7c52de905a04f1886f36c8a0fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be0000000000000000000000000000000018e344838e2efd9363911898f27882f67454dc3b1bbc71f1d99e787bbd6a1ec9744876156ed8db2ccd826f2b4fa784050000000000000000000000000000000005528854a8568ec6491c79aae1df15d965cde683c9ea400b470105117f2bf3b41d2f958a8dea5f866a55e60fd06c1f07d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc400000000000000000000000000000000047dfb6a6125ff02e12c4a9d88ebcdf8a4375367e1473f5a0d99152bf0a4055138aa9a83d98d7f74d9fb8888f643cac00000000000000000000000000000000019d0bf5162ca55d8113a97cc3255d090c6924362e6e05083fc323dafc3b12e898cd600d2730acf8cf5cdfd4420962881c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e132de353cb09b69ab369c616718b9cf492cdb9d3002593319a6e7b61c7d90f94808b75d8c7e3b9d7a811d01baa47a1000000000000000000000000000000000d636abffa063379e2084cfc09da5ee04d40d8e74ba0247a01be414cce820024766195520f1d2eaa90fe254e12a4d86026cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f0000000000000000000000000000000013f2367ff71430cb541557f79c5ae8a0d9053d82341d83037c1f73a52585255b205706227de4e87d6ea2ca602483d2170000000000000000000000000000000011f3f4e882de30b40bc160e69fc2bf4f7c588cc83bb9dce3467accec7c47714e2b326be001a36c42ba39c7f56b72d6fc699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000014bcf3f26683234584d79b436cc608462f1e2c20b5ecc5019988d8e30137859a4b6d0e1135dd5bbea0781b8ed3f0653700000000000000000000000000000000090ef29bf63ca97ae8388588227e1d1a0653c43b16a35a63f2ab4f0b11fd8005d9a85d30a7406491d983f347e4dfb9f140e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf9523000000000000000000000000000000001817fac61301ea6a43d7968b22616b836ecd1f20e5883e9b475c18353b066f93bd68a8274d0b6ea4480d8e314766dff7000000000000000000000000000000000c52fc676604061338bf0712fc1606dd09783a1f9a5250e3417056e3c39e59a28c7707d5225808414279ab61e49b6081062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d",
+    "Expected": "0000000000000000000000000000000018410462829b3a72024468ddcbc42d59a99a70296024654f99b591ce016304537c525513defb655417ba3c0f5e614aa8000000000000000000000000000000001416a19f73407c262f5e464021eeae1d1f10c3ae5e45f132a2f402a75cfbe409651d3795e482b15d29037e2f7105255b",
+    "Name": "matter_g1_multiexp_34",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018e6f25220e4b4011a0291424b4062930f5df45eaf1581d9591560fd77e630411e0abd57f9973d4542741de5cf3132e7000000000000000000000000000000000b31f903e7fc36327e404973b90efe5a5d2249770170ea1e58839e19d8aee99743be012b6e8a3fa73efc6bdc08be372f764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff20000000000000000000000000000000005dc07fa620d476d8f64358c920a401f8b08abf739befe1c266fb307b959f37542140e398c33b082d09f9f53cedf6f810000000000000000000000000000000019d8e51a28c936b5037424a7ffa8cae75496131eeb2b2d5034e4e882c1c91f6bbabc9ce4fb2fe4be3da4eba46326a3603280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000006f5f80dcfe8be87d057e2162788f7599e55b69ee8c6bb6a47c505aa324ddb5ffddacfcff35cef3dee6264ef73d6a353000000000000000000000000000000001056081108195d4d27af7332215c0b444c9f63c7574eefa81046e1d064825492e2dfc5bf2ab5847a37e6b253d9dda9fdd4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb111770000000000000000000000000000000008db431907692896f9e6e254a6eac1a0ba5f9cb84563da69c3601aff1370b7a5a98edf5a5fbab06abfb4496c777bd83f0000000000000000000000000000000018a3bc407fc42236c4429f241fa760c6513614653e8b02835480dbe1152763bc6a1a7fe076e8bb44ddc04322cc906e1ac66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000cf32da94af97001664607c7840631a8df02a008fe262c6dc649a3eff34a42dcb98884212bf3e979629c98cbe5fc457f0000000000000000000000000000000019b3b4d82326ec1aaa3de3b2f8e329ac0243d3f6bf9356886be4033aadd0398a5c58c68510de29f92a7ca910d851da244b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b845000000000000000000000000000000001504d90c52af16b5f88357c87d4be7c329855ccad6f6633af0fcf4341fae54aa4b1ddc1aa22fe1ac12e9d850a05a9ffa0000000000000000000000000000000012ea642b96304316451dcece5a6bb324d197e31f56ef3f1a17c973742322d08f443b7cd156787f8291b52c0a6f78b4b1708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a000000000000000000000000000000000fc61e9589a2dd7f6dfd613225d80a70ceb977bdb518b5a16e415f887eb73fe9fa5c9130d5fc6deb4ad153c5de0907d6000000000000000000000000000000000a0fd7de87139581e9b1ab707e25c186640db92875a7822d61d8c476c40ea07bff000cbfe6975076434d0b703695740685ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000188901f19a776ebd2ddad60209f4545ca9b0a038b0b3c67b6f5e35d61f8cc2a297d51450663c4af182079d3ab6b01d2000000000000000000000000000000000151b9eaaa281acd803abd71ee4098b4ff6535e5081a33cc68ecca54eb9f1a8f94f3b1b21440f33b8648ec456dc1cf7f3fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee314780000000000000000000000000000000008ce8bd24052a8e1472bb64cc215974e20bb16d502b3a8113cd6e3e9a2bb7c3fccd45ff711518e8430221f40859374ba000000000000000000000000000000000aac2e8db9123be3e82905a0fe780daf4a841f6f961428b9b431c3ba2ac31e8c06118402bfc7fd15fbe3ada0ec8bbb2af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf2000000000000000000000000000000000017c580f501a1c4823483ae718371432a8a69e16e42dc0b15bb8e01729b6707ec20b898e3835bba40d7e8802d9438281000000000000000000000000000000000bcc167264fb9d6c27272c2280d8e89f9655ac7e6408694a3a4ca6fd0b46d1d7e3cf608bc2ac343806c5de42ae7a99e80ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000017ae89082d6f531bb7905068a9c00017ba8ac8867c6e467fcd3e88e9229ba5b21ff4d0a5ce937b75b3d5dfbbe35f2e7000000000000000000000000000000000005bda8d641b782ed51c416d0ebb1cc7c8f623d49b741a7cb93b3514e71d5b9102ba2e6c768661686c2af2acedf466e4dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d099539000000000000000000000000000000000150ed2c2b2d1b0b87badd0dda44325000a6fe98d335e03f0d4d147b20d4738e1e0f0ae0ddb2783bef283684e631ff45000000000000000000000000000000000ec1fa174f3f42cdb0fb67a520da161d9a9d1e53a5b0735738580fa3e80550c95cc3a1cf67fed67dc2eee1597e469fe0e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f900000000000000000000000000000000111f184636052719c6df1541c100d5a21d573370fa7afd18f5ddd1d86842169eeb02c494b33f2bb2f54278530729bfbe0000000000000000000000000000000016be03c9764aa34c898dcaacabd1493610f55efd36ca0b35eb48e89c7968e7a720d545b18fdb95954e01596856d42975cc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000114fa84ccbe9552a2ce2368f1778a1fd3c67303d8036fe4ba171ba9f2f6039aec1a59fea1b8efae88c01bb50e53950440000000000000000000000000000000017a51bf70c41571f36d003c0715238b6c8fd64185f616cd9076b730ad16caf364a75fe68de246249a42cfe013606874709a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce000000000000000000000000000000001450fe1500a6fa9d966a0c905167a414d59a3f8a064089f09db047241e9abc31d9e41ef73558eed741541414731f838a0000000000000000000000000000000017e61d4092537ec48683f86b72123637df25a5fd926e5703f993678a798dbe635ea29303f8b4d9ac76231a71cf515a70d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000000e72f0c855fce66335533c05ae30031cbde78ef07571eb1b645fa3ac5f3a7d76a4d60cf078145617c5a7ccb16266bbee0000000000000000000000000000000005b3981900432b193985f28a88a72ca9958b4628e5ff9d2cf8b0b23184e2bd433d495636de3d56711f207719fdd3fd2f9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12",
+    "Expected": "00000000000000000000000000000000110feb31a1c40d570d7281ed9f0c0ac8418f4a7aeb6be3221b130945becc15bb353ea63623ec7dba2844d3f527c167e6000000000000000000000000000000000d76c7aed58945a7fe52f37eec3be7cbd4438645a649a04859a487e9e2d4c82bfc76f7ba990f825302861d82a748c8f2",
+    "Name": "matter_g1_multiexp_35",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000030c6580a3dc73be106748d070b24d9231c382df143fb4bb8ad45e4723b40f90724b7e54510da1b2bee523a29aeb58100000000000000000000000000000000010cb3562fa1b0a3778393412994e46028367ed52dd62a1d446fa02b50acd48a784ab49141778bee5036b7d3a95c9ec217b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d000000000000000000000000000000001972db503f6d70a0b247eeac7fef277098604e54465309967b68d24ec1cece802d8c4b699eabb72e03736902d41fd5b60000000000000000000000000000000007f30233f9043927a629b11e7da48f895fce86b31911ff5c511c7b50642c296d37a3078e2e12f1adfe668731d0e6810ec6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b4800000000000000000000000000000000011ab9fd98e42539382c85bf76b563478fae8cca90ba1beb0be56b405da8326e6f1348b94eba61fa29c78645f8eb96f8b000000000000000000000000000000000f30617240632d129ceb69de1d69a23c9bdf950819608deac0600d1d1fd730a3a6d22dcfd635b25154b5ac7e22b20c70410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d0000000000000000000000000000000007556b86cbfa9f186f38fb1a8adce4c08f93f874bcb36ba61df5750c7927cec8896bf831c0150c249067ddada2e914bf0000000000000000000000000000000016ecf045f13c78de8aa18c2ddd1714bfc532ba8ff5b7851b58240cfede20f032067e943486df628995b8f3845289eb02d97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b30000000000000000000000000000000011379452e627dbed2ef1c74eb917b95b3933b8fad8295235cdbd6a4394d9b75cd3598c930d48c2d4abbf1558c65e97490000000000000000000000000000000005e7044829ae3f9b073e4a2237de96b0a1bbec3a30dc39c839573eff77321b1e0a49d555f0e31b8aa096f83f5945026bbdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb360000000000000000000000000000000015f8f0f22c1553ca663ce7e9ac00514eb53443f6c4869f985dceb118ee60a88a4826e9dc7fdbf61e77cbc93768fbfde0000000000000000000000000000000001646ecc89754ac57d7d6fe9b871692d65057f23d397a410bcb07ef3df0a3c3fad9eca515f0d0dcf0610edbdaf4cdb5d743da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000003a18dcef4939e154aa790b0ce8265f27cfff48d5fec149d91307759eaddf601c788da6ed8124764bad940f117751b0e000000000000000000000000000000001813f4650490f3839fdc9f96ef744ea93a9fd86f8a43d767259c2e0abafe308fec2bc6b9d62c1dd7b5ab1aebc19586e93c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b68058930000000000000000000000000000000006490f351e78a40c0cdb827aed3869db293c7d654b43d69ad1c9b3b536b1fbac67d50a835878171974669a30ae9ad1bd00000000000000000000000000000000041816bf846528e23eb129689a87c2325f1b8edf237c530eaf578a908fa0a2604baa19d6e0b4a5801280c27285896d5a51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000064be79c5d382c6dab72bbf28defddf14cc7cdbb23eced6bd93abed078175668d4dd66d0b3abc6384165d26bd86680f9000000000000000000000000000000000fa4c8be5d20d16bee7bd5bacc0b0086875a14a119b4888bc408850c0a099603fe3f79d334e45bdc9130132ea15a180f6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b000000000000000000000000000000000182f23242108b022ecc1d156a97f1a5fea2cc2e059dcc82273212f37c312ab77886c1adc370bdcc6ee05cfec957db970000000000000000000000000000000014ceefb3ca54bfde172e0455d34f1f462208df69328782b7961ade821ab91e7b3ed5426b4065fad10cc8fc88c90d8e87f4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb10000000000000000000000000000000009339b95b043903f2a3b5926a27e57cd0c45e7955946718e7dfebb01f18e9d7a2002c670769c4674773a835311f2e58e000000000000000000000000000000000ba94f6b625c507934f633d5420654056a939c68899c41e3f337f7b927fe82191d39905b349870ba0c41c8bfc97d64a9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000007604b5eb3218140b94732a601da577da3cfebe04dc7dcd94396c1a6704a0ef5a5bbd0c31c196f2876e1a4bb7490629700000000000000000000000000000000193098ff839d38c9bbda43944d7b0a3ec9d0d6732519d4cfbec506d29801780813b2faab46658c4383b2f26c477580af283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e500000000000000000000000000000000022ca1a16df42ba543a118212a75eca13707ee01eb3ce27d3659b1fedd99b9fae859f4eaa51e9be9107704276b578a0c00000000000000000000000000000000012d60cf33701caf11be6c9e3ebbddb9c7066dec3821a2e0f9e5b94e029dfea4063bebd4b2fe18c2442311c2bddc7c08d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b00000000000000000000000000000000011d4918642919c801fff0962062a387a4dffe693ec09cd3d0286a18e3a22c84fc09e8396ca82e6054d8535cd888179230000000000000000000000000000000016a1f0c7fec5647dcce688d3e4e526749bbf23c1fcd9e9168ace47399f9198c9b3a6b8aeca68febde1b7beeea0641aa2310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a990000000000000000000000000000000017203c37b21375a524bcc906843a0045229c5531ca23177dc88026e83723db21d9a8b5e52cc0be1d232818ed9abd496800000000000000000000000000000000097b4d7fdfa442dcdb64e405965439ebe70e4e71cc8e13e299fcc0b5dd88c67d6d0dfd254ab9b545e66295e2f3df14dd91b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab9850000000000000000000000000000000000fc4198a87e789015a1e44935321677e84356aa9e06592f9cdbd149d13ac312980f3048dcb9bd02779a3b10fd24ec98b0000000000000000000000000000000011425345ae1139647f93fc13eea0e920c491a49998430a339cd9d4260479a427515109753e70811be4cfb3b96db5c78b3e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8",
+    "Expected": "0000000000000000000000000000000011cd4c4507778871fd7b28aaf79274178df83f3e53c256dbe7a52df884d28df6a0d87d20238a15f489546a459195ace0000000000000000000000000000000000439a672492225fc09e46bb178a5d38956ae237d9f7d187d4cee14597facf2c06d7e70be5ce20e1f1389e4da6321e455",
+    "Name": "matter_g1_multiexp_36",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003790fe37a3aa78cdeafa76bdbebfebb22ab5f1e09e4e488418568fa307a5db18f9d93126b0d3cdd6a28abe3a4648f6e00000000000000000000000000000000043244b9c78fa56c611bf72bd6a17148abe76fd0efbd25085d7b46c90318ed591c5975f79653b98440f5f7c04cae4d7ea2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000008e8fcaf6d2056c6e144295d437f7f1422f6af7a1b62e0b8073141b2992b6ba865822aa2d9fe439aa1d896b2a6d231c4000000000000000000000000000000000bc693fcd2021972914747e48c600c444bf69ca8e1386655bb5d987608d648965c754668ae0a72c2439ba0ed98e5e581fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af0000000000000000000000000000000004e877b9032e168650ec3502ba65118aa0a8013b995a647210c1c36a6e6c702a93caef674d03d82da1f7c5d7ddfa0d0200000000000000000000000000000000063dd22dcd667c8288ca5b172e34b4eb783403105523c0467139b814e048fa21245879a5e9188a1a87d26fba52a9f601da609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000018cb2fffa3181bb665dedf1d60de6096e8c5ce43287cbd86c2df5a5d42d0129c73cd281c085fc562b7afdf52f0a680c80000000000000000000000000000000007f9884780460ea018351b4ccb5a120d44312056b96c5ba77cc38789627d20500d6b7e69dbf6ab49d6bee998a6aded67bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000bccad9f23b4c1231eb07df139548b66714a064dbec4ac6ac43ce18671144f2bf7ed99f16442b9f6600e1122c58f52e50000000000000000000000000000000013646b3c310a4b3f279e17f45fc8104d2c9d00f698b869479a5a0e1c2131e3f3a9dce86115ccd539bbd4346261c5a75f3222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb4000000000000000000000000000000000d02250115596126e858a63a7082a8c8f8ebe055653f5a60c855ddbbe3ed05792d08e5cc348094b8dfa4584037be597c000000000000000000000000000000000f68ec7da947cd0a57177fb91d12a820ef8574f4c524fe54b9420f9ba4944759c92d5919d6dc8030fc663c34519b64c37431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da1000000000000000000000000000000001960580ae965c37c2ec219dd0753749bd70ac2f0c4a3837418023c5142caf7b4dbf592554a6dd95872e018e912e3a20b000000000000000000000000000000001210b4093a07616543ac2034faa9c4a93b5f4cc3daffef2d8450b1a1770948de56c5bdbfdc9f1dc9af5e20778c1e8e6cd474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c73000000000000000000000000000000001955d93fc0f3ce0563ca4f4ffae0257297002001a3eb941cb9d3bf82b8d7f97657ad7168bd386636aaf45398745d5158000000000000000000000000000000000cc7a0babdf499322e060f2c83897fa7b6c3e7b4f56de3a18c823e0ffd87545a3dd68947df8cd8d3de5795ef7cb05391976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd1000000000000000000000000000000000d451eb31b21eff2c18b52b882e1eac68a524e3db43f233a9d08139667cd0173e3c716f29085c599a09f19019fcf447f0000000000000000000000000000000015852c483c8545fbf0932c99b1944ac58b37228d15284c7be5f5259bb8002abd57b26c244846652a862d46016221eab19db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000044e70861dec38d2b5ac7fec042c6b931d4e0a072073333f03ec4382fe40919b29378cac920836b1641e5e2db053c5c2000000000000000000000000000000000c422a91c81a99caa32666511c0ae4decc67cd94e85260b49760ac9e97894b0eb434d39c3884aa4614360b79681403f94ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce1997000000000000000000000000000000000af002ec82c5ac0dc87e1ac27f4cd052eab67bda318557c70fcc2edbdc071ac4a3fcae90f73ee514cdf8a543ef59050d00000000000000000000000000000000109f720464ff2eb2978d66370041206abd9ef0c6ce79d51f7d233c49b72da520612e59c39f3a775e288ba2220fac1563ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000f62e7d0aa954742a2018d42dd9cd76f041d9ac46ce659f4e192053a1d0c9b23fad78a06f61d2c90eb7b4d1bfe6d951a000000000000000000000000000000000ad5a5ce7b66928d8e6e3806a25425bbf2bc63f8ec87002a913c28ab702b83b6ba590b41a0691daa5b921a12375ef47b275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000008b836a23836624b39e3b3388027093125749a5edd5df50ee0cadf1d485c9dac9c2569a82484269fe7af02334369a29b0000000000000000000000000000000015232caa0c064d8d1bb7fdcd23c0eba21685fc4671e9f04cd1dbaa0382aa4e9d87aea42a99cca22205367d7b2261defaec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000875311ab0cde9a925383dc84e4ee8e1610b2f5af0e1f530aed4155cb8ef0b5050d907277f55d8dd542a89e4e0990bc30000000000000000000000000000000002c7a0d315bedb602f8ec558648ffa69831b9fdb6c14fdd44e636ff00777f2f8ae4aa23aca1b261460e6dfd87e7e501131e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f7000000000000000000000000000000000f9531c4998aafabc26e1ab588a97a78c236a854c3fc92424320a37a236d5181d34f8e5533aaaab2a6ea3385acc85f6300000000000000000000000000000000130350be432fd7d68940fd5f54649820ff5b3d015448d48d1f4db3a05ab0405a73ccfc8eea1966abce35833b5d03bf79f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e0000000000000000000000000000000010fefde43b2cbdab52ba664e12c7a6ff29f647942e16ba5a0d41701754ec63bf199ac8e710ae8dc6a033abbcaed3e05c0000000000000000000000000000000002189172e607876a6e1664fddb990009dd5c7a8412d60f7dcb235ed1825c756598bc67f8d5d383c2570a880492d4ee1967b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f",
+    "Expected": "000000000000000000000000000000000765877938c1a8170e2c2fda55241e3c64f7485bbca218f4a2026d00ef4708d014fe4194048da8e994cae1088694d1b4000000000000000000000000000000000b32833dc9a39e1e73578b21f75136be6c6aa2b4128b0e6ff4fe099f7b7a8ba8f2b769f68d32ab4d1f37793aca8ecfc9",
+    "Name": "matter_g1_multiexp_37",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ab94114b3ecf9535261a0726a9bc0e0907385d56206b61b7a42f643d46296c4022bedae90d761d3c002dceaa9167fed0000000000000000000000000000000008e67942ab2b9aaf2f6f865b7e957a25dd7ab8d8a0cba02fb1648e4c7f15ce00f4f5d09199a583f38425bc62d32ddde69f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e000000000000000000000000000000000e53abd9ff27231fbb09155f794e5d126c490314016e31c0b12bd1d2af97a705bc267f92e20b64c91d9af1bbf5e45b92000000000000000000000000000000000ce7d0cc6656108aa7005a56d15a497009c90871f01eb38f1bdc82edcbe4945a2f2b67c9b812aee42cc9a9bf9ee84bc08fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f30000000000000000000000000000000018c2688f573d4849b6d19e711ef4d14659c2c580eb938434a3b2afb8c20c522423db4c7fffa42eee9ee907a6492b77ad0000000000000000000000000000000016a7e69d5539263fd6b7eb893d476a00efb8cf09f21a54e9ff0d1c11e9f3651eac8a5db31b40598af6c943f864ff60ba128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000002380f3260c7289ab2005f7b1d7f572565ec938bd894bbb0047ced0b652fa2e74aef19c9fe6bc1fd469b2a4640245777000000000000000000000000000000000f32ca31e6bbb72a02f4b0da0e1627dab9cf1195fd7f48613c89b06c702e662478b24d8b3730321f803ae3a307fd498bd45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c2060000000000000000000000000000000001892b0d219ebabc3be00f45b00be55ae486eb79b1e41aa7dc8457aa0812e7276c21024c79646128fcb2b3c517aa41c3000000000000000000000000000000000793bed9530c814fa0d0ed1684614c1e6968dec931868a64372dc1b648b1f99ccce20fffec7d485a226033601b92a7f228f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c132200000000000000000000000000000000173f49cbbe6304aa41513d3742b89c6b07a91be50264350d71bc03fb9efe4faac4a19e2591795ff4a7e67fef7a85ed430000000000000000000000000000000019bb5dcc59ddf055f099a1c3949bb50972c4cfd035d4d829dde4ae94ff9669983e9b1a7edccfc2436648dc942862676fe6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000e3daf60e4929b4a237caeab203f86e6eed0ac630a8b955a03460a7e609398d076c660401f8d2bd9601e5bb5e315e1e400000000000000000000000000000000058b20160ca2232cb8b6cc63c5a8e11613afb9776e22d93f687e7ba005b099531f9693f65f153db01f20c8e9bdd7839ea83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000c89f1ebd19fb920b6748b15192829d58820ee4995cab9035ad6bfd8dedadbc6352058806a7d45fecefce40133261f360000000000000000000000000000000019151260431a35d124fe44116d86ea99e3f3aa14e2eb09be8193dbaa8f26fb0ae2451ca1c70610233d3f0af9d2e33fca4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd3700000000000000000000000000000000162b48d56f439ff56197fad444dc460cc6432722b9b86c7abbbfa383ae1546e160716d94e442183196816084da90bf77000000000000000000000000000000001278d0796c26110f66930ea9248078c222a0590a031df30c62fe6beeefa70deb0c8287b0d204a911c147cb6344632bf329d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e6000000000000000000000000000000000f4fdfa45aa3b5d1838b4dc8a2dc6250c069806ec3c551ac961da5b44eb58d962d843a1c17ebf89bd653e9e44d16300200000000000000000000000000000000052ad9ce994c837596339dcfb73ee25bf8326657633fb5861039f197249d425e35c238dcebb287b77f41bfe7f4db5c9b971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e400000000000000000000000000000000185c62a080df61ddc97ab56d2286ceec655172b6c863b509a1a92eeb0719060528ad3a3365ad5e7c0858167ac2c6d22100000000000000000000000000000000126b489e107dfdf4a4638069944d1b1297db734e5da1964086114f9f62081527d7d3f6032c2f29e75b4e1ccf5b3776d4659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e000000000000000000000000000000000f884244e098975b837a58ae0218e7e2606821c95f51d114a483ed5d31a59c9b9cb3b1db029a0286eb95686e0457afd8000000000000000000000000000000000caab7f67feea4752d3822979a770a28c879f5e8f916b72dc71a3b14820ce170fd229fdb61596d9e89b4be8f515c470e12ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c600000000000000000000000000000000155d3e886cce6f257513529e40c21b5657ef1ff1f4e71bc32b968db3e05652b1ac780da573fe1a1b94b7fef86e7c260f000000000000000000000000000000001184cf09544ec2826d0101d2b79095da6e5f77d453203c52ea17b6476360ccf166ef092eccf86dbe3a260f7fd25a2794666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000001523e919446b532593b8e70cff1206e8910444c01399c0dbad932b596cd0b9c2e40983ddb38eeff4fbd5e8d2b15bdc780000000000000000000000000000000004be8fdc3a3296e543701ce8c1184a983a2932f33913d6d733f5baa3a783382739b697fab4a3d6f9ac5b85ffbbc78a3540a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e401390000000000000000000000000000000018d20e7846239f472ef42c78454b6c335979ec563ecbbc3a93176a7be9dde603e6f21afbb68058035958ef7392dff3f20000000000000000000000000000000011ae4de8a7e1a958a1186bda4890d282773788f7d5fc5432393ac9deaba8bccb5db952547f6aae49b8a90c813c5a93a4662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000010641c99a359d16dc3e3f68547288c944d44c7c3e6177fe94428ddcf3c86937a3fe1f41a31eeab551e11cffac012e1fc000000000000000000000000000000000f407b01737dca388d0793521b667757d70e626ea0ba3b051f522639e752280b5657b1b97beae3105489161ae95a470059401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6",
+    "Expected": "0000000000000000000000000000000010084535f50807f287eabff2fdb83d34ca30454e4cd747cc3818a9dfd80c30fb3bf2f9f771d435b79a2d36105266f0c1000000000000000000000000000000001663a611323246a963856a92d52947e72dc123dfbeaeb9a3ede6147246814630e5304b50a6585894843790f5d4c818c3",
+    "Name": "matter_g1_multiexp_38",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005315310b8412d62f5d63fd996e8c6b14aaad5a6c83eb3505a28fa6bbe469f7a7cfcf10b49382aad4d6764859ef4910e0000000000000000000000000000000012fbfd9ee8bc712354fa3b73e57fcbb07231aeac980e99d5843fdabc081a159bfc6507911212adafc162dfc21a5afb739c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c0000000000000000000000000000000014e14689a5ef5b9ee89369c5c0de07fbb7980f37294a0e7570191b73f4406ec4bd9bf4ca2521f8d90157e9c3c7d4211900000000000000000000000000000000040d06da8127e64a71532afc8846bd7eb6fd5e845ca0f1d96effe0b12a2f8afb121d7fbe89f632262ba0e382e8204701ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000c5ece364affb6af365a4c7506389694b9a10f3ad6798c326852fe85a892014b6901d097aa8910256f47ca1d4667b5a20000000000000000000000000000000003f300682da34e22416f1ca2bc3430e3b153c95773c8c76660603a0ecddc20ba570545d9307a6b0910eb406aa14d196bdfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000ad0508c3b4fcc1cc608d002b66bc703cc16182a6e83794e4f3739238c3e02fbb6387ceb445791d54321ea52f779a35d0000000000000000000000000000000009a442ba572cdd9e658080fdf1753670c27e88fa894c307eaeded6ead17799365d1cefd1fd13f0dc321c0e881a4965d3f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c30000000000000000000000000000000018825670284d3dcaa90a678ff37f23e8ba36307f3c1146a8f6c782f7b43ce16f281dd346962904684c22c1980a772ffc000000000000000000000000000000000d65166eaa6b4ed79b5ddcd7b44f06ca1bf8b960211bcb17d5a26a8595a1ae1aecee9945a674b92384ad05f2f0f64fb6c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000098da5d9289f26b61486e3ea52b0145a47847ff2b9f1d2756e363e5ea0bab27a98fd01d633a46ab48aa1d2f1d2886f9100000000000000000000000000000000191412a43858276e4d7e69542f9e6ba4fa9bc0a8784df590aeb1e0d65ffb56cce0031916af640dc3e57662f5e5203436458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013a279c27bf2234542f4ac0e4c2676b41b3cdfa1b55d5c0eca1c686589c37ac63139a7f532910fefe275a08ce2d37fe50000000000000000000000000000000002f56719390112560fda45943509729fef3eed60215190ca1f90143a4d2ae6b41aeaff7edf027f27857d56bae1900ecc804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000016489ce6e2b8298e2fe0836556875156502d36aaac621e45514ef03db87631cfcd308285fdcf8ca7ae8bf65bf53a37b3000000000000000000000000000000000b6c8fe0db4492a309148c54465ca06c59c7b71e4418d8fc1874cc338df40fc1355a523387187402b04f5d01b5e5b82b6f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000065358f885a974a1f64ffd526e5ced18ae5ebab2ed6c9719c9f879adc940292ad124fe5b6c8278c82a33d1ab2a1916130000000000000000000000000000000010d019536f727f8ae098dd9ccb6344417042855fc6722443218d83127cd2b07a6816698dc1a48776d2cbbc937f83163dfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000004248c5eb514980da698bc5146fd3743f5b1a458dbb17edd38f65c294e48bbd55e0d9afb3b39df2e82085fbc03e5655c000000000000000000000000000000001830c1d21ff8cd1ad8467ae0a8d2a34367e7c44829f7530263ef3d7d5bd9eef76b756f475448c308f4c03453f54b43cc1aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000000dbb72f9afde915110f2483c09291595c369f0b4ce2c91779da9266c9f74764da4976a221c4997cb940302ce0e59ac080000000000000000000000000000000012de4b2ca14004be2c64ada45e9a0ba7989ea0e22d0407088a092cad87b4e26b33d5d8f96fe6831e085c6fd27901af61c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa289000000000000000000000000000000001387a1edcc34afa05541e15e2355d3cdefbfe22ab7481e1f194e461521894b97b2e18c9fbab1eb5d8e508a0bdae08b5a0000000000000000000000000000000016c4ed675f20aaf2c825de5bc4c11ce1e85a0b91b08577080108ab7b52bb674f78943a5f619f557b96a72206cc1bd447e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf000000000000000000000000000000000498481301a55b2d1dc95f8115534b1baade13c2cc4d5bdce1fe8cb1734004600a2359e5dd1c61c7338275e2f4fdf455000000000000000000000000000000000a3d2ee413b7e6c0e32e51dcb7d124be92990b7e4307b9b459da1db20f85f4a35964b7987933634fb62a07f797b00b27fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf000000000000000000000000000000000a885ed8c3ab46b60a7d2e198b6e8d069ca8f7e0692f2b8ce99df2f44979b6045fc17991bfc27867be79e2055cc8aeac000000000000000000000000000000001728864f0fda8476fda4df08fb6aa9e40a01dbf19a4d22c4fa0c319d8496d405f0a5f9c79ffbdd5a4c1b617326f3d774dc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e0000000000000000000000000000000016edd94f91c43f15818752660e4737071d44edcec5d5de426141966a9880bb894f3566e98a05232b9717bf85d66a57c6000000000000000000000000000000000a789ee6ecb80e2ab9c6e7a945ae4839c620f9a7bf430ce09b57a64479d5a10a1ec0a721678b5bece737f0dce97a3a56e4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a0000000000000000000000000000000008affb2247059dd4bd1498c8e229dcba313b156e2f420fa55331e7eac93d44af55a6c02bf2101d90955b95ff6fcb411d0000000000000000000000000000000004759596f12f17d7bad24723ccd6f86c646a39beb2aad35ae5a219ef57e1ce6eb310b2098130489421709bc20b4a53d703f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c",
+    "Expected": "000000000000000000000000000000000f841cf3d8897108b4a57a7802a3cf8a43ae31e711a6258991b6d5b3851e9e0d759fb90899e462828ff9cf996bbe9ec70000000000000000000000000000000016fa655a67f441e967d3137f6ea8f6cf636fc1a7bb662b1e22f87397e0c77f34e015e6bc124291647727102a12561dd8",
+    "Name": "matter_g1_multiexp_39",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c98e02c9f7784d0dbcb4a49c97a9365cd069817d57cea3042cb4198180b95d141c5ba4d383de188f06faf8f845f78110000000000000000000000000000000014be6f602cd67fc2d147925cd6c90457dd253db766c4b8f737cfca02ae15b47d5798c621091c4be71fec75e0b8b1c00feb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea00000000000000000000000000000000077bb03ccd915742dcf3c2640ec61f05bbd70987d2dbe9641e0e34ebed3600703e8f9c382e77f99b70c47f54496bb6840000000000000000000000000000000015ad452396c23e820d1e8a8a9cd7557062ca9c627cc7439d43c528e0170e2760e7761c9cd872141543834c89c75537d72b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a0000000000000000000000000000000008eeb6c2c00a9f95c5b238290b06a67c1cbe0e96da246537c29c0efa36b53230c3c5d91e3fa9d129743e5a9d87e81d0e000000000000000000000000000000000ede1011370a956f240419cdb8a0c8ae869c3d583d938ec32e29c5ece68ec8be0e69296ff0c97aacba59991d65a25563babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db00000000000000000000000000000000179776b08cf2da01a94bfe7be4b89b3308330cf797906f85889b63487115b386c68c8518158342747377fbda82a6d2240000000000000000000000000000000003e51d69bfdb73a2abb469b379e2b4825423d2a2cf2cec62e2313a76d260be1b0f2892bf82e5435e88205ecc9424275d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000000af478b121104742d0cd13473d1b7f647437d980999cbe7aa8d2246148d970136f6194df1785027ce944cf9ba00aa4f500000000000000000000000000000000170e9f798184188cc21b0950e0f3a570398a97405dc87a2e077af96799960a938f363d216474422d8f4762fe5893ece61a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000005817e3812f73d3d236e45664af8a4abd2d4a44f741c3c1866588c2bdd88b11741b1c272b68e20800abf3adad7125a400000000000000000000000000000000008dc859c2323f0d2dcab76bd8454209c86685a971d531a32b00985eb822d33691c2524fe25d14ca386047a4976b9e7159b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa453000000000000000000000000000000000ee06092a2ba4c33f5c9dc6062d50e3b133c7fde5c81056f74a2d869e8f92310f07629db9cc2b755f12016cb7894aac10000000000000000000000000000000011714a54e236d1e13f9b649a0aaa80cff9e093342c71a8dc9ff1e2d4e95b0f6b4219ed847ba6620d23feded7d95944183b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f6460000000000000000000000000000000017f6061908e62edbb8fc5498eec23a51c861815bc1b437b7383dabf303e6a45d52e73f8363addac61974043afacb02ef000000000000000000000000000000000f3fc04d17d801741f3583e072110b327a3488135659fab2e8b1d2aecf4694f6d168bdd60624713a7c2c3314f8309079f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b000000000000000000000000000000000db7d958b44ac5ff3bdb4991dbcdcbeab36bc6d21d9e0c8fbb1eb66601df227a6367ccc783a92c534a30b17be462b95d000000000000000000000000000000000424eb0d9da831c658ff048d3e9ee43a900bd1ac98bee97be073ea55be1dfd07d425e0906779f0e3459fc69d316599e56440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af000000000000000000000000000000000c9cf785be01b7f4bfb0140004873d0db4c8b1387dac0fec42c6ae1a72123ea5cdd2b8c98c69b78d617b16c48ebfff2b0000000000000000000000000000000015c4856f183d26d13196739d9b9c971af111b4905b669f3e46bbc8d8c4281cad1be05e9ac28de0a98031923fcd1f5aae43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f0000000000000000000000000000000001802675ef47f9660d5969dbfce973c8bb3e6b2a2717fac9a509fb3c7ddb272db86f283992eb3167145f2e496002fb1f0000000000000000000000000000000014a5b5d966ff72e036c51686dc6a9f39a487ab8adab6fa4a906f28acc67d64576fbb3a00cefb7720f42ffcd62fc8adefa40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e00000000000000000000000000000000076ed600ed860f16ec5dbae3f09471302bf85fde7702b3376b0d670f93560e77699bed969e7001570f44dc5e37aaa830000000000000000000000000000000000c993a8b08d2eb00bcee05e1c09e8a37834fac53643643402f60fbfe2cc7d795f5c68f3d6a32c8604c37211585830426e03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000eaeaec30bd8d8dd9ad4d38ff97e08706ffbe51388a93967cf16155b10d218e5b1213c29c8054cb778a0d3ad22d32eb200000000000000000000000000000000079e5f2bf405cf2dc79984ddb3f813a07225729d4cae8ddf7536e9240fbd0480f6b66321749a6a9286cb07758482e7f865cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c10000000000000000000000000000000009a0933829c2a3f2c3e93f58551e7572ecf6eaa7857aa899a7ff0eeb15ccd601559b9ff844a177568632bc0ddd6e80a5000000000000000000000000000000000b69f23cc1556385897bb7457a706cdd8539a3ed3e7fa504ffbd95abba1e824dc77911efd1ad0a9c37e1a41a76ad38d13ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000d3c4cfdc03ef5fa066be3c26744032e5a2045746cd303b6df542a6133c671f4d25dfbd889840fd624125b63839a1aaf000000000000000000000000000000000102fd619ac946e99c765010a4ac392ab907c37b31f628d6d58c0ade093ef394a7547de36ca0630820f4b5d857dce449e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a3000000000000000000000000000000001589cebd579c2cd31226245f1dd3e428a76c7d0012f8dfac4dd3428a716d05a0a79763f0061d3b5846dc29a8a006a37c000000000000000000000000000000000bdf3425e6cbe628f9223930cb74ace4358e12e5d367a3604edb05cf0f0cbde84346ef45597bd61592500583827524144c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d",
+    "Expected": "000000000000000000000000000000000555fd5a7818bbaa7e786f11eaf6f8620b9686b76c6293fd91690a4d181c0b470295313294589daaac429607b0020c9d0000000000000000000000000000000009c3a53113a657a5f7e30ec28056455f700cc7c3d40cbe4219dac00980675023bfb7462e634c8a131493f12725a27d5a",
+    "Name": "matter_g1_multiexp_40",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002d49464783e5ff91aa0dbf6827315dd308e778b3da5833cfca3b6431ae784193d915a566142ef347b6ca024b6f1695e00000000000000000000000000000000029051d39ea4369a837d4cc8cec1eb8f9e7f9c3a247dcf99dc75eeae43378b4b9c4175aaa5eb3f7abdb1afc15bc2076d5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000001363b8347ef6754f61520942fa8cdd07e6dc2b72cd40ae41a23622be239ee25834482533ea7edb9cfd5a4e21e4f33f020000000000000000000000000000000004495e8d41b145ca7f5268e66c03528c8d976cd650d815257906e46c1f9a0827e0e79f5a8c2906ec96718538e1da3b1d2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000007735147af3bbef7cf0c4a7c8f1dea302a5e4edf01d42c1e484f7fb1f4b8fa23b8a7a16fbece9270d8786016836bc979000000000000000000000000000000000053406bb3d2a4cf37924643a186a56844a4e77ea4c9e9e2c707b5f947ef956369f400e448930aef7135449f8cc51ae1530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000133b92eb9f9a3c6cba655d5f26f396dac467b6444657eb0a811dc6a58ba1898f24b336f4fe9b11c1e0795891b00b6c150000000000000000000000000000000018952f3a7f8aa78a8c5e5bd96ecd5d2b2f237916d8e2982c40cb7498423f12c6ddd3cf1afee75a3e2cd773bad7ac3bf6d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec04991300000000000000000000000000000000051453a8b81b0b0a1566540b3026e40676ea48e3c5aff89ec4fe3b36c61aea27ebe01fe8a811fd3ff73eae0a67027cfc00000000000000000000000000000000090b399b1e5af056b428a4c270eb204df4999e53807d34ca750f30b292cd38030491c3d1b0e08600f40a16f707b4903242a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d600000000000000000000000000000000142bcc3458437416506631c4dda54572b5d66093ff23f152957350a3aaa462000ab000cb8e9c9b23a17149b5d012adb0000000000000000000000000000000000734c0fe1df24449ef498fbb60558010093cbc8a14ae068aba2f70bd7718e30450411a81499a895e3d84079a9dbb19557a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad0000000000000000000000000000000010a7a3380a6d8b2bbf212da72eefb57d2fc2305ce222e8d908bb572600bef7ff55b1df6a9af717e1345967cc18e779ac000000000000000000000000000000000c5a3aa84b489c879eddd3c20df6d510edb5e9ac5c1a2e42b770571ceec315d560235b27468299e2e60af3ac1283be12bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b000000000000000000000000000000000035471ee35c187e24cf0d113c0ca1ab6322528153d0687b15953c39290ec295c0dd4197b72448f2a692537064ede8fb0000000000000000000000000000000002717020e3369b288314a42fd8ab6c6ddf7007480ebc4fa094ff7c4c4b750f477917caf071d2f1897a826fe870c2b7dabef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d000000000000000000000000000000000b501cef8ea57ae253de63d81998768e115d58b353ac1ed6e90d24f8c39a31bac1a5be1b535a1dfe05e72d80d1db8b0a000000000000000000000000000000000a3b62c001c4b725f7cc861fa042c31fde4e77b3b0610df63dcbb7e89d3fd746919c2bd8ee4d623838a05d42b6932383392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000011cda0c937d8fb2b21174ff3a5b88aa5e1c9a8ce6eaf26cac9fb3ee7f3ad20e74ebbe2d1bd9f4faa3acc43b6e6d0d70b00000000000000000000000000000000195257a442c8e39ee6b72cedaefab0034f48bb988a3355ad07b3e3e314800b2ce30267dad6ef3fd9dccd7d2318dbce0a20a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000001630ea3c7f910ee8574f29d652e86fe3125c306218a894df0b4688ba582ea7d597d7e62cc2e7c78dc2db289f587f10ce000000000000000000000000000000000d2ecfe74480518ad4f5ded701afa68040246a08df1b8dcfe6fdffe77e33c6bbd37192c6c41c6ab5af506ba58d8b3fe4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c4725880000000000000000000000000000000017a910c111d7a0f7e7a3d48b1cd358e2a1213edc077034b06d1e96beedef80473ec17d1c10bc2d33d4fd2a8c052d926900000000000000000000000000000000040167897293a68c980bc34b3f79802b95186200b40b4763fee9cdce8afc681ee916042d619cd51361e6e02688b4915ac8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab000000000000000000000000000000000dbca3203ee6c7fe8d6504ad2041aad2681b889996bbe28ff1282cd20da563dcd5c9fea5fd03072134019f579e4ef7af0000000000000000000000000000000001317a861403866494eef2bf59519f2d324586e93a0037d07312dd8df4ab844525afdf4b70f9e21a6e0230bcde35db4d8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef200000000000000000000000000000000069ea1da08dce1c1239d49411861d3e8ee7e6082d9bf8ff0aad1cbebdea6dbf82fb0d6332ae436327440b71ce6535ed500000000000000000000000000000000079904ab7b16de5812ea3eae39d790aad32db02c9cbf7b8a3a8d4222d3baf710ba1cc5bcdcf4fc9e2c4567992fa911edd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d00000000000000000000000000000000173ca2615b65e574bd77c8cf55bb116462a7ab9ad4a3879f0eefe03f1a6c0d30feed076e0fb21fc60ee9f270af180cda00000000000000000000000000000000179351092d68e7e0d428811cd4503a57bab9a4072f1bd27b5e8445ec0058eb46af58c4752601b53714b816a4bd386048b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000016c9d1fc1790a15985028a38e57c87cf010c87bdeb2a288a055b4b08497abd1d616fa8b28d6da8cc23047e9f8bbe6bec00000000000000000000000000000000089601933b759bb565d849c3837570feb39d442461d764a22f993a695fe1c55283b8c7db02694aa66032512d44dc88867b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f",
+    "Expected": "0000000000000000000000000000000018bda18912ce64106fd3d54ec2024a1d3e4a807d7bb8aaff7b515d75c9934d4729c14a4a72ca7654ca811a69f09d170b0000000000000000000000000000000011478fbc5c03470d9cfbf3decf9416e1dbea8a696636b94244c5c637e43f81eaed0210b1cbcdd291094e8581dba3548e",
+    "Name": "matter_g1_multiexp_41",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000446af4281a01e0a20b7428d06b63b89573912955971be4a5cddca514708419640f8a7f95b50ef8714a04e1fd81bec64000000000000000000000000000000000087b94d8493239047a5cef74dc20d7708d7e3365018df80624cc5511483c3a5d9b14ac3d4aa391da60980397e4fb1e96f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d74100000000000000000000000000000000033e5eb4bae80d55f512a48b44054d0efb8af1f9870fddd99df00f31dd437025381df3f4023ca217ba924a961864223e000000000000000000000000000000000f6d7a7371eddf7283890d59bea3c61fc3bff19eb7fa333ae713fb9a73c4971354474986ef5a9a81ca8c5b38bb67f58d5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000013d4e9518d398fc0add8233fe58c198d65966844fe286fe657891245fba8f37665e2bc40e4e70886667c9e2c0a1c245300000000000000000000000000000000089562c10b287d4d66b2b694d29fbac936f700de78525e9be59a83543593b42c5c577910e7ba1b67d840d88e7a3e53fdbbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce50000000000000000000000000000000005762727639503eb63854e5fd3de33bcdd80227e16de19cd7cfaa10b7863915e490087dbb980b6dae5114df7d56716d300000000000000000000000000000000104306b38970a94b5c8839ff282883b7c88c7ef45a7ed49a02b322a16521faf2b881e2dfe22da3f4472e2bea9fc40d7e607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000056e61b51113719c1829d4ae4361f79c543961de801b1a62ebbc3cff04b0722be241236d4e1b2dcf7c309ab9735334a700000000000000000000000000000000031ddb45e491ba2d719b1f72f54640c63e281dbf6ff84eba2eaa2b781d87e243e7bf84d7151f27556156970dc8a2407f41c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000029eff96206ff45ea9bd0be2b83cdb660d6bb2d236971517b962faa54535f01097327a00154bf35dbe47841eb36417020000000000000000000000000000000013734f1218c3c34d2780920806c5ad211128352d8a41c2a1035594f470ae347e372914827775094164a5db9d0b2a1ef7c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c000000000000000000000000000000000f3aa9f069b07cc935a974ad4eeb47e8b0083397928e8102651ee54f53005625c359d82fc8b5dbe1c76f650cdccc2ee2000000000000000000000000000000000e2bf6a8c4234d118676a29f12daf244ad9aa562faa970d2d63feb074946ca70da039e2de104f1524b1a8f3897f053f4c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e959000000000000000000000000000000000795a2b6b27209b48c00cc8d37864f14c6be66d6a41038122a28186d7bbcc4b02f531aaabd000fc93c685ceeb67bc3c500000000000000000000000000000000143926b42a6654e439fd01883f1ceb524cd8b5b1f2e3eed3e905f6e948736790cc1325d1b04e30247e4971b75939a766e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd200000000000000000000000000000000074096150c9e04c082a1aea20c785b3a7396568e43707c42c512575a97db8127c8c1e0548d640dff8821d7d235f268340000000000000000000000000000000012dde2f1d15c04292bb5da4c467cd674ddb43e401799257524cf3097d0dda1f3c9f2f0637cfee914a4c66d737f9e3278651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f269000000000000000000000000000000000109196dc59d6ec06fc4c774f665612c11bc3e826ca4ba528a15c6290f733f3aa1fc441bd896021471e1e85943fc9ec2000000000000000000000000000000000aa0d17d44bf354e48275ee3e4f06291e242402469be6f4cd4a62ad3871d878c1d27a8d06974c5c1138281802368edb01a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca000000000000000000000000000000000067458ca402c19488e2515037abf9323ab8288e0e11f7cdee18b3da50cfa377435cfde1f63dcdc451ce65a05641cae370000000000000000000000000000000010ed9c895629bdafae66ea176388be4e4ce45cb13ecbe0869ce57f0f48852b6b8c47bcc4a14fc5327f1df372ad9f5d4a7db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf7000000000000000000000000000000000f393c5fc8e5f1cbc7b59742e5b6236c9d1d262d0b736c1bc188ebf58f954bf2835cc70617062a01459c139f328c912d0000000000000000000000000000000015501635aa7565045ef59067e0ae91a5ec4871485ba411425987d540bcd7b5782aa7164dd631e4c7896b3949cb115f9a14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e600000000000000000000000000000000174b0620cb49d8b1a5798c3746046c2888c8e96664dc7bda5b4e90336517448eef534469a40086703d9a835d2a94930500000000000000000000000000000000033db9968fd6322e7bbb9de572e8c92b5e3717a9496803e3f6ef8dd796dc6487909ff318ad6d4d91297ae6f2daf07bcbed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000000449ee22d2c23ec02fdf1751bb59feafef9291d6d56f7120612948875afdea56453e081c5c5086205ea83f0b8cd541ca0000000000000000000000000000000006114d6d8ef1e4c6d79b23a2b91e5577323107d90523001cf7d6d18a0ecf3b414d4fe1a3eb831a6d907fce9d22030bcc30fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000000c2082409ec14f6121de6ebdc06656a28dfc5e439a0278593dc6aa845e8091d8caaef45ea1ad05aa12e3c1533275a663000000000000000000000000000000000a2ad9980247640d44d3b37c7b7b2c1b57592ac12cfe9aabca4f88ba90c8b3221a2b9f5e4ce19ffcdbbaf99ffc584219980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000108ea3fbf78237f0e90d4addb69f25eadb0f21c89d92774b4fdcbc97632f1622ab4ab408fee95e735281ea5da5c2c8130000000000000000000000000000000012338527c7932a737daab3f8de98b9f2aab59aa1b12e84d3674a8ddbc1f86a8a9e7eb0ba854e9564407aedd489b6016c619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1",
+    "Expected": "000000000000000000000000000000000aaf02063d6b5b395b75faae4039cf2eebb69053f1f161242b47854cf07786788930f3be2598520c178301ae0bd13ab80000000000000000000000000000000019574e1de9161a11e804d8077973c5ca70ff7925c847d870cd2bc985a8724d41331fec6c1cb341f7509a37371db9e4be",
+    "Name": "matter_g1_multiexp_42",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000048708595ff4f08cfa2b1c101ec7b3538a2e6044157bf39a63255b5540211105f680464be5b03256f9153a90a4e62d44000000000000000000000000000000000f2fad0353cd8fbcf0ba75a403209094d88d8c8d068eb0c7077b8263fd9f7bff8d6234d75ac4da232667b5c566604706119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc000000000000000000000000000000000866fb774b231d82a4508ff9b017ab836936299954b2b404affea65f315b62da34c76019192f5c9a447dba8cc1b9075d0000000000000000000000000000000004e050fb7a17bc738a55f1ceba48920c62648a27cf438b770a66166522fb0929069fa6f2b2b742ed689f554e9023ec14f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001710b342a52b0781d1ea18a9f07d54fb18e9c90e44815cc7509aca3a5c9ca3cca6bc32ff6ff726cfa353faca4f097e9f0000000000000000000000000000000017fd38b122a7ac39533af597b462224b86370f6e6814ca1ea71d961b9c7cf94b952fd75502031cde0851773b2c6b0108e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001fd243a3c69dd5e7ef19cfbd9b7cecd475e88d7be85dd3a8f48eb46d5dca39d05aa4b43c0c700b6632ebc0b4cb3baeb0000000000000000000000000000000008ebf24e9d2de0fd82c69e0ddd1625da0367c2e9f975118dd2ba5606d77de377be10515d9eb921be5136ed25fa6b27abf7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000021f919adb62791296db3a0b81f03b87c01d94ca312f55cd94364eaa654bc47684d7b0336a3afe813ef1aefc7dd0ced2000000000000000000000000000000000b40dd6bc2fbfa2ed277d88f77aded330c54c1c46a781ccd039b270ee9b799a70855ddb1201dae29a1b124dde1e6acaab1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef00000000000000000000000000000000054f284874c53bc914040e6751ddd444604d34a38314d8057fa0f77978150fce0add250a6bd8693ede79c9f6b2e025de00000000000000000000000000000000045f6579793d166198d73ccd03da2e907efdb31b54b0b0fe3e2f1e02edd7d9cc0c08af089330d53aedb60aa7cafb0e0ca41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000189aa0df86ba479009d4bfb8608c31d3d49f52f1bf758e5c05ee9e5a673bfa15e1c6c37a978c4c431ea035cb7948297500000000000000000000000000000000120c90261fe77d6f41a42a170b28df1c9e6e0cc4bae247303f399d3be7c6ce8319a43e7d551fe554783ec5ccaeba3bb363cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de0000000000000000000000000000000005e990869491ce375477b586b63641ec71adf226c631a14ebfae3514718ce546987c17c9ef41f9005c10eb04909a74ee00000000000000000000000000000000141b8edf812a2918dc9a2242301a7e7f6433a83298be9312cb48f0d3f0c819a4368ca961a0b6f09f9e077cca6111657e6a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f0000000000000000000000000000000010f3f93de5573e42ced8278a7a12b58086c04f8b862e11f256f26731560e606ab81d61a1090857eada5f8eb3afc363c400000000000000000000000000000000111915ab2711479677489dad7695cb02626a0525ae9ca51b5271d5fb6ff438d99730369654240b05b5d47fe00847c6327d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec0000000000000000000000000000000000ca6c2f2191cf86c596b439de0e0df79b441de41c7661d4b80723f14337a379bed9b97958d225700f06f8be5401399e10000000000000000000000000000000015904391fc3cb879147c2b5192641c4ddde11ca8129c3a03b82f5f824b2ae60b3a33c925112d2de94ba3eee10761da528bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000fefa6dadbcd8edf2861c6ff4f5eb501a76507b1fdc1b8cc992226a7e5ee17ea343cff89426c409bc36c2aa3a8f5793600000000000000000000000000000000166706cd1ae090a41ea211d1333d360a1e34dde717979295a0d6a870932f31158e43ca041d1978815aadbf761275953163b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000008f02061fbfe82eacd770520b46ab49bc29bf358468adcf904854e39b30ec4e363e80f18eeec8064947bd8612c37493a00000000000000000000000000000000138888a1fd168e9c94959cf026605691b4100a828c3a75ce95f3dbeba2a21d8a44dfaaad834dbafe28c12154f41f652e70de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f531500000000000000000000000000000000002a07974c00de6936c31202e2b0c76c30ad15b6c42393d5c5d2b1e0d5eaba8b5680d3837a8029283f572d43d2944e4b10000000000000000000000000000000013fa3f905a5618b7aa3ee5ed37055f0472fa361fbe07733f9c500657338c62bd4cc3b0b89e8223894f365a58100ee35416732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000000775861019fd75c201b3a23141c8e962948ce38fb0f15cf9d08d56ce0dc574300e0a6ed90a7c50b8c71a1a9c466d16200000000000000000000000000000000066ea30b3a1bd410e3c70b1173b91d3eb9fd0be55b2d583c4be627c3aa9cab1b2a5fe13ccb37d781965b1b121079916c4a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc24000000000000000000000000000000000ec3ae37e6e5b0c623534f5c02d998bad139394daa28aced4b9f781a5ca671a02f1638cddd3bfb5124f9c5c830cdd9e20000000000000000000000000000000002688ab0be331d6f8246a54749c54fc111d2f7414ddcb1f3b42724e5bf14cb8ff3546a3b9be6115d91f62af8c3eed35efa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b890000000000000000000000000000000005d5e2230d538b05b690e878c03d793fc70c391e853b0ae3609f81a7f24aa6d5a67f3138308328783888645d1d84a15c000000000000000000000000000000000d625eed47e245ee74aeb91fbd72981c4f2afd53deff7ab478f32e2a8635431d9ab9848f7912dfa4bdf8ee7201ff418bc4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515",
+    "Expected": "0000000000000000000000000000000007cff832bedad3caa9c91ac59292828e79811f91c51c4799e2752ac0c12e2c47a2a07ad0521097af7e4a939f3fd230b70000000000000000000000000000000015037ed0ec4981737fa94f276aa9a4325a2267b90f34844f14a92d677060a23e23e3ff0b3df77e7e67e85c23b28cd44e",
+    "Name": "matter_g1_multiexp_43",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006984b92b5b868004f39ebf04f41084d03704732363e65c823e5ad4a0a3fb4c983ff9375249bdcc2f46650921031bc1d0000000000000000000000000000000019b9d69589cd29a9909af5a303586aed5e33650331b9866a6d959b8580ca8312ad0e96c7214ad50db7502f50ecdcdafb0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e1500000000000000000000000000000000173433f7025400852ffdfec020a44b545b365158ba8f919f434fcd995c0d84509c77d8a05405c79953b8cb667047690e0000000000000000000000000000000017d73ee336ea56efa64038b31d5abb6650c4c6f7efe67add40d09faf93fdd9fae44732bb69dbfb0dc8267c4d01d8aaae6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a2700000000000000000000000000000000184ef5b6e309fee5030e2cd8c6c3ec49b1cfb09cc9cfb349ed47e17409d9c478e8e54f285a3b3a4025464162b172d33f0000000000000000000000000000000009b78ea5d2fd2113a4bbcbbe6d0108bcf27b60ff435b5b587e91155eb0ac6ea35c27f276b7e11fe5fb59508454fd8bd24e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000d0b8aca446806ab51b4a49049cede15587aae742ce7d80c2a05d255429c945d1337b4fa7ecb8f2c3b7c0b0299a41ad8000000000000000000000000000000000bce866df7061aa4319336ba1f876254a8e0faf3faf2f9ffdafd0ebd7d7d0c854c61b476583207818f484ccf7faf90fddc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed70000000000000000000000000000000007018908a64fe5795ad178b8bb1c8540ccc5c78ddccf4e6cbae72bfb84e794d23172998d29e568b186cacfd025962a010000000000000000000000000000000004751f7d225407a8d68b4a196e32cb4c0bc6a9ed9f2093e4242b268d6c5df978b8595d8940f59be860b66310bf8a5460f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba74000000000000000000000000000000000f61d66b0539c7ce56da9308d0ccfa9245158541b2d1b14c381ba53471ae9944ef3ec9f4eaf52c95d5d0bda92d6b9a47000000000000000000000000000000000612e57aaddc6eedd9b8a08b991bebe6f5cdf7805c2cd4de5853856f11eaee94c4c2e0799254f98348cef63236cbae3980eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd5107000000000000000000000000000000000b8a8cf0fa6ea9f3154eb35994cfe2f7af4252adb8f26d718163f2bbee3cf1bfca400f4d3582fd5fd407083e0bb48ccc000000000000000000000000000000000c3251d0d9e8520b3e7b43acdef58c75348786654103fc770c7ffef8593b169bba3eaa2686791f919fc70f40a171bda8067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b10000000000000000000000000000000008138d173e3e8f5e63f6aef89cf2437690dd0c848435f6032f943ef6cbca87bd2a622f9aca825b7caefb497450dee4c200000000000000000000000000000000183379ed3c9a6a6904e169c68d627bb828a05a93e38ea3b7886db2fe6d1015319d3887136180ab7dbddaa26b1fb3335f34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a00000000000000000000000000000000083f8fe152f7edcde2c81107eacee9c58ce22b5aeb10eac15e7df1657a813c98b182433655380c9e8ac18efff2188b5900000000000000000000000000000000100b06f6129bd9063d2841f4c244adf2dfead83e23f3b1586126623ec35674ecd6422efa0e86ed0502a83549551afebd8d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e1900000000000000000000000000000000072c644635936a91dcaee40e3b4794e634c315a39a9cb5cb99ef6784b332fdcfaafdc80e228cd19d0104d5796f584c350000000000000000000000000000000002318bea9077484e9c1937dfa63774b5ecf6fc63ff06e5cb653553d5111a981c09c907069ffe11b5704ea60a9987328329ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c698693000000000000000000000000000000000aa7249112c7897c9b1f95a7d8299790a25d155dc9ef7b1ad6dd7b186bcddfacd4c77ee95e634b5f283c8caebc00b9c30000000000000000000000000000000012e31211b2bc88c568e08157da9c3e3220dcd563cebe44653ff4d62f8c306ee9136832704272317342f634e66e8e66a240bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000003a5576b3663114b410276a8c537a93f790276754913df727ec6c0a684ab3c705ec04b8bac882bb9c5223702860885520000000000000000000000000000000002221eb21003c6512428cccf8a9c775df9b72ed8810dada5c92463e6cfa3d619f22a22e314b9b8882c9e2f609b73353a1574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d700000000000000000000000000000000111829f79d4ec1a80533f76f32503cae2842981e29ddf9a376d16ecd7037d3e0dd1f8cc84d512fbb39d58564c019a559000000000000000000000000000000001808e65ee7f31a1fc15d187eebd76c63a3158469099bd6acddb0cc96354072f636651137d060efd850fb599a6965044e6dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001274417dae37cd33b2a3e086f327df292b6f997e5c93e71add346d6e5f6ded135c8d6047978c10c5c38752006b7f76910000000000000000000000000000000014f867c58d3be7b09891f087f47c1bcdf82c16f899ba960d8a0db4a5eb66efde12dbee75e77816cf9afd4877d9d08f32d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f423082000000000000000000000000000000000d98fa6b2371f65f6f0b62133d1a294a7faa9949c7df16818657a9757fbd8381222cbea98a72a951e4b2b69b216f705b0000000000000000000000000000000016331e8f0661228b1e5f4df59a09de5133d16e06e1628afaf8b2a1160961ed9738400078bd79cb5bada5f99748ba220b19d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb0000000000000000000000000000000018f129d1799d9b46dcea6d239679eb64f144adbe1a9561044355cf66b4b1158513406ef4423468b5ae446c4128dc03d8000000000000000000000000000000001669ead3f97913fe5448bda1bb0be354fff223e51bda5eba9743526e964247211e9cccf75e6f99c6abb5b8912af94f5d3f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6",
+    "Expected": "0000000000000000000000000000000013426d2d18267fa615975295d2029d2e5730fc845556d501c8c6ff8442cf0f3c7facfc329f6703043bb2d45acc1639130000000000000000000000000000000012fea8316f8eb7cd655aaf9cff8e395592360eb6d62bd42f6e1d1e27b9b54bfb7be5b56791d5ba55a798f073f9b5634d",
+    "Name": "matter_g1_multiexp_44",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018eef275d602fb152cee956b04313bdbc8f0079da7bd8d6841fbff1f63a9626f17ea3f7a8332023fd9793ed2eff3990f000000000000000000000000000000000c41214c40c5c65e79f561b59d7ae103cf8c60f78c2b0a16d96a2e714eb30eeb0cf748044bdca077c4be5f4ed155ec50cdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c90000000000000000000000000000000001e5db25f5964e3a5030005152fbe9c00252e37dba6febdb7441046f734d4b86d60334d91960b09bd32d258b7ca2662b000000000000000000000000000000000949bfe49b0256a01da76f5c2270cd0b6ae70fdbeb55f932895d0e72d94eb6db236a8ea40d419ec6d9354c611b8010a918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd00000000000000000000000000000000158addae39a79638dbd1a7cc280e0a24d18a4914ce4e290f8f79c147c4e817d95a01bf6b074ef8e819a124cf4e3bf088000000000000000000000000000000000bd2f13538d08742b3bc7b1cca9cb9e059b4bcff76b49a189f506b4bde56d8a58fe0bec2f8425ba5d574dcbc5efe0e93650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000f8615d47e4327d862fa64ff4b9be14df2cad729b816ac7bdcddcb32500b6523af3303fe36c0e93b638992c671958d5c0000000000000000000000000000000011aa78c5d0073fb9b34235555bb2e3f27e55a1d576ad4e3f63974cfcb2646c6ebfd6e595d46613987c0c8e86846845dc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000000fe66db078653da2fcd1490a36db9371039f3630bfa4d574cb700b19697c7194e8e44453e16ae71db6c9001e18392a76000000000000000000000000000000000cc69605c26212c6a088b9a5c2cf6024e46f035e4c64da67383f294d6186bedc18922ac891f742165e3f09fb1720d476283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000a280b29948ccda96a2a05ceb9fca703dd63c65ebe18a0002cf1c63b8f64282cf9d3d4d73ba3a13426f253d09f83ebbe00000000000000000000000000000000146f604d1e90c4a14aa6599ff5c6389e426232a2dff39334f3390006f021f83500300b7b0f1585ad591acb1e0baadcd7145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f4720000000000000000000000000000000000be58255d1f227af95dc9a818204d687064d62166c16f1de052aca69a37ae98c2a73a9a9cc6cf187128e5b86969e2810000000000000000000000000000000003f1155d7e91220bf0b80943a16a9f41e4def1d5f8ce44d95dc2f9099019a1d5e770158338ec248eeda7c5af412890cdd9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af00000000000000000000000000000000077c3ebd0169da81bf07ab1bfb8770732e4182a30504cbdc8fb1abc49f31d698c17f68de1a6d8bada62e98e09bcb22130000000000000000000000000000000000d677a33c1590cb55c9c78afa455fe2b349c465e90537a73906343aef577afbfacc8e157ea6f834ff959f3dea5941bcf262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e000000000000000000000000000000000aa0a3898520c5bc19d7f3a8e0710585dd08419b39d9bdcfe12f7baa6b4cecb50bc0d6e877ccc2518e4d0254934669ec000000000000000000000000000000001376af22bb714adbd16d8d41ab503066fbe78f799aa8c1d8958eda9e4c8c6fbe119e592f655e0c3f93455e8acd8a2bc14f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a80000000000000000000000000000000011b1300312d0ad0352ea153746f051816693008f2d0b980974bc354996ebb664e910350e28770192f59c053f3f2bf00500000000000000000000000000000000125d87c441a1dd88f489514b1d550387aaba857d5a6bef20acfdc0afdbba3e98c2e0aee0528cb78970395a9da853ffba25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d0000000000000000000000000000000006a05ac512adc0dccb74c7b4c2187763a6ba8db9e290cb0efd1325b7a463e0e14a3e7463b5cedd732527dbd131246c6a0000000000000000000000000000000001c1b41b6d5c823c05a5d6db55d7068409f5fec25986db6e2689dc6ec3e0d85749db6deb737445c5feacd69925c5dfc44ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a616300000000000000000000000000000000143d22823412da99f7b87a794662bded7b7ebad9742e4d6fffd471b1bdc748c6f1b5bb395cd0a79c7291b9e8081825ba000000000000000000000000000000000f2b98d54e293befed0a97667791ae35494084229b2a25494fbd7295a04f03173a52efb8ff9033c4615ad1185d4e9032eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a00000000000000000000000000000000172919c33fd97de83b30740356c2bb2a9c97c3616d9f80a8d8266e07a1de21ad974ea796d3cf56660fc4e0df263a27c80000000000000000000000000000000019afdfd10bb736e8a6596db59f4f9a8244e585fa81ae315a768c8d91716de32d42fb75a57da238dc597885f083049a769d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b046000000000000000000000000000000000a944d2667a10dc5892760cd3e13289785f0a5a461068d70960e6546a0543474f92d68ecfa96efd19619d976af2ee491000000000000000000000000000000000a88a16dba3fa6cb5ef21015b18a14956ec9ec29650929fbd0308fa59ac4aa389aa2e306a3a68fc04e062367a72b3f861e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f96220000000000000000000000000000000014118a990f2649838954ab911e795c453ecd0d700077a5fffd1a4f303087074d595caf1b20399578ff1e23a2cada7e5200000000000000000000000000000000145bf8164b82ca5f8f93d89ca65a894c6d15e38da2cda296a94aa1a1efddc4d2663b8f09efc3b2d78510c4dceef8558fe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000012db33b91d99f44cdc785470e67a781b4a72ae2dcfe4555efe5b7527b9a76de8e492c4dc7793ad948cb46070eb1cc5be000000000000000000000000000000000ecf06e454ea574dbb9ba6209577425a58267d01f29f8706d06018a9caac994d2dbc9c6ca9fe3ec01aed9aa2ab886c60dcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c",
+    "Expected": "00000000000000000000000000000000003747597e0f9bc39306319018fd08bc936c4d37cc6f84ef296df5a36cebf0aa46ed35ed215ba89a247174fd72fc7c1c00000000000000000000000000000000150f973b61f26aca79a3f7d1705999308a96136b08673322b4729f16b471e38f27e66996e2921cfad0cf889878c2ce27",
+    "Name": "matter_g1_multiexp_45",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000046e955a4631d1a490f92cd40ee0a31c096210ead2b307a7aac60e84efc04898da5d4d9767f1303ad5652a0e377f0738000000000000000000000000000000000afd054be493fb26c7826c9c1f62365ebb28ed853bd3a45d266f4c690a24e179b2eea5261adb0bc50dd184c165231d2eaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b6000000000000000000000000000000000ec3c20a24a5f9fa7c5754007407d1aaddaeccf3f7956914ed3b06dbcff7f15c6d487a3b71fa9aeb61352698a93ed14f00000000000000000000000000000000086f3cdb1e21cf60a7a57e7ea7e00b4698a837916eb1f6ac1c6cf97ef2abd48292ecfa471ba7d9b8688b6f0dcfb6af62c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c000000000000000000000000000000000769b870411b65a1a86dfdbbd7dbb65feb708f9f90ee73153e42f7141cc660c50f41835ee44f58c7ffa136b944e84dcf0000000000000000000000000000000005f0480b4a35dacd304d8feca77f8580f66396a6434af09b98d57fd4f9f781012f3900407a49f4e0aca8d3ebddd2a7bea1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000000208f1b01599c969333ddf9accadb24f1c8239f82f5beca72d0d6d823b59a3b8c450e25a2da32b5a8cf8c0f47137e04000000000000000000000000000000000054051408658f025572a45c731e81f3fb88d741a632f1e2acadc48a1f257a69481c9c11e655c226d8e0623d34fc9fc158810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea0000000000000000000000000000000001738659b582e3667cee963fbea8cf695daa6b811dd808e724ae77db2060f248accf645db52f9838802c5322d993488e000000000000000000000000000000000a36fe571929153dd774fdcbaff2b924cd3f0ab4aced47d22a2662ac6f415b89372406c4ea5a0a466d4a4c5cfb02ad7d93e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a000000000000000000000000000000000c70dbc5f707fb949a2e0cd57e0ba6a5d28a2d85affcb55bdc9fd24a3fe395bd78b7431175a629475c0932b769b55d6e000000000000000000000000000000000a49fcd19bde4473bb98384bd63e96508b539fb80e1e0cd9fc9aedaacba0c36d705ad16a47f345c083401c6640675823eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000011236c10b9622f4e3d468d91ba9c6c072be74aea66f5bd77411193bf2358a03fd47d029dc7b50343ef72fe9bc08c7ea3000000000000000000000000000000000b923cf7f612e800c2c52b51203e12a72d6f106c0d047d1317711954cb33d44678f509da27f03dcfa1d4482a9cc2eceeabe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000883a868a58809bbe3ec9df32f8b963030d71a3ae97250ee9aa8446a8b1a4428324f22fddbe77b338ca58de26b1ad73f000000000000000000000000000000000a49fcca1f052e82fef8913b64268a33ef1d2ee213ce96e60a3a1842aa304c63cce711bba8f523302d9252e3def20e3fc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000d1ed017ef4702bcd3bfbbcff36000af6a1d26ab363e68ea5629027e0b90352bf1d8e03c13a7955da6c15507cc1c9f47000000000000000000000000000000000e09830e54fe9eddd416479a1740f6f1b7693f2d153d322f27779b16bb6451d7657df85a55da75a4aee0a2e33b3a46e637d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b0000000000000000000000000000000001f902e2947de38842c207b9029743da51ad0dffb61615b22c73d88739d80c926c07f97507ca3bb830c66661b397dc1f000000000000000000000000000000000d8a1d29f87b3335287142baf613fceebe9d4765d29e46bbc9e459af5450256295538b49081d849f3253f07357451b6e4904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000001846aeb64ead3a9b6da3b6f5de234fdc98442bbdc402af2d016c9dd25de8f9ca09269a3f01a812187ab7427b2bf31603000000000000000000000000000000001775e3fa3bd35f96faaaf9c3ce1d2391f89340f8d533e41a1d637fde7a2cd7ad997e50a6e9437468a1d5940e4004bc9068911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb490000000000000000000000000000000008aff7ad8d3e83ecbf5c3fa2cc9a5328531b1dd6e30b2aa618aa087281202de8f4d356586d64082fb039db4c9ce6c3e40000000000000000000000000000000014196e8ec67e5f0093da2b1233331bf1e90a8fe1db52b2629c0d25e3c181d595c03bbab3b399c87236d2353f1ea6bfe9481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef00000000000000000000000000000000018ad28e8d8c1d9dfd8f8cf4e60214446a988285005d92e38d46ba32f619e982cf96ab10b605b1e378d7b46b54282ff300000000000000000000000000000000029807f431a2101ac341241af021ee35c47e0ffa1975c982f75c10ebf3ab9081d294578288a5c308abb074b3e3c756c672780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b0000000000000000000000000000000008cf05c3d3bbcc63ee761f7cab1494299a3e2274ebaebedcbae5b35ff33bca129d79f73ea77152f19cc67fc66ff774040000000000000000000000000000000009ab576dbf0e8cead9450eea0a506c83f12d09fd2267715a76eb46602756859146e96920174dde3a361636986a3d38e084ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c6567000000000000000000000000000000000006f62bad30339a1a912280ba5d982bdf0d3c04ad9051555eabc32eef501e80d996f183a990ebd17301ede13db85f6b000000000000000000000000000000000b0c4bb1a10f8a281b83384ee05be2d65d6dfcec36253b9101cec7f1193f8fe3d29333034de96dc62d18a97153ce1d153256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd0000000000000000000000000000000010afb2bdbea9f6eb0c75ddb0a4404116498920557a5d416c6d855978e47aa90da70f29519ab244079762fbf965edcd070000000000000000000000000000000000b8b62a1e52eb3805056576810721cfcdb5b0d94759a11862cd7b0a88e3ddadc0efaeccfb89662860e187f8af2039f8575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b",
+    "Expected": "000000000000000000000000000000001422eeff2bf06ecd67e0b310e7201773577a899fab8ee7c5b6ef5ce1021c9371e106c49a6b607cb3d58852f0e671000e0000000000000000000000000000000017ff4ceafb7c286f2036e6bf47a15e6695beacc7b988dc35f8521459203f69975d297160dc67fb88c4ed2fd7b61ccc0f",
+    "Name": "matter_g1_multiexp_46",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008fae47827bf8786df7e9f8cb38a8e01354ed4417a05332e45a94f93a5ec61f11d517f5554d5444001ef2991f2e7eed60000000000000000000000000000000005cd17cc813442f45e7c2fc542a6359b16db4de7749677b1575f12ed694514b3569b722ab257f7678a230ca3ccb6e0ed1129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b070000000000000000000000000000000005aaeaf87735d9e9895e8703177faf8b11bea34aaa045852c57e9b86f6283332ab633f3e6947b84784733f6f73b289580000000000000000000000000000000004957220d5264c0ff61dbeeb0d0d51278386227a9386756a042df89fff5ff9a4d3e3e52293cc94ed729d00ed3e70b1a32dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c900000000000000000000000000000000049bec519df011ae5f19c85afb3301f41f71119ea6cd9eaffa9a00f9cb901681eec5f3f694ef9b4fe768a25a55afec560000000000000000000000000000000011414953ff3fec28aabaf3d62236d6a972da12c42102911a3ee8e88e188970a11487df719a739201b31fcda4e52d7c515a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b010000000000000000000000000000000003e5345484f59b269fa25b659e9a43573d4191c3c02f5f94534bfcd63d9abd57b2f3ab92f9fc746a852b185a6ae2c778000000000000000000000000000000000b7d7648096606b0c3fb93627e484eca017b95b27a8098e5dd332bb45171793570c69fdc16caf5b16e65f68c817de3bb579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000c6afd550c55cc41cea88e670443d97c6419a295918dfde1d5490718f18ccaf8fa0cb68c42fa2cf583284cc70bfb0a11000000000000000000000000000000000f88ec67e9ba0e169ebf93fffed1fb14dd1aa3e1a2fa614a140c1a2147fcf051457cba68043efdb1b851bace84078fd64be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d1600000000000000000000000000000000143a1884ecb4121e2c1c0cf2998b690d7f01aa3deec1a2ae5542647a3721f7be47c21ca071f92d74d9c3d9027b56d9c300000000000000000000000000000000113b01f060d10d95776b35c2b294216f768a323aececb308d3de24299dc12e55fac82c3134519456660a3465abeeb5950983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa000000000000000000000000000000000019a5620f3241d03d63ccaffdfabf7e99e784399929cfc3218d6b828d7ce137c9c6cf3ae830630fcef3cfdff705490e000000000000000000000000000000000114347768e5c8109c1bd47623eb51764d4b3f63f333677bfc28b143fcc1142f4d9094b2355408cd8c412a37a4579e0706615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c00000000000000000000000000000000043c0f4b09396d4b14deb7c5027ef6cd2d426fa4f93d4ba9c3647031d557a759e3426c113fa3949cadb8b98a64bd69880000000000000000000000000000000017efb6ab8b2eaa0768bb740cc8a4e5ecbad81087cec2a307e5f53b5f431d19e3467dee84df6c6453ad4566ffa2380c9ad77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba18122727363900000000000000000000000000000000140f0efabdc88a109da948494a9fca5ff790ccd6c629a088cac62e043e00e38c4281e49173ea0e423152c5b944d80ac10000000000000000000000000000000006d3d01cd44e56a4cd62d88a22c701b42c116082e92abb629e64040f57a240d71718927aedbd8ddef910198e1bb09c6841f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000000f20ace44f4b981adbb3035e450a656ce3d8464fbe4c45b9f7035c00aef11e389cccef660dddc025786d4f9216ef60c1000000000000000000000000000000000d5fb0a9e9ab03893a9ec61675af29e88bb30f3b61e05d7c5a3d823159bf8e641ad894ebedba4bd681df789e0c3d2547c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a16000000000000000000000000000000000dd38f99872751b4571253940ca588424190bae80434a3126a7ab5ad1383c55ad769e09179d148d151506e5cf5007b3f00000000000000000000000000000000032b2b9a8b13acb6589fea9e8b8d2535285bb32ab0e519cf8c63ea3e25d58cea7f9fb27481adcb9475abadd6f1384f4f8db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c88000000000000000000000000000000000c696064b7c9653cce986e119686b2e01216faf8098d494bdf6d302c4d176b24b05bfbd70b9ea3ecc16312f899f887180000000000000000000000000000000001b5b8d333dbf1d84feaab7737d3af13d3995d3ea976d9ea1cf1d005090a809fa6c210a6363495c2b22902442fc5080b70751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc000000000000000000000000000000000082bc6c7ff7924b88b4a6cda58295d050bbe8087670bc6036b5bad53247b803306ea596ee0689d805e7b4de65a634eeb0000000000000000000000000000000010a79825c716dce1572e6e8886f1c698d730327f195871db7a9b6690e9ba1dc38e8d92b34ee32b33705edc021f42349184bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000cbd4ac75eb0928f366d3b99e05799bf3d9dbf187e557f211af5ae514101961ba750e81ede07cb5a14c49884a9b55b980000000000000000000000000000000004fdb80f44f89e6cb44b950735703653152466f30a410109a24b555c4e6907b2c1d4f54c9c0d2b7954002a74f1b65e23d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000019a41f73feae98fd65e365912f5bc6c86142380b2633feaba440a6c635ce2bcf7f871f1f033f93f9f8668360da3898090000000000000000000000000000000005bd1afda6a52adb550fd9bb59826bcf492cdaae8e9600e517d77832a8f3ae8777756421fe7640aae0bf07518ff695a66940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000ce2a96c1ac3e2cd01ee4a20258436b62dfc2efb96a7148cf887c25d635aded48d18d38da7347abeaf72d73d613fafcd000000000000000000000000000000001773ef3bc5044059bdb5100430d4936f328cf876a48bd30784c8d3767a119bdbd5f1f97d78d52afadc42ebc85f912f0f7b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e",
+    "Expected": "0000000000000000000000000000000013fcc5da42975bad80f3447a1ba05d9c6a51383921164ea3e93e983e24055f6398fe773b0e7a50d98568d49de36e295c00000000000000000000000000000000188455bd9ca4a0d3174cc8f0794d8c35356f697e62265d9e3d8e72bb2d1450caf5bf79dc5ba78a363a23d2ad58891165",
+    "Name": "matter_g1_multiexp_47",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000466047055d438bbdade1bbb00a7bca3ec0ce30b042e56afb9a25de1407d5937038e01e3c07595f46bd00cc8202d2200000000000000000000000000000000016bcc696716c21293b68d4f29d9cf675d447b726d579628417cc457043186d54f27c28b47d2e430041f9417ba323109dfacfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe2433513000000000000000000000000000000000e8eb8fa4c0c2c86d0e571cd4708361e606c9fe789b60e099278d35d169424721bc789a6048774d739a5ceff56adc668000000000000000000000000000000000ddb7d2e6094f1940dc0f41509bd430163b220aeed1b8c0a2b90e37f791410a35d682b75223b32febc95500c7006f6626546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000001080ce47aa1c38db9c71d1834c0b5d59676b0d938ba55a62daaf50911d23e286b3b813c7261bfc19e95f3bc8ea3b91fc000000000000000000000000000000000bebf539c3c03dd260d579aa853c28ae582b9c904ba2c56bb1239aebbfae10c05d9e33c8e1c2bf90553025d3279572fba9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc000000000000000000000000000000000ba078b44f92e90fca4981c66e89c5490b34f92e4026d826c2076a995269e4d4fcab419a508b530793c465531a631ead0000000000000000000000000000000007c19bb972c27c00b5b1a8731ed7dc9af8270187cd26b1b9d65cbc96688fe2f0ae86ffe753a50b4500a46c01a75a93032c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000009b70de2dbfe9af8ae771ad5bf0ff962c9f906a3637f992b08946c864b3d1dc996a2ff918ecb3c9648ef9188b15b624c00000000000000000000000000000000186a9f4c06ce9d5a969b959e4b17d4428393d02d0e7259fcbfab8898481bc97582ccd0e1d87d1735e28dde10a99b683e9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000005b7dcbe86bb6e6b328325141c1da77f8af531bf1463bf3c8c94812784314fb13e457fa461c1c51aef0721c5d6ceb5e9000000000000000000000000000000000d9d1ac39a5ecd61670c1b0d061d93a198eca1d294d2e64c3f9e0a872e7c93212ce7835ae0a7fc2a42ab5c02192d70715e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee2000000000000000000000000000000000741769993f2dcf5869b8153bbbff2e6e5d429fd2d862bdd590fc50a8f186bfb105f5d57f736b07d919bf0dff0cf4094000000000000000000000000000000001917c91f954f68c6406d6dc716dacf729a8c4a0de73e04cf0ce554eac40d750fd25b289127023af299c6f63372c01b7d4a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a2000000000000000000000000000000000bb29ce10d6e571e62611364143e08a60eee5ccb13dcb77f17fde5829ae5fc025b309c98f892aec1fdedb7d1920e658c000000000000000000000000000000000ab6fe2dd5eb1b90f15a3632749c351ec871038f0550dc54cf1bf2575f80ecb8a3c0d3c1a333bdd803e22fb6bd3e64bc5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000004fe17772d4205d7b1d0cce0db3404119707893e20f6b27138918d2cb0e4de49cd5df1258103c1fac903c1a443cb62530000000000000000000000000000000014d8246911dc40ecea823f02c0e17e690a5f66848223218dd1735cadca1a0ae89d7afbdc727158257d2cb248323c55316e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000784a1b282846404f71227064ff1a97766781900136d4b7ac73bab19cf8e03b449ddd35360fdb6dcdac80e335ac5cd1600000000000000000000000000000000074fc137d93decad1cbd4b753fe9ef3b8b3445c12e358450ff494a1fbd6e192ad7a4812358d85f6e3cefedea3aadaac6428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f000000000000000000000000000000001661e6d386aa6516f08decbbac9c1c3411ae9cae62b05037dd626a2e2273eece64615c54a4d73e09814d497067f9e6e30000000000000000000000000000000007543030f8995237f65cec9b69b0356a29133d8be27b5f79aea580955042242c2bc1c6a01539b6b55ec9af96db60b394ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d9700000000000000000000000000000000148597902b3ffe4ba8a5f9012e699a3cf189f58275557d98d132b72d3c34e5faa0953ec8cb10b0228a23803b70836e200000000000000000000000000000000008741bbe372a1e5a697e7059a9e80de8a012b0cc7b12c14bea098c16cfea154204d4e27753f1a8fae0e618223da14fdd88a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000000cfbbd4632e8998ba59721686310ec115b98ef470c3c4bbe427495d6d95d06ec6180e64b509c4c06e32862e17939a2cf00000000000000000000000000000000060042078794f4539a9b3e3127632c3c8b46322a669605d1774e995c5d82287d3d9be51690b4b5df6de8d55b20941dc630eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000007e67da2f320e1ef0d3afbd50634aff753a2e2104ddc03244a0c79eeb117ed1beb7316f7c5e116bbde47c53d47e725b3000000000000000000000000000000000b5399ef864331db729724870b431d8dcd8d3279cd00a59de2fdc15bbdff2035794025edafa21fce97836e93b41aae067e8dcbf708682225fe3f71b7a687da23de5ed188e40585be05533580121325770000000000000000000000000000000014bd7f0effe81cb626f92422ae7900bafa7f4c2d51d4ee6926eff68b60c7f41e667a57bb0506f7c36d3549cf154f6cf300000000000000000000000000000000050aecd688a63075feacfd29d1ab6430176dbc5ba6d406636a6650427a9e0b0d51df51d8dca27665b0b6c60e08d5b087532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000009252c5f7f7f3b36c5dd32991641c9f8244579960fd2d07a8641b82c5cb1768a36f4e5ad623319ef3f7d0c670fee58430000000000000000000000000000000018e432d33e506ce42bf3d873e36ed6ede0c9de44203cdd453cf91c42fc2ddaadaadd2e3870c5f5c171cfe76862ce44dc3ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5",
+    "Expected": "0000000000000000000000000000000008b38b298fe2dfaed042b35ce73c76ece7537fe5181ce647de756349a8dc48d3296e243fc7613abb10e254e2b0197d7a0000000000000000000000000000000018d59a69b976b1bacdffbea68d523da3fd0d2910db0a509760bce56bcba36a55fbfe11cdc14cad50e6951ffdabf97a64",
+    "Name": "matter_g1_multiexp_48",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005d929298c9361736ef5f7c83b6a851c344d72b7bb92a8201d629bf9bc1e66e4db6dc9df64ffb41a11eeeba10be52ec40000000000000000000000000000000007962e1b1b823b770b44eab51b3b84fd7e0e57a2a3f7eb1ad9c3ab02677376cee08b0a2977552a0f9399584b576f17f148da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c0000000000000000000000000000000013283d9b3cb5ca4c3a39517adf466d2b7fc90f4895a24effca7ebaee4df8735c69993c7cf2483c3480cd2df4be04366f000000000000000000000000000000000fc94dee82225161feb78f2a7c951c41f43ff3c1109a824b56c01854688feb86e240c9fa48534809354e74ca8360cda4def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000000fb727cd02c5f69af676f9cfa68cc4363cbfe5343e304ff5180ed1f57e6928fb808539276feeb1e492ae2455f65de0b00000000000000000000000000000000082d09bb2e1f1585933e1b9076711803e71c2236ff78e83f5dae6ad492c1d723120ef64eb25c8e91486d102c2297c9e5b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000000882f34897651c59970934848ba13e815710b4952dc0ee1abd0e04ed82ab399ccfb16ec966d010eab51e5fe514af91ae0000000000000000000000000000000017a32754dbdae7a2541eddba29cb8ca85a0c6d189f9bbbfa24d477e9f1ec2ab8f7dd2a5aa7a596d3a2af916ecfbdb2c2a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000db9ec112ddb4a9c6e371440d0c79bf043c5a3c6c6bd613dc031ce9b81b49a32b006a165ef29a8e05f019b76b3cf520c0000000000000000000000000000000002485dbc3c3e2aeafcf18dfecd842ec48b2e79d3bf7936917df759a9ca2e25fc3f137eb88a701f5fee1ccbb06d5cb08c0b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000100d1fb78f53423c8cd60de5d39a004ee1c99b2fdf6847a62c73c33bb3d317ec06afd6424359481f8ff2d0730cfc9095000000000000000000000000000000000211cda7659f1e848c931ba1f65ca9c6021067ca01cdc8e87f5c742006f6dae39645553b69a4ae00ab6eca16afd0bda7571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf00000000000000000000000000000000004b07c2cb575e2499e333140e48446fdaa00368a74b87e607e285781b42eec39d1578d2e34701ed28488f160e9e50680000000000000000000000000000000001c2d66d28031aa91f6aacfdd80d222b4a0bc699a9b58b7f5d68bb9ed0a297ffbec3a6ba968f225732879f2f9907ca3954c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b000000000000000000000000000000000bfa7f8b7783780a2b0f5b9f1b10da77cb5904618b8c8a1d062fc94aedb0fce090d8c4e65515c0d05a471f2261d0063c000000000000000000000000000000000f45747e4b0bffddaa13c7e03b6930ec474735b6a0e779d3722330828ca26a07bb731a5d4884ed3eecc710356a00a897000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b000000000000000000000000000000001245cf167d097de0753d29ce6018b7777b1befe43b5709e8217b9f380d958e3e9298347673dce432e57338b313e84950000000000000000000000000000000000d697bf8ec405e252588e3ef6d979bfa60ba174da03266c3a2efdac176c1ec1341d737b16d53bda6ddf8be6e1f433ff6909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000000152d7f1e704619bbac7e594be6e105120b76d9bbc711ea40beb1063c2996fad70bc8f77a915411f3601e75af2f2059b000000000000000000000000000000001622a6467c13c534ff1fabaee8b29452d689e7f9e118e050cb91328b8078ef97fc82321b80d28d0c02f2b0a7b66f04a36d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000001c118fddc8df59e2d4ef9865d69cc044fcf870f296b009a2a471b1f74692f99e392b455b8b03d079b1f39b09e5fb720000000000000000000000000000000000032c05dc9eef5b55857956919f7a51b5f5225a45ca12d80208231304e66c77b24707a934cb9814108b44427e658d143dcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb280000000000000000000000000000000012da4a2c89951f85757c59a2630bde25c30af955448c972d256f1a6a259793c7b2bdc3f8734f4e312897cb6a3550800d00000000000000000000000000000000199939ffbde7b14b5f23eef23d4a660bf3f561aed38205e68d091ddef9679df9230a59e8cb03212df2e99788fa2595bc23d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000017a61df7581a341f21da2d1768fb41bb89847c88b2a0d7b61aa3275e376a46672dcb919eebf20b242ce83493c83335680000000000000000000000000000000013edc932b7755115f530d1d044c4afe71807a6b9810f555432910b54b0fef441b4618652fc4bc2ac5b789a75d2d276aa2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000000c73683f328a0aa252c10bc3fae9e786ccf183f1b606a4596094fbe10630d4418a527509c93d23e62dba263d86f88951000000000000000000000000000000000260c9dd70a1ddb422491a20293c18e4749427cbe9841aaa3370533b6e5d6fcf882f8bd68b7161434bcd5060716fdb97e3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e000000000000000000000000000000000bad0e395f46f714ac9d40865d588c06adb54b12439bb408a9d546b0a8ba5b3098c242cf5c17d1e40dcf7b384e81b444000000000000000000000000000000000e595304cd73c8c2a0bd1dff70e89edfab22be69bafa16877ecf669ab1e1160c9719952bb6103f31f2ff028cae0f0ea45d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000011f0037c9bc2bf953a3eb7d8a0a3c8d991e6eaa5f13dc1978a31f0eddb550432c70aad096cc0b904ee540e5d2d1ee4730000000000000000000000000000000004f8616cc7476fd0b95f7bbb7fbcda389aab60a88ffba3c819868f7ed6cf08e7c0c7da0958bcd957e0429b9a7fe120bafa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc",
+    "Expected": "000000000000000000000000000000000e87aa419d686c55b1ed8ebf6e48d8f218d7198edcbc7db0dc3bb9581bb8dbf891dc383f27717536dc5fb7265ce1ffd8000000000000000000000000000000000a00646bc197307a7416aa9e35db9ce7eb81d762a065cf8d2e07f6470e424d7d59021be049b36eba2e44750a902f3124",
+    "Name": "matter_g1_multiexp_49",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005d5e69a8876b82b1de0b2d2a0d808c739b361d1cadf3ebc9c6096afbc19169f237774be6882caeffe47e86e3b8a33710000000000000000000000000000000017bf0fa8c247af0078d486e1961577d7977d0b4258ada3e158822d995188ee374d900c4d8b1ef4887fb03d8f6a4bf1776e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000e6ee0b0c7bb7c3f62284efda6bdbaf38bb5a72b4435b76928c5640fedbf9d4144358a20629403359fef5bcb99a795eb000000000000000000000000000000000e72324fb2decb0b0c7fa18061a41bddd6e2c55f901554de9be8ac7b2263631fee8bc77773318f6b13b2db7eb1ad0f3cd51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000102df0d54108666e7aa611fec5c09b72d269c72e6fcee7787ece5f33153a3999ba5f22adfafa461aeda64e113b795dbc000000000000000000000000000000000b77ab3de0a2d91b8c24a47a27fbc5b2281cea40d87872010b94e895d9589880385f82ff53fad55af4f4e462df1c9ef6fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000018e8fa3c5bd83b51b1af197f0dee78e5c912c742df0cae1b59ac44fb2b903ad5ee1fe9750a034d18141f09a2b8298f850000000000000000000000000000000001526a80337eb938420cf2e825e5bcf3152e90e448ae3b40ee61929117d35f694eb5ce9133b2cc664c520fa9da8ed65a7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca0581299490000000000000000000000000000000007f11b03e06ca74a35cf702f19fe29facac855d7f5adac59bbb8c058b1eff7d4748c886eb08600e0484aa976269e5d0c0000000000000000000000000000000010a5b0f723371690f6ccc5fb346874e58071167947d45e54f9d5edd035f2d68b4ef9e301f26ef671634121ae6145e44e41b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d65000000000000000000000000000000000db2e2ef96ea61075e063629eb031235543e8f39f012fd006e143eb137524976c5a81eb26996a4ec3619a7fda051df6d0000000000000000000000000000000015d39e93da2b392dec64c58e73740376552e69caf87ce9162801466e75dd1e25b7d5762099112b21411e8d8bc18806fe5615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc000000000000000000000000000000000e048ef3ee3bf3c41cc10b89b7d0f8b3f27c89fa0ab25542653155dfb7f8a7e8488a737bf2f6dab558910c9ae98aea33000000000000000000000000000000001357eb0945e2c4933b358970184a21b3369dd7a43a88841e94c3a388681f338770fdc3a32862c3a52eb251721b2979e9bcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000c6321bbc74b6b3a9f0c9470461c80b1713a5092871dc54dd022d3ade73845852315b3e85b53b74ce2b31d1780939d13000000000000000000000000000000000cdef7351c2923faedb211e79a44e0e02ebceb8103cec2ed7541a54bfafe3967791edbeb6d4b0da1ee37b9a5d77ac8f194c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c4000000000000000000000000000000000b925015af89d42f155eb1f5104db1128faa23101fb9bc1a9757266a2717d50e908c64c502a8d19bb1e8c01dad554e41000000000000000000000000000000000fc8c5cbacca685c24188e8f936637c7c8010f6126e9b9b49e7d38191af1246c2a3cf7ca45bce6f1e11c404919da61c3dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f20000000000000000000000000000000013fa1374d37396bc60386d07a441a7d21fb808e3b2ea0c39ca78a6dd70c473a8feb972e2981e50cab6288dd80c40c06a000000000000000000000000000000000f35c2a2897b35cd7417aac29ade18f86d56ba24848aed78a31513d5115bd964ac6711c5f71736490195bd97d2d5b507d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c1000000000000000000000000000000000799d71ab5145a8a4726cc5567d99b344971eb8bd6248e41aae02bacc358f967475f64169e1828a66905e4373cf5c9670000000000000000000000000000000017c680c55af98789584e073c3caf32373f58bea6ef7f839f1d5c39e512058360efe80a884ef5822bd5fee34869d028d5aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000000b4be422e3d3e96f6a6821c55bd2a37ba57de1bb59c8f4855b1f4b6906259de6be1c1be40523d5370ccc426b89478a350000000000000000000000000000000019212f598150b576c17c32a8f374db52c19431d7a60b99379f570189b3fa15edc75b807adabbed712268087cd9b89a8a106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a0000000000000000000000000000000009df46395e64ce38bc79acee751484ce1bac53c5e5233d3545df2ec776440e3f5b04239d6de10bdb086aa3c462fc6e820000000000000000000000000000000009a5c816b2abdcca7a916b1eb015b3d1c01f766e01264b5139e5a34a82a874c1efa8ef097d23b9e9441916a2f5bb17b4c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000023537e0238470f4d513d56d4ef8e244e3d853b3b10a893928547675c6b2d409ef6bbfaa299a726eb472067c48f056c000000000000000000000000000000000b48f21e01e72bb6ec384a1e8ab35db6ca032e4476f37a3282214efe483b672c34989e6d5c99f69473eb19e472d984bea5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed490000000000000000000000000000000013f1ad33a2016874de5265565049722929528a1c66b84c1876f4e4396f22fb2583d025c481d4d9aa2877e0062e842d7c0000000000000000000000000000000008a11522b3e6982a4b46ab6f1f6b07d33443780c914d4bcd50ef7ebcbec6ad944ab88b82640971e890a363dd92c71531083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a460000000000000000000000000000000019dfc9ca394e105c6ad51b130aab8a043ee58f26a0d8efa5beee59eb1543c2c3d33abb5cf2b23b0882a409d32f845b1400000000000000000000000000000000143e219edb6fad7dbd64e6aa82fafd05ed92bb46e526468cc3bc0d60c89319d3fa2032b5a617691ca2f136c9f7904225d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d",
+    "Expected": "00000000000000000000000000000000027cfeeac9c1606a0942a95068faed1405a5cc9b5e631c25a0010ea14cae5b7a51b406fd0b8a9d9ea7be9a8c0f76c86c00000000000000000000000000000000106c07dd0d4e6f44fb1c3451942bf10060d16980f50b63005936e35b5add71e1352c48d1048e5d77cda79a09f86ff213",
+    "Name": "matter_g1_multiexp_50",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000065d5c6ad252823540ff4a4639cd42443a3cccd808d40d8bd71379ef939b47c3027ba5593167b4dae93b62b2bd498f910000000000000000000000000000000012623162c0f025b16dfc1c7e5fa02f8af7b7fb0f2d42d6fa0fb01af45621f00faa4ed6da6f33c609448bc027cd6a4fd367c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000008b3c274f83f49cada0a1bbf0f56f6fe0f8a0873cf13efa42ff65dd6fda913102c2034a31a1a92cd154210d27b0120450000000000000000000000000000000001521dda1b2c9b42d7dc9822c64bec62e71c629d61e796165d9a18f8ab44056914fe5c8809f21663bfc70e310ddf5d952d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba9400000000000000000000000000000000109f72caac5abd41a228bd82b6649fab639e4d22cb3a9a060ff7577de61f33d32217a73014f5cc2c2a76582a6b751ae200000000000000000000000000000000059d0e9e64b10cefe03daa146c00c5040381ce6ac63886b5fcf19a0555a22a395a0cbe8b49c510c9bb7a308813fb482958c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e70000000000000000000000000000000001c5e132707520c525045a08626e014a84d8da23dc27b6320d5915e328c3bb0df3618cbd7ace26834920d4a8757368050000000000000000000000000000000008f5127405631bed295596639ec6091e97f16ce5a3062831102be951aec98c9ad34721489f65e731026029ae3eb13aaa50a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c3200000000000000000000000000000000146124bfbb9a3d253670be419f80998382895ad6237138044c55764f0d6fc07da5b70cbe17af3ad0c4b0dbe33f869e490000000000000000000000000000000011cadf640e78298347115e6110d3ed63dcbd251c48d3e21cfba4bd6859b0310041e67d212b54e63be6d68d2e7fccd83b3b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000012ab7e51b87512007e1baf2f3c3473cebb553bc2ea3d3146358688ea3167817a449ab9a7e0b090e00f47846da7f46340000000000000000000000000000000000702c1e0df68bee2666abb90bd593a17a6f9dad02a7d66102add9f3a525a1b4f1fefa3abe262852fd5ca357d2e1f02fd1d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e39210000000000000000000000000000000014635c8b9cacbe976733bcb1245eea410008082f240cc8d8246200abc0eeb6b7444f38da3ad93b1e029b06cbb12d42f7000000000000000000000000000000000d9aa00397e1799a82d73040122515b98be82052b784a4b385417f6e260e555c7c0c48a32ca1fb28224f75f887fa4bf86fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000009a0efefb9daaaba4b2beabf6c381c27df7c32d4021a4d722118886405414837cde5c55933de23ff6769a0a42933bdd700000000000000000000000000000000101c9941d98dc8a146a75f2fa48a8650b25ae8f6d943323b1c10360cfdcbebe220494660f4d6f7921fea006942e122ac06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e118862000000000000000000000000000000000e5b403702a229f36c9b83bab9335cbb4e39fe8f5e9a5aa4bace70361dd05c87ae356a40720c4a8214765d028cd161ec0000000000000000000000000000000006e447c61bce31b4843530e504fa1324657eba731a272ddae680c202a7d017ffdf0ad0656dc0984a1fa297f5e32c2740fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab00000000000000000000000000000000135fb22eca115779ad1295f8c7f149a6eb4fe046df664ddaee976a15e11a7a59db5e2c44b4a82c8ca1d17c0043f41ee0000000000000000000000000000000000fd9c1dceb20e85ef80bc9ee44e483cd0e2714882734a561ebbd0982d6d08e9c41484ee99790c20e83d051dad0a1b1e04a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf00000000000000000000000000000000040f65cac81c01f04db3e331659d6bbaac8fa01581b1bbfa62891c1bc95a67182d254650019dfa3171e16ce37deef29a000000000000000000000000000000000afd5e22abd5d5cf78764262a91aadcb8b807b2aafecb2aa3d3ba5a187304208e212e5df46a4dc48d6150a733075bbaacd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf00000000000000000000000000000000051046cbe6862c5e37cd2f3c14dfc2825d5c32de69b40f29140fd31405615edf6c116d384bdf1552a33fb00c6c65cd97000000000000000000000000000000000a61a19fdfc994105f03aa3e1b907f5177409664b2e50243cf7e0e6e7e74c7bfce582929e5670a351b3d7b4034f101ffaba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa0000000000000000000000000000000013b76183fa2e01d10a3ecea5be65ffbcb04724ed30e4655e26a7ac94d5861f0f308b7d4577789d2f4892eb89202d84100000000000000000000000000000000012c3fbed77d9c37c47c838899aaea0fb6585eec54801c3ff2b486086e33040aca6baf6192c33af59f7db1d489ddf7d086ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c00000000000000000000000000000000195c1bf8dc0114a472cb4daa31be44f22a162d22f2968b7909374fbc4d0883614d2911475cc3ba242844ef1c046885e70000000000000000000000000000000000d03e5bc3acdd01d174e1d2308e3f1ff3f103db8e2804210da44c47229bd983ac127295558dc5560c0fb2ea34def196f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000001261cc24d5e69fe8a7747fce45086499ad54f7c138fe76fa665517c58e475683c5a219df303810745dc554fa3c096f300000000000000000000000000000000122fc4c068c079827635d29e944366516c1d7cdb1ff62968d847f4882da8a4919b59e57690f6e0f6aaf083af0a04b2ca32b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a00000000000000000000000000000000050b5493fdadda15e15b2ad6104274da831753b1cd247f1dacffb6f896b9db7190bfae2ca202907d36b979b668540ea400000000000000000000000000000000141245d4556c7f1032d0ccd606e3a2d3338ad753fd7d0a3c1b8ab38e94d8618e85c22a269428537abe003f8de89f2c1171a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6",
+    "Expected": "0000000000000000000000000000000008bfa9c347d937e2ff2d77ce7127b1af4e7adad5f1aa6d71c6226b64c62c955fb0dd0e36a5e87472d6979b656c8a740e00000000000000000000000000000000032de435000391e441ecb7e777741fc72f4676f64cfaca6fadf030e12ea6374a7fa988c443a38595def0b434b185c75a",
+    "Name": "matter_g1_multiexp_51",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d0296528c7b2516ea73cf14c5625a4296c311fc2e09722f3b381279da52ba9482e43d3fdc1694b96c3f62b7d98d6951000000000000000000000000000000000da2aaba37d0955c5fcf31152926f2fb345deba744241bf66511da5f4ad9fab8a1cffa270c4e838c39b34bc28fbc08b02e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b200000000000000000000000000000000175c976baf0205ee7326c84c49cbd2d7c3db91d1ec92d87cca896ea09a7cfe4ef8ca45873f86e28afc4b525356a68cba000000000000000000000000000000000c442d3edb8b614407e0d138417f8a6c028b29dc1beb5825c928dff3a08820c5a8ed5de643068bf4d239bbcc2dcd0b7612a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea7778355947000000000000000000000000000000000f45bf893109177d3c336915c5e28c338ea28468cbe215ee6fc6f6e3c9aa9e0b7120586e42c5c087b55fb5789a4a9eb2000000000000000000000000000000000b6ad0cffaf555f081ec7a6fb354d6b20950fb6fee059f2f571430f86a7cb9996b5f655bc7cddd14f3f8ed37c7fd278889a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000001944f2fa08357307df2271f4bb57cd07a998df56425f7b8563902aaa0330070ce260b6d86fc38a5c6a284788d9cc0ed700000000000000000000000000000000165d8134931f7a4cbeb5114a10e44172aa6a0c250989dbd88282f92fc238a8e1e21221b04b239cfc597e2b74700c626d27dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c7a02cbcc758fa7b1ea5fd30b3b88cdda7c8661b3712ba5bf924b441e056fb9bea804bbfa1850c21cad891ee253ff7100000000000000000000000000000000012202a151fcb86875b4dd2dbeafe5ca484b63408ba01440007164fa2a2b7ebbe9d7f738f382a010508408d26a57c566ca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f840000000000000000000000000000000017d02a3ec6d45e9b49ddc8d1bdee168f71c32ba26d4de8c1bdb328cb4c46286328387aac8785eb5a7c71d0ed59810f4e000000000000000000000000000000000d23ee9c9fc914404ff46d0f6ee86984862e97a777ab516c2b84f5b5a7c1807d64e93fe57db53c7b95257fe46a7a15495aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000040ca6ea6cae1be17996106cacbc5d9f1962203fd25917dec2c053816f3200b9853b218a07db690d8261ae3cc85679bc00000000000000000000000000000000097e8f4b5a24b010382888ebd7ab7cb71f471bca00c1499486cfbf1bc5ba6af169ac27e1ed8cf31b5d9600361ad13663fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000016f95e3e24941c2745c009437c1b2f5ebf690c9c76e269f877bbf73ddc6b15c6132d424c26a3c7bdd9c5302dcbab171f000000000000000000000000000000000cfca2fd001c0da52f231a60288b22a134c7e16aac8745129c351dd96fa37b72a9ef3d93d5e8e45cb5fab9e73ff188e128d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b6224000000000000000000000000000000000e2bdbc906c10b04c5fc1e867af43bea7ca43cdbc43cc3574a47b2b0670716a92fd863d4f423f3392ec8849e74850eb9000000000000000000000000000000000ae76847a2524be3a04bf85e096a1ca4cd3674459698fe326db2d71799c8906022e15bcadfbc9ddcd43dbee3443842a81208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a2900000000000000000000000000000000035d66b8b8b64bb0d3d1ba6bc1bd34c326ce6abac3a97188f82be38d1756f14a63bfedd531d5e19813b668012f77763300000000000000000000000000000000060851234e4cfa8c168db199bea8cbc337e685b565a6faf67e07c463632a6a163a2d22acf9fc6bc6a1f7ead5d288fcccd3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000010e398f6c9ded2fef3cd95cbef681c5335a1e9d08c05dc05b6391f65941cb3a79df9e1cc4ebd3fce82d36cc628b7f65c0000000000000000000000000000000016dede30728c57650952e9425b6da1ec8ee5702e783c69936eaf6857f199bd9ffae569db3cbd61483d48188633fef7ed7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000005eb3a914a78b4bb3041a32397bdba3edf6943ed474ac8efbf9c84a6cdae5d65a8f55ce4ad141b846f1bcb5df1206417000000000000000000000000000000000c20828a5d8abc2c8f72809348e770649bdf4bc0991f45979501f31d9f31e028731a8ccf07f0cc51bf8b59632897c540c5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f1300000000000000000000000000000000122cf863d9ddfdc627a0993dc7ca5810e84ab254ff8147a220d436043c0a695b0cceaa374842c335c14b6ebb273472d800000000000000000000000000000000150fc0b14e30ee797e3b9202533c681ca9e6b1b43347cfa11da59ceab439c9e5cbc038a50917cd9167a0fd591d8175e484ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000002ec5aa74588f6a7fd8076b9a846ff3542543dc7a3c798c423326eb06ef92edb8c35583785cfff21f903f08f692d6293000000000000000000000000000000000df140c1539cd3d94b5f9d0aafc38294d1738c5b3c1880d8864e83909b152de0a469742cd31e5e8f5838ad793ea32649a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b5000000000000000000000000000000000a440f227be209dd1bb816a4dd8c1abbdbd03d97c243ac6e48c4efcabef4d7a4b5bf65ea7bea6f4a1da985bbb9fac626000000000000000000000000000000001431a99e1243e57054d2b43217286b35bbf37afff72b163ad40dd4ca92439f4b513284551b0fb137f968f9f59a540cac650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000b99ae325f1fcf4f3c83f251183871d1b6048a43d15da80650e0b5c1b671031cc9af63a478b5939210356c4c2dcc7aa1000000000000000000000000000000001382d6f0550aad61dccb47a66d004ab3801445d55dd320a6ccf03577b1c1c915022a955e7f3fccbbdd20e4175bd0ae38282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57",
+    "Expected": "000000000000000000000000000000000c62c70aac1893222d967bde4fdffc565cc65fe29387825224b8571367ae8fa857b63c32033faa564f6e12d409b5cc060000000000000000000000000000000015cb57fcbc876f5aeca01d90d55ea777aa1421547e8aff0de69fe5527c9a575f9cecd1235a37648c7509d9bebb4e6800",
+    "Name": "matter_g1_multiexp_52",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000123fa54665de1ad1eb74d400f93b70f8502bd9386a164ef9ac7549b3693525e3fd077b2b2d8b15ab0c6cd5da30f8317800000000000000000000000000000000185921a0fb38ec1eb6804002b3bcbd4d4bc759885e9c1fafe275d51840434382df783518ce768ae40e736ad2ca8fc8803d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb9000000000000000000000000000000000eb30c8da4c7eb16d797f24b5d8e210dfaa68684939cad598518298c84214ad769f6a2634fc290c2c267c8f3a2872f020000000000000000000000000000000006452f211931b8d7ccd8777b2407e5cb073097ae9b309f1e95633f39d1a5a7f5843a6e87473b4b9c1bbfc17971108e3de71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000162c0325ae75a81c92a8885f14e2f7b9b8bfb249fb9a352d0007cd8bdfce2d8024f1e4674614cd0afbded99472d547000000000000000000000000000000000010d8497a5f31cda80af22bfa6695b4e2c8fb5557ee74581a33fbd0cf8cb2e0b4ca3ecc42487cf957ea81a5388d9871fcbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a0000000000000000000000000000000013270ce7a1b4abe3026d245df9b93061a435ed00d0464d8de14675247c7f2f1cbb6e21c8282e71d2fa28eca1e3f5863c000000000000000000000000000000000b87656d14cfe98c2d3f34b03de0b9f08207b00aaf6c5a4a6b9b4989744581772a2d6d1923c3d07b784853f7b2d789b9ef0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab700000000000000000000000000000000067296630285ec7da7cfdeedd387d52d905ec39e183b87479c8f0fba967e840f8394cb518dba4f4b7d4e2cdc00ca62c3000000000000000000000000000000000ed41fe0f04e0c63f3fd7ec7560d24974fd06a1566e8f129f580251227cb9b7e10ed6e60c2e7449721d5332709f465973ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc00000000000000000000000000000000006f7bbdc3c8fe2f7da9533a3f8a3c48c630d6cf567c75dcf89e13852f7a8691e2625ca24517ad3b59ed3513f7d3b4fb20000000000000000000000000000000000a2e63715ec49b06a78e014b98effbb03f99ce61b464c66108cf18ea49def3e1f035a8b88f37b453b31357d2a2a48f4e5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a9e51eaf24d2d0fcb7f1dc7ad985ecd4da3ecd19fb75591467edb0f7fc7bcef67c1c272f39c31ef36bbc73d7ea6034d000000000000000000000000000000000332dedca239f4d1272db77dc388e07005d90f44311aa889b42e931d08c2669c3f4aeecd9052d3f2585b2a4e41c8abbf97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb5926000000000000000000000000000000000c6b189ddc86e2d6722ebabc445190cf94bb4c54135aae2601c957e062d351d0c9fff19cbeb45cfc5dd05eb3543a660000000000000000000000000000000000133794839bae14fa041004f173506fff511526313da5a8f4e32c895751a22ecf01cfba564006037326187b899aed596ac54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff67352000000000000000000000000000000000a15343698b916965009f1894c8b74a790d59bc39b7f0de01095275ec002c97c66e7a6a970b4b9091cdc54abdff1cdb800000000000000000000000000000000045f084e0a7c0014e58c9988e72e1861bdb4f962ff9869d444d5ba4094178d52f9c2aa511feb6e8717098cc1f09d49eb47ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001656928ad3ee67675951e2d2ddd6a7d9c629a3148face6d1269f79c3d0699f95350e83a6ec20aa3be78a2794c3f250160000000000000000000000000000000001b8c9e4c818774dbd2416193e795a429a22881abc94ebd9a8b42bc4d7069a9778e4bdf7270180784d914bc6be99b41c14ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b0000000000000000000000000000000013d779138ab03fafee1e4bfa2a290c4f20d2b57854a5133cf5ad7817bd32bbf2945a02b4fd5c8489e704e60ee937f962000000000000000000000000000000000aa058528a4f9bb583295ace843feac4dbce24a22ea6bf412be019f590c621bdfc7562e8dd49afcc337cab474d9abd0129b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e2040000000000000000000000000000000014f30fdaf2f81f9d941af33d53e2d9e3162f62f47c60164e9b5ea3a5cf3a681a80b66ebfea391331c231abc4341cb94b000000000000000000000000000000001854addff23c2f53a21a6d39c72f91ef0e8d9a6d6468f319200466f78854c41be3e914bf7f966f00e185b44108af30f092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f00000000000000000000000000000000188a1934a28c7571ee94f1aa5c161be611939e52156bff158170d5e12a6480e3b9d1528082cc2e537ae1734b1847f8f8000000000000000000000000000000001728b57eca86cc8fcd9dfc65a8f5f055d51d300d8781839d744a1b81a0233221cd353f642b3507703880eb0a33afa05c8f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000006c99e6c8b554d748a3526da79e8a867efde15ec50ff62e43f691748996dc087dbc538cf65820ca065f3adb5884e2f0c000000000000000000000000000000000c577c42243b95b4a613c485026306513685cce294333b72388d6968019d04214ed4bbbd5b64bce78fc380115a4b067ca9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000d7b48e69a9807c6fc867f59c894d5bbfeeeacff500a3ad4528ed4848f5ce501baf8959f822c259b712236529dff0b0a000000000000000000000000000000000e7d7932084a0416a4bafe237c923d1390dc6662e7842829ab6747024378f284af07ccde9cf80042bec56e7429ab3acd80b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000a1f74acb627d1814ef90b2d756bf76383075134c1b34dc126094238eadebd780c1ab8a3d1f4d9566dbef1c706d931920000000000000000000000000000000009bf8c2fc78b1f7af25941bf429059e9f86b34a36ff865b33e918c8435a766d897df83005c54871ad0d3e82308e368501b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61",
+    "Expected": "000000000000000000000000000000000a912d7d352bdd182a8b431672b496ecbf18276da61d6a8eb066c41783b7cf3df287796b34b165db186e815c8847b3ea0000000000000000000000000000000002881de241ed8109f544f3a6262eac1aae69de7a7e9953812eede9e63a970225646f5c441b7de80106d53cb6dbb43f10",
+    "Name": "matter_g1_multiexp_53",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018896a4d84c1ec1a20e1b0e33f159de4d82b55b6d27d863ec7cbefc2d9c180beed2285aadb34d29ceea681689dab06ce0000000000000000000000000000000013398b5f6f2c0c9095af94796572d603de02d41c599e09d3e254b326fa1575e0c6a2b7263a196c5150440daffb0d60e810e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f6631850000000000000000000000000000000015b31d591dedde69dcfe9c23df11782c090c443e505d2edfa217121a1d51b6883d782917b2a082a41ce698ecd95ba95b00000000000000000000000000000000164b18eaf53165842e50112c4a8490b8246376b58bb6c188fc929160f49cb0b68ad2f13dbeac8466fca75e6f72a398b8cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000012876e247618c76af5221a50780803ab64970fea8bdeefcdc1ef4c9a160718fbaed9dc6691502433295d54d4030ee157000000000000000000000000000000000cfd8dbbeecfd176cce05ca1663930be8cf3b300a287ed053e36f64618a14850a3e813582da1f54ac7e96ff61ae57c86290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e800000000000000000000000000000000085c5db53c4abe188f44f084bf17084d3ae409b753089636d3c528162c2816b9b9ef3c0c8c05e88189407d7ca95d40f9000000000000000000000000000000000015d9ab325a8ae365f173821829aa395db9211015903c08491375f82853d9084d8aa2e35c2634a296ba14b50e34c1feb71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff570000000000000000000000000000000014bc3ba096662dc560e88ea6b7b4363c427d038fe85a49ab8d9d63524940f26106447ad6e3d7495ca562c98b64d445880000000000000000000000000000000018bf745fde497914d81d1e3ab96630f24f6ed27ebf1208f7a46ad9fb893a3f183982c0acfc001984de34f617841524f9250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000019b15da994067a017c3040830b5e5f7eb77ce0cf0674e96b209b80c54f1307cb04799624647fd1fb990c61092682ef730000000000000000000000000000000002248d31211c2a37df59a0a4ddb0cc7880dea316519ab7baf1c614b26e2673f03b00e387fd537aee442cfc94f734aad8ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000003a499813ed2a3878ffc11d27dba4d55837d1114049a72444b6db0c8a7d23a53af765d66b5017695efa39bcf7d1c97ba00000000000000000000000000000000011fb1a989afe2b093fa2ae3c0405483bb1a52c21226acbdf2a52e2e5fd5f7404776551c2deee87f431ff39dfb031d716ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000014540330ba54d2f16a9bdec93a0b7ccd58ecb44361c67f209d36d2a42b5d5a4f9b9dce0701ad0677d6d6ca83a256e8460000000000000000000000000000000001a64d5b128c07848ec579df1d26755e5d2f70cf123013ac249a4d188b0eb56cd74cb12f7de2db69b3a0f9f4ece2c4201271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000017783e019baea183ee5d9e1f671a23108e403a22580f5c203dd6ff72dc0adaf802d031a236e72463e0fa2c5f7c6e68b300000000000000000000000000000000132d32bae3b92b7212dd7db16c87360274a409f46199f66e572bdb21c4af24af62758978e6d01af60f5fb87481d9f4f23ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd00000000000000000000000000000000000a625f252a8185bea7f1b73d1c7c9b1fc7f4ea5cdd017afbe9e56e7c12d58d893ddc387b7c2870f4a975b613bef0129000000000000000000000000000000000aff6dcf60f78bc908fc4c2466270065766792a05d8629fc7f5d2b61ce4882644947fcc3600d63bd5f49fea5574616bcc6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000008ee6e9521f32feaa034b533c0b7c749f60d84adb53d6943d3974fb4b92ce3cb3f67fbf52fff27802c893cb97e587b930000000000000000000000000000000012000b50d1c9628f822c41d56b29e21f3f496f00bcf05edb234ffda56767bb33dfae736aa9fb9a84ccb6a0e21131c5887275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c6150000000000000000000000000000000014d9d3051d073d24701f01631408b7dd1d37f0855baa64a13c493c15f7acf36da116595fb3d69dc386cc611c998f9ea9000000000000000000000000000000000b33438dc1f84da6ae50b1aa76fc52f5ba0e547fb15e8f655db9e0e26d6aed15c5cc4e48412d089d1ca6fb7a550f8eecdbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000008935c4cfe2a1620a0c895feecd91ea7fdcca3bb06fa514bafee38ea5819b7372e75a106904b9c9e8af268c9f5e5a45700000000000000000000000000000000114e9944fbfc05ee1ed75603bb9b79301a1f90d3b5209ea14989fdd16f5deeb01e3474da2b4692a3e0b9625d3bf9b4b2ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf00000000000000000000000000000000105268fff23696890182b5ec307b38ee1cf28336e1c3fa28b9b697998567035323ccc91e974f63c55c928f64fabc2ca0000000000000000000000000000000000ac2f8c91fa31e2d950385509b86d512c80f0d1c73d223f71b26040d58822e4269a85e82ae390441853f8169177943aa461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000d5a5c94375029e5511a6c6ca40108377db43e4e0b03cceaf9fb77fac7906f71019c1a85591719bfa5d9349f1089ba0d00000000000000000000000000000000163bdfc6d40c96bd24a3b83f89037ec9e4191b533e36dc699a32c854291b0823b3f071464654eed00f08a691aa68636bcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d0000000000000000000000000000000011654611997b772db3111d2d4edf92b83689451b1e7594a7a4bd40d85820df6a1ab090f6a1959acb322323eef27fbd86000000000000000000000000000000000b905fec9e379cfba09fd502197305ae39b48facdb01f52afbcdf159c5674234ac9723643830ab8e2639e7a0d6bd979267de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda",
+    "Expected": "0000000000000000000000000000000004de1528d78645a4055ea348ef2618b85f8214c1dbd21ee08ad164abc02cbb2927202302dcd142c65e12934dec409e18000000000000000000000000000000000de34a6fbb73c7152f1636e5c02c75dbbc5910c763bb790d78bb56e48cbc6380bcc2ca14cc11ae139fe009135c81b243",
+    "Name": "matter_g1_multiexp_54",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000184094ad33f83f5b229643d9808f5b3b7f3e50306788f8485472405c79e57e489549c0901c3d1694b5f61f74d87afe9600000000000000000000000000000000007ec616b56868e00563d8e8bdd36de3b5d1e314be0d81c4ee97fabab1641c89cc21e70153a4d3d4e788b04ffaf07bad624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000017e7c08cfedb74bf88c1a80762be0e0754a86e5482c27b41240f4ffa9d014e9e8560e172519031eecccd897b869c365f00000000000000000000000000000000115ff96d404829597f16b9b97f8ff71a8eaca1a76bbcb72d53803d35335d8a8c1cea58559136f9b254c28262aa907414edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c235000000000000000000000000000000000b5450038d49c91e4e5a40a31f1f75923c7e1599695b829d9975ba7d845ab20ed5a62a7238d6a6479b2c6f9249068aed0000000000000000000000000000000013066cb8ef171bdfa11e70ddf83eb2447c4169fe38e008be5787c38b1b8a946fc474e07795765ca17fd5bcf64150fb04feb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b000000000000000000000000000000000f19fc0ba8a0ec5a2cdb9844002601f580e0eb9b2265c86f6efc4b633079d43461d6bf241ccbf422eb9d7c00ecca88570000000000000000000000000000000012e744ae937ff9e8e4f611fbd1c9896bd31bb1ca36b948d9be89960fee6c0cbab3264aacb916ae3596f110cc1b26bfedcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000006ca09ce8c07e89e9e51e208b5d32b5ab61f0de60484d9185a26911b56a728a7473b70313fd18c893ed3453719b074450000000000000000000000000000000003d372a5477fe7fd84a58f6f2eda8f5c61aa0c357c7fc1708f7616b8cdff249e7d2910d753c2e531a278f5853fc065970b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000152ea2fd1934c32c3c8e27a6ffb278741b899c5e296549380d019307875629d57ae44580a944babeecef73753e30c92600000000000000000000000000000000161a77844c90a6e83ed2c40c937de21fbd714a5cde60015a71bd4c960e894d3cb54a8d1e4bb4cb0a1985d4469814a991730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000a959b12e3af03cd4629f5f6f412b7084eec6aa55369e2dd2f355c93ea984ea6f2a7a01e6a10146849503d230fb08f7300000000000000000000000000000000161340908a38e4ff5373df643e3cfdc459d872b5cfd41ab34fd3297b10c37dbf3088fc23fb71f2a1751a121bcf51ee36ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000013e31a4f0cc29a5ff7f4df39db999c95eac789656bc9c6b91d0209b8a5ec2dbab698048fefb75a3dfa48066ed5743215000000000000000000000000000000001851e72741707cf96f887d13e01981f1e3db5834185eedaafdea99eeb11dcd3e90a9985f40886b60ee2a779b141bb62082b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000001501f155cf6f053631ebac7d2c57cbb101a750f98b6e11df79dbb24ec8804535b1b24942022aa64713fc60adb2017bff0000000000000000000000000000000012a08f9b1ab90531a26221b70751efa598b4046a5482c01d72f506ffbb3430d35016848755674d01e16bb78a44f8b6882396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc0000000000000000000000000000000008fe1ea18cd8e1d2c620356430ca43782f844a2efa6a285a7c9c086e972b12735faf6237447759bd93d98b6dc7c42344000000000000000000000000000000001731f36e811c640f44adce6bb68fd71065f440eeada278ebcabfb9bf0291e551ed302c592aa4ba7e3a502cf58e3eede69a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000018e3a4176b543f2152bd7f72ca358af6226f77b5e10f3f9006c8bbe4283776ac31e6d10e838e89e8090215a133e2cc510000000000000000000000000000000000f88c3eab9ab32fc165083ba1650736e04b4e8740591f6e3ffbf684fb359fc8d82513c25a9ecf4d46faaa14d9f13a3ff20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb0000000000000000000000000000000016fb47b4497cdcc75c0547f4234ce94f45d160e7bbe199902b2af5a5896e7d46cdc866d0fd730f568449032fc3a2df4b0000000000000000000000000000000016c2da30ef51e6728c09c3b29a7abdbb104f1a4fcc8960248b9773d2ea7f1bd161bf17203a271edfb235e8b0be437957f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000001992edcbf32707e92506e5cd12662e730bc96b5f33bb88c5569fe6b266aecf63548be20b03fefaa078231b17424ac98d000000000000000000000000000000000f6179cb8878214222c2353a60e0ee210c86e306e335e929050543f084ce7c7ef56ca8444eee59856f4107e0d8cf997b52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e000000000000000000000000000000000d3ff341e9b3821ac23ff7a87cc9dec3fba38ab8f2bc0f58e4c0135a9d66c6d6731ad8bb97468ca44538ca7f26fdfeea00000000000000000000000000000000053240b8429fb290453de18000ac58df56b5bf3c279e35d9cae8b350b932b0545b6c19ec7ff186c2123731d971146df1d32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f0000000000000000000000000000000004b424dab429bb3d22d18b52c4f9412a65eb7e8ec40b5e308f65fe6c0da1a1ab55a629ef8ed57adf108d146b46e6261e00000000000000000000000000000000057b7d5285194693a7ec1ed9ee3dfbe8598d9acb670baf03bf77c7799227ea788052de690e229b0d28c0a6cd79d22b0c1f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad2694000000000000000000000000000000000ffc202d826607947dd8f63b227a06d8c6b04848dd102da57723fe20e9b06b7c125f0ab2d2f53e14cbe95f1031624f99000000000000000000000000000000000880400b425ffe1b63214509f9acb0255d089e9de8e4eb643fa3b0383aed760f4c00babadd32f48af724a2c80a8223b383052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692",
+    "Expected": "0000000000000000000000000000000013c0b89e259f71ae41cc73ffa3c900ccea45a8a655353db6eb17a87582b00bfb971ba91d48526d934b95e9bb6a0fb5a200000000000000000000000000000000042a78ec26bc1ac4165c36d84588ca132b7366a3fb548801810da041213ee84c7e6aaf5ba02ac051cc1c5be5dfce0ea5",
+    "Name": "matter_g1_multiexp_55",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000083ddce067e21b219535e477f77ba100fb86744b1b82b4ccd0c72aac69025038e719ed173e70805c025b19bf7ba5908a000000000000000000000000000000000a9eb816ed60bbe55d4833c0e91ee73669aad116ff793d941223c17c86fea3ea434172c3214a4620d4090915cfb15d11c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c705200000000000000000000000000000000050bdd7d98b9df55ec0ab87e757de009c804880f06be3ce13c5e051c3080df45bedad4f074812a698f50d6774cd5921b000000000000000000000000000000000a8dde7b81feef753cf16f0818f29256391276847cb832bc2940bddb329b249af4970684e95fc02e702f09a84e7737dfccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c0000000000000000000000000000000000d989e383d1c6e48d14332a72a8efd89260fed65a47c4baabeb0c0cd8322e26ade95b8be9f532b4813153cc39e7a9402000000000000000000000000000000000f6f7ba41c95beccbf59ca1ebb1dd43348c51de617a09ab8a2d67d3f7065d3f4699b1fc31197275e5b895f92dd106d667ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c396310000000000000000000000000000000013f3c3eec6fd2d4c830458cf58d5e18f0367675c47d38fd5ddce1e8be3d6ab04f71d09852b987d2db64652b3255e874d0000000000000000000000000000000009c0000761e1fe517eb32bc3da4f7a933e77db6f960f5405b64d9088776b6ee8e23743cb4a1779e8d0d93787ca029d7c6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf018403978000000000000000000000000000000000c7a5bc0249717c1e39a4eea37de1b423960b409f5e0b3877e90d5278cabee197948383936739ee3f25b4bbf7f32e18900000000000000000000000000000000113d6fdda1f4b2a20d98e1d458920658c762303ee69fd7273a8830728f79be00358b3f3000927bc4d26352e5b9e6652b7fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f00000000000000000000000000000000108fb7a97ce429fc3ba1ca54ae841309e2ccce748dca953cb7dd9dee3ad9d919e3f8ab635b294b94b939cd80d3435b5e0000000000000000000000000000000003af838ba4ec485ec2a17e6f592fd832d05133952f273d1b472800b210c96cc503caadc17b38d3d1e978606786d9ffcddd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b0000000000000000000000000000000004cef28329ccf221ad7ab2b851e869bd433116753e0d8bf38d22ca46fbdc71fd9d96aeb9c0df69c47905a99c96fef0aa0000000000000000000000000000000012ef5c40d8b6469d9f3921eaa99446fe494a55994551fd1996c453a4e5cb4a2cbabe20671ff51639710a5e45a57271aab4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000c81313e8b5689935fc01b5f999de2fbe9852bdccf484edd0771e8427f2a194e29d0af09db1152fcd91c8f7b665f6929000000000000000000000000000000000f37dc7f87b8de48441861ce0c88b1a24f22aef2c321ddbf385cedec7810c20c7fee3d2c5a04b5390a5fc24612e4b3e9457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd10000000000000000000000000000000004ebf9f75e92ec4fb7168bf71215c9ea8ec17dd9ab392c9810316a30a33b4ace8d93ab75356baaeb51a7f47b4370915d000000000000000000000000000000001307c68414b73db43bcd9062580f7c814c3c34545ad5d943685ed8df26acd457823ed628e4b215875a9008a406fadb5619f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000cb5860f081e314d4fa3bf70a5eb18d6fb7f5257a708f1b1726b539115050754724ffd6a34d3b5c95359f40f41f2390e000000000000000000000000000000000c392d8603c2ef93d2765d98c695dbda8e4b64ed90c4771a4e69fa00a77d788981132336f870a3a93765902fd8fe8763f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000011c802516f42e267c0f9db096fdfff77d676eb301ef1ad440b6c2129c5b5722c420f6e479443cbf43d48803f7e32d8470000000000000000000000000000000004a5ef232d3582724c3eda67cf2e69b26ce44bd927555359820efc3fc67912df560edfc4d119c5595e1ab1fd7e2a262f50aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000eef1e6400dbda287910c117ba17eee1137377e262f7f5cf13710b521bd26eca2aa9731b0a1cf182a0d57a329369125400000000000000000000000000000000188e925365fe7cb96875e85f711d8ce233cadbcdd4c892eac52d9c77f98082662410db4cb6b24889b21f162eecd10f42d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad434000000000000000000000000000000001307849ed4d685815c670477ac54826e94465aed0b70df9683d09ddc62597e7a0a7a4b2839fbec735eeba08bbd3e821c0000000000000000000000000000000005dd74ee1018ff2280c3dd8faec3c97bbd00bbb7cfbcb849bb003b590a999b6bb3a973ec96bd9d825206eb353086283485c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae000000000000000000000000000000000458181a1019a65c34835eeca4898b88b0351da7422bb5982616c90740e8773b5a03272646f26c3a5801c6c16be33ec900000000000000000000000000000000101c2091a08179eb0be41e20a545f5b53b8ee39365dc9b57f12d75b2beebdad488d63e857ba5187c8f92af447f72896ebacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000cb8c637a9b8f053d5104b582ca03ecba768425c639fef23c4b624f31523e0ac669183639991728135474ca19e0335160000000000000000000000000000000009e0798589417cff12eef14f00e415c51c30fc26461e92c4e3fb4a5ab1a653ae791f05f4cde0cfe2132c377175cec1c2473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae0000000000000000000000000000000008400ba7dce60413ff085c0904066b8e9e9ae290781132e739a5a8c7bcbda322fe1c8d0fdb0e9b0abe44ff99d4ca22ee0000000000000000000000000000000008b54feb64f59541ba3b7c6f86d24b69fa30ba057db890cc6d958e3a7de8bd379257c90a413050f7789ded9ee7b28bbd2774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a",
+    "Expected": "0000000000000000000000000000000017377baed9953cc7fe1868baa9e2f6c87edfab1700bd1f60e2644bb97cbe2dd1fe031a986fde6555930d4c464399f1f6000000000000000000000000000000000ff69a282658b49c270e829b326502d83a82dade820de50764a7df957e94e7c0a1f0e3d9084b45d9a875134bedc4a0bf",
+    "Name": "matter_g1_multiexp_56",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000d1de82c29aaa76b17079b2e1000005bf37df08de2c5ba7a0f9a14870e0ac327f46f59a116c72db57cf5110aeed6c76000000000000000000000000000000000a8ff0afd1cd7f541775567134a889d82727e893e4f57d1b5981fabd4bbff59dd3d3995a181efc9b5fc078eb3d4cd0e7d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f026672140000000000000000000000000000000018120f0d0dc908dce4adbe50b24b66ce12e710fd35e5a8a8c357dd80c078d6854f20b12d40279b9d6a895460d8989cda00000000000000000000000000000000064f4e282ec5cac74e1a12f678391730663c83afcc0b415fd21475875762de2224e389d607cba84788a16d622d2ef5c13e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000dae339b418871e2f31ed380824412acbe44e6c73ede9b4c52c054924297aaee1f7da749374d7ca44b138acb85dc182f00000000000000000000000000000000155cfb670ac94e7d5a095d2797cbbb5b8ad3e037fd246246f8e8c2278f5d4e53a773e6518ebc3ea5aeec6383d6fbb62c145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f000000000000000000000000000000000c1f7aeb05294c1b496de11f743c0c7aa4255211e1e36389bc93dc8d0e73fdb9af7bfbcef2c196a95d1d449b9983b2020000000000000000000000000000000011251668e9edb38ad147f22cbab7d280d436d11039d9fb823a19dffedf2c6a484f112560623cde7e5525c85b4f5d06accf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed0000000000000000000000000000000000ad488f5b9934adcdc834558c8db1d62574e1ffcd03da30eed865042abae4dc03d69010e7e591d9f0a8e421d22cab23a0000000000000000000000000000000002cb0a8e0713dd3c4833af74767ce46aca6c1efdfe75d09a50fce4df2eee3fbc031357691e23ceee810d30004d03f6b9d9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000465d95750a3c688f560ab9ca6fd1f77457592a0d5f54c17904a222010444d048df2be3dd402f046b1375d75de446d2500000000000000000000000000000000166289d948aa518167e72591a011b3f5ce209bd32ce091543bbdee1e8776269347ed711e1e9f1193f818e3045761a75141733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced157586700000000000000000000000000000000012abc4f1c56f9ac3760acec3d79b77e9ac71bbfe4d2a90cf43da3607c99035e550a4d0fda734bcfcb16ad08f773535d400000000000000000000000000000000030953a6099532f7ac352eff43569914c3f8d736b8aca89f778b4a67c754ada78e121dff664feb751532a41c8081380eb21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000138cb559d92b392e94cdd8666605cb5b05e585dccfc023bb6f1abe82fad35c108fca7a41afa49a801700dd8ef89eb3b0000000000000000000000000000000018cf89ad3e05492ac8699ba0723d5ce43e81b0166fc33653c967da921faef37f3ee2e8e3f71f983774966ca183e05f9eeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000129c48a05e3d6bfab6e6f5200fbb90fbb743b045509b129e3622929712939c5d15126a09f1a650489c8afde7ace8baea000000000000000000000000000000000abff3803d605dbd63bb8453e304335a943bebd224d2d8067d76f5591cc6a2b954b9156a243b0c23d08424fb9edb52383d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000194e6f217b863339824d95c77253ddef4ab97d9744d10392d399b1f165170bb8c13ef1b7cbd995c1c1dc2a9d1b87f0da0000000000000000000000000000000019fbdffa8df167a5e891d09aa1e79049d377014e58523c0eb453f5f072a468809dca8ce0aa22b45bad4f8853d985be1df6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda40000000000000000000000000000000006f0b72c2a934e430e4b773a61317007f1ef02c5f978b3565d623b6590b6cfec22f98b49f9d7f7efcc6913c139fb27a60000000000000000000000000000000018ea7df5f807d4c4981a9159d73d83ea84359d6aa00a5ef019b0dc307d096676c0d16c6b167fc55e14329a858c044c5c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e108700000000000000000000000000000000130fae66f6b4e1a9b0b39906fac847f1285a7d37bdb0d3ddc2c2bfcc6320ccbad2ef1f119f2663e3a45dbff005a469a10000000000000000000000000000000019ba2ae0c371256e4c3dd6f9ae2568386d3a8bd90a57ff982294eae9194494add18958dd516ca9dda6a0b334391cc211f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f000000000000000000000000000000001152651000a16809ec599f2fe9f330b0782685f6302254450884f0ee61ee2dc2cc9211f69d5d9dbcd7fe3345542a0159000000000000000000000000000000000b5c017e7ef71eb089188ed85331815b40c37abb6ff73d76f40fd8dcc6d2120c6a52df0da042b2b63dfd0da7db2bbca9ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a3130000000000000000000000000000000003a6f178d8c63765b2c8df834ebf7e96a4f451c6e05692f96b71c8be2a6e9af17a5cfd8b263eaa254592ea9a898488bf00000000000000000000000000000000185537df1a10c4c12fbcff08de45b349a90b0cc8cd17827df87abe160e84b661d58a1fd03c669015b991225ba08e171e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000085475c2fd70cb7caaaa7c5c1fb17e2346903a962fa68536240d041f2f8cd3a7b83aa79a77f713bc31f7becd347d18d7000000000000000000000000000000000c98414bc318b350113186db9e965a238f1f181b00a2265638d914d263e4a71ff643907ed8dca814e5b8d5713baa8dc9d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd7100200000000000000000000000000000000001064b94e868fa82c892dd244c6247063a276cc651e22d09695ac6e73d20bb801a189e8fcef8a711ed471fa3b2c7d19000000000000000000000000000000001561503962d7314fe41f7b2d34eadcc985fa748cc98479a06749692a00a46fb2fe5b5a68f7001a0f89f20f7f42f4463c38f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0",
+    "Expected": "0000000000000000000000000000000003e62892118f11065ebc66c58c86e2f0d3c79541aca8db33bd0e12f034f0e106a76f0aecd537539cf2d793cf823ebbbe000000000000000000000000000000000067e42ecf23e1b0590c30106b0720c806ca71fca0601b499025b58f137ff22aabdc6cc5eeef232fc9a20fb9c2bdee16",
+    "Name": "matter_g1_multiexp_57",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000184a661b34e18b637bca53ba60c891da69fe743d5336d92e811649094c15ecf2445736d0c1577bba4eb729aa7204b44f00000000000000000000000000000000129a348f7fa726585badc23f5dabf49ae095d300056b219bce0ce15f1f6a9fc5c8ebaae56362c3501af3f3de19515143cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a000000000000000000000000000000000badd515b1e0959e77e0f00c7420b46bda5fcb6db59cbd431a1b0ca68c291c6dfe89ece299434f83a980613fe73ab7d3000000000000000000000000000000001266343ad330fcb2cc8242e30a8085cf6995ebd810780115ef881516d4227c6051564d7343e4a5d6bfd210e2e40b91069a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000085e7c22d51db0a45d8db7d5365de9541eb87b81c237fc47cd25c297da4435b4c9b8212c76c929b7c8f32e8d9b11374c000000000000000000000000000000000a4b0f905b48145f1831e453d0372b7861f7be6e413182153cf77d737450a58f378652255cb4516a482d166233dc88c574e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e00000000000000000000000000000000086b97f87625356425a79db717f940debc7a7e932370ea315d1f94b1ead853e3ab6edea6302b6b5b0eb4e4bb3c7fd14e000000000000000000000000000000000fde70203ac7a82901250e9798ef1c671f8d5f878fa3bc83556437b9b98e77f7fe7d3a0f31b8cf05ff6332df0424136fc23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000e629b2d9a57bf96cdc6871ee7dd7675257cf62dd10028201448d8e5b1c0abe777190a868fee83ff5d067252312e82dc0000000000000000000000000000000002102d461c9522542acc185349ea93810c3e2412ebb427f8556b947efe198d616fe00818bedc22765f697507d7678dad972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a32640000000000000000000000000000000015727f52d46099c0ba041be660ca312204afb0f927fdcf0f1afa4cd3448cf3e9fb76bce7ce0da8b4c0048f76f0e7b1410000000000000000000000000000000009dc4e213faf0a8216061b59dd35a135b364431e2be37e42d065a42fc8e42eb8669d32a5f5ecdfd9234487479543471bdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000afb691289f877e1de6fbeb38cee0e36fabf3daf904256d5d6db6e96ce555a9304219bad41400ab6278727e5fe2faffa00000000000000000000000000000000165a54d6db7332b12224d59d8b677517190744c039d9bb401c2e3c4437dbf230b67308fa2d5ae2bf5de282c9ae38a3fa4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd3300000000000000000000000000000000032dbd300fa383541e5c40c849addae3def5a4f5392c44b9e96981dbcedd02252f9bfe4100de9954ab34fae9b2ec21ce00000000000000000000000000000000185e62adc2a44462019c86028c617ddf59a6b1c16071624de5ca755f936e73c47cba00f552d2d79baf60a1796dee009edc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b0000000000000000000000000000000008a574ccaa24ef76112a25b990b5d3b462ff9c43589c9efbb617b45a87bc26eca6dfc6c9e58a12650c202a06d3c86fe60000000000000000000000000000000011f41e39dc0f0bdde1b9e1879741824b20d9237dd7b462272115e8ed44a1e6b7bf82e8ae481204dd8662418fadc63bbf8af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b000000000000000000000000000000000efa7f183cdfcb25cc5516bdb45c409581b6f2a5bd8ce8092dbf9050a20b2ff57c6add39e96a6f1c8d2134a5a37778c7000000000000000000000000000000000a8213977e8512648b6aeafff2cefcd17a14a052791d20236a78e0b462dcac81db74f1625e787540d7dc279846983f647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b8000000000000000000000000000000000287e0add9dcf33f37a10a5ee89cef5240313af0bf0dc183d0c3d6b919c88b979c932c7f141ec5faf012a7f33fe56fa4000000000000000000000000000000001313f591d1da8f6baff044857d2c04f01935b493f5b951cd3538054756d33a52f71be92ef908f016c133aafeb9b9ad2470cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f85200000000000000000000000000000000192d02ab0a323e85e9fa6f553eaafe0d8ca2de63f0fec8139e24805f0785cc85b39908756ab4eb39354ecd8d9440d5260000000000000000000000000000000013997cf706bc8d40b019c2dacf6a7d269e0ffdf8bbc1b4b39e75b48ca5e5e6eba0007b8c55b59530b34b7ebb4c657c57244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f000000000000000000000000000000000a61b3cc7913e45c132cfb06a26fdb1882bd700b32361572fc79a3d2c432644392f341cc70905b86cad2ce52c30e2ace0000000000000000000000000000000011bb3d958600993ec04d9f98ea3f29df0dacbfe6557b36bed865c564595a64132e4036b6240c97cdb38a60533d5a08baad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf30000000000000000000000000000000003d2bc11fa699b284b37d1b45c8dd6b41436a7b2fa09cef316821516801afaa4e1282d717d4eb3d46e54c0208548dd9100000000000000000000000000000000123f8cdf2bcd7d6eab31975ddd610afa79c3c95fed2a6348fa6872b74a6e2816509c71f11d1f272dddb59bafc0f48fc454b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c6000000000000000000000000000000000982c1cbcc39867c7c8c4512392af1489a5e6aa01ecf56abf4cd9050a33536feeb1866421958b929096d2c3f6923891700000000000000000000000000000000104ba4defb74b35d15db80df1f4029650f00b306d702b5934c1705d226886d4bd22b6c88e71b862109f8dceacde3c6d2ae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e490000000000000000000000000000000006e5fc17bdc786eef8cf2140bd8002ea859619d319126fcc5053be9c28526e14e0bc8eb924fa242305069226d766f71c0000000000000000000000000000000017ee60b0dc932806dfefdff2cdf00efc4d5c81a1e84ce48a25db1d49ca26232d4e4cc1f37b34c80375597587dc183b4259b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4",
+    "Expected": "000000000000000000000000000000000c3dbdef90052dd5bdb70f15963091c8fccb5b8720a16140ec96dda18eb6cf003b6f5f7c244d25cf6da34f2a1703a9d800000000000000000000000000000000187456c5af42c3de3d5e08936e8a3f411fd6458d017ec508e48f2b634319155428733634e22883492d7612b2bc38158c",
+    "Name": "matter_g1_multiexp_58",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005a912b6b2b00c2b2c90ab1aef8c9240831ea9ff2ac3a92753054f159f5ee4eadab8ef57eb5972e3169ff9649b886daa000000000000000000000000000000000981b901734dfb3b5f63bcff802536492664ac13dc695960ad89342ea865ac67d00da7130833126a33573d55a9baf128a53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b46000000000000000000000000000000000b052881b3e27d232ec980dd99bd0ece4e861cecff2496472caffd741f2954718d605de98d9c27dd3ff473ff12b238400000000000000000000000000000000004de4bb9e5a4cef93662cee72259b88f7ccf8a495b733e868d76cc25e04c53a65a83c853c98a25f7a551641d54ecd9534d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000016e4d257db25c08a68943e6e0065b375422fc817539d2874279e2b41428da449627e6e04087fd448f651a23fb01816ba000000000000000000000000000000000e80d041b65789b3289a94848ca4b1109028c9fb314e652486e650221945ef4224ca03a693e062b06036898eb664fc211b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c74000000000000000000000000000000000542fcc8d668a827daf3726bd71d7ddeabc440a6fd0c08a4730803be6e76613cc0265252c41123146a5d7aeae93f485f00000000000000000000000000000000109a61920ccf34a0a71f51f4fe7c882b3d6fe449a8c67711dda64f9eb684b4a28cce6e8bfcd6f3cb599adbd0771a132dee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a50000000000000000000000000000000000a65c746a1206b1250598823b9b6fe5df3dfbee21cd31000e50140893875d1ad9fcd4fe12bce0758544ad8cb4cf5ba700000000000000000000000000000000038c25d3c35fb34151428d2f6bb8a459f834902334d195da214ee9fae4bc6099d225588a001f8fddacadeff0d3d215463e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000158f2288a667f475c736dce9e404ed37b18d677f984b6d9dafb051b87901e6fc60289161bfcfa46a3fdbea7b4cc3f795000000000000000000000000000000000d7faf96c636ee38347b0046d333e72d601e20b018d554d50ed63e30c62db7fa20e29b3ea57b1f31e0d7544ad711c96aab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001933815ab2d8b6cef8790f87dd9750bc2b7a75a9d6c425a3e84cd109f5f5ea866e171dfc212f6f8641252e9421fe3aaa000000000000000000000000000000000f8ba799ca5dd885046a4ffce1d26688d0bc6936f3a5a943dd54f89d991500908c81ec4f9b116e73f74d46b67731421bc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000d4ba404254175cdf5c47c08ec158ad83b6ff5b8dd32b8cb9753fa157726271f343cc0cf5231e7e31583877d2591930000000000000000000000000000000000191f45fc4b8c94519d13ab28e5f84e22dae2f82550b44be737728a695865973ff5060a639e3f03904d74717963dcd764ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba0000000000000000000000000000000014d348b7dbace24bfcb258c853b19fcc1637d7ed9b0ec00d4124cdf6d608c6849e8d2f9858afa83ff356380afa1376fe0000000000000000000000000000000008c509beae3cc22f0da64bccd2e0387c05d7613460942d25182605b3eae6ce052540142d5975733cb6554e6da9f473b6e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ef9aac66681015bdd9bf287caff9aee89225e30a7976e9f503a1712fa863c8d6d46a80952a1d94d96a5e0496f64ce5b0000000000000000000000000000000016c66018f43bf585195b256ca106f47077f977701d97f42564223817ade0a520aa3d7f06d868f1e91705232b1d2440d9f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000042d1d00a946085dc6329e852342573db7dda7385e6a50a2660a924ed6202968e787559fc58a162a775bcb115bf1fcf800000000000000000000000000000000162b52027b08b7d91fe0814c7be69414121cfd452f4d0407a2300bdfe9ba81a4561af74d8067e929b71a92947eac4fce71927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000001598949030cc21d76a9c69305f023bad3cc761d5f857bbccec4de6b0f7557395efb2d126382731aca994a5020039acf5000000000000000000000000000000000dbea8852edc6bef41dd317e7d70eb2a5416d5087ec5207af3f5b3fec39a416dd9ccf4cfb5400cca152f173e66df05f75ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000081e898b02838558c1c9d7ef9f86fefe512e2e7364ad824506c886b4cbe947657c5480353e4f72e237da013d81e5eeb10000000000000000000000000000000005353bf2dafb1b9b4f2cf58e16645aa3fb759eef6eb8f516db068d2768851e7724fda5cb85241aee62b4404de2862dfbd37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000028453aa48ad0302804f9cac568467668b1dc0dce2cbbaf280810ead2c0a94e156420f4fd2566ee7f629e57c3741b8960000000000000000000000000000000001cfc5ed80924f7088ce6a5414372d13fd8f6eb3dd83c66d8b8e4dd1d4db2bbbbbc6ffac00e3a880d8a8fb5dc07fb23f06d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b4000000000000000000000000000000000a236833fafc3da813b95f4562804361aaabcd8166780a4646734e4b65e3a1924c075d402404b52adda4902bac7a2cbe000000000000000000000000000000000def6beaad6a180998c4c70f9a8dd0d948a79524b31fa44874908058e9e58caec2e23d5a0787f1ca05a359ca276c840ff56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af00000000000000000000000000000000188bb3e69bdf0a5f31ad16751a12c767c86df80f53f6688ad74cb2fb32b81bbf9d60be1182ea1b6c0d6fd12ef73e253e00000000000000000000000000000000139ce5ffa569548f1bb877c3d573136a8eb12e7c69cd21a70526f8724bc67e0b37cf7149dac3f78377ae7d5bf4882a6771e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c",
+    "Expected": "0000000000000000000000000000000006e5af407ada013abf1451bc9d5c08e5ba9cddebff0cb77175b152fc19bbdc48e1498673ae4698dc74d039a671ecdcd9000000000000000000000000000000000c8783b3ce25445209b9f1d8bd3ba539c01d230c07c4fdff38ec902467d5f7e9e8e660d8997939684e119fdfcc963836",
+    "Name": "matter_g1_multiexp_59",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001731f73d2ef1f87fe1752c9d6428de241ba71506c76f31aa9697d1c436af51de363405f60110e8e69ab268280c20f92d0000000000000000000000000000000001ec6ede05f60685e39acc7e105f60602f0fa3c4a6da7342da755eb34aeaa5adbbac4c13197a2c93314ec79f5da8b90177f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da00000000000000000000000000000000158d295d41540fb1a27d8200ddf51fbb9d31a70fcb639c42b7fafae4a95b90ab1ca777125092aefe20f856e3291e528d0000000000000000000000000000000019670ff04a77cfd367c5f0c14218b5d95ea2eae8577da10f27d96e58039b7dd7e9f7f75c32f99dae0920509733ff9c96630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb00000000000000000000000000000000052f8e8098f9e83eaaec1c2638aad30b043c2359f2551a02b2b95816e1c55d37bbfa6e284f280f15dc174d5f03a7698400000000000000000000000000000000034bc698f07544952274c21f416d8f1281ccdcf6bf53ad352afb15a3412879a10b37e6b8b9fc5f46ad715f9ce7b46e3d0969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a5162000000000000000000000000000000000c2e014d5068adce3049cc326d36ef92f294700ab64bfe170260727117f098727cef2e28dc10fe473a46c98867c618400000000000000000000000000000000005b3ea9c12179a47f7e69690f3303ccae614e06878189b40264f02e9bb26284dde846d704121340723bfd1fe5696410dddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000119e9fe8723883d9ae8c61efdd3ae961795d79409750dd39aa6f0f8727ca2429856f977697c4f81894061da278a0f9a9000000000000000000000000000000001438a4dca0c786062aa9cb21e26b87e92f90dcc0bfa014f654b1734cf7cdb8a2e62fd3836a802a9917539dd068c6b4b1191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000012872f4dca3a9f3fcb07d67c76836c23eba3f7957bb77950a4b43ad9c7ee54f53187a742b13e026f8234df9e91659c400000000000000000000000000000000078b9d597bd9b5ed2f7e0d5f8e4a518012591b855c5352fa1450704a33c3cbd5695a0f8da235411aa99aada88086f643aa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f8480000000000000000000000000000000006c414c6611e00c6e98b370bacf2ffbd7ebeae890278a0e951d6aff7dd3e5fb90f82b4e65dd007a3289f97a9600786a9000000000000000000000000000000000cae4750f99ba13f03d3e0769cccc879a4832210d6a2f25b2696099c0cb184398b7d432e801d23200166a4c53a3e70f3049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf100000000000000000000000000000000122f404ddd6b34938d8e57d9d6ee78c3fdc1b771dd7392944ae88c625f81df63915a87ac63dbb69adf8fdf856a92bffd00000000000000000000000000000000197c20bf1392d4d68efc6ac3bd5d8b53b360e305a501dcfc2e350e3738503ebd44a574e478757240236762db2f23d4310502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f0000000000000000000000000000000019195a36dfc449c19b172ec061b4825e4de85fd5b9c633c953ba7a5617973e61abd0de3d59d441f49264a0dd2e781b20000000000000000000000000000000001430f743ee98a2b2f37d9ecf2a7d4dd4963707fd4cd6ccfdff55c3eb189aba2fb295877bc2d3db9032af26eff6485e459787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef52371375000000000000000000000000000000000be5d90e5fa172a2034667160f635ffc190fa495aa9af51b648125c29bcf9b4b31fea7a7e4b49d91b4a8d081c9aa2d3d000000000000000000000000000000001721ebb02265f698528ae1bdc5bd4500d7612bcab9ea939f552ffd8e9dec1d267dfd25ad4d3531676e2ecde3d2170c4810b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000012b7607bd9f1701ed002b6f72b2e832dad7c9b2bb6eb6368fbe78c48bdfa17b2546574d7876425cca7986fa6839b6da2000000000000000000000000000000001975f41ed7cf252a658e80634872ac495e4b518349487930610906bd396f7fe4af3c97acd0ed3b3f265917560b13e6ef072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000014ddf2cedfda66e12e999d0b280883c546e00dddc0bd17817d6df90b7a614c472cb2840b133eabdc7be39b63e50cd9ae000000000000000000000000000000000b86e0559e27a6061aafc091f93b744a8273032f0e8b1c8b7071baf3ac7008a8173b71f51b27efccba27cb018b25257ff3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d60000000000000000000000000000000010f691744e7094b801c180810b24f6a29c21a13514bcaa6303ae49067bdd001213f13c6f980c51b050a684b525c2dabe000000000000000000000000000000000e4e4cc3769cd3e0e458ded43b5c7c481c17efd3283972919212b877c21aa7abd31cf86ee2bdfd3cf0ef6d730c0907db393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca177000000000000000000000000000000000b5e86c2be33255bf6f2f2aa8b17109467543168c0bb92a9ce19bb64c5f84188b2e9f93ac85d948c76989d9d4dc9eafc000000000000000000000000000000000c5244fe670dcb16d7994b7db8f933ff98744e5c6dc124e057c05d2697881115a99f983be480e30ae3e0ce75081b261edb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000df7f56643536b20f65cae1ce4c67c6bb6def8c9b514d6edc92673ae743a2f4e4906aaf7e3b048f88f08a4f5c9f85c8000000000000000000000000000000000176cd183f547a3f38a86d604f8e76261755f72e7222f3734a456a3bf7029590848970e8836b3570e9a4f3500e54fa3008e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf240000000000000000000000000000000003737e58505d0f4c6890c7e03d5f252aa682c110f5bf5dfe8bcee9393104393f4a6a22c34c773e1dcb78881a31b33a71000000000000000000000000000000001988ab3430de7a463dcc2156db572c43b68e58ac2ee26f1ee1bf8e9889f6cd3250e5d7f9464a8eabb127306af39c13140940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c",
+    "Expected": "000000000000000000000000000000000d541103aa046ef53761e929d50c88796b90b3284668a2a75ecd461ade103f086fc443a3681b6440e6b8a8b9558870f20000000000000000000000000000000014120e26b160a37f8e18faf541bb863ebb425fbd0eb072268a12657b6e5ff62d4c0e504691b3d09b710741820f747b85",
+    "Name": "matter_g1_multiexp_60",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015c91ab58aad72af3364a3d05e2893c756a273b2c731ef421c0552dffcb32fdc4296bb79afcae2d3c8aec6e0dcd27c17000000000000000000000000000000001901b4fec7a1324a34fe403dcc51656145fcbeb4eac94f955f4fcc5ad6a016eaa436878e85dcebd8992e1a177c5bdbf80f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000016df7578f74b1ccdfd537a074d71f2dbdd581f1a2f78875a7d4e1c3cb772aad0d02bf4935f7b08aa5163e82e5a747bdb00000000000000000000000000000000053931dd0624377808705d3fc6e12c4894414c8f6a5662ffd71251bc7725e6d23b7781286b8be1e35eb615bb1efeee9c34680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba200000000000000000000000000000000056f0c5d78c5d4e97fcc7d6c3132dc4cd802eaa1bf18921d039274104b56e8a701c25de6ad33e57997b2e8491d7cedee000000000000000000000000000000000c87632eb73c464f53c15ec127cc5c72fe6a413e74313e80395b55e122108e2984eee6f53742ce4445f455108002398fefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad02200000000000000000000000000000000040f1ed7a9f7c70a546088822088c476f8954681f3741cffb7e6614dcefe2963253599acbd263b988af3764331a273030000000000000000000000000000000007f9d150a4b34b9a6f872f9bbec4d2e0795d02c5411d6b3a844ab95ea87f9330662c8b0789e12a8f6dafa2f7cf2f13a12c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000002890e211b1969c72a15c0f24b21dbf672b2cd33ba9ab79790c07f0734709bf13bbef4f54bf17db9629cd7abfcf1fc2f000000000000000000000000000000000010f13eb17ab7ccaa0bf32b8d4d38760b72fa0fbbabe04017d9d8283f6dcc5500a336339400bdfca06749f7c1e08f748b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc93000000000000000000000000000000000f7e328026c07b116dcb8950273579e0c4af027bd3aa442a41d279b1b7d87d672154d2513669428e8f401db490404e6d0000000000000000000000000000000004208901e02756c5a2430200d562c0ddec0224446b3fca62cc98e9efcfb3508f50794301b026d47eb99aee210dd2f898202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000001309e8c1cd6ca596ab2c9605ed0e356cfb97c4079518b0241d40a3e0e4769a8e58c0ec6a7bda173fc427aaedaa275ff7000000000000000000000000000000000143b1d1bb451cd56d800d71a747173e56b75cbd6fd28ff4abacbc1dd87653abcae715882af29c29a1631850694c5aff8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a0000000000000000000000000000000013bf8880a6c95a8791b8ec37c2188e4c0c2cf188e2fba01a9e7e4b81116b10da49415a0588385156e4bbd45b168467e3000000000000000000000000000000000be052be3f3278259b6e01d9d81afb4d4215b0b738378e56719403e2ed31bb6e15e47c9986aac19f79001a76f35e4162ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a730000000000000000000000000000000013b27128d2e8bde36f11503986c226a1613ba0779de9b25686284d12bd995c83e0db9eb0b2ea759ee81bce0ed2c0c2ad00000000000000000000000000000000128d6ea67c8cc9ce6eb93111780989b4b33afff45a5075691026ebcc607e61b7a48e2549ce8286cfe4a72b182073f373b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f600000000000000000000000000000000137033427de6a6d23e0a2fc17d396114f8f4ca3e56e42936c96029c5b829b3b8b7ea46fa47fa39f6e5dbcd804873d3ab000000000000000000000000000000001986563cad41be453d14ea3f166c2ef2d89ada32a345554ea7c7141f6b1306af815579d7399c73039d1696fb62edcf80b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e472000000000000000000000000000000000f0878d6eda3d119eafa0e5cd0260cd5c9bed5fd3251f0eda5a6aab6b475ad8982b55a0c8c07b6921de77c4e23478f2f00000000000000000000000000000000181d4cc9e77cc1e21145457948923cee50db145dde59520e6ddc2da13c3380188856c220cbace98f7ac4bcd7dcbfb1812810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b8936389000000000000000000000000000000001267b7c2a91132c46ec835a5c2ea1f1c1021449d4ab3c14355777f1b7771787ca8b72b61563dc7587db6318c2661551f000000000000000000000000000000000d9f7257977b3f207e889678b72b584b84bf736bc23081d1267145a886e2dd6b669bcfd8b58414def71c27cae868f39a175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b6460000000000000000000000000000000017c223749282ef77696136edd0b30041b7743e40c2cadf8b491c2dee0730554e39ecdce41e45d647340e73bfe77407d900000000000000000000000000000000025924e40885fe566166bd4c5de6e5bdb3ab993c154ce908afeded5614cbb0c00e6ddd648263f17ebb3d81bd6a4f79afe7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000730c41758d12795c7e5540e4204e43c75a01dc6263833f8db435117429ddff6cf4fbffd6cc27f553b8524710aee9ab000000000000000000000000000000000154c3ac230c725594a3c985b7ad71d98c172de8764926e74f6932f5a5d40543b5060c5d604877e3a8df093927b0b171c6aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea50000000000000000000000000000000005c6852bd3eb4db383e9aa8c74f4c158888ada1c9ba07ab8c7b4abe9c05bca51f0065a29a814892303a42a6f2736043800000000000000000000000000000000086d733e758dd4f0f911df6cae3d678dee3500a53d8a364986d88c50576ca6bdcd10fd31f3cebc7a35f43de1d90ee4bc985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000008445e5c464c4e10fb0a10c97023c5a9b169d042971597eff4380821e44430e3790683c7c66afb89921f06199c72c87f0000000000000000000000000000000017e55467ed664833131b82a2875e22fc5b29a3808639e90741b731d4efc0420b4934fc75ebc2048e8196be55a600f9bca3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010",
+    "Expected": "0000000000000000000000000000000004f03dd7334a0dfbc7cd3b74a8e06ccf23fad0802e44c95712e920a2b227f09ac3d62429f40bef8241abe41575cc541c000000000000000000000000000000001092b20e8618beabaee18b4752a5b320307d22fea2125b3d23c6ad178f0cf2af7a1a1c0af3cfc669929087235e2f8960",
+    "Name": "matter_g1_multiexp_61",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000121d2cecb2c9892d69e6a15561688edb5020dc39fba96eac835c0577ef017191572f8bba780a608c41d53544d24a306100000000000000000000000000000000080c59704a5ef9251654458bebe25d949bd5c7793c438a50019a9a7cf26036f014fa3f024edb767d233dc09710d53daa709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000012be549d6b4efbe6e8c17393390f3cf190abe4621a16e951203747dc7faf6d6ac831582fefaff20c952502fe43e2020000000000000000000000000000000003112e26ed614405376dc1af80b9f1984439c0b67863f5cee6d3c44f74f320e66574aa1501376cf8f924efd83655a72b9ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e0000000000000000000000000000000013b6249bc071ab2f9f048531e6bd27a1b8a45d34c66623268402bb37f6be2d71bb5127461221089ffead4a9f6c708f0200000000000000000000000000000000016a321e986c6301240b1e9258423bb8f38012ad533b42cb487384d9af63713d4b84c383ebd4512145b3e518e0c935b1391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000069ae7a90e9402d4f9f1b4a8a799fd5bec30002683692a700ac3a25f8f0a8ef9fa9e6f34844a6c320877f4b4883f36e7000000000000000000000000000000001214fee37b448c79b5c3097dfb65f8b181f16f0daf54861d4e5e7297db7981f2ea20622d12acfac04c066fcd23169f0294f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000006e8cf07f48627571ab5fd1a6f988723465ea3f741b71b9aa9156c50e13d5481d66f7fe4006a54cb283c6d43eecb4ff9000000000000000000000000000000000ade4e4a949e6dcb45cfedf2eeb91abe406cccbbc7b4c7804b77d04fd7cbd91fd44f0053196bb344fb8ac1ffa37c83d470f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000006b130d66b74b99a2048127c24899ea6ccb0a53c4404f36371f30fc1ea99d02853d4555385a9fa022a552b85422daa71000000000000000000000000000000001824d4d0eebb0178947adf316258d297698ef4575d8ebc2bd300558df914fea04f0269fe67205db1e3dbbae74c0db22bbd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c0000000000000000000000000000000012ba5b9c8a86cb99337a7c4955b1a1b459c8a1a7eb6ea908bf27d5f7e41d5f3423c1ff44b4615c689df14709c703e9ae0000000000000000000000000000000008627851a30e33fecf67dff807bfc5430a77d0a85f1e4f8b790b2b072fb7b86d5e81b934ce197fdac6aea60414a616541a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001051ddad286eaf9c9ae5b3757c53e324acfcb6a1a7d5b490eb9479e337c9824bf619167bf8f2aa5c7f175da534e91a10000000000000000000000000000000000754b16cc6cc813c5c4d44eb4488b04abb659d89cf0dae5fd5f59f257cb396e139443a99b71079c5aa10f8f48465fc398ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd00000000000000000000000000000000035732a9fc03435f3dc3e31af693b1d1ae79110cd46d07541a35b956b928cb4a2de2a16cb8295aa8e8d0c74556b8189a000000000000000000000000000000000d4e762f40fcf43635151631fd6238ab3e1dcf578dcc84d462dbfadcdb621be918f1f0a7015377b5ff9c182494ae149c26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000018f1eb31d3d4e915cd1e0cec33b4838da1401c6667d8ea25209e4c5683dce96b1d7adb4feae7fdb80144c30145d7f35c00000000000000000000000000000000050693e8b9c90d12af4ded25e05df86a3e233425e2f77c7ca9e99b0868eb8d9337186113b078f8083a4273c9411ac1dfc470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb38000000000000000000000000000000000e1ca58d3eb507f977257ed8bdff474a05dee19a00818754e3a85f1cec882b8e3e0296d5c3788b101da669a716772936000000000000000000000000000000000532526ecf42eb00da76db02ab6236dc51a346f0a1271f1e9d721a40a4569d46fdb63e0211f7986b98475d81998dbf8be53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c92690000000000000000000000000000000017bcd6bf54d51fa12356f3428f02ad8ca31131a77951459d32c554e2dc2487be1bb9f10450e5d1f38af3cc7de1096a9a000000000000000000000000000000000b7b5ffa4d08175916fcc542660c85063e8420987b2e16ed2ef9464adf928a4c0b8e6d5dc870b4f00de8bbec6f0dbae3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f6200000000000000000000000000000000151e2e32203b03a054459fe391ff4a4e962ba5e10ff93a1592043ad968c9f968a6e50b5943e50815268a4abe055a1a4a0000000000000000000000000000000004bd116c6857c2f4efa087272df160b765dfdbb842a342f9cd3e5cff006030f32e5a8b60acd8a376378096743000b2fe95f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000ab6c3210ca0b70b2b3bb916f31e17b8632513b15a99c7cc61cd21181152bfc6ba6ebaf8e96a05d0d2d42a9dd3b61a53000000000000000000000000000000001308a33fccdd6cc8990c21fe7ed03bca42e3ae24bf07aebfe6878c2c8316a7a52477c929fc7c67a3a13ed811a2adda7b740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b00000000000000000000000000000000010db7de8485e5504211088ada8924386b36b7dee37170f73469bc77212d56c3dce9802c7599c83c5cc5b18883cca5845000000000000000000000000000000000ae8d817daba71325b57f81301c17f401a6870a13506de2a443602ed44b6b0824e6cb763ef556908f9b3f30010f86394f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000000fc19f1ad220ef5bd76cdd7d3ca08539a97514bb21429af5b1774d4c58a7e4ae137505fc240dd0ec01d1a9eb06a157c0000000000000000000000000000000017ce712d74d68568a945fbe2e0b21c180c58e9297f1f4dbfb0775a133832d4d8aa0688f031385190324f1e8ed65bd5378390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9",
+    "Expected": "0000000000000000000000000000000016cd97de498b8809f4de87bcd5a66c41c231653fdec9bc1ebae0ab3b8c56258430bb13bf400f5f971a1ec558ee66ef02000000000000000000000000000000000cf57701a5038ec26e4027c3cc5a8cc298c2d86e9425ae027dacea92d4d475130487b2743c64459990e88768d8b7a4e0",
+    "Name": "matter_g1_multiexp_62",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016fb67277c28b5665f1b7aaeb1bd70f679b507a6b30f956a1fdc0d522e430cb4a9c089093cfd14714f25cc9498f89b610000000000000000000000000000000018ddf06c643bd77c953a0bde77e80e77334410d76910dfb587922e6dff23e821ebbee2dd546e65591726f9743defaf9a414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d0000000000000000000000000000000011d630f01000c6e1279f330893a18b903b7246031d7d05d80d4172b08e1da182594cd42934de3d1418445a76bc9c8189000000000000000000000000000000000c3e335aba4402bd3c711569e466293c15d89f4893ba91d8690e4eaf4c7962da458471e8c7f22c417abec313c2fd223399eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef942000000000000000000000000000000000c38e3a1c95f0faa10980976f83d85954813faea27c120fc3102de51096f6c3ce89fc4155c6fc878fbd18ffb32092d7800000000000000000000000000000000178d0c64b3b7da5b6f57c69bccbf73e329b18e29e9187a7af31b9b8e480b210dd36589540d77b3041472d9612b05693a49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee00000000000000000000000000000000146c5b46bb4194ec04b5b63f09e8066f24e350cc62fce016b8a25ae57877614162f2733a5df8909eeed2df30374004ba000000000000000000000000000000000cbb312823ea25bdbfc4afd00cb65748401b47ab7dbd5a40905162c1ea676268745af11a2770509eb74aad45663f7f5b6e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d000000000000000000000000000000000ef489c4443175873e33111e9ebb3140ca0796f13ce8d34b30d8fcb7b9130ea0574754e800fa0ad15d71c35a3584e11e0000000000000000000000000000000018bd8ba66d5b67537a03030f5ae56c01e640021ec2524a2cb4b2005ba267e737d27916dde1e94d1f15b6d3e1d480ad82d3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000182acbf5e02a0b1344779f7ced01961f418fa8ce94f939025110823e5d5116d771328362498324e1067a3419062341aa00000000000000000000000000000000174d3a7754b18715722a07ecff5ee3b7f30606c3c573770c88703b6e0abd9ff4aa4bd2879c4c0512f879af95554f47316de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000178c3a28f9333be85ff364329fe897660261092d9bddb36687cdbf5a7a450f27060a3aceaf45fb8acfd123116f195d8c0000000000000000000000000000000015e0a930af79ad263b115dc733560752cbc4453f111550fe3e9448b6818a75babbd0044b9b4f133bcbf16f8fb7586055a28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f0000000000000000000000000000000007fbd9b191af6a797c68ca85df2100b898e3a4d9569c717e3d02c259eb4dff3a1ea948e56001f33a3ee1c74eb966b6260000000000000000000000000000000003b892510d5073bc3597f8f513908077814a7efda2df6051c08f7347433703496e522d70ad4093f76a3e5288044ba5dca3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c0000000000000000000000000000000015d26d3ee6fc5f98584c206466d2c1a4323f597e0ad665b289e76184770e81856482c9f45ff8c891622d8de353b172e80000000000000000000000000000000017fe0582d363a30677bca1feb6d7f16be6b07d6e5d6b2a2080d07ca306d5cf733103f20403ceb486ec703277804e7971b90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d00000000000000000000000000000000108de390a69c6001124820072eb5d9ed9eb5b5a6199c33db1ab0239c447e009df4296f5324660e7ea1133df0c8e6a9de00000000000000000000000000000000040e7b3392a116c7289644f393bfb24d84b76d8378c042d86cb4af861af42374b709cb0ff5341e3ae9d21271c32c0a5914f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb5180000000000000000000000000000000001ed57bfdd0542efe8734b0af448c025eba4d60053b7b45baf682cd310f4c2ea07e708bccaed390c2b061c89c2855c9e000000000000000000000000000000001496190ccfc4bf428706ac344ed691fbcc7b9d6a456f2653f0da421a44653d4b1e9e967954b847a4e6014df15ef48719e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a100000000000000000000000000000000147e5056444c7ea97a319bc71a3ee4188f68b517b92c64f556d22382389c5bab95110728cbb7d525499cc3b2d70541b1000000000000000000000000000000000f05b91c8d05b31ef6497595ecee6a6766f03a006b4c2da408f4d7b7601915cef64be69735c269007fa23e5f91fb07148c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e038930000000000000000000000000000000011b1b7c28754f3dc8b21dc823fe02d617374bdb9b96dbca572eaf8897f98ce9409ce8a63eafcf5308d8236bc3c18b4960000000000000000000000000000000012360ef03ee4dbf0bad68232b8454a26b666d827bebac03da314b2631a45cd365248316f72e991004d0158f89ba5811839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc36520000000000000000000000000000000008b563f6f97fee7e2852b44d8e39ca314963b517116733924d2f57d9c4f202b47fb3fdb85fbca42ffedcee290050ef0f0000000000000000000000000000000016112f264c2b3c838b02b78822d27f6351860d10da3ccb763c1650420bf22755938cb45c7566a2df0e4aea4f0281262ac395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000a8397b009cac789cfd496f4f1237e92ae570f67b4bfe7e8c80171bb9d9cb53201c2ce112473b74646a4948d7c10c338000000000000000000000000000000000092b7425031fc7c328e3be114916a06305b62ffec8e7e93a591fc5f4f9022333cc664057ff6983677cfb998defe249553ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e1000000000000000000000000000000000df6739202d9f1f13145b697d5b78ccb84845710923a0f3bfd5a3f337e200b3ce5390aa185ddbbe8088462926a7f4b40000000000000000000000000000000000d00ec3648b2e5790ca7b05ff32c6bd3249296bd693f520f6d8385f15dbaa9f808d770f9ba28efdc4aa6bcf862c17c4abadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa",
+    "Expected": "0000000000000000000000000000000002665808cfac4b9befb1f285c590d639132adf9d36a4fd460de0b3347303aa056a14780deaaa02072fbb08e1dea06b940000000000000000000000000000000001ef22acce32662085c53b2080df9354903f151089faffa43c5f6a1a306d2254fad240bb1ba3dba4927ea5640347bac4",
+    "Name": "matter_g1_multiexp_63",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000194b906ed067bab0e26b9ff4c0ecd909c6aa23b5cab3a90d1761840b784bd2af6e9f9ca570ba6643d4781885553f3e4b000000000000000000000000000000000e8a480cf75e20cceb6e1d9db5594d19849aee6d45bb3ca7c0311bfbff8263420e0278b7e814088abb69e73bab6368a92c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000019ab570a48bf15ce6f007b528d7113cf423e1c04d9af9497dac47a69deaff52dc9fc4d202649fced07378b84fa1b0054000000000000000000000000000000000e3c2971aefe89a629600a243c7967ef001ee17f9ac452a8131ea44815ecb6596f4fca4f47a316f62234851dc485fc50b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c0000000000000000000000000000000001250315bb81e9ef7de73e709f18003018fc1c55f694c0e28152fdb244b07dd2d7812c3ecc4ba362fdda0707d02d697b00000000000000000000000000000000188a852c5850f471d4ed207d5782518f189cd08d63279c4cf19c76122df0e4663217f1cc8374c7a02d99bf6d59a80457ccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000000df12b5c659c17c808d8e875a1b9c125396cdc3d8a2bd6f99def15d9fdd1fc7fcbb309333cce1b778612d6114bba63b500000000000000000000000000000000019f11577152bcb0229e168a8e97804e8e00a58fc236c8ae59c575c07d6a3c1864b7c8132f245aeec55d999d54745cab99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000004b007a33b0ddefa5ca9379614f698dbdbbfc6bf8bedaa485dc360cc759ffa4ace304fc64071e8f228a8882d5bbdce22000000000000000000000000000000000927e9f018b8cbc2f21b72f0f19994705197d4b6ab3f03e231e51b9cb3d899fd8f8b71feadf3c9be61994936535c61e8338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d0000000000000000000000000000000005decc41dadef7dc4ebd8911af09974686531907e41dfa16c857fc3a2451b96069d06ce1159d47e6f1c97cdd932486d4000000000000000000000000000000000151d369a147cae5d78eaf7ed99623675491f20aa2cec9700053f853551208fa21e085962342072c96d79233bacc7adc1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a000000000000000000000000000000000fd41ed8d5b7e5ca6a6feae98592217dbe676accaf6e73062d9de9eced8af59563f7f441a50ffbb591b8a987c47988f80000000000000000000000000000000001199e002504726f2ce429cdb3da304f9b54a933c1937e8dc39a3a416d068cf46f411b207d9c6862a50962516b2867ea5202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000017cc9741662834dcee7af988d3e4de2c30d4f9e90f2b3f7ad07f756acc793c58acb2a04c2726129d0f0c959f1d3154650000000000000000000000000000000008052061afea4c307df56a72530effa73b34beea4d731b1562de1e985ef455d39b0d6c57008ec092241262dd611ec598a2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f97220000000000000000000000000000000019adb959f4807d3bf7e0616a8a3c02e9babc94b8ee9f8898f2ddbc8fed7a5bd88e83c70c5a98afa823a0f46560e32198000000000000000000000000000000001189adca458e0ef67fc686b5a94986be37c414cffcea5b4fd44430c8d5902512d84200007a93104048160ca3f5bbb9a8892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf0000000000000000000000000000000013d55a4b466ddafa04c5690628dc29deb0ae9115a4549767b2aa22b8aa02a13f1db82dc86fa3df85a6a15463fb0e7903000000000000000000000000000000001488a03340fadc9e8f7552273699870ad444ea513cc7bb91259ffa7cdd5e7377d8fb5510adc2502fb8124d7914af85d5fdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000a847c98ccbccdec67192529c3da593f1d6de5d7dc0bf4452e4f09e93c2c406d6eaea30431ba95568c92938150a00a05000000000000000000000000000000001201397edaef2f9b89dba7f67b22088cb954f95b9db3d1c11bd77aa0dc94def6283af2866a64f0028fdd87b587669f31660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000017f76412c8e679676eb464204348d591221ba17a1c90a22b2482991deee6b61edd7520ed10b0105426a15fa3282cbff000000000000000000000000000000000c65a821d170a9726e947868d861717e8cbcd2438e4d4b8ffcee38eaf033f8f3a57af68ab6314a52952a305db54ecb361ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af84090000000000000000000000000000000000b14fc1d34bd7d85fa96a4d12ee99a6d327347dc63608f94bd750e2096dcf11066e384ba3c68610c70dabae795e668c0000000000000000000000000000000004f3ac3e885cadfaa565b1ec15cb81e3fd4d561b2a8d92a9287bd0de893563676118d34a9ef3bb3112aa534605219feb2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000000979231339f20ffaa38ed21cfcef923fc9a4ff77f7d6fb4df212a530ff456a32f50a77d2e7f6d87c4a58270c006e68070000000000000000000000000000000011ff95871a91385ffeafd8a609a0c562bbeba71a110081e5db6c8035d8176067a528f4d1c6d7dad43b3bb8d090077e1357f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d000000000000000000000000000000000e2c7c67fd50bd2cc8ab18808a69d62bc2d3f110ef49a02259163f8fb152da6ca9cc771d1221d7719f9bc349e68594120000000000000000000000000000000008393769453eec7639d66525d6e875bbde7a4a28c434c82571468d496c4313e12414f929139c482569c003a6c0dccadf3017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f0000000000000000000000000000000012cfa8448935a292911ae6fc175f3049eae5e30d714b3439f55be9970ca959f218157097bf9837125bc8f772968b0d52000000000000000000000000000000001747193c5402daffffe4b1ba9034231321d01966befa174f526014d6c27fe3683eedefea8690b95c8f71fef1152929bd08bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd",
+    "Expected": "000000000000000000000000000000000bedee9e836b3e046bba7fca7632d09a5a25fe1f0fd25cc6ae1d1e07d98ec52742a60bf346285488dc84b2360e0db58900000000000000000000000000000000071ef77988eea20a38fe33564689a39a7113b1715dddc1b212c6edab6bdea8de54089eb7b49b63296792bb2f4aa68733",
+    "Name": "matter_g1_multiexp_64",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000b7db363585b0061a4707a2ee67d6d7220e9209b4eb9a59c02aa6e177c948057826780f292dbdd824d67ca9f78864cb000000000000000000000000000000000a31f49bfddb5c48730e1cd429f128a540ff44b6a5031e7975ec0c6661f9f3f2b79ccd2d13cc1b50d50ef9c7f658d412cc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d000000000000000000000000000000000d4fb266e9fb18590037394b18971cad5840bf89047dc11e52c90284642be7e27007c62a1e331a2f90ae67313efcbc0000000000000000000000000000000000047b518cd6a7d7c4d262d1f9f5f11480e30c626d45fee2d6caa274aa1353035a3c42ba96b5875de36442aa5d4b92d6d257b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000000cdc9bca5cc807710948d5189dfadca2cdfa6fca5496234f13024efd84a37070a2fd51a609c4ed6aab54f8687ac9700200000000000000000000000000000000011bc450e4222090603ccfaf7c1dee67bbd59aadafc3810d3aaa8362fe43f48952320e25bebef482c5d21a541400df5a03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000000015d20abf274edf0c9d45c2675e4af7987e98005b2a0d128ba7df6b16b88784a7134d37d0da2da02557f88d26de33f00000000000000000000000000000000190adb20cb0f5902f7e92f79dd6e7d214eb892834611ef222e9a80ade4c7cf96e0b5f9382b61715e1701c7e9cc4f4ba5976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f0000000000000000000000000000000017dcf175327086e058e4696d689f2e8a167aca5616f2317b7673850a2272fd5742b70eb362b37874d573cfefa25ce3ce000000000000000000000000000000000e5e1af08f6174641aaf4f1584ac40d53c393314dcb1c405263e8689558445196371e2858a4f44d605550fe0f15962223aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c830000000000000000000000000000000008456d980ecc64b04a203d61bdb78bad67b4568b2dd9a123634cefbd7f7077cd9a4c038c0aa3654915c12242dc594b37000000000000000000000000000000000adbd582b0a8ac28ab21961476e163255089c2d362bfe9daa7007a2c9d8d261593eab22a6bdaa9740da81efaa24cc3d5f9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000b02d326ecb5c04ccea4cc3d29f82117f3d97f788b8e70cbb346d43d27e674540c7a94d242d290e55d992eebac546c9b0000000000000000000000000000000013901f8dd68285d73093c30b37419ef8e4b28371474a040a2ea293f7274ec4d6ced0f32686405205324740884306e3a693be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000181bf2fe4bc67a1d10335a0ca9427f603610646de485a7cf039f0706c0a0858ea694db3b3e5ca85317c98b5cd75865420000000000000000000000000000000014b1b652e2ec7d05956705f692860b83713c5cc98c6532b3df50259f27f92d485e8df846883a4af4e46020ae54038d955d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd3000000000000000000000000000000001084f77ef23ac990b43363db38d652f0e6dc04a4bc395c8018083fae6fa6e42f463af7748d71f65b14f94632ca0eaaae0000000000000000000000000000000004ebfd75ecc9cea5e49082e1adacf6b50e4f14600d9343f6459900605c5f36ee51e95408a3005c0c1093e41794c282a0854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b2400000000000000000000000000000000062a062d2ccf5c131e1278a63e713ebcf8a221e429b52b3a7688f7e68a12558fd0f584e03835daa3988233d6a84010310000000000000000000000000000000013e9330d29635892fbe0742d1a8c96ef527b78ae389385a366b6dcc6a04b8cd1d5b8bbb79ea649179e78fc061d23cafd6f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000b131e0623b7f30bad70145cc4964257053f2ead992d28aa5b24c04bc316d479d077af0ff409cd395a86b808bd3e4f02000000000000000000000000000000000380fe6e79e5e0a399365d73244f2962facda8b7b381c111508819309ec5b1d3d8783067245dca26641a966969dcd0ab191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e20000000000000000000000000000000014971f46efae601309f3d16c15ab5c46ac48d2199431fd959cbf4efb768ebcc4f410fd66de04d3280659004a6b54e64700000000000000000000000000000000113e6438dd8088e73eed84d24ec286a45ca51f0fda88c7ae3f1e6a2195f6b11877e606773bb9a8db19dc92c3b0729754b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000006fceb59d8baea4a10aa9f1e825631e28bdd379189eb464a3c6d2482319a09337a78173f9207a58ce15bb1c518b39328000000000000000000000000000000001609e1ff34ad2e4bea4cfc4a993d8d52a1a8676679c91544ded432adfd7fdb5c016f8d825af1c6b8207170d05c10e04a7d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000069e48b113b822cdfc02f2f0efa02724193a5f032dea902b189290db91c6e4550fb33e2915eaa8e56ef329d6c61a0d95000000000000000000000000000000001426fa2fe7c160e8e32c3252383a7c7967b3515c3f76eeefaa5c76f02b3308d86ab95f9a3a0dfacfa6dc12eed2f3a5e8a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000000c173c6c949a7f21df4431025ce16c18b1008c75b8b1b23d03122c7c6ef714b5741804ec7aa5ac40f6b72a1a74ca5c340000000000000000000000000000000001b32d54f8f9839dc39e08bc6a5f0efc5db9bdf487a60004ee135c30efda577d187d9b9e68bdcdad558f2028d66e935cc0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc00000000000000000000000000000000037d1cbe4534b82ee79b2c957a6eb19d18dd3f3f6faf3313b0ce12a98953190aeb55f9d494bbac4f56ca6986c65f7668000000000000000000000000000000000734f505be94516149bcd6302a2c9f2f9b952c9e614c8e90b5466073a7e734ca203fcca242cb97abe1c532d7f59a783aaf833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94",
+    "Expected": "0000000000000000000000000000000012de1cacd791ab81eb49d2e259f36abfd90e8c7ed4f9f23a4c95cb48cc7bf9df233951291342d1266d016a7fcc6b9d53000000000000000000000000000000001938d574d02b8802a45ddf8488340f2397e93125678400cfbd8e64cc3805704bd5f85f8fb2f64a96ec1089b03d9d1c24",
+    "Name": "matter_g1_multiexp_65",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017387ec261c6dea7bbcaf4537182de1620adaa5842cf52c8b5b6cd851ca3c27abafa584547db7366455281d82d3f83ea000000000000000000000000000000000246dc1cc9773db7151e05d131398146b28850e97f6b13694d696be374095fb153b206723afcafddd4b3b56bb15bf778b16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000a909dad5029834df0202c298a577f897a376b205812d79e0bb58b91ca11262a766dc396f69fd2b199dbfa52670515ea0000000000000000000000000000000003737873dec25f011b24543071a61590646e4319a2128eed87d40193a22c47b1a6c0f807ba3115a7e45823e5a4bb433dcf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000002b959df6a1badcd306209c1f3c4c496cbfe4f00995cb4403b04ffa6b9f2c8dd9875a2747354a653a74fbb605eea50b00000000000000000000000000000000004d6b15939c8e282a5995c8c0b67fcba3171b35ecc039fcf32d1e96671698d8a9fd2cbcaa7019cfd01e56d68cac64fe51cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f421290000000000000000000000000000000006be65e97560a40394d9295fac0029a0c889bf803f09926359a1ac40deb7777cea7dc5d2c4a9600328605fb994f87b5600000000000000000000000000000000128249d2137f7ab1c5622a8eb1c59ee8ed792fe6b09e4d868c9d9ba900a8d28bde5b783ca591f79e1d729c99e10d5cf6d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf00000000000000000000000000000000002244c1e55324a4aeaa07c414cd3f9872290e729c1cf1c05a5b1de3443e12b2335cd36f0e84f11f04b62af37005ce0ad00000000000000000000000000000000151684aed084d38aba7127434ea73e63219c4f5b4b92017142d19d0330417fb2806e31440e0bb7c9fca2bc8dec73072f02e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000009f0c959d995af6cd0d45750cb35a28461d0f791e59b2975ba4edbd7db015858b41b3b7c5c2da0a4c6a5d7b4e855329d0000000000000000000000000000000012d495ae3096c2399149afd00f640f8840c3f8e5dda5835b62ef0dd8bb7303f522692efa72c37190bf6808ed3d4fe8e89b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e000000000000000000000000000000000334cef31670360b5ac7550b55cb03b770660ee79816a2742c059b2ed6cd9d5c53c5ca54793a9912ddd7603d975c3f58000000000000000000000000000000000144f221db562b0daefb20238a527a10ff1ccc279eda86723668f8ada40b41a2825f82f5ee5d619fb193b9c2b4180d932f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa0400000000000000000000000000000000037f14fb2d51b25cc04768d50fe26c1e156a3478b80e32da980f7e8d5692a4cf282f75e5d8be325ccb4223c7ec2c04af0000000000000000000000000000000004eaf2c069c96dcf18051a2c1d7ea876af67bf344070415894c07b3dd69330d8ca18e1313ff57d83b70e5cda3c9ea8582d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000104c1f5bdf874c91020d410d8fe74834cf15f341b86e66ac693003766484cffaef2c57fab5888f02f5ebfe1b9ef2fffd0000000000000000000000000000000014a2f6d185c2989ecbb766179c0b0d0713ea9714da2ac555bebf0522ff00766ea7e39c8237f8515224fd096d2b1ede34efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d80000000000000000000000000000000003ebae6a494d46ade2dc7d4630a420b519df7086b57a33da178616d4242fc20e4d02d38b5d00675d2cfdb51adc1921f6000000000000000000000000000000000edc56e6eb4aa8556225d928408702042d49cf3e1410e1c78d8ed5832ecae449d17c9d8f2a89ffbfaf01bfcc85ebc1669848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000000f96864832e7a9602196f0abba78f456300796d5afc18b0ff0c5c23b61865256fe5cfb960bcc8f73231c21b1084cf04000000000000000000000000000000000c59dcca2249b5b01c1b54be0e4114ae8228bc150e5ac7593bdf96136cd7cdd7562eb936ddc5c9e42bd93abe91bac5b0cbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000422c390e56fa27e3d7d5da1b2ef00a29d5340026becefc095d4cfe830208d3b94cbf5ae6f4506ae45d04764acc8044c000000000000000000000000000000000d1cc7c147cbedefa854fb9764352a9689fd157cb2540fe070ad7f6f3eaf761b4670ab9334de4002fa811aa7a01aaad479eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d6300000000000000000000000000000000018000e31f0ca43417865a1cc128f33383106f5bea71015e9e77cc5320cc3e5704e437ae8d84d96f2c4530c41bfad29b0000000000000000000000000000000011a74c3779c8f351d39db6745210972f4f299009afff643e944f30dbc4367e17271c688e1858e6f79b6636787fa56e6b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000002a6c7367526da989ae093350b7c1ee9013f977d6e75563f996e1f15cd4279932a3e4060a26262f27403966a7e0111f200000000000000000000000000000000038a85281b09e5e68d7e31bfc323c9c250b42248cbae47f9c018d72f3e69ec572779d7f8fc6ed3f027499741565274e5eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000b788a0d47da0daa1f0d802d340e68f9bdb5ddf91875732b4ae82f1a89ebb5787ec1c9f539b82e3c94c36a5df4ddb4ad0000000000000000000000000000000016f46ff55e9f1e19a332ba4ba43d66d2a11a2728a484a719ddfc9e223b54224db55af162e73a8f5c3355f0127a6b7cb652b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000379d868d91304b24e19694937402bb685f064ec5a89b49e243e2ab7eff5ca0a2023af9828c4ce9f768a1d6488c10e110000000000000000000000000000000011a9b9432ab253d47e8dff776c8b5810ecf7f7aae2ff36ce06b87436b4e20c22596c7713def3886549a36bb535a96fd1e83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384",
+    "Expected": "0000000000000000000000000000000001a50ddc4d4542aae264dce69f5f5e93353a9eba30b177b33c2f74ef61149a301599beecc4d0f78078dbb9638eea526200000000000000000000000000000000160a2fd67df885232b06d3dead7ffca93cf0434a3e9a5412ed3b4c342819b44aad5385bf5ab60bc2b7191722d1372183",
+    "Name": "matter_g1_multiexp_66",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008788a699276abcc2d8e4a35a9d0ddcbd8006a809799374ffd56ee8afa1a89461602d92fae6eba7fdd4045ba34d917e5000000000000000000000000000000000c8e03ca0da00c6829e2d7c49360e67e46ce12e0c99cb3d957119bd9c8bcac8e03cf32ec71db2a18568157f4b44cd4dca4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b0000000000000000000000000000000001373b4a0653f48c205b36bc50541a43abfcf35974a584953bbc40f5cabdc3ac2047bb86267cdad1e8f00766682d2e6f000000000000000000000000000000000faa8c977b4db7a3c9e65d9cd5af4ffd2d7d67fb038d92c1096124312a98d94e6dc3f3b8de73eeb057cdeec4bc0e0482bd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000012dddf5b96d0dfd2fd619b634b086ba5d5f25a53e93938559a7adef7b988749ca27d14f2ddbf5a9e7e6c1914403a45b900000000000000000000000000000000044b5c8041fa805cf2ec5a243814308369e5af534729cc9608fd17583a48132809f507cdb5b76fd6597fcababa865ddaefbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000017c5807458fbb875593ebfe83c49ac2493ddaa15671a59032528e0464360c64bf564f9727959108940ccbdb8d01f329e00000000000000000000000000000000121dcb798111976daed483f4efc95f968f5212cdfaaf0497eab0419a1b55c7ee4e2ea26716d0c1a8aded4804228b8ad860d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000717296a20594f940a05ed3ce4bf2b7779c428b33a297087e08b2283b33228a7d4d5b9c49a71ce036d6f2a078d8344540000000000000000000000000000000000fc78f64a461fb66ca081ff4d67369058e57e5ae0e284562161fc3244bae0b9c70ea6abb2d0da6cea4942530c64ea0e386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee8000000000000000000000000000000000584bbc0c537e7f37ee64604a134d5fc21d838c51a89c608ff9e3684357ed7f931fbd4fa4a5a56d20304d6f6f072316600000000000000000000000000000000191ea3bf1016b6402dca2856845017dc49c74d06bc3c5f10de379e04302c469015f205cfc97fa142727ba7e2439c15575ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d000000000000000000000000000000000f1ff007860ac58bb04d992d639a5f882c3c647e76e2d6d96888a55648f81ad8b7edb3dc2b0e56b6f2dadca73db7cbda000000000000000000000000000000000fbb952eff64505e02e0ab34875d7a79c72ab724cea7cd8f28df2578b50f78601b9a9eb4170e1b7e8d94d9db252e23c592837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c00000000000000000000000000000000011dad65f38b4c24527ce87f8893c8331a32a3d058cddcdec9f8708a3bd1e31871cbdcf944ec14d5f101b8d138b2a46c0000000000000000000000000000000012a6981c5100177e643dc421c5917896455107c5995b1e969bb18b4b2752700a18281f732530af9684db180290dcb138127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000bb4dccab7abf3f5393a338a3a07fc20d337ba2ec3b33227e8c9a832900f347d582d88cab123dab489daf471191538b20000000000000000000000000000000008589985e2952db000968a793cc0fb5bd1764ab1ecdc6f278a11dd4a1de87823016e14e9fdd682e6c489192b154cb997ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac6148900000000000000000000000000000000056fd39f2a5356870a3ebfedf35769710c16b2f2eb4a061c936f6de4f9001990769795b1c756d7c67623ce3931ea1b5a000000000000000000000000000000000b7fcba295d34fc38739c4b36689653731fa46e6029bf8e38ccb6af5ae08ffc09c86abe0de62230844a66cbde876f52663da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f000000000000000000000000000000000039b08e7110b0d17c41709378f75844379c662f7f3dc480bead6bd4996de2d8889f458aabca142d50ba0e34c0c327970000000000000000000000000000000013363b0da7c7dd343ffcf6cc5e9ddb5b51480b04a472c38f90ee08cc97507f5dd665e15a160860c6df4dfec154c1504bef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e00000000000000000000000000000000034edf693e1b201be14c496860d508d12d9180b62cf3bd2407b8ff95b93da67dc0c4c43344614dfed516d7828ffda4b20000000000000000000000000000000015246f388664b1d817fd17831f85d84cdaf31212f093820835f201c3fe6ac99d67cdcfdda3c2d74d75d5114e32c65cd7bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f000000000000000000000000000000000982b8886abbfe18cfaf4c0e16c2e7045973f5efa27e5cdb56443a22f5434e2456cad041bba3e6deafb072e5fc40f10f0000000000000000000000000000000016a45f684caf0eec143cf8f31ed5111750d8c4f1092651a471cb88cf534e81df117e3b0e8238270d3b03aeedf04d7a9f70363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a0000000000000000000000000000000016d13da2900e2b2ef8f6ae295bf16d100d451ac4709455c55323988c71ea6aef694de0fa5a33cdd7fa2512d3548e39a70000000000000000000000000000000005795677001cab950d1a7b802bb14f9203036f15fb335daa5f0b0ece4bcfcd3b31b581b439da46452e4e688f16685e37997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e88000000000000000000000000000000000b7d422ac85798cb5ef5548805bd6d3de20ada4994fc38355e92cbf0d0c9da356a5e9e1674a50a017643f652f71226e8000000000000000000000000000000001715616f53a501acbaaec470121caac29827b6b7bfd7e689d8e48822d2c464ae50158662e69c1c232ecd09f5ec946a7a5ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000010e7530ba600fa531878ad0f798a0ede2d025f149ca980bcdbb0e4316e8d2e7d2b248619369e36d21dfd766aba5918070000000000000000000000000000000000ecfa746f1cadbed34fc1ee3483307de400ded69af4a7dbb598802b7908495519b0cd4c1fa98c9cd8e82daf8b3e836e03c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26",
+    "Expected": "0000000000000000000000000000000016bbc2dfb25ff6cb9cd03b0cc91f53c6ed9be933746d59e78c7251080856cf47c12bbecda442840675b03a812aaa5d98000000000000000000000000000000000555b0683640ef1795c918f5d10e8b3e07a09030fbf26eb34bebfbeac34e13c93ecf53d54dec5c8654cdedab2fd60570",
+    "Name": "matter_g1_multiexp_67",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f08b9765910d97ac42bc31d4b4c8bad5f3db3fe5374f11ae1c08af41ee226bbb4b0869b039fa81a935025de11b1d1fe000000000000000000000000000000000ea29999ba91652e2e6dfdf77595b44da8e5cddf2e3ae6c782dbf1f972717833d03478bb8651bc0cc7946d813371aaba2849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd000000000000000000000000000000000997b39892bfe0c67c296135573975801ddb99d06de02d96853f44336fdaa25dcfe253708583f415d882115ec68dbaed000000000000000000000000000000000a88e2f75817ce91c7dbe365d67aca52186b5e94c735e5893bef6aabc61f015f854f9bd110d3201be6f35147f9f9b8fce6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba6990000000000000000000000000000000012a730eaf214a874448e654a06604c4b9218f163b979bd3700b7a7fa3856b814c380532afce59b6253344da5bffd684600000000000000000000000000000000182fb293f9a63c705501aa0ec7ca72698d7d4d50af3a0f68ee849cd3f82ac24aca2e2ee813f68e708991a97e58f2d03d8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b0000000000000000000000000000000015bbe08935721cc6199f9255379589a4512c178bbedf69c82a0d9cba22b285730d4f27a3629d92574b2c24dbe09300de0000000000000000000000000000000007aba01238f2c4ef0192fea78fbccf2e669f802a2822baf067632daadbc1d07e70095c14bae959a0f706092b0be10335c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e40000000000000000000000000000000011727d6d6cff667f5bdec92a3b502f9d9fbcded2ef12ac058ac51ccb4064443b7a2671e9ffa2fefd9b121d89bb4ded1e000000000000000000000000000000000960f8ac1e52246529fcc6f8f7cbaf42677297c00022d312e0deb5fc45d3685bb33fd68c193758258439864ba4a073e5650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd50000000000000000000000000000000014b3a9296c87b54f8f51b935a8d9ec0af44d711e3109e75fe34f07d0705e9ebf0ba5e81dd8b7e3c4b4f862570637a7f80000000000000000000000000000000005b834857b8629cdbf514e5ac2e0e2a45e4374c287bab5e4c163d669e7b1a36c72cec1ab7d857e28f2633a6e5f298f55c067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ad8c626ba39823a33d17a4f06cf17d29e9e0ae3f28db0b369fa0bb4b7343115fb3ded39862381822c3b2d74ab7f70e800000000000000000000000000000000117230d8da035f40c181b50c12370f159748955f63ee1eb61e8242e476575e9aaf16bd43b7e79a35ab4e2da20f43fd92b448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd00000000000000000000000000000000027764a17af5328811b305c21b0fecc54a3f225eaaedbf453ea4c0724fdbd481873d84b1a7ffbdc7f1cb07c2d1efaf5c000000000000000000000000000000001090ec8d750ceecf682de76d4794f9a8bbbf3a3f4ab591fe882613c1b6db0912696974a1f2ce349bd8c79acb4891719d441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee350000000000000000000000000000000015e707430eae84b75946f21e1fb0b6ede203b843671911923efd9674421a92ff13cd900bee1b27d70b8e8cbeccb165930000000000000000000000000000000001263ed28f531d8197606a038d7d7c3e1d732690cd69f52533470f6fbef193be5e63d5af0dee3aa8a73a23253533f8223020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c0000000000000000000000000000000007fe70102db7df6529f732b5cc2b1caef0fe03af9824a5097922dc0b07e5ff32bc195fbdfd7b5e4b2bbcd75b1badc6ef0000000000000000000000000000000011b40afd78bb5e835227e5a08f94f7c70b06dc010f5a710a025f589521543eaff27d789d4de10fd4020879b45bc0a9dc82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000e4df86963d375710c681c5b3910fe79446e73e00613bd554ee20f47fa9e2b0cfb6c14a29ed6dab0a56c49708fc624d80000000000000000000000000000000010029bbd62162cbca140c56354ea070ae3f1028e438c70dce31e7bc8691541e59e9168e9b689c19d177d4fd68f8b1081338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b00000000000000000000000000000000097f3f61b164193da313d88429a4f34b0ef2f864ad8fdf7183c3e1da02dcbf0ddeba9bc04a7594516e6255ed59527e110000000000000000000000000000000008133f297b8da5dac5e1ac3db3073587b92a5d821949968c125e5c9c79a19b5945ab47fb0ce5d6f4269231596b157826d29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee7502100000000000000000000000000000000028cacc78001b805c3e43e92fb8c4477778ce81fca9068240e0088e344cc8201ed5bba52e7ee09d5ea6f982f30d6ea2e0000000000000000000000000000000012c5db0995324657574a27c48313674d2ad3aa931cee78ade96408c5e04e6f5f8eae88018511ff156bcc787970ec40ab675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb25000000000000000000000000000000001768f68b0ec15fdd37c3ad9445e53a582ab5546f9eeec590b84e11f5a72585eada71129d1b93a72b334bec4df57ea4c40000000000000000000000000000000004d6e137e66243b56bbaaac98717061b36545c1c3e24801e6e054bdaaa6d28d641821a51233175f5e5823b7d2b7b42cc544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff70000000000000000000000000000000008caba9658e420fa17950c995efd00447bd5074af9b57122240d4e709229d382e371d7de867005745a35a2a7d68fee8200000000000000000000000000000000072e0c25435616f157284b48fc8da4a3fdaefc4f6d484e071cbe648fedf30b5da4457852d7715741615317e21110d4c2bb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a0000000000000000000000000000000016c418a3b3f054188d6891ddadb19c00ec629a3ae0f49cb1b6801a9db0afb1b5e473c75cc8e9f352adf7ce8ac738ae0100000000000000000000000000000000110b8099a39e40541dab01e10314a0cc10fd2277c8766c7c73d32d7d0c6edd3ed3984c8bce249de4776920dfa28ee86994a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc",
+    "Expected": "0000000000000000000000000000000011104fff0cde20e65e73185321d26defcbce9c9d3c27923a2366730c1b560da80066734c61e0c42bac1da45787f39e2500000000000000000000000000000000066efa830182669715da4dbafc6434be27a2e95140cb39738fa75cfba2b5786d27fd727df4e9b0195b49be2dcb9c2c1c",
+    "Name": "matter_g1_multiexp_68",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000188c13fde41e3c7d84ef3b5d1fa869dff4bf02cc8448ae49c6b72cc005bd06916a5d0a74fd770bbdd3d2c58839840095000000000000000000000000000000001637ed432b4ac6b5021aac0c9d5f084e1f6c541c101a3d650861f7d860572795f04e986c4a890ea0ec049da7c6025fa3f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c27700000000000000000000000000000000063a1afe2f64f1d04f7a5aa727cbd0e9dd9b66234120118db1f8fc3b90ae50cf493c3c4a48949441cc1e46488972d39e00000000000000000000000000000000049261c42dea531a6e8fd82f77605ad0cc9addb23e429f03f1aaf2fb8d9dddaa89101bd5b5b169dce793de9bcafc3b5ddcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec0880000000000000000000000000000000012ecb0f3bb6fbb4802479611a25781ab09c81ff7175170805ebadbc5f25d2c40bcaca855ece57f481160d49af008d2b3000000000000000000000000000000000bc4bccd65e010b69676d3c226057528dbe08271d65f83a918b06969c1d5303cb7383645fc19548eadb83649ecc54a551d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c67000000000000000000000000000000000e3dfb86c2eefe0b25f117484a9d693702496124fd0dda80830a4e917bc418a793519dc269fd4932236f73506ecc949300000000000000000000000000000000140faa4b38ace6e80e5d3fdd57079c215792672ce651563eb013a90e66665dccf6bfc9f9df145d34894e3972eb524f86310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a160000000000000000000000000000000016e70554f8580b8e9c5e421c6a6495df7df846ad67d5d4334e9aa89f7e3fae505a2d335d21624e66aa542dccf38081e0000000000000000000000000000000001090383d5f42c056c291a4c4c6127315849c647783a556aba3dc41c52545549d67560bdd697fd1f47dace750483ec9b72fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000e8ba15ec58e5de08935384a3674418942311beff3887d7b5b81da0d03348791e4b17a06397e33e988ac6719f4d6f5c300000000000000000000000000000000159841665c915844ed85abdec0c1e78f178df2511da4d3be989f27063a8e572fe746b20e3aff056a63f4832d82a7cc75aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef00000000000000000000000000000000167848a43b68c8f4c205613e1440f940735d7d44eb1b046e63ce50fe8d7acc5b2c020fa936d6e07347a7858be57870e5000000000000000000000000000000000aed7f9b7108aa4e7445be41bba256667ce7587a867b9b8ca70d3c42155521ea3bebbfe01bab038969721364eb758be10227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa8000000000000000000000000000000000699a81c47bcab8342b11a207af072cededbadd374aa79f6b401e4bd5d429a0443234522a8955b3a62a21ef6697410270000000000000000000000000000000008ec25a0e0dc6a3c8906a1b3413f522440d56f67fb780545fa022026c6faae016108cb6eb23d6d6d519a4aa790327ae6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc0000000000000000000000000000000009c2158ea44c3b590df30e15f97ebda263670c1bba0d97ceda7ea674af0e61f0b5928fe0bdcd8f18efe5340525259b4c0000000000000000000000000000000019a5534906413fdacde78ffb03e6564d8beaa155f86e4f19be2188854a8709e82d2ade21621934c1aef8be723ea91a141a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec000000000000000000000000000000000165b15830a84e786d563cc3c5117a3e7dbe9dda178bafd225503467ea4c9aa894294c4fda58734eba9864796974a016000000000000000000000000000000001285a2be50f38fa6a068b75386d468d8fc1c11405291e794d5aa5157cc81d7d66c1095f2fd9289f1306f74596e9b5c21b206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000019d5819c1c4f10c83ca6f1596e6cf9901611c1407d6d7abab989333b37a8c21cc3deb039722a51e2dec161c38f3ce74200000000000000000000000000000000136d05ff33253260cbbfea0390e78cf66845afb4ddd0b684b928da017fbdf6b0e840431064e6e6d5bc8e417a74c811ab3a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e000000000000000000000000000000000593e66a323cf3efa13fe19cde7a3c254c90b23bc836e1f437f4a4b85790f325f0746147aeb1d0447022bb138178bff50000000000000000000000000000000011a4b1222d0b49a27e66cd34a12f252296ecd1aeda435035f06c059aa3e6ba69acd1ae6d7da394f32ab78538f4e50a351231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa000000000000000000000000000000000613bef17f6b6b39f9f6bde785a82d2e4c368ef231d8cb89940059ac2c16bdd707170b660c0faef9e927ff7a72f6712e000000000000000000000000000000000fc85913ebe30f0af146df556c6984ab442b286fa70ee00d39a802f4c76c3e41cee68802982ea42fb25d4bb04593c0b5393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000003da5997b7b3677f6cb03fe969e328549b1c0b083a6df457a70f1276d10e01d65feaa5a36cfad19dbe41cad9eba2fe73000000000000000000000000000000000345176bf6a03a49ae0b6d89d07548ed47dd67dd620e5e29066d09a00a7e3bd4b7fcb79b114a046dcc0c705068f71b50d412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000105ed7acf8c7c116842dc159553499aac7b8beb36dfd7eb717c571ad4ee1f86b82b736b72c2936925afdc3c739e0ad56000000000000000000000000000000000618b8fbf8a2aa2d1030c6304655b1df3cf8e8260b7b2d97639bd857d58606d0eceff7ff0fc1a811396552719407daee5b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f1000000000000000000000000000000000eabffb8ece92d4b22ee47560984b3efc33913953dcdf5e22771bb8db2cd8eceea21a2b14d70b1d467d692371ff499a300000000000000000000000000000000143282a2cc502f477be295d5fb2ec847cc988e43f72be848464eb4c1dcd0b1ab66a6cc30dd4b465050f6c37e8b8e08a7ab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5",
+    "Expected": "000000000000000000000000000000000378217eb65cf5ff48e40ebbfbf03c307aabb63955f755a93bfbea0c176e90dc986a8202c6ea4e9ecea176068cfc62440000000000000000000000000000000012ac5e1a0c75ac5b96af3959a65eed134dac6b35559cd923facd2062ee06cb3ae2018db7d27dad2927b3e9c2e3dada33",
+    "Name": "matter_g1_multiexp_69",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d6ab2022d950cd2ad2f0087a079e435634a1e24008d12a349836cb7297defe857cadf3adf061e8b55ece662dd36ca280000000000000000000000000000000007682f1ced1ac2aca6ee9de682c7a6743fd32264eb0a087eb1df7c520c5748cd598be45213b398b073dccbb6bd67b44c3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e000000000000000000000000000000000927b5590892a4b897ff2d6ef6d5abe32bec8233bc5f35ea9ace2ec516037a8f3d162b0161c91d4e06d80d73528a6ba400000000000000000000000000000000064d3d8340eea43bb2d54dd6f5d9d49fc2275ca1ae7212329a11ed9a94c70c80584cb6ccc1eb653f001a1c1c4306e702b3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000322791d0e53364128288e40b621e6c47324dafcf86e9a8590a79eddc8d3e6c9d74cf9721115550e7e33868ced39cc4700000000000000000000000000000000112a246f82756d88f30e74b3f5df21e18ffc9cccd713e6509572338ccb4f52cbc0c3a6d5b5c112e304f90ffb9179238521953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b8250000000000000000000000000000000010bdece8fbaa604439e942e2c78aa5904cc1a0532d5bbf624794d3f10f4b64df30838799e374982feaa7346c039c08ad000000000000000000000000000000001085372e79e1046c870b1d49a2a8ea83bcddd6bb8718c7cb340dd3032739319c54eb947d518c7e17d6e603dd3539f269505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000001551cb2abe299a01cfba81bb306457b662ad57858a30d55e0ae0c0f5851483123c388ba06ead8ec4fad0b1e4f69ddd6b00000000000000000000000000000000159e5ffc459d38a6b1e49b30647939f37c0d4fc02b83f9dbac123d64535752977005e0cb1232ebaa7cf0bfdc203ccaeac4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f0000000000000000000000000000000004111e81afa9fd09e39df891cbb99d9b62205777bebee33b2914e24570db46f75db5dbe2e9831c50f9717dc317f05ceb000000000000000000000000000000000a999eb350750cd505ea9de43945cfb0c9c4ea412cb0f0e769e62e47d08f8d50392d3a5e821f1e9c947990e6398b5ec699762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb000000000000000000000000000000001496d3b0062e9e7166d777d90553545ee7dfdbdacb355fa7ecfecd65bcb96321aec0fd835b32c8bce462c87a2b52a58f000000000000000000000000000000000ef77e6ddce1e0eae50a1c663374c31a0c5846d6c2d777bb2f4831ecc806ac28591c3ab0222a6cc7821a45ddde1ce23e298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000194bd82f02047bc08871e431ebde41327a60e838d3a1ce6eb5470ba21a9b863025c8663f7d509a73847ed41515fdd3ac0000000000000000000000000000000006c9303814ddedc68b0047b5b2f0333cf226908dcb14ccc0aae4e14456a0c83eb4f498d559a649bb64bc78900a788a4b177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e573000000000000000000000000000000000736fc761eca44cd197ec6fc680de349f96e5294e42648825ce9262fef91766a8d7a084e5b598b5b47d947548e0c61860000000000000000000000000000000018eedf050da521b9af0ce2007cd664e2760320056e14ddb162db5cae78ed7ec859bad03fc60caa06081f0c24bb130ea4cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a990000000000000000000000000000000018a8e4887f0c08dfb7a741858580a1e0ba7e7ee1959284ad0955beb186e84a5d503ffe4000d5a8641575540b6b7a3885000000000000000000000000000000001946ae0b124fb60fb4dd32181783564dfb8ed0616a220d5650fcc1f6968ff70dc74535c71b0cf1019eb038c19cef0caae03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f5030000000000000000000000000000000003cc2093935fcacc3fbe4429868c7b31fe8c8b12c1184e2181dc8da4d56b9b3ace85ad8d6b850deccd047eb002acc8fe0000000000000000000000000000000008cebb95902576d96a3a257ccfe76bc727174e08d70492dbc2132b9d5f534de3b6a7baac2d90338278064565aa67b22c804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f10000000000000000000000000000000003a03e6c183afe6aae9bee030f46086032e9d81fe337e7e1c77ac6c903fb33154bebdc15e81422f057ba1853c1f7cf110000000000000000000000000000000011f5e4fff35ad1d6e2d2d4e30ddeac28432eaf13fc7c35f5a90f7f8a17de0f61bee21529b3db3633c178006f5c5fc403ed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db000000000000000000000000000000001986f950d86f35d45dfeba6c3e484a6da296ccda2314d03adc37bdaaab374aa9011e07e6c8fe056e66b9204c5e16fc990000000000000000000000000000000003220ebcac8189b30f6efe6051a2be1001b85a7f94d9ce289bf6e04edfdf2ff17b17702a1ce116445d763ed1c0dee645e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd50640000000000000000000000000000000001000e0934c04c36c621d9b308565cc75ff58f6c1c778b8e0926b4d22d58025edf8a853139667ab3d3616c33d8a98afd0000000000000000000000000000000008776b843fa3b1449a0879616b3a37bd5eff5c809c077fb0274fccd67d645439a79a410fe2c2db44f52887ea7f20c6062f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000e7b037fccbb3fed299960355ff2c6a51562814ac797ed6b4b770ec565bae5ac998eeba19819cf2b3d4e91591e7f051f000000000000000000000000000000000143dd07288b59a279de228ea59aecfba3275a87fd8307252e6b5d567bde87088a8a8f52da57cba4c0fa0e2aed423241e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d730537000000000000000000000000000000000b41b673bab477cdb21ae5f1c04922f2b8216d7a1423a6f6b86d4c33f0b4def9c553faca2798cba20a31ee7d71422b21000000000000000000000000000000000b64686b90964104f8e79bf9527f452d25c3c8e9d53e715d884e795d26e391dbf510d72fb2850fe66e35d31444814e650cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5",
+    "Expected": "000000000000000000000000000000001673148063c4f68c59d371410a8ef20cc4516c579523e2d7d2ba04b03fc5a481a30fdc6b9ebaee1138b28a9463011227000000000000000000000000000000000067e9b23610ac41d41e0bfcabc455342360d4ae4c75fa0303631e97f702b9cb660b84b5d422e8e9a74890b803c944ea",
+    "Name": "matter_g1_multiexp_70",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014cf7c57711c1708096cd33a9efd4f907112a3d4e5bad1767ddc6fb408cb7ac3f866143000154d1270c07b4294480543000000000000000000000000000000000a20191e6786d94721067d6942731110df277047541383ef9847fed9e4b8599723fd7cd7e2ca2186d56986feb8dd24d72063b046a71c2674e35466657a85d8e02253b42517b033619e31a53665917212000000000000000000000000000000000cdb0c20ac2c22a458d2370662d665005cdd8c662e318bb8652a2123f2d65d21c8e150daf51d7874c69bc039bb6163710000000000000000000000000000000008480687d726eefe93d5484ca375557e109fc64f60666e1b8aaf440100aa15e76aab6f821fde170046d2714d8986a1fe92fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000011f20086905f64c21bec021e5726c05158f892658cd69536945a3337a8075994caf4fa16fe66b85e3e0ec71ae5b4c09c0000000000000000000000000000000006d71057aeaf26fc685bfc0ca071126a81224692b3eb90e37a1941782b8f65d45b6a31567c6e3d2935d38e9e02ba08654484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df80000000000000000000000000000000005a186d0ceb2535037b22a6455c49b6227e54c6e6dcdd98f46d996f23301b208a87c4bcd0608972961b67c523f01c99100000000000000000000000000000000142367fb02fc6b2cf52a78e4cb1157d273e9fe13ca721e0fa725f2a6dd0b4897ffe7affa25925da47fe851362700c31bfae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000a758981a1524501c48ffc9990b738d51ebe38a0ba07b2b049110c7aa439253bfb0491a66cc42eb241a47d5e963db75500000000000000000000000000000000082adfa66bb46b97f14dec70b970469478d73d30216201e7467a927ae4ab9d93747b07ea69c406dfef789226afc4240a3168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a2000000000000000000000000000000000de8dddf04e0c2d9ef1887ea598030f2bf3bc7bd98b8b218d19f661ec4c9a47cb087639f72fbe97afe9617acb162bd1a00000000000000000000000000000000127e78f1f41df717e5f76692b9ecf21ec0fbaf9b1d56e51b37cea02143f3b91eb1f16a65046527339dc65d29435a2874f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda0000000000000000000000000000000009c37c64ffe9bbf264c475076ccbe6638653574ea84b30f4eb2601f1990f73fb5708af6007f21e4dd52f23ef5041cb3600000000000000000000000000000000170177e891c421ac91eac0dfff8bb397d7fc531e0fbd275c17cb4d894d18278a40a6c3093b92fc537244798f24eea4e92d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d000000000000000000000000000000000178d45abe2415895e0a550005c76522962c0ef0193cc7475a52f4d9cec9d4789406b7afa2872485722ec034df4446d90000000000000000000000000000000005e4253dde4284944b2083e07b04940cc72cb24d9866c953564bc0e847b72da59888e7a08cde7aa7c0753cda94a6e97c53bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000e48f11dee27507acd407ce1b810cfa8d0ff4414380fe26aba6c608784ef756d605c8c3ba92592ce342baef8aa927bd90000000000000000000000000000000000e604525ab4ed10f3a9a688774c6b27e679fe456190e67689959da296b650dbfb75610dcf54b30ab891c40784a9b90ff944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e43100000000000000000000000000000000199b1367bc3aec710e82f98d3564debe9e01ef2beb878935df4ea98e3725391e873d2661e2a27d778bd29ce6f66a9b24000000000000000000000000000000000e77a3ca6bc4584cc1c3df35b18402b75936f68f0f70193708da21649b6def59f1baec4d6d1a2733c369cb5d9a6b39347de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e10000000000000000000000000000000016ee4a1a3f99134ef55398e96b86a21708388c3ddbd86746745e24bafb062a6283c5bdd771f15eb501df6a19920162d4000000000000000000000000000000001001936f457d8241a4929aec1d3769bd1955433b340481936f9443c63a6c6ddb3be4f4e1ffbf62a5c4b154fa9f8acba3b0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000019aad23ac037d496eeedaeac9248842b0dec15478f62ab61d000a402cbdcc240186248ed931fe3eaae5a1d7153d3e135000000000000000000000000000000000fc1c74c4d8488edd92b42ca7c27e22a4776761829b06efb0d1b2cfa37738efb276cc5121d926665b99497841afcbd394aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f88990000000000000000000000000000000014c557c44a90fa9d958d2e701cb2aac1c0204246fae4ba7b060e74e5d4ff50630fdca918c47323f5d0eff118c7595a040000000000000000000000000000000015821312dfed1e0bc2cfb23536baceb7ceb45c6c5a5f15ce0d4d67ef261a30ab8154b873513e2c44f652b93989cb6f1badde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca00000000000000000000000000000000001ce81da6511eae9d2e155efb4f999a5d75faa99eed8fe784c7a398bf4b0e135bd0e8be8d9dfa2aa8ce9c63e091cb44b000000000000000000000000000000000695ff4e598b9e469bc62dffa214418536a6f49fa5f05680e09783b2f29bbfec5d43d42c969ad3b62c25c6192e328419b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000002f155e83bcd838ee8840996a3d8b0bef77334b0e8e75c8e4278411ae1012bae06959e8394dc4d1fd4ed5f07804b41870000000000000000000000000000000004daf1423e319b18dc57753d39777bb127b651f5294fe03a15dc4974eef8cffe337704c7f867fcb4c2fbac382e444a2b0cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e00000000000000000000000000000000000162f530647fc6290626d74753efe315e64dca2d73571dbd4416dbb41b07e8ddba40b3dbe170922c64fabbd937c961b1400000000000000000000000000000000021ac62abe15b0f1318063428d89f22d2090050b913973de571871125a391affb1cd595f9c596c9dbeb6025fc8392e48e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7",
+    "Expected": "00000000000000000000000000000000127c2a1365d966520de7aef12e66e55d807b1a3e2305ccd5a78547fad6e6112d60a206412bf56866ca1b0af1b422c7230000000000000000000000000000000003a613a2e2abca1f42d3ed594a3b99a6cc51207b249aee2b5829aafb1327cc7bbf99604473b4048c4b2033e1befbf14a",
+    "Name": "matter_g1_multiexp_71",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fd31933662cde0814cea424436ddeb6a668e20b69259424a652bab722aac70b3582cb641d53bff963ead87ef5dfe1090000000000000000000000000000000007d17925b0309fd8c92e52c1ad67937efffa7ae3c783177a82f1133c8e3aee2b8fe71095b6b88b01576c5203d7dc8c3f75888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001782f625bc3b25168b1f5b388b7963b9d158c09abbc0bc4c0bf5332e1817fc083d3d55124532fee2665c536b6923fe3b00000000000000000000000000000000118650bcb2d32f4e83257cfebbe8209c2c9062ab0eb805ae2977f79ef48af6fd78e7512b331933edd087054273eab52c18ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c4950430000000000000000000000000000000014a69e56a173ed13a9e2568a8af49d74c74dd67609ca58744f96f9213197b45de6468d69ed084ed8b1b29104322ac517000000000000000000000000000000000739671cdbdf98251ed4bf13d23c675500cb66344731ea6aa66ffe401dd6daa8157676fc46b413378b8325ed4cfe804a24a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005c95d722f8e50603951c21421e8532eae710929e976d76f28c050fb2b093618489c5f892198ca822d3f287fea6eb83200000000000000000000000000000000077a07fe1348e4b6b2a46f444137eb86bf7c58e320afda3d75769a9839fefd9142cfcb75da1d1aa0e7ce84b880ff1b3fe4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a135000000000000000000000000000000000efd66388da0825c846b6437b13ce5014b94b20cd3a713bdbb41a80892820ea7b12b6f6720fc7aa6e6756d496ef5ffdc0000000000000000000000000000000000adeb6281219c324d14ab4dc29841d52f3f21b512ef0a784454a01358747684afe22b34d4ff1ed29ea013d47d9059c838573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000010367597f1deb2ca9338b59ddcd8d02440ce8cc34c71a6ff93205375077c00f3f1c22e00ebc9fb60de7475400976e1860000000000000000000000000000000017d148179e9671959bf03fa1c95ab608fe2fb8b9b1a650f524a070d7857dbb8b14a67a813ba1b22e4b71df52e46c42c002257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc163000000000000000000000000000000000797e0eff7ff579b0c5161c8ee06a2b99ab44e515045e83438952226046bbb4adf3c8d0538a0bcfe27a533444e2bfc9f000000000000000000000000000000000c556867cb0238505da3b55321df66611e6a018be4e181a1ec121dd55c509d501558af880a2bcc71fcc641edcffdb13076b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c8000000000000000000000000000000001357812e6d93272645cacde880754514ee42aea3690d9d5d67e3bb5ee4444b7a3473ea2af0fc563d246b4c3e8ab5525200000000000000000000000000000000176c413594ca45019a174848f765f69e138e70dde1e184515c6f3012df4c5fa39a28a7e202c6c563db7681b0c4f8b3a9c9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000c5f9145b11f6af0895eca18ba6338408ce40ae1b25f8c04b40c0410a6c69b0144541e2ca1d4303c4c55fc407ca11b1a0000000000000000000000000000000010f2a09fd8b6cffae5a06bf50597a9c0d496bf5529c8925c1141cdb25ffd3afc6b51cb5d21d97c99a8d27281c657bd842467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000000c16911df03f532313d162bae1eb57c947059fb5d776ce3bfa661bad92ebacb51154697593e2321bbf85d43ae7ea567000000000000000000000000000000000564ac0f20388ca3bd483033994bf76b1ba135e229487e0c8aa10dfdec1887c62651f4cc0c05622de6356edbfd9abfef2f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae0000000000000000000000000000000001f6de29a7cf8a89e3cb5befc926eeef59270b929edb68e9b0cd96feb5286e130f1f7c0e0d46cf2a411e499be21d47a00000000000000000000000000000000002b4c8ff1040a843a0e1d691adead4fe3d5306f89f83724a891abffec3c742a3416fe54c27c97bd131730ad364373ed0474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000005d07fdc2e2afd92d5f0f1ab6541313b5a58868d1707ff0cc9e4ccdea0c105cf9cf1f6e52d0dfd22c70aee1f7835ee90000000000000000000000000000000001229bfa1d5c5e4aa5ed0f6753dcb40952fc5446b0c5d0d90b22a7b2abc388cc18e8ef74bb2370b6ccf036f09040f62dceaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec0000000000000000000000000000000019f4a0cb264a617986898fbfb53d1bde9cd82c092ad86e608750ffa990d6926644c717f6a63279f8061b066f0c4e86fd00000000000000000000000000000000082f1b79a9ccf56b743e14caf0cf18b94f1978d164d9a95fbf87ce15c3a9b414b098fb09654c23ed2981249233e8baae5158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000011c516cfd059a1b8ff75df3b9b6b135c2a52371f1a0dad631e96d8673f1b26daff9e776e9dfb225e9881635a28dd34c5000000000000000000000000000000000bb0dfd476dab29ccc80781a92f5a998b8ba2464d76df001440240957eb1237d9d210be62c9187d7f17891e837d52635d66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000928c4d78abffa6517742e617ff8efcf59b48efe0b55eaca1d93a434b84c42f29683952dd08546dc1b88bb63a35b49c7000000000000000000000000000000000d63b1f625ca9d33aaf51f8251a088642211a474deac9931c3ff8ad45f80782f62f71f014505606cc4a96f91c79a25709acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df00000000000000000000000000000000131c7e90e794b09da6c4936747e6509f94a467f38ac7f4bfd0c5da88d1733d1b6871a9df498b265c65695ab3ca889f9e00000000000000000000000000000000190e566597ec19df03c473b8ff4ec0cf24168f47c89525b31b1f3592bc7f87540caa8f91e2eb2f415c05502f72673dbd0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8",
+    "Expected": "0000000000000000000000000000000015610fcdfe0bc92be1f3ea82eb0b0512ed5f448e0000951fdcb0e637ecca602f715674aab1b7555c8f8bdf886d3aa62b000000000000000000000000000000000103c3ceee1b45d9d8912887c40ca16dcaabab3dabf56b8f77cb3186b267a74a5193048ce4165c419cf727d6e9925ac3",
+    "Name": "matter_g1_multiexp_72",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cda9f382fd65f5ab92cc560477f1e3b69d0efe355e40ad3bcaf258509b6ca5e179deed8348836b0e723d5f7ca4c43ea00000000000000000000000000000000037011fda0d188f8d17436d21b9bce522cc9f8e4f473965803b242694f403ecee29d2abccbf56ab0a1f2fe5831c14380b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000f591bf508a5076b26dd8ea3b0f7a92889131142b34cba3f35a9b861cc6deeca7378d5728c0af9503441144bfc82038b00000000000000000000000000000000156067cc00e82414150bc05ca2d0c0ce1c19e5276e00434754616c9021120bbf9d1c00df6a42b76c3ffeb6e32f8fc3eefee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b2350000000000000000000000000000000002a8128978ebfb99e20ac99ff5b3088e8eb95a7b6b354d46e775dd1662a27d5adc9513467690f377d4a13766276bf87d0000000000000000000000000000000012ba903800e9641de498d8e286c7ee48b48f7d36255823b88a24cfb67f8d2b7b6411ba3304819f588fff0d730cf130e428728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb96000000000000000000000000000000000b107e62453c7181b26a3accaa624a612b7498ccc50eaf0d47bbf350b3c8c54e940266cde786c608e42f59d793e45eb000000000000000000000000000000000194c2c3717a8284051a29586e540bd9e456c0169eab0412699865c12226521796a55d598f60280cdcf37b54a24c931040fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000013228e1a6346683320d8acad4a5cb1c23cfebdb9d9c451ab81335d27e8b82297b38e1fe2fd02651a8dce3838144cf650000000000000000000000000000000000c6d54add7bdaf9ff8158680f35be7f51dcd5c26a698750c7eab857140b6329157bb7aca8d7c68f107ed9f68b3a076aac14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d050000000000000000000000000000000006e1e7c15fd14ff3bab1e9b8f8b7d6244c707744708db629ef4146b8cefe68c505ea034c180fcff95a452f7e1e5433e1000000000000000000000000000000000735faa57e1c4349be51395bac55a331a04851b41d2ec98072c5ac38eb7bb03e00ed64bcf32c3eac8b34cc6e26769c3ad81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c60000000000000000000000000000000018008132dcbd9455c3932155a0b0c58066bec4803eafb0a2cc30a93b0a335738b52e6cff60b379fb04b5aad342baf11800000000000000000000000000000000149ea542cf34141fface44046aee2f6c436218374d095bdd46638ebc804bb0c9a7e1e3b01c0470bb6efc7749b8f70eb73a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c0000000000000000000000000000000003f12b0eb97856f3ead3d46a8321481351471e558add0ac4e1f285e7ee8a1f2ca88ffedbc8ed21df31d599e80b8f0e94000000000000000000000000000000001315ca27c955f3826da43745809fb1759f0f5d5674e4d94118bf2f2ea0411c7d9cbc65f054c41ffbdf196ef24eb9afc55af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000cca2b061959fb70d383f7e247c131f51920e048dc136036cc301f1ae6ce13809551d0a8074cc05409d124e2df6536d0000000000000000000000000000000000a9692e0263b563cda35f8497d182fc05e78e7bf88267aaebea1f5f41bd1cadb39c61431bfcaef208adcc9118d4dfb546541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c600000000000000000000000000000000005bfb5a43e3643846f92310e9d5439deeb4fdd6b5dfd3de2ab3a40b9b8b3461136b03c5601add616dd87b9a72e81856a000000000000000000000000000000000212c6c42e24a3f11c30b7751f37c0101b8a071a3d56f2d10b6c9f4f84ae12079d8c4f2d216cdc7ee93abf8b9d6973394b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000008adf951da1f0b64c17f84031985bd1f3561ab44c80c339c4c753a7c2080e0f57c41b79b6cccb75662e8642ae0a94451000000000000000000000000000000000d9082079fa53008a03f58b87fe0aceb121c6c004493f3da7ab37f3236942c8ac01fc28db26b87bd2546f93b12577ee57e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000000f352ce042cbbf1adcc42030ba8e0dfc76b4ca313e82a5c5105ec56266977dc83626c9a9b3b5c25ef459a6feb2722140000000000000000000000000000000009443440da963a7e64d90e4642861f3f5399835fc2fdefa7e87708c033848170eb02407a6a9edadad27cb02793055140fde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000001699cd7355b0a0be2946f8f49648bb04a90c6bc8ee7fa258a357455864022db999793771a2e66adf3cea5a54ada82d6e000000000000000000000000000000000a3ebfef4ba72cbccab5e93155429a14fd61c106ed6d2c0db0694c4733b6f1730cc9f34a5e9598c60e189b8e4943efb56f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000be5ac701c69b81cd75fddb8da92066cfc9d0d2aa7f01495afd87e44076f9f022179b7d4b4781d0b5c6c52b498b63dd80000000000000000000000000000000006f2fd1ca9a34fb09d922a76943b43505f2aad16489a138668f08b9f388c67e46a4d5df7387a1c3aa23c76954913abfae2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000019489b41d8b1f2e8ac09cf3f0930e092afd74405e213454c458cfe44e5f393a88713b62715097a1aaf01a188e8ab07c00000000000000000000000000000000018471d616eb66f1dcfaf84b7d49f632e0a5306888e44c70710bb61d4afd440e5f692eefad842b5d37762cab649fbef34762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001088372334c452709f81b57f5e5c148e0f88dc29dc9a118abd6911c46ee83d0c6b58ec9b854c15f519d33d281ac9e21d000000000000000000000000000000000394a7e49f32e4f7d27f276892002ad034dccc8263591b5d941eb2a5e60097e757ea67dcdc5242b755fce30c3b3b64cdffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843",
+    "Expected": "0000000000000000000000000000000009e68140307d9309c32763e4de857372a61733060ac582922e3822e46c28f67bea87a75bd98248466e8938abdc4ef54b00000000000000000000000000000000187dccf66e6d0593ac8caf6723033db545c43cb90a5f5486418325860b692ffdf8dcf23da9103dc65336d3cec2577a4d",
+    "Name": "matter_g1_multiexp_73",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000083dad213737f1789595316285a77c859c469b9bd0cf08c61884456e4fc5ef0947847186bd420af252d822419b1de3ef000000000000000000000000000000000795a6ced1d34d91bf5ddbe77fee452699a1b32daac270b4e8661259dcacbb9c8c3776043f2e149773427fe109818c87be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000005db83053f9824116b9d14ce0173c2243a4a8506e161db7f97408dd6fa77b65d0e0a32e95062699f7aa85cc9be448dcb000000000000000000000000000000000f20953295dde557a078c981f0b988cd9da8c7469fb7fa3361f2386c7dd609bf80ccf91cefd797eb3a4f849b2cec4370bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c726000000000000000000000000000000000000bc3e40ec1b6e863f75e4adbcb8b504026d0634d1d3769f7795ed2956bd450e68aebb1a9d11a71fbe5b51bc79d97aa000000000000000000000000000000001703e1fde7f2c740ca3224c1994282e633292f86095be38dde3673b78729db84bca33ee820532aa92bfd32728d9756404f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d72840000000000000000000000000000000009622c13e8924441b0043770faaead6db793ab818532c7323d9ee9a8d118cfd2a578e1c13723c8bbdd049b1d8aaad9ed0000000000000000000000000000000009da68565c05aa28648c0d0a0e185335b4e58903982fd361fb57f544c1f253a55e8a233b341537d78c4f229ec5f935a85d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd0000000000000000000000000000000005818b813993d7c346cd70190e1e6410974e64e08fb0a70721a0ee430dcb0d92d302943836343e274b26c69030226c0d000000000000000000000000000000000ee84b6b251c9d4f7e7abf843c73f0456968e23e79c54d8742cd5967737b9cf9ae8c6030722134c376c7c9433b749563feeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000006870d696789986991a222b988c3623ffb51ce96ee35140e817887ea37068ec77d8131a97579f2ea29a5b45ab55ec5d90000000000000000000000000000000016b203c189343e67e10928c2a45259593cedb1a016491e94435a0823522010469729bd69af9c3bd6f4e71e96c7d8ca72edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a600000000000000000000000000000000191a7f7469739ef4da1fcfed877b875c4b0af45df7aa9055b7d5f0c1360e4c4b7b67958d03125fade281c663923670040000000000000000000000000000000014d5256c242839e0951390f00affb226ee6c906214d8d7dca7e4fba7eaa8b1944fe4f1f93bf6ebb21b4a8585e000a76b7a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000001968eb742dc0e128c94c1f0dab2ff3b0d300966537293ea16856e5f3ce5e12164d9c52fa59e08481bce84f3f87dae8f100000000000000000000000000000000098ec0e7bc53314fc8729f4688b99c3d87e7e2770877a30898c37c68a5e0a4459851b8fa390cab18e7cf0d325d906ce4f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000064ef5e6fe9de3e86ccc7a8b809cbdd945eef98e8e6cfa82dc64ba94070cc107090427c13ddd3bf25d542696d5de44500000000000000000000000000000000116b4babfc4b1a7a36405f597d4afb478c024805495e1a412a3ad5e9ec5f01dc47411ee6e81a9477677b89291e91c2b68031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000014d07ad766b50a6150a50decabc56f04559d1b196b713be88b5543a673ee3f4499e42b58c532e38dca0101f639aaa9fe0000000000000000000000000000000001678e7e66f44cff05163ce249df65063c4ea2d2517a31f42dfe76f67041d7927ad4b0efa4b30c33156b14f5127af190cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000745f042a917dca8e35c8f0301612ce198f75144e145a3c3041f4ecf893360eb0b7fdfaeefe78733bb88010d6a7b9bb3000000000000000000000000000000000e8879142826593a2f1214eee206ba69b7962e9a10ba014af5daccc1e4a2d3c893fa47eb533cd0c0a9fc1c09d389db19d983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000009da4fdf5b86facd674ffe6d91d03674ebfa3aeff5ca2a659777be20109946b1bbd759d4dc2d9e859d587ce50ec3bf01000000000000000000000000000000000924985f655b00fec0bdacfc6914eedab676a962e21ffedd83be646dc17f5cdcdd3f43a9ad7ff9d976e4828b4dd219b7a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a700000000000000000000000000000000187414507425106691a2dac49fea1eaa14783b2a5b79a945fee44957619793be1a68aa110867ea405a076d30568ecf3800000000000000000000000000000000034e932247b81bda0a54568f2887824028d69767b9131c106a4d204c0b2bfb929b9ed7b3fce1e354e405aeca8a28d92e7ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b800000000000000000000000000000000027361b6341bf8985d79b6dde029a9ee54ef441894f34d60a3324edb502bdc78ef60789e5ce342c240db0fa91bbbfd00000000000000000000000000000000000bea3c850bc9d0860241fc6de65c203d5a11e6425faa503c37641522fba6fcd31643209329e6ad75a3dc5e4a4790db4a297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000000fc7095889f943697577c8867b411ac925ea7182e47a7cd19387dcdd48fad5e558de3d80e3036992ba5fb8dd7925774700000000000000000000000000000000160f1fbb346c48a6cab0105d343c55b3714899e931e7b4e0abe68c4fc7067189181afb9c040d41e4c1f7c4e2f1b8a63b1b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b64000000000000000000000000000000000bf1740d01ece251c0f0ee4f798872eda7f5a4ad3152d86db12844ffa88ca52835799f0b2601ed1bae6d4850cc889940000000000000000000000000000000000557f274109f745af6cd965d6e706b9ea1fa3c295cbbdb203ebf049c1070595ab820efad6652b1f1ba4e2d331b5bc6da5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6",
+    "Expected": "000000000000000000000000000000000c57ca082c662618951201a98d88170a9aa076fd1fc48c8ababdcbb09b63c793725261acd370d03d28ea47c0ef176f1500000000000000000000000000000000110234e4c6a99c6d1ef8b43fa897990d605af9b469045dcd0ead52b03f9f90dc441f7fe5e624349788746802e4f74015",
+    "Name": "matter_g1_multiexp_74",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ca64fb3ced1d15f94e9b234e6f6fe59d805eb0b50ae29c9b31514ea5c6e79542688e871de6ace893868fa0eafdf46890000000000000000000000000000000019c60ebb5ca4e605e3b0eabdec53f566c9b96a143631be93250260560e47a2ff6b073e432cb1f9104ff913616e7d81c834aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a60000000000000000000000000000000010c867a070e161939458694cd4015b76bc4c76eea884d9dd309d6642436a82bc76ab57b2c0e2d3ca61f34645db65f2460000000000000000000000000000000014d9df8b34369bb23fbeac29aa8c35b346992d847fc2b9e3b96345f4a2245fa8eed505daf17edb4090726052be75662308ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de000000000000000000000000000000000017ffdfa10cc8e1a8b3751312e5bcd09772462618b8bbdca59a60701a96dab651fee0dc755969e1c3a1d2aa4c11e48d6d0000000000000000000000000000000005c2aadea5a4b11077a2a1641eef2d3bc40c2d8001e9853e44bcead87cd968ce41ca50644ea0fe1d0ec4c2d7eda9dcd058c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb4150000000000000000000000000000000012bb9b8a1537c2856d4b2bbcc6fdec6d69eb6196d795bb0f1f49d8a886076e7fb424f63400134622941b2b88ea61b8e30000000000000000000000000000000017206fbf293f1ca1f2a0971b920e702ea39996058111ac2c041c12f58f67037a3840955e1185b413859a6f845b333b58ae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c0000000000000000000000000000000005eadaee4c48dca28f9469e882ca8ccb71f82bf1f2cb5b7f50b2e63a05e78415b3c5d0767a27f19a0b1c88400116e5310000000000000000000000000000000017e95e480a145b5e897c7a1ecc1b21c5a000248f87e74bfecc21a3cf8a06c04fd075612a62145ac089f208e567e4e12072022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c0128710000000000000000000000000000000007b834503ed3e1cb74738db29c91f415beeb3ac5b75bb2cbf11f4a9cd1608ea6080dd1bd50c195dbf5ab6808fe9d6594000000000000000000000000000000000eb32afb90ecf9923ec22a483ffeca3a15d358013e64e521aa42d3db1ed0397e07a85321492e0693f8f041f4f8346c6c800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc0000000000000000000000000000000018f208e26fd7c03313df686e27bb6ea09d9a998764e805fe6182ee221cb9ff1552e4db5feb91b3b2fa595bc32f81898e00000000000000000000000000000000137c06c3f9eb27f1c0546b3c7ce879218a309dc37c0590fc3e151d9f7fd5963f0fda201faab489dce0043c3180abf753a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c0130000000000000000000000000000000016fa49bb488a35ecbfa9e714235790cf6e7c3ea46e6a9a424f59c63d018206740e9467b0575077e86091ad6e0f9f56b6000000000000000000000000000000000197185b7c82ab9e6dc8e2a71c94dde328c923eedc6e305d8f36f4b636e7662e501917b89b33877cb2094b523c969dfeebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec7304050000000000000000000000000000000006b797e2bb8c0c2a5a6ef8d9f08241d42299efc8af049245c254a2e4bfd122a01954bc596750942bf7ee467b22bcc528000000000000000000000000000000000a655491c6381e81473c23565082544d9f223042c82e241b1cb8ba48e847d98a373fc68b762a600489cbbca612defc61fa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a7000000000000000000000000000000000d7b7ba451334d1391a51142c4b7cecf0032fa6d28fa7f36d2d43ba39c6418946244da3cedeb2bdfadd453eb4d54d05b00000000000000000000000000000000127655a7acb4e3271a188cfd287cc1af890756e340eb4648bf3ea3e469644e6d21f63e64f81ccb55b9b1e0a62ddf58b5f0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf211000000000000000000000000000000000129442dedea08bee8661b558bdf8c22dd391900a501f1841c77359b20c1a1ff8838829baafd2a6ab5eff31e3f9ee884c000000000000000000000000000000000ed7c27bfcfbf9b41c833fc0d8573d7b28a6d788ea3cff4d96900559cc63969ac1d5fd366fa705357626eacf402c2ec560d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f90000000000000000000000000000000013a11df012f8a55c263c5c55df0fb682e685a5feef160d77d26db7125ed08e6605f3d67878ec78fd064487f30228f4cf0000000000000000000000000000000019292997c874c72ce7c432f20da1a338e9dc433f9257b7353f99b5b531a9997bc3a3405b0aba89ab5a2f1cda98dd8199006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d600000000000000000000000000000000006a56b816898a1fc9954495b711c493ace881e3989207b2f862dc41c5fe346fc2eee18adfbb9db67e774055561af00600000000000000000000000000000000013971cff1e9a6ce35a7ae40118a007518bbdc5df5939a90fb263a9c345a70f4eef2f94ec671ac6964390d0478cfbf728f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be40000000000000000000000000000000004962ef115a4288177df2f0e4665e5d1976fd027f7f87a24ccdd0584e265e2f5cf0a7490dc7824f5eb26c9569bde9d6e000000000000000000000000000000001544f43d961320d59c65563d5f04341a8ec3e6e64fc2dba7e953652232d615c90eef2c859525fed99ae6ede2c39f510a861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000004b250ff8bea739fd73b3c3463617eaaf3b6bb9db11c2b915f7435996bb4cff3561fc268d2cf0db1705711de522382200000000000000000000000000000000001c428a889955fbb5fcba993f2defa5906ac7b6a3fee6c07f52de8d54b0665cbea84e89a0af3523213fd19f7d37944012dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be5500000000000000000000000000000000106fab277085c88a7d664587f67aac8de95aae908177dc513fa24c8115fa23db44eafa7075b036242306002ee6918da80000000000000000000000000000000009e832e0d01bb5e89460e2cab772c308da07414ff8b880288c7b55d6390360924b806c71c9f9762d84d8d3cb3c2f6a6199893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23",
+    "Expected": "0000000000000000000000000000000010224cb0e43534025f8ba7a7c426355a2091473ab16a752a9819d8e5f3eb5b1b5c0891b1c0cc0017655dd8aa7102cea80000000000000000000000000000000004313278c1bbc33ae2c3010c50be0120bb3ec794d9ff77fe97154438606e5f6f04c1dbf8dc01b827643a31899342e1ed",
+    "Name": "matter_g1_multiexp_75",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001812b7bdac748d2c0f05f10edaccd351e35347a4a582671762c0567f55e411839ec0a776c18cd71cc6de0c3a3b8bba820000000000000000000000000000000011afad9a48c42d8c3bf74dde15d7b744c6c141ea57e133c9dde7fd762636115e0296a647fc3fbca8144048721902973fd8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000f4900ffdc92661bb33e7561d08ce7757ae71a2b5ebdf6427922454044c6c6695e249069e83f3053e8a8a0adb5d3d3d2000000000000000000000000000000000be84ebce32bce4d58557422c7a8c4020d1bc643a99b00231a4d4a06d5dcb56bba61ead26fbf07079e9457dd4364ab6d33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f0000000000000000000000000000000017399488c58e24c6e1f5e9a04291930595389536480ee6dc493cafa7f0e85410bccbe5c5841a1a0e495830be7e32c0da000000000000000000000000000000001055ca833e53172ac1d2d3d7c6fd625dcc72556e8d49bb487a83e56deabee4fb672b6cf7787d1231c760c2b7e9d4e55e7894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde48000000000000000000000000000000000c57fc0c785d6b81d4831ba71bf27f9af318a730a9502917a68397678c7ba22f21335ca2fff5bd495676faa418fe21a9000000000000000000000000000000001012cef9cbc88b838492b6a0074e0e5d24635d36d669288acebfe446157a202443fbaa5241b288fe418e1fa50eb3e65cfb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000000a2a4bd7c7a79c2336b05bd5e0558736697c435477d4d0dc790033366ffcdecac3bb9cf48d1341835f7a42e17af833c9000000000000000000000000000000000ba384bfc6aaa8402ff869d78973c68ccc36c20a839da8d570b6890614f692f3a3316f0eb45e4afee0cca078cded752e83c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000143e594b8762b4f821a6cd294251a114e248974494bd16a66f27192d3c2dc56c19d886b6305d420f8b81b22a2ce4faf10000000000000000000000000000000012fff0d7edf98633e1b10ba09b3c70fa0ea8674120160933689115275da6f95a8cae1ec665f89ef3c5454dd91d291ba4d2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b100000000000000000000000000000000159734584d9cceceb9a27808a5bbc1be9acc15c6d2edad81759312898be4efaf85420cbd004102f7b051c83b27bc3fba000000000000000000000000000000000eaaf5b8e35ea5d52bbba19087520a96348b418159e043d3b39c451fb77d5b98aeaa43cacacadf3e6ebb503f49c5ad4c120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f000000000000000000000000000000000c62de2a514ba6a74f66312553218cfcf49828b6f01ed05561b54d5f2a87806694ada45b80429e60fb985d9cc39e9c4600000000000000000000000000000000146b134c46ef783488e0f2d6d9b7039971e8ab7f3c29fbb2635bed84b44013159f483df0e7f0afd038b64f9e5cd105726af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a00000000000000000000000000000000189f02eda06f2d39974098d874325e4711a3f4dddf78c1b9ffb025425c8abe6dbcf5a01de0ebc802816fd67b0a9882fb000000000000000000000000000000000b378df4be4566190679691561aabd7182e68dba4ba05cc67ae19cef483fae99f4cc54540b5a5180c3854f5a82b6fdd0f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b0000000000000000000000000000000011c0ed482c1a1f030fff7395db725633a60875028e2a7763a1ac801f00a8f4aff5e19e556516df899cf5e798197f6880000000000000000000000000000000000fa7faf03f2f636ab340a9d27d9b5a66fb8daa9c083a32904a4407d408cd3a14c17734d7a14abe3655979230e1a93e4d23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f500000000000000000000000000000000101f93857688bc4e4da2c5407d8bc68b9304d27c89a44daf7cebeef81ab96d89c83ac34ccd0dcd87297929551810e47f000000000000000000000000000000000457eef8e4d47638f83aa2165c0f2581e6a0886595f03fc41319d6ba71da0193a4cf9f52c39c79327a69037b11a382f696c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f00000000000000000000000000000000110957948a78ad9c04b7abea4d1caff1de20b5615909c2f5b8ab7a1dbd02b9cf2ebfaaf3b21908aeeae55e47b9a21b7500000000000000000000000000000000168f08d45ec66fd4c9a94d82d9533aeaa251186478851a421f097d00506fe6dc0392114115e3e66d8874e0aa4b15cca281c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c0000000000000000000000000000000015fe358a596150d9eabe6f18e06d562f9e6c42e9df7ad9ef57be8c47c5764e408efbedf136059d0e04f81d4838713a83000000000000000000000000000000000ff7a343274892ba23daff40f5f8c56db9a4788483c16a4a0495a1f696d3304c6276ab5a6d7b3cbdce14e9711b033582e44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d30000000000000000000000000000000010d66f27b2da2ffe49b7540da57c25f0d36de0c43d04da9b123c153ba3eb63f3d26d28d4cc4cfef2c0652010be2f9eb10000000000000000000000000000000004d4cf53935c01bca14c75d1be55e7473d17de6c5a2d69813df90c7612aa4815ca6ea982222793ce66bd1c69f6e456feae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d980000000000000000000000000000000008ece81bc19694eb40ac3ed089d8fb0cbed88371c7e314ece92547151165a017b0a5db4eac06bb2679a8d82b296f522b0000000000000000000000000000000017732041d736996351f132c92fa7249483612bcd79532156694314834c04d3b99579d44628c52eda270ec7c3ca7c3e576a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e00000000000000000000000000000000026b3730efe162d58adc8d4845706f9bfe8ff54116b518d6c3b2bc6418997a44e98071e83566a905973a2d512878cf1d000000000000000000000000000000001449b0e28d1c43ced7cd687a550ff7669df47e80d3f2ee621b791848f1f7d6cf6272e39c66e8a69c81aeb67b06c630b2ed3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5",
+    "Expected": "000000000000000000000000000000000154edd700b8cda3a0532b2d6e81cded6a9662547b7d638f229ac44975d3b4b2f19816eb664e6208f33bf6f8d070fa58000000000000000000000000000000000a41ce7605d7ec592ec3a030307391ac64be5df9a7f8ff9b7ba15f7f6931353fb824ae8aa76b31466b7e06cb89fbc1e6",
+    "Name": "matter_g1_multiexp_76",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001227a5d20faf2f8d9242e1a7bea89b5d7c41c3e0d8f2629b4004269f9babd2521a97cc23075e13a53f4c66a82970ee76000000000000000000000000000000001726ad8abed312a369001f53270b5e7ad8f3f2a031804ac055ed4ddb2f40eadf9142416efbc90e84f499e07a307994db8c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd1000000000000000000000000000000000a07b5276098f9b3767908192f91473c554eaed23b810d3b464a3677089c45e2263600cc8d84766c7c67d9b5e6a057cb00000000000000000000000000000000175af857d5b53d195a17ae246208b55f35f4ff193545ea5a725a70f11fdd34ad2fe22431cec7835d4fe3c401c82a93fd8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000039e109e0c2ccb5e6cb4c5451125047bbb854488ddd74fc4360430fd80f16db3498a8be9514099d3ad50ed4376bb5e50000000000000000000000000000000003dec8af7f6805ff9df65c39262959c3c80f271d2f0e53e7e719fbb16080d7d90a1211a6b4d0513c771ddad7d3dc009063016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc10000000000000000000000000000000015a00f549c3a050a5ffa8427bd0c8b90a788c6f9150728b037232ce1148c02bce908f60ee367b70d0c9642114d6e657d0000000000000000000000000000000016831ffba7d7d0bc239563e9e62990af4f740e57ca56d0d8826a9738338e9a1d2e8dc2b8869d62090b06f5a3f68bbcd36c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000165e447cc890b383b46f251531cb6d29cee835fe2a0fbe14c65f0998b2911ba86337ba79decd2701a4db1916e01ff4bb00000000000000000000000000000000007bfb52f3d4a281238eb65565af329b3e043e412588ae00342144d168d903cdc9131775ddcb5217ff692b0f922504ddaaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d71000000000000000000000000000000000ef8ba702c88495b63ac012fd9ce54b4a7ed67b5f7d25bcbedf951455fcfa95a8c7775c5ccc875ca5bafb9bfa1af738e000000000000000000000000000000000e53e18a3e7d294b508ec4084cf57557dd1a96ece8eac9873d35e4f1ee812a1380bf56569e5e797ef54202b1ea69291df6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd4900000000000000000000000000000000021ef16de941ce6394ebd484f6b9de12787aef9e7921292106e6c1b18b8de5c640e448f53abd536953b07dc41db21ec0000000000000000000000000000000000a5d482a1c20571e03501b89d2bb4c6d3251bf0b015f23ecfec87dd7cfde705f946c311483ffc84381609c394c83513a0cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c6470000000000000000000000000000000019c277726fc9c53de1ef3aa2ae6e15b360a98b4a2b27f9057f91eae5b2a308b2f5d618d8e458839d1d60105e4888e7920000000000000000000000000000000012ea8dedac124f05ff58ac72fc967e325e00e83aeedf956adee447720f491ba1bcee564f52e4f0e53faa106ed8088d4cea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000000483da14288400f7b27d712ad849fd7c068db47709f78b297c746ab3e15f17f20130b415c9a1b024bd5b24f74428f0e0000000000000000000000000000000006746bb7d3a38fd833187a16d5500d394303e2edf7d5341d787257a9f811411a5cd586b300b7b4398f9d266bcc27d9cecaf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea3000000000000000000000000000000001700795ca26c2cf7dbdb64034e45362295b7e9c60753d728bf689239b0ad7073b29fb872aff047605509ecd10cbd4fd2000000000000000000000000000000000266a09604de2ccb74c5d97dfe4e9a74cf89d3612de9b2d2d39dfa3362b500be127b83566a61df49e639d548a0ecfea7ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc00000000000000000000000000000000146b2b839ff63d376db418a51890c46b0e3df6848a5a39a26a02673e93ea8dec5079e89a333c85785eb0cd1d67b1e101000000000000000000000000000000000f57e8e4cdf2670dc35a12072923d334523e7ccaca66795e3a762bdda8efe5424f88ef7e4c48b0d6760234ddaad4d7370d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001989144efb1979a42399f93fa80bdf256316f6365bd82b89e0e2371de79ce9de2435a6cfe9704ed710bdfcbc8cc2bcb000000000000000000000000000000000084230cca1eb5defbf2f2ee29fb2c47b417919f220c25bdd2a017b514840466a45b2c00047e9628852d48a057d6335ad7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b8200000000000000000000000000000000128df806a651c43c7e0a3b2c5833bf158ea40953fb0efb02620cc4ecfc4c32a409a8bd9e98e82812b54d027b6346afc70000000000000000000000000000000005e28760f1e574aff9664e373622147c08538ed45cdad72a546e4b5840758f5ed442f8cf24cb0ba35902e64d084406f32a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab719000000000000000000000000000000000289520e710e7ce4a8a671cb00a015dcf40ee2a69309cb89b514f6fb2c6e8fc92a49905893e3e0e9567956fcc86dd89c000000000000000000000000000000000d1329a4174f802680dfe8410fb45e23f96eef4649579ca8e29b3040de33cd6bc485d1339afac9593097c70a0312f5162bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da96000000000000000000000000000000000175f1ed2dcd584f9c59c9c747ea1841792bfd9a64747f84dfe32e256ab5a48eb2dcaa337990089c86b3dd589d276e2ce0000000000000000000000000000000014d8bb6e278ae9bd9df2609690286be593eeb668f5e2adfe880e1d34276ec3bf4ab5514c7898a6504da63e0ecfa49d020f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed0000000000000000000000000000000018adcc61d9162790bd8c19be058afcce08104a952b15efc276af8a8807a4d2edcf8557aa03a297ca01d6a3869160148b0000000000000000000000000000000004338e5f7a12f2ffdc8158a51b14dd36934f01d7fbfe45e18276f2432b1b8210ba6bc5f246a52646bdbf99ed91f2f48f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e",
+    "Expected": "000000000000000000000000000000000c1f9b78641053cdbdd6545691d1a5238389614524365bcddb07f9b9c6c654e58a40047084532b8473c7d541ebb187ee00000000000000000000000000000000028eb1aeec5e4672c41eccb45b356529e5331bb5fb0ca8f9e10b20a2ef1ea911f03af896ecf7575613bce5eb8a0b0837",
+    "Name": "matter_g1_multiexp_77",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001242be79cbeb2176ecadb07d205d532bdaaaa26bf9103371f2c4d86ed1df72ba8b6d5c76b7aef25c743ec4f43e5237fa000000000000000000000000000000000d2de7792d0655ebcbdc123ed6093ba68948b8ea156a31b9f23d1abd948f4b2ef2f27a3cbf72b9e5b3e966576e9ffbd5f3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d8000000000000000000000000000000000fa50f78e45b1b7b61f8508bb5842bf59d0f41f2a8192cccec6e56125ff94b402dc47d3bc7762f3196a163fb148105820000000000000000000000000000000002933cca4d82c6f89ff8db5f9239ef8fee2efdfdfa22e0b4d0fbe223910b08060a77eb4328a05ddd31d205861db090ae4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000c15db9d1dcf646bb4c169490256050ad5e408d1f45221a9b4bf02f7651fe93ffb892c98d19d730bdf3971281c9e2e3e00000000000000000000000000000000150a6d1978ec63013ef3dd3b258ea3a716c1e564469d2aba343f3d15c30cf287b706b9eef8363351cccb79ecdf5aa189518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af07000000000000000000000000000000000f7e810001b9e3a11a535f6744a0dd357cffa585baabf065f1e72c9bab5484829a94159c72ff2221406c8b15de465f8c0000000000000000000000000000000009d48808fbf21370420cad4df7a269e1eeac98d2aa5ad5890ff362d91cca5ab1b57fb079caaba3a135c15515e98c6b175561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c38655100000000000000000000000000000000191dcaf13a62fd6de0bdd16151b3c27f54b40ad82da1299164da87d0cb7b4c769f941c39fb4b68a8915fa95a5ddc0e900000000000000000000000000000000008b0ad7fa07edefa61ad026d42df18273b6628b65a4e655a98b705f588494d06c37153ecdadff83d94739bc254d6d8f837c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e260000000000000000000000000000000005e3001f37e840a9edba48b3b436dce520203b0b36c3871933464be1c41178f7a8af9b14000b713ee8fc0faf5cc1a870000000000000000000000000000000001732dba0dbadbe7db31ea6af17520d791feced0a7bca298b932f51f3dbcb355699db533cfc8b61d35d1a346ea5de8032981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de400000000000000000000000000000000072e4d38aa0e168255f1d69ef129642b4b1b57289e630455b147574b03d17e3cf0f32326afb7c45da468e0d8c2276da9000000000000000000000000000000000b60685ad05be8453d5d272c73365d645dab6c50c820c1fb7fb50d82eebf9b03ad3c8f711140ddaafb2bb128b7be2e6c9913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d3000000000000000000000000000000001401e023aac71de3398f89893102efa8760cedf47938a655983d73ca8d394a239f37959e629cd908b4e4f5e55955b153000000000000000000000000000000001458e304efcf48594d7094d30a804742b08ec94ae479cf5d4e0575828ad92cfe8e11847d6078f5eeea4308a8f0644172188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe000000000000000000000000000000000d446202ebd7a7995a4e8aa7fcbaf6c4c4591c4bc40b374720752a150b452b461f59b775e3088733ca967854413a9f0a000000000000000000000000000000000d5fcb5510c0f7ee77c7584631149cd494a5fc496b325ba93ac5f801e34c815fe562be4758212f32ab0978930d142adf5525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a0000000000000000000000000000000000e034e4027e846a8608680995860b2673854d8fdf0e61e2663d7e0d904b6725ff28bb4593e7bf5e2c252d9c9710e39c0000000000000000000000000000000010bbf60b95669468e5dbdfe912dfeae9945f44454df62ec116b097b867b14c402349af692490269797a30639177151945ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc5700000000000000000000000000000000095e1315b3568e8a069dee00c3676d5d6ad94a2164795ca5f1418cff4a25052e741530c0df6d50c5cbcdd55a084227f3000000000000000000000000000000001993b036a3225289827691296b51ea4e42735af0506b317932b6719a381a59c89871a2a394f4a9de0aba3bb9a2b881f86ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb94280000000000000000000000000000000010aad99bc8570d83847a2a2688fa61d5d0ecc978ae842715a084d99392db343f581290478bc1bfeb8bb692e0d6fd58ec0000000000000000000000000000000004f82c0527d3e9329a6b460f1d781f881073b87711771699e9cc8c4229d5112d91d4357380c12c120313d2c9eb7bb427854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000008ec7244587110fd3fa0e1888427fbb3942d0885e002e4f846fb749bfc4a82bd7edd15cf81af454354006a2ea85234f6000000000000000000000000000000000fc7a19df5adfb5a154f32b9022e54b1560237f4319160c9c945b7bf4b55e45fc86616d3ec3cecc177c9f6bc54dd2cdb7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd00000000000000000000000000000000037ab89247516909dceeb59abb90d6968ddc3ef3abffac93c68757f3c9309d145cf9350e4d8f85db810cc5f156f8f126000000000000000000000000000000000289168c6dfdc25ea10e1839e10ddffbb25522be7ff80ef321241c6cc887fc7a42586dd9c1686c6c5c2e4caff0278155923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f5476000000000000000000000000000000000523020b4c34e867e75cdc668e541cfa25f2afc35573b2db083987fc585a487f1eafbac1c4267d2fdfdc5d2f94c51a84000000000000000000000000000000001581bf2744d78d680c9bb38a3f0fee76b6f0231f011b3f7ab3fd59c1ec6c99fac518857dafd410bce2e8610c6e5efbb1e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf9000000000000000000000000000000000615745e737980a923e87c3ef72330f55e38434b3974c1cc997a9d1136527de9bc21dfa73ea0d33d27324a53f12bf6f9000000000000000000000000000000001164b6ac376ef24ce3cba8e2ae74eb58437bbbedf68b4d0b6e8b7e213a789c8c3b7f173bbe52150faed93fa83bce0a9db6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa",
+    "Expected": "0000000000000000000000000000000016e6cb1f899ee8f3db4b934c1facb3928b08fabdce74f569a06ae6eeab201925f6acb1a47ffef3c608fed32c949786a7000000000000000000000000000000001796fe817d2e4a69b4e8c539f6f2179a802cb02caaeedcdb316b3ec6571c13c349e115a7a05b296d6b182c8a618ed918",
+    "Name": "matter_g1_multiexp_78",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cd7cca90c8742e7f541981a13b177a4e639195af5f15cee2ce37b01d50fb8478a3f0d0abe4312a4d92a201b4dbb030e0000000000000000000000000000000018e2a69bd1cd9bb7ea75ceedf28ac9c9514e8d28223af69dc991e46a03d8d272d267842f30552583a1f08a07188058ce13ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff5660000000000000000000000000000000011cbbf1ee7e9cf8deae286ba67ab0eeddabd2471d2ea15e86c77c4f2f23ce38e17ac3f3e3c2a40a1640bb3485be4e59600000000000000000000000000000000108e9f887f86f03dcbd515501f69a5983b4a6d707c26b69cb9ea7a387c5a914612ef645cbe81bf29ba91d209e839c72abcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a0000000000000000000000000000000015c1856c661396f8e3a477932e1eea7e124b2e9ae0dfb1df67c4b3928c462cfbb3220c4c2fbd755fb6435e144a2b937e0000000000000000000000000000000011b114659fa71c3ac2412d5c2cc1e184f05a45871e5ab08fbe5eff68ef9e457c4f3e2bb4f16d10e91f7ee2231bc3266359f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000b693c93d4f06be5bc8a84157c6f407c3db14175c56310e7d041118ec869f3992f75809b209f6dd01085991deaae2a96000000000000000000000000000000000ee21d90cc3825b401e6d452e27814672d849386eccec7be992581b1fb9f4ff4f3892d63e124bd669603e6269f099452995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c00000000000000000000000000000000013b40cfe91492dc53089325be73b5d404288e8056e30cfe4bf3feb6b854eb7d0efa3ac4afa822162ac16608555ccc92000000000000000000000000000000000576146711dfa2ee08bf08121c30fe63ef0ca4448b28076eaba9298ab925c615a56d497044be803f73e9586763aad52497b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a50000000000000000000000000000000009d66d52069b0d23faa33818a8c9bfc812ae6938dd02604e98a422f50c085a5641a46272dc9c8801a9c76cdfc2020a0c0000000000000000000000000000000004dba0f971336c813933bc6386e55044f5e3d3e5cf38ede5811b4e775fb41cd09d7f136d9de6fc36f2f435b8cdfdc26198115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd17810000000000000000000000000000000005ae8fd5c52fff0b80a2c5c4fca4bccad28f580c94edb7e28ca2ce2390cc2fe476a2b11f63c3c8759847e647d5fe5d1f000000000000000000000000000000000edbff5012f6efde3a9bcad65c805b1c4ac0899fbba5fd760513c673ce8ad18d3baf28acb3344f511fd4d9785afea33c27370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf50000000000000000000000000000000009d003d4213a46812ea1565bd9a6f0f3da1e69e289f026e619911354cd7444dfbfff1d842e3d9c61c305b2154851b29500000000000000000000000000000000070a1387dd16f9d8b4306ecfe0e9ba7aaa5959ec917e06da4ddf90c992fc569a56c61f6372bd26e21f5cbe7d720b68c66bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f7100000000000000000000000000000000008fccb033a3e10a0015b11ffe2ed5f4c96ea2262d06ca4b0eabbc15c9b299a5220444345c65e7092501b56599980bd0d00000000000000000000000000000000127583566286e52f2f2c7809cea1170a49993f171c1c217b82c17983e02b7e69cb8c948725c7a613c41f96e80c3f1aa96b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d000000000000000000000000000000000084501b09915fa13908466d6bd50a7e0d8b39893bfcec9c6876b7ed8effd100b8f0a459d754efb6b110af2becd882cfd000000000000000000000000000000000373669b2a03d3da4e907da24c61f5e7928c5fcef4e6c9ad4303fc4cc2cb641212680f7c33605212de8914caa58732f44f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce110700000000000000000000000000000000010608a9f87f46e528d782ef81493625f9a47134832eecca6471d2113060703750b679e64179e7a1c1c81311c38c493400000000000000000000000000000000032a0c82e42be6203415638e6cca4dc1621f87f030a9d742bc77862f4f10ceb44f1ecd377acec6587be0fdc33d8c17c98b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa300000000000000000000000000000000126f62cc3033b7235be5778289fc568a1c474b70cba2d35a0b9fdab5cf239a2d4fb03f0bedfa84425b142c04284da058000000000000000000000000000000000dc1f91754d582f57b413fde9b837cbfe3430582b0964620b02bf854c6f666914157d44a165f16ca1d7204f35caa7b0630e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b20000000000000000000000000000000016d277ee7864b3af3102190cc99db1cff9fd1b1d6e7fc039040149c5944e7837895532ae41b4db50e29a5d6bad7ceb630000000000000000000000000000000016c3f6e29114782c84734cb927d1a89b7755c3a8fbc99076ce3ae17f7f1d088e5fb9757237773fd4e14c2855ec12b93723a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe000000000000000000000000000000000dc8f59e410ef7145d636d2c7d43fc4b1c903d6c8c0efc3ae162293c7c65c48182f9a25c4e5f111635881533cc558cf7000000000000000000000000000000000082dcb0872d815465131953c69e260e3a9ae44d16975f361b5effe13ab1d61c18f050108e73f50871221faf28fd79771ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000002536653a945e03329279f382937d72bddd71ff8f19053e1fb19ef83d9751eaf101676249ac65fc61a0cbacbfca3cfac000000000000000000000000000000000806ebe4d62e62904ead05f814dfa6e8a392b887bab4aee61552c6f93ea5ffec6593e9078a33f4cefc96393a667c934c212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000018fe7f7093e0313737b8e0c6ba2fb0c93afe1e8241bc769f14cebbfdb4c73aa578fe3d37ce1221f21aca8af9ab99201c000000000000000000000000000000000ea0f2ff4c8ed0a51fc8fedaa056a369c5e97e347c6883b215d0f7e019960c0178a7962415c220766c16f4596d4b9d8ce9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37",
+    "Expected": "00000000000000000000000000000000184197d1ebcdaa25c041e95424bb891fc9eb38160cb0d91e702ac283280c6df697ae85674eccbd0fb130b6f2e1c193b00000000000000000000000000000000015593ed27279ca601616dfcdc493b8c7bd68260f97f8a9f10c03cf871b17cf8f492518d2f8569d60056721723a0172dc",
+    "Name": "matter_g1_multiexp_79",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018c31653abd67897b3bf8f046712b12d35ada1799d1c18071182fb61273b7bc506779ff2d774576a725f2f1035431c82000000000000000000000000000000000011b2fab972f183c75df3bfb7968dcdfeed0755f71ec118e56c61203e97064355200c5f016b9ed66040fc886062dc58f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000030d7e368c99113318a6657deb3c89424b9acbc5e3568e03dbf629333ed3a5cb45ce6988a3e5ef79e5ee91aa6b990b1d0000000000000000000000000000000002700af33eedebc8a4847d6772cf615413149e6d98ba3b36c96e43c8d97619cb01117570f263bb2f7579c7da67f40a25c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f0000000000000000000000000000000008cbea0d07e870d679cd20b4ad088bf3c5c23e83266b20e816f69bb918824c9bb4d0b3216f8da5a5cdc6f43359e02d06000000000000000000000000000000000d1c9949921e37e73f95b0e4c444e390bb71fef0d893d1b341b9338321bff4a23d1da4ffdd5d7148fa9fe9cc52ebbfa8f2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a958530000000000000000000000000000000003111c080876670db10abfc439b17b32f9e96758b057d3344c7823af1b0320037906b1a9d8fc42cab9e9e0e8449aa997000000000000000000000000000000000e0c7d19a0362a173b70b6fee3d3feb541c7d2ccca71f1f01f8bd105a18024fab05e0a6d448153139f2777b189ba0fe41022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b0000000000000000000000000000000018bcaa4869a5c6ae46e6f5fd5fcf835965d21d48871010245e722bead79d844e96e10558d71e425377f4adacb3f74074000000000000000000000000000000000414d616a4207e7cf79352dbf7f319bf554f043710cbeb48aa502235db7d30f4983b5381269f34ad6ad4fd5ff56d9586b80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000c86dc8b8f38d1e4281269ca252adde9f0fe933d4cc051c7aad55f96252d1e6f9eb6f4f876e153c11b61714d985d318c0000000000000000000000000000000014113f8e2c3ac4919de334eb5c04c909b88df39998e58883a5393a4d760cb6d07c65eae053a7b2100ff3028a786782bff397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000009b4437230d9dae44852d88dba2655070162501702998ea5a035cd88eecb64ad7c9ccaf696545dff98d778cd7400943f000000000000000000000000000000000706b196155640680b257a537c836507d95e6d5cb7f163ca340dc0f8b80859721b7b2a2ba51dd4d72ccc4c3cb91030c928e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c50000000000000000000000000000000017bf848757da8e7ce5e5e69574a9b31d35eb628102897922d4c996443fbc970374ebd601b96b3ca9412c13f50943c7590000000000000000000000000000000014741c0b49e4f02630a6cc1a723cae1a6a9862158bdcf996b46a9614dd34527a859db0b5718788eaf2caa059671f3c683cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000001269325967fc68b78cee64d0386e1fa6ecaca1f85d672f8b63831a1adfcbdbb40461a77ee0e59b1fcccb7c1d543f08a100000000000000000000000000000000053a22e8c4219e4d68a961c2127201a23443d8fddb02e3756cfdf74e616dd4abe73c4ac498ff5f6a68d730c0050b79e18e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c500000000000000000000000000000000018248505148876ab5a5ec3be7e3a6cbac30798d52f437bea7e966921723e6a4a30a0e53518e109d1683f3a4b3432136e000000000000000000000000000000000120602fd461206973e62ec8a3f1cfedddc1e9f9e1769ac06e2a1024a9af19d402f40ffe30f9cf77b8704497d3cba4a3674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000009cf2460e5121b15d177b8ad803c045529933d1abf62205d04726b67d64fee85e2008b5098ceddc42d5c8d95d39147600000000000000000000000000000000012749abe2d8b47bd9c899b6726ccc749bab2786e9568d32299f0e659664ba1efe764944c4087c549e2bb717c87c6b876288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b340000000000000000000000000000000008d764f80994fd37a21f6923d7fef255145ea875c892888d45efb7a37310182b04d2c16d4d91a2e7c41164706afdb617000000000000000000000000000000001156c016a289989510f1c8b39bd6a8c358a1c5611bd2286e9f15983f984e89e061e60717f1b700abaed57076e148a8a956e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000734cd0d73ef7d79fa501b98b7211d551127abf68c473c1c72c591180b605c938ef71f66c422bf2a8bcf16c6c8946c050000000000000000000000000000000008ded96a9fce61040c1acc71d6496cf72590c63c3514c4f1f77d4582635af9eccdfab2e60749ed24fd3b6e30e3576c58ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd000000000000000000000000000000000df9ecaab534bbe9c8531f813a95a7733df6a4c8785575c5ee89647941a6984cdb5a33d2eced340c683339c18f5da32b0000000000000000000000000000000003632b2377ab368bc9f735609452e0ec9fadd6f261cd5352e0a5ed6a37b25ff7a49fe57452e79e7330661b81d7d80a64b96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000006bc4871c0271394c9d6099667ff68e1dbfa9980976075bf81fc18f1875fc91b50a0e3be622882c90b1594419da7dbcd00000000000000000000000000000000168e1dfde47d19280dc213bba9fbb61fdce41f81d4b25b2a7abae0404bbd7a413cdd89611966a7f9bc32617dca51f369d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb000000000000000000000000000000000132a2a6832653eac18e2fcb2c336292dc7990fa1a004404973029a227c9871181ffdd88a74adc3edc7a8308dee803fa000000000000000000000000000000000b230c171d5739fed98d32a3b27584bb0128434401e9e05ae09a4dcd7a017d1cefe7a46dad2db5addfb389feb9c846181ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5",
+    "Expected": "000000000000000000000000000000000cc6517e655697449988bef516e60c8202fa48c3573967491ea2ff2db9fa0de3f542f656228a5b90d7fc1e5eaa8b06d7000000000000000000000000000000001191ca6ef2791726a31f77b0431ebe170d8fb595cf6e6b9b33b7fb0d4acbecf2d171884c392832d2a91832e982c5c0f4",
+    "Name": "matter_g1_multiexp_80",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000111de2b65f5f94851aee2861910898b74dacf013591772902239ff7f71a9cf84919bc4a84d6936f9552e97314eb52e7d000000000000000000000000000000000db96af045180bd4d88dc8c40f8cd918d2195c2f3651c176c1ee3ccb583a7363e2c2c900f2a54f26a881938cba98565f7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd0000000000000000000000000000000000e48144181d956ebb37d72c38c062958f73de8944995c7e7568997b04ec19949b348fd80e810632462ce43c7c6571ae000000000000000000000000000000000b4a19556d8c21206c4198059adf5ac2b8a0e08c948a8a4d7465bd31c5ce5887a069df5f80b1df89ab868ca53e16c730c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb10000000000000000000000000000000010547f218e33dd9f9425c8e7be4136e65ee3dc23e0cdfd5f1caa8986162cc13b77d30259b6b9c359ab0faac9ba29bda00000000000000000000000000000000006729e532ba87a77d1e458663690110cf63eea96f8e41a5a338493ff71b68e78e78b9c929006c0410c3739b15ff2810069700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000000d3630086b7e0c068c60192be8724ab4d18409fa6ddcbed02b52fa776e84e2115457c40cac7e903047fc435114150d5c000000000000000000000000000000001066ce26d2e940899e80e9c0e515ce9d5810a4048925a7ddfe0cbb24b3d8d654c6835c6872fff5a988f525c648661cbc19e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b000000000000000000000000000000000ae73f595bc9d22c8c959eedec4d1301a13c9b8c643f4335160bab4a99886694d112ed6fbfbf082629b76d1e2509ed280000000000000000000000000000000013dc07950689ba36736838714eeb28ff3be77ef8ba181718ea7b5229e01d4e036c98eb9ff7a867c017857c029f7f13e3199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e0000000000000000000000000000000016e11b169dc405035037a10180fb368988498b6e209ad62260c7ef45e9bffedbb0587fe282d193bbf88311f3d2880cf500000000000000000000000000000000090a277517ea7a1a7cbd68598aa1e16977cc57c8d095f66a7cd3f67814c2b8f35e17e20d7a26fa67274dc5aecbe778648159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b0000000000000000000000000000000002ea8cba4bcbaeed7feaac63caf21645ddc97daf9250ae29994fd04e798f94dab33bac6e08eef8e6c20f122bc5f88996000000000000000000000000000000000f7a0f6ac02bc9821a883393c8265ba748f9d7c3ea763037bde3bb0178067e93aea4dc70d25e5bcda642d06f41a7f18bef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a000000000000000000000000000000001618ee9c413dcf713699b7910989c20bffc5ba1ca03e973005f49084aba558797e7f9ec20cb86f308d737b97c08f42a6000000000000000000000000000000000db1daa5ed21250c696ca4da3e82f6623c54d643d773286811e21c09e9ef7c9ecb9d84d90b9c76ea9f65e04a29f82750d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c50000000000000000000000000000000012aece7d9e7384ae79e047ca4b4fe72fe541a825530d6c38b9a8fbbf8b801883ccbc3cae7c33e4d811198a7b7876c92d0000000000000000000000000000000013fb42fb1b4e7785c1b66364de150d1e38fd9fe3d8f209b7c168beacf4b26c35fe0fbb4a41f30adabe4314b20b16319561d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000033fce20f9202b89411dbeea59a5b1c632435eaf29e2739163b0837ef9278ee3903ae569931e70f79a9af5a2abd29749000000000000000000000000000000000a50360c73c3f735f97d7d71b21b2831f7d7fb59c594e85b604dbb79ccc884349cba8eab9ce613ed60416994322916db03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd0000000000000000000000000000000014d9a7dbc453effa7a76c774a289957b0ccd72994e568c0de345b482ed2b6db9a3a3e56e0fda159c25acb43b4a6765d5000000000000000000000000000000000b916f28e3fdc62d296e421b1684efd4e9a4b523f79dfaecc00872a1d17724e1e07e2386b4bc6d76b157ae94559d0bcde4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b0000000000000000000000000000000012a4670c5c2847bb188464dafe41360f00621ceb3b5da0a3dcc16732f4baeb0491664ed8c2f95ff9b44e2b77e698eb3800000000000000000000000000000000077b561ed2fe5c91b30a12a2df71e76cc4ac882301d1975c3cb176e22874e28868655db9d0c91003442b0277eff52669c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000570e1a0fe7f82c0d3cf38d90f77634f8dc2bf9b58ac473d9bcbe7242a4bb76d11f36083c90588a680004c077e957a9e00000000000000000000000000000000038ac2b58a16af0a3a0070faabe3969025440d9781e3ebc22ff873dab532d6ca1b0bbf21f32eb9728a322c158f5390fa7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af40000000000000000000000000000000004a3a287fe4401c48d7dc804363941b5836cfad6490b00dcb0ee830e876fa05a42d6e2b036a4e213bbf5b6ae5a4e31ee000000000000000000000000000000001877a91254211b2af54ea910d9efdf4b4e829fda5bf6b0c2dc849903c357bfc6f55b45c7437ba538ab6cc795b71e95796f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000159bf4b0dc89cfc9d1687d8552489b5c3e2ed059164197028bc67c51ad18b341d04e4b8be660880a76a44ef11e785ab5000000000000000000000000000000001643a41fe4104ab0bb96200472ca67064635bb728e6d909fc0026216a90083eb612f11bd5983cf4d7fe664f1c527b96a12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd60000000000000000000000000000000003dc904709f1da618b6a623888015a875b11e5baa5c10eb6d750354c09359b180858bf29d24bae18e7c78c81465659aa000000000000000000000000000000000c61dabb7085a1937782433ec46b0a063a34e102ae9a6b6bae7d82c94e93c3cd05afe19f0673f729761462bcd0d9ca5e5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256",
+    "Expected": "00000000000000000000000000000000109dbdd05f92274f3edb0232b827a4abbe115bd4d51f8a1e7b8ee609511125ecf35ca60991e75a0b7973c4085b7d4bca000000000000000000000000000000000e14a168decb6893e9c447b4366247d997701471a33bf8f762bde44473d516d857d1825255d8b4cee8d04736cb369758",
+    "Name": "matter_g1_multiexp_81",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009b85ef81b184c6383ff4e2695a8c261ab252ebd81bdb518001f110b2ba72fbf5014214f816c9453319934d8a010aa0d0000000000000000000000000000000013ce486b15a77cede98a46f66ae51d17713bef6dafbb2ff34c8f441271d52f4fa27fb88c5695f4af6d43e32333e68130acc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd0000000000000000000000000000000006a10f5973fd2aa312ce8f30ba5caad0ae6028bca5c186e4fd55ff4e3f5ce00220b94683e440b09a9fcee238af140699000000000000000000000000000000000ae8e9db6953ce2461bac3be78bebf6c4df8bc57bc7de375aa652d793bdb0899477464097514f0fe2d0badc9027baf3898c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf000000000000000000000000000000000c0c430ee1e9112d901b82e43a25ce4e5b61c81ed7ac7220d88bd10d44d28c1bd20fc8e1ad85f9b6eb43fc232594b4f1000000000000000000000000000000001233dee860032e2f9a67d7b3d61cea99f18b91620b76f8bd178295ac4fc3b8d0db4c4ff602085c7a897435a283e2a4eda0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f000000000000000000000000000000000061e9d3a919bdbdc42500b7daec837506bf0841caf35aaac34a3670517a59bf52343b47b46e8212208cd6fdca6b7140c000000000000000000000000000000000b87f7efb446cdba6e619d5fc04ca8dce8e57f6a76faa4a773c03ddc0666ce2d83682f24d8463d9331ae58e8afcc5641e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe000000000000000000000000000000001606311f79e836a03da5cacc4e1c3930695372f8f679c8f910627f86af15d1612d653c76d88b9d33f848f94bb63fa1ce000000000000000000000000000000000075b5d9626107a486079315a85991f3d77461b45e5c8aca6876287f624694c8ef1a4f5f0a5b65eefa8d6a4746fd2e5fa32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000012bc152cb7df01fd9ca35142806664fdbacb881adcf443051abac7c979d09a1c887fcfb8cad281f376ea3f6693812914000000000000000000000000000000000e32d4d6aa1f5046382c1d5e6e2f97319e8c6887b850b3cee498c482e35319a4f062be80f7f48ff3d1160ea6b18cf67824301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000001599c2c489535375270f0d1f370c6416c83c4043dbdb4999256f187e29c198b1f6c5bd1a52c997f01ebd3622c40feb63000000000000000000000000000000000b60ea3ee221eeac4a8a364eb52ee08579cf5a907aa5642971bd5523dee5dc6d6584ab993d33d9b8ad9de4a1a4f0cbb117a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff82569570674000000000000000000000000000000000b85c776ed6c9c78001ec7bf3412be495f40b0978d0582ad4f86ed54464fe562f9e699f727f36b2fc753f4328f0b2c6b0000000000000000000000000000000006e11a826fb4a8f0ac32f5c52a531508ad1363bf9b09919ccdb61ef25baa7718a4829fdd10fb6b680321cb7ef12d0c01d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a3180000000000000000000000000000000013539f96257faa2ae642c15f9c04e8fa7b2d6d095f7ca285e0dd90f022ec4a8fd74cf48557afdb57bace088b017b8ec20000000000000000000000000000000006cbc3e4291f373ee280eaface275e0334e46e54f65efc4e18b4ebb8ed1e61941d9c859903b56ed0d4aa3f4f3152b5b4cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a000000000000000000000000000000000675b4dab12db428a14afd8e696a64c0bb352bbcbecdcf2b064428b489194112f1cea4a383788e0bb0e97b7f88b817700000000000000000000000000000000013273075195b02abac630211c5870727a42e11bd96a2e2c6057d0c96bb60b73db72dec3135122865cd520c525588664a6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000000cb4629e659d5c2d91c5f909bbeb3381271ebde4f8486f76c1903e86efa78da06af752404ebddb3fc5d1a09ed28b3aa0000000000000000000000000000000019202a57e95d8d2623851973c324d1ed64b48b15388e052761493b1cdd6f3b54c6f47d2b312edec23e9da4c815f02e172e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b000000000000000000000000000000000a80bc4a39d62ca891044795e2b78f4eb82a3bf38c4ccb2e6d24ced4526db7c57ebf8b1951af0707af5ae5929f727c290000000000000000000000000000000001cbe991b082e840d8bd505a2eeeadf034f8f8c2bb530c742d7953089da1447e090d82399bc332127f14f1521c95f0042eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f18400000000000000000000000000000000112e7894d90a5cba2a8bdd0fa750d6e57c0a9938ca30526eb5289b4a59f92bddb33f59ca22a51d1bae03b850999180fa0000000000000000000000000000000016cf6b093a188ccbf1a000aa860fc794546ab0cf261784e7b7bc5750848f685d629ba55f71f2266edcf24d27667d2720caf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000005e2b8ac9124e8ccb6665842d77a2e9398e5b3519fa4fddfc4b10acb5eefceceb1cd6cc733e300ff95ea80d09e3bbeba000000000000000000000000000000001273d1990fa922276859d3921bbd49a452c821a9746c747734692d12c6f7d45533c0a7692d1a2d95e2d2be6dbfb3f6ad106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000dd2c7410b5f5ee63ad2a9ff3a96df2bad103caabe00a9892cc9b2ed2cc3bbbb53724b2ab63cabc44da7097b619f34c3000000000000000000000000000000000f695edd4b67f81f09fa89104c81717577cdd16db30901f4f04ac97e2e0749a80d34422bdfa85b5cdb65c042d90515742b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd300000000000000000000000000000000113c8f77a2409e0c7ad34186119833605f924545821895a283ec83bb6cc38c549a356b205c24f65be66fa627a378eae30000000000000000000000000000000013038ad87e3b3eb6545a0b5f7eec060895deafaf509ff6687024ada75f700d466df86ae5f95463c05f19750c0ce6cf56dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5",
+    "Expected": "0000000000000000000000000000000015a7b2803cd9b078d457d3b7c62a2418f19c0cfa006739cf3878844c9e1ea115fa982a02fa6fa0cef74404bcf145952f0000000000000000000000000000000018ea40f019b9226cb0428356483f842ad73140a8da065889d81e35a564e99aacc5d5d833d35fd15713ec76c65f9d3307",
+    "Name": "matter_g1_multiexp_82",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000163b380ea90b97146aa11c64b34de710e41b2ad54036a1a98659046f0e051e5961f30ea5ad78d8052f4a5d2a8388c28d0000000000000000000000000000000012afed5aa2e8c75e437fd796067e0c610a8a4c2f3368752413e6f179bbd4db25b18d5b3f8502186259a6368dd4321148ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000001bd4abe425f0418c86716516075a3ad09812650908cf383ec1396cbb6929bbc791f5cf65dbd95b51690b58ae3cab3f20000000000000000000000000000000008264362c7fa8021dec396c8355197ce4ef70e7b8894fe23d881d34b9a1b883cba1eba0e54d928b4eaa27aabde0df9b3c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a77300000000000000000000000000000000032112872b64559a03629b7ec8b32344b7d5f044670f6099d8e8b1a1d47223f9a42a072975c821d03b30d0994d782d830000000000000000000000000000000016042f6baa48d7c571e1f6c7cf3c7a0887bc4e2b2de51bae133d266dcad23c579e03d3284c09c83a54eff7f2151ce5b3dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b00000000000000000000000000000000028ea1499ad8761d908d863849ab4bbc155edeb03a7ef4bb93e96e25ab11c6dd0c21a6f06537a688189f08a00aa33171000000000000000000000000000000000ca3ee57dbe627ae681b12e0de4ed602bc3c09558444f38b0dee27320708549491a4482f7f101e8a722ef85e3fd742a5da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce4780000000000000000000000000000000018f397d7c84b8125844e874ea31d18b8705a75027d5324390e2eb7c9962d9de07add34a436db21a34fa7fc7898ef04aa000000000000000000000000000000001591f2cbc58c0841e5eeb8d9c75d8dfa0f2dc5e479d136905abb772a6170d131c0f2c9e8e55ffa215a4bd732c2fd85556aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab7700000000000000000000000000000000135452f0f8d4559ba041dbd2ac45f15416070b1674c9d8094556a289716814d2a4efe14857aaccb82c5ada5d6f0d15ca000000000000000000000000000000000f1c47592319db60db724c9d0649d0d713320be7dcc28e7318517ef80a3fda71fd1f4b722633ed7ab7df06218ee593e940273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a00000000000000000000000000000000003c75767678539abf7a62dcad5f90a3b4a54354fa70206e789a1f9b5daeb5fb6d9aa222476c68cf9db8a0789d7ad43d00000000000000000000000000000000139bcede61bcead99ef0d9554ee1c19db1869fe041671c199246824a923f5fd94e1da04fa17ec921bf6e82b14f126702b77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb5400000000000000000000000000000000020aadb198678aab5a71cd6dc33bd64c47be6d080d24f2f1bab7239808c10867ddcec65e27977b9eabef64455cac25e800000000000000000000000000000000141e58a9f8c9bd92d2de58bf3bbe77a48fae9290815915d7980f4835d805486d678ceee9676ab4fdca51d0fff411ab1b0be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000017abf5f132e8e466d2cae445d75978645c3b24284e1b7df7773c256ffc342d1484976ea1046aeb5307f735a69e2fd20a00000000000000000000000000000000087ce2fc44b9ed797f29c352393a8ea109281514490fbc7dc489acb55753fd5c577c4af0ca6c267c83408cd95b355e26c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000000b106b2b94858155849ec36741c7fef4d97ac704baa6752e8230e172da7208b7e9f187ef0a6cf054d00f2cac99235b8000000000000000000000000000000000d94c6e2349941a20884b9c2d702237c5b5ca2ed277bfc79e53452f1cd6f9f49360215d20fa06df238a7ad4ea253c93ec6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b30000000000000000000000000000000019b33665a81d0ceecd43f003eb34e1292945da1361adf118f36aa5acb71bd821a6732758a4aa6988e29d4cb70004df45000000000000000000000000000000000f3a244e578c66a9263f020e2f6ce49dd655c7e40a992c44cee40e1c874588e464f6254ba644e46adf348a26025d6d3ef3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a2100000000000000000000000000000000015618db18e00670281adb20c975f4774aaf169a653d5f583ff6966113fa773075db78507847586fcae82d6a468302706000000000000000000000000000000000301b18d0fe7d0db7793c62b3da072f4cc2fc3425583537110306e31cf63b228cb8c285029044c7b9439c1227d4c7ace2809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c00000000000000000000000000000000053001a82260b26e34e05a203c8233095da1da58c5f804da9cd6cffce07170e39044394f379173e1340da055066d320f000000000000000000000000000000000bfa2bc7fa0476eeffae4df98bd814db751eeac1dc67205c7629c9921928b55c70c2abe242728bc078bc2685690a38503521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac680000000000000000000000000000000019a7f78102671f6d84ece4a5bdc54e59cbeab60a8c6c15a708e0169f42a52e98bbc1f8ff52f34959befc859d308fea250000000000000000000000000000000016b5d76caac944612d1dc687c6dbaf10ba60a12b491b17b6c1c876a5dff933c4bd9c6f923e2ca4cd1dab38fb06dfab6a9c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000007741d8f72a5ddeea2fe82fbce4b3d0aae61e1ab9243ae6a3200711051ac74f30a4dadb597130fd8389353c230b6b7d3000000000000000000000000000000001809f1cc2fc23be0f05b3d12e6891a6aacea121e6db77400638031065d75c7b3fd9a02ded481eb3893b2449aadcf53d6dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd98966400000000000000000000000000000000013d9fdf041ecc7f2c728fefbd6e9da3169d872406b6fa77a52e342fa8852358b02bb2ae7ac77f83e2b25f0120603d0e7000000000000000000000000000000000101ae8e945d31a98c4dc3ba0e01592285c0c92721372bee6b138d9148883970708ad5e585a1b81d82ab0656a3b03a2c00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0",
+    "Expected": "00000000000000000000000000000000193115466c33711b07826d2a021792b7c238ae67e3bcba00b24026503d818f912c5635f85e85402e3a6b2a8152027afc00000000000000000000000000000000157fcd63d3a0e48e25ca0196b7ade453fcefea33f09123434f418cd1d65bba65a789e4c7d6ddc92d4fe8aaf6bffb1ef8",
+    "Name": "matter_g1_multiexp_83",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019f625f232faeac09266c2c4881f92c980db551ea236dc1250189c1e71dbeb151cf74e43b4d5f465c6ad92d75457d10500000000000000000000000000000000175ceb7cef0f8144fd4dd82428bade99833023125d34fb296f198673f4848bbbee343f2f2137b55b5f8c5f74032c1ccaa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000001353a419548d05e568f36adf72d40ba8b30be9a78732660331a5196b0f81b52330ed70e5c635acfa9ffbf083e46c8ea40000000000000000000000000000000013ca17c0dba35a747bcd314d87d1c6558e9f569955aba3d958cc5736db78d16132c9dc8f93d5eaea749a0452c13139da92073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000019a1bdc1f5a43fe746df46a7559bfa0bc5292f574fc424b134fb8b2d971e191b3c5d222d39515dd145819d56d5379d12000000000000000000000000000000000a08d0b7c7f5d71222e984bf574cdb7de76a7b3c61ab5a3ec202b295c62366dd958ffd5bb5a5c6c84584342bc76199c62117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f50000000000000000000000000000000000f2c85f34994643712207fc431219b925f4e701732fce95bfb387ac26ff95c9b10408d24aae5005e437bbae924816b2000000000000000000000000000000000d4377368df00dcde448d8399ceb7508a8fa1c17e9d9a5e09c4fd7c09c253529c07068e4484c7e7c6d3ed6fd3ca777fd9087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000d1483ef230a2ce75a59e07f83091291d2524b5d043db8d5583914a6775ce2c80368d9441aa2dd53061a8d9121a025ac0000000000000000000000000000000019100e75a72e07391db9574b3fc4aa1c669436fa802a1a5d71146c5f4b7fe118a5ee71a9df50ff67633f161fd151b947255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000003a88ed50b36d92aa4411afd0a340497962c7740d629edabd505d6023ecb8f9daf0e5bd8ab9dca26ed2ae3ecdfd98b680000000000000000000000000000000013d9d64ab16ce9401988db4855b26b994da09481a339c2a2597401adb72c80718a4df242776f09ed208a8f34ef7f67e6e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c40000000000000000000000000000000013b16751ff7f6af64c06f9ae6f59e1eb6c3ac76355e6192e6eb44bd1a9f866705eadf0d2907e2458462ad731523bd340000000000000000000000000000000000ae691a4fbf3d0fc72c0e14d4b31fc19c52ca07a81db0ba93949c56a9b75433257d784f7bf0611259dba8af77403f536fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000008ea61aba918d691a0d04582e1f48d671df39bc7de29a6ecc17b31a32d485fb1dbf499e01a9aae5ea21be5d6ff9808de000000000000000000000000000000000f7e8863a541be553b36b8424ba6ad057986a9f78454aea770449a23de70fea8eee6bf8aa30e96e90df9a373917452f70e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000010d41968ddccbb34b3faee226750e99301ac068d8e6f13e72962b53fa2d019da108af82bdadb3cfeecfb85f53607400b000000000000000000000000000000000a90e50ac4e0c39f579a19d49e6f64de6bdd5d6a3f9a91ab654f5be01b258af8709ce1c5a994501177d1c70b25e474a9a49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f000000000000000000000000000000000e85db21593e8d3d86df87ceeea7d7853758d69e15edd53fd7da52f0328805db785aa9aa5db25417d76d796200a37d1d0000000000000000000000000000000015d76c5317e1c8cc5a58a0cf0700ff73d92e7f60f4094030716bb8c657d5c75262825fc0683a88278018b4899a1c1ffeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a70000000000000000000000000000000014c9d6aa24bb34080b9a99d31e1bb431e911b2ccda3c8dae9c2c2114abca597b3849c5b3dca756d0f9ff97616c0b724600000000000000000000000000000000050224129c08fbb2f2d16596f83e2d09a09526851c4d52e8d5f0afdae7001af0006edce648efe7d94b6712d012817ff753961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000da4574f20849e04bafbc41bd361e8f4411815b9e7c2fdaa9a3ee70d4f608f89166dbe9e1cf4ff0fc9ae98f27e115c24000000000000000000000000000000001463727b23e6afc17101cca45de7d08b78358605c7b1ca089fc52f6a3c46f590210083103e51a122ed0768be2adeddefa04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001363808474ae9481f54d40fd35ed90c23d4349403d43af0dd603f1db6f5fd5ad8b77d21426977b78f1f5397df17f0bfd000000000000000000000000000000000118560d0cb0eb2fcd3b2d51fb2aa379112b3075e1d4c20757ec241a4877af271700d3412a8fd6f3f5a3dbdf4dc8cdc9b688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e2252700000000000000000000000000000000040c13a6f53ca485a578c6f3f49d917b774f7b2d1b15ed3e748a47b0bc0be8a7809f0ccf509f09121fdebcf8af46023b0000000000000000000000000000000014fc7869df366473b2c4adc2c0b12acfffeffaf22b4856bed6ec6d15f0f080596b81f3aceab9360e99f35ee7c43f1e2fcf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b600000000000000000000000000000000177b54249c613f044b40a11047778c86f09b20ab387ecb8165c83b36a1af046936623fb00764740a90aa232b7f7ae6bc00000000000000000000000000000000040a52fc58007717d6e1dd8486cfccb1f75827c2feb2b7d59b927c4bd23e5ea80d120875f611bed4b7c12b8a5c929475528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f365000000000000000000000000000000001918e41c557305934aa72aaa361d15843ca77c747ac16cb4c251a2f0d7c218b60a5588b0e5fb3573e8186a48d725e50f000000000000000000000000000000000cc4fa5302c177f9ef018445ab722e568347f4f970dd893e3227756dde9dc8cce3eb2bbbb4c3cd98af0ed4a45c022cf1c32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1",
+    "Expected": "0000000000000000000000000000000016da14ee1ec80ebf06c0622a500a8eb5a560dfa3439a8e53a19b51c6c4576c31a5486c4c49a8050cc1dc30287f33b5b40000000000000000000000000000000003b04355b2d78266675927706874bb7fa67d441886972a8190a45398942622f09ece67ea74d49bd97827fee82d4a7a37",
+    "Name": "matter_g1_multiexp_84",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e0ae8df03e4d6e36e1c156a35425a3b8189b56e8ce90045d16cfebf7fdd973d207db6391dcd007c311af34f495cfe0c00000000000000000000000000000000198e58d5278b2a82606af16a9af3f023b7182b6b5b2d685fb667714e9fb5c7a3fd5c98dbcc84ee31fcbeaa8f832d7c854f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe0000000000000000000000000000000007a130c85d67f97fd0dc2159d35be8984bfbe94c28d9d96bca8bab844dffd9a6eb3052c619646a4e564c0d47864b31cb000000000000000000000000000000000e2b8362ef5fa5be398a3589413ea69e98b15cdccd203119b79d96405c2c9ae9ca8eecc7533512a25421e1748ec3a1b74b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000acec379756a1fe9fa72f03da4dfa18de1fad19281f262ff39fec77684f0798b6d8aa895db93dab58165b67a875572cf000000000000000000000000000000000a246df19a23260961ea578a68ab4ae8811f9f391f673eab2b6fdd56dae8ff3b059e5b69052c9216529603e7eaf4ff306765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f00000000000000000000000000000000001bf749b61d7081f1e6141380deb6a5517d64e8c290363306fa23d6ba3b4e72ef53933f15ae77060758287a5a5c2bd4000000000000000000000000000000001661c564a5bc4dd852f35660d1e7c8193d76a48d1f0f3dff25adf312e28ebe9ce8972366ab224a95a7c1f6146b9f22412eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa000000000000000000000000000000000462a37cc68530a1c45001cda667e1ec10283b826b52986adec03db59a266cafc18ff76a666c9de9fc2384c5e336404b0000000000000000000000000000000010736bad21840f49466d9db82f01a922f4d6ab71f8d8ae246765300531b2f806663da2a8c16c644cf871a877b210b9e3f8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000013ee85b0c8f999c9d0682bf3f18a553b64aed8addf87e4baba55c6ad88de9c9955b82155caa83b8b6b7961d88c16c7dd0000000000000000000000000000000011bbe00b5ddab0b579375e2014021e3bfb1e11b7ccfd774b8679896c0ee34d1d19890fe5cf10e33e3332283b3a3dceaa28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fc3750c957b3eb656ad552c3997755bf28a54fe4aefafde15619133ae04a47f7c65122c86ef36fedac0c8e0d93c3836000000000000000000000000000000000f7f21014b7a9f07c2212af1b85395ef3072b84ee5e59ae675f6fdb9cac858b6213a264a202e29b45a57c69be5259470dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000d1fdcb6768654b6bc1b4d885039f1649066db8037f212b2d699c02606257388000b0543d25aace7cd1426462ec25c6b000000000000000000000000000000001386eb9bb7d8be5cb9e74a37759458091c44eb814dc3afbdf017a891359831ffcaad85d00d8e100886cb5624562ea0390adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000f4d6551f5587cdb4d92e13e3749f977f5bd35b5b71667edd79b5006d4b0943331a0b417f669c6125edc42099bea22be00000000000000000000000000000000041b8ec8547b710bf2c15ff41ea779f996db7996911a5b4ae9f23073e02b2c252592229af738f684e9cdf48aaba0512a345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000000c4a3756f2affd338f688ee90501f4bf4be43a4549ad8ea6aea69e5a4be015c97ef088da1a39d1103f866f1675f401900000000000000000000000000000000023e5d0bc92794536d59425c4bdf18dc5a208841953e5d45ae91f25d3c61bf66e704a8ca62a574ffefaea854fd23b8d65ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c0000000000000000000000000000000010129a00ea1c30e98c40a6c86090327d0a9b6c25b488cb0e369bc5a0e0658ec9ac9305e5d1469dd43395f72ef8a0e7e80000000000000000000000000000000006d2f5d4f3f8169f722427dbdee62f45f9791e55988910fefe188d6535fa15e2aab8de5130e81183e6ca25a8009be66f853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef3800000000000000000000000000000000005364313c0d2220ed57bf22cee05b77a53c24c97addae502c7b3275a19522b8ae8167194929770191b96b957b19e5550000000000000000000000000000000016ca50cc1aef3890dd338c8a89b906812ce26e0ef9035d1a026f686b0eecab718f6b0ba401556423ddc99d96dd812d566dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d00000000000000000000000000000000007dc52982caf2f5efa3e1a21e22cb8fc53cd0355f2777272806710a96a22f8e896d001bec053acac6241c7637df158a30000000000000000000000000000000017e9f4fb0adb96150095ad5f0d464549d1489d04c4556576865ed3045e0c477beea3115a6ce63910f797fef29f75bad521d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000000695feaefc8fa22f81bd48a41e6c85acf38fa542e96a7562b8d65834c2f64cf5770ab6731ca85b0c5a80a73622acb83a0000000000000000000000000000000003df65226205511218c263af6fe33a09fa3db22e636da54dd967741657e9da6367fefc5e33a370947f2003dc139765083d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000001588a4aaee74856a9d41305023b7eee367648085516c8135fca8c0a6c9cbdecdb2d7b44317286f3a06f92b9eee2470170000000000000000000000000000000005aa06c47bdbcaea82e910b8a2c43c13c23bdfe1897efb2a57d622f5251f0db6293ad21d988c3ee30e33f3a40865fadf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000140d9a251d355cc6a8ff9fdf2223df59747eed11ad140297b6189a8d49a711ec748447ddcc45733a3c36a48da8cd46880000000000000000000000000000000008ce7046871c0b7f781c667958ff22da6ef5447bd319b2df36c9fae9f5597c020c12c7fbc733cb75ca8f9d9dfd942954cc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3",
+    "Expected": "000000000000000000000000000000000f1abe4dabd68ac4443ff50c0ecc5425ad60973dfbd2b29462254ad618b303dda061d80b28117803159ba503214b9ccd000000000000000000000000000000000d3da8a424576cdfc147e18fab66de0d1f93c05c0dcb08d9d5b2a9f5dcda76600b771991bf2d67061ea5b96d20a45c14",
+    "Name": "matter_g1_multiexp_85",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004d4ad5e9acfcabc0b93eb9ea59a778a37d7beca03e285382d10d97803ad63e11aa2e3cd1eabf72383d93528e309c28b000000000000000000000000000000000855cbcccda0476699ad3de8d58b4502f9e61bce8d7db37e9fd26ac4649a4cb831cbb74ecf044ae6014c21148382cca3864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000156e86fc66a8b684327a4de77c31abaebbaf2ee5f0c4d5f9238c7d4683f08dc78d59fcdc25928d180a6f292bee23a523000000000000000000000000000000000f64634ec7de1fc93959639991df42e7dc621380f4433fd7efeff72ce75f6ac7738396a912f78ecfe70bfc4d0ac4239093064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f539000000000000000000000000000000000ae2c40a49f6539bb257fd759c2fcc9f7b09d00059c7a7fd41422ce39aa0792413894bc716d66dc79092223b63de6ad80000000000000000000000000000000017a82c6a853fe29f98129998708f0d4d2b09fb22b07474395050d87cfe4d3bbf94967e05861c20680dabf3f4367135a75e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000013b950aa9b7675322d7b39e81b13b14f2480155f74bdc5793425a02f7de41dc1ebefe4f07accd3719feecfe366e93c440000000000000000000000000000000003378e83277e4b02c3b517d3a8cfbf2d2a6585d028c723b2a263e6ba17faf14bb9aea301cbfdfb73f84709e2af99867693f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000004d87c87f8f05a0999c712756bcaa0572b70264166b16eea7fc4785a59cfca18d5b819f0e65e193dd7ec38d0756b84f20000000000000000000000000000000012f64e2dfa3f00ad8f7f68e08b24aae83a049390fbdbaf570a7973d8516dc90e9c5c9211130d5c6c09f5b29183e24201145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000851a636dfc668d1c5d5467774deaa78283a6f56cc514420fb2b6c58ec831add57b5203e31377a57adcfd9097a1cde2e0000000000000000000000000000000008828c34d4e712bdd5133e220167f3424491b9f47dfd95406bc833b3b030037c0ac0d2c84b06b4a2891c8181359af350ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000011021119ccb1cedf88be6f72d3999df899efc4dc28f828831be911582b61894aa37302f84ae9269b97b03a2e30d66c93000000000000000000000000000000000c373df4c0cc1d8a75cf2b9a99b5889811d3ed42850f55480d891b2f44769a371fa4894cb5bf78b7e995b4912cf47dad87bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000005bbd2831bb4eb8ace45ed719056b95dcf5bda8831bc1495f763ff5e82be9708a004a00ecd102d4fd084579d892e5da40000000000000000000000000000000004de171bf5fab4c89783ad1d0cc9fe697b827f023ea1660b0fa2cab108fbcdc80837d46f292b6062761dd865bd1f905f68cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000018b36452aa579eab36db9b0417c999fa334292bc7174bb88e4bb14025a20c86437d5cace5369b90640c81edbf2d60f2b0000000000000000000000000000000014278d1cc3fd07e947419a6a0d7f7bd5f9e13fbd63779ffadc150e3d5efdd1a3f6f6e5ba8516066b75e1925282d0e644a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c5000000000000000000000000000000001365922301de7c81b839e970775854881955f35ef7f718643a97e54746b9d9867ced3fb7525caf5b5bd0d382de02fedd00000000000000000000000000000000000d37c4e106e51c4cb65fef8460846eab04fae7e5ae1d1dbaa1e0bfb2eab7f2e27a9cd5c3cc942e38b021ef71827a0224c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b0000000000000000000000000000000003b9de0464ac24606ae840185d2ca6cc78773b674688a028161341b88907213e275d7dbcb8d8bca15b483922a09297170000000000000000000000000000000012ee2a578c09b7563508d0d94ce6ed75d277ebd89a7f1d6095f8992c0794b4de12e33ee24547c271e17b7a045eb3bf5b17ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a0000000000000000000000000000000005b7aa071b76f93c765f946b96a972c1d11a2c44244355e90cd77ff069b930b2e8171f7cb1ba29f7ca6e62d88cb83c1b0000000000000000000000000000000012cabb25e52f00f89f2758790f9a81d0e336ccd7bdff06a79552a346d1966f54a5157130e5aa8db175aa64a431e19e494615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000dadebc30ac3e033f433d8d012ffc70adc146f4d9574e5431360fb4a8ff0891c8a9f38a8754984a385d704086c320ca90000000000000000000000000000000000238439bc4e8c7dabe260c7b40d317014463c4728d79f521e7e321346747e9aa65bc6b32ee5920969c34421bb99bee9d38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f700000000000000000000000000000000029df69b4ad5cae9fd974da7f58e4c55e83c61eaf011b5f22e1308b56e2c31530c170b304d39eb3e8a3009b67b308c6700000000000000000000000000000000140451659b4d6eaf05db63be5a7b0341612747eea7536b958b0620bdfd7b9918e8bb76c05eb2a528bf4727e38605f99a364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c745900000000000000000000000000000000062493361a1a862e63eb8f20b0610a78d30ac8595e4c6c3487cf3add7cc38613870c2ecd0cb5a869110a99b76fb9055b000000000000000000000000000000000d8918e018ac5490c91cf2574e6a6962b69c17883caf2caa473de172b14961780fb237236b56a236ce8c674dc9001547031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f00000000000000000000000000000000016470ccd107b2afb9ca03a0efb958bbc165304871e683fd606d2e78f65e34885668c6ccb655d4fa98f5776280e63cb3000000000000000000000000000000000982eaaa34f9301fe0ba1915cc5632329715c506528860701f5e52d1d77b8fabc89706af2c4ab3b729251b9472cde96f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f",
+    "Expected": "00000000000000000000000000000000112fdd661f948495ae2b9200d959ddc873c53c96ee4ec175f6748e62d76d61d6b15970d3a3a52ae9bda30a822ada25b8000000000000000000000000000000000f5b38208d69b5b842bc22ec9d91eb6b653acea5cb16569c61bfe0921f2d8ad613239e87d48c6a9b29ed6a5f43578987",
+    "Name": "matter_g1_multiexp_86",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a9494f10e6187fa12d88e350ab84ab5bbf999554924e781d6470e700c3da78e411b8627459b3359d7363b088bbeb0da0000000000000000000000000000000017edbf1108591996f28ae17beadfd6b52340236c2741bf8474dd7471c19c1f62a0f28e8d8692cf3e700ddd86a931dcab4aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b900000000000000000000000000000000052f9c6ecc29239c614936bf9ecdfec677afe80de019230180d0fe529a2e82b9e15d6e081b02475e2bf812cea3ba6c640000000000000000000000000000000003dd0afc91516b50d9027c0b132453fab92b165c08428fd5c2cb994646b6b1cd5b82b7c3f7924e4a5cf8b45575e8dfdc22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000da6e13230b2236b2bdf671bd5f3f8bb47bfc637d6e3f1796b555a95e51b86d04fd310f3d3198dee604baf48f69ce0950000000000000000000000000000000018d209b03f61056147d6734003daa776011b70a57e1ab17d3b92e2565b31a846d8fb7c3fc6fa1fff04552800b73affab895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f683200000000000000000000000000000000116c72b5bd9d30182463c592adb8f73c16d22bb4a22832b8d47b683da5f4b8179d4c80d361ce69f92a393027ab29c18900000000000000000000000000000000026dab8d729338903d46a219004fada41eb666a9a90d8ba115f53da9e89a7bc5d824d7f4071c8859df52b3ede7b7dfaf4c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000004e0627475a0d4da458475dbbebd6c36f4ce771bc2b2a8c6adfe9d372ffed05afbea207476af26974476c0cf51a9267900000000000000000000000000000000199ebe83e44a269752d92629810d0c5402f53a1bee03ccafe0b3299a9968ec45abdb5a74a6d90cb026cd9b28cfd2b89f6d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa000000000000000000000000000000000b4918f4bfad81349edcb45439e148af7af6664094412c9a51b887271cc3c46e34147c8a306a19f08922bda9c7146c61000000000000000000000000000000000afc3d1a7c4b6d899149801cb74a7e64a126631b3e758a73feda92a2867c53fd3efd9adf025ca6f6c762029c57706b0b4cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b1384000000000000000000000000000000000a093eeb354ddfc5ea3090b20312788923c5db9d78905dd31d5bf15cd83521f2f186fd284de0858270eea05d21801aae0000000000000000000000000000000011d047410dbf6df20f81971327b38996484e0862a9f71879ff63462e189471c1ba391496753456f0b5379a3b36380e1296111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c0000000000000000000000000000000003d8e8e3a442f911e23b353e9efe396b746360254c14216c752fad17d96d440988d5a25f044afd37f12d74c89c8cb2d700000000000000000000000000000000179ba95a3d3b5ddd3d181e2312385f4ad7232d9af0c28f375e2036157e4603c1a01aa6c9c91496bb28508e5885bc2e599d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d000000000000000000000000000000000f56dfaafea0ce3152458b7252fac14ea64483e1d4a00a44f95bf3932eda2f2c51f0239e6a7a503cfdbbdd88aef2f4880000000000000000000000000000000010e02e9be7c1b795ebaa84f83bd27eba4f12dd49b146db0d788e37835338d352445e82060dd595f616b4f6d2d03cf4c911ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d300000000000000000000000000000000010ccd262b0cda9ad39177d31be0725b83e935c690fa8e07bc7f24e26f8b03122173f4ba43fe8ac933a7fed79f4496c8000000000000000000000000000000000318da543dfb04005a3cf6d93d6bc4058b4b93c4cd84ef978e6a30dd85d60e5e359b4f518842e73d182567ec4fb236b8b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf4400000000000000000000000000000000003dbf6c0676cec0202e328bf408a8fcc38758db1adba3e8184cb3904ed204b7e18db2183f5a1833737ad8eb089afcafc0000000000000000000000000000000014d9add10a0c739dec7fd09c57b3e959f3b7551eab8423ec5bcab4b14e63b7a27f128758d63f8e43a22eeec7bcaddd41a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf2411691190000000000000000000000000000000014d0230f7d5c51e6fff6490c61972e2564bc31fea4a6d1f293424934f75629cb96f189c80ab32a79b2e988582d0283960000000000000000000000000000000011813cbbc0cae4cf6a8d5d58859f1c3b75ac53819129f92abe0ba9123a1a277b55231e1a24745d0d2ba6242ee758113c87fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c5894500000000000000000000000000000000006adb1e972755f04cc57170d19414e6930d0e6d42c09f587e490593a5c01ce6e827a6dd1e21570ba11c7e4277d532e0000000000000000000000000000000000ef599058025f40c9f77ef858aaf314faaf8d72277cd319a84a9d7038d81b76aa260df0516dd38633b22f9d3996e4761c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000008190fa5e3d23c0186ba502a5892b76cf8faf2c15c91ee39d51b269b6bf4bd3e7ea395787d989c1a14ad88f3702cd6d00000000000000000000000000000000118d2d1b28f9180155277b80f1a7937dc7fe6be3b00cbf6a7ddfd08cf653ed11a4ddaa44576e70b27cacb7646a100d03f29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b49899117000000000000000000000000000000000d28e7ef8433f8d5399ce3cb847f2633392bf44ae9fb2d402ed8e7e6a22de35c39e4f09ea0fe673ae3cb652f75ec80bb000000000000000000000000000000000ebf2ed9df06e2d5688d0ea812b7f9de78fe292584476b20bd62066977f5e221dbbd8f552547f06a3e821a53aeab83c1125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000e162f9ba960f452c269bd2f9f06e8bf1ffe737788d6364b1f75ea2788fda7e265dcaa907e45bc6ef7a31c4791b470e0000000000000000000000000000000008a778bcedb58f562c7b69ef3073c81866a395d6408829816be3172e1e825ca6b88f156ed2b2ac5a8784fac62b893896bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6",
+    "Expected": "0000000000000000000000000000000003f6acb4e1f7856413fe9317bc7cffa45f2053ae62e3b5e2b82ad57b48cbeb4110f53dfcace08bbb1955c21b50fc636f00000000000000000000000000000000172cf1d257572562f9fc390f96f2c39dc5809765b0b94240a554f8bbcc44e3658f22e81d1e6c43742ef24882934cbbed",
+    "Name": "matter_g1_multiexp_87",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000013d3d80910d9f43a707ba389b03bda49b65081f65096bdef3942f0bde2122ea575abf810f400d47ced92c45dc73837000000000000000000000000000000000755b4f5a055c718f268cf3a74533fe8e8ebf37aff3045b58927ee6ee7a862c8c1cd61f00dfdaf6cccabf981fff16c7908c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000001530565bb621f7cd530c0eeb4cc41c2587ef8123c552aed339f80711c157e1595baa140434385d0977e9aed2629ea76b000000000000000000000000000000001806c5a90120fe65450e84ee0a56e0176e944a3fffdd2c83bf15d7dca875790d2f842eb31f640456a1221e44035ad33ca0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000143723a10965da7b47bed0d0b5bdb6bfef5b748f6e185ff2efb73c5756d41d77b8c217a6d92245ae36e0add4d743e7e9000000000000000000000000000000001274e8842cc812435a576b2ac19edb84f72d08cffa129d7f4e44be5cc88b3449ecaa719b4d76aaecf08ddd30f7b184ddc252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000379e08cc1f47014f7eede433abbe881818c0c3a9cb02bad8cc86242aeb9f9542aedb67313f494fd19971a0a15d4ee1a0000000000000000000000000000000004e83a0e52981faf6a787d0600ccc457ddf3bb81c76117265c1bd011e5b4f3237383e97dad3b019623521b3c94d67df36d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b0000000000000000000000000000000009ec14585b72733f621a58f35ab30580f131c93db491d4d704c8da2a7a0a1146e144575083bd963238434e2af48d3d57000000000000000000000000000000000ebd1a1c160ba7c8e3c20745bbde05f08d7f3189ecaa831d05c6a34562d6d3ccaa92472c67bdebeac8494658abf2c0405c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b197300000000000000000000000000000000084b7f967b141c94df69804a723169f69e05629c97a7a8c60b140787f3361ac87458372c91e04c08da2d01fa96056ef8000000000000000000000000000000000d731a1a900551ca569b8066af85176b934b94332679aa59924eb7d9a5fd776a55b4d7e5ef2413c53c244c848694b06411009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a0000000000000000000000000000000001847861de1064a4226435ca43c1cfbc5d4660fcac177654cf5d497ba9aa5a6322f1156adafba852633e111576698bd00000000000000000000000000000000005ba738972bf139d91f0a426c96fcbb3b77a01af0f2316f2427a20882b5f355772fd6d6016ed77c31c13f88b26c628763e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf000000000000000000000000000000000148907d2335e046c50fe213b717fedac86eb3920099526a62b4466749d435f5ce11a45032b60bd5d7b26799adc63f830000000000000000000000000000000004bdc2bab60cf6df6dfd25c16f04edd96d5021b97ef38cad02cc1fc7f12494098eb793d99d15b327185718f81ec0ea620444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000145ea0ffc3b24a623d74c27b84a390be062542795eb93a2f71f9358b44b76b93dfc0a2ae507f07a8a07edeed2410e5c10000000000000000000000000000000000d407c6c245316b5cc6b62efcd082829354d7e9e69ad739ae0ee55e6096ea08a48c59ded4595032093c32634576aa132035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc736000000000000000000000000000000000123f333f3554eac47c8daa1d4b362e42de1834ba9f55e4fee138eaf1a057036aa6ff9f50cddc78dabd3d5557b05b8bd1000000000000000000000000000000000116d786097bcac320327d7d56aa734d76d48a677e9c02ecc0bce550d75082c319f568d94b41e1c57c6075ee994e33304bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e826000000000000000000000000000000001012b1790e287a6328cbbcf80eaceb2c518a70e80cfe17143a41c4045e8c6c5317aafcb34f4f56494b401a8a9f21b5fa000000000000000000000000000000000613a88e513248538c1b767ba4d3667bca7aeee7974f691b7e4f012ea9b2b32603eddab0943229f53324c51838d18fe3369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c31aa6f315a1102ea973d13e858d079221087edf178d98fb05701ed0a159309fed05942626b29ade066f8cef465535000000000000000000000000000000000177a3468b7de9612a93b9f2bb3f07acf505f56c63f798b4dfc38a25d0fc133c862e90ec8b40dc94004cfdcc9da197ee7ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e12000000000000000000000000000000000092095e7a431ff3a8e51e26c24dd4a5fed6d4a4a169b5ef79e8822611da8aca5d7c27139a911d5473442db9ee1529bd000000000000000000000000000000000c59f5a649682e864a792ad50fad57b7cd14cbb19d1feadc3536515f01053fab26950f56bb78d5a51f4368e73c19062f2cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca3610000000000000000000000000000000003930511780f28217a125f524ddef656581a4ba2d461730f0837d1846d63258a02e659b25b882a3c3d077c880a64e3cd0000000000000000000000000000000019c682245c941c76605502785b1f79d37f65cf9ec61a4558092973bb2514de4e5852fc757c2fc7eac1b01d414248acdd60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff7900000000000000000000000000000000000edf518026cbf2dcca1d46340c24fa947261bcef36e3c8d026a09068a10a5afdb0964b54b70bb3b27e27c4d2e0bf9b0000000000000000000000000000000005cf718694ca47202be8c0afd56c88742e2b467d01e7b2330de778c434a57610fe7b8bd6071836a58f5d6b2876cff05a652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000013db13bf10b6d8b1ce5dccec98745dab635b8bc81d03601785185cccddfe2dfb3f3f9f6ed16d2c1a7a6bd63264b094d60000000000000000000000000000000001080522766b6cb5c90e6e0ae11ab4ded3db3ea3c7e69d00f29155283f7b25f762eb35bfeedf00caa83dcf04f22ee72976a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20",
+    "Expected": "000000000000000000000000000000001975e01d64d38cb8f852b4177104388aba62c167da2a8530bc59f510a990ed57be0c6ddfc92622234a121ca407853dbb000000000000000000000000000000000de737b9097065462dda93321d5881430f50631f6f3deabca4c41cd491f1e441139bf8ceb598393ab2a3424b0acf290e",
+    "Name": "matter_g1_multiexp_88",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001624f6ef9638cdc5f0b16b47ac8c5465cb479333a4ee4caaf6d2b656464d8f84387f01bc1811924312e6cc1e29a590c300000000000000000000000000000000012b3bcce18f60c4b2159df93a2d536bdcadd675439371acce011ac5b542fe1bcf89161fc3b3644679a395aed31dab569f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f0000000000000000000000000000000005d49b4ea69c41ffa7727b98db08ab3fd3ca0c0261ef04b426ef29e724bd6158b3f3242cb915cf0992f2a631fd9b4421000000000000000000000000000000000f635c26698cf5dffbe25ff496f80c5de6b181f94a907204b79b548c1fee8c7dd426b49e9eb9eb0b17e34a26628c38e71deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f1080000000000000000000000000000000003a80767130cd3c3fff0610f215337bc1b4a88886778fc0dcb6bd3cf7bee48f4c23c974c8883e2cf32fb01a84f9e148f00000000000000000000000000000000173f518f3349c1f704fd200747158940ecc395b04b4c476f406cc27836df182c3f1b707aa05767ff1bc75de42dba2a824b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000011874da4371ee8bddb34bc92fee6bf51226878e4550aa33313a434b75243c1f2296c1d62d9f31f6ffe2735f4f26a8082000000000000000000000000000000000f82551ba2b803e35c7118f4294626c151c7137eb4b97aa5265ce383f7ebc5ff5fe381776eee724aebb963d2bcb3d9f6124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010501308d1a05e69700111431a0ca99aa41a991555b9a53df9c38413c67fa1b1836853bda93bbd8679e7724b3141a8d0000000000000000000000000000000000b033cfca384e480f73a4f8f79ceea706d7390e5b702305b79e30890e158ede03814d1a0dffcc3608fcb9926c5c65eb65a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000c7bf31a1f30f8e0de1a4a77b8b6c115d1a5d825b51875cba3db857a9cd2c589696ce2abe5a87acf8d6604c1f1f89ab70000000000000000000000000000000019ad7a6190a69fe1df07d55f8c792fc72cf2be11bbdd83c06325682bdfb5c31efef11fcb819d39f25bb1978570a250218c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab870000000000000000000000000000000012befada1cdf63d34ee2334ba2e42d7e69ffed71a39714e7ed89a86fd5cc1c65a01340c986abc37e7e3ac5a22a2bcc860000000000000000000000000000000006e5b16316867dc33a9770aa2283691f379581ff2b0b7986003174d4862d8b73bcc3f325c9a90097328f881b15f877c7f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff0000000000000000000000000000000008e763f110c9415b63baf27236f1c0975e7bebc04bdaf47ea0d3a2709a455ea48ffefb7551a73c9d599bc5c9fbbca78f000000000000000000000000000000001492e70f2831c87222f7d7a9d00842870b77aa68e87b8cdc9d8ba61f86adce6ea514bf5b8f9d66937b1b640c43b02fac22d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000001685898af1ad3bfd350980872e6438048f6cb37398ceab33d7bae1d621b5b2859e6a07b4e4db891af37e29881cf573ad000000000000000000000000000000001084663fadcf81b9818c999c26a84c6f9a3a1f71a0a2982b5c6d01c56c2974656c08e4ba7833d1ef8bcf9af53d2f0732e816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000013b077eb9130821bcecfe9b366c7a14f4487121095d325e74de44ea206078a6b1ac7d29a4e80f75c7714b6053cf2995a000000000000000000000000000000000b825b95b52382195416477f0bce73f06167db02bbcb91944e9e7534f804973bb363adca8b5ad80e77b70f4f1b9654d004f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea00000000000000000000000000000000014c48b3b2fb994920957b046643bfff19533dbe533df980dc60d9c852a3d07b8cf67454820a89ec9c7ea73a209f911ef0000000000000000000000000000000019b19e64d977d40b95050e4af365541b6c815534dc4abba7ea0af4b0a7e6bff0495fbb347250f5b5a48020ac20ea61cb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d20000000000000000000000000000000017fe39b732e6b815bde4078cba9f926e117349e3e49fcfb6308a0a09296fa27da4580d8fd18b0ecfd0ca68312cc0e5c10000000000000000000000000000000018a4eda1862c5c296de2eea0e720ba13f8a60defc65870f0112ab394e8160d6e1a0beff5db8c450d8770792b7efcccba05edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000e65750f3b9690f25b5bf80de0d76da21752a0daa8ce01b2bd8d172577f6c7d46c119ed20e73617ea163575705343c4c0000000000000000000000000000000019d0f934decb53a477b37d894d6e651a8a4f25b9375bac6b6d3483ee8d85f56b8374bacf74bb8550bd26b3d326962666f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b000000000000000000000000000000000907fe95f32e22ed75f94d96c191bcb19f88355bb84f91a8a535441da04dc211376435ccc60ad2089835b51e79f24b5900000000000000000000000000000000071e35d64ffa38024f4ccf7c4a713e22d8fb4b8450ba7b05ec5e759c2f8ea30e7d9e71ec2c90b8c667370131de785116059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b419000000000000000000000000000000000ccc90617f386ee2a76da43a745972066955c8e346d3de214834ea79423e7d95a008a6c119d640491d515b801034452f0000000000000000000000000000000002588711ccd23b65cf2f63b2d602b1d7dbf97cdbdb159e02e3bdf84fa65685e14d4832cde3662950a7fcfd11e68ad40a47b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface0000000000000000000000000000000003b5acf5f4e39fcb32a267034c5e905eb3df32f2f6f7150d94cd17bf16e3a9fff9dfdf75a966040a6af5a623787a40170000000000000000000000000000000018e4b8d163e5176bc9a45da14fabbac696ae6870717bf5f6c00b5c73dadefbe329d86a761935b18e81d65ab6c48e241567d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c",
+    "Expected": "000000000000000000000000000000000905fd0b04e8db7657431b5f69f1d896e79ecee300cd67ea8fbedcf51b0389506da0669c28ac8158d5357a61fbc3976a0000000000000000000000000000000003235ff6d1acbceb35cd29c5fe524a452064a6e51d1480ce1e57c538b2ab6ec4f98c3bac676817e25e2d92e701ba881b",
+    "Name": "matter_g1_multiexp_89",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000688d6eaa2964e33cebae16623e228256937ce9a7721c4fbc85233ffb3edad5d6349d9c8a00c16faa0efd9c54827f46a0000000000000000000000000000000019fa249ce7be07208cdac9f9927163bb1b79b40b320623fc1a08a299d5500cacdc55386ce451173f683a9ce3f006c1e4aaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c00000000000000000000000000000000078eef7d7951f257b17c579fec05f3efe332534b2f56a953a701a8b92664b9a0b37959f7c3dbd77ac18a5e72d174b9f20000000000000000000000000000000017cb59169aee6caa1dbc3c47c29f977a44a81d33f1cd298d5df3e9469c8543d919b985e1b588a96a9268cef03876effbdb48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f200000000000000000000000000000000025bae0252e5d83a3b76f2a861ebb1312bd344e3eaaed5e7169de248137a929ab94156be11e9b16ff312180d856d93900000000000000000000000000000000013c207c57a4876f6bd6e8e87eed0021d5e6b2aa3b2a323572fc2ad521e807c366fe31ec285c8412f89328cdf09dcbc99ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f4000000000000000000000000000000000ea93e5fe055ed1ce77de5d298fafdc4201418489b64d10c447de3973c1b98c184c0cae1d95831742f3d50613c5cd8c40000000000000000000000000000000004f2f3d0a5caac826632ee95dd1aafe181976552abdcc7db737f5693f3d08d3c4a85365e05e369365a37ef1b3df5cbca36d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f460000000000000000000000000000000004c8b69c09f67ad17e8fb9fea4b7532c7c5bf3edb7669e26eea4f9c8f0bc10b0b1895acdee731da5999318d83095ef5900000000000000000000000000000000054f950a1ae65dfcd40eca15e5fbae984e7672a23ec030eea0cbc0424cc8073186b8442e0d71d6a4a77cee37c1108f941a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000b69b6b7b6cb1569ccbcea029dc71560d54b8bb88bd33af1c12a09d867fbeada2e58585385f1fe508a0dcdf8d2143f71000000000000000000000000000000000277561e6ac810ddf4c46288a065e5441ae0fe2d7ee79ebd6cea8712281a36f812c0bf49c21beb63a1f5cb670dd37d03ad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e9000000000000000000000000000000000c0f4206d4cd564be1efcbdf57f99ce43b97d3e170017fe352ed3ec60862f87730d4d9d9d56ea0aac4f586d2f1786df900000000000000000000000000000000073202e8c73d14469d15a392589db79f3897b72bdb2b788da9012c7aaa167a157f85f3431161d35f45bdfe0f2255b6378fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000191cae6012ca07ddf511ed586ef19e9f0d913d081cd752f033c9f74c334c6f5d075b4f6ec85467caea7836f51d0159af0000000000000000000000000000000016e65314e34e1c7ad577a36eff992abe6f26fc5349d12db12394bac648cbc1452cc366aff69e8cc4e2e5bc85db237a863023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e0000000000000000000000000000000000bf583ae5e3a7827610d91c0d2433c8d358fbc12c016c59be8454c039197971f90191737993bfd08aa96d7838b7ce6dc00000000000000000000000000000000046fc386c5b456bafe03fc84b4f98939f9c736ac74cac507ea036d2443066090118138547766f637537425f64be9691b8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000000f08b3868ea056ff8e82fb7e22a6522985e92df1df9db77f787bcb3ed701bf8c90badcfd94e9d3e3b3b68ec497b9fcc700000000000000000000000000000000002e6f5e9eb44fcc7aa96a43856a707f5a82cb4c14c99b21df09e666d4802d15fb50d535184b63ae246d4ad77b6c4851415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c000000000000000000000000000000000167e13cc54e9e9866bddff0c37e942ef8393a588ed3c2e90da12d0a8360edd6c3980bde808ff16588a57100d1a8898fd0000000000000000000000000000000014b21a7a106640b55cfeb19d3c23aabcf1c0be78fa554613e68404978b78e5d34b6b6378c2e87d0b8bf1cf3444d0db31ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a9740000000000000000000000000000000002e8053215ae6894e8df09394353fe98b38fe4b17b9f20c7b48c4baad91519587f63b863e4de79be71672e1fb00d337a000000000000000000000000000000000c2ef9251a148f1ba8cd75a60ee18ba6328e1c3a6780c790cba3bc91a2145f44cb8bda5257c03890d5c5674e4d09296d039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff3000000000000000000000000000000000aa7fdd550eabb1b734db00400304be9663c008d322d67fc771a85991bca6413ec07ab3adc3cb40d390fd41021434b97000000000000000000000000000000001994d9be11443f0a95a2ba4f7240a9dbaaffbc70256aebc0f10c322fc5b120feb2cd8492d02c60578f8becd7a8e589c92c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd05550000000000000000000000000000000012b3574c35288c63930be8024afcc91194b30d2b486edae832dcb34778886af5816f7478df166f0a7e4752d8c12423e30000000000000000000000000000000012cd382d17ea10ad3fbfb40fdf4f3814a19384e302542a0f5731920443e4498a1f8f4d89086764beff079583a672b93bab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000117294ca9961249be6570ea760bb1e562cbd587f78be482263e4228171d9ee3d970b234455912299933689096f4afbd000000000000000000000000000000000029f88a99c750a388eca5dc6939082280ddefbf7d23997cca3653aaaa03a3ee4677fa8291641ad1f46fee0f8f1268140297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000001006f64c279f074bf036897ded9deaf9b4ca380a9a7542490be675355c3979b2925be09ac4613fd6b7a4a8bb9e357f70000000000000000000000000000000001537e170e8dd88a92a6bfedcef69bb370f7bc1f32c36d203f5b6859be9b60fcb4d1e3948687ac7791d867e7c200967eea87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99",
+    "Expected": "000000000000000000000000000000000603f6b2d8b806c5e399c686878eba299131204084e2c31c38640d757e8a6e5b318da785d54ec8335398610e9e3956280000000000000000000000000000000002abafc5839180e8aff2bbac4db043e8839ea25d8fcb7f6faba2a1c0a567863f256f820e846e49b3296a754650ca9b4e",
+    "Name": "matter_g1_multiexp_90",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a2eba2e26da82458a494738fcc816405760f4991616d729415ee502d13951c319be796cf35d88a8e00e17fa3c58126900000000000000000000000000000000117f6b75a6e25a786e860df05505f8e107b23c6f4338b2f87ac8740554304046f7cbb43f2193da35350e5fb39077ff3f9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000000f6e3064df312fc97c4f30d3cab398f7921453b933d428a4162a37af5ea27c79d5b21d1d305a9609c994e61e56db226a0000000000000000000000000000000011edcb47b9d5339d08f24be87e52eabbdf701ab15f7799a5ae26cfca9d49e0e9107d9d1f09c711039d096a5745b89c9164be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a6769000000000000000000000000000000000d6bf9e905e907ed86f5d3a4cdf61c527ef43ea0befcf6bb7eb1bb790b3dbdb83e0b958836669827251da94db1d07c420000000000000000000000000000000007f85bbbc54af3eb9e1c7e4c4700b4c784b8d2e6b2ff6a981a534317766790942898b4eabbb8d6c893180a436faf88870c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da000000000000000000000000000000000b17bd374136dc1717cff915f7c898e049e892ced4ba57a16752a6dd875cf1cf9a2005dec3e3bc6f87b7a257d5ce7ca6000000000000000000000000000000000874999db06d15bd4b2f60e9b61d195747d12f38b75b74f3089d5b47735e9dcf79ebce22505399e16492c4a6e0f83abba2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a00000000000000000000000000000000179108aa8a7d8443f69b7c906f9a4869ff4c724aaac4fccb5f52cddec86e32180b3ab2f66ba76d57f69416b70334a0f80000000000000000000000000000000007f83a847f4c7e7b35fd091249120bc59719ede5b6db083b33f5ea6249f9e13457511db006f416e0fb9614b8d22d51e1e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c589000000000000000000000000000000000154b40b3bcd0ef04a5e1a550215c238adf07f92757c227e4d32e42893ee8e7e4fa9d7169005220d89b11253cffbdbd10000000000000000000000000000000018daff3cf04f648e59d00df4b86d8ea5dc74adbbc6fe4f080ea7a84dc6443d8923517a11f264f700e209af9bc52f759c76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000163cb54e83a9935be82161939360356f7f0cd0219f446fd243d05f6333c68a1aca8f5d2dfa2b54dbc07f81f756ed6bd7000000000000000000000000000000001667e7a040817e83896d62adfc4a9f3d329e87f7d598217c7d2195c5b0c3eb58047d4b9bb640e3959f7ad1242e10783f7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d000000000000000000000000000000000222ed79e925d64fb58bf0cf105a2087c538c9538070bd742f7acf5e00ab371766d286fbccb3e708bda2d227523a40cc00000000000000000000000000000000126a9569e9ba97e5c41cf11af3a601560d037f1594f2e352ac86c744542618e9d2b6def0c7d3bb6a3707b80cdcb60f15d35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f0000000000000000000000000000000003786245c244c9508ba94e994dd510a7485f4aed711c75a2f509cf01b784eb12ce2f3907156aa15675e36b4b2587e9770000000000000000000000000000000018de0e75256cfcfa2df959f1491d87dd5414a1b51b6ff02ed5034394ea636fd0bc5d3b3a3b84fa7156ca7f97aa65feea3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e86800000000000000000000000000000000026828a6409635184cb929a5b3fbb881ef013e8342cc9b5123ac82e7ce24fe7aa6a507ec3c017bba10126ad9bab5e63800000000000000000000000000000000132cf4a23eac460fb1a3db9aa43b542ae55d19f6bb2f408c399a570c1e479c4dd0462f9573c95c953bee07a51c543c4e10325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c1138200000000000000000000000000000000035220c800af6a330df6b6b6cbde47abef2e5fafedbb7a0feb84a317ca3cdb79eed934847694e85e2873ef97b31b6ba10000000000000000000000000000000011edd4c17352914beccd8c062aa7b95b913f35892c7cc5dd8f736a31a33d33a98d8f9b4be97ffe608531eb7c9643f32109545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb30000000000000000000000000000000012148b58f805c38bb862dd9847f12aad21d1ed760a022d2f619a0a077a0bd79fbbd6c066f0f6c58517ee9e912c60a37d0000000000000000000000000000000018dd847881616f7410f29d4e68854ded4e97b31d5112fd46437739ed62e6d78fab89b078581d052266b7c2ce403d3a79eef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c81000000000000000000000000000000000e36ce625adc496ac94b53552effd651a73ed0c69abedda36e88d408ca7bee73777fd87b4f55e2e8b567c2fddbcff3d50000000000000000000000000000000008a209510caa720f20cecdfc9b0bd71d3fd4015627d0227a027aeb9992ec8030056a5046feadaf149d2392fc98fd60bfc8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e8000000000000000000000000000000001373edf053517ee79eccbf02cce4b4b67d6efc53917b7cd548379c3f78b447ae5dc331285a28bc2aa5863befe2d26f4b000000000000000000000000000000000fce7f982bb8e937802fef7b3fac517054e6c9b288b03ad6497734d78d4b9074e22b1acef45938a08440948dd8b88683a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000001ac8ab3b3918836a5ba14e3d7c44eb8a0d909dbfaa2772cb9d7f8f517963662b5d4209e9a5d44ca0ed897412792792800000000000000000000000000000000169f8127198935f06d26ad8e4ca3ae5b95ad967aac69f7958fe9fb9c5b1f0e98e596fb73a0d8bf90174ca21a02a3e2c2dcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a000000000000000000000000000000000c1cfb4660400ad5d7ba2f394cefa878c6a8fc214823dab539c0aa6d08f36ff1bd706be273f25ec5f1abfb06bb57e8160000000000000000000000000000000012ff9bad1a1d71fc49e96950c74d388229d4e4c68f7fcfafa42329ae06d4dd3091b5b1c95f6498743393b6e3ee794e4ea6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce",
+    "Expected": "000000000000000000000000000000000630b9d9da596e60b15251aa74de850ee74c7804414e3e4d7113cb3d5ad6b985e436aa67bed66c36f135050d66f91f75000000000000000000000000000000000ab084fa126c573ed007f983e01737453b3dcc99ead0a74cc0e8d7cdad89ce81489384b311b7ec4c34736e9520b37e1e",
+    "Name": "matter_g1_multiexp_91",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015fec44912af2bcd34f1ad42ed24b6ce430f6d07b311d65ffd8b6d726ca23f5bc4b7437d158a36bd1790e806fd5ab448000000000000000000000000000000000c4a4de9940c7c26999773a396a8f9a6ff4b86f0525189426529d9cca037d504385dcbf1c89eefb5ae2cbbb394be42fc92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b7000000000000000000000000000000000fa1258cb0d8a37009e8c56228bbd11aa854a4695bfe96ce205efc1c9f32bff8afb64df0fb7863512ff8db6b091f146200000000000000000000000000000000188f128e662e8d28be612c8a17cfbf28b965340487df40bd3f0312187d027cd23b50e713e21f8595bc790ab8011919cfc193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000000c7b39bc2477597e37910b1888ba0afe5ed03e809618bca0e543add93519909b6cdd6281e2afa65a9b45627dc1c6334a000000000000000000000000000000001335cbe866b3139dbe22266c4ed5f9fdbc15a1b338a290a590c03811b6448244027c12d118e6f829dcd352a419bdd8283dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae600000000000000000000000000000000178ea2552d03f645fc3060a61b35af6e3e12095ec65b2e9972a5e346ac1019593298925a887e59a94af2adfac7a8361d0000000000000000000000000000000013996dc427ba51c4ec1f67b30c95659f35c8e71a225bf357f636fbfb428140f9b9e5602eda78bb38e87e3ab77495e505e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000013555f26c2e10b79f8f2a4c397dfda0d8839a35a7cc15673ee5da34578f3fc4d38bd0331a5c42665bf40fb2cf693f31e000000000000000000000000000000000bb16b5b1dacac465a751a68b99def392a69a293377a22194fa4d4d6662b912d3ad804cbe51a4ec4792229de57923ea14dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d7200000000000000000000000000000000120183e73d23355da316783eb47ca687ecd34d85e800aa65d2c95aa5f8eb730a33d3273307cc05d81fdafcee5138a080000000000000000000000000000000000171f5e63fd3c71200720cba782ab863ace945cf405a2f961baf39ffab2d3283c26347ba297d16c3f2567814c6f9914e795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a956800000000000000000000000000000000095ae1795306c8a8c48730987a842a05fcb263d1f9ea49d3f3c0ae70c7ff636fa4e7fa33a35637059c0b11b1b1adc6e000000000000000000000000000000000185e08447394763607d6efd8660118429469a1f6e7edd03a7a3e12ef99c2a15670d1f7ca664a8a14f52814db9810ea2b25b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f08380000000000000000000000000000000012b0afa7f55ff9131a9399cdf0fbf2da69dae7cd504a0160665f0cd74a02163b8ad7ab05cebf3195495a1637134cee450000000000000000000000000000000002a130747763c25b9b6c0436390da91f02c9d5b24178318717024390a841baadae6a9f933e7f87f7965fc96bb498ade5df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e0000000000000000000000000000000018911ed6adc5f48db7221656c622c6cb981b1ac1bffd64e30662035c0daf4bc5accbd53cdb1fe8eb60628262584de15a000000000000000000000000000000000b753d21d823d1050f109683c7c153514dd06663ed0ce118e388d18d36686e94588159e5afbeaa492d021a700caf2dfa959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000001107292ce4d57209e9c1e2c396688ccbe005699de4e77b1a221f9004585ae6cf8f901da6811ad85a88cd85cb819d040a0000000000000000000000000000000012cbe9c273a8a9c1404abe51af4a647f6c89e7e177efc04233586d70df6dad3aacc9ce2a9fbdcf2ee5c73396fe4e498d26ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d7000000000000000000000000000000000297e52ddc42a7da1025d43f46df11009ee035a9ac45e09a0902ba86fcfc5a4bb4c35ae8b0e0c9b86a8ed7e5ab751947000000000000000000000000000000000319c082c39ce4e59b952941dd7d14f3fec39a9eaccdf7bb41a2b935f876ebbb6778c90e1919c1e5804df91abd3bd9d5f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c2000000000000000000000000000000000ea5a9d96509cc5675e165e3a7c9f99a8c6b7be9c33fe5fba895a2d96a68e922271c90badf3c41b3ff52f359f5c6dae300000000000000000000000000000000106614bf5ae42409881f4889a82c6a3bc8000bcdec23b093ebf29b24cad128aaa7aa17566c4293f67af010e9b5950028b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb300000000000000000000000000000000145447f37207ac8d58c706af0b900dfc1f2638f840a0b44fa65245b5e671ffc6c008951ee17217e010ea6cd5e8477d4900000000000000000000000000000000187c607539f8d2b6afd15efa353e2fd1580cee48c469992785f02b3ea3396db5359e0d6743ff8d41648fd8680a4a8c2bad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c10000000000000000000000000000000012a022fc2dd9c201e9d86a0983fed4a71abd086068b8ab8c9586cf51230acafb084d559239d86a3713aef4b87a04c09b0000000000000000000000000000000017e02d69776c705bdeb9fe06d412a67601c6763a19c840f15f96de0fecf782e3a44118def54286cd52227361f0db3bf93c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e09d94291ce5e8310871aad89e0744e6b319b4fb1089048b0181cb9e885aec881fb7577fe0e80222793068deed473560000000000000000000000000000000017c8676e4b8216a98d9e9a05891ccb74e64d72a5ae76dba1b5ab2d1c4eb8291cdefe7753abc5fa59efc4a4834f815488ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff70000000000000000000000000000000008902b3f9b3ed6f0dba21e5d6bfc13fac8f003b3e11de4b883024c3eca0d2c4614604d598d31d9e328c7ee4a9d9be6100000000000000000000000000000000017a918bcd38986300bbc7a401e09b9ae20ccd382280b4e79294b6c8ae7bb1dbe2f72a582e0125381ef2b4fe24998e72f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af",
+    "Expected": "0000000000000000000000000000000015e9fb1d1a586288f07f399b87c37a085df405bcf88505a7d2b0ae6609d4baef7ec358f70edf838d3bb7291c6e5a413c000000000000000000000000000000000cc7d7e2d372183766a842f5c14c1f2a528d502f1bc5dbf5dfc9d812c56503a0b7cf1e6f052e998aaf45cfe24a261551",
+    "Name": "matter_g1_multiexp_92",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002cdb1466c13290ff0c55c38ca6afe33efdcd09ddbdf7461d6bdb3e36fb5d8be851458620a0bf54932c4ddc1778c97bd0000000000000000000000000000000012755c81c142c5051ec64de7c89719cb59d9003fd8785ed5b36993123418e49cd3afab18b599deb72c969936633a956114d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d9800000000000000000000000000000000067b3a33aaf3fc4b885035e60ba7f3afc7ccfff469cde1a67f48fd8cdf4b15b7beb9e2fbb13daa9283598aaeaff5014d000000000000000000000000000000000bf43cb79d63db544b2db14ec18c11bb9114db93662e8e6e7858d3e4a99cc332890ce90775b6c190d5ed418571fb907d82cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa86260000000000000000000000000000000019329a66132ba7ceaf5c030fb4ae9a599895aab7df2a27fd92b55e3a52b99ac51107e798175f2af83991eb63147901d30000000000000000000000000000000005c71bf6552c314dda4bf9f2b4fd8aa368c9e88c0cbf4b1c2bef9137d608738636f40579a360bcaee1a3f12274687063ea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f9824900000000000000000000000000000000048a258134ed95f91070684d04b83634c2d4c16601ad259d41e7d27292897a4d4ac76eb73425583ab1718b91f151019e0000000000000000000000000000000013a0b600765fb760919bf273a7b88bba568350ef82fc382babafd40a7e006e6808a03160f3747878368d8f6b31c619b1e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d00000000000000000000000000000000069d94fe286a9d39b64756e79669add0f66db69ead7db5b5c2fa1a9e5338aaa9051457a3a744c3b08d3afec8b87d2e9b00000000000000000000000000000000105028835bbeff46cb7d9be4b21f07670dc5589603d0d695355591ef5f7ba28c04c8e6dc40f0bdda031bb54a5710b4c0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc0000000000000000000000000000000015e6aab154e33627f92560e3def26d936a8876c52490732c807749cc28e34cb98fe8f86addb30e129f8149c504d1dcaa0000000000000000000000000000000005f6040a129df2340f3c3fd0935c02cfe162fe1afb58dba7699e7e08851b3a3c3fba36745bbc769aaf01a4f9a401d038eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000db5640083674fc75c0b0d1b2d6eb2b03cafa2e63d7a65c894d9a76b196d92916ce85c708c6c451aad65e0b439033d9b000000000000000000000000000000000ac8d6b508ff6797668ded6ceba4680443516d601a155cff48a51297e321417bbffa6eee042255e9ec054d837bffe628acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a90000000000000000000000000000000011daaf23ab5fc0ad7abbe7d5f1dc26c8ce388491cc049f01f287eb9b133e52f33d40f8693921d330ae57853539ee30c20000000000000000000000000000000017594ae7ac7f6e4f02df862b6d4ff946ac1a47085b554ebaa720ad3291f576ba720dd455829600f930e3964a44e5c7f30cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d200000000000000000000000000000000174b5b9d4ef01fc9d0f05a03612210690d7d57ccb772aa53175f11b9623388de8019ff2ae1d564e7b30ee06bafc37a84000000000000000000000000000000000e4c03b8dc45b0567e9ddaa0a085d169799d2a595c03f2ac679fd858cd59341393e6a0f62dfac0e53598af4758843673b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a054400000000000000000000000000000000197f0ad6576bdddb48c58adb1c9b2115cd9b38368dacbea9220d6a86bb621dba93325b676071e38aed2338273c98c4100000000000000000000000000000000011514f08bb28c37f078a47b6a0d53b311d5975c8a3c8e2c24a25f34bfdcbea53bcfa14b7f23adeb20bf440c87a251a66825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a3000000000000000000000000000000000aa5f3a29c47fed2e4a87bb4c2a46a5a17102535aba9426235d42f00007e35d1c902b43c1068af279cc9a1b689a0dadb00000000000000000000000000000000056d9729f8faa8e12027b993e8dc41a340d61c64e4388c3166482ddecbef8d04085d6ae3764f0d9cfe76288929749235307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd00000000000000000000000000000000070e376dd57cc8e2146d49ff08c6c6ada6302c36c4eefc3003f0cc3d75040d73599c7e0c2fb9f7e24484c37262f0eb330000000000000000000000000000000016a272b79edcb7e7fa92400bd55fc937d6389f1f0d3d2168656815845d92ab1e7b555fd4ea311802a62cb6c94bdc5d58bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d000000000000000000000000000000000bba6375b28ead3d49197ec9d3662e34c70735ed0f987f05f439da164afcbe98f25d2ce7a5e1e32515eaa4cb7f5a1f98000000000000000000000000000000000b1ec74ff999ac5a7a3ff2c91e93e5f0edf5f296b063d80bca22fa64198a798fa6b6385d25cde65b789454bc2674231058aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000012fadbd9c50f2e8518dc15d95a59ccec0c9886488ed4601b3fddb2bddd77a4bc861f2862c9c4666622e42a5dda7138ad000000000000000000000000000000000b2aa31218a13b4ab0b00d1b76a9ac7bb3d7e6473a29f2f0d137ca63bf7f152954e52182d32d3de31df0e6ef0d102c9e6810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff000000000000000000000000000000000076846443079520c5b1600d5faa5a6d500998ae355c84b9393c79f83f1a2485b1809058bc53cf5f8a1a46bde6cf2e300000000000000000000000000000000012027dd1a4fbf6078b70c507fc2cdc0fefc9a0166694c796eb26e9838195e68fc76297e66e2a0e9e069274d110efb095c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d4700000000000000000000000000000000183c019c306c08401b4f2c1d852b29dc47b56bce8cddfdb66d4e3d5385e4bc75bb9806da1eab476ee02e25ca2b4d41c900000000000000000000000000000000066d56711b80dc8725e112e4e2af6c939977aa66c931c6febb21735d78f5afca4bbaddd77387e52dd5bc9c29cf26923613a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc",
+    "Expected": "000000000000000000000000000000000f75ea9863e14f6151c452f7a4268b099f97d076b249d02d72caf5d52635bca3c686070d4a4bf997b753c283c82cec600000000000000000000000000000000014402b3e738bee3bda4e62c077e9d355ad8a71b0830ec0e67a4fe6dc59b1f3f7809ca7d07184f53c5afed32db327598d",
+    "Name": "matter_g1_multiexp_93",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000107072809eaa84dfeba5a95f94aecc2c985c9e5dbc49a741811fe4b2393ba7f6597ac99d8e80c0fbf715a164099e9d5100000000000000000000000000000000124d1694bad88200cde42f1e7721f3390df8dbe4745715a2f0b6f11cfc78996c6f342693acefe88b3d83736cac6e3e05dedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d200000000000000000000000000000000188a853e19d512149800bb0aabcec450561e5ad08b5159e0879422cca1f957ee15bad2b881979d7c8551eb19693bddf3000000000000000000000000000000000dc097932535d21656842615f08e7016f55752556da3be69027d0dea621ef46cc65e335873e041a3dee6c7e5b7589dd5db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d3a53b9865082b23226042f69ca71b99978fd6dc3c8553e33ddb12542d05b026345a23c2b24dbd934be2ba3cd585162000000000000000000000000000000000b0832950405431722c23cc78bf0b9f33c6e2dfecf10e6d503c8c96ca9732c7e7a29251fa5b5b161d14b7155a50846886e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca40000000000000000000000000000000014e19a8a203bd2e9e9601cf6feeac5699a3b2d2129b6e756b9b5a7af0cd9228083de8c9a2a0ebacd636ab1b662c8c0c7000000000000000000000000000000000faf049bd6532cdad26403b269d7dbdcab6c147ce0ddd6285ad9ae0e8ddab4b6706bbf038fddd7f63e6dc9a766928ec327a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000007e2e69d6c96b1841340c48e8ab070c67054b574bd5778a8e38a9873241baf8b85deb73695873fdd9e3387fb1fec3b6b000000000000000000000000000000000fd151202c399636a360cc014c90caabaf3b01d5a6114e078eb2473bc2fff94f1c24597e39a3d2298a2e9210726bb48e446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000000ad0e842dd19673bfb8534ee20591a9076268eb203940212f702131fc6a3e7b226a84324954eb4bcfa8a007669d2317a000000000000000000000000000000000693801615c5282a327ae034c3a1480de0e1c471a412f194178a59582509ac6fe8ea22c8ec8e98034348ac465527f4b35f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f000000000000000000000000000000000ac9f4f22670b52e0e85a37bcdd729b40c45fcbd6e8aa78626752d736771ede9c570991e347134f95385bd77e404e4700000000000000000000000000000000005964a351f406083b14726ced542fc6d95dcb8bccbd41aa3ca9cf0395d8d29143b897c66c78e2fe56eedf17d4d6f6c1f94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000018e270b6208be13c23cabf52e31a156209abcd7bab03694fcb7035b453bce8464fa1e090d59a1139fe451d8c699669c800000000000000000000000000000000158dcfe7736f4fc63071a70923d81db9f7d2a03512724dc41ca47a873132da66eb0eda58134312fdaa63ecba7ab529acb677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000000a614d7a53b7a06e71aea4014f9b951bc19747cd8822da50f7993c0821e05100dc5fc8d043b2cbe7cc4dcae9837679d0000000000000000000000000000000004e0495281282aeeea480fa47f53f8b521a7df4c5619d4e58f730fe346a6deb3d501ec8b55b581489f28b4d991ebd90cf5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000000edd664ad8b77d86bda4ba772f677d34c9341ce2b4d2af4b2680383bce0fd4468e936841dd57753d06c50a3357a47eea00000000000000000000000000000000063eacafb540655984104f60569720625e4499f048ec7849577caf240634ffc42612ca7ca92c17e3e50aa627059cddf2fa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000a9d9bea7d8a058cf254d2b7e10f6d2e8244cf131c6f87c4e25b5febcac352d02b1b45ba347e0b891c8b08e7b5dec82d0000000000000000000000000000000001d256cedcde615d01e15cf526c4a8bc8b565055567aa1de1847b524fa49b4b9f654f5b66cda0a78f414848aab42b05c93d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000001519b05b59250c72c9db7f425954694b29b36af21d9293a36d7bcd1ffb53d0ec55a3ceb7980580ce6f9fb6a0faa7bf3f0000000000000000000000000000000009e7d045b69e2dccad22dac427f5938974a6394c9fef84633fb5f90a0d09d437219f1b7ef7e7bb03eed106948eeb560d3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d060000000000000000000000000000000017cac7707469b98c6b4d24fecf6d818dce6c8b9eb44bb08d6e475e385c30fafc81551e74ee98cc854d38d77d15459e750000000000000000000000000000000019d5bea3e48fa7bd273233bd6325bbe38267e4950dca4fd9ad051f487e7933a366469107258d69f0603b2f9a8dea2e4f5143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000b993d9303ecc19122654d5cb10d488af5411c451b39b1e19e7a104477da50324472076c55c4557576a9e5d7755a381900000000000000000000000000000000172b34e576f0539e32c5025b3a8f25b5bf407f3f3dda863b194a9fd97d3a6facc00902c95fe076b91713bec162f61cbf0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000170d799ffc4c0abf6c582b41732308665d790900ef07a74183826e48c9f0fc500b09109b2b13b2b33cc17e6e639d2969000000000000000000000000000000001943fe62329fcb67a45b5155da7f950ee12fcfe0e8e9ee15868409ae44adaa5f03c330206d7d97fa733c9e93957755a0b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b00000000000000000000000000000000078681739039a022499219b298799027a341be64204a34a97a8115e5e10486420c18664825b764fd7bb931343c2558a60000000000000000000000000000000003313d3482f952c6f9cd4ec2f2b61f28ecf7d8cc7e60f17e9aac8e63ab25dd6bf2da2d67805debce0dad8fe37a36625298df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af",
+    "Expected": "0000000000000000000000000000000010aaf24dce0b05179769031ab81db339cda85cf68963640b888d3aca310a6de690150166c0943d658e51524981a1f391000000000000000000000000000000000d1af37c2bdca5886d73567cb00d5a9859755267800d7239cf39c291ba83b1c67e6a3532a2d8e8590c1bf2d845001038",
+    "Name": "matter_g1_multiexp_94",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019401d9118a5c2b0c6ae40507cae6180083258eb6c45cb8bf2fd5d2703c95fb07c031c82d0568a395e18015fe0a48a2b000000000000000000000000000000000511b992882f75fe98131fd35276b7a1de527b94718549bb4f5c9980917b6301a86e45fb7c7e3ea99e54158e49c7e60ee49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c89000000000000000000000000000000000d886eedf2a2b33a50dae5ca6f41237c9425b0a4daf08bf4789a3ea8c7f598d53257916d9c03df0d63f12a1a804fe0990000000000000000000000000000000012cb777812e76378f04fdaf2cea12456aa9e11b4c3ab0f12e63fe7ab11c716562b07b3864cb9dabb7970c81bc1da324c79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f39530000000000000000000000000000000009f09107ccfc5c4ac9b7e0058d6a0c4d7dc4309134d5fb972de3156a554211d4a2fbe639bb8a93d86091137671ab8447000000000000000000000000000000000b7f9955092221c8a2f09c6a9ffe6483ec0f8a0f6c555ec1772c260fb62c4ada6dc7beb92e29620afd15466b5f025cbed2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b0000000000000000000000000000000009a22492a1b78342b919f7b5c8fcdddac408cdd3e8af4d6de5a4b1e510fa3b7e0e6887bcbe074fa839f2d0dc892db631000000000000000000000000000000000e5eac3a77c7a3e89e9324abcc0203046948f3d62e40156a5e1b1d9a274d408d6cb49e06b8cfcd21b596923f86c02c6be9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d30000000000000000000000000000000019914b405a24e72896b3d231582f0075fa7e59b0d0bc796d790754902238943ba634dce66131260efbb5dfc3925a1d54000000000000000000000000000000000352a5a986c500e41d2fa4f65e5a917061b3f9449c1e720caac187c6bfd4ce14f1b49ad414864a1510894530cfb4a768c818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000019567f8de70c4cbbb25335e69154ce48d4106c8c9d0027e17c67777dedf758203b0a8fa3863d4e7812311f6cde36a6640000000000000000000000000000000009947f7401d03fa8b0801b130b43f729d6a71c04edfaf7b9d3265f82b039131fa09f20f9b4565d21939ab7dc7dd3477e9ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c496450000000000000000000000000000000003c42ae9653d1d1f00d79f8b1a0c53d0f2d7f3ca52ca1960a621fc1bead7ab31cf6e5bf30c5cf7877c83b33b6b5b54d6000000000000000000000000000000001221117f45dea3fa1f832bb8280512841ad1798b76f1dd16dc320ea7c86473f6f8c98ce007ebc3ebc39e7a860be987fe96acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000004340b7dbe7c27014add4ecbdf310de758ea5dd1100508a96501ae3caf9955c877113971a61f66e3691d09f0a259d4ac0000000000000000000000000000000001d5f83065f6d178b4dbbe0f00f0a88edf0a90021601bddc2cc27fb0ccccce7e48c6283a1e641408a20de15219b5553e60bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf0000000000000000000000000000000017679efa923688425fa9cff1f8e89ae681245371017f574f4a655aa780bd11009579d7daa47249f503592bf0ab79e67b0000000000000000000000000000000018f57a1ee533981c8df24895ad174228330ea361448ed63e522637df44cc1b888e969ee94d7b44bd532b655123f8f5d8537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa9830215000000000000000000000000000000000bf47631b34b2694ff7fc5d1e25de2195e606daafec34fc2c8ec86c0a325214d874002422810a81cff654eda187076eb000000000000000000000000000000000931c54d05eb43195c3ff6b396e324b5878c3fd507637c316c62b3b6e2d3d84cff9f33cd1046f1939187979330d3fc431a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a200000000000000000000000000000000138adb70a3dce09176914deb0be17821cd0212c6ab554f7e200804dcade06c6cb5f7b084a1d6ac0ef8eeabc7cabe7717000000000000000000000000000000000a4422c569aced58938abb7bdbdefdb27cb06677c1066d17f98a59f847928d1bf2343acf8b5d1717aa38cf81959ac1acc7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000fd9ff095adf9e3f666d3141717ac4a96deb5b4f92dcee35be1d305031d06d51ecabf863a41cfd8dcda0fc94ecf79982000000000000000000000000000000000fb55855aab9e557046ed53421cd3627b519859e26338328d7da249fdfa6a07fa533f748eb5dd564f9922ad911121b2784f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d000000000000000000000000000000001054ff028d2e2875330e3d0ffc52e2a83ee2ad2adf024ee294f695113d9d645f0be2a3d3c70f758f43f2deeb542aae810000000000000000000000000000000009a5e96cd08d3ee4e740e2f7b94a4e390ab5f6f572c4a1b2d927a7ef2365557ab9be65b8e2388fb571a3765892a96445c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea000000000000000000000000000000000bb1de70113edd86e5304248fa2f857f1620dc8a6bb28680f537e04029aee158e2ead4e0eaa373b812f6ca988dc40e7f0000000000000000000000000000000012118b670c9df77af087ad01e3b766d4a2b7c2b2a319cd733ed6c02ec36d9002036964fc442db992bf730c57a7d0a407a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000001968aed17e572c0d99e4e9262f239771976dcd9d7df19c20bfa94aefe1d4f3a3117bbfa4a6e329bc6b9552731446dd10000000000000000000000000000000004e64ce59b928e8cac2f744bef119018de8395b712013b0c69855fbf2bdc6a750a947b1a81c9df959c78367ed0e1575d95a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000148cd980512e0aa153adbdef262f098b1ece801ee4024b5561e261d39b495165851781d519d75f83dc5f298d40b4e9e0000000000000000000000000000000001dd43f37950976e50071226b6aa47c229085807ce9634e6583f5a2d47eb8547d4de0669b16a2771791c9ccdb4289cd9319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40",
+    "Expected": "00000000000000000000000000000000059c7ca50efe2d0a64b55a560a08976b437c350860f9599fd30370d1cbbeacae147144c216cb3e63afb2ddcf34282a5f0000000000000000000000000000000018f42ef2fb8eb6cc8c31600a3be37ece06ee567ce0c8b8c7a54c910041b8c25b2d94a94722904de079737f06e817f380",
+    "Name": "matter_g1_multiexp_95",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000172bbfff135f33357b0dd0e8545da99c4ee74d6414724c2aa66ffc85f3a9d0e35ac80850436745a12ca6f1c4ae5c0ecb00000000000000000000000000000000152dac882023cce1a3e1fd4d8d5aedcdf6acb2ca9628a94ce92a4a551b1b7268589b52b2c90af6e4be9631eebc2ef8a62a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000000c40d04b3002c21b041ea8b8ce967056435fadb479fe1fe20c373b2e2c5b568b7a38d031424bc835bdbd85af8ed1d0380000000000000000000000000000000005e7357194364947c8dc32fd74757a3f3014e914dc25f42b2dd86230ca4f86981476e6f10b1559694bc17d014cd243d7f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b000000000000000000000000000000001474002c92db026ff5bce69eddf1d8ff8e6d2ab9427bb82377911597fafa4d60256836c094cd513a52a3a09797afbf5300000000000000000000000000000000176a7b311a333c2d4f6eec66e8c889ecd7becca75fb35a38bcccae52f10ff69630393fb7d87c3b6d97cd648be099c56507f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e40000000000000000000000000000000009ecbdc4836c6c0cb4ccd014f9e582112bce0d0ab047115f38ed5dd51c54de5a43321e85c9b3e9af5fae0caaf2493fcf00000000000000000000000000000000034425e05f0adb1577f7b1bf9b9b50a76bc894f5ff0e9a8d190412eeeaf80d0bb96f21478fe8adea327f69c9137f57094e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb6050000000000000000000000000000000000ec3d4fe1b5e1c26de1558d7dc51eba3b6c37ec037de184e8a6f481ae20b830c92221593e1bbe4ee76a85cb10b33e18b000000000000000000000000000000000e51f811e16f00626d934e69024b55dc29fa4ea363916cd8f44f928fda6e3ca4947eb15de24ce1952c39e9ac52d2739d27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f2289000000000000000000000000000000001205dfd803ff3688c2023913aecb10c138be4d03756e2f05a63627973f511c2635571469d4f630758627f7977b418729000000000000000000000000000000001186b9c0d2b2073b495ef9c233c275922bdbf4691e8be085051c09e245242526b13b7051782a80726b381a72b5ef9d5ec2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc000000000000000000000000000000000413f6ee9bb25469af4298dde67f0a4a26d2f528848ac6646764703922c78d65e046204f891ac94b0b4c425110fe986e0000000000000000000000000000000011860881aa871fa3a6693b23fd7b1da0bcaaf044058ea0700b786f12f1074c615577e572e33faf8b3562bc285632696d911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000000010e8fe8fd7863c2807a4bd717fc4646a0e4f99598b9c6c2cf0547d039d58290a367e4ad851c7a67e8dd546d5e328200000000000000000000000000000000063ea10e84e4f5824ad7b9b68398c9154ab25ddc4043a4990d80e09dd94a890dbabf9c3d93b13c4f40bd7b1ff32b14b2fd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b0000000000000000000000000000000011cbc725705b809ad69c5ebb55ade0039989728e7103b684feb35c8142b100175235c2b395e37a20aa40845ebe2dabcf00000000000000000000000000000000057b5b5a5cf5f5bce985295f8a50252967aa54e934e87855097eb083a59863aba19ffcec4354a5a831b747175ba10e878118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c00000000000000000000000000000000041043cea626d6ab553b95c6e09de597454a3a3d1b8a75fc9ecb3afe15bdd8b5e73b8012ead8777df8957701fc9c9022000000000000000000000000000000000185da96dd1d54bb7ca5d7dc2fbe4cbd8ac95f06fe85a7a26e5e0e6353f6a6daf73b74117ee62be4f3fb268fb4c86275c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce773000000000000000000000000000000000a06b91559964aa8e8628946bfb720047915ddf08d24fa34f7b241e16bb163ef67f1e84fd205485d17725a8386a7016a000000000000000000000000000000000ec787cf5134bbd832d2a7dc1ed87b8c824552d92fdb30a790e1c73b22c753540a9747eecaf14dbf867d9667b7b852c7982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b00000000000000000000000000000000053bfa3bd311c1780afa1862de6ae8a475b8eb9c61fcee2b63dbb6556022d703bc7eb204fb038056c654dfb940e7039400000000000000000000000000000000074ab5797d3c39804dfd5359b69a4bdd2b738670d13662eb2c112eefbc0f90da85dd1a4b6e0613785fc66b100d129202000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000cd50ddae4f053bf5b7b3237701bdee2f5167e09d824d260e89ea498fb3b593e5053b781c159302b0433ead35f072c850000000000000000000000000000000001abe8539a4215a3b7b78c79c306dcef7334c83f571f4d6836e1c1839a65c8cfa9a0811395e3c4bea26b22ac2175757e773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb20000000000000000000000000000000001e6e0709869922c36e073fdf1404a973e0467cab3a04a806361e743d67468f0d66de28f6c0c7b8cf92954330485db0500000000000000000000000000000000084e96298cca174344b7b86052426f9316a15b4031b9e42677253fd9355b1c99ed9ca3eb3949005078ba228d4167f8b0759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e0000000000000000000000000000000002c5e399eab947a52660807752ca662212cf3a201c1127dab3586cae88f8ab6dd23deb0312387178e0e9526bc8fc7b8d000000000000000000000000000000000ad86b21dbf58098fc4f758d7ec9204bb16cbbe680b58fa42821456d4fa508e42b53c8988dc0d9a4d6f6a782a5fb90b6cce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f052530293800000000000000000000000000000000181f41dfee6effe70a28e4c53bb6cec52f232caee076f680fd63d73cae24b44709fc63ee3782a36278edcceeb7b32415000000000000000000000000000000000088d9011a9db9294bb4451e9981e84efa595462e26e5dbe14e9c84a8c5ddeca94f49857cf3b8a70e6a4047ad76d234585e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f",
+    "Expected": "000000000000000000000000000000001170d9938910ce68e56f9e84be6f6ad284a244adbf26daf8d42f6411c9794b707454ec9300442fcb7a0f08eb91edf86800000000000000000000000000000000043679c7a917148d141a785e65b0a067e35a016c24bf7639ba374e21a817819ad67d317ee70852bd13a1fc3a373a38d2",
+    "Name": "matter_g1_multiexp_96",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008d9cb39df5b28781d33d996039da8c94cd810bb85aa5868008b4267ad2a8670924d4b3ad7898b33689aab2211bb9bdc00000000000000000000000000000000007a8a6f888722e4717acbfc42ef1907206db31603c403e0a8c1ac0af9b37e63124d4645a506265487e5f9eda09c8baf85431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c000000000000000000000000000000000db6af04eccb3ceedc11378406a26613aebbbc2201a9ea2089848c7af3b34e46a3421d5704242c4b333f72180f6baa0200000000000000000000000000000000105f40c8b702f0989a9e20f72ff6a4f7310d81787e87638c33a61985f02116e106218d64976d50bcc61cf5bcbbff7c9eb6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa000000000000000000000000000000000a77e39abdc9d64d72ea4b321e3310a145feaa5d342bc1a5b16c0143dd01caeda4f18909acccb3cb5b43ad999a94f91b0000000000000000000000000000000016fc4a4f6b488fd1f45a158d941d7aeb5d431821589ee845c64eb198ff10931d586f8a0678237be2a394f5976d895bc854dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000000d0ddfc05bc9f89eed488752d64698bf00633c83cc37931d95a599d6be6e4c5d611a4151839133e86f74bb91aed1703b000000000000000000000000000000000be3dbea501c822730ab0176f64903931aa46b0179c59556ee7e1ba54605ed8da2eafed7eb2254a7ddc34e553a9b6d59eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a0000000000000000000000000000000004d6f288744016f15b21da736283af2ed1f45df12067a3a70391f66fff3ce3953a51169eba6288cabd84ffe7f597c9fc000000000000000000000000000000000f6556a63def531a940269b073ea98be79558d832123dd681bb4446d4c11e2fed59a2f97904797abb07ba53e0d48e923b273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab850000000000000000000000000000000013e692a13e79c734f3758780fbdabff86fe5936bf6c60f2f155ec4d1c49cdefb97dc02c1f1e4280c5ebb055914d93f9d00000000000000000000000000000000060898a9365ae49697e5ac23e320261eda04d818c5f1153f647844b1910bb3430d3c06df9a64af8ff9dd25c18cbfa79d1342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f000000000000000000000000000000000a2a4d92ad63dade4d666ea949dd64d5886eaa3c7ce466677356ce9f65520591c1aab590b48e9fe1eaa0f0f3e306cefc0000000000000000000000000000000002a2bfc836409b33bbe078a5f89c5142411bde621e9117ddf9f81f37bd546c3e2ba94975ab4652fa0858d5a2361592715b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee8239812500000000000000000000000000000000075d3ebb18437feb21f94ad5e2ce96cbaea2f6d68885483ed54ee67f2dbcf8cfa39f405afb46e45d08cb804a7aee3b8e000000000000000000000000000000000d42851366ed4694730b7c58450c3f9ebd365f15fa4dfa3fd226d180aaa921a0d897278506ede76b85decddc9580a365249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d17000000000000000000000000000000001722e05d33728260ebf5e4b48104cb2c89b4bc3073767e56fda373bc0e29261c9a5c53e5768b453b116494c1109cba2000000000000000000000000000000000030e4da8620007236b89103b215e54751ba2f2dce19b0304997f450791880ad34f3e43cc4e6852aa599fd65ef72dd9a5c014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e895000000000000000000000000000000000c4e7e44e8e0387bd99311343d2ff3a080ddad557c8639aad64c4f6e47d64f48b91f9de2e33b4b9c182a87efce5d4e0e000000000000000000000000000000000e7cb49fd7aca3daef3c0329c950c832e1d007f21a4f950f367eb37b5d7433f5d6f1ab1c206232b2ee32137b56b53967ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000014026b8dae20a1913ecb45359e9ceb317137244e16a036ac760b47363f2d389ef6cb12cd5f5fb9e8e31ccd39bf114f8b000000000000000000000000000000000f07f9e76789dd937b85e02a9c346f81e87637bd03bd5f98a9b18ad6d109100b540aaadf1fec048530bcfa35dbb5b8ae8b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000a0249c354052094cae5a3d77313360a8956839af614184696b5b7fbd2af6555c6ae14a150220f01d624484b9096eaa700000000000000000000000000000000043098df38ab37f42175cc9f9fa9ecbde75bb344776ed078632b3d8bbfbf04103adde27ef0d361177bb3814cbb8bc54994ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000011e90effb7ae193b47afffe6fcaa0a28c358222cbb087ce479b7fe88d25386c5a9c9527899d7633eaaed9d982d3ed4e100000000000000000000000000000000174877f80e5e9daf2cc219545ce67b904319f75c0284e41552662512727c1e05b364364c4c8835c1c9c6fe028ae45895b1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000170e754e54f64090c4c7520bfed82665b44728904092fe3a4fb2fd2d3667ccd4ecb796e5ed9fc4dafd315c0b6dc22b86000000000000000000000000000000001081e62ee7c502159f7a8e28c5ee45fb7fc5b301f3a081899bce10096c74d1bf7834d12cb7fb1301b986e9c6f7501d53e670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af85000000000000000000000000000000001434584d8d1cb34eb29fd1c95871f218f4dc46f8b2ddabafdc7049e88f54fa4b80c88960a76411e365aa65cbf77f01ce000000000000000000000000000000000e4e2e1318c5907a07a7ff154b07e959d681a69c066585ba046b8889d417d01c503b32a924500944d43e68d7da8da35d54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000945a9d0603a3bd0278fce30f0cf97274319a760291fea5aee143c364cc0bc60e59dcd1093aca1a3ef64696ec47845e1000000000000000000000000000000000a77cc690d55763a94aa48c210610833427ed3176b6dca184598755f539359bc7302f8dc2cc941d447d9b5b68fa716b70563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d",
+    "Expected": "000000000000000000000000000000000ac708f97c9e9fef4482af7b95c59c2ce6b8826f6b046b5a29ee2224ad66648a8d1f9959ff434c77394199fbe75780db000000000000000000000000000000000983fb55837062305d7fbcfe8c76cce1c431067a97392923720a6c4f963839d7d2661e2a8dacad1f151f505748239798",
+    "Name": "matter_g1_multiexp_97",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ae003001e3173dbd17f4d6598fcdaba9966f1e22a06ce747f7d2a06b2bd37579d093242a4940bd816ced07ec1917365000000000000000000000000000000000b27db470845f285c792da64e870b818a7598fb820313e075ec72e78f59f3903cb0860b749bfc67540a8bc80e844a8de5b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e9900000000000000000000000000000000018a33b2c2f1ea187672612b51c8dfdd9e86674df58ff4f77ff3f71628e7aafbb80ad22f34ab4203c42bd39a4f73c3d6d0000000000000000000000000000000017c3a68d8782a479ba9aa829e3f261a3e1b832595fe3922d800349bdc2bf58e0c1b523eb0924bf0996e38aa83267f570a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000015347adf6539116167ee71557b78d8fe13373512ca7d8d365179e25ae8ed2c6a65e1f643cb0ed677a2f44eab809d5b640000000000000000000000000000000002360dbbe0b7f8e97f6aec4b20a7e6525d83056975a4228901b4f19259c9ff2d2ee00da9bb9085232fdf843e5d305561677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb6071910000000000000000000000000000000005b62380515d49aa1427800077a11a8f01ff00fe7df53a13a9266910e4038167ab747bbd0705fc25ae2cb0e2451c893c0000000000000000000000000000000008de7bcad1c67d7f1fb5cfb9d20ac2134006618ce0d22f4120f5396bf8164c0effb0e3ebba7959e9dde757973080a9cc53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac00000000000000000000000000000000185bee837e3212323dc40fd471ed9a1a58f2aebfcf7f07ab761d40bc1ed77b385a134c99385d07e75c5f8c51d6496482000000000000000000000000000000000d7d42e4e18040da671799f981d404085fed490182d397685498e80967cb9c080a766d5c8822152d78920fb388b979f534c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d50000000000000000000000000000000012961da3be1ecc774fc9df2dcd87c337ee50a99df7c4821fe08da7327276a24d754be95b6e916d5c63926b6e44b74310000000000000000000000000000000000e44d11949fe33bc3a0ddfcc74c5b0fa79cebfc0d4a00a574ad7659c7a5e72c728ae4ee031af57e9135a3eabd93686edbebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000f03c013d5554584c2030ea02cb451ae508fe6dcba72bf7c49cb47a25d3d65eabb2fe043b9ea90e03571aa7b64be8b11000000000000000000000000000000001479789662864eabf677d2a541e48e5ce70f35a2cd6c0a476d4179d02955a51123e75c650888e514aecc85d67781c8c18b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c959600000000000000000000000000000000139bf8fb623dd156a3fcc46eca51e61155cf58e2dfe8edfe717effdd4418c833db7fde2031ef27edb4a70f9d60d67440000000000000000000000000000000000c352a16159eeca4dc9a86601973c02e39f2a11c8a0955ad52236d7e46dbc405147258ea8558505bef0f09ba92527c76d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca0000000000000000000000000000000005b8c4c2782a2a2a3abe4f99e60db6ff4179399aef4b9e305fe037e1a14a4c03ff59be1e91f55e5bf316356bbaf876af000000000000000000000000000000000eae605cef3beee4a176a0589f2676b3e212edcd7ac5834ece3066bbbb587bdb6bbe46663acfd9d8aba2251a238004106330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b8000000000000000000000000000000001038258f67b0097ec51adee244cc15d63c4d3bf1b3b3e64ef8ae6ac15a7c4195fe97bfe8c5a42981a2463ed1b39032de000000000000000000000000000000000a6f27fc1f2dca48f6e26456de5d9fb840e4ed3fd9ff12372e51130d7c439f4ceb4fa929da2dfa3ca271d34e9aa0985ec2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe374000000000000000000000000000000000373fbfebce5c599172ab017e8f4f9813b0e6aef3031faf61c336aa7d6b64c8986827a27605b476bfc1057a0388f864d00000000000000000000000000000000079ec2c41547d98277c60dc46a61ddda51c9df65a8ad2d0a64d483eb245986de36eea2509cf7949c5fb05a77f9cf3bacc9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000d50821953bbbdb494e48c59c940c5f2ac2b902f4c2ba2b2ad50960a51ed7eb1a9d592bb903a03b0b90d8817d10848ba000000000000000000000000000000000bf0898bd20e08205aa218e529db578d5118ae411159ed372eb8968cd773ebb1619f92107d2948020bb3c721ea63159dc2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a0000000000000000000000000000000006bab55b7648be3eaec947694311289f17258876d74a7d92f22b7807d007fe142a71210684593b1aabf74579eb1b1c17000000000000000000000000000000001016b28dadfe9b65d86a1f843f7ff4b774eab74431b68b079527c2387ee6cac69e95ca564346fc54237edd3d2d31f6ed9fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000c0d5ae44a0863ef3d6d32f1d8f32f2c5b89112652e2e3d6ce620479882fafd73cd3627f9f11315020c8fc9341c7fb4800000000000000000000000000000000197067de9d61733dc0367d91f55a57ae268d5e7babe7882c1fbcf03cc38de7a2dc41acfa16bac0ae63418fc349b9471cd4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000c3964c79741fe8093ccf2f3d118b33897a18d920ca242ae153118bc17bf0102fd19a9e4000698b256930a2f415305180000000000000000000000000000000003ce4a6877879ee56299ed27f634571126d9f8ca8ccb1e67100064e7efb435cacb1ada74d7c7529b495957ce7a5dfe709644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000018e6260c0cd6cf806ee82a047c51a85e0d7023883cfb05993ee81220e0871b122c12e65bb99b20787322d93b82089e98000000000000000000000000000000000d5b66fc46b7fb60fe8efb6659bbe948c6776d7780633f007123c5c49f5fbe7e3defc0f3d896333d0ca01244f2b6effe0df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d",
+    "Expected": "000000000000000000000000000000000c7aa8aaa848c068104b88d0757551612285327ba5fbe35ccb4eacb1bc96a341786e4548f5f86f6ded789b712a4be74b0000000000000000000000000000000015c9aefc358834f21ec85092cd69df37e0036ea6ddf23a3ca052a669446b183e5340ec7ce894ff6575f5e6531947c84a",
+    "Name": "matter_g1_multiexp_98",
+    "Gas": 64128,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004acd4cb6bcfed3219c3aee9368feeb58d77a7ec81d19bea11402015f4bd0ee2d7afd86fa7ae9dd320910ca28eb6d98f0000000000000000000000000000000009fe1b0094c0c2ae80a3c5accfed5d212ce39f867aa2150b781c193a0053aecb04d06e005fbfa0a24595e5968d024be18a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a8330000000000000000000000000000000018cf9bf39549c35e94211b4e2d0a0157d73e1ce8a17cd724eb33c38281dac07e12eec61b27b440b220c4f21915a73a52000000000000000000000000000000000fca6d956989db84dcfe58b0310fc21b5bdc82a32838c8d9cae912d683dd9c67f68e15b3fbf9d7b430ba239c8904fdd2bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000001897956bc232fd5a9b0ed1b533bebef8ddd9e97002513eec71d67ce1086ba8473f2c013af7d8ac548290453d9f71bd5a000000000000000000000000000000000796da5c8ac165d416c8fa36d84e11bcaa80c1bbfe18efde4b4b2c71d6d00fa24f3d51eac312cad9e854f094dcb6ec7458b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe4000000000000000000000000000000000302240769257e92899da03fcc4abe1ad3944b74c3046e790e4e950f2958426b5fdc691401a1c8a531f42185d382fe5b000000000000000000000000000000000053750b58b6d2fbacae94e22b397261e541eb4abf4715b3f528dbfc3388122918b1b4b506f2fef89ea936efdef0105b3b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe000000000000000000000000000000000168a635a14f61734372f4bdd2fd564d77afa8588e1828d88c4c90bb50f57473b2c20585dc0e93726b84e73c61f29ef1000000000000000000000000000000000e6e92355e59304ad35b1dbfbb98db803d5fadabdef4fb1b2a54080ec9a33a7147ebb4d5219acabd949337bebbffa793b049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc000000000000000000000000000000001797bf2ac9b490cd43a346fdc64bfb22301a0a0e371bb4df8ec02342b4fcc99af43b4735665c6b1386fa04a3dc5406e3000000000000000000000000000000000fcc20f4aec04b7896ddfd86f58c2e1e9dc6f863ec3b477572c073c0f4fb07ee8dc0d5a843321446445b6e7846fbc5d556197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d9703300000000000000000000000000000000013ddb8ff149222a5a0a997c0b89aeee36a6ff2540de3cba8bfe6a2a64fb505f13ad956a3882082ab85bfbe72f3a3a6b600000000000000000000000000000000102c1a1085f60cd5326966a2dda0872290e1658002ff3ed95c47cc0345565076bdecdeab7082bcfb439cf7f3e445faaf721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000189515e637d404ce6db58d24774609cf946074aa22066d808dc022824a26b381bf09148005c61156a976154b025d71c90000000000000000000000000000000009102e313c4517cdd3d07a66e0013eeafc996c21fbf5f0f3e7d232ad5adb781cce1657bd5750193cfc0357ff55bd012a461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e0000000000000000000000000000000002e166e475ff083faad64667b683e546b2358f945b8656f9c2f3f6e87a40dc3fc087dd94874bec1c4bd5929b7c96024a00000000000000000000000000000000022bb4ba4be638d8c14a16c94522c41cd3b3ad917daa454f820b8fa35e5a48c676266feece6986e8fe920b2a5e43e4b3569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d0000000000000000000000000000000004947bd8ea8cc3b116fb7320c573fff0f107913c18cfdba2e7e9a4c8715e334a431156f384548508df8950d681163aee0000000000000000000000000000000001e9e7494c295248184503344b8ac7bfcff41a4561de03d78691ac47980f14aa47c1eaa3cca80103f0f2ba14a2842aea2061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000004789b01538cfc54cad0e99538e874d13eaa7f07199af29d460927c3e622c74e0bb4185afa12c53446f56033348c332f00000000000000000000000000000000154291a8bdefbc91445ef1fe123f326b8aad652c8c54502920d4dfa912c2f42d784fbc5a16d08468d2d6ee56e7e8eaa24129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000029048f227fe8d1b7247a82cfd3e1b4b60cdce6b52de42c4b96641bf8fc5ba9b077e33bd4c4fce9a51b63a6a2451b427000000000000000000000000000000000c83518e1b7700d68966d592cb2e3295a2db5226eb6fef972c8a84721d1e49a30e4a8ee3494ed4bbcd2a6877e1ba597d366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e83467290000000000000000000000000000000003e96431aae4330d3d204093b7af21343ace4f1960de951eeaebea51e778b1fee43ecddc46667d096edbc5ff4735586400000000000000000000000000000000183a282f4b0513be661b1b38eb5f02b51aadc591745e0bd5d2d4e5545739e26470a9ec20d78ec284268d9c54c8e4f7b6d997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f5000000000000000000000000000000000904c85edd36dfa18ddb4e1809607708142f3c0861570f2bc8fff14c462675661f2111c10a01557fb21f7f38957bdd840000000000000000000000000000000012a3a37f34ebb23d4c9268ec9e1d53aed4747aaace497695e6ea8fdbdedd58031cb479003e8bec0d14aa1d062fa30f2ce881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a337100000000000000000000000000000000148532bffbbf8bb1688f6448854214b4273b9d5adf132aa9142c1605d1882879678b6cc70638713b9438532d427f447c0000000000000000000000000000000010971ee30d83719e10e91aad3f1f201fe35ba1a057531b1905bca3a8391a3786cd077ee0f104305eafb3c94f4546da9edcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e65000000000000000000000000000000000f73574aa5a06ea569de88e48fcb96e822039af296684933c1b417dde95e08d2ac9c6ad4d525b0734e24807ee99ba88a000000000000000000000000000000000523deae09e75121a6d89b45161f69f0733a9e43d88d8527a03cca8cc126aeb7a680cfaf291554403723e20440b79437334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638",
+    "Expected": "000000000000000000000000000000000db91871e4cd84b3b58216b6c2e77a9521f3080182e78d3f66fe33f313013f06aec2dc5a6d483f35fadebde873bff9490000000000000000000000000000000003b9685de062b09b9e277ad5cd664d28af59064448af2b1b2b2357df6fc88e3ee7e0ac837100e0b7593944e8db43ab0f",
+    "Name": "matter_g1_multiexp_99",
+    "Gas": 64128,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsG2Add.json b/core/vm/testdata/precompiles/blsG2Add.json
new file mode 100644
index 0000000000..64ca2006dc
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsG2Add.json
@@ -0,0 +1,730 @@
+[
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "Expected": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3",
+    "Name": "bls_g2add_(g2+g2=2*g2)",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf300000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e849",
+    "Expected": "000000000000000000000000000000000411a5de6730ffece671a9f21d65028cc0f1102378de124562cb1ff49db6f004fcd14d683024b0548eff3d1468df26880000000000000000000000000000000000fb837804dba8213329db46608b6c121d973363c1234a86dd183baff112709cf97096c5e9a1a770ee9d7dc641a894d60000000000000000000000000000000019b5e8f5d4a72f2b75811ac084a7f814317360bac52f6aab15eed416b4ef9938e0bdc4865cc2c4d0fd947e7c6925fd1400000000000000000000000000000000093567b4228be17ee62d11a254edd041ee4b953bffb8b8c7f925bd6662b4298bac2822b446f5b5de3b893e1be5aa4986",
+    "Name": "bls_g2add_(2*g2+3*g2=5*g2)",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "Name": "bls_g2add_(inf+g2=g2)",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g2add_(inf+inf=inf)",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40",
+    "Expected": "000000000000000000000000000000000a9b880c2c13da05bdeda62ea8f61e5fc2bf0b7aa5cc31eaf512bef7c5073d9e9927084b512e818dbf05eab697ba0661000000000000000000000000000000000b963b527aa3ec36813b108f2294115f732c878ac28551b5490615b436406773b5bb6a3f002be0e54db0bcebe40cb2e2000000000000000000000000000000000bd6e9060b42e36b57d88bc95b8b993da2d9d5acd95b73bad0509c2324212bcf7a94a46901932c0750535d00008a34f7000000000000000000000000000000000a374afd32bc3bb20c22a8864ce0dafe298bda17260b9d1d598a80830400c3fd4e8a8f677630eae5d4aa0a76a434e0ba",
+    "Name": "matter_g2_add_0",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633",
+    "Expected": "000000000000000000000000000000001963e94d1501b6038de347037236c18a0a0c8cec677e48fc514e9fc9753a7d8dcf0acc4b3b64572cb571aebbe0b696640000000000000000000000000000000000d9739acc3a60f6dffb26f9b5f1fd114a21f2983deea192663c53e012b9f8e1cabd4942ad039badbd4745ddc0a26a91000000000000000000000000000000000b4206dcdb80d62195febb6773acab25fa2c09a2e4be9416ca019faeb72f1fad1dfdc51e8cea39b371a045b18947d40a00000000000000000000000000000000100758b888fa27e9258ddd5d83409e8aeac576874bc399b33b8bc50d77fce5358cb091d42f9a1b1ed09be3f200959989",
+    "Name": "matter_g2_add_1",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f",
+    "Expected": "00000000000000000000000000000000079e4fc2190d3441fa76c2d925d23b81e353e09e9138fdde51234195e564a32c98aa0d240f051298bf966d17adc2d6fb000000000000000000000000000000000aa327776fa7e15000dd548fcdc3a1cc6f9d0ab33046dd4240a3002962131b738ffed579945a348c795cfcb33682cf3b00000000000000000000000000000000179232ec56602d1ff79861cbfa2edece34b296541483aa65fe0cb493f520b7722cfffbe04294dd054770a38bf75d927b000000000000000000000000000000001826b88a6b411330757bb304a380487a02f7cf421115b84b3f468d11a83dbf304ce7a5661f4f01299d3c7865305a0006",
+    "Name": "matter_g2_add_2",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c",
+    "Expected": "000000000000000000000000000000000f69e3616e7122bf78230461bb1f4b194988adc6149372691d8794d0086fba0870a2255a2c79cc3426e7ba4d032fc2ab00000000000000000000000000000000174752301e05dcd62f7a3ae3357344e64d1c94835b2b742ac24449ee2728d693a0df10c3beaeb45d1b4af4ac2bdbb8b200000000000000000000000000000000051a761a3ceb275ec28a2a269b5ded1d9fd11a617c958e73c07de3a92ac480aa82c7d2a1852d291804e734526277f5740000000000000000000000000000000009bec9045ea89d5d16588e3373cc977f6d975d0e2213b171403a9b2ca460b3b2e1106b474185516d4200655b17a179a1",
+    "Name": "matter_g2_add_3",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca",
+    "Expected": "000000000000000000000000000000000c079610e6f8770d65352f911863b6cb4fcb25cacc4a42f75e34e29e977c93244a6241cf3d5bd1040ce7d8987996f87e0000000000000000000000000000000010d08d8f6fa8ee7042c0891ea0c3b9b59a79da52cf3a91627c79d456212e3f6f39e1f69aa0053bbdb4076a3f7d05e5dc00000000000000000000000000000000069047218b0ac1e07650ac8f4a1b9235f68408f543517c4ae3c0ec47c79b468713c704ff3680edc8abd1bbed7a5fa75d00000000000000000000000000000000137737706162e02cfa75ce2154d57c9a3520818cc04626654824769ad92ff7977942f3881a28284ea47c14f353772d0b",
+    "Name": "matter_g2_add_4",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e200000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d",
+    "Expected": "0000000000000000000000000000000004dd5dfe38fa70625216ecfec60ea8d38602552726f0fdfb8f392362ce845fe0fda76894d0e456796e08462bb941579f00000000000000000000000000000000195a85cd0685f4053ee539de7e04fccd2380819b291f89cbcd63d5a0015b3214500284a7c6568a71f52bbdbc38be410a00000000000000000000000000000000107c211bad49c7dd8555e30f2500c67e7175eb98a8494f3d5309c65a93cce89572b7b5489428eaf3f0a5c1be323c5352000000000000000000000000000000000c11f978150ac35722679cf79443b3706d288c968116ddedc1f1d0fca8cd746e3c92dc006330be14886c53c41feebbf9",
+    "Name": "matter_g2_add_5",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4",
+    "Expected": "000000000000000000000000000000000fb33caed4de22cf341bb3e04d41c0198b064c1d371a24f5cf59595ab4a1edfd379916a40cc405d35f0603b2f8fb987400000000000000000000000000000000131ad6172c20b3a1cc2542db037de1324086fd9cd140ae97987980f260023d91b24504181af6fcbcfa242f48e99559320000000000000000000000000000000004a0404c00789459395f5344544041785d10f2fe74d4bf484966f5e9b6b4c4c8cb113a811a4fa82a1cdf8e3242bb418900000000000000000000000000000000086ba6a914f3f07bdc6750fcf6baf76124a17964bf9eb9a12982e8a28ca04360da3544b69436d5663e4e94bf7189529b",
+    "Name": "matter_g2_add_6",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091",
+    "Expected": "0000000000000000000000000000000019c8a1a206c0006a3033377abba4c31c55710a094d8c9dcef7560818e90411861ce7d189e2763f8fe69bf75e719e4efe000000000000000000000000000000000cccc6bba8691c210aa0a67d26584a359fab94041d853160abd9669893c0d398c805cc37fa3c33bc5ee5ff915b985c45000000000000000000000000000000000e353c1993c36763acec2a75495560e743d099b565f3de195e011afcacff3d60502801f47695da7dd589af81e772eb7800000000000000000000000000000000100c6123cf08eab6c59d78b414fa504ed10c204851289b0598b40ac31971fa12cfda4ef7cd2d64f9797d4d2b193e0bd2",
+    "Name": "matter_g2_add_7",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2",
+    "Expected": "0000000000000000000000000000000009fc3870f88288c680b43d63d3bb5305b99fe461e59c07be981b8819fbee0d1fdfae0c037e830fbbabc40cedac7919720000000000000000000000000000000018bdd4903da4d14fa28af4c2cddcb708238cf68673ce77a04a3926c4aaf17d39a831c5401e84dd042d6adf595a1763710000000000000000000000000000000002c398f0e8ad9752f4aded980bc5de2d91118db06818d815c11e818ead47e7065823737db8e304bae32969cab065d1ff00000000000000000000000000000000180642a633c3aa402e5c0b18fcb6fe8c115575b863abda59b5d91997ab01014faefc975d0aee994f98cf37ce79eb95aa",
+    "Name": "matter_g2_add_8",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb815960000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90",
+    "Expected": "00000000000000000000000000000000199600699a6108599c638df8f965d73b5de4ca74598df281ec95c539de2c7eff9767569692d8e0ad120fcbb3d9335b95000000000000000000000000000000000c42b11e2585ba93521b3c968e9dee07e4f5168c11087d8d750795555a105df70c969bfa79b1ab4e5fc8d81657235d08000000000000000000000000000000001370daa4699daa99e9940fe04f69150e6f752798cbc0e66c91c3bd46149d935c1815f32d7f14b510e16d475044eda9cc0000000000000000000000000000000016c7a00be10de5732795cc3ee2951e58cb9d42f9b05d02fbff1b83fab5d3ad830cb8178092b76172108d7a53afe8c539",
+    "Name": "matter_g2_add_9",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e97795505419600000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53",
+    "Expected": "000000000000000000000000000000000669cc8a3acae17f99f805afb9012a38851a9e8d4fd9895a9946c29fc859849c24d7ab7b6278c449cfbc5f1d7ea1fdbd0000000000000000000000000000000007a9095be808d0ebc99bce94e851d2a7cd3e1977b923064ab5bbed2347cf18f3343e60120fa051d12fe27da3146cb423000000000000000000000000000000000f1e7f75887651f67457f6dc064d7c11934035d15fe4dc40bab970160ed1b1aa230a3fb84dc1da08770d847c0216347a000000000000000000000000000000000efbc62ade1678cd70eb38c644038bf19e52b0859f65747068d9f3124762d951e4a6ff05f34b6d14919774f8409adff5",
+    "Name": "matter_g2_add_10",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a90000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773",
+    "Expected": "000000000000000000000000000000000e25a38d0ce2aabd2538c95ed463f226e3f29ce7f10e1be27af2d3db741926d557178c4b125af8789b40480d8beec0890000000000000000000000000000000002a94b7c57fe2783d055a537004a3b67e41f5374da0813094f5944fbabf4d27eb576dc8b21ccc15f8339df14ff8785220000000000000000000000000000000008b9efd8abfa4fd71a8eafdba9df38360ef0b0a117c0052528d1c24df5032635eebc7b201439f5de858514666c68cd270000000000000000000000000000000012a2fde51f6f4a98435c325dc3b1ae846bc33a5ffb3b13fbe3fde2f74dec0aa815fa8e42392b3dbf798cf547fdb4db0d",
+    "Name": "matter_g2_add_11",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b381731734598360000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e",
+    "Expected": "00000000000000000000000000000000067265782d58b04a2ef3dd419cee506e076e49d1119e28db1df7f0e22cba9bbdabc560084cda50bc8db3915fa9c489a30000000000000000000000000000000012448a61fb2f6fd8e355111b671f0e888304284b72d5688091f2ed00edf7ccb7e5bd8a733a910d6964dde07d393798470000000000000000000000000000000005f687356ff6c634eb46613be8e98540107e706714434faff54510234d4aff42ef7752e154aed63fa8ff905ec0af628f00000000000000000000000000000000180dca84a37c964b30f5cd11a090e54acea102f1b884319f8d1252a37bda005512ffc39dec8e33af0dde0d37993f846f",
+    "Name": "matter_g2_add_12",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792",
+    "Expected": "0000000000000000000000000000000012a29d35c9af52f172787c90c5a3e77ed29d66feabf5d7bdd6bfc14dd9a05d402976b84d44647628c908d1816f4e7100000000000000000000000000000000000caf3c372e36de557ecd7eba02e6a79b1b4cff30343119df7a23662c8512095e051ae2dc27e577635c74a260be2b084c0000000000000000000000000000000002ceca293a58bc9beb4ee9a0679eab037f5cf7b326d65c0efeefdbf384ad8e4bc08a3a75a02e6b9cba8963e65d6e76ef0000000000000000000000000000000004631773a6590bc89b49a75bbbe2e732f9466ba259ef7a04ae69b6aa5d5a2621c1918eb213101f6f7eeee4656a7b1472",
+    "Name": "matter_g2_add_13",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5",
+    "Expected": "0000000000000000000000000000000017f155ed9911ec56d71d63d57556de071ebe89be36e6bc9943ec068a70dd5a6f045dfb9fde5c1e29d52c9fc17579452e000000000000000000000000000000000a60d62ea549edf4b11f62f2321f39d41bf11f3c4f858dc7db85b1dab1b7644e27eeb1d022d6082f59c65155068d2c390000000000000000000000000000000009d309145fad15860e556ec4b4aecb415865954247c2034d5bc96026e4d6f7612af6e2db99f4e462acee2b303134b91b000000000000000000000000000000000114ed157e3d020c5397cba7e10cb864aabb47461f166a6724614e689274ae74c505fb6ebfe3e88da0d6c272a15a0527",
+    "Name": "matter_g2_add_14",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db",
+    "Expected": "00000000000000000000000000000000118e0c81f9157395578f0fb83b179721de2af3326d13189cb8f43911d8c3268a11fd9702f09f14c115bbdc43d5fbc08b0000000000000000000000000000000016a548df8c87f432c31e4e32c3e5b4d48d6f29fbe391d1181174be9dddee450e7e96bffe8c9f23692ccc080116592944000000000000000000000000000000000eef72a5c698c58f1d2ae9415da256b54d7b1ac37a1d1b88727c0afcfd854a41973c6cb10ecbc3a90050fe3d8d3ce8780000000000000000000000000000000019b16ca8f955dfd21830a3f7fafcc97d7de977bafe1983892988aaedd430d22674d97897d24c1643e99bfa6256df4bf7",
+    "Name": "matter_g2_add_15",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca800000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e",
+    "Expected": "000000000000000000000000000000000f2bf3f69276d390c9fc2c15e9f5f5d0b3cf9a6eb028c44811b481f376ab60e17d33a04b78348e46eaa94332c5f16ff8000000000000000000000000000000000bedd0437fb3f4baef87e56f33c77fcdff6a5512571cf11fd9605697abd8763315f1fe4bccf04acc6e971d6aeefd9c1500000000000000000000000000000000067c3ff69733baae2fb4ab77cddb7563047c428b40a257a375f8cf8c9d230a6619f7932b86e0836fff0c1c60d2c4dfd900000000000000000000000000000000057526faed8d62aa10e89add5a338320c748ca1f96ba5ceb579efec69d17475571fc4ce6fce3a93398ea88340f0e969d",
+    "Name": "matter_g2_add_16",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91be0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9",
+    "Expected": "0000000000000000000000000000000004fc19f8fe47e6acd37567016704b07f906e8741fcb196f697e1fc24b0204292693ff424bf1c5e407f5bcba5a3b1ab85000000000000000000000000000000001816f992c3c461fa6d2014ced382a35b0d70e61927d72b4d661434efff3dafe2f4b6cc91bb1a5dbf809f10f3ed7f36de000000000000000000000000000000000dadf7f7223ccedbeffef31c97df7e01f99299da71b589c8828b65715012aa343d7e041dacc57b34a6b5f84523a7938100000000000000000000000000000000167f7e73e22df81bd2a7a6f14e940a401bf414e5d18b3aa610b2a82ca8f46aecb5721d0092b27f8968b2302c37957268",
+    "Name": "matter_g2_add_17",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7",
+    "Expected": "00000000000000000000000000000000041a5783c748247f05457d30d16f93431e9046a236d5025cc07a27b9f2abaaa556e2df65cf0f0015107253fe94d8b4dd000000000000000000000000000000000193638bf69c7508c4b12808a62e89883c34f97ded6e1b5dcc3f28191e5c7fd901a72a85ae386acccc9865f8144b1bd500000000000000000000000000000000180e8184ab583da58b77b8a4d108a366dff3e3b336ebc5c9153fa815188edc95e7067ef25f7d79526c295d634bc98f5100000000000000000000000000000000125b147100f6df0cede8e22151b3423b1dd364899fdee103c71a44388ff002a367627a2342e15833644bcde61f2ef6b6",
+    "Name": "matter_g2_add_18",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780",
+    "Expected": "00000000000000000000000000000000095fda8adf3981f4468fb82aa0ccf80e55138c922c6422cd8e67f53ee63e7a390bc345469e9211a1f8d810cf4ba27d0a0000000000000000000000000000000015c19b6af21f75e8e53fcefbae1c8d7f97853a8aae5fa62e606cfc92ae71890702ef9dc5609d3ca8fefd415fbd820c04000000000000000000000000000000000007b7e908766d34c5d99cb7cc76d5d5ea83c29ae1d9b83b163741bc9962e293926b1e251b546ce0c1268def728da78100000000000000000000000000000000084fbd6253211f7d66d52b7f14360729d54b2f94c52f2b76e521dc3961c40b4f19944923f64c6425a44eb158a9727a4f",
+    "Name": "matter_g2_add_19",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e170000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e",
+    "Expected": "00000000000000000000000000000000121e7f2eb906d0b31b8ce5cc46638428b6ee57a1ee70e4ec3c2bc044230b9b86875abe0862145b442c0e34308efc690f00000000000000000000000000000000139120d0a10b82737561d0b3fda01b6df69d9beb7dbabf3ddda036f9b4c317f3ac1eaf400013fe5ad664bea44a73b336000000000000000000000000000000000a923184b381027d8cb3f82708802b204566b2b8bb6a72767aa396324d8a26b4e0f0cb92fd1914d77a4e9af2f1ec31e3000000000000000000000000000000000409732f2225cb5e5c002bef17512519eb1a18bf6c3d7f834d0c7ac8a38433c88b550b3f443d259313eb1133620ebf0c",
+    "Name": "matter_g2_add_20",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b710000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67",
+    "Expected": "0000000000000000000000000000000006a200642d5cece5eaacacb36000b4b897e8d8c661c8282f90495002aa515c7638183cf1e80a0b35e953adb92b6bb845000000000000000000000000000000000e88d4cda34e98df4d727fda79b67961b5b8efb1b125ef2a8eafc481a2cb2fa1530e59a091f31c25cc49d38f545491ff00000000000000000000000000000000082f38c1a1c35981f537547dc3b59331ab8c5e8dd261df58fe6f0c44ef1e65d0cdc1980e1a62f6248f38d0afe91e5627000000000000000000000000000000000eda1002e202e9ee4df5354cb87760d4df32eba1eafdad27cb0636879370a8f93be0bf2a30f15f2fbcd7e52c1bdf6b05",
+    "Name": "matter_g2_add_21",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4",
+    "Expected": "000000000000000000000000000000001341cf3316152ae8d57ea2194224f04756690133d2e02d077dc271aa577278e346e0ff66e8a49ff8c983fd34546e1f6f0000000000000000000000000000000016c9093da650643f4b4061e1c6e55da6ebaf9f234bef8325aeecad3863a0a2f53e1cdb2d54aa8b075ce6e6632fb4cd660000000000000000000000000000000011eaf3dee010bf2a16c5fbb1f7aa559cd4d831f087d9dfad4e157a6d2b6495e370d9791cbaaae19339a65726ebfc3b910000000000000000000000000000000008476d793305204be414819fce2ca70754a532682876277bc0586514f2096ba9998ae848c722ead6722d5af9395ff77f",
+    "Name": "matter_g2_add_22",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa097000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa",
+    "Expected": "0000000000000000000000000000000009792d98ab9b90c2467ad0d070ea44f382ec7ad5290a59d889313c5a55d7b8e837333ad7ecfd97221d405cd6c549dc8e0000000000000000000000000000000002b92dd07b61faec23f48b8a7893dae29509fefd688a978bc2e870d4cd6f963d708a0611b4aa65f5644fbc6ba4c5e66b0000000000000000000000000000000011e46a283946a8e033afbf7c14ce3162a05867809d7de94a090c8cc2cdca8bb79add21f6e2fa8d7f39ea6d26cd37ea850000000000000000000000000000000000fddb7cdf1f1126e7a6780e4892601121b289a386ebce0caf96cd392ddc57c47e3f9284889fd8a18fb330d6c40bdf67",
+    "Name": "matter_g2_add_23",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e",
+    "Expected": "00000000000000000000000000000000054dedc002c5f2da8c6e0a0146bfe5c83200b276b074e6d6f2c397e1208f152d3ea3e8f0da7da62cfd2a028d4c94fe5b0000000000000000000000000000000012ff307f86e266e7a212484a169d3e81df98217c6f715176913b0d383cbe4e790212da7feca0cea66df09d92544fae010000000000000000000000000000000009c211438dcf8ccb664b535e73eff304b92aa2f568aeaeb8e10ec142f92b211bb8147b250dad77d508cfe353667b6f150000000000000000000000000000000009d1734f4ecc88fd56f412f9243c387b9da659faa3fe7295580a6b7519b1980bd074339fa9b0bef44dcdd0cf0c4a629b",
+    "Name": "matter_g2_add_24",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f",
+    "Expected": "000000000000000000000000000000000896a38ce734c550c178786092292e737d44fa5f503d6d3b66c75e6bb70b59d1db9e8baa1ea3e256e2dfd8a942311e75000000000000000000000000000000001231db96a35229a4c7507b0ec193491446a0b43115c27d18b3715fcd4aea14d4e5c99db5934e73bb0b86f1bb91ee96fa0000000000000000000000000000000000d6f95d5637b29ea889c028dacdcb484d8ccdb243da4d5ff49e5ad82f234d414dc1484e9ed6cba1b5940eaabd3066860000000000000000000000000000000007de052fbb76902e06e1783fa8afcbb54a5069b4c5e9cee78d43da2cf76f24843a740a9eec6fe9b8f9bc4ac9baea77a5",
+    "Name": "matter_g2_add_25",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b8592390800000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f",
+    "Expected": "00000000000000000000000000000000156914a9137e52abd4579599dea4c0f857eed0457ee1d80635d3a6ccf0c766ba8ab1b6f989711fbdf125c4ff06b597ea000000000000000000000000000000000c60184e8ab32019ce20d2d137130f657c8964406fe4abb26da232c9c5dbfab243837d700c88d6b9ea4b8f0a2f514281000000000000000000000000000000000dc3e6e3acb898552791431859943d0a83fb4ccd62e4ab2a971370a93a99a9dfcdbe4c42535aa063354e0f2cd48308c300000000000000000000000000000000025be02da875d4990d1f0be626ce634c4856ea91f88f636bc27e313e73897c9c13a1e3ae70c1227dfd4fba97f521d6af",
+    "Name": "matter_g2_add_26",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a300000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352",
+    "Expected": "0000000000000000000000000000000010124c1c1c10868b570d2969ebc3bf5cd6bfab13ddc93f0fd2b8a1742eb8e04d31063bb81c52b92e253128d4cb4413a60000000000000000000000000000000013f89997cd2ddae00cbf24cb66a92146c553c6fae41cdfaef14d49078729f239ad2661937dd0d4d6ffd7076b03e0aa84000000000000000000000000000000000ba2ecf990cd846c95b35ab60d4f97f5814c8189190df9d521b3dae462f2d44db006a0daecf6b82c1459006bf82ef7c90000000000000000000000000000000016dc129b83cca5b3c699628d081306c5fa61faf9dda5e92894931714037628fb829c595bf64d4a7fa295f136ae244601",
+    "Name": "matter_g2_add_27",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e055086016000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6",
+    "Expected": "000000000000000000000000000000000a66f36f2437db57473bd8b7670994f1cfeb8b43c0ceae358e63a5e4e52b737fce6b3d24cc4de593bcd44c63f2c5935900000000000000000000000000000000070b7ad970f03a38c8a31452cf11422159cd3331d746031781a5861e26f54efbaba63dcb1db8bab997eada9c3dac39cc000000000000000000000000000000000ba4a9d7350adca1ae64e722df11baeea77c5fb75c5b52c8c46b9d863a70bfed1ec47888e907213f4ed4dcaedd37f20f0000000000000000000000000000000008a64244f1870a1dbcc4bd4d5c9eb5cd5225713dc73aa22bc46b1cea36c88a66f85251a8a9ba7279c88bd5dd37a06f7b",
+    "Name": "matter_g2_add_28",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3ff000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703",
+    "Expected": "00000000000000000000000000000000079f89f2defd1f97efe0ba1db28523abc88cdf66efd39918a600a07c5ed5b72ab9d3354a172735e7749b5f6814a48f4f0000000000000000000000000000000009e361b8609be8057e5b3c99eaa1727fdac17edc59239af17f55d72c8b8daa89726f4ae240c742ec4b02fbd89d45c46400000000000000000000000000000000121b475a2ab50357ce80fe01fc461195029de20f61474b0773d80434253adfc268a775e1a0e3b7df5e85d1ff8c5008960000000000000000000000000000000019a76aef4e04136b1ad0d03586a3d8608ac4573715f18d5fd6907d03e5fec7c5659e15c19fd87f242da972b651dff5fa",
+    "Name": "matter_g2_add_29",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b200000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb",
+    "Expected": "000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c6700000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02",
+    "Name": "matter_g2_add_30",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233",
+    "Expected": "0000000000000000000000000000000013f8cdab447ef9be450b87f941c96d4e93d5efd811d80c6a910965728f7dc496dec132f3fbeee5d1e84ed7c24ca9c2a8000000000000000000000000000000001537d5caa13ddfac93f0f86729c743d9a68175a78c730528b581fb54b1f4d020473b3b766e3882a485ce5d02ab381c33000000000000000000000000000000000b370903684ede24f3df80e3834ed414a765cdbad98f20c49bef8663a82a468d3911d6bbcdc021e22c252e83a857e55800000000000000000000000000000000100cc8d05f071904753776c6092a38db84c5de751bf93216131a0f9a50bf78a722344a14b3be2a9207568d1f669d208d",
+    "Name": "matter_g2_add_31",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f860000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce",
+    "Expected": "0000000000000000000000000000000003c5498b8c2d4765a270254dc927c6edf02acf0759540ddad951ea8c097bddb949ea0bf19942accd615bef21e8572dff0000000000000000000000000000000004c17bb648909bdddab4dd86560cb6b341e96f58c515ce471281f226181bded16b358b56d72e363f9ec491b8a9dcd92c000000000000000000000000000000001828973958204f8ab8cd13f5af5f3529f368a149bfe931a8002b61a61895457fbcb0cc6874631bb55799c884b998d8b9000000000000000000000000000000000f61460bf61bbf3ce38917850bfd3cece1e3955ce29d200c6f8aa89076c70919c02668678edc0bcf94efc9e9ff6a650e",
+    "Name": "matter_g2_add_32",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac5",
+    "Expected": "0000000000000000000000000000000002c6104b3494fdef86d53f87bea68d313188c0908b935fb3b9f636ccd401c6e9cbd33bfcdd437e1a0150d0e4b9c3a881000000000000000000000000000000000bdc88396f807d1ba8d4d6e284d008b5e40445ce32c23a0178824fdbb6db3c5aede7687eaa2f12249125cded57052ad2000000000000000000000000000000000c7004365c1d3027997b55bd258dfc61ae07a762666fba2a14aa2ca116673fc03a6f694c069f53cd915fef6d37513101000000000000000000000000000000000ec17688d8f53e2c92502091c859cef4fe9a57ae984cb1e72686bf1f0656b10246293cae4b96214a38dc76cf2709bd59",
+    "Name": "matter_g2_add_33",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c99739",
+    "Expected": "000000000000000000000000000000000a44e6a48ea0a95667f607ee66290cb0094c964baed779bd6656941db28e30a7e9effe49a617be9ab376af4f535cc28f000000000000000000000000000000001933b87310bf5fa60b1abcd13bb7ac3f2ec0a278f6a0a70c953a2905ac1d3bc5a70cf1da885af45d1c7680bb4f7ff74c000000000000000000000000000000000597ce9f1bf7efacdcb0250427d0341e142226aaea060983175ea149912c5c4f3019fe87be6d87d186a8f562fc3059eb00000000000000000000000000000000198b5a891722a237a5e23e3004798c8d3f069af3267152508e283b4549fc5e8388330343f80e606eba30af51c99c7020",
+    "Name": "matter_g2_add_34",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe20000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0",
+    "Expected": "00000000000000000000000000000000047c2ccda315b9c013e87bc9168b3b8dd6d463403f1cefd824fa9f93a99f4c4f98fac5f97e4237f76b1ec91042f99bd600000000000000000000000000000000036861fd0a69cbc851741475905441b51af12c5b2aaee6ce9a27a01a43db810be9c7d6fa401406e98e327703404b83a5000000000000000000000000000000000310cbdf53f6cf8d87e2d178869bee4359a8dd666986d869761a79963680a33ea3ecefd40a1e558acae5ded2ca04447300000000000000000000000000000000108bbb28c73ed7e76a51a78e4d15a2c88c25e05c7127ae89d4347cda00be231b5e70e0b0562caddd4a7083efa4516722",
+    "Name": "matter_g2_add_35",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a84486000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c",
+    "Expected": "00000000000000000000000000000000137d23ed3fa0d7e5928af8d1f4bdfdef08e0b4c0f3bf6f51ed28960ce9805eb8fb254233bb18cbfecbadba95e112fdb80000000000000000000000000000000018615147d7a8cce1dfed6de25cf2fb52f54a243bed4913e20e66673f47ecddad9c5e4ff9653f522180de4b90ddb3ad17000000000000000000000000000000001521f12116b13f785b5211aaf438aa6668bbfa318cf0ed6d91aae963f6f00d32cc5f25d3a02bd902ccc25f847ee2db830000000000000000000000000000000014263b23396f4facdacf13c79864157823db724350bc640abf8fb6d62663cec1069eef9db56817660510e2417b51c616",
+    "Name": "matter_g2_add_36",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e370000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d05712",
+    "Expected": "000000000000000000000000000000000038f9df6c14f84b8ef8045010c8973e5c2f8d2e37268f6a674298de7b15cae82361ebbfaa00ea1cb2653c5d00886b45000000000000000000000000000000001376f7e2d5621aa9d6f7ce45ed11de7e0e1095ebeea976f78eb83189c6852ee199840c14059c233bc3d40efbeeb5eb36000000000000000000000000000000000c7b0e53adf4f0fc5172f903e3fc479539348241edc3e277f30ae6b4fc419aadcfb73a8f8a09a1ae1dd885a6250de0040000000000000000000000000000000007a00b57ecc8b056436ecacd7e0fd346b906b15042e9a700f54f8c3b1d251c566e0c55bd34f7a9e30f1566b7f2ab16dd",
+    "Name": "matter_g2_add_37",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4",
+    "Expected": "0000000000000000000000000000000012662e19e41bfacc0c792f5183596bc7f1986f9bea72c626e187d72111b6ef3f36f5afeeb640cfda99b7044c0d0b846900000000000000000000000000000000050ba08e1b9fe95dc67e6ee1ce60664b291c80fdb59729cdea75dfd18f22fb88f837b439fd119c46c996787d3008194b0000000000000000000000000000000004ea0f488fece967675abdd3c42f8fec25b547cfc45d42fba14bbc55ad7e1a75296a679113d0671cef0aec0c2165f4a0000000000000000000000000000000000f617f51800b09150a7560505079c785ab45cea4705992fc0325edaf4ceb30e1f0bec35a31898db5f810685e55634076",
+    "Name": "matter_g2_add_38",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb300000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc",
+    "Expected": "0000000000000000000000000000000019c774e968049bde2188e844c3413203bfe2c4355edc8cbc2cf6f977c34c0a42a206194e6eecba3c97b24558048f3aa700000000000000000000000000000000081ccf6f111575a946341759b9faa13f3608998fbf4ea3b547804737e30fc7e33495caaf2aa328b19bd48315c5c7f9e2000000000000000000000000000000000a4098536041cfb808176c7cd8e980eda613a2b390e8d63d607caaac26db02fccad6d87412b90cb4b3e186bf9ccd31be000000000000000000000000000000000d3c784c6587b9f786c06099a62aa639f40535b512ac2440912f04dfcd1cb5851b7378f381fcdf02d4e58312eb7e442f",
+    "Name": "matter_g2_add_39",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05500000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6",
+    "Expected": "0000000000000000000000000000000016fc7c743c5ba747640a6494fb3c30caad5a1e9719a1994d0ca73bd1645fec118a2887acc8876d105102241c10274cd300000000000000000000000000000000058a42a0095a7388fba7ce71dbef4ecfd2018c3fcdde14afd2be26588de4689d8de757e1e3ff22645fb8c17aa60265850000000000000000000000000000000010bb622f649e346834b95e82f93ae83c71c0a65df7842c4ba88df7f6eccb0217ca9377167a6d14777e0474c24821f8d70000000000000000000000000000000010c180c685ea3d0146eb82c007fec3efd129880f18f838f1cd2f80181f5a4884d6b5cc8247430fb0c1701a57f9d1d485",
+    "Name": "matter_g2_add_40",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d03",
+    "Expected": "0000000000000000000000000000000019419b635c3742cecffee02ee7e2b1f18ee9ff15e647ca0abc4398ddc421ae7e0444e3c1ec377def9e832d8e64fd40e2000000000000000000000000000000000d9b4abfdaf3b4c7bf00fa07579befa10a3418d8fa0f3a9c31e59ae48b0de50fc8e6d583aaa4d0fe6048bdd1a9c60eb60000000000000000000000000000000003c96d57034ec97c4abef1c2c81f4d4b0f4b6eb1e9dc5464bcab28572555b9b874df80325941501c3766fd7e06bfe7360000000000000000000000000000000002dbb3d72385b562ddcb9a80400ab3770f00d22b880cce2fce1641042b9da669b22b2fbc97617648c25ab644e661e2fe",
+    "Name": "matter_g2_add_41",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbc00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d",
+    "Expected": "000000000000000000000000000000000d32b00154a5fe75c576c098419744ac36b911ee800f94bd598ff9b6adcaa39c836bc158c5d6af72c9e715a242d0fe710000000000000000000000000000000006e057c13885d6c05f5d92061fdc4d532f10d31d472c371e71367fef7c5fdd3741e665321d1119b895660fba3770431b000000000000000000000000000000000bfe695c3364e15479741e974f838649e789a76d073e552aaa60981fbc6d185eb7b297fd59e51535965214a02f5cd67e0000000000000000000000000000000014f0a27412248e3163e5f82fed02a25d953b336b0201692f08a3e8e9a9d223b736c70c1a39826a0888fb02a314e223fd",
+    "Name": "matter_g2_add_42",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6ea0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47",
+    "Expected": "000000000000000000000000000000001566022247ce012b7de92c8495876b4de91c36448f4f7e00f6e154185d38a735e701dda989ae9e37d332a5e60af5d06b00000000000000000000000000000000065aa42560df7990df2098827a55ceaabf3ec592c53d2f20e5dddc1481ee64381accbc8e58601428d33589b3af78a4b70000000000000000000000000000000002d9b0cf8bfd1adf76bca80ca351a4340f02434090518807e07ed76440497042f13a0cd7a9c30086872d6f145808fb290000000000000000000000000000000015daaa131431e3e78a6221091640811fcf88c835ac975a041a7ab50bc1d06b80e6a3c9ae77d2390fd14cc9bb009b47cc",
+    "Name": "matter_g2_add_43",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710be000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf",
+    "Expected": "000000000000000000000000000000001290bff629c93d992ad2cc709317c48980b0e56a32fe239258c7aec75e4523e0bc0b81319e100d10568a44847869a8d000000000000000000000000000000000055d9098e08eabdf2b883df35efebec9f6afb16d651ebaca1067e2129146268664ec51c8a4f28f13a250f3e9883053780000000000000000000000000000000002424dab6f0d18ea8bdded2a72bcf87c13307d27d53e8ec35e91eeab97fcf3398135fd436c530c609fd47a3508472bad000000000000000000000000000000000b25d0db1e28b98d4f9d3c77c0b71489c51186105d93be7fc2cf8c72b8abd8959340114635e705e698b0f257855ea4bc",
+    "Name": "matter_g2_add_44",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be100000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2",
+    "Expected": "000000000000000000000000000000000cb2998b4e634bc83b5585b0683b7b561f260eefb826719bdc3c95e8ae51f8f7b442d75d69e0f9228dacde2ce80ef4e60000000000000000000000000000000014d30d1c02122143868ea01b454a4f33432d875f8ba66e6bb1e02fc161bb5f9298e673339a9183a15759f8b94b519cad000000000000000000000000000000001068bf3c768e8c9e9058805050394ea820b5f60bea6d271f8e1fb665d3b7931ab0cc03dff4cbd24577b2c254a956e8200000000000000000000000000000000008b7f4148bd1f4926d2a84497b60a48701057ea08855bb9a2f838d2464e66360a59d058d9072f1416023cc72045af558",
+    "Name": "matter_g2_add_45",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da900000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb",
+    "Expected": "000000000000000000000000000000000a7843a1d67360b8a6976aeda2e4e98f1ea229a4d84b947dcf5ed8215173d5cf783920a7714f5b048778df30f01a0bed00000000000000000000000000000000035663ceafda9e5bfe934cff725b36b258f12afe749f907a560a06da4abf8380853f8de31adf14d62cdb310d8740e29b000000000000000000000000000000000f210d576aa5d4cdf5aefd8e55be099c422debc217ddf0151b8801f7d16456c97d1e134b40e6d71d296ee2518e50af9d000000000000000000000000000000000219efb35c68540c6bb0ef224e68dae6f7d48425c2908440072f5f63eec3c8e750b559c73e33464d0b5cdabb50fc4d3d",
+    "Name": "matter_g2_add_46",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee",
+    "Expected": "000000000000000000000000000000000ce704e650605f747cbc0bc76e82de8569ba7b3d897eac2bf5f79aba17ef4c989731e959c0bc0b7988000a9b0aef39430000000000000000000000000000000003cd3f3d978d6c85d98812ea0e3d21149bf4151ad1bef966ced124ad62dc7cde55f16e8d08bb1ad54d3a23bb73795d8f0000000000000000000000000000000019d37a20fcf6244c2898b271535e3b8f279eaac5d8fb1ba142096da383488eba28a21d038d7a9d3f9e8a008d6d3ee1d20000000000000000000000000000000001ba9c1720a4ef07ec752efa1ddb629505b3586af415c916fb0ed2953cd8943d9343268f438db860f0bced3e690a66b0",
+    "Name": "matter_g2_add_47",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c70000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb9",
+    "Expected": "00000000000000000000000000000000160d8b4bef36fc3d09af09dcc8357067c22e421f3811deea66faec42a2f00fa4aceca8725cf99062613126a9fd7bf7210000000000000000000000000000000004e8691a42c8f3ce0e7c0470446689e9d2b3cf57d55fad7387d624857f977cb9c6864c87bb4b6a2c17538478ac5fb5960000000000000000000000000000000015e20f6baef033efbd38081d5a10eeb3c67d89ebe5cd652110b778313c9e86cffb45231616d5b67e9ec8b7be15980aa9000000000000000000000000000000000af75dc221050256015fecc2bd8113b42afc9c624e5d28d7ff8312af499e34a603d66a4304f263729b440b6266538316",
+    "Name": "matter_g2_add_48",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c6",
+    "Expected": "0000000000000000000000000000000013edd8f016f6af49e9bc461ca14c438a32eaa3d1270a5acec99a666aba3f0a7e7eccea81720971cf4432bfa94cd18392000000000000000000000000000000000dbea5617e44c82da828844a5a4a1426d43422fd0158204a99f53cf9821f82f0bb0130a2123297a6941f695e172d9c5e0000000000000000000000000000000005f65a445e9f2d57dff2b210209f9faeb1c8b446454de4724d990aab20bd68362dd7ceb5b95de361c129855abba83f7e000000000000000000000000000000001219ecae79d62d3039e642369353993b1ece049331f06be256f06b01a1c3b0c617221c8d8f0bf4b6a0abe1191a3ee8e2",
+    "Name": "matter_g2_add_49",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307",
+    "Expected": "00000000000000000000000000000000158da32df45fe3e9102010bfd7faf3fde936bb8e52f68262ef479ee825a0d7169ff753aa042883a5403103a9bdafd2be000000000000000000000000000000001800a5776a47f52d2af08144364a6cd7442a0e2fc214a2d8d285a29bb7bd3a0293e89f0a1856223a527100d0abf12899000000000000000000000000000000000a6079d18ff3367c47fa61a57a967b782f3529bee93f452ecebd4f5c404b3e1769c100da9b8aee4258b5191ae1dad9a90000000000000000000000000000000011d3188a927e8f13aecf7f8637be6ddbbce309393a94fef77923c286244f8531d3e137e031d8c1af829891425afd53a3",
+    "Name": "matter_g2_add_50",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52d000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc26",
+    "Expected": "0000000000000000000000000000000019294d87be784f0f8fa29de80d45a697bcb694b32f3f6d7641d4b08d8a7ebdad0ef78ba5ccafd6b7f240e1cbde019c51000000000000000000000000000000000645f7851644e1e7e255d0b3dca769b987ec3ff2c9eda42cab65dc39be2f9858c31f307d59f6a2caf9dd932d873d2b08000000000000000000000000000000000e8e93f39ce05a11d40f3b52262980c79ecc52939dd02b94df3e5034a57061d040b0c8894189f4626f37bee485712dd00000000000000000000000000000000001e0b7c9c3d7456b2c0ad842083e9ce2a00da91cb1aaba371ff4b9370f0f2c08f4b53b8e5a3030c99b2957cbe5f9e967",
+    "Name": "matter_g2_add_51",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d761800000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb34150",
+    "Expected": "00000000000000000000000000000000040f355021ba50c9a3b2b4267668ac8d76dd88991be984ab5bab9c96faed6dcc6e8eac78ed29cd6f7d687dd55cc5d5b70000000000000000000000000000000017853cf0a39332e3c7d75b08b2940d693ac7cfdac46719787c22b55a2ab1036d6f95b68075f1c585942843aa486f17bf0000000000000000000000000000000008696feb333417a7262e8976d1546b6d0a9d5970095485b18efcdee8993b16f42e6dbfdd08d30c45fe4af6a5e203de07000000000000000000000000000000000ec26926720243124ca505c0e04923f3cf5eeca2abfdaf4388960b87c6c1713fc54cdd1c825e2ea359cc67b3bebfa2f9",
+    "Name": "matter_g2_add_52",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e69000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b1",
+    "Expected": "000000000000000000000000000000000f3dd56c416db1c06fd27e18fb852c9e1662fed42005e253230a7a8f7c3e0b8ce637666e1d20952c219cd2068d6865f1000000000000000000000000000000000aff045afcbefcdcb5255805a86e8af3de881e5482188c487d15ad1b799cf551c1d48c7665028b05ceb2e82e15ea4ae5000000000000000000000000000000000e0e6ed04926aed1f8c6a4e13227bf2a99d9d6d349a9c86214373be693db702a0011b4423defdb7d842bcb6f722c70b100000000000000000000000000000000148b1af285c65b12eef498f1c9e57a673e7a3803088c56e32aaae13dad3977dda8d3e27809094f8d8ed607239610a1a6",
+    "Name": "matter_g2_add_53",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df10000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e",
+    "Expected": "000000000000000000000000000000001220b3da7e7d03823458bcdcee82db56957e5aec335e9b543ebb0f3cf4fe3cf6ecacb6198c886b9abbdaa42f528b4963000000000000000000000000000000000138233b166547e9e9ee9d11048e2d2579b2b111af5cab372d36159c4c45e28d836d733a1265e8833da64f461c0a32cd00000000000000000000000000000000005f860a0c72034f1a928501d9f549e5c2a9dc72670272fbf35a0b301025c0fc751d55ef6fc2c5bf7ff42df7693f3dca0000000000000000000000000000000012c73105adf97bc0dfec1f56153c57c6fdb9d68341f4397b72f5b6c667873ff7ed5cc841451b391e33290cec256395c7",
+    "Name": "matter_g2_add_54",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0f0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e6",
+    "Expected": "00000000000000000000000000000000014933a0923416428b5fe5be7120bf399ab62ca091b07d03da3fd2ff080b9c411c3cda3bfef40c8450ae31c412dc5feb000000000000000000000000000000000214229a73780d4f260364649e9eb2ed751ad3f687a832a3738ca2cc81a3acf12757651e88c4bcd79239bc0b0c40e5a6000000000000000000000000000000000548f20fa375e578084e085ee71df5f8ddaec1db03a1415938d9521b5d9c914b5295835fc07263cdbf49d7802551156a00000000000000000000000000000000063ecd9efe55229a76fc848728e940183c23bf47363cb34c5a49837e6df8a5f0dc29d7108cd10ea08e82ccf017d246d1",
+    "Name": "matter_g2_add_55",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a330000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c32",
+    "Expected": "0000000000000000000000000000000008a71a08d2c4e2ba3d8774dcb42d3e96c7f72d36fb3b880a4049b078d8257a7a9a51b0b34c093568baf4aa6de70e709d000000000000000000000000000000000daf83b5ad4b91b557982fc4b9b7dbed2998aa39fc4658ba671f5f27b3888dfec7602949cf626c9e6ef21171acb185600000000000000000000000000000000013a7ffca291d9ba8790ca0462c54c147aa22e03a2413b756f27583155932aee65060924e46db321b3fd6f22ff7f54041000000000000000000000000000000000289d7de10285285279aee024e52476fa6fca85550f7af183a161e395d72e1339b629c64127f96bc85858d80e73dcbe1",
+    "Name": "matter_g2_add_56",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a6000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95",
+    "Expected": "000000000000000000000000000000000a4ed8d613cfe4f5dbda1d0c6812d0edee45ffc2667323c3828f8ce4ab55c119e92a82f2c3d06afe3adaa4aaccc18f8d000000000000000000000000000000000fe10c5e185f3f8ba81c93754132d76e05eb3543d8aaa8a2d0c98833ce5fa9e2b84420d6e3412e005cf89d11f5400a510000000000000000000000000000000004ac5f8cc614e3833b3b6dd9eee9ac29501002ba9054554314a4c516bfc8cec870995e811f7892811346574f3c58b2ec000000000000000000000000000000000a6bed54d8ed4ccb09211ae7773c604edc6ce51a05c9acc94e8167026906d387af681fb33a40e72e85cb076e072db7d9",
+    "Name": "matter_g2_add_57",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f254000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150",
+    "Expected": "0000000000000000000000000000000004d145ad2575313a922667b897052063139eef8c61dd375eb055c4a5c52cfbed35391a85df915e1eea50d000b9b6bb5700000000000000000000000000000000071cc73c16a234e99faba9b04fafaca1a943f2bdbb68dcae0a1742acfca1f90c5f69464aba42be6c18be31f79ce30791000000000000000000000000000000000bf725a2f4d7d33c66fefeefce13fb5649a68a93fb7086c943a7bd5663b5788a5ceaad7fd2a219ade832dfb3c0022a5a000000000000000000000000000000000fef4a2610610afef43da2161b86b25a8f6e30ed90053d57f5ee0a10effcdd2af769d32ef6843804b2b6590f95eccb4c",
+    "Name": "matter_g2_add_58",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c5600000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b",
+    "Expected": "00000000000000000000000000000000151ec7c35a67b878420e198ee7bf359d0668ab61ba1a0bc2e5e57b1b7b18838a015464f9910b659fb7d1e10af2801d86000000000000000000000000000000000511536f34067fe931c6e829e22443eb838f0c938eeef6f839eb322d72e2011dd1c33c504dd044e3cd721065d7075b520000000000000000000000000000000010c486f846242024f9bf40d805c8e33ecf1b44cfaa04455d5584db7ebc32c0d29e8742c61886d4ebae93f22c518ea87300000000000000000000000000000000072e184c836a853fd1153eabb1b645bd35ef72eefde4a52db169acdf2d8d68499398599cb4002994c6f4936de1da75ef",
+    "Name": "matter_g2_add_59",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87310000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421",
+    "Expected": "000000000000000000000000000000000642f215b772d17a3aa45ee3aee607321c02b4f7a7df3884259a25ce78c73e9536d46333fa388e506fdc79c708bfd9de00000000000000000000000000000000145864ce36521fdb641761be541a27bbd3f4797b923a870148bef1d5b4b0d463c0a7c8ef07954dad464510d836105e05000000000000000000000000000000000ca038e667fe68111b583dfaa95f88d3b9e46c0798abccd1476071435067e6c0e2fa81d25db6e1175e60efa1705538b9000000000000000000000000000000000cf1cb1b155e4ea47077c42a1a99c3f11f8b27516a808b5e73498ee12363652bb46eab7e55de93513cc2d6272f26a537",
+    "Name": "matter_g2_add_60",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab71960000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d400629",
+    "Expected": "00000000000000000000000000000000128c909854a20ccf9e8e396b617b36f233909a5f6c3524c93cc659d22afe0e7058a438a5ee4345bed914288c64802e29000000000000000000000000000000000239fc43718cd27855ee5450cc9be5be5d9bca8188c22601242a1bb4269ca0fe62ad5e12b2c65558cd3dfc89ea31205f000000000000000000000000000000000a0aec9527febbd35bf041a901b0b35e5e0d48a2d6d733bb557d0767798369a7ccf2f1c278710eb764f721821f9aeea300000000000000000000000000000000194931bad52daa16a648ccf1ba9a4768e5e2900fee4f9bf46ae07d1aa605aabbfe96684f5d2233c0b254cb4ad5517775",
+    "Name": "matter_g2_add_61",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f20000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d",
+    "Expected": "00000000000000000000000000000000189ee5ac642bfd0b612058f96e63acb1feb6b4dce125bf0ea1e56e846775af1a8b0864d4ece6bd96c3b5dbb04e2f6c33000000000000000000000000000000000073d57ab79314e38267ee8015de3156f2c1d5dfcb6655a150b9ab4a3bc9eeddf7b37b3681c49611e02abb012770b3f5000000000000000000000000000000000cfa1363275c7bc5bbb9bb7c03e7bb7f6d6d365e39fccbe62cfe0bb93280527c9ea99079fdf9871abed035b62079856b0000000000000000000000000000000010048e4e96f26710d254110650de36460be2a8302badfc2da8b26147da498e4620e79b4329033fc3f3a9c99b1e12aad4",
+    "Name": "matter_g2_add_62",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e",
+    "Expected": "0000000000000000000000000000000005889133be5f447013d779f2b9b0033667c5af87e1c8a16d239ca3ed238920004d87e00119ded46658026c26988ee63a000000000000000000000000000000000d4ed8fd88f7e1394f2b5a65588bf1c461a292acafdb77703c2790ef249f2de695524293c826252c94967a3ea4a3a28500000000000000000000000000000000001b5ff0aa278c7e87a89d4748aef13b516c49b7dc9f7cd5e0448dc6fd860a7a8af7183a198eebe6c7dd549fef806db00000000000000000000000000000000003c9e40ed44427cc3cf886ca2db341ae31f015c542b857f6702d25cb5036e3e6abeb8d4bf9a0e203281ab85ad89ce0da",
+    "Name": "matter_g2_add_63",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f",
+    "Expected": "00000000000000000000000000000000093b692a68536b16913ef38c3bba7b19ba94a6af1c36a2e54b8ac1754a29c29882107cde142deb95365af00f2d1f537e000000000000000000000000000000001035e70852f38f860a1a04f33081e84f3ed17d83ad894a6800e7b8b9259067b755fe7e08d4c1b297c6d53064ab8209590000000000000000000000000000000013d38db0d8575131865bd7acb6cbe994812bdd8bc7f51b810bc382a6eb379d442c47be20a2c8e751fb08ccce8fea68690000000000000000000000000000000000bd114951193e3bd58cd0025e0b0c807ea073b1c1f7bb04a2a00771b6442e70ea20e1124572ef5b74d2bd87c93c82f5",
+    "Name": "matter_g2_add_64",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d",
+    "Expected": "0000000000000000000000000000000006db1eef1f614613ada8383e63d631484015224902ca38f58ee384a70af0a0575b0e7063675d2dd997ed8a140e2598470000000000000000000000000000000010d7b833f050f18ff4e3a8d0df227a9494dad9cbde88f68802b23e87387622a5333dfb7bcdcbfe2d4d137cb532ef4a150000000000000000000000000000000000c9c40ba972ee0be2823625a23345fe352d701cc8bf9a153d5a55c205ef1b7e5544d0a7f65aaa24bde8d77cb4c31ab3000000000000000000000000000000000402f170c4c3ebb9b1e7d64765b66ba9b8d45b2ea9fe9517626f38e00a11d180e1f8872bf80f6322bdf3a8dd90732ae9",
+    "Name": "matter_g2_add_65",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad300000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0",
+    "Expected": "0000000000000000000000000000000002dccab673b26be02d2c645c82a2c73290f0eb053e07d4f81d4d315d9483e57c58b65cfabeb0172934b9fbb52ad519210000000000000000000000000000000011c34a27c850fe319fe89399e7680064caf6dcbad171c3a23c45b9883ee06ccc3482b2b81e5777759ff81b16bcc1b0f500000000000000000000000000000000119adca3e2b052c045124f021fceb03c979e6eec0a270c7f4ab13674e461839a4d3a10fd48da4e9ae750a238a2649ace000000000000000000000000000000000fb5210677e1096cb5448bcda16646d6dd29ff8a0765c5aa51d83fc952a5ab8063aa96e97f33abf701cb8688c989c363",
+    "Name": "matter_g2_add_66",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0800000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508",
+    "Expected": "00000000000000000000000000000000056489b2248ba672501069ab6742016cc8ab2af50a119239bbd3c0a4b9b56e014402b78bf62b2b37bf4645c3bd3d95b800000000000000000000000000000000046956432001feaba6d230da27a72e8db5c8eb3d52f00616f87b55c951217095f337a302562cda789e5714c4391ac27000000000000000000000000000000000172c2a583c9563fe02d43b2b767c4ee4e3990fbabe4ac536d64cfcf059f0e38672876289bc86915b6344eb398fbc4ddb0000000000000000000000000000000008915b0edade80caee9b386e4a560ff4b9dce33946ee992649466315786e139e3ce241ebbdfa7ee28fad7e6214e65666",
+    "Name": "matter_g2_add_67",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e1090000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f",
+    "Expected": "0000000000000000000000000000000005b81843ef3f98c6a6686f1fbd26f77248497ec3d41aff4be5968d13ba86f86309b0ec4792d74220ad8ef147bdee9aa90000000000000000000000000000000019825376b243f3e374b6e9e7e51e0c969bc72b39cde1dfa09187a3c7c5c2c752ee16fa5a4c8fcf94464287419b3a3845000000000000000000000000000000001308cc0c77219034a9fc3018f1d668a41e6959476aaaa5461ec73d7155c6a68fb08e1fdf8140e18270cd338c266a83f4000000000000000000000000000000000fee2a6e245e3bb570c3b605f7ad805bcd68e9a1f2bb2282f92e2a2e83b69e275b21b923f33a65defa8c4224934aa588",
+    "Name": "matter_g2_add_68",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b",
+    "Expected": "00000000000000000000000000000000166414455bcd0e8e40397f4cafa9628d1a092beaef62d35211cf49779ba98df5c1d692f650c1fcf0893a9d4ae1926b1c0000000000000000000000000000000003dd898d0725ee899b913042da8566a1379aeb4dd5f0222ac784205b4e74f32858ae490f981801b166a01fb96266dbeb0000000000000000000000000000000019f0fe4f12b113b337361b977aff7cc7dce50bf37c2609b9f311ce340d30225de178999b73345ef49625518e52aa4d7800000000000000000000000000000000090bc07c6270901d706a8d28d512b07fd0e03013d94d4e43eafbee59677998bfb7c2a58aa93571fb49c35518b6331bca",
+    "Name": "matter_g2_add_69",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b22000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d",
+    "Expected": "0000000000000000000000000000000019ce0f31d9ebaed0ea1d12d4e232bd3ad48373fa465af44f1c8015102b624d2f8330d1323fb2fec524e83de0f6699ad7000000000000000000000000000000000915d65fef96562ea3b76f3152aa1b8e445ef50fa66dc487ad0c04cfd7a33b5ee48aed919eb81fe83b1f4dca59b4990d000000000000000000000000000000000e4731ec887261f29475523f7dfc5d21cbbc1b883439701a33cd58bd24f5d447267707c2b60ea38b04510be7dd10d72b00000000000000000000000000000000146a679d7a81aac5952645b2635f24b96393529ab9571ecc1078c4c20a77e59acc4591b9f45df00428250c5e31b1a8e9",
+    "Name": "matter_g2_add_70",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f",
+    "Expected": "0000000000000000000000000000000016790155e57f7103d9e325a1f3a64c0b8a1875365eaa0c01c515538b64bd8265e8392e755a2f7314c37ec09026f13d290000000000000000000000000000000007bfe690fc4ab166b29de35e341e8faec4bc3c2d4ea2d42c9f4166c0d748b92b743ba646c86ff9e570612c75bcd522a9000000000000000000000000000000000c11b9ccf990162b772099fdb4266716b11dcf46c5abd12d03caf222c571e2a9e28cfb47e11db05162967ad4b430930e0000000000000000000000000000000000bafe02785607bae144d9ef5391fef02b9f2fd5dcd436e2506bd40866d8726eb83c223e09c00f3b8895181c6710912f",
+    "Name": "matter_g2_add_71",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a00000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd",
+    "Expected": "000000000000000000000000000000000965966a8a463de1f3bc49d9873668e87f54d95612231458dc8b885681cee8e2835482b4bfc476153c41b206f427cbb400000000000000000000000000000000183639fa14dd74c33e8696496a3ee269160f88e5daca4fdc468724d9b6af8e7d0706867cdb1bcc608029b89b94c531a800000000000000000000000000000000026257fc32efaf241c7712b0a7e9f881763d8fa0711a452d9b71ea25e973bffd88433cba768f1e5b3ea15bdae9cb9428000000000000000000000000000000001527afbb6594dc0f472673606fb8f4797fc855bde4d308ac1acdaa26f19a70f80f2d2bbf3498b53b887b79fd6273231d",
+    "Name": "matter_g2_add_72",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db100000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3",
+    "Expected": "000000000000000000000000000000000018123e82a5572e6b6c62d5db07448838df9db7f7d15dac1adba1fd924892c8bb3c417354e838f706564a9ac282c2ac0000000000000000000000000000000016613fc38997d39b2761aed3485de4d7c273e8392e434185605e968ed942b9d4712cd0d538ed5ed1317870d0cafcae27000000000000000000000000000000000354365566b6e43f8b7f4b94a6343146f35ba3abf61a204e9c976b1ad1a90d4d493494c957def69ff270371c1c8d953100000000000000000000000000000000066adbadf1b69dd16cf19349c82e362be4a3768551599b81a4853ca524a24326e6c9dcc38b5a60ed6fdeb3cc4e7973bc",
+    "Name": "matter_g2_add_73",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d",
+    "Expected": "0000000000000000000000000000000018ba8af47c5cfa552374cb1b25ada1ac785381f2da0501f86c9e7b11cd4417e64095a5c4bdc2480ee10d215ae2296063000000000000000000000000000000000a2e09eff98280f6a9863d8b8faf8871b44650496eac1aaf90fc2b256f88e937101407d722c95fa76846776d4e6bf0dd0000000000000000000000000000000003824f5bf25fa4aec5a9e044703e5564122bec11da155c01ba8ab8344265516c1063983235863d826f68bac455327c65000000000000000000000000000000000ea72f8c6768736800b141b477610e37477d926acaffaa1951a5bfebb042c94c065e984a8812430153d529dbf07ce2bc",
+    "Name": "matter_g2_add_74",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296fe00000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6",
+    "Expected": "0000000000000000000000000000000009f1339cff0b58b00a871add058929ffebdc58cd1bd8a9c2c965c63e1843945b28138008cca8bf7b7cc9afb69a11767100000000000000000000000000000000011f65b337710a4043e1fa58bb41d80d505e2aee434b6978129c80fa1b124db89e61617e89bc0e596507566f4a484e9f0000000000000000000000000000000017560f768496ed583b3522c4a013f8b96073197e5b53e9041db6dc935a266111e21d8c54fa33b7bda944a573f6e1f07d000000000000000000000000000000000168a0742af91f42058e6501e122b6fc50dc966c2f5981372704694544aaa68fba2b6483752fa2464526d5072f84d8dd",
+    "Name": "matter_g2_add_75",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb519000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be",
+    "Expected": "0000000000000000000000000000000005daf8338637bddeba63c788d78faa622e014efb84d3ac1d655d15af06317fe31d1782b2990354bd507632844cc87f2700000000000000000000000000000000185550250e2d9eec798e8b8c483dc37e2a917b304a6036e8ee518a0738d6bf946d99f6b7ee352b1a259aa894d53a8e1300000000000000000000000000000000105a4865d66ed4bc4f51dc52ffcf284615593d573b6beac490c3ee8e08ab83a529c8dd062d762d1d70b9b3290b6e8bd50000000000000000000000000000000014f598e5d0e40090f29aec1ecaccbebbf2a2d6889bbb9439798924db41b70c0cacdcf1e8ff6906f61943e9a8a1ae4fb5",
+    "Name": "matter_g2_add_76",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c",
+    "Expected": "0000000000000000000000000000000006b63929ce97554659ae731d60d11abe858383e39a67007877f68233cba8179777c0dfe511fc730448da3f1c4347f85c0000000000000000000000000000000016d4df414c287b0871c69f9745a9ae68ea3a1ff41ecd17d87623338bb8750bf12be52caa81537bacee06cebb86f894890000000000000000000000000000000007ad72c98e2428b90bead3616f1b31b26e978cd3f9b6b759ad53056098c18932c48ba78d3da112d7a738d7a9ba21d84e0000000000000000000000000000000010dfcfc53d0458296686fd7e0555593e0378d2cb176d456abebfd8322012bc9b408bb180d4237679985457e689131705",
+    "Name": "matter_g2_add_77",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94600000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3",
+    "Expected": "0000000000000000000000000000000009b166f124b5b85875834b5b0c088ab79a2dcf262240b284f57722e78b6eb56a192cd32544c1bb93ef492fe6d7a6216b00000000000000000000000000000000189b9792982b51b13cc3fc1691e0569b6c8d998168d3a3376e63ca60de4b30a84ce8d04fb265bdcf73f158d8e316bdda0000000000000000000000000000000005b99948b635750040b5b59568f0e8bacbfd512db2ae52c5032cd23eac18ad58d83b8f78cd26ae979ce2abeae8e1f3c3000000000000000000000000000000000d0b6561a49c358101b30f714563bfefc72e0febea857b1ce78cfeb9508b0108c2089c9b35cd694bc8c0ea8afc8d047e",
+    "Name": "matter_g2_add_78",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88",
+    "Expected": "000000000000000000000000000000000bbb59d3e6b0b4d86ffc89bbfcf543a5b8ff922f1999a1e06c501a734b19dabd54632132c865c53e5287f69f06942a58000000000000000000000000000000000a3bb94431530879a7fb46b317d4f3d65b5a790739b396c78521a20e1cfad9c44248c9576be11c70970a49a1914ceffd00000000000000000000000000000000198df068ac5d3cfb9bd6896ab64495f4b9933a72872679ac3a46764478f043e9fddf17a7ef85fb72a8dc1a722804198400000000000000000000000000000000155c1a9db0c90634a6d214e996b13252bd4db3a4ab84ca7456ac3e7899e6fa096904a90f1150026307a1cac8de00c6df",
+    "Name": "matter_g2_add_79",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d70000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff148",
+    "Expected": "0000000000000000000000000000000010684ea0303f0e76b60eb96c470e1f0466f1f2b073bbedc1a0c0df1d2f6c66d77cb90ef9bfa4fef6a6a9eff8f5c66f9b0000000000000000000000000000000010e7ced79bbf01ae9f65d26894c73a905514296f19561ab4d00c0cde31737d01e7b4e8b8e6050054a7a17e8acb74d49d00000000000000000000000000000000174f771a98e262825ff2db7571f5f5475007d2f73a2c265f24e2929671bd173596b8b163abd46b868a644dd464dcc7cc0000000000000000000000000000000001cbffc9bb3195672ea2d998b169f853d3d4b4e147379329b1bbe69ce76d08ad78f87fdd876af227a050c31884fda084",
+    "Name": "matter_g2_add_80",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d527500000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f",
+    "Expected": "000000000000000000000000000000000fa306f630d06c801e0203525c75fd6065bd12bcb3c4d45c7e02b597f85a53fae1e65a969feedca75068433547e4632d0000000000000000000000000000000004b1bdbc29f19f6484ea4648c70eaa47cf5bb07bbc255bb72dcf68a7b661de433dafb682d51321369cd3372288b2b9c400000000000000000000000000000000136671654b24e1ff2e8223ba747ded51f5c826b6e2c0f02e2865fc35d15045f41952835800406f60f966d1f241914726000000000000000000000000000000001007b5e8ed7f0d25091dd959d89732e9df02561a829ce013f5ad1adb8d6d828a8ce87b52d39fda1b5dc2b581ca420e22",
+    "Name": "matter_g2_add_81",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae452300000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf",
+    "Expected": "000000000000000000000000000000000fb74d9ad4de11df81c48d10b9a14fde8353ac47dc902b4420be4c086332be480552e26fc42b7c0f30e34f740bf9a4e6000000000000000000000000000000000612a7e23bbb525f91084b122dd4cfce4074c9e6eedaa7cddb58a14e0b1eccc2f08296baea3eb3e003e576fab7c557ea0000000000000000000000000000000016dea145df47a2c5262893c273c6158ee14d44c3740981c161624a6e9ebb982a52c1eab6160c3849f2bf3821d953f4c3000000000000000000000000000000000e920661772b8b737f1a663badead0e89aec4cbb86e6dece5d4db8a673e75b844bfe81662dff671658cb8386c16a7f3c",
+    "Name": "matter_g2_add_82",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627c0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77",
+    "Expected": "0000000000000000000000000000000015930559743b21acaf390b557fb960d3021f3cde80630d8867a063d445f860c8a01037057de1929be16d879416b12a6c000000000000000000000000000000000c6074c54c83f717700f61c5b6bfc641502121b59b196a1f8c5f2945e5db1bca0d7a94fdae96bfeeb6204c8c3f4d048a000000000000000000000000000000000b3a78454479c0990e4c65e4f831606c7eeeaef0faa86596350c9e43e84ae959a0f32c8d03d1f631d9b2ecd046efcda6000000000000000000000000000000000aff797d7572f20b06bac75bcf8cef879df11599ba7f8b86eaa28692d1239cff22841b66e28662309e81a6a599e79ddb",
+    "Name": "matter_g2_add_83",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ad0000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee41",
+    "Expected": "000000000000000000000000000000000351bad2f1fd9adc84280515c2d9e538b69dd63ac93514987ecace75d6bc4585199b742eae0d357d587924333721a1d90000000000000000000000000000000003e495b544aaf19a6415d5558170b8686968dc922367c5c8c212fa1f2785535fe0e71498b98b9a39c8b1f2384956170a000000000000000000000000000000000c7040f34872eea5f98ddc78737dd01fdafe75081cf66ad5c7c900674fa90257105b4f4fc59103dd5b92727a072ae462000000000000000000000000000000001312bdd27ef038d4a89b12c86281975bb34b435d42642fe0732709baf55e9a0ecc0ede8a4775a33e880aa2e1fa7b7ed3",
+    "Name": "matter_g2_add_84",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa800000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f",
+    "Expected": "000000000000000000000000000000000d521781f60198341d116fa5cd9e2b5c2fe51f91f6c8318f351df007c96086f6c3baa5cd2b9b4f442305695dd9b01ac70000000000000000000000000000000013454fc15b1d182bc98d75947547b3bbebef6d5e2d38ed7c67d76eee8da89ea2be19280af4760282fa7576412d5f2107000000000000000000000000000000000d866015c84de74c24dde252542d0d3823f435203c71cda140af235d88f3f4b736e9d75ec32c09ab73bf74083e76866e00000000000000000000000000000000147dfb5f53a9cc61b6788c911dd8649c09cfffbbba368c1872a31cfe3bd6d6427d7b00163d39f8e0b81fc4c40dc60b87",
+    "Name": "matter_g2_add_85",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bf000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c",
+    "Expected": "00000000000000000000000000000000059fffdf2d79b4a297f6912e3035cf0b07db9372f3485150e00d60bbe2e7d86f45b5c2ef062dd92c7e8b1e2be5e9bd140000000000000000000000000000000016acdc57e7231b020268373ddc8b8a7318ead02a8c7181165ab045208409373eaf57ace9a6db1fdedcaa477c7a0ff6f40000000000000000000000000000000012fe630f7de8ef5a129b99faff2de080849bf3b59aae1af042c29b1cc49c8825a4f28c4ccffedc6d568f306416b5bb90000000000000000000000000000000000d86ab3e49ffdc7c2485ecbd00256af83e7f3f064d212ea91245d86ca75e3c7f28b42fa9496a5ccc0514cffc60c9fb83",
+    "Name": "matter_g2_add_86",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e84000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb1",
+    "Expected": "0000000000000000000000000000000012ba9a8fcb69d15eff147f663a5d7927b6f3f79330eb9ee625e0100b146597554debfcf97a3afb51387a73554522ed0e000000000000000000000000000000000a63a990d6454d4db6d58642eb3489f79e517fbbcabc06f2eaa00c4b6f9a07aae97991f169d90af3461b7a62db276e00000000000000000000000000000000000a95203a1628a6ae2551df832f7ab94ffcdbf985e4c9744e244214c8e8b8079af05a9321d1e49b7240c2bdeeb7b783280000000000000000000000000000000001ec747203be73526d3f943e0af814dbede34020144bf247eef9a6ac2cfc83ef63f18a73d3baae18bfd8d5e83d0519de",
+    "Name": "matter_g2_add_87",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbe0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc",
+    "Expected": "000000000000000000000000000000000eefda9046a950c232c6244a79c33e7135d0896bc57839a4f971030220e3ca8196cd0ad75269f3cb5586a384dcd17f9f00000000000000000000000000000000195ce623693996f5ce9e45b4e285adb969e6771e6b0701fb5c95715523c8cb93aa641583821a3b360ad6f4ea1aedcc9f000000000000000000000000000000001553a4d0f965d26fbaba56294591935bed63c84abfedbb9d5c61f3d43484ea71600935fe3c8b6b137d7a9074d907e86c000000000000000000000000000000001673c42c88e4acf8ca38680694b80458f988403a4bd667468506452303000d13649c4f610b738a94ff88b65053731c08",
+    "Name": "matter_g2_add_88",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba800000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed",
+    "Expected": "0000000000000000000000000000000007145ce58cbe48405392edda6022ba8942df055ab582ac402e7c9a0a951cc6a38cd147903f042273e736f30849996cd10000000000000000000000000000000011b457ba464ce818a34a11afc3c0007908091fb528836691e6eccaa9a23ea90cdc746769c4b7ec73efb1f2878413c3b70000000000000000000000000000000019ca519fa6a91cb7e83704daa9b92da9bb70b003f9e9bfe9f323430bfec9b19b01005aa9fcd19d5b1ac59dbdab0c0d84000000000000000000000000000000000ae356f5e5de0d7662bab8d947662bf87d792a3438ed477cf6ed4b27c935b1dd76a5aac446d4dc36db544d4aea40b505",
+    "Name": "matter_g2_add_89",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591c0000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c5",
+    "Expected": "00000000000000000000000000000000135c42c10ef97279e3d152b18cbb8dac11ca8c805dd1d80818851424f592e7522589ec7df6748b5c72d0808399e629cc00000000000000000000000000000000083ddf3843434937e05ba9e101096371fd8fb34f226bcd517716200003ab9855f7aea94980c57a6b933494cc57afc562000000000000000000000000000000000be9215d936a49538442189c9a0bd3be07d4b0b1d14aa45afcdebc1fde17d33b66f7dc36da1ea5411549577f5a1967ff00000000000000000000000000000000176a4a4962c4af75a712e5093ec2cd5cb5c0433aa0657809dffbc0bc02b1ce303ac084f39a5721d482d41412d391317c",
+    "Name": "matter_g2_add_90",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff200000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de",
+    "Expected": "000000000000000000000000000000000bcd916c5888735aa593466e6ab908a05af528f34a7901fb60feb1f51737c73612436c192dfdecf927019724ab2a9b7900000000000000000000000000000000187d4ccf6c22381d0c40c9d7820ff8efe6298c6dad0caa25402412661737cb482dba2719c3a50ec08cd022230952dfc600000000000000000000000000000000164510d4f2cf1e14e039561f1baf82bea678d0065e378d5bb7443fa782e6ab2a3bf7e4ea125d6415a8277c60f5346468000000000000000000000000000000000281f2e28b73eca4db9966456b75de9ae3830c74ac928fc4c36b4aeaaffd47ee587d948f68056df2826ca2775415a53a",
+    "Name": "matter_g2_add_91",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5400000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157",
+    "Expected": "000000000000000000000000000000000cceccfefe04f94e0b67b29b5df8007930665006cb5a59504c3656b8c0bfb52324cdf50fa2722ce15b0ded0efa7fc85f000000000000000000000000000000000cdf34c330c0125f524f0711197639f8aca3e7c435f8c5ea30b78e9622c4bb72a7e584980cb4c3c6ecdd0689daf36b6a0000000000000000000000000000000004b1505d7fb65f6c06ef23aef85b16f3d991218187c5782fb635ba805da463cec9cfdd670c53d680c603adb827a4460a000000000000000000000000000000001104af6bef6482ae64b3b6b39664ec06c39bc18fa91b7b4e5bfcd444c827bab30ef548b28ef5487582d88fbc6d7983cd",
+    "Name": "matter_g2_add_92",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a70000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0",
+    "Expected": "000000000000000000000000000000000e1ef3003fe3181f690224cbc7008856e1251430ce3cff56a1965c89a892604398f5101d1bec7ff1590b0cc3d23b854600000000000000000000000000000000185b4d4b5fd8313c31542bd1bac034046ddc705b41a034a00570181503a6ea4c2d808bba0478900064270fadf3d655920000000000000000000000000000000005bed63ab9898b89f92027c04ba256569e6285c851753e12760129c98899bcbab34b62172906a1ea4cb056d4d0a5717c000000000000000000000000000000000961129a3e212c7412018d7407d7ad16412feba8c138f4f6ba69daa1a25c6b23f3466bfde6f5f0d09ab67248a2abdc68",
+    "Name": "matter_g2_add_93",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa2000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc01204",
+    "Expected": "0000000000000000000000000000000001504c47ab0c410b32d5f1fe3d3996dbf1b21c5ef5aa3a2862a9d561b419f818f0b32b8e931c65fffc393ce7beec70ee000000000000000000000000000000000217e9fddd2551a171a13183ae3aba6bc5ce99e8f3587b92a7cffc738b478d8293b8c71989cabf9a55c5f5077249345d0000000000000000000000000000000003874de865d93650a95af4e153fe557c45bfdc4837bd6e209b8f05ad12b8fdee6432675cd92fd739b7e98e56e7ef16b60000000000000000000000000000000011303c0c7ec1f434cdf07c110da5f0bcd85935c3a0ce9fdf5546ca61edbc2d478562dbd9aa45a5f8d96e033feac2fdd6",
+    "Name": "matter_g2_add_94",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99d000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6",
+    "Expected": "00000000000000000000000000000000101ed22b16502de0d83303134a97db17ce956faedf47256a9ac86004bcd3ed112a71328a58f98a85977a7f22eb1352c3000000000000000000000000000000000e841a88d10493f301af54c5fe07a31ef90de106a6c87d5631b6967fd017f561a56176a5f3544dbb34b9f94040ebd2770000000000000000000000000000000001bde3c0076f26973651cedd3da97c7eda24451bda856026d1e22d3b65c66a3fcbfbf506b4b664b5fc06fca2d712d8a8000000000000000000000000000000000ce553ee3b7d5389798cdc5af8569aaf477b5b74ca1138454dc61badcf3ecf5e0ee8457e374b5735d0b8408b04fdbcdd",
+    "Name": "matter_g2_add_95",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d00000000000000000000000000000000030dfbb89bbe5c14a7a55e68edc4fc38eaee9fb539a6b2f941264c7dc295da5712b0af0f2bbcdb74f785dc9ba038b0aa00000000000000000000000000000000132b4e02fda605a69251a4a6289c47536f9735dd90908ed1fb619b3ab808b3a1f1ca3fcc8f4b35c9864ae311c15747f80000000000000000000000000000000005858ece0bb09e55e012450551025ad2a6d93a15d29619433742851a62d987e7f8bfa6c6faed76493a27060ef5f51805000000000000000000000000000000000dd6b393e6d1b8d546e3f5ce69bc1737399e6ababc628f25734030e10d82b5e9370edfb5da15566d80e23d2fbf8aad5f",
+    "Expected": "00000000000000000000000000000000182f90f5d3ce3f5ff2d91430376144583247def83b3e83524094d57c0f1be98b1c4946964deccc25fc303d6450edfbac000000000000000000000000000000001844806f711735c5ca18ca48e559a9e327b87b91d22a5ef161da7874668130e21a9499728fbc2c88366bdb59f8ced0cf000000000000000000000000000000000815e7cff14b4ceaf26d1cda5c267f432fad294b6baa239b65d886ffb039321f9e24330ae738a35298c6d1ec1ce1c95f000000000000000000000000000000001188a4a2f0920ddeccde1a47a0636aa7c404fd77fb9c828e4fdb5406df80ee6c258c2d4a89dae5e2a2b05210df9100d7",
+    "Name": "matter_g2_add_96",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e740000000000000000000000000000000017032b16be8656cf23bfe0abc8c9e6aade223fa9bea6fe25f95a025da79cea6adf38536eae3859b25ad1af1756b639cd0000000000000000000000000000000010975ed27cefbb43bafad0fd14c87ada8e84525e1d199fdf1e77caa0b718214b33e547a42a040ee3bfd51621a20d22fd00000000000000000000000000000000133d29aa41f92de37523d281eebfe91103f017e5fb390f6bad9a2a4419fa4702bfa04847edbca1da96eb1ad563a92c8a00000000000000000000000000000000014af850de7e800ebee4be1a33c7e3b30aa94106db7defa148568ca3c8d82edc97ab5769ac40162d3728687cdac201a5",
+    "Expected": "000000000000000000000000000000000cf42f2ccff2e0cdda7e5f1d7652680650b4afa523c8f9a554ec18b905c837a189fff73982cbccf903ea492ea902b87f000000000000000000000000000000000d38219770f669557cdb623f2476b5f3f7478422b016123bf86a17bf75848548d1a1ce96a292637b8d52481321d80fbe00000000000000000000000000000000170d8722b824e3291b570ba8e4f9279c1dccdefb95cb5b7a94d27ad8a93513737f12d18ef3153c4e12b530bc457af34100000000000000000000000000000000021aee9e5f578328caee3177a4e08303c3b5533e288dcb75f94992db3520a6da16f4201e60367240b29c48d175942cef",
+    "Name": "matter_g2_add_97",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d55300000000000000000000000000000000185aefe71f24281e5b03dd41e6d6d45fbc8975beb175118de7568bff0a9ccf917e9df97dc26bca16e8da06b0e9a8e7bb000000000000000000000000000000000015b326d401b827fdf556e4a24a3dd6c8036b1c849751b5ae3c3728cad88f931b06e3a345523a723481193f7afeb67800000000000000000000000000000000054ca16b4c87293002c31e64ad303e8f040e11de8b45c5fb9aca9dbec59b29dfda8532a8ef5ae6a92ac8ea90ee4303e0000000000000000000000000000000000b65a233a7731366cf24c801724265215a8626b1290d86c60bf1e74b021b0b44d7d6552f936fac7b5e60cf1feaa1d82f",
+    "Expected": "0000000000000000000000000000000010d1b2f595166929347e06c1debefead06334f554dc31f320cb844abdb1810b5f7c4b933ff8072dc03d303f4a6d0d09b0000000000000000000000000000000013ab41dfca0a7cb0c58c2c19e02f675a94d9e73312cfe2999dbac34e6a80bff9472506b48690f24ad3171ad495f445420000000000000000000000000000000015bfd0db53fd4da538caa3aee7a90a669cb84460365696ee79b190d09a6d4c3f08965de7fff4efeae435db52b97d213b000000000000000000000000000000000182ffc4304b911b47b092ab678edd63ed5f5e8a9069daf9247f3bf9c0dd149cc9992728a13b0a236fc9b37714b35882",
+    "Name": "matter_g2_add_98",
+    "Gas": 4500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db400000000000000000000000000000000085dd8bfc00ba517dc8d7ddb49d711d35bd36f9fe3843689019e779624a032d2f023533b8184b73042d1a1953d2885e50000000000000000000000000000000009ba8d5d36e6efe02097a3206bbed68529f0cb9875ab81deafd886d9243bfec8b403d2abe713a2ec929b93305dd2da220000000000000000000000000000000007f8f90ebb2771136a92023901ca85e87fb7c8b1a40f88ae564a124bdd0ff0bc27ea98612a817e2c871fb4bcea3bb06600000000000000000000000000000000152de417d02f1d14e5899201db8fd5db8ecb40ea8d415dcdedce8ac70c28d851db68e9aef94506a50ec28145547a2d68",
+    "Expected": "0000000000000000000000000000000017555399f979745302f08210de5311a6401b6b181100b3bc6b6d450f0f62079d2f02d7badcb164f50dfc46a975cbd6720000000000000000000000000000000014aea86c06e4c1fbf0711a8cfced2544c7624abc7ae7906cd992bdf575a702540c45c2117e221446ba09960cbc9048ac0000000000000000000000000000000002fac56960c4989a84e02ce36e8970c2e847ee45579d31ca77f042bf96505af574af822da084ae64b22ff876610ba9a5000000000000000000000000000000000a481cfea2aef8975c80a297ce5a185dacd25649d41f8466d3c63d786e3c264a8e4ccab5ef6b80ab1260e86ab6d5b3f3",
+    "Name": "matter_g2_add_99",
+    "Gas": 4500,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsG2Mul.json b/core/vm/testdata/precompiles/blsG2Mul.json
new file mode 100644
index 0000000000..886b0c6adf
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsG2Mul.json
@@ -0,0 +1,730 @@
+[
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g2mul_(0*g2=inf)",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g2mul_(x*inf=inf)",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_g2mul_(1*g2=g2)",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000011",
+    "Expected": "000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d536944",
+    "Name": "bls_g2mul_(17*g2)",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e",
+    "Expected": "0000000000000000000000000000000006334ba1e361fd94bbd98f44b75ae9ec00ecb4d3467b5528870b1a1fa9a7d04449f12af90bd4c7a1e3f29e717d6d19d3000000000000000000000000000000000bf4cc1626393956915845ea7ca43d30a59c7196fbe309f2d5ee6de7e40c191d29821dd6aae46abecf634b904de8f7490000000000000000000000000000000014aeb09e252cc74610ab956057d4ac5af95cbea8a6baba9e5062643dc037d6841044cb38b22d7dfb978fe0b58f94cc3a0000000000000000000000000000000000fdcd73452fc1ced1c06e6271410a48dea05afbe889a692905e1baab8d72418c62531aab8b74842b51016f0a9cbb93d",
+    "Name": "matter_g2_mul_0",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d",
+    "Expected": "0000000000000000000000000000000010e70bef8eb893377e7ff92168d7acef11c9efab990fbded728b173b94e1d99e471a8357f16625d353287086543551850000000000000000000000000000000014043c1f00221c439e5febd12724a9224bccf0389914461644daf329208e869b1bf149880dccebccd440b1748d15e944000000000000000000000000000000000f7dee1e7d122e410b29a9eb011ee700c2f230cf8f611e196ec66e153c1fc331175532a8f9b060b573bddaa705430c2e000000000000000000000000000000000e1f659470eab7c0741bc8777ac9fc8dcd11a6f1b30ffb4265e96b879e795a4dbf851d1149429dcab95464e89f334627",
+    "Name": "matter_g2_mul_1",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1",
+    "Expected": "00000000000000000000000000000000119a5147fe9ddca7123f721b5662c1a44b0964c37a214cdf3a4fd34166e3b25210344e65220c38ec84d0e3b5ccc7e46d000000000000000000000000000000001642dad5dacf4295b871fe9b2787f0861f158807b2b6c01c2dce12ab053c9472bd3cb98de5dc33f40053ff45ce5c9af40000000000000000000000000000000005bb5761602b6639f2ecaf79f2d1f853fbdf75f4b3852b90808b858993a83f8a0da8a2ce7072aa91e3b6b3ffd0b3d1e20000000000000000000000000000000000a75143b9551d4ae41fb8bd71fdba7826b994c65904d9189a5ac5130a59cbb9d8dee0e016735565148fc49823d3969e",
+    "Name": "matter_g2_mul_2",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a",
+    "Expected": "0000000000000000000000000000000017ebc9446f8c8e17dfeddab9188d0c808565da29c0bdbbc4138a44ca3196c4564853be28286b66660cda36832d6940010000000000000000000000000000000007f29a9583b4ae83d3913dcd72590a3f20f39eb5a6d36663c1ef433058e76550085b9c01bf797d98d0eef45cc22ff8c50000000000000000000000000000000016eeaeb123b12d1913ff1e50f974228c79f2b995609d2e3835c8e1d68773b0cd484df57b86111cdb75de1e19eaf062e500000000000000000000000000000000002f5688c1286aed42309896bd65d1826dc64dda615238fa9043669806968b8e0e1e3e77ef192b7df540aaf0ed282a9a",
+    "Name": "matter_g2_mul_3",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89",
+    "Expected": "00000000000000000000000000000000042d0c1941ae0ed5e8787437ad5e2753bba02185317848e8ec2e425ac954e0efb1bca534725adfe87e8507851ee337af0000000000000000000000000000000002db55ae8126cbe86327aab880381a81205e33a351d172c883b9cc184799866a8db5a6b4321496e05d3ef62d00416d9a0000000000000000000000000000000012c45444403dd62d7be3e7658dd85909204751dd7d085f6edd38c0aa9185d3c32407d8c95bba371b380f788d0dc48e0900000000000000000000000000000000111421c6dd0db595ab731adfb4bc76c84a61197cb023b6f17e7176c443f20a4b6f8cd0a00cfa61e831ed20b3c6a84d98",
+    "Name": "matter_g2_mul_4",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944",
+    "Expected": "000000000000000000000000000000000ccdb2a0b670f199a9b61198e6a2ce2117075733e6a1568c53ca493dc3674c6ae85be2491d2ed983f52e2c7040824afc0000000000000000000000000000000004f52450d7e041c561c00200d5b142b32f2df2e2156e4f6c15d6c00e185e135037a1ed6be15e2ed920daa00e2f9bc8da000000000000000000000000000000000f39c38c18f03ce6baf1d016cf32d7387269940280f2e8d21db4da33dbd2d24ebb93ae3dff9f79b015eee25813d677c700000000000000000000000000000000189df61f7f1025fa6fdd0a4708ff1d53db7d414019c4828de2520af3d36776062350061c2261e46e746a6475fdeccb2b",
+    "Name": "matter_g2_mul_5",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1",
+    "Expected": "000000000000000000000000000000001388a59c57ec8ca5e68b99631abdafca1b71352ac35003a55bbc415b48b8171857adda31123ec86a6ed9e1060d56aa67000000000000000000000000000000001471913b1ab5bcf9336665d3d44232b4e58da70285b7b8eb1dfd7c54442afb28c339f56e6389f89b84db0879e1ee058300000000000000000000000000000000022101b4de40b7180ea17bb36bad0a668a8def3e7361a96fbfabcfc4cdbe6f607ee4ee80d0eb2418b848ad056520092900000000000000000000000000000000103cda694792af5a51e04b6422600a0ea6f50808ca54423cd4f59dfba633daa5afea49c85b900f52e182610efb62fe7d",
+    "Name": "matter_g2_mul_6",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c",
+    "Expected": "000000000000000000000000000000000cf5cb957a752ce9187940f63b13080790348814debf84b91e74fd6e822c2735941d61d50d492439475bb3ea7aa849ec00000000000000000000000000000000012e546ff33dee9875510a68301f46d89e6175f5cd9a6e179fb8599a580e9478fb8d92038982551dd29041d8185c7474000000000000000000000000000000000d52fb57bf2996dbbacdbcb4088df38e77e25598b91bcd5e41eaa27b1398eac150586b142f068d5b498e0ce458d3e8950000000000000000000000000000000012295e1d1039abe7a5fea51a04a34e9e8d44a0f24b8c032680703c119d54274d3bc2e548854021ab027b693e43964314",
+    "Name": "matter_g2_mul_7",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108",
+    "Expected": "0000000000000000000000000000000008e4c57309339400ac9b6b5df16972c272d47cf69ba7baf89afa4f4e72703999c5885253cc35686f6c8d277399da2a390000000000000000000000000000000018ad4e1f105f16b0dbb4eb089c51e709c25e407e54b64346224b1abbe15d62fabb231e36a69eb05a9ba7860f772634200000000000000000000000000000000019994d20a7ecc0f234ccb6b1793fa7d1ece64b3e157c579fb05a8c6cfcdd6f5456ac1f4c1beadb69206988ab543bb8bb000000000000000000000000000000000d435e74bed382442ab83ec90dffb91336137932524bfcf9753fa5ddfe038d0b98a045c8ec9deb53172e5662d3fd67e6",
+    "Name": "matter_g2_mul_8",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672",
+    "Expected": "000000000000000000000000000000001425890b6c46c5a07a79127de4ddbb751227dca4481ab7c2f601bf22b8f6a149767c73bfbf57ee399c0f2d0b12852a0a0000000000000000000000000000000012cce15f53fdfffb5f71de3567b0c0adea65b9321c85677c574787f7048c1bb5e2dc985b65fbc48115aa129e6000fe4100000000000000000000000000000000041398497f975289fb9fc6ffe671a19fdcd3753c82ffd3b2084574107bf7fadc8de462507f4484c32df39967c3751a480000000000000000000000000000000007514a7f246006e714d4a8cbb4e89d81b951b5c41a05bcf35f61283e888074fb3686fb6ecc1a66e491ea1e1ce0738102",
+    "Name": "matter_g2_mul_9",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea",
+    "Expected": "000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0",
+    "Name": "matter_g2_mul_10",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76",
+    "Expected": "00000000000000000000000000000000048ea2c854a0df7b10a2147db6eabcb16eba340644f737fc99663d1ef26d8ed688c2baaa7d7699c5f540d7605eb48485000000000000000000000000000000000c959efb835d48d3e7a8ce643764f27c365f6248a88e39092e3a6498f04ed851c55b796dacd62ae73d7edf23aa45fefc00000000000000000000000000000000114337b8caa68cea6f22a25c0ce3b247cadae24c63fb02c6a98a728b54f97b12b1473c8e23f55338326b9575a637bb2e00000000000000000000000000000000033167b0668ec650581815cefab61d13661f4cbc6e01711af0aefb699e1979b551d0031c603ee5f6dd4f716ea7aa4a6e",
+    "Name": "matter_g2_mul_11",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c",
+    "Expected": "00000000000000000000000000000000142f6b71471f3665ee6269cf598fc3587a62523f9753eec48a2461a2e313e376828cf6d1a9ffc9e64353c8a668718736000000000000000000000000000000000153647cc4a5aeb8ea52f845c415651e167ace9f331c1d73eccbbe20a4014f9e1158c281495206de4b841839438a595500000000000000000000000000000000151d07c3f83217e63b332a6c47e91ef2418e9c658353f8b644f23266f5fbc727562f0935b4d892db947cfbd0757ed61500000000000000000000000000000000035bce4bd2d8261e21476c325cb68e581f20513eb5e0e6a0ddbfd4ac4674bc323590b6f52d0cd50010c13642e7e03daa",
+    "Name": "matter_g2_mul_12",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a",
+    "Expected": "0000000000000000000000000000000014e83f87e7f66d8ed880ca46a76a5d3bbbacf2dafe1ee055f04af568738f4c6ddf2a93e1810b616da6f64f25c35a7b5a0000000000000000000000000000000003d14447254b61168d36f92710f95f7100cc8f278b0bc9528da763a18a5386b3f5b83b96f4dc426e4b0fbe755bc986790000000000000000000000000000000017f1a79ed64abfe5e960fda02cf3330e6ef5612c1b8639386959f86c970adb797bf077a468273d37996a65685f75ac30000000000000000000000000000000000d973499a7bf7132541c0976bf2e9bb26a2b6cfa5bda720352fa7a180a6b8fe95befcc13de5a2efe58be934cf7d8e664",
+    "Name": "matter_g2_mul_13",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054",
+    "Expected": "0000000000000000000000000000000018bb69dd6db0beb468242265c382de5ac342d465b5f72d4e5a24c67a48272d9a1f3af28e0bd3712e16a854c5d91c616b00000000000000000000000000000000072fbcc86b7dee9c2dc177dbabdbbbddb630c98ac3bf3737fd22f99e2b2b690175d9c5aa4b577f78c545dc6a5d2d03c900000000000000000000000000000000161c4218143ab1f0387f19bccdcd08f9caeb2d1331ca890741799ff1b40533076b6a96a910714176c770b25d2c17715300000000000000000000000000000000063098cd9d1eeb899724b40a2d10ac951ba0277db09aad639957f58541dd391fffadc5d97833bb9666b054e12debfa92",
+    "Name": "matter_g2_mul_14",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746",
+    "Expected": "000000000000000000000000000000000e43672f1bc25e7e0e64a3fd26cb246bdbd6fb5c9084afdc87c888634916e6a6cc9a351cc67a6ac77ab8e132ed6cbee3000000000000000000000000000000000dee9612527c8ee9c574a4c51f5d3504ccf1d5781b59c78ea15294332c6acfdcc7bc68853e70f1f72524c930e4c3d2eb0000000000000000000000000000000017eba629eb14a0636926275f1c2109318ce8818d8171c69fd371751b6de47bda5b00a0b0e3765d05bab7b8dea9add90900000000000000000000000000000000052f0a4cd9b91695e1e58ead1da1480fef08cecef63896aa51ab16da373b99b3b91767a374645ac5932d9c7fd21d4636",
+    "Name": "matter_g2_mul_15",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf",
+    "Expected": "0000000000000000000000000000000019b7ea673dad96c8352870136ea262c9ed105550cb403eb1e64ad598b2145fe1b95e5d61f1b5a6ebec47568c67b68086000000000000000000000000000000000f06ff9bcf2ba284e705b12ef2311f1a9b867ed742ee0737567b5c878547b18394b82c2bb97e16586515728245692cef0000000000000000000000000000000019dfd2d8fc4f2c989c7e1016e147f336174c84d380bab992bf1adbffe96d93d4d2d1d1dacdba3adfaf283b184478229800000000000000000000000000000000068d230422006004cd88ab0dd46a84af3905c7a1d329446cc23c1c5adb401a86a9fa76aaf577f77c2678cd8de8685ed4",
+    "Name": "matter_g2_mul_16",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da",
+    "Expected": "0000000000000000000000000000000015ffdd83355978ebfc386e13987effac0137ec628fff1667ede29cfcbd05e31cf8323959dd0247c20cf28978dc242c790000000000000000000000000000000016b1f810da2ae3c2ffbb6b83c47ef03eb0f298ff4c304ab0dd7b97207949d62858458d789c86c0cd474c34fa720ad3b70000000000000000000000000000000002a2e1463d5e795e6a25998a848b079363efc7d0337c3803385f4f17f11726b04108adfd87a811d709cbb6750c969526000000000000000000000000000000000289a3f472799c06a84bb1f377a36bad910220e1017884545159fe1b2505e8e7473882fcf324ba0d9125495bcbbc7226",
+    "Name": "matter_g2_mul_17",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833",
+    "Expected": "000000000000000000000000000000000b02ddcfbf391a2d6953261c786945093b09377352473a86cfac6456a811233809434b566b9301eea3105eb86922efcc0000000000000000000000000000000015430deba91113b841303120f0738012d77207e9408474998df5e68d0d61f1a64afb947ff93116ae766ca5325046e263000000000000000000000000000000000ab7094055919f6f707b458cda552f25104d95e4ec8d020ea4c17ac1d7efef5c4c3a769120718f1d5171eb8630a3018200000000000000000000000000000000161e7209f8c98e511a698fbf01735798cb632ae1afe00870654ffa0ba93a549edf4b97d60f03974ab0964cd39298401f",
+    "Name": "matter_g2_mul_18",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f",
+    "Expected": "0000000000000000000000000000000006cb218607a1f66ce361c89fd20edc3f00421611adc9aa52ec35d45e023174962c863f740ac36c984c2b466cfc4827a900000000000000000000000000000000152b22d46e9660da8b1be4c5b14da613731e750ff7eebaf879f7074bf3c33e1528a2c8479e0178707e3855b49f85f045000000000000000000000000000000000c928cf78cee2c8b9da8215d33d189c5636df1e8e9bdaf143aba7ed40f29490ca2328b4a20cfc56f62e4ce49d9e77f14000000000000000000000000000000001574b7a9c3931933160ad4eb17400b6297210db47bca034bc1b5d17a0cb8c41834636b9123e625e5eb0b01738cd6b9af",
+    "Name": "matter_g2_mul_19",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc",
+    "Expected": "0000000000000000000000000000000003e17452a80996203fdc4037db072c452f9eb2dae689c77c88b299d7ba266d111ab2b9c4b24149968d72cd143a34fc4e0000000000000000000000000000000014a057d7a50c9b0f34712ff8008770080bfa671650fef43c82726257da180dfb9672b266d4c54d65fdc677d917e6c5b80000000000000000000000000000000013b452c980bfc4a484637b578be100753aee9dda9487d5ee5c017c689dda838fc673804369328192d780d60a9a3de0f700000000000000000000000000000000103aa86d1807de242a6d4fa4a49be6c91cd757df5808501acfca44940733c6a524b851ac962b99a9be41bfc8d6254478",
+    "Name": "matter_g2_mul_20",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52",
+    "Expected": "0000000000000000000000000000000007c616472f9ac60f749979c6f870b587425d514395ed07558ed287fccabc77f0c90872f3885d0780bcdfffedd124eb3d0000000000000000000000000000000019531e9c25e84a2a968a85d9f1ab61a372ebc59ba5bb7a2bbb3c0d6e4c9d04061b28fdc719735e97ccd5f7243a58cdc70000000000000000000000000000000007772d3cff12bbee916a6569edce0c6dbc2bd8a794919a4dd7bc37024c8273245210511b8f6da551fe626b7b840833f300000000000000000000000000000000186a3e858a83a7ea1bfdaac65c2df1076059aaa193961559792373886c68acd2f9fca61b166a0ee55084a6ea122ec3e8",
+    "Name": "matter_g2_mul_21",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1",
+    "Expected": "0000000000000000000000000000000008adebaa95d10b9fc0f1a1f0d52dd6741517d2ba23e3f9e7a9221039684ae226ea602dbb50df0efd44b2b5bf7495c0b50000000000000000000000000000000008e276e78ead2473602d37cb9f2f589f9c60514a1fc5c215acf487bf57c935467d29945d3d671b41a8e47c9495dbf5c9000000000000000000000000000000000fab06240cb8cbe9afcc4ebebde50c2881e4bc4d4f2ed09a1065e3620e6344fb3c5f3019250ca4edaeae4902abb7400d0000000000000000000000000000000003fa6c48ead374be1dd45c8417ca8234c15ddefc5039151e6cd7fb27f866e134cef2f59ac9b2ec1b26896eaec9213549",
+    "Name": "matter_g2_mul_22",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9",
+    "Expected": "000000000000000000000000000000001412bdb48546014adf3c4eac4dbe79ba700f90c8030b063828fb01be5977bd73107533a4e8030c8d9cbdde9bcf10649a00000000000000000000000000000000126d3e1006abfeddd810cb1e12c898cf5f543e414438e600ce4c94cd8dbd1e17c0f3b9831add397feda74362eeace6fb0000000000000000000000000000000005b3159638afa34f219513cbcbc51567b16fd5598b85e6ae0d232021133cec25a6269250df2ab7b5ace726e9e2fbf0b0000000000000000000000000000000000c35bfdd1c10e903da6d41e9afbe65b0cd66addd7893fde41dfda8e543a93938cdeab52cc9bbdbe61f93d651bd1c923d",
+    "Name": "matter_g2_mul_23",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b",
+    "Expected": "000000000000000000000000000000000bcc781f144bc148687875789fd8c54dd820170984b6f8ae75855f7e45619c1d2ff85c330b7743e447b5fc831dce9277000000000000000000000000000000001409aaf3c94c9a6b5123c82a7f311af7c2f60e9b197d49fb5b010f84faff972151b383a83c106de43454f8097005f6c800000000000000000000000000000000064a91226da8b9cb587030f1f4afb0d422a51e4d55212f26c621abc06fc0c57a473a9be75518a5f4f9a7f8d4aaba69830000000000000000000000000000000002cf239343bb77865ceabfcc1fe34cc9be4a1ebc3a70f16f8b7cb84eed5843524f95673b01466d6cbb0d8d9dc00793e6",
+    "Name": "matter_g2_mul_24",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6",
+    "Expected": "0000000000000000000000000000000006bbdabfe104b62d22e78bc8f3446a86cd5f10c4c5a54501140768b55a7e6940b9952c9a90a14d8fdc7c04600195cd6500000000000000000000000000000000172e718c926cd393bf303984518432693c304a2758174dabba303ff4c0289b5bf5376b61e8821abab322d53e88f71d480000000000000000000000000000000000a2f84fbdb5b05107a0a340e81b56ddf6d03c23848448f841dc44f07cbf8a575289cf6d53986f581fddb0f2d07e38d70000000000000000000000000000000005cbc10f143a9a1fe23f670a4c47d385f5c7069d8c46580322d6939122b2d39d185d6a8c2e51e88a1d40fd2e82d08b8f",
+    "Name": "matter_g2_mul_25",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36",
+    "Expected": "0000000000000000000000000000000011769e191fe258ffd1922295a9fe877ad5a52fde6e343730f8f5ec6cdcd584f8ed1dbe0f55b5dd81f5f78b7437f02abd000000000000000000000000000000001253689089e9192d10a45342214425de36740c120e49f596d24658941ce2b2ecfb50e879be0125e3d159088f88e234f10000000000000000000000000000000017b642d1b5a953f47fff8f0649263f16f41a0ec0397d5a81571174aeb85431c352e2bf6bafa6894d2e6cdb5eafff16d40000000000000000000000000000000017b3438d0ddbd2ace1e63802013b5bac00d31889dcb2d9653a6f6412d157aad2fc45267322a62129087380bec65ec169",
+    "Name": "matter_g2_mul_26",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc",
+    "Expected": "00000000000000000000000000000000089a07bf63b8029e0506393828d8593b94b73c750815552f9a3c74ef7470b5810bc27212ba02ca6fdcd97e1e28a52a1e00000000000000000000000000000000051a93291d4b912f0a594d45c0264a9073663a9ec75e6ee81e13e79383d96e9330bab845fd1e5163e5b28c41c4a854c40000000000000000000000000000000016610bf2b2006207046e489294a132937edbdf95caf508f0df3bf8502e641aab9c44903cde75cff3c1f86873e06cc58c0000000000000000000000000000000005d33669fd8a6256dc55f513bb93cce8bae62a593eb8903cb7d7902a7727efb8fb4bb2e5058441c30b99f146ff5394c3",
+    "Name": "matter_g2_mul_27",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792",
+    "Expected": "0000000000000000000000000000000005aa23543088a9a833d773a71275e73fc3081e13c907b8a04a330df7d6c06618fe69e644e0ee55869e364d3561e40f300000000000000000000000000000000010eef9238d2c520f32243f07161f3e35b15fc949b9401baa1a9c5df7d50b2cb3bdd237747735b235862bb57322fd9d090000000000000000000000000000000012dcc16496c95e39ecfd8f0514b5ab2569d89826d957478cdecd4e827095034e974039b37e767a0f25bf057ed715aeb00000000000000000000000000000000000d0593865fd2172ebf1b94c7511ab7d433a276bf833515146adb6d79b6e09d7c18f4c7f4d3241c14d01a4ad0f31580f",
+    "Name": "matter_g2_mul_28",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c",
+    "Expected": "0000000000000000000000000000000015785bae0c27680cca2097ab52306207a61ba9903723f574091ef5e57c2e871e076d7f46e6e39f65a01e183e7bd822f000000000000000000000000000000000071110a384248664db46f21d87b455a3ad3c43782c68304ce17f52cc8579fb2e3378995d6eb3b8c97665e5fb7de665fd0000000000000000000000000000000019153a01c2b3c5d481474a71e5c67f27fae3232a0c8f1655ddd4da6b4c79870bfb0b6beb4af8c54aaf7e9251ad41d639000000000000000000000000000000000c58375439a93e0763467c6a11dada3e579ec53a968c9b9c1a446cf3224ea0c89c9ec218a8b78de91fc12f087e722f94",
+    "Name": "matter_g2_mul_29",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa",
+    "Expected": "0000000000000000000000000000000004c7495c03fc3fb4d0fd4e0e660d6424de9e060eac72eee3608ba95bac294a3a62d246f42dcf3b575ee1cf8e20a9106100000000000000000000000000000000091140aee42a9dc875f87f3ba29beff95138790140f8bb522c6c15281b3545995f9c13b0b73ae691317e674295db6526000000000000000000000000000000000a945a215b2861427e0fbbfc6fea04e79edeaa1eb87df5db8e5e017cf98fde7b8d5a04a1b2129a4aadd2e3924ecc0bb2000000000000000000000000000000000a43f8d3d92a03b7bd4c8a34ce31729ea0b8e6b051c30241dca2db31a02b6e537071a914d8f0876f944dfdb613540c6d",
+    "Name": "matter_g2_mul_30",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c",
+    "Expected": "000000000000000000000000000000001821e14e70e12c7caf2a1ab651eb81dd61c4e1eec9a02fe4124abb865a7029e066f03b62e6ecfcf0fbae5151272b524f00000000000000000000000000000000044ac4a7399d6a67e7ee8cde3f5fe20b0a745462c870926f0ce8554061eba5bd62a8a08c798d8bfe30fba5567d47c7ec00000000000000000000000000000000178b8f061ad9282b3b2057f20c115c91df994ac40aacd05b7669e934bc7d650a0cd88f9fe17d7b766e34bed587ead58200000000000000000000000000000000188311eea279ddcf75f8dd82643ca3efd560ddbe6c8f2696cf7da03e65cc90d97b9f9ce99e29269644d8b881e624cca6",
+    "Name": "matter_g2_mul_31",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd",
+    "Expected": "0000000000000000000000000000000012496dd3c1278b55bde81f6944c4bdb71869f5e5e21db7b1425ea32fa1dbc8c301e7f5e68cd7629c91650265d1361e690000000000000000000000000000000004a1251591efdbdbeda21eb89165ca61a2e090a73426451b6933d939161364c4064a67a90f859a7713fb6a9c5321d5a200000000000000000000000000000000163bcd07d030fd6ab8a8e0bf39b136dcb34f03925c3fdadf55e94a90bfde0ecde5c51d2f4d06954aa6a96c913f2ab4610000000000000000000000000000000016dc065a852ef9e038d93cc583b4a71db9b96a7e7a819dc530598f1ae256368438f52e4b709f15f56279b9c7f9db8785",
+    "Name": "matter_g2_mul_32",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45",
+    "Expected": "000000000000000000000000000000000a2397fb3a3891d1703eb2112357c5fb8acb070ba9f3a39050be6f05b49b8d2488e94adfbf849c8b4a42e287077e9fff000000000000000000000000000000000cf2c02a97addbc1584091e411f9a07135f1fcf989dfc8ae29155ac90b214ce20dc11a1fc75dfb697694891d934abf0f0000000000000000000000000000000018fd4af647bf0456aff9ef80969613829f8eb837205df552aadca46bc3bf9838e0ff2515d3fe869f80d78e2357091d8b0000000000000000000000000000000003c5671ea4723498359f29d49ebe974099da3dd59d21065a721f7a4f14dc7fb1de3a67a707bfa4bad7058312632c6113",
+    "Name": "matter_g2_mul_33",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b",
+    "Expected": "0000000000000000000000000000000000676bd7ce63d8b58cc1e5399ced9b495baba4cef9503c44760f92d6d9e092d6d5308fa88144491eda6c571a8c308786000000000000000000000000000000000605cebb4c20bc9dff0258f75a825f55f23a32cd0804dce56bf3cf2f19a3504f0345e0f1b839d4d5920aab19b363ae19000000000000000000000000000000001512f95f60a6dc79dd9261c321328ab8e22ff314e7582d8de83aa3bf280805cba8ba6d359a620fa6f0564396a45ca9760000000000000000000000000000000005837474ba78e0700c77141d70af1d8fb95a97cbadc95996faa93c2e81b7c8877d08d5287f83219a24bc0080e630e39a",
+    "Name": "matter_g2_mul_34",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca",
+    "Expected": "0000000000000000000000000000000010b2a9b32e431c11ceb474942bbbd6915a3cff64a74d67570fadeb7447c5abcf1bb35c822d4441565322ebf75e61f64c000000000000000000000000000000000b75a0212232af0a59440482a1f953cc29bcd35272ef407925eccd70c1dc4705dc1e97d2da604996d3c52155d05d77500000000000000000000000000000000018751bc59f5907cbd7f1d503bc5aa266f4109fd3133a1c4c2e58e4a17250a40053b4489da4825b4c368b0f4947baa6240000000000000000000000000000000019b41fa1af9488596b09c587fc33e044d51674eb6087c647d5a762d85e38a587eb5482687d9346a1a701bd3a8bd36a61",
+    "Name": "matter_g2_mul_35",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc",
+    "Expected": "00000000000000000000000000000000054836eb7ef9edbe914bc16d1498e0bc3c978bbed2518802c2f8e1c0b59fee482cce0ae8e805c33861d4cd595f6b8bf40000000000000000000000000000000007dda36d55aa7a890aeaecf2528a390c98d9ecfc8a5c78c2a6def30de55b90ae408ab770cf9a9a4663ba601c4f5765a00000000000000000000000000000000007ff7b24c8ed9fca572069e72b1e93978cea87a0fac7ba60f54aa573d881f21b73012b010e9c0fc9324aa7697bae0c4a0000000000000000000000000000000002d9773bf294efe64021e755e4dd2936a5060bbea5688b6369ffa3b94eadcc58cc3986c74ff365301be1e6c785939b69",
+    "Name": "matter_g2_mul_36",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447",
+    "Expected": "000000000000000000000000000000000902c1082ff09bf93b91c9ef5e447bd6832fec9297cdb065f11fc5ee626e6e8834cb5d74775c586609a0394e6114e8820000000000000000000000000000000018e414a40c27430b98246fef556e74dd3dd7adc601e3c05b79f8c29169780a173be9a725df3318d71b6e82abf97930bd000000000000000000000000000000000f924fa88f43c86ec98b34379b9a649c7564ef0dc596c95df19522fd50fb3a37cae031e891a7a7aa6a5e6a9062c3726a0000000000000000000000000000000006bd3340412f64d02d0cb3ac44d1f31cdb1906e56dbfb66d86b60a74cd26c1e241963fcd8bba4109c428db0bb083e81f",
+    "Name": "matter_g2_mul_37",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828",
+    "Expected": "0000000000000000000000000000000001415fbd8afeeb5796460a9095f14a8f3f6fe0374d4cc4160f030710a6d4d3a92febcf4dad770de3a3ba1a2efbd858210000000000000000000000000000000015792220c7e53262b56224d230a8a4b32019c77548704ec16da5ce167854305e6cdb9924c248f222d6fe95a8383af7890000000000000000000000000000000001694329d8e0f41256b703a8bb6548f1d9e0749a55c124c9b60361b4cb1daee24fcf272327ba598022a92815764fc8570000000000000000000000000000000003350658842c5b6fc5561a14df27d950a00c5bcc13d6d9d014bfd6dc95ec1a030594625f41d439b90b05275a0ffefdb1",
+    "Name": "matter_g2_mul_38",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a",
+    "Expected": "00000000000000000000000000000000054c6cb26c8b0a9a4700e0b95348e6fb1190c577eba03a44e84fe7744c543321d02c4d8f55c03f984b44ffbd899ac53a000000000000000000000000000000000e7ab8da5d573cb88a78f6a6ad2b307bf867777f79a643b6ec89d9cb208711c85d7d2cf8f8ac69a8b322000fc7866024000000000000000000000000000000000fbc5926b9dcd9e4d1ca1a2b43dab5c98aa20b37aff0868c54441de44eb014e5283010642717fafaa95000f4313e14840000000000000000000000000000000003671ee05bc20bead72f2306203dad55cf20b13d3bb2cca079bf4391411b85ed4df55e1426645d73b6935889d4450c58",
+    "Name": "matter_g2_mul_39",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb",
+    "Expected": "0000000000000000000000000000000013fdd394635f42a926a2324b8cb870b5995772ef4e25ebc1da41dc5bf724f747da8d95a28dd703b5ed65ada5555c8b5b00000000000000000000000000000000118fd550962d1de8f1e60c312643ec7cd306f0bbcc932739270595537c8d290ca7e20b962fcde570bd2ed7ea43009fe70000000000000000000000000000000018b25fef4b75fc7649a489d078311dfb6da9909f472de7bd9bee9c3ee353f345c83119269ab797fabdbede41e0fe6169000000000000000000000000000000000b7c2a73741f6944ef4ce8fa20b2900612645c224818b7faccf6597827fa07f7262295f42be5f34a751a6400495f7eaf",
+    "Name": "matter_g2_mul_40",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425",
+    "Expected": "00000000000000000000000000000000177d29de8a81db2e515d4241e5f7e3d35de22bbcf9aaa616b057cbf2dab57ab8d98213cdec82a2034964f3e1def8a4e3000000000000000000000000000000000a0cce8113eecb064a60ee2c470dfae8b3921f8da2c7ad8dc918b355ff44542b007add28a44848fa8d8f8671617431ff0000000000000000000000000000000010470fcc723286327e951e758fd0474de394778d0c1ec5fe6f263dea1957c60f05dc8f9d82b3c6a7d73b3e783f35ade500000000000000000000000000000000098a6ed331f03da7ccc9148f07b19b132152e15d9fdaee5cc092524b33795edf2b458b4e8383c5e29affd3f025094033",
+    "Name": "matter_g2_mul_41",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96",
+    "Expected": "0000000000000000000000000000000018a1f1a60172a65abc8f2d855ee7510c1e0af9bada084325027bd493ae86ea2c62c15ace7f63562a82cb80ee7095661b000000000000000000000000000000001736b977fb52eb1b466cec3d42df7e89047784f0e8362eb6425e37adb1e84d0438f5a6e82c7b31d59b0959a5f4aaf9310000000000000000000000000000000013ea0f849830f8e48161e840295637d8596b32eb576560289620b797b14bd395d835e8140b69039c904ef1d07a82127b000000000000000000000000000000000d7f58873701c138cb7e18ffc36cd0e47b07d70448ddd9fdc4b947003fb29cba0775916c752d531e527ab744c277e5da",
+    "Name": "matter_g2_mul_42",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc",
+    "Expected": "000000000000000000000000000000000290fb3f38937ce4439ceaa21cf3b31db8a22f9f5ad9db0fd7d38ca978192bc05d41152f8f86ca7b2ee0bb58e125f57f000000000000000000000000000000001775913fc24699bf08f25fb946fc6527178ebb821c654b7bc69f6f86b5168fc42057a5d3bfdc53b3d57fa1ac05f7a0930000000000000000000000000000000017b9043cde8dbf500ad90463250a49f56b35713f2fd9a35d8391fc36c78c083e39674592a98cb857194ef9e73a62a397000000000000000000000000000000000e5e62e39433d443e7d2d32754d2ca2556cf6deea45e5076ac040e3d6de14e9965c53f8c65bd98ae7d17ad3a26f3accb",
+    "Name": "matter_g2_mul_43",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca",
+    "Expected": "000000000000000000000000000000000d9927347a9ac9b0290e68143fbc6a5f4476604c3fa5ae87e729a03ca055e4c6543f9245a4592e195180d88781e46ac900000000000000000000000000000000175e0ee8de4002b18f32f70f1bfa9e0be87288cddf1c436428c2969884112bef5db19e041cbaeb23596e25cabea3777300000000000000000000000000000000074ed9e981818102b9ba818d478ba27033eb38e3fa19cdeb9f5820e59a64dc451342a160359c54bc8ec7d866b62080ef000000000000000000000000000000000a853930020bf01e20816d3aed242e00792b0d0e78fb15403fc3cc255f0dbd99ea6ae1d59d5978e562be4862b3317324",
+    "Name": "matter_g2_mul_44",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a",
+    "Expected": "000000000000000000000000000000000e9c290ba8a22f7bb3f7dfdcc9f5a221a5ce838d4fa85a00473a4dd830bacf583dd91a6a6f78d2ebb54a4c1bb217f793000000000000000000000000000000000dc51b0ae8bda6d28c51016764fc028258171d7c7646393228692aef7f1dda4a83e53553f63d6ba996d4c0a802bc967f0000000000000000000000000000000014ab155029dd35206811be9ca4efbf762a1673367e6b57528f79eb50008ce7c3b49a2d25da0ae68ac4030ab4bcc0daba0000000000000000000000000000000008cd743bb52e7908aa973c8518eaded75fc2858f4edb25fb7f2e09900f0abd3ac87e93cf1068bbe0c7d99619aa7a6b76",
+    "Name": "matter_g2_mul_45",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4",
+    "Expected": "000000000000000000000000000000001746a449993b0684740630f3f0e46eddfa135371e33e8de4dfe553c78845399e63bb3da48798b35df48d27e1f991954400000000000000000000000000000000057e0fb1113968858981c9803166d8b3eacc91bfad320ea0e610fbc5b276da1b46d74fcc54183ba61d1b2fe6743097c90000000000000000000000000000000000b3a178ae3b739cae3e80f3f44db42d8c465a5cfe4943b449d4c3b7f4ad153916c6cf4fdfece14a00b271222c72764300000000000000000000000000000000041c8b293ded0c647f2e4d6f9b35304179b723c3e6e421a5cb103e561d1655b92e74877ce22c99f22a3700c3aba9ebb9",
+    "Name": "matter_g2_mul_46",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556",
+    "Expected": "000000000000000000000000000000001103cc395acf81772955bda38f951a81c5a6a476c0b5e1543616a5a7a7be22dd487ab2a8586524891300adec5225b4020000000000000000000000000000000003479a08e2811ae9aab0301d66ada470935984d7466201f3fb28c610c0b5f67e7305f5ad3514cec5f30b51d0aae775d40000000000000000000000000000000005ea37a6d20c1ad0978da68ded3a5bfcc5ad8fe81e39b525fe7d1f2b2b1ab0be7ada80173b1d0b7fe1e06ab6354e64b10000000000000000000000000000000008f2093151a285dac511df1755e99a652a1cad0af3a019650fbdead1421ba8e84afc9eb0a4fea651f365d72f031a0ca6",
+    "Name": "matter_g2_mul_47",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7",
+    "Expected": "0000000000000000000000000000000019f79677ea0e011e5c9a892a407646798b05be05337c73135cb771abf101f450bbffd08e125f077f5ea989decc009b9f000000000000000000000000000000000ed15f35966024cf1de2926108151e976dcb0d51b2736b0877d79de81f6fccb9dd299d14855f4e257cae33ab7455b95100000000000000000000000000000000125e2fabb5cc95c0a7890e9ff2b70102a97a03f2d11d915cf4332dd049a467333e12ebb27955c0310ebdfe2afb3173ee0000000000000000000000000000000011718167000f9b749f1615610a30023db4b986364da5bbdc4506c726624a073548a94307b282590cd8a43b4900a1afb2",
+    "Name": "matter_g2_mul_48",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602",
+    "Expected": "0000000000000000000000000000000005af8fd9e79568b46fc42b2c1bac62d115365834e509dab032f66425b7a571a4bd3bf702299d3c5f36c372750b3281f30000000000000000000000000000000018499089f306b3c9f7a645ca2f9aabc4e57c046992fff87e832e21e21875c6adaca050ea8bd7043afec3a36ecf8eafae0000000000000000000000000000000000827fa0f46134e2dff80088129841f0469ec7360fd8b9864e9ed99c5fd3458e6360661ab4c671846681d491b8b823d200000000000000000000000000000000120f829e8d0ffc360a14eabaf52bc653b1e90a36c0a8af806ca745fa306a9739e31435039a377e0748caf5e80c2b0b09",
+    "Name": "matter_g2_mul_49",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013",
+    "Expected": "000000000000000000000000000000001745500b00e5ebc6f71c779ba0b0f8d6601a065c550ca19de9562455423d2ccb507e659b0dce982faa841267fb1a27d90000000000000000000000000000000009c36b54f12d130868ff9b9b61b714fb1067dc91637c09614c51b5aafa2cbe3ca7dce0f3e366d4200cbf603ad4fd630000000000000000000000000000000000172e543708bb853712d81c000c9f9f2378e628b4d13b074317e95deeae98e11e7f917f91e02a0b18cfe9b25f1b83f16700000000000000000000000000000000189fc572ff6a8c6606ba0cea7da7040898d9ee85a58f12fade8c5a22031ff26c2f9cc612bc6e1b82a0999fa93c6fdfca",
+    "Name": "matter_g2_mul_50",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8",
+    "Expected": "00000000000000000000000000000000013c6f777df97ad3ddab9b7486d54d1bacb3b40ad3035b47a25a66c02e8866955e27a8ee52872c8222ff7466c1310bad0000000000000000000000000000000014a5eb510d7c743e824f4daab21c43db4d6de8ab2e825d13ae0e186aaba828d7b4a2343a11011a8ec4ea82f456e394a70000000000000000000000000000000017a55d3827b78a9c5ea792b705eba7777df74951930791b17ff5b861e98a4488f83007c073c3e904ed4ee328b6f6171c0000000000000000000000000000000019bae02f8d6f1e31dfa09f4feedd5217ade66f6e8248aa98b273574f72aef83d5048534ed38acab9e0eb4c64f4389af4",
+    "Name": "matter_g2_mul_51",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b",
+    "Expected": "0000000000000000000000000000000006490c327790b4c451f93197d7db24211a3b4b5f573a6df409206b4bbfc36bd10d2d0c989889efffd8f4daa4a68b211c00000000000000000000000000000000168f224738db3f07af77494f52ea5e957812a1acd62615f0eaa95c1d363cfceff29be9cf3be5329bb41175a0231ced4f000000000000000000000000000000000321f06b55f7dbfd4900b329c914f9ab9be2794e51e54498e18f83ece5bfd205131fbc254bfbf624d57ec2954b05f6f00000000000000000000000000000000018ec54f3e09bb2a6b112b575f9481bf1c85666133051e9c0ab53369d14eb90e27d2ed02dcda1250d5d539df0d0cda37c",
+    "Name": "matter_g2_mul_52",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244",
+    "Expected": "0000000000000000000000000000000001641b4ad10da5089164809d82ae47f74e27eaebffc2a2ca3c1b924fc69c1ea80ba3da78c78e86957f6a24e7f75dcada0000000000000000000000000000000014e781e4fe79ea1654460f4b0daddaffb29b287efd8168cb20d7ac6c729f684c5f2a7cfa87885accee3a797febc904c200000000000000000000000000000000001c9a44547f0c5b1f4df190285644c5a31df61e3de7da085835ebda917d5e4163f2deea9a83d641a4759fa3108567ad0000000000000000000000000000000014c3d2a79d80687fd6e6aa423257644fa5d0cf641aaf6a7c5675a810767904166fabd9a2ced0727e3badb932e46fd181",
+    "Name": "matter_g2_mul_53",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83",
+    "Expected": "00000000000000000000000000000000129554de7de9a2b73340d94d96f0356a2d1c0524cfb007d76a75f462872e831f45553de05f5b6a1f9eeae37af7f6b4c9000000000000000000000000000000000b1ea2a649ca13a3dc7882f2423036670f68aa05792a8fcd72524420e37381a9ca80dfea701fa5e6da57afa534059617000000000000000000000000000000000b7ff27aba408f9759b5109600cff66c03cdb4bfb3dff64a4838d0516fa46bfcf429fcf9d5cbf74a27f70fdccdb1238c0000000000000000000000000000000005a99aec88967fe775c691d443e2dbd45080eec97e686ee6d7b32e801efe6563315bfafd5c7622d0543519cae4417029",
+    "Name": "matter_g2_mul_54",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852",
+    "Expected": "0000000000000000000000000000000007997a499b2194cab634750a189cca6783ff17d866d66f5998603f8639d2242e8039222c65b0d14001167a9b09afb58a0000000000000000000000000000000015050fe6b335884a225efcfea4acd025cfc05e8f5fe9a0e22a0c91b55664c118d79887de91f1ae6cbc081f6f55f0067000000000000000000000000000000000195b23c4c2c087082c30600ff00485d169dbd360643d163f1db363f270cd7d4f177c36b4c291d50da4101e67b229d0de000000000000000000000000000000000df596ba2350ff7d3e75b4cbe5f8d6b2cc0e14b3bd6dc021936e3371ba64031f6266fb1d2951801309f22bfb1c4b27e4",
+    "Name": "matter_g2_mul_55",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c",
+    "Expected": "0000000000000000000000000000000001fa243b548f8f5c2e5d7736ca6fa95b74dbfd31f95fd532b94f81a255c73e7c0e000e20f9ca6750cb0dfdcd2c1aea8a00000000000000000000000000000000132a893a2326bf61962e1855331a53667e6279ed7358bc84c4a7c218b6cff1d3f449954f56daea72bc2779c60f1113400000000000000000000000000000000000091dd23c75dd8266f556bf27ba54c95c3ccab06168e4e6d0747239722afb20f3db27454c6db3a88daab0ef10659a66000000000000000000000000000000000d3b2e3fd358aa3dae983e87b5d1fce6d5688e66ced6e3a2c96b8d48041557295d5932af6532c13965d4b383fb252518",
+    "Name": "matter_g2_mul_56",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0",
+    "Expected": "0000000000000000000000000000000005095d1becff61df906815842112c6508d6cade4ef5f4b7418f5f01e8c5a383addc1c572237613dfbbb88bcff80e4a44000000000000000000000000000000000bd2561e7bfbda8a48ee038855e37b03fee805689452e9afaf0da4185e0c194e407ce7149b713c689d25f953da36dd1f0000000000000000000000000000000015ba3ae4d4238175425ac5dcbd9e6e9e055b8c1b7752931b524fb546f7bee8723ef2e69351450c6d1ba3c366a22355e20000000000000000000000000000000008c17d77dcfda00a1d75ea0087c58e74263ce5ce4066e979c66397de8e236708831c3a9ca6b35ade8038a28930655eb6",
+    "Name": "matter_g2_mul_57",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45",
+    "Expected": "0000000000000000000000000000000005cabaf39b93d7fe15ef6a7a3031df58219bce702a5a77162551a3d916c22e8ec9af2aa20659e7c4ce5f6382a5f82726000000000000000000000000000000000dcefe1a48d8c239164b54771118f7520ac11a7a6b72d8e17be1cd788cad2f26d3a0d9113e6536426800a744be9f0d4000000000000000000000000000000000199d95a44a4334c87aed273a0184be9602ba443d5b8d34f3495b04e927f4687fb88487f586395c7babb4f218fdbecf8c0000000000000000000000000000000010972032f9cb3e8f45447bdd06df82656fbd3ce38a9f7564c6e5d62ea3596c9b7e0a94046f1c65bf0452ca25b15a885c",
+    "Name": "matter_g2_mul_58",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f",
+    "Expected": "000000000000000000000000000000000f250b5e47ef616be106a3334e2f516061eec8f7ac69f08f6dfaedecd76fb1c9685ecdac2c3ddd534e3947d007ab177000000000000000000000000000000000073819a6de38303725aa3a9e5a7a9122b4d1e60ee8deb3554b5e06ef5e60d71517c2279c5066af003b32cdf83b7fcdf200000000000000000000000000000000070721107ac6dac198f7ed1a7f84697cbbc3199a220d1aaf82e6f015963bad863f99190f18a482f730254cef753ba22d00000000000000000000000000000000169910eb30b8fe1ad8f84c4a132c6c74a6ff06ed6e792af3baa6619e3c8aa6cc3e6f687299467ec9554f9e91bee77aa8",
+    "Name": "matter_g2_mul_59",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d",
+    "Expected": "00000000000000000000000000000000106e892e336b2155909946ab73b980ea761cfe8c48b13ae8a5302eacea08b9cef3e60d5b33c6ec4033218ae5761433dd0000000000000000000000000000000015daeaee59f3b4cc26d3da745661e74db8fe1ea115d50ba49ef5e6151a9ac2f3135f0232235cac7a53e1e8a70eaf0476000000000000000000000000000000000ff494d17c735b934c2c7fb8f413103188fdb116fa8f4d4e43262968ab0fa1bdec23b0d4d8b1c2defe624092de36610d0000000000000000000000000000000008f70b7e9f2d7083774fbce3bff58a1c73fbcbcd9cb049cba71c0c3f0c363517c8956240bcacdfb7934d4c67b1bfdd2b",
+    "Name": "matter_g2_mul_60",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a",
+    "Expected": "00000000000000000000000000000000098f32b35e3b7dc1862ca1ca3c76d009f016c6b91c227f2cebe8f1fe87567d936bf1c54103bec31b3552c077c0242fb40000000000000000000000000000000005380a66d48d348487624a15b63d2ecf6976b5b599901101ea8b1f57736649b4397f6679ecab0ae29573695a921ac475000000000000000000000000000000001710c368f70a2b9cc92ec65c4c2ca35fd63440eb350f488e7c6646f9c42bf680eb62a887d533a91e47988221b46c868200000000000000000000000000000000033c3327da938dbe4630dbe16838229d7d427f3adf18dee6fa26b1c8067838922c1bce78cce08d590ee1acf2baebc7df",
+    "Name": "matter_g2_mul_61",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997",
+    "Expected": "000000000000000000000000000000000404587c60a4bbd8b5b929ca2ec2a9ff2ba4733f4f2877478a669b238d65ca130cba398899f2910d6de04615f8ffc99f000000000000000000000000000000000940659b3e6de7c3d8de9169a28e68dad433bda78de0991fe4a1d404e5f4babcba9d57c7f3d638aef264642f87c61fc8000000000000000000000000000000001676ce240e1ff70ab03f94f3ba3acd31725ec306ce1fd707e29ec22cf91746216dd998d03ba13a79dedf878fae38d68e00000000000000000000000000000000098a81422511f77191ee15d402614c86f9447ab78a89cc348414108f36857a1929f2b92ced78752ab3604f276861803e",
+    "Name": "matter_g2_mul_62",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a",
+    "Expected": "0000000000000000000000000000000010a4ba6952d22a51dbb6762a3f9bd09712c2be5a98bf0ef298d7a7e3a9735ab0d3bf39e40b334895c73a36c218ad24b50000000000000000000000000000000002860f38ef61b497bdaf4faeee7b406007981c17246cfa36cee906452ae85e1c1c6385898ebadc3b4ef8887fff25b8240000000000000000000000000000000002dbbca9034fb17c3f37727d44c027cdf47c36f3f628ea9385fc9fc371d23f22d983656caafbf1cd1f8bdeff4ad7669d000000000000000000000000000000000b7e71b65765c4113a7884771952268a9fe10576f745038912e6877c78372cd261220793b888c43accba1646e902fe14",
+    "Name": "matter_g2_mul_63",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d",
+    "Expected": "000000000000000000000000000000000e9c1a6d591be4da37fd6dc283b8d899b625ccc96371dd3d7731aca66cd2a978810497171f2aeded64fa2b10e480de2100000000000000000000000000000000006d2ad7287847255002480627782d513eaf1f68a3d583d4762fc156b8eb40deae6969fa8a7d8f8aae923800091386a00000000000000000000000000000000003c7eae0eda08df9b9eee2605a44fbb486e3bf2e409aaa1c8f38c06f969ff1f74338004b01288dce99be26a837e45d3a00000000000000000000000000000000178174d2f569a9392eddd2715ceba8762c5bcc6325217db5e5f970d6fde069d0e48a824e5b6ca017891de175c92f6b29",
+    "Name": "matter_g2_mul_64",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616",
+    "Expected": "000000000000000000000000000000000ce12c9010b4c4afbddb459c1b46063a8488277948188b4ec0b739e1cebb5653681d0e43a0d2c6b3f842bfc609bbdee3000000000000000000000000000000001123f60cedddaf4385e63758d64d4facdc443854176ec199ca0df0a9c258517f2512594f2441a4b9a68aa9a2b4a1f4bb0000000000000000000000000000000007cc6f77d181d13bd9736ee23a33b25b0bd969760642ee19004e095ebb8e2b3c0e09321eb15a2f7961803c0fb10b6ffd00000000000000000000000000000000004d8dbf2f0c14b07ebed2b9cb4bc87df78ac8a34ef0b05cbc2c6fb8e8156415399fa52dfb968ef0e6ec697030fb003c",
+    "Name": "matter_g2_mul_65",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af",
+    "Expected": "00000000000000000000000000000000172805bc715a8cfb2e25c384214f4005aa6d3b809a0ad95322209851ef92151526a9d01a914c4d7f0c120b9bf3837010000000000000000000000000000000000473ceaa092a5ac12f38b4065477672deacc08e553d8e9e6391bac0d9ca50015934cdbc340deb05aca916cf50c7915b30000000000000000000000000000000012e85461fbd26c2d0235acf5c8665750656819bb939e8fae77a8d526ca23443aee395a985cdd4b1eb700311fb87e91a7000000000000000000000000000000000246d45fdd88448c93bedf4799becfc7c80e67abd483f2a0aa41e8bbb3f38cbc900314436364f1db6e1d88595544517a",
+    "Name": "matter_g2_mul_66",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145",
+    "Expected": "00000000000000000000000000000000122e1f2081cbde0055fc34d2fe61307bc333b35a1e0772a0cd6fb25338c89824bcf2f066bc7b571b2fb314ca7f45106c00000000000000000000000000000000027ed81b54372d858a6ba2faa65fdc132efbca6ddcd56c3625bd9267cf0ae04f6d342209b995060f584be8d40020669500000000000000000000000000000000002a03427a093a3000a1bed9eba91a82dc2f2fcea1a16a1fb8af29c4988b589abe6a505ec87a82864b3c683beaa6420f00000000000000000000000000000000134bf64871d69a72e42766c2903fb4589b84d7772a62f7d2f8f8d02a914f4d3a278c680c626ef4d69de8aa88b57589a7",
+    "Name": "matter_g2_mul_67",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a",
+    "Expected": "0000000000000000000000000000000018fa44efeabbd1cc47dd9b1a1195ca921c99c77ed43a44502aad27b6c663f5ce2623382c3ddf208f42e3eea741281f4300000000000000000000000000000000138d11e497e3c5656bc8fc0ae4322a0bfb6fc20e249a47a103b164aa3d9fdbf7df4b1e3b0842b4b12568a31992a151f000000000000000000000000000000000182490d6ae35c1208c0d608984df4988d057f3ce5a25073c77cd5b224a5892768badb1ad5cef8f41d1d2022573098c320000000000000000000000000000000002a6e0523781ccdebb75063dc7ad1a9526f9ff8ea1364bae487914f254c0eebcbb2cfc3715fecb9599bfc2f5feaa62d2",
+    "Name": "matter_g2_mul_68",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0",
+    "Expected": "000000000000000000000000000000000dc7488491433d5b3924105c01ffed4f30b755d7253d867fda595e7d80197823e56e4d182d5ecc72d8ef1ba9bca15a310000000000000000000000000000000007bfeeadd6fc468ef6340a2b394c155bf50808cb11e89adb0de5499fbdde91760e9531c1deb23050286a15e5910f1d5a000000000000000000000000000000000f096db706b08485fd577f37b7bd232b5a10c3f80c25bcf82f7a3b666c6efaac8e856bfe5f7dafb7457e33eadcb4133d0000000000000000000000000000000004460d1f25159ce6df59efbd7c693355af4634dadeaee2ced68124b2a887698c10e9c4b40c4f4f9c8444acb881ceff65",
+    "Name": "matter_g2_mul_69",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258",
+    "Expected": "000000000000000000000000000000000f1aa4a7a22c568c41270d24824138bf9ffc763a5356b7c0bc1d051a0a0db12616700d9214972b63eeb2a398d27dc83f00000000000000000000000000000000020d0c2ff8f93db6b415c2a01712034e46bdeb6e665a5177a3877db9f5401d3dccb99907ef843062e394c1428983725a00000000000000000000000000000000088abeb6fc3ead45d5b261b7d684f168ca8f5f163cf338863e6b102dc40e2cd0ede97c47460ad6f560c27e95c8b71ca8000000000000000000000000000000000ca2e5cec212d581c737928512118e2f51a0d74070f40a998b7b06d22b9fc754bb2fa5499308058be9ab81521d057414",
+    "Name": "matter_g2_mul_70",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e",
+    "Expected": "000000000000000000000000000000000cfa23c46881893f6c50d238a83669deb520a7fffab4f912f77df7cca43f6827a1a0ae0b3f36c8f116ecefa33b8bf37a0000000000000000000000000000000014b7e5c18d2f9bfe15b0c1af3bc6e230039a341e135837d123e91cde9cbcda298c66b93f692232c912e5d7d3d6331c430000000000000000000000000000000009c8984999ecd3a4144ccb925d3e5cae5c1662dfbf8871013b1cb2946482fcb075c489c61b8d6261f2574b44da3fc1ce00000000000000000000000000000000196e7feab383211e4825cf98219c63bf9f45a72d66030219cb585d5d25237a01a97f00e122db6a51325022e69e7d8cdb",
+    "Name": "matter_g2_mul_71",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83",
+    "Expected": "00000000000000000000000000000000005c0282830934ea09c9f51b52cb6dee75b874b155c63076dbac2cbbf220863d55557ff1b7d681fa185435df1522f49d000000000000000000000000000000000a1680ebbb185c8e7d8a197a523a7a5e618f97c46670622034d312b3eeef140150e03b00ae3dff8d9f1d872f3d3dca380000000000000000000000000000000019bd2eb4bc25f5aa6bce206f0683dbbbbb002098a118fcfb060c1353a310c2baa1063a782bafcf6ff6bb8edaf6f1597a00000000000000000000000000000000082edf49a0435e0b9f3dc7f207711d66004ae688b18f5b62fd1596899ee8edfaac7da38973d81f12200018fbe8151572",
+    "Name": "matter_g2_mul_72",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07",
+    "Expected": "000000000000000000000000000000000b8a715c1c2792a30f7ad752a808b621c34af1fb7f1e3392a36ca9481a019108a21e3ef338a1d05f2f23ac3e2cc42ed500000000000000000000000000000000101375c9de592031c55a7a62189fd3fa3c565abf7c64724796dca3b1c7a6e6834a16ef1c4e2afd6ce2e69487260f0028000000000000000000000000000000000cd385ec8245431d3b1aff88453db7f66a5d7888a5c1e0dd0abe9ac7db752933a343b8be53b7bfffb704768ef0a3dc5c0000000000000000000000000000000015d55c8cddb8715e25fa260d1e1fa672ff76eca7c80d19d00678fb9d08759b810cf266ef0a7e9dd749a576ce07240fa7",
+    "Name": "matter_g2_mul_73",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6",
+    "Expected": "000000000000000000000000000000001311de31229f1825d0bd2c9d726fd71e05828a20406a4705ea65f441537486338022bac4e552bf3c25e15717bee00ba400000000000000000000000000000000052e082cbe36c854a028a041981fed87d39fb218a88208aa1096e260a3932a1155db7f306c32d133070b0a5bb6d161760000000000000000000000000000000003269d4afd20002873f4305018a4432c1925eea28486d657cb458198ff2df9d304bdfc7455233243b1712d8663591d460000000000000000000000000000000013376fb98929cbe7f7d090d1c9d5c4f6332bbf25470aa03c35a70481931e4bc91c937029a5e11d2a3418eab698361227",
+    "Name": "matter_g2_mul_74",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659",
+    "Expected": "00000000000000000000000000000000021166263d1a443d5b2eee9aeca3678ae4c2b44d556a7cb9631d47e4fa3bb05ecb94d6582f4ca0cd787027fb5f2efab60000000000000000000000000000000015335d034d1a0ce78e1246a16e35e0075f73d4a392da1e05c11388084cf80bf31d499e57c48f4be6e72d3abc7b387ec6000000000000000000000000000000000deac4ae1900a4e1814624fb4b8c7a3149fa9cff2ca97f02e7d6765e034a1532a7b8475ef7aef5ebb851063cf4b9e79500000000000000000000000000000000161e3af03f226278a07ff3b08e5788f6c5029b2c8293e7a7e3ae11c4d78676b60dc0208cec6b82e1714d976007fbb389",
+    "Name": "matter_g2_mul_75",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d",
+    "Expected": "00000000000000000000000000000000120b4434babedbd8ff295a6e2ed5fc7af0548d7e60663110050be797584c0cb638988201ae7707cbedf0c8b3dc5ced85000000000000000000000000000000000d2de0a260bdd241a145e3f68a6de48da4c65107a500e02bfeae6ae7dc428026c7c3e9bdda9a3069d2744705df2eda9b0000000000000000000000000000000018a237906c0e277541c4f00c4c2feba7cb2c9b87709c18b62b7c36d78fc118cfd65c127765e01dc0ae5875b9552bb45300000000000000000000000000000000197485daf54e98e097b6bca24b0738682969256decbf3ebc05f6982e4608829f37e2877937b3f26b88efc3deeb4bfacb",
+    "Name": "matter_g2_mul_76",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712",
+    "Expected": "0000000000000000000000000000000005de82540aa67c69b962d292133b09e6593961da8944ce02557141abd19ac471f766b4083db85c67a44b65dad2202488000000000000000000000000000000000cd999bf3cb004074fe9f355cd8dfaa7b9d3439d902fddd2fd0688419b5b7f8c4300ab26b658936a90c0b8e1488249d1000000000000000000000000000000000f97ae779429a5afaf7a3343586eea84a4e76f00a1852ce42a4940babd565bc8d61bf72fca9b123922f1ccfb1db8c06b000000000000000000000000000000000935960fa941c27e74234a07857ee680f53c31047235c6152d1669724bdef37ba642cf4e0dd355443ea470e6430def8d",
+    "Name": "matter_g2_mul_77",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72",
+    "Expected": "0000000000000000000000000000000001b0aba02b0e907c03d2f4003083c824ce60f2f55f70dc6ec7c7f81f3d0ef4bf533b4c94833e36e8aa7aeec18b7255de0000000000000000000000000000000004fc227a6ae303f3006f75193cef7c653e6bddd28fdb843b41c7d39966a701ba8fcf611efa71abf059d7d98833480e69000000000000000000000000000000001077fddd0bf3d5c80eec653916f9095e900cf165315d74a872219285f62b5412536e43c4cdbc120ec5c7753318852dfe000000000000000000000000000000000ccd90e01c1d4a00f0d9e29a88e8134f2cf68162da66bd343645a998730190114a6921c9b048dda58b60b42a133287f2",
+    "Name": "matter_g2_mul_78",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6",
+    "Expected": "00000000000000000000000000000000185520023714580a3f235e24316478b8260565ffabd39670811519066844e131e337bd62ed2069bc6d2305e6638e539700000000000000000000000000000000055fc74cc7cd3fc393d5b5ab2419414effb783ff4da2516e5465a4acc195339c7b5238be4e0744b3d7fdbce46ca7f5dd0000000000000000000000000000000005f584a0311c02d611c15163529130a2fb3dc853083e7225b791ce5ff32d5ef7039c80edfff317ce9ddeef84443b5a51000000000000000000000000000000000f9d5acb355f767cc6286cc09f6df232532f9a0e9e4ed1fe28788abecb200e22066c23f3ac6c49c47071cbb023e70183",
+    "Name": "matter_g2_mul_79",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b",
+    "Expected": "000000000000000000000000000000000ceb56d75f3aa1548c50d7780ea1e33c3d069b2f37e7f96be6a8ec03266fa8d0868822afb3b2e54750722266f6032a8000000000000000000000000000000000080f15b7f9f2c22f1afacf558267b5b84f3a6d199fd3349eefa2e46c4f332849c0955d19d4513151dc0f3b566c0058440000000000000000000000000000000013305f8ff6080f7da05c28155c0c2bc1c78d855cdcff0bb2c6b82cd5107d7a070d0830e6705f6832ed5baf75a659c8870000000000000000000000000000000018f4e136859b4ceb230450f9abde0325a4d59db98279d7fbab710305ff53250dae1c8789cccc27586c9b9df5c0c4722e",
+    "Name": "matter_g2_mul_80",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035",
+    "Expected": "0000000000000000000000000000000002a0214be95f020c70221fb4fb6856af7ce3845a4b607340f85127b52f8a204efcd94a152835860a4ddeef84946671b1000000000000000000000000000000001767777740a9922a91c39a36e2cdfcd544df902b31812ffc88418dab7321f73406ab142055b5bb264c187f2d4f2d6f9d00000000000000000000000000000000026e6941364c74997506df0f9fbe6b2769839e8b7c7293f4e63d13bd7bee90ff779cf82adc2f23c569d1e13826cdb0e4000000000000000000000000000000001618ab2ffd4b823b9c9776baf849641240109b7a4c4e9269f3df69a06f85a777cb4463b456023b7001adac93243c26f5",
+    "Name": "matter_g2_mul_81",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff",
+    "Expected": "00000000000000000000000000000000054ce66b9b0b3cff6637d6cfdd788719d4e33516b98402d8fba54725309307711fb576299ba99104d4e7df0deac9ea2500000000000000000000000000000000055e06ff52cda9116a98ad3709f788d39db53844b7db58a57af52848ce1c59ec2a1f083efe79c5994b9291a2d1020fb900000000000000000000000000000000040c9ad63698ec78d06b41bdd6f5eed089b67f106348f9300f822a2d61ea1e5d2ddda0efd1025825c99cb0e243573f7700000000000000000000000000000000195dd00c48186f8d1337ca857aea02c4d199d638133e9cbd2dfc5f633502f656343746ec2a416465c3c0d4e9d53fd097",
+    "Name": "matter_g2_mul_82",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b",
+    "Expected": "000000000000000000000000000000001141b59af8fe6cafdf2e247fcb0ee4642a9b4022b6d71163ec9b6ac2f7d10ee3c5c0173ac686b03cd6a7086b039ec786000000000000000000000000000000000f05ba6973c5d865ac5c037583b65eb4eac826b5a04a7ebed1e10bec6ec7dca93b1c2eba70ee0189fd552d5023f2a87c0000000000000000000000000000000002e54475940985ad2115223c5ea3a4c95890f3e9992e3e1a6df2170ab77143bcc5d29b9dcd1ed3bf16e545e9be21a8640000000000000000000000000000000019acc4705955761518cea482b83e3726dea8d1f66a5f19b06cd7ff95828e15d1b139077e0d274b0e6fb86c027844d97f",
+    "Name": "matter_g2_mul_83",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d",
+    "Expected": "0000000000000000000000000000000016fb5839fde95111742255b33f040c41dbd0f142d1daa8abc7c63008ba9f63f07381d9d6128240ae9b6cac5befad84e5000000000000000000000000000000000389a11727c356b8f3bdb6a73bc2f6d2d73d33d287365283359521dcac64f17810bd58c0ec5bef4db253bf630bdd9599000000000000000000000000000000000629a8af1bd0c1b1b6d7e447bb779663d7bae8e895e09418bc350e644d7022fa877496f30e2018f5dd1c9683b2715adf000000000000000000000000000000001950185d2574fe0c8277e3f93f59dc5628ec3487911ba9c3194a2f716116ff0bb9a39dde802dcfaa61633ad7657a578f",
+    "Name": "matter_g2_mul_84",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33",
+    "Expected": "000000000000000000000000000000000024c03edb9b54034eacca4b321d51397348c57f406b074b16a9d6215e03f842380f5358f5c095fcf5bf3cabcbabdc260000000000000000000000000000000014e62dc442135d729f65090475fb408ebae132cdf2c2932582af887ed54696f3cd15b163f11285b99e8d8f809aa2e65d000000000000000000000000000000000438a2c99df216c67d92b99d9ee8cbd0e9751e538074d146767bde9675ae3a05bdae051efcdc6bbddeb1b7a8288370ed0000000000000000000000000000000007c462a8f5720e442e1917bf75fc3c3dafab6c39c80d0b93d81d1db4080f6e199be092b4b025e7b02efce4f30d00299a",
+    "Name": "matter_g2_mul_85",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f",
+    "Expected": "000000000000000000000000000000000e8137c15436264b5960c27d0c22be7fc5d56a12f43b3832ad0d7f5abddbaaccefd140e2f7c476b99e6fd9b3a52743600000000000000000000000000000000019ee3caa56f0329a2e2acb8907b3edb21f4eee73e312352796b51282e097f9b10af61805d5c222332888737c7f8e227d0000000000000000000000000000000012cb9c610391940fed7882a5cba08eba4226c36eca8a2ed22fb5e752e0a1a5ec556673e47013258b499268f1de77bdf100000000000000000000000000000000031b769f606fa25b81a982db86a1cd442ed738019e7e64728ecf485cddcc17d9dc271146196178740b9f05f56627b061",
+    "Name": "matter_g2_mul_86",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c",
+    "Expected": "00000000000000000000000000000000080807a0570b628549629d2eeee39de773badaccefb76e01efaecb0ef0356f535d32c3947f0613bc7d847ef8c8778f02000000000000000000000000000000000e8c091ea30465d204ace72015cbef29645206390fd92ba7c4aa0fecae4ecee53c0b06e1fece99511efd8c7e9cff1a8c000000000000000000000000000000000c881c678c94d80164bb3295acf4341fe6c726ca64a1a015c890450e719b85720f41f80369f99ad3e7e3169ede0113e00000000000000000000000000000000008a2fe01a7100afda40091eb0b2b14cd00b7a4d8bb5cf9d9a3847970a94f2035fec7f292c04c38d7e49890e612830aeb",
+    "Name": "matter_g2_mul_87",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1",
+    "Expected": "000000000000000000000000000000000d17f6d9460566d0543df2666d6ade685565e668521a87fabc58148343085415fee92c32907311c9d04713c34bf7690d00000000000000000000000000000000185da28f07b86885031ff5cda913a85b0e4d07673f456ecf2a9f0fd1b21d99e22442f9b705039252d57380b6a42912050000000000000000000000000000000014a4bde5973ef43691b61b3c0f6c2fdb4bcd6ea88e53e2787a7d93ad6e05ee2e69f2799712520f72b3c577ee278008ec000000000000000000000000000000000d92a565b3d8d0fded054a75198b31c521e3223650cdf762fbf7b851f7ac0fc66b8c86c20b905117585704c23b27e7db",
+    "Name": "matter_g2_mul_88",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb",
+    "Expected": "0000000000000000000000000000000008b1ebd753364a5a0a6296ab48b348f91668525c0d5f7edc4f2d29844592f34a209f9e77f94ebb38ba76bdb3f96063ec000000000000000000000000000000001062e0ff0a67372207052e2520d8b2823764a5075c94011afd6c60288e187ec77e08db01c95dfa195f2409b58c9dc4e5000000000000000000000000000000000cc2b87b613d97a716586f371c457fa869c2b8d1fa1cf4b9e8c34bae23e0544752b997df4711d0712ec11d3a9d96ac2600000000000000000000000000000000140eae891c87c2026f0b1293df2bd8ae2dcb0ab3f8de74676f37c905334ac1f53fe4b75511691dcf108fca51abcd524c",
+    "Name": "matter_g2_mul_89",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf",
+    "Expected": "000000000000000000000000000000000276a138edecfc9378be4e241d64cbb48bfa6fd4fb1788f8bda870d5ec8b2132fc9ec888ef84c43a50b7de0527def36800000000000000000000000000000000153e90d52c747859f88223555bc8bc4e8b6fc846fe7028de728a4dfa085c6e350f9f1d12b9dca4ca8e07377648544400000000000000000000000000000000000cef00e7217da6df0a6d85f40be69f154300c423e86e54e513b2491e65002e308445238082da69aa9e5e83b5f4fc17dd0000000000000000000000000000000008da1da2a0d1da9d2158b9408dd9b0eaf414d237b8219fa7661e40c1a88eac2f9735d0dd6ad67b85aab85952369e8287",
+    "Name": "matter_g2_mul_90",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5",
+    "Expected": "000000000000000000000000000000001484993096c210c7bebbc4c0bda24b44a70e982b2528215c0e8578ea55f1181472758caf935aa0a3d6820cdad753e2f90000000000000000000000000000000011802324a6e03c3174bbe7261ecf3812c1a97e1be27269214f232274a3bf82775d47c5fdd70fe1c57155068b296d394200000000000000000000000000000000050f43c874c1cfb5fda81059cb7b4808492632fa20369dcfb611e503ded81a49dacff253e31d7e27ee84bab79e3c5d53000000000000000000000000000000000ef945b6f210fb09bf0ad5bbd4b5a6630f43304ddcb396807c967eb5146741f7432bfdcbd7e5f3d29917781efb62e6ff",
+    "Name": "matter_g2_mul_91",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e",
+    "Expected": "00000000000000000000000000000000028233bf12e8dbd8510f119be30ea1fc13b755c6ee3ca2a3637a3bf8f73776c9d1fe231b713396ffc579ef9320a05b150000000000000000000000000000000018e7c00b8047d64ca0c5df54486439c5fb3d1414c2f71cf8a3ed591b7c45bf18b37473daeeadcb625eda638885ddb9870000000000000000000000000000000018b89c9b6bf9ece36f1eac08fc35ffc9f7f964a0a9b19d495ae1361fb4bc98aef8770efb47d9961aff694b878d659818000000000000000000000000000000000eb2fda2c29c6761e35ca4c9772bb232ea0d297582af4f50ef76c0b74fefd414b535e356c069f54ef5224225e95be6e7",
+    "Name": "matter_g2_mul_92",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5",
+    "Expected": "000000000000000000000000000000001239935827fb2a269ab064a3ae2bff2555f89bb3a71a47ae815ef755fc1363a89d20326855cfdd0e13f6c85f727bbe120000000000000000000000000000000012fbba047478b5f5b07a582200271a0c331d6f76864f9b6c6ef8ae6b0965eda481eddaf72c7a887b21719164c633d39600000000000000000000000000000000017eb4353b413437244983554a639a9253d105395ff9652504df7700d879cd9a32d5f0824b1eaa532bcf2fea34f8f08800000000000000000000000000000000054ea45475c01ea0557fd143b21c7bdcab6d287bf6bf4f88b6fb06e02ac6fc5ba96f323bb1fda3a1c4d8f42d01d267b2",
+    "Name": "matter_g2_mul_93",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0",
+    "Expected": "0000000000000000000000000000000015a145e379b7ecf4566a039b753f91e8ad75d9e9c9a20725ce34a900eb9a1bdf66cabee2100208d7792a963d1fb8c02f0000000000000000000000000000000007f0ca14fc4e34bbdf5008d632dd112c7368e037ce019b7c4ec412000ac02302c85ae64f9ab495361fa5b620e46420aa0000000000000000000000000000000017c00a08bba18426dda40e773d79733030b5b3b199a62436ed06b773fd1f10688e8af00e8a223cdf242bd1ebbedbf634000000000000000000000000000000000a17365cd9f7655793682b72e342227048da0cff88f6ace33ddab548ba126017e4b7f7439373a893e3b5803e662814b8",
+    "Name": "matter_g2_mul_94",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169",
+    "Expected": "000000000000000000000000000000000081b4dc78b74250a82da9d803876add659411cfb467860b2ac6f0f68929d6377deb71d6acc9ea8fc8c1286b8f92056e0000000000000000000000000000000002c5fde71346a255ee9dc896f654eb2e0c66f4cb4c51541d2bbccf2463ecf0085a22b9d2bdc5bef39d80c4477824f116000000000000000000000000000000000ebda0cd8bf6ac7e86a1bdbe44ed1e15f8ffa1fff92afd67fb564306882f35037b61cf0d93f278f15149c04a2e83041f000000000000000000000000000000000fc38aa811f5ec015f10a99bf175f1479d4983c9d2180a5e3da88b4e9b62ef50560ff0a6c2fb7bda4c46c54551f8390e",
+    "Name": "matter_g2_mul_95",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4",
+    "Expected": "0000000000000000000000000000000007b46fcfb2cd8efe32754306ff2f503d7434168c1c3cbd7c80470cc5a5c8bda10a80bfc0129da349724d2d6431c5ac90000000000000000000000000000000000e1078f4f4ca993d90accbfc036219507bd22d00930ffcfe1227780c00914fcff845698b2541510daf59cc83d8b947e7000000000000000000000000000000000b7c6d9951570e685d3a71b19a38f5485f974f85fe8cd4b4c196d33a18750b278b6d374483d81dc3e15c9b8b9b5dfdd6000000000000000000000000000000001003a239ea4a2f213f0f646bdb62cbe4f98cfaf7298d8b2e0eaa07bf3f939e779caab5ffa0033467c5b297166df657d7",
+    "Name": "matter_g2_mul_96",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125",
+    "Expected": "0000000000000000000000000000000000ea29b1e059560fec21c3692d4e632a45c88a807c953fa23dbedb271b049d7fc717333b498ed12573a896f872e795dc000000000000000000000000000000000de0d10c47df92010a6635e3403dd6e91a1bf35bfcae82c1008998e86aa2d18a6cfd3f2f1207fde3bb39b723ec4d3ca60000000000000000000000000000000005e2aef9cd37430b15e5e76b2c7870630d255f630c12e865caefe308a39833e00319406746dbb2af3ed32135e91eed49000000000000000000000000000000000c229fad41b0d27ad7b5db33188fa70b97f22e323e429ef65fcf98f5339e908c31df8859b863356e0fc90538c5c49cf2",
+    "Name": "matter_g2_mul_97",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b",
+    "Expected": "000000000000000000000000000000000444a00cfd258bd46f659b09eef17be9929008d3d1c65e46cdc762eeaa2f0b52abfd636e6094e21983fad8171194c71a00000000000000000000000000000000090833e68614be5bf298e04e44527480cb35128bbdecae15eb95d6931a718f66869ddb68352130b4dd8a921ab3f26d080000000000000000000000000000000000994015b1b55340c3839d48320d178b2ffaa0bbff038f7aa63d4dff41a217582fae9613bc537fdeac8d0670c0cf479a000000000000000000000000000000000fc486e2a1680c10ca28d4c3bb22dbccc9572036512645bf868e7693ae4591569c973f9ea26342a573e23a06c2fb4b70",
+    "Name": "matter_g2_mul_98",
+    "Gas": 55000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf",
+    "Expected": "000000000000000000000000000000001375bd5ee66c330796bd8381a26cefa3f40f8cc8de42d4d59a7adbcd3852e6d632422e6ad9a06a6e497b23b17b1df87500000000000000000000000000000000165d8e7be17ecae9bf51a773da705aea42536d0fa3a2206267da50451f5104ee241811dd0e6710a80c38df77b126c009000000000000000000000000000000001559572407aff34969f83c394d2b095a7ae9f53a8e6c923910f256bb87b6ec076fa6acb85465102fd24d34031f88f7510000000000000000000000000000000015ff9ba89b55ef75f63732dec1e64106d7a912a6657fcc970dd011a03b5364117cca46d6cbafbc0c5049db10fa83fe6d",
+    "Name": "matter_g2_mul_99",
+    "Gas": 55000,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsG2MultiExp.json b/core/vm/testdata/precompiles/blsG2MultiExp.json
new file mode 100644
index 0000000000..b5b63625ac
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsG2MultiExp.json
@@ -0,0 +1,723 @@
+[
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000011",
+    "Expected": "000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d536944",
+    "Name": "bls_g2multiexp_single",
+    "Gas": 66000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000019d5f05b4f134bb37d89a03e87c8b729e6bdc062f3ae0ddc5265b270e40a6a5691f51ff60b764ea760651caf395101840000000000000000000000000000000015532df6a12b7c160a0831ef8321b18feb6ce7997c0718b205873608085be3afeec5b5d5251a0f85f7f5b7271271e0660000000000000000000000000000000004623ac0df1e019d337dc9488c17ef9e214dc33c63f96a90fea288e836dbd85079cb3cec42ae693e9c16af3c3204d86e0000000000000000000000000000000011ba77f71923c1b6a711a48fa4085c4885290079448a4b597030cc84aa14647136513cec6d11c4453ca74e906bbca1e1000000000000000000000000000000000000000000000000000000000000003300000000000000000000000000000000176a7158b310c9ff1bfc21b81903de99c90440792ebe6d9637652ee34acf53b43c2f31738bbc96d71dcadbbf0e3190af000000000000000000000000000000000a592641967934a97e012f7d6412c4f6ff0f177a1b466b9b49c9deb7498decc80d0c809448aa9fa6fbbb6f537515703000000000000000000000000000000000031d84356ef619e688a10247f122e1aa0d3def3e35f94043f64c634198421487ca96af5f0160384bba92bd5494506c4d000000000000000000000000000000000db8fefe735779489c957785fa8e45d24e086ef0c2aba2e3adba888f0aeee51385a82898524c443f017ee40be635048c0000000000000000000000000000000000000000000000000000000000000034",
+    "Expected": "00000000000000000000000000000000158d8ef3d5cdc8a1b5ce170f6eeadec450ca05952ea7457a638b8ff8b687c047799eb3dd89c2e3c6ca6c29290b64f5ab000000000000000000000000000000000807d135b6b007a101e97f5875e233b41f12bd2ffd77fe1195418a73a4c061248118ea1049aeea44750cd5ec83bcc1ae000000000000000000000000000000000f04136354f45a85a53fb68527bc8fbc7e8c1a0056878012b548a97bfdabcbd3fb8eb3ff187fbe65e1ce233afd2825050000000000000000000000000000000007b15428114e2ea094ba1e64df4c244f80aa2f75bbbf21a407bc84e80bf2a5ad787d02ae8a90cc1c137f0d898edb1684",
+    "Name": "bls_g2multiexp_multiple",
+    "Gas": 126060,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000005b000000000000000000000000000000001638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053000000000000000000000000000000000a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577000000000000000000000000000000000468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899000000000000000000000000000000000f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3000000000000000000000000000000000000000000000000000000000000205900000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e84900000000000000000000000000000000000000000000000000000000000b7fa3000000000000000000000000000000000e7a30979a8853a077454eb63b8dcee75f106221b262886bb8e01b0abb043368da82f60899cc1412e33e4120195fc55700000000000000000000000000000000070227d3f13684fdb7ce31b8065ba3acb35f7bde6fe2ddfefa359f8b35d08a9ab9537b43e24f4ffb720b5a0bda2a82f2000000000000000000000000000000000701377cb7da22789d032737eabcea2b2eee6bb4634c4365864511a43c2caad50422993ccd3e99636eb8a5f189454b18000000000000000000000000000000000782c14e2c4ee61cbe7be6e462a66b2e3509f42d53ff333efc9bfe9a00307cd2f68b007606446d98a75fb808a405d8b90000000000000000000000000000000000000000000000000000000004165ef1000000000000000000000000000000000411a5de6730ffece671a9f21d65028cc0f1102378de124562cb1ff49db6f004fcd14d683024b0548eff3d1468df26880000000000000000000000000000000000fb837804dba8213329db46608b6c121d973363c1234a86dd183baff112709cf97096c5e9a1a770ee9d7dc641a894d60000000000000000000000000000000019b5e8f5d4a72f2b75811ac084a7f814317360bac52f6aab15eed416b4ef9938e0bdc4865cc2c4d0fd947e7c6925fd1400000000000000000000000000000000093567b4228be17ee62d11a254edd041ee4b953bffb8b8c7f925bd6662b4298bac2822b446f5b5de3b893e1be5aa49860000000000000000000000000000000000000000000000000000000173f3bfab0000000000000000000000000000000019e384121b7d70927c49e6d044fd8517c36bc6ed2813a8956dd64f049869e8a77f7e46930240e6984abe26fa6a89658f0000000000000000000000000000000003f4b4e761936d90fd5f55f99087138a07a69755ad4a46e4dd1c2cfe6d11371e1cc033111a0595e3bba98d0f538db4510000000000000000000000000000000017a31a4fccfb5f768a2157517c77a4f8aaf0dee8f260d96e02e1175a8754d09600923beae02a019afc327b65a2fdbbfc00000000000000000000000000000000088bb5832f4a4a452edda646ebaa2853a54205d56329960b44b2450070734724a74daaa401879bad142132316e9b34010000000000000000000000000000000000000000000000000000008437a521c900000000000000000000000000000000049cd1dbb2d2c3581e54c088135fef36505a6823d61b859437bfc79b617030dc8b40e32bad1fa85b9c0f368af6d38d3c000000000000000000000000000000000d0273f6bf31ed37c3b8d68083ec3d8e20b5f2cc170fa24b9b5be35b34ed013f9a921f1cad1644d4bdb14674247234c80000000000000000000000000000000008b7ae4dbf802c17a6648842922c9467e460a71c88d393ee7af356da123a2f3619e80c3bdcc8e2b1da52f8cd9913ccdd0000000000000000000000000000000005ecf93654b7a1885695aaeeb7caf41b0239dc45e1022be55d37111af2aecef87799638bec572de86a7437898efa702000000000000000000000000000000000000000000000000000002effc7b302730000000000000000000000000000000002142a58bae275564a6d63cb6bd6266ca66bef07a6ab8ca37b9d0ba2d4effbccfd89c169649f7d0e8a3eb006846579ad0000000000000000000000000000000012be651a5fa620340d418834526d37a8c932652345400b4cd9d43c8f41c080f41a6d9558118ebeab9d4268bb73e850e10000000000000000000000000000000015f4b235c209d89ce833f8f296e4cfb748e8abce6990ce1a5a914b9416c08e0d3a26db89625915c821a5f152b7fa592e0000000000000000000000000000000006fcacb3ee6650a1044852d61c9c20bedc8ee90aad97de8e24670a9ef57483e678db11dd95428915088d76e30cb01a370000000000000000000000000000000000000000000000000010b4ebfca1dee100000000000000000000000000000000018405e4b67f957b6465ead9f5afc47832d45643dc3aa03af7314c6cf980fa23dd3bb8db3358693ad06011f6a6b1a5ff000000000000000000000000000000000c48e0d4f9404ae0a7f10774c55a9e838bb09d3bae85b5eaa6b16b0f4dc2354368117f3799c37f3f7126d8b54d3f83930000000000000000000000000000000007e61f4ec5bc9e2cc8ca471ce4ed40e729b1790cd2c0d9c1cb50e615ec7f346636e77e1cf632c881c07c5385898607620000000000000000000000000000000011dfaf9281901dd356fc5dfece21898a93d9ad9e4e246dd6e18d3ee46d58ab7e77401a3e8d04057e5638ed74fb95688100000000000000000000000000000000000000000000000005f04fe2cd8a39fb000000000000000000000000000000001796abe0d9e4a703962be528e6a5cb65c60725886f925db0e2a89107ec248bb39fa332bc63bd91d28ae66e0dfce8f754000000000000000000000000000000000fb665f5a7559cb0fa1300048a0e6f1ab5547226e86f8e752dd13c28eda4168492e3d3bf2f8a6b230dd57f79b1afa9910000000000000000000000000000000003422dbbe4a06a4c6c9fdf35e54f74b4ab1528abb7249e99898e6fd7affebc7aef95bf82d328dc01d63c25f6a735c35d0000000000000000000000000000000010aa5504b469427eb3584a286191149f5c3c5a745f338278dd95337cd2336d3c4e7532d98eb189fa543824953e7c1c170000000000000000000000000000000000000000000000021c6c659f10229c390000000000000000000000000000000009303f04d568e289a35102b6df883d5ed620355c0eb5d02236718cdaf99fba6e19ef5cee2996268eb9a53ae1ee09bce3000000000000000000000000000000000190be857d602284393305bfe0a29e29a6982ed3f04ccaabafb7e59cdc7eda85c22bc3e8690355c7a0fb7590ae40f1b00000000000000000000000000000000016efd497a0c5c6b59a1fdf2b590eb67a7da8cbe72f49084e7050783ff12a783cad1859e1a0b0ec8ff784c703617670330000000000000000000000000000000017a957ea4d53f4fc8412cb015ae91b38445cdb3e7078d875c465c941e0d9a852c78d90b31b6b6010efe8bd5117e831630000000000000000000000000000000000000000000000c01a881f8abc4d8843000000000000000000000000000000000173ed58056bec9874464d3f23c3e7d3d429d6c8a167fc7f39368830eca839d0eb8260d64ca823f6c785c71f85893d8400000000000000000000000000000000123372d7d4c91a249df8f3e4f8e669087b252ab5d8cf2529a87e4ed3622e4158cf17dc44b473d5debd273261383e8a0f0000000000000000000000000000000000c500eb55ab86381a1725f339f686c7e38ce9113493736f57e999badc661b5b8494d220ded0711e841228a389abdb820000000000000000000000000000000010a4025d823c4262367c53f50e67cffa046e4a1e7c69ff30373772e49ecb310de3b313d83cc41f40a00205722f233e270000000000000000000000000000000000000000000044496e633650ef8f6fd100000000000000000000000000000000152110e866f1a6e8c5348f6e005dbd93de671b7d0fbfa04d6614bcdd27a3cb2a70f0deacb3608ba95226268481a0be7c000000000000000000000000000000000bf78a97086750eb166986ed8e428ca1d23ae3bbf8b2ee67451d7dd84445311e8bc8ab558b0bc008199f577195fc39b7000000000000000000000000000000000845be51ad0d708657bfb0da8eec64cd7779c50d90b59a3ac6a2045cad0561d654af9a84dd105cea5409d2adf286b561000000000000000000000000000000000a298f69fd652551e12219252baacab101768fc6651309450e49c7d3bb52b7547f218d12de64961aa7f059025b8e0cb500000000000000000000000000000000000000000018461a3d444ec527fcbf4b000000000000000000000000000000000027513925b419f6c581788578379995290ab9478e08ecd1999d5e1a05c58144d2f9f06fb8c7fd1586f3ef6a973a3ed7000000000000000000000000000000001292b2ce751f6f859ec7882e14083eac9841b035f9d5ed938a81579dbce07dec2c0202b7f6b25226831cd9c578e893d00000000000000000000000000000000017f36da49414d7706209d52840250eea6f33970fd7eac448ee122f24c62f6a6e09757aa29761160be0f65ba3ce7a153a00000000000000000000000000000000086d471f958f3ff679805751b183fb6310e871ba72bbdefd59c58e95ea62de0820d5affe601757e318abaa5a0c2715bd000000000000000000000000000000000000000008a0eb53c748001536d7ffa900000000000000000000000000000000090721a089bbbb130c21a529be0ede9271a91a2dde9cb2a8e091a19fd2c0a40c390ac2bda8304085c2d6e38e520eae44000000000000000000000000000000000cc64109c67b342b6dbcf86cb60fca7ad378ed6398d89076ed108685c57a07d26e40ed3d5c4b3560b21e519db5875d49000000000000000000000000000000000b0ddd488f5a6f61f087cdbf011b50209a4460c8aa8c5f48c0b30d9cf6cf24259f4e7badc42e1b7a33352949ae566fc100000000000000000000000000000000038430e8db04d205d81aa1632d23919c06f89260c7ac5850bd8b780f8388e53db3a3ddfe98cc55d1c686e582f85b0c8900000000000000000000000000000000000000031133a6c7d698078a7ec7e113000000000000000000000000000000001800ecc167bb714100f31e7610cd3fd010ca299b394c01b1a89afd11b051e92989f6336db5e6d3212f6b04673526d83900000000000000000000000000000000070401d9bba01c0445e0a682406b099f21d16d9c348cc97156769084055ca328a145c134b8c8b58f019d62882b2965de000000000000000000000000000000000287f071bda99b0318e386b27a492a6823a9664084b12acddeda34cb53f827a362ba97c0e652c37bd9d6023041d8c8d8000000000000000000000000000000000fa708ca7dd917541cd02281e525d3367b5ebf5e9353103e1f83f3b894d03d8be7e4d819c123492788855d1fdb63f2e000000000000000000000000000000000000001171d5c4909480aae3b110d01c1000000000000000000000000000000000ef786ebdcda12e142a32f091307f2fedf52f6c36beb278b0007a03ad81bf9fee3710a04928e43e541d02c9be44722e8000000000000000000000000000000000d05ceb0be53d2624a796a7a033aec59d9463c18d672c451ec4f2e679daef882cab7d8dd88789065156a1340ca9d426500000000000000000000000000000000118ed350274bc45e63eaaa4b8ddf119b3bf38418b5b9748597edfc456d9bc3e864ec7283426e840fd29fa84e7d89c934000000000000000000000000000000001594b866a28946b6d444bf0481558812769ea3222f5dfc961ca33e78e0ea62ee8ba63fd1ece9cc3e315abfa96d53694400000000000000000000000000000000000063376fcdf64c9bcbeeff0f9f9f9b0000000000000000000000000000000004b6570b4a6affe97649b0dd7a0ad0df160b37c332a8a7348dd3994cc6b1eb65623b4a9f0a3f320e7278844e261546530000000000000000000000000000000005f8fb4cf5e5313f403f15c59c79b9cebaec78291f2053c49d6427f40f2db2aa659d3a8fed7c7b07b7a5680c7b95ab5800000000000000000000000000000000045cba5ec3fa9acd1b11e1f28a01ebc028f89f96f814513453c553f58785baca8abd4150f334b405fabb925b71f4f4dd0000000000000000000000000000000013daf00b8f53af776c2e8c08d55d164aa15027611188e294230477dc1c926102088f0451222fd2eff9802db8b884ab9c00000000000000000000000000000000002344b4be368d3b617df4aa8dbdbc190000000000000000000000000000000002b29192945df0a74eed138e431962f1d39978202d247335ffbf29d8a02e982c69e96b58d7d92528baf5c422ed633f1f000000000000000000000000000000000d52c7a82fece99279de7a49439c0ff8463a637cc6003320275d69549442c95184fd75ee5e7122e5575af7432e5159290000000000000000000000000000000006ddbaad6cc16c9e62b0da9ab0196dffe92253fcfb2df9aa2076d3f16b3284997d6558cc4432d2aa1705452c4e951e6e00000000000000000000000000000000175f906a99c9d65c4647807879e5eb781532db184d28a326ef9691f8738af067b6a80147bd69327d219fad7c850a7545000000000000000000000000000000000c896c3f9d64341ba7c5f8a06271dce3000000000000000000000000000000000c86c92c9598dde7e6fc5e05d70a34c7a14cff5f400f33cf6cc26e6bf6d9a0bbc421c00f3360721f51974d76be43bd38000000000000000000000000000000001137d93502ef32471f47890a181d7823b3a86dbfcadcc930ae53952f528d617e742a52e4f243c615cc28163dc31bd80600000000000000000000000000000000088f7f8bcbc6dfcc8005b8308cd4780d574d8530e95e7831e52eb2c9a88b846852e111a8389e3d3a67accf78b08326d200000000000000000000000000000000149e43fc675dd3bde8b89cfeb29456f130bbf674cea0266bd1b2e7de23f9a7294096327b452728411ca58acc949777fa0000000000000000000000000000000474d97a9cf29e85d4a35f6102fe7984b100000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f00000000000000000000000000000195894e95ca3e59929612e77c1075322aeb00000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000009027ceef3ee429d71b58b84919d9a8d5418900000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab1600000000000000000000000000333e268f0b5b1adf76b88981fc305f03ce4bb30000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000123717b4d909628d6f3398e134a531c65a54e8a10000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000679956d49265608468757580db6b8b1821c2eb13b",
+    "Expected": "000000000000000000000000000000000728c5e6e69b9103d82358cb6ba3a45a677df1c3eb3cdccf694fd71cee94f1e591b8021b0eef638cd9a1d878937b5b2d000000000000000000000000000000000ba9bcf9ccef956f2af8dc4c3fbf1cc8f3f284b04ae8710af6ef4fb36301254c777d4461858fb38fdeeb72c0d8589af5000000000000000000000000000000000224b80a57d30bce4c752664f3b5b5e3443aefa6d4e95dc334821f754b8b8d8fda4e73d03cbd4070d43b18324a686b500000000000000000000000000000000016909a02214c6c0f6682895aa99cf6cf0a22eab6f0b574437ef9c36e9df32ac3b8c5adb9f6b8827df0ccf51b16f824df",
+    "Name": "bls_g2multiexp_larger",
+    "Gas": 409750,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746",
+    "Expected": "00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca20000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b300000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0",
+    "Name": "matter_g2_multiexp_0",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c520000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e900000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a60000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c",
+    "Expected": "000000000000000000000000000000000153da66acafe91b6f13cd739ed3342197310e4824e7aef2e3414654c2678b8d09b296c3f928f3cc489893420031ab800000000000000000000000000000000010f501a96b86343a7c8d8c1250577cc9be6ffec81b5175ed07bd14988c5bbf7f2f3e7111df7d941d0cd267ea191d6ac70000000000000000000000000000000015e0d88894f7f83aacb6710f6c03ae60db8844dd3beec160fdb1df746b1f38a5e23def0893a0b39bee47c97af6535fcb000000000000000000000000000000000bcc275115e87f2f88c4afe8bf4faed46e6ad0c0357884356a26120591ba283f06b464c4853217865b1d2301965f2bd4",
+    "Name": "matter_g2_multiexp_1",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c4250000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556",
+    "Expected": "0000000000000000000000000000000013b49054c3957d1e77ba2dc3ef75775bab9f0e9f76b33ff22e244e897b8ab80ee0749c81eceea259e99b5d2a72251e5f0000000000000000000000000000000012e017e4354ef86f73ec51921cbfdd01e3113cff044a049bdd34e36401712420790cf718bd28afa280ad12104c1851ed00000000000000000000000000000000097f28bee5d903e3c6de14e834d5beea5c847c3106742978e586ba7e913f8b631a69c473aa10e19df9795ebfa3ea6a98000000000000000000000000000000001953493daf65b974b549bb98e735da44b543d6fcfd97176fdc7f6f03617d90e6bb952a607fa8e5791df5dc1c9bba2286",
+    "Name": "matter_g2_multiexp_2",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f85200000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da000000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd450000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a",
+    "Expected": "0000000000000000000000000000000000fada9f43b29abe15693d047adc277814cb94694cab3be56b92312ab7666649b8e9d92aad81f8e487be0f74b9ce8c250000000000000000000000000000000007f6891775811a325cd7f548011ad4c705ca0327ea0484d938ce061c913a7ee6978293c3258c4b865d5c2325816c39990000000000000000000000000000000016761f859beb90ea03aa35e954d112da02daa8e76de80297afde9c29cbfe8ef4d42dad535917685a99b2a91b1f952ae50000000000000000000000000000000012a4f24ab88341dfb8a60c19993b8abea96dbd7033d3686c40903728b4fd4da7d07961f2584b51e9e6c05976d555757e",
+    "Name": "matter_g2_multiexp_3",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e36160000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd00000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de070000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b60000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6",
+    "Expected": "000000000000000000000000000000000b219032a2461a5fd1e43361c46beeae92e30247acadcdd241692abe81691c295ba38a1f0a2a45ae76b1b95d7d0fdc460000000000000000000000000000000016905f64e581aafe928520adc27c24703e7adeb36dfbb416a159cdb9b9a26c9cef0821ccf52f5ea5253b7c9d78769e9d0000000000000000000000000000000015cfff195b2123aa140f963628c41deaf19dfff44d26a38de4547c3d15edef10fe9f65b1802dc374d7ba8fb62117c8880000000000000000000000000000000018dc725cc8d8919a7414b7866fdc54c4467b0f87cf99fc9b36cd65c0ec526e32649f9c57495657a93487f1f2f5769168",
+    "Name": "matter_g2_multiexp_4",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b3300000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f10000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be500000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c00000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169",
+    "Expected": "0000000000000000000000000000000007638fa4e8823dacb40ece440f8f1e57cc5c3851f94357a5325207db92380dd57a7c8709e4d00b670e8af1b77368285a0000000000000000000000000000000005b66a6e6b13ea0eb367a61ffe7c620d9edf5563cb4cc0cdfa68b99d9691cf9a40efd967c1e880238eec313eaf4c92ad0000000000000000000000000000000004f7156c69ea88a71a0af2922d1caca24055d40df058eef02bbf95d864156f62fb0e17d9fccd193840c36ad8449bb4f7000000000000000000000000000000000b8f46fd695c5d96d939d42c65c3b709d32f134710a67909dc4bb43d752521a8d4f0465d0590f30f06ce42bf5f8cac28",
+    "Name": "matter_g2_multiexp_5",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e12500000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000196f78b64fcc342ba4f4edf34a3080ec950532a5de21a875dd061f09351def5ba3b85745a561e38117a14c20d33a14610000000000000000000000000000000003929c2bc55f323d57dc3529bcf6644e61c941b72b424d69969c1cde7a804d157045bbf6d5b79a3e6686509e11ecdac0000000000000000000000000000000000f6b659818510cde463c52cf00bd99da045c80af4d5cd0e55f9bdd81f34169fe869c519f37a98ff20c56db554469087600000000000000000000000000000000129709e97757724e765f6600c2b1928286efab55ec8d16876a2a3210bf9d31cc5425265d0576a2d5469cbd9a6c8c27c012adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c60000000000000000000000000000000009f5f167c9b61a0ef76415fcceff04f3fa57071c2d79f443ef8a7e6049cb1352f650ebd8f358904bb432d42772c29afd000000000000000000000000000000001524a875d73e03c53b92465bafca582479110611bac6a98fc7d76966e9781308a10cb202289c0776cf5c36515733ccf900000000000000000000000000000000002b1acace94a6fe196b217a9aff413fe0bcb55122ce9e344942843e5afba0d5f2cd0bba14c9c8cb9dd1c3e9024918fc0000000000000000000000000000000018e4f85c7663e596182603862adb559635fdf16ba35fbce7278680ea289f871bcf6755d85654b2a37ae77a37e77ba06ed1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000018ee4b4855f866781f38a618c2fe4214c63034620ea5b72361079b0a5c2b2d6fb9ea73fa202db3a2678cf07219cde81100000000000000000000000000000000180870513afef93870ca64e2363fa1aa43a599db97f3b807ada1c25ae331c80b8ead5cd69b6f5a65a083606591de90ff0000000000000000000000000000000010afd546703baa35a9eabaeb45d301bd5be115557bbb4ff2a0e493668ee790e947eeafcaa923f62ca00b8e635994e39b000000000000000000000000000000001089996b218aacde4ccfca4d2f66d79fe161d962baaf2d6696e1a76ea40af4ae7195e8cf9f6417ffd054f20b65ddfb104c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d30000000000000000000000000000000011757ad74a3fb341c8eb6862978ab3fb5e8cfc8fdbda7d82756532a890d61919cce931872ff339843805e00d8c62ec4200000000000000000000000000000000060783a06e93e82cb08e5dc1aa31202ba11676511300e186ae8e45248b7fdec3b7d5b6849f8b79b8f78ad84f36218544000000000000000000000000000000000ecfd8ab18066fe3408fd20f2a4478156e9a19a09b58da76486c9f6a013d861960b6b99bf49cbecfa8c9d01d5615c1bc000000000000000000000000000000000b45709845d35d7b560745375df79fb95df15e85b96cc1b98cc832c74621339c609018d153bff93f2f5493a52b7326073c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd0000000000000000000000000000000003f9de90222619216852356052e9819d7c6e8ff91e0c6f1d8cec832770ed9001db4569fbf579ab16964d76ae7d1b89e900000000000000000000000000000000010b7cf8f0d283cc22942ed73c599115763dcfc1ddc98d87979fc3dce2f33ca3531cc2909d94f86736dda2a4e94a4f0c000000000000000000000000000000000b0aa4d947644cbc7df8d1927cdec66a68862e5a806e25554f27cc1a3701f429fc7097497ad0419e21cc403b472c8ea900000000000000000000000000000000146270ecb66e1763437b824f2ae122f72f20eb93fb30474691a0a192ceb932b1dee111fa44954075335ab360d31ee68d61301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000000c977cb8de4b6e2e33d916f74eb4e42f089d22b54b59fac9aab0e4cafc8aa2b0f8c55d7251662b3499ea140e322dbbff00000000000000000000000000000000106944a9c2d2ecd08e109de29095f3460128bb751051a1f079acb58b6a60b0bb5f52e63d47b688f4a382a77c3b039eb5000000000000000000000000000000000d2f8be1c78995d54fbccab61f816b6ec52dd19aee6aeedc0e4bde2898b2d07c2925da0440a38c4c965a823fff10389f00000000000000000000000000000000183b5d15b243cc5d9584842ab1a0a1e01ad87268728d72aa8c0d7ec6e7069063a11fdd1525d2b30b35e4568da7c44c5495cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c000000000000000000000000000000000f65ad4c21fddadcc49a8f7bc281d2b7901707f51a67122179fe97da46ea5e1bc6e70d68eb4eb6776307510a67e972620000000000000000000000000000000009003dc68cb0cdec4a502436718f066348f1957ae65ecca8d32c5fd776215cb9a098c0ffe56c92d79dd68d251f49f13e00000000000000000000000000000000038ecf0bb98ff2e84b388c58059ba0de0cff3d5881ecf01d668495ce81b76b00323c665ba88309af5552b7950cc8c08f000000000000000000000000000000001924aa0f460659f552458fb469467a2925fcb2420d4fa6249310456853be3d08bd5c37a3f0a9d6e94e434391d20cccedfb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e00000000000000000000000000000000189c3ee691387fbbcffdb147c880218c3e5c0bf78c44461ac1bd3ecd5d4b85225e46cdb068049607fedfcca14882e289000000000000000000000000000000000260efc08531083db2839d1413c90968e87d79bc1a2c730f0020e40beb92e84b73ef43e80f7c61e1a30c0cee11b3cb370000000000000000000000000000000005c852ca0aae2c575c65ef18b624f50a32c007d299f24a3ec6cacbcef1d6e3bdba9650fd7d639bdc60a3e107ee9c013c000000000000000000000000000000000321c01a9de69d6b89db4ed88dd48261ee28facc5e26511fb2833fa45edfb58051c8c3ce9501e8b4c3cab9c456705889bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b8600000000000000000000000000000000183d50635b22e4d620130e0d4008e3bfffae5dadd7e34f4496899ca54eb4d9e3e95c54ae1d9664609c58d02ee5eff65500000000000000000000000000000000029e3b4496a379464302b1476a4549db371f5d6721704b1d6bd35e2344d7679f8a61a0c3b12f287fd86fd247f9652cea0000000000000000000000000000000012c6a3793fd23e955708f5aeb4d6efb670d25a38a67813ecc72f899cd5f926ab7ef198bf6d591328383aaf54f756c66b000000000000000000000000000000001914d3e4b6ea96bb91333468fe8f3bb74636e9a4f2ed198e9ff01b49ba02791d5bd63224f6a38538aceb777168bef688e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d250000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0bd4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b0480000000000000000000000000000000003760958eac45397eca1a1d951a80265a728dc3c584f7dae111e7ce04248885321b69b334b00cdb0334a362676c2d32f000000000000000000000000000000001031e4a63129ec40da5fe9dacfe148a67662eaa00e1fd5c30336462371c167348a10e50f4dc18469a1a6b76485f77e12000000000000000000000000000000001412dbf993c557323426b486f18a91d16b4baa2c497b30fb332a710ac901c96d46a577d04ea87afb08258aa6d204a1c9000000000000000000000000000000000da015ca09ac0c3245c090f39852218f46fea62198fba35ebc4a7f14887943c3bd1bbbfbfa300611e45f419b33988e404c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d30000000000000000000000000000000015c9121f72e2425cc8aa4c878907628dfe75a903b7f756b9e13728372cba598859d20a92a8297d95e1fbe25fd1cd968300000000000000000000000000000000025a3faebfa53918efa733949f914be08b791794bd4963f0c3fd78df48b14ad214374b08299327575c0731b54eafed76000000000000000000000000000000000771782ecd9980da521618af2f9eb55d91d67b20ba615c7b3cb1a48d483ca405fe99a1cdd17e4dc7aeffce586987d41900000000000000000000000000000000136000da90a76d538f336608ce877be943025b4c8bf15880ea9c1c001c20c954292d362dac9783b7bf66b8d51ddaf0f2a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae",
+    "Expected": "0000000000000000000000000000000014cb24001bd933b1d5866cc3de9f4b8479fe23e4fc26dd210f9d06e7a05449b9f5ac4e2f48fb847599f625824336bf1e00000000000000000000000000000000033fdb2e899427f1cb9757022c5b614f08c64b53583486148b7431311a6f15aea3b968913fd5f3e9b624705351074be600000000000000000000000000000000035420be9c7ae3203d0dec61ecea70e22e62f50368be870e74f9a7349453647a7f61d2a42cec6522164cca0c7081d4de000000000000000000000000000000000fea43388e9f6e31d419c7f9fbb9839b4cec04163a7b401d8f7de73a4560fbfef4e272f1db9c9d5b37693378f139452a",
+    "Name": "matter_g2_multiexp_6",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000039dc2b60389f6c893c44072f4db23e7df4c2f299d6b70b70784d9370d9ff8e5413872c227074d429db999d30dc9499a000000000000000000000000000000001702273db356abe7a3f91a9fe4bf56584f13de4069a91daa6c0b552089bef60da98d32c615aa5610842dd8a507f9477c00000000000000000000000000000000095285e8c508ff12da79e16e0391dadbe9a823c586a049e729596864c3cae117305c05f009f9e8ac032abaec8a63f8de00000000000000000000000000000000078fc70e926decf7aa4c2e4b395e88f367757dc47a4cedcd5e632c456a4c160393837196af474948ce6ad53f830ce8aeb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff000000000000000000000000000000000cc3cb5e7b033cff3e5cb01ba29ce8e9f4a93e836ddea7d417f7b07ba8aa71a0efae2e1d7a8ec70bdff12d84d229245200000000000000000000000000000000019ce3c830505324b9bc7cda1fbb328150d71310f06a8424dba861d67a7bc0428beaaf697646d22cae9e00477cc8066f000000000000000000000000000000000f6ff67efefa5636b104a0351c90fd3e89a32b8a9beb0d123d3d6ae42eb5e8bbc19c7a972e27762daf852259c65fce6f0000000000000000000000000000000018d98c43fe5b13b701749f4a5dc25f0e713d241d573639fcc73429226bb131d448283338a909670066045c65789bf9e7064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb3346100000000000000000000000000000000003cf82958d68429503265dcc7d88a3763cca32baefe3c8d32564cf30e8e6b8255d4a9f6a76bce1da473b50287deda74000000000000000000000000000000000bfa9cde6c06b2a2ff8f877ca90b3827d0aa0408c4ccbed23ad461433dad71017d4dd387f49c5febdeafa17d06ba784e000000000000000000000000000000001770fe70513533d91c83449ea52964cd8b449aa81f71e71995db5b19ceddef18e2919439c80e10086e670be669696e4f00000000000000000000000000000000194c20491c9d5ed827cd9d370b9bbec55e4a7b1c34ddd1d80201e7019d9487a747b4fa57b480dbdd09af73aa4f5fa0e9a3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca000000000000000000000000000000000a79d9e0ff43249ff54526c5e1cd55a9bce93adf272508871326c933d526602dc9dae5b6f129a0f1c38139ed1c39be5c000000000000000000000000000000001458b554e0387c1ddb9dee9f4e9fba9c81c15807f496442f4b7210267912b9439a19f95dc80a1e09a0e5cfe750f43c8800000000000000000000000000000000012c06b19ed4e8d5d1b9fed56bc5bdaa3bf0112db997e33aa14899d53e1bddd6aa91dce7e9d25473b66b8578d398981f0000000000000000000000000000000015369b2228e728894f2fd7c2d8c41ac3550da4f297de445cc0f0ef7134c478f526987643cb5408a0bbb79f5f983c085ebd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000016649a8231407074af5ffa93f9db5a2ddce8785be8ee77149602d6afa24ab30b26d2f74bdb5f7464333924a817e242e50000000000000000000000000000000001b990f5ed0b23e113042ff004236646c6eacacd99d1d73fe0c3d9351ce8d622327e827b2c0556802c5657f8f06062a4000000000000000000000000000000000f002a2a5ca90285f9b2fd429721c2daffcae5fe48c571ebacaf475606f96cc8350ce88a850ed75e5aae59d445249bf00000000000000000000000000000000015157fe1a767dabc185a8dc8fea3cb208fd995ecd9acab762638faa987f8367ff7c1a60b657be6e9461acc9df16381e5562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000073884ffbe6deff99cb4b0ae1c0e91e2f4a8c2c7296339b1d7e117d5d47ab055743d643155680740befb379a1dcab666000000000000000000000000000000001995bdc23991dd4cbd973e915a16691fb860490bb54011384c553dd14afc37fe673d13950c1e7eaa29c324fd9304624c0000000000000000000000000000000012197a19a498cd94ecbb3a409337b04e76e1a52715c40203add20eb80f7eac66f3386242d51bea34ea016d778248836f00000000000000000000000000000000101069ff0af2ac4dc7a5bf7bf7b56d82a310d67cebc41a9abf1e1af489e1acef3e726fe9571b4382777573712663e26caf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f000000000000000000000000000000000849b88e7ff52d8136a120f924b20b45ea9ae654a0fa037b62f3c275f0661091038a4c1d6ce7d50512e628b6b397c9f6000000000000000000000000000000000e50e82e9b368f2e316d41febab6b0f626d6588b7217b4e28eedbdf50a4abc9039be9e66c97790d12cdedc90873993e2000000000000000000000000000000000bc5d2bdf06fda1e1d1f5c5eaa7988dfdd790bf4d952f5d3a532bb59edf619dafcbc29274fd3661a35a3f15933b1849300000000000000000000000000000000162e5ce45499e620d0977fa26a291a8e75943c4b5a2a80be395ac9b89767ea5a06606d6b75ee4c8a286d2ea5a197baa5e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e880000000000000000000000000000000005c11afc970544b96fc1a4cbb27259e19b5fd588d1be1c8f19eb4f111882292a463c951521388cb8cb743e5a4a1b57cb00000000000000000000000000000000013dc433dadc122376b75fedc923386a7ba5a363678fcf9edf165a50e160dadcc151b6f402648193d9ef960f5e401030000000000000000000000000000000001893af155aca343bc29989ec2b5a583d020a7558c7663accf6f3e40d0a8eb98ac548e933eb8e2d5fe3550927acc2ed4900000000000000000000000000000000043a79bcbaf07bffe6c6890d95c7e74d127446bdea51a0ba3adb164ea39684bb3ac552020ca28b86e34692c9b36f4384396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000e6946ddc8a9d73e5b140af80cc91b31b9a226a945a9574f0629566f7ee7650730c5ed758cc30442770ed1602b84175c000000000000000000000000000000000da0abb9f5bfcad73b3f24903e9ef887c660447332e5457e4a5764f6628c04d6fe903679b8dc8bb3aaacde410812286a000000000000000000000000000000000656016c01d3405dce9f7d40e47976bc8a84abc370e7e42849dd0bd93ef1da0bc88e428efea43dfea37dd834cf246d69000000000000000000000000000000001939b2c92c8299d7ec1dbeb9f291c5e1c9481e10df10e6ba18ae695a780aec5a185ed4c7e82dc2bb5af87a74552c2ea32087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000000942901572722e5005a9ef5f948c8cd6f557be8d114d2810d3cca29933a94de3c7658e7e28675c2a49f138d9c98c524000000000000000000000000000000001908e8b815e95ec07a90861ce53f545f0cd44aacc47df40c24d6cbc61e7b28fb91cfb1cb3c67b6c5b38c34fcb2ca35710000000000000000000000000000000017bad3616d8e510e325d9166790239c8c817c68ba7fb937fd5fb70a4219265edf6625b52ff26f0a34c0bf481c482b2c600000000000000000000000000000000023ff8a50a9c0e9ee829ec81972386ea012df5e8476d8c342df6b98fa1faa1382ae921c2f1018a918868672450355c44f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000014675ab3efd44bffae321791e6fb35a24b9c07405d9985c685795df2db183ee9dadf18c76cf4095e1e0695dc2c08c4c4000000000000000000000000000000000835f2cf09647061ced2bdf4211bdaea408148100f864f47ff76c0c63a43e44e8ddd9e01709b6ad129bd574d71a1a63c000000000000000000000000000000001017eaeaa6eba76923ff27e5848e5f3b09e7b2b9d55b2cb7068f39defa8628d1c8cedcbb0e1cb5810febc4ccea712b7100000000000000000000000000000000054c873449c738383e9fc2f0f74a6334904171fdb704f5ac35a483ba19a8f661187d36fb35014af9ecf88225466c86e48624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b60000000000000000000000000000000000439ae88636244d5e09607960fb033e4217343899d044b21e61335425b94a5067c941e83e5a77f4b0690e1de037325090000000000000000000000000000000003a67653818cece3ff0390d097f1bfbea9ba954a85710f5c24d1de1893f25f2863991fb9f330e60cad725708e70384b4000000000000000000000000000000000243394c3459a3af236189ec6155418c1916b854a20b980ca1044b48e23b725dab7c60a48e89f642423c805c117e64870000000000000000000000000000000004c8c9fd9f278dfe9f5e24e0f5b42699bb9751b56520827afc2fae8393c690a63f10e92f77c4a10b0c161408da9bf505b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb38320000000000000000000000000000000011ba67024503301ec72bfad101a48708e3521c8a23c6bf2994078690041cf7eb75675cf5f20c8e82d11145e31751a2300000000000000000000000000000000008ace953ed2eaef19595cc7c9fb1806d26cbf1e888075e3985b28f8d93b9c0b4c820c8e8b50fd4e0b23923d428da3efa00000000000000000000000000000000054ee6f7247296e0748d0b52148a97b930e69991a242767d80bd6434d42b0865a64d3ce60953fd2631aef873d8b2acf3000000000000000000000000000000000077748b724301a8bc48efd1cd66086e727e9872e4efdaf55ba90ad1bed7e229a9cfb79013333b50efb46090ac0bdab488ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb0000000000000000000000000000000005008a1d62dad51132ad38a226e8abd7421392414acda61111c728713a2ece284b04d75c2bc58d355bb1d3061415010200000000000000000000000000000000189725b7fc48b8a648237021e9a2334247f1cf18ca50008b813978db01667ba08f00b23b3aa0e015f549ff2d5e5c535f0000000000000000000000000000000010483cf2310f64cf0baf556cb2f2828a1c15922547bec03cdb182a316aa86b5473f03373cf7e59a9a78f73193c1caf520000000000000000000000000000000007f635394301441bdc57dd1f4f97656f4218ebb139c13a17e12839091e2e81327f3353c56880c608de824a07a17b2bdd951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f44000000000000000000000000000000000daf4090a229a1ce946064cda1c4b19c88100c8785c69f2eeec3aed12065787ab0abd797ceed07617d55a9c70ac3020c0000000000000000000000000000000011d77fc28355f61037cae3a8342bdf8d11e963495ba3b5d67055f790b1fd632b23565cad77a3d9968d364e4e2a553c9d000000000000000000000000000000001038d7e8fedea873c864b79d1cf8045485299a2bd4d26c5ab5c8d4a073e2c3fcb38cb230dc6ab7e8e228cabc6ed97da50000000000000000000000000000000009de9209ed14d62625ffbf770e8c528594aeddcaf1aaeedb4f3ca973e7b9f9f1a40370cc74b154f3bc641665d8e4d96b7056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000a6b0dc04591cbbb1b82a059e08b488fd66edca0f2d264c352f81cb6ec45e50f0af16917fa4727ee9888f84b6c888c60000000000000000000000000000000001369ae16bb0743f65cdfc8082dbe0d588cf8aa5406a095c3deefc27eb3ed462dda9dd4921cde6a1d878a805cd144515800000000000000000000000000000000124e08d4de6e831229005663df4e4bd5bb7af56dfb13244c50410e6d0aea420ba19208bf1a774207e0e0170ad3a9b4f60000000000000000000000000000000011b2973743034a2c362281b11a1ac1c89f59ace09f0a53afb0c2ceb061726c7aaefe274f6dc04e5d0dea2b687a00609a8aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a",
+    "Expected": "00000000000000000000000000000000136ff52e440da609b6b73aa838f2eb9791221291b7b14d902458aa7aa9e37114c573edbe8cef7a98dd07275a8c3fd650000000000000000000000000000000000ba625eb47be09ac8cd1e2ec9015640f416af0e3e0e79d39ccac600ea08bdae7a2bc9144f13168a8cec03ce66b9daadb00000000000000000000000000000000095c51e81b5881b009b28006286c704ce3b002e4ca50ac8ea8e574d1e9665a5b1efdd60568d4a4a656ca6a2d1750a39900000000000000000000000000000000143c0c4b3b720fcd0b044a6f420961e2b7eb5f9f1b0d200de56ca8b02709d819f47f0a6ea7d6b49c4f30520586a45616",
+    "Name": "matter_g2_multiexp_7",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000da4cf56fdbaa9004bf8ffa12d5cfb3f296ba5262dab079c91bdbadd6e41ee5f89912bffd5df1643146bce1f0e021b3d00000000000000000000000000000000150227356e48f29443a0ab4536e7a2f86f9e63840e23bbf1b091a59f52c27978bd6a15b29b105132298de45e51134da50000000000000000000000000000000017f5271c97d84f55f8b7ee0d73267bb69cdc7565c470a4b531f9dcd29596eaedf46e61bd79e71e5ade7d000c1c1d81bc000000000000000000000000000000001322812590e6c22bd90511ed72553c1cdb0ba83487b00e3adcb01a9abb438f365ca23fae9ee4a953544253696ddb0bf1a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c40000000000000000000000000000000007c59f94693320b01b56b36f8d1c39fc9e01bad289577738e648771d8940778276cdbfd59f07926e516fcebb70592de0000000000000000000000000000000000aa71d6dcb0b225526eb92b79891ef920634a007b87986fc0f776f85195ad7ec2d84b9bc684add947df8ff42c33b034d000000000000000000000000000000001362cbd6cca3d5c1ec68928be38aca5de1f224e7cd4f5c3ab1c2cd589bbd7c31022d4adc51720bedf2580d2acfa0f06400000000000000000000000000000000162bf0f38e19ddca9aaa370f988be9b35461d2a0f46143e8663f1fa549d0afa1596f029cf2f800b027b90d1eda6ae8a2b54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000000eb12a92fe65f79c646ba508fa615d09d86e582c3337ae16f66cd3bd74a9caa9dc17defb4b4e67ad62f0665c9ad1b6cf00000000000000000000000000000000058b6ce2582c46c0fc108a37e1d2713ff21ec8b1d8c18da0e69f0dfec7f2f327043e174e16d9d64f9ed4d3818a302bea00000000000000000000000000000000068192bd2ebc0a23092bb98c23f5792e179913c4ff1f23eb27296a77e83729803764b8db3b7ba4fe154ca467475eefb2000000000000000000000000000000000482b16e876aa90da6da35e0d7495a04d5b0a1d084c61821f23e1ad63cb1e66ef5975a3cef9ecdf2e696e9d9b50bf9b65849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b712fffce3e63362bcc246da566a14139a3d12807ba83ab3520b0aa3aa20cecd5718e2b7e00f24e6fa705315bc2175800000000000000000000000000000000057a66fb12f27e4a5268e56805fe2b61b5ef019b31fcdd861e2b0beecdffe1a3a69e8d193815f97740324aaa40ce34a8000000000000000000000000000000001080a9e1133f37288dbc3835e45b6611fe84ec4790e23e5ff84a2f72bfa2837f55cae9177e5a3a918adde777b7298a9200000000000000000000000000000000142dcaefd73d7f6342e87fff8c6cd161389b6049fa077f35076eadd2b4aa66f3a1819bf8272cac1c28cc02bb6440dc42aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000034c0f8249d6aefe4cdbf84d151ea9f84add42ade087048bbbf9de4a412cc805dd9b608fdcfa34fa224066b5f06d18630000000000000000000000000000000009e235ce5eb936bae00d3fecead8859e6d909da3d57bbe0a8aefaa5efdc94969a1cb2e12642c0099bca4e7bbf9833469000000000000000000000000000000000b6fbab498c2706f0efdb4effaf79218cf4b652a5205eabeb84f05a060da8cd18c8154a3d37594485ba50a8228f27f6800000000000000000000000000000000130ab70e17dc73f773df99cbe3f978bcd3fcb92a8226a1450239d209cc6969e2cecdc0bf3cbbe9a9c1de072bffbccaa952c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b20200000000000000000000000000000000031a2c10e95b841ecfcbddee4b458385e5650dec9a2d1e50216d9fc261a9829eb5fe894e47f171c8fd2f4d5d89771341000000000000000000000000000000001378471c7f770672ee82b70fc87af5ccacdf8995df9ce48aa9fc2f638105a2fdfa48b615970665ae4869f1e2dc7988e8000000000000000000000000000000001969517c503df5560628555a8780138e4c340d9d49d8fac4a8a11c894d283d49fd06aa81e9f0db8f015d9372762dad75000000000000000000000000000000000f5c2d9b7fc33167a6e9b5a5fb8c5d16ca009282edc05cbc8a048b835b16ba33515c226174d6ce5f9836581611ab403bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd90000000000000000000000000000000000a6a32f2006c4b7804e99011d934ac91b1b3fa6f5d02c574cecd6570bde1e998f135449dfc148aaa8fb8757d0a7299b00000000000000000000000000000000198beb461b59f57b85d858b730fcf853d967a1592e5e5787fd81c6a3d9d9b40c1cd7912cae21a47aaf78df5540604cb4000000000000000000000000000000000955701e84721866683b4eaba82c2df8a89bc906fb0a3cde565d314cd7278b0c56936205cc8ada10b03e69b93c48067b0000000000000000000000000000000004740253653a0d6cb15c76e145dc0b1f811bdc964f7d595b6027bb012b42409deaa8da83e6ddc3f0f7b4b237eb62b537915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000000f88e1e30674934bf1062ac619f1834f35f804a958e82121255f8087ae08f10525e740ee53d7514e0ee7c49e324513c700000000000000000000000000000000019d554645696b7beae881ef62297283c5b68ad3fa9a84a47c29cb53449d33d6ee7a5a3cb83b6acb75cd41ac3f52fec40000000000000000000000000000000004b32776966e52e8a72c88a689d6c56833296d384e2059d8f615ccd3616972074987f839b4689d5610a88addcd836d930000000000000000000000000000000000fd4d21b00d81ec993d2350f1fe360576fa983754a7159c2e81024a00931d84e419e8b5231ba8cf8f05a0ee6ccea7e558fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000177830cf34186191fa295b7f279bc819d8a53452e2114dbfe709971584ec7a2da7453aae3e64f4b14c261e22314027c3000000000000000000000000000000000ebf2aac35fe070403a4b7a5c2f102c67300bfd68af7863b45185b37ade1bc53d46772062189f348647e74c77caca4a600000000000000000000000000000000128dc7846b2dc5c453ba5fe4675d0c22f4d7089624ede05b0910c34ae623d4671979fd73455b35b61a57c51fe2895adf0000000000000000000000000000000008e33a3c3735be035b550613c712b220595a83c1953b24b3efd38c5913fc23df823e00ae5a1c2ea8a8eebbb93c5c721dc29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea0000000000000000000000000000000000a060a957a8da4384e3436110657110653685bb621c32810b6516c690a00c13e37f70185958beb0ed886aae5cdd611a7000000000000000000000000000000000b5afbc85e274049985eac230b2aede7b2df1485c9539a4a4eb6aea406d0f6515ad8bbece7155fb0dfb2123919fb8af9000000000000000000000000000000000afa722987390440a33d5103445dcef42cc4a3c461daa076d56fd38e0b220016ed2bb8e99b9a8da4af96b7da64ba90950000000000000000000000000000000013ea6b8d327191e53bc71fe43fda305a4a0584cad04048afc0480f179955cb27f2ac8791d847036470ffeb47aae36877dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e900000000000000000000000000000000032b490f795ac3242b8c7185c9e19f0440ecee3a65263dd4e4c9a431571deb7339bc6e2d73ec43750f6f027bcfd674c400000000000000000000000000000000076ab4ab3e8ed6ea3b882fde5cacb3bd094567288699e11f368c3f60f4283c5bcee7b4c5debeac541ead983f5936d9f80000000000000000000000000000000012aa2060e421f4f4249e83ca0ae1752dfa2b7ca958821841a18f05071a35fb9c1448619bd96f8a7adb2202d3ffda8eb30000000000000000000000000000000008b24f29ee7571f31ff86574e654a5d849acbe92653ae1a1d2baf4c9ca6e67da4937bfda51a70931a6e60d90162efb4f21c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea47000000000000000000000000000000000c8b41e5c47babd6ea113c0ad9f45a75d1ef6bd313b768ac01e6f581ef6630ada623c1a27d4aadf543af4055de7f6b73000000000000000000000000000000000a0f73af06f8f0115bf17f7c5db0a6bdea77a8e3d8fd0b52b0d4e2c558f1331f655dc272c86d98bf166b532ec8e45285000000000000000000000000000000000499b55964186bcc6986e7744c52babf47e274e47a202abf6f816bc748baf846df2b5ced2a5f61fbb0aa2047bbaf82db000000000000000000000000000000000d6c2a9a3fa5d0524f772cca2c7e72a5f2da1a6a1b9550997e7a6cac5b6b6c37693a01d30bebe4b9c742b63bd31487a1e1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c9600000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000134125416c7908cb4454ce6aadb30df46042ef2a6b4b69b19fafcb9ebafe8b5579046725590266cfd10fa26e1b5ff3dc00000000000000000000000000000000073f4147cce24e13b9eefad7c69b457acf126bf278a58a26a7c7c6b482edea6dca9725d7e5e4138b4ec81bc2505ce2e60000000000000000000000000000000006125caac1061cd6c556f4cfc122df8e949622a46ca707b48ef088ee5623df058bada1bc0cce1399f0be1ee86225f13000000000000000000000000000000000146e398c161e29c90c8a4fc44bfd5b3dba6f9e80ead561fa3d91ca5f416e06318dddcfe5147ab5def858fb025a1562352c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b6000000000000000000000000000000000944942effc77ad02c5ddb052acf86f3a9dc4127dd032181450295464b49ac1dc0047790acb378221fbeebd4c92886820000000000000000000000000000000018e1d201b38d88665696ee6cef11fb19f7daa7f11c5a5ccc73e6b66ac7b89df8437c9f07132ec8b69e13f63424ad694c000000000000000000000000000000001463117fdcf17f28956a42677b3ff431cc17ccbde067b91ecd6fae51e1e24ba8d594ea368d041656022611ad3ed44a6e0000000000000000000000000000000009715cc5add17395b7ddbcb961269fc5d4739d799fe9554b3c9e9f59c895ca5df8ec75bda05cbef3e6a165f7987e78662908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f00000000000000000000000000000000007c07cf1ac9b8b28e3d2f1f4ce22b8ee46e99914ba20c7362c679559a1618a906c6ea65c475ebbeca4947019cb6fbec0000000000000000000000000000000008b29f72cda71e0bc2246ead57b2f758b741b9232d87be75331275a5cd63afc9aa98b0e42c1b82cc258e93c97e596a81000000000000000000000000000000001512548a4bbd537a4d5baf673fb76ea7e35b2977216e7b29a6375e1f92049d7b7d5fd5d8b4ae6191f5592b738e149a5f000000000000000000000000000000000cc9d646428135296919808c6ac10c142e769bf71bc1490196dfdd4e1fc7b84e58155bfdbe77a9e684622ffd83e97ad3e8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399",
+    "Expected": "000000000000000000000000000000000ae9da7d12d0a03cca3b41ad869f762784cacb988eac7ce904ec9ff47824e058e2e211e2285f9fe2aed0b4385949b4540000000000000000000000000000000005b0c873d20f7be1410d39885ce4f79884eb6ae2b2f27510d6f6874dacf2a66c64e56b7aacac61ec88261624936e695700000000000000000000000000000000076c6076175ad748dd68fee64431e5e4ad013797de4528287e7226c3df90233799ed5c8b36848c1a2e1c02591a013d270000000000000000000000000000000001f7f6972121d38ee2d10c621a38448ed12271f7e0e9e4567fe1b5fcb469c7906196fe92c66c37f8c5abc91160fea8ae",
+    "Name": "matter_g2_multiexp_8",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000139cbf360b10e59c20dd4155af2023d5dfe0681c62351dd541cbed41b6a328aa44b862d1985b132a4d4ca61c95b61ebf0000000000000000000000000000000004af6b5a0f7a02d5c93304be0d31959bb4b1a7a5838dc3f9cf50180c4eaf3d32e68c006263d75f9735c8f0b5f811d3cb000000000000000000000000000000001644937e5ff3b8d2005a2f3b0984917837d44412362a121be481526173a4c61e20d61076aa10b4072b79743c5f7f4c4f0000000000000000000000000000000009bd399b55a59550dd876f35e54a5803058228bd6ab6c9a66e438cae473c63816c96bdf378ad426a236b58b90e737831e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a8000000000000000000000000000000000f343e5118d7dc3a38e9975a5f40084ee5f2305e45a8aed28ef105f76345d9f5646b4f3924b92978846b4e605b78fdf400000000000000000000000000000000017e61a2ecf9b3403b43f5a10a97cf5088b4f98e5a4513b0912ea7ecef44e6809f10dee60367cf2fe3e903dd68c2a97c00000000000000000000000000000000039f37f414338cab0e12f99b2aa1e3c02cbdee3406d1bd17c359ba899b7cdcff605f894530895aecb469f41628c3da120000000000000000000000000000000001b78bf69f1b7168d735fb4b7b135fe70db79f50e792eedea23f83cee9b48e092536c2ed146c7499cf047c5b4f0a08735307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af000000000000000000000000000000001342346f1b553e29e661c9f6c0a24d8f788db98262d6af86af55d313a37eeabed1183e367ee3d83faa3f284b260e786c000000000000000000000000000000000960c8af3f7e6587cf83baae447491e73cf41e637e1efd730e3acd9793717e57b85530584942e7a030bad3b91a76996300000000000000000000000000000000166daca4ee2cb9516b5178cefef0553115dec8157f6194d24d191cfe6340406071883c89246c0cd5f89bbd5d0f1ee15b00000000000000000000000000000000187f668086b9b6307899d301bdbfec915cf24ac0be10d6897b0677e4f1de6a241f3dfb19225644858be0941530e67d0f7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e22600000000000000000000000000000000032c3783e701bcb651aef40c91682eda03f9d90f252740612c85a5727f8bcc41a886b328d5ce787031c08ace235ff465000000000000000000000000000000000b0eca06f9fb69ebb46d0af56d3d934b333514d7f31208b4ee2fb92009e6041749028a78246a0adc324034a94503e80d0000000000000000000000000000000019eb24ed35f6c7ae53047814cab14d51ae6cf336d140a17e794d5cf18450b7fac3e6f990e12d340291459197bd353861000000000000000000000000000000001983a596485e657deaedf01614dcd5f4ec515c0050e8068ea02c9833d0b165c0f467107a50da0d8cd43bfcb59db6e710eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df0000000000000000000000000000000002f1c29ffdf7bf20fb8a13363393d5f1cca5dd9af82888f0102030fdda641abd5532ffaa2669c0c4159a989cef1c5bdb000000000000000000000000000000000bd548079899d49cd368bf5c246aa168fc8c777bb84a7930258502c8424a4b68e1ab20dc9ef39c307e52bcafadb0c8e100000000000000000000000000000000070c18918f037d5fa1aa005e2c80ce6a80b4b24d33ce72a2bd824f9a061af1db236f04d6041314310b31b805b8a674800000000000000000000000000000000014422b173840da655aac6ea4b7a04313d5d0675bcd565258c73039f879176e51ec0c8a9deba9c78c33179a5ba54492012c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000a1be8e39a47dbe0bd19b8108a5bdac582e1d11ef7fe28df1f12da52924e734e1d591e8e33ec20c6d5af5bc8c1161fca000000000000000000000000000000000eaa7a7cec93b8d5eb933103b52a35b3d58214feb8e2de0bba3a0e57e7993a9df0dcf8089142f57f8e0d1d303588ce9d000000000000000000000000000000000089fbfb389ba448eb77722994178ee3cfd15a27be4ed6f4d4ab6ea1a4c10d6ee8424beb17d08190fb18ab8498d4a4fb000000000000000000000000000000000ab02df2eb474735e28c45b915299230ce159816419fe9c99a7da397b7210590705262ee14c2a244f4922c35bcb119338dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a0253800000000000000000000000000000000006508fbef44d36cdc6fb37b6324810ab2a1d94e39abdf09d530df34714168105e23a7d6f7fd9caf31f263b658f16b76000000000000000000000000000000000b5bb1802813f9f8a16991d41275ae6d18532e3dcd2eae091da7256aaddd501855e775b779959fcef2822685725cd43b00000000000000000000000000000000052146ee63ae277911fe491420651a96994a30c7d1b19bab32eded008a125369baed2ec5a963bfd863a83c29bc1afb23000000000000000000000000000000000a180d79335347a8be350a92491760c6bf1fd56604d4d99a1c49bcbe50b2d04b7cdde55b4aea8ddda4bfeb8e79ab6ce4146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000015dcdc17a9afbf88b54af22ed2168329bc43ba50d374c0507c790f37f9669d0af167328d50d322a827d45f39724d2b2600000000000000000000000000000000169b83f2567e921a4319fc03b2a7eeefd2aed79914bf608d9e0a54aa71b9cb3e09f1cbfbadaa520c0f77f547fd407ea50000000000000000000000000000000009b7a8ff8388c85a0fe3860f26b09b81b5dc51e00a8961fdba96eb462e1334e9e28a2cdc4be49dd8b96c548c64921718000000000000000000000000000000000243782436fe7cb20a3242a3a21402a43a2c4fcbe77cc7182ee3cc04f4795c269d8a64ddd25e89ba4fc796747b608092de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb94973680000000000000000000000000000000013ce6856b6df48e4c9e3fc0be0aca5b139e1b874de6ddc148c1c23a846d61e7a531cc889bab99706668a3b69d32b9160000000000000000000000000000000000a459676071c7f3065a6dd7632edd5842db34aeda8fa0e7d7a8ea29f842ebcf2c5fdfa74ee7685caa51481c4f46952240000000000000000000000000000000010c1d9ebf7bed9195cf0bfefad6ba45f1bd19a9a7d340b7c630b9953923efe4907bd75a3da066fe3d49d656f3ed91d2800000000000000000000000000000000039189de73332d5b5a160c296a195cb9d8a736cca23a92948d513da7e4fc46e1ed9c207e86751b3cf1310d8a7284877ec2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf00000000000000000000000000000000129821e97c65ad3801c011792f4c099e19919d7d03bf9fcba30b3735586bb7ead7d4f9bd10bc5f0e5cf1dae82d5651ef00000000000000000000000000000000038cfbe45bbdc494988a2dc72dea6a7e36652f5e5a2ecad41b4aeceec05dc4a389e54cd3aab349adbe32e65206eb481b000000000000000000000000000000000bbab53f2be2c471d6e9cbad719a73c00b582d0983e25e1969c0be1faa56b1dfa5b7b55797b3340cf8c7eabc560fac71000000000000000000000000000000000b0db19410e552a2f7889c2204a93c5cfc71c360329e3be3171e88fc7aa1e993a5d089c28b1a8f8fc80d93ba194c63ccc1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b0000000000000000000000000000000019320bb8d29b7b5a7130b87a39e87e271b96656b5a2749f13208520634009c26f9829401d3e21cee5a757782c6bbf9ca0000000000000000000000000000000009b37068d72463e72f3a89b9093c1b09f01770e647b5ff7daa50e0679bb76404cf7729d5575a39f5b9b3b371893967df0000000000000000000000000000000019ff29e41db50c736e12f62d76a28f4ca4f6b0f4f61aee00cc0e9dd4e5a75c0ca965b82698f704c604bb309aa5b457f100000000000000000000000000000000062c352a554dc4bb96b459378c21ec6446e15b868221b2fb745d31dece854bc281bc22827d84ea3b0fecfe5d156712ce6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa200000000000000000000000000000000021c52e82b0012537b57fd92fc276e8de842a59355cc15d69a52effcfaa7cc43dbda0c34e1b9af44c2db8e9356b9c71e000000000000000000000000000000000371a6da5dd39092b6108f631a0f4c4401464a109ea1e5d14e262c8a9577e1421d41734d2c3ed73645cc13ef3988e9e90000000000000000000000000000000004054159263ee60f6b1882ad7c376c738c7ed87e6b34dfb4be2fd7aa29ede414c2c6c3ff098c53f22a1c1cd836a6b0600000000000000000000000000000000012d7af6b57c688e1ce90e9f2796b0e525e775fcb6be65f5d2fbe3d1ce1e5d948dcb098c98d495a6e3dd813527b4635258e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca1000000000000000000000000000000000e53128fa5392dbae9e40ab1ff0149d5b577d9d30dcb85eb5e4fcdc17c7daf2ff1d6fafd4a1aba88d2e7aeb45a01afc60000000000000000000000000000000012972781f214511e9b78d276767b1b64bfe5b43215c7680c0063b6974f703b209b2929470dbae16f9767a7cba5311fec000000000000000000000000000000000cf6b37c5a60851d03752f68eaeaf37ac67c661f644cf507c5458cb5404d0ce903c92ef66a657b25ce07e5cf5d956929000000000000000000000000000000001835f202705c8b984a4c7a6cd219c718ab27a96671574cf7cb618235d19e9046a15212e0da6233f15f18bbe192df29c38efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c000000000000000000000000000000000d01c6e8e34e646911391b012680f0dd8f4b8d77c10192ac09ce57b6524f0eb8c7f83ff8f26d856e0945d7a909eb790000000000000000000000000000000000070fca42e34dacce0051f9e26c7c0dc328fe652110976df6df77af04202831dd095715af1714b60a99f2177e86a3443d000000000000000000000000000000000063ba43df0155373df59b009a8083b9f62004327b16ad455037487c5b8325e7eaf57a4d05c533e284004be6de79ad1e000000000000000000000000000000000870c2e5a7d26ba54bf0d45ddf0a4c3011152dd12a5e01a80e42bc4dcc784c7ffdb66f9d6d69ac445c1d9aa29586245147f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d53000000000000000000000000000000000d84ca02ffb6d3cf6eb27a143ece73d5bf006ff61569f0eab00c5a512c5b46e1fc21e8031d1a578010c9582d75e1faa8000000000000000000000000000000000a41249cf01ecd23d06f6a3bb8573186fe47e5165ec0d447df62bfc236f4c203b4feb8e2a4785648af86646cfb0c4e32000000000000000000000000000000000244fa6caa86fd27e044145557697ea89baf718746711c8dde334a2c5ae3c73d7a0e04fed6289ddfaf26e47a9d26b09e0000000000000000000000000000000017db897060c0a8e3e5d8eca9970407b46dc2c2ca0c004d50a171450852f585268bfa8a379acd01b6d4685e04c0b8c106fb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b55800000000000000000000000000000000071e13963e20eb1dfb671aa4a090973e4a4b7ad3578f8630db8a865847be46c796e6f9e095a9ce558b93d702f8f8572a000000000000000000000000000000000dfc4c89ceaad07e3b4c35d96e8534122ae48421cd4443de478ddf9a8867ffdab279ad745e55c87b731afa7700bbdb110000000000000000000000000000000015dd6b0c26f6821177d0cfebb7f1481a971e7601fb24ea365a0c3127a5b1042eab69446de05b61cb6ac0576752f87aa900000000000000000000000000000000156326c52bc78c82f5cb4aec5de35e3c128c5561dc80da2cb24d68a7e912b1f2dac2078508fdd4ec38769102c082f0f74b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a",
+    "Expected": "000000000000000000000000000000000b537dc10a6f518122665f7d78326a4728a2889325e5be7da7e25e4752c680fd786cdaadfcc426343a9844efbbce8f2300000000000000000000000000000000085ba3a04aa8cea82b95dd994f5b3bdf0dcf63f13909aca2c2d61e4275a7ea22445c953b927ebc6b0987e98b553469d40000000000000000000000000000000019cec2e9fab640cc88073bd39e46cd571324904b1950fa8f626e2725936d80daacce2487f46ad23fa8af9c6ca0367fdb0000000000000000000000000000000007039a0e11cbb8bd940eaf4a192bb94ff8c6d6c79f775fa67821b5ba411641c09dfe9fac4cf45eb5fae52d2fc4beb6bf",
+    "Name": "matter_g2_multiexp_9",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f73a297cd6444809aa11b0756167e71986ab31b52b57d3c0aac5637129b8702ff21ec649541e79644c27f0017c8ae3f0000000000000000000000000000000016f96d6ba02aab604dd918cc799cee61cda4c0164ed9f07d4932fc4ac3eeb92b1e6b40dd7b18cd8d26056b486e57ed290000000000000000000000000000000012156f3ca3aa1e79014dfd92fbb6c785cf0ee449a8920b89ad04355e0fb7c8ea804bbad082b4edc9abd3b24ab0df2b61000000000000000000000000000000000d51b5f62a6e70816d7671bcfc52f11bdac6221a23287286af78605b99ae8bd0c722e485bd0381b958a85f61e05de68368ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000acc52d0fca02c3228cd2e5202c4eda297b8227bf4e64308226bc487e5b64738efa4c07a3738397f90251ea9a1a9da29000000000000000000000000000000000b85b853826a28777a5767d5b1966ce12fa8999ceff5d6deab5c947fd19d19de9c103bb920bad615186d132ec22187320000000000000000000000000000000006b5a83827dc7b3580579ab7976a70ee160b712580919b6f5d4e180165e50f5a1698fa7cc63846eb1f5e6df955c3eefe0000000000000000000000000000000006c2957d8adc55931900145388583e5c2d5f6bd784e022702801c38534d2c92c6df9f95d022aa6d800e1e458eb7f313061529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f810000000000000000000000000000000014e4c5991f9f2ee262019c1344a0843756157dc85aecb15718217a2fbe23fe0843992dcd3953ebe79acd85517acece0e00000000000000000000000000000000076a18fe710aca2875bc102f21782c9649f107684a4edcb0c4538f1a2890a2ae5b46a182d5470e620375327965b6d37700000000000000000000000000000000142a0fb19b28a034d326121458628356561e50cd3a471ee78bade0733597b8b90f647f5199d4b5b1ee6be4e1870bcd310000000000000000000000000000000018f8b5933848813cc2c1a0f079b095d565e7875ba6693eaa10967d496fb47257c9c674f301349dd8f2d22f8857f9d5ca44d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c000000000000000000000000000000000db331d2b965dbc053b01a61e671d2ee6b04b072b6494e482f48f12221f23e3b1ccebf48046d92b4be2e4283c77f51380000000000000000000000000000000016704f3e1ce14f49df400592ce29627833ed1dbb91ae5f00779eef94fe9ab313c3e7c8da940085034e1a49158043599d000000000000000000000000000000001956d492f5764c6de0b8e9a716766c762620ebd3265a95b47a8ad2c0614c337692108800e22abbe321d77a6cc17f4b880000000000000000000000000000000017149865739d6aed0f2a4c3c71c2d02f8080d9339025b03f89a37a165fe6e5a4cbd489b5fc90bb2cc432e5baab213c8424872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000036804da102cce975f980ed5a69e0464241b5de87238f9892c77fc2b6e5ceb00d7a37a45b5520fce5f094f8b9510f49b00000000000000000000000000000000049da8b6c974f2d680a80d2007333f15702f1517d3dc11395662ca1db945c795bf64167840c4df0fda68a69e127b2d590000000000000000000000000000000000e94cc66f1ffb2112e37cbd5b4feb7d65032c2e57260504a42816aeac85648558f6997ef12028655103a8cb9de1297d000000000000000000000000000000000abf7703ddf6995d5c29124ba9a3f890854fe0622d547a4f24d6a60b036ec9e58f7ec2deca5a71e1fce2210cf810e2f901a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc00000000000000000000000000000000008d22e456c643ce680f5ea14553a9c249a43d4f92d94135dfec85bc58967ec01135507bd8ac3954b5876c5bebcc1179800000000000000000000000000000000022029d4abec7fc9ab3bfddf2f462660bef7449c4093144d9b7d6f9e84f4f1c947855ca6e09bbb3bee4db096978ae0dd0000000000000000000000000000000014beddf6a3fbcd621e2a592e1c87952ed277163ebf390896f7c668944d6e0a026d3df74b0fc877ed560527a80b981d1e000000000000000000000000000000001414af918645ce0d4d1f670333fedf286b01213408019e327d3cb9321f06fae311b598c2f78bb578e85692e6cb787a52c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000129f1e25d96b8c879710a81b727b31d27ce9887c245bf908a3768f3606870ca6bfa70dbf5135819d36582d55f230e94c000000000000000000000000000000000e91eaa33e7cacce4e1d6d0fe905c72221b534a72cd51e1de79a25ef0c06ab454a849a241c023b0f82aa07de28e35869000000000000000000000000000000001379e390f2f0f3636312465469b532d876529d58dda8b024b6b81d242af47b5720af4360d5a3172ad80fd9fd8a14ba2d000000000000000000000000000000000775992d5a8ae0640af845fae03dd0b2197699f413f90f6130d21db0dab042324094b36acda26ed86c65821d2d8a29d9e62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000f5420156358ddbabf31fcc94678866f899e38747e79dba8ae280704c4b199a03eb423ceed18b5cba7e7ce84583c84a0000000000000000000000000000000001127669ef3ba3785a859aa4e942e8fc3181f2703b0ece6ddbee8830d7ffbfe498794f1ca2e67c3ad39ebd33e838dbc5300000000000000000000000000000000138113386846310db8e21fb8bfe40035cd89e51736b491d5f2d3cf5672e6836c25f62eab80f25ab49d16dbb83796aa5d000000000000000000000000000000001711d74ef4995b473239a574fb8ea6edc6eb7a88793a093df4652da240d069c5bf9249b58e9b1e11f7d6619cdc28a5787a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000d32ccc6598af8156f1c5b35e69e7c7f57f9fe18748510605a2a81b4ee09882bf3fb26abf50206cd57c77924ebeda8010000000000000000000000000000000009043d364e0637c60223f9a5db8c50e983746fdf4c9f7986d27f5f4f3a6df487592ea42078f14efcb3eb1b7e81d058eb000000000000000000000000000000000233495c4961e71cffc2abcde4007c0d587687aea905f3ac5758d0f8d9020197adb6f9d7b86a542b8efffb05dce997130000000000000000000000000000000015b084e773e66ab1459825b6e6dba055a96e4dc1d94ac0b640e906e0a9f12d2124a58537c458e6e1b571311b93acc26c0f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce8880000000000000000000000000000000000e4b859c679a90c03ea4d4b0b3d38211f685db053aede0f7f359f712e1ae808185758546877502d57200da2c2137f37100000000000000000000000000000000173b24ca19436b51aae22838674c41c752536eada3197de6efc98303eceb3e6e8e47ee6679e61e3cb5c8c734c96c98720000000000000000000000000000000005232b8c97a4860a23999d6ed6d173d300ed50b77c7b3ceb4e8407d9d6877a6004e2f76c553bf458b7cfd8d1e6fd364e0000000000000000000000000000000018a115201e3f4eb308c16656b3ca0635e6284169cee3f28101903ce1cab0659c3d83a449918df6e58e8af2e001036b8d9b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c000000000000000000000000000000000131232788aa3038a6b8a055a896af4f8129e3dd3397dfd90ce86b3e09a775e5b5e19f4387f4c02200a36bc2a1e09d98000000000000000000000000000000000eb8cc0455cbaae97dfd05c1246d3d5ee58c286d263184ae342f5c0ef432355a574bb9fb8ec67634f999b6d1419f2b6900000000000000000000000000000000188b8a85a6b255408f074b3cab66b95e0e1a1b5b8965034246dcc196f2bb84aca3a78907409826370bd65cd4c4d0bcf30000000000000000000000000000000009603984f6d9876e9c235621fa817efe45727fd8c4f76abb7b0796ae721701161b39ff7cab4c57850014e7f1750954ab6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d40500000000000000000000000000000000025809fb06c8a31f31ca5b4a5c795bc93355c78d9a2a4c1d707e32ff2a71d94cc1bf7b709cd5d6a183cb05fb6b5f360c00000000000000000000000000000000127bd8c9ee6388905ffe59bb0fec0e42b4aa44be74e5961dc2353e474baabfea86c41c6173db413ee28681a6bfd3ccbc00000000000000000000000000000000181f40dd8581b9adb2981dbcae27c7e906138569ff41a833ed3e6ee4fb0baccf2ccbe5b28ae2ff8e08c4f534116b58c40000000000000000000000000000000005cdd822cb47f35f31e0cbc26f6c957d51c6880369af94fd84daa1f1ca95e41e240b910f031585842fd2dfb170d618aa88e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000004a06984a3916820368076ab8cad6ffffded2cf1e67ac33f539ea8fc7a79580c1969e55b2a2fe3b31de912d6606c20780000000000000000000000000000000008a1152a581b6fad2a23aa8b0b51cbe523e701193207c896d08b99a672dc047498e565a568b79f8f9188767ba95212be0000000000000000000000000000000003539e82e5b88ef660b6593fdfd9591ec23e7109642f4aea0570f1f8f8e00822d2af277632ba74910459535b35ad47120000000000000000000000000000000015d3441f621c7e6922c489e474f80ebeefbef66cc59e4350b6f803e409034b7f498be2dedc97d902590fc1e296fe983c26989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000f775e13276c2e32dfde955009422557f332fb42dd9ccc3246d2b080e3ec44d910aa734478899698a9b04f6fb1a8f922000000000000000000000000000000000460ee4df6dd0184bcdae6d53cb66967c2213fa878a829c3196664f8d594ca6d60bb2a56f93bda3b0d2e6aac0a1a222d000000000000000000000000000000000fc9bf81d4cc80ba4e4df7307f976c2ec1ea2415df3c263cc970583824cd83703aa994daaa6e5c20450da2ba90a242830000000000000000000000000000000011f08ecbda9a192b232e8330ccbccb16a26bcf4791707f2cf52c2e11a8b3993221666563a772d82f4665804275b03b613d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b100000000000000000000000000000000027fe7ca0fdf1cab9a52e304e55350195492abecce4289b0f1c02235412bb012803e7eb59e23c665ea86dd4f74c35c440000000000000000000000000000000011301ecfc78ada92885bcba8af75da6cbcb448e0c49511f3ea306f4ab944f5bc114e72f473cdadee2d0e84021905c5300000000000000000000000000000000010eea529fd3162ad7b49638a70f6f2c26a6844251b2c2f9f8ba54cd334914e84e5a1ba9c7b4e7a8b9cff1a909db78bc8000000000000000000000000000000000b8a6235a7310d52fc8050bcc484e6ecf299099e193f91bea9db31fae71fbd14978984a9e6de10939d0fbba96314b0a55639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef",
+    "Expected": "000000000000000000000000000000000de312093622aabdc7523cd72f568060f4236c7287d61c3372bf81d9bfebfda2795c3182d508f0268d8f445f6ea0a5f3000000000000000000000000000000000b027f117583406916a8f139d47227bbea28502ed0df91cf0841345435376c944a587c3b4bd60f8ae0be7c7bad1c8199000000000000000000000000000000000e9a7b96136b26b0044b11288d35969c17146241aa529e581a8fcf000c33fcfff2dfe1e55c0fb63f6032d0b6b0cf81180000000000000000000000000000000002a442e740ee390d87ec657fc218b76adad7f6a766cbe8f34f4824ecd1587deb3706af77a95c1d5f8e79eab1dc482c45",
+    "Name": "matter_g2_multiexp_10",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f54bcf1637d03854cc2b785e52bde25de7e45308048ed8ec0169069c2124871782bd9d26471014d039c9aa022e1a99d00000000000000000000000000000000106698139b096a5a79d43321ea64adb783011f04e5779625c9f77e5c390b46ef0d249387e978e64529bba2db8d7aef2f000000000000000000000000000000001668d5261a4ba37d79c76f44eae9ce2aa3e216c5fbf6cd2e90c6a73cebd8b59600303afce70de3e83a08c20de4609b100000000000000000000000000000000004b1b122cb55e688f8297913b84d466c6f3d99c09f4b039660238c8bcd0b7f6977851a6ea4b1deb01346db06d75180c142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000192a28dbc40d5ceee4d33b5c2778cacf8c3ed7d3227e7ea0d6fbaa7cd4a81134b63415f4f1960656b1fed15023ce3a4400000000000000000000000000000000138f296c45594a930b949756d0ae14dc9a720bb2bd9e93c7895268121a086a9d55c10135962a172c02da1eabfcb8caa20000000000000000000000000000000001605ef8182fa13a09a6b7661472296af2b0fdcfd7b051e7cf1d9e6d7c7f4ad9521d7732733399bfd5d09a088f25d215000000000000000000000000000000001928f2e5d47d7273e035114cbdeabaca724409a56056b4e95a4ca3b2222716b3a5368da3ed406d73f43e9571d1e04902253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b70000000000000000000000000000000009b35f132a903579d82cae6a321c1ec7fb0281c3e82e9af05c3b2830ecb4a941da5b1637c1bf0fe9a39fcc9ceb0d09d8000000000000000000000000000000000eef9c0846064c866ae07b3709091b8bd48bb6b20f995b44fb49e030b5cb6d78b7f8201704b53697190a5e36e9a4541c000000000000000000000000000000000a98a5d0d5640d6399a3580036f0e5cd693a7cfaa26438a00767d5ffc0777b83c516316d9cd4597cf8601544038f4d9a000000000000000000000000000000000e59541068a62f105a0d26a5f79fa5fa8b41b2211f1fe674d84dd853663962d64a7f70e785b51ac3cc07267c73400fe6acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000157f58b1c7152a7f931bccd9a79073967ec28855a6d74fb8727f59c5e3728fbf07a5032dccb28eb8d8b24229f2dc1880000000000000000000000000000000019f41bbbb853edc1fe3ee82f901e613107dd4ba1d880284ee95a2c4cfb2220ec1408f8bff14defe59775136bc75b4a1f0000000000000000000000000000000015538789157505a0798aa36fdd171e0bb14bdac75339b35805807c18bf9175d877360748f97a8570754af0e28e89df660000000000000000000000000000000010500aaa99216aa979acd66c5b0cea2a6a973f1cd10c412e823c61cb897bce54d783a6c0acee22cf9052166a4bb5adb8d9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df000000000000000000000000000000000439729e13e6a9b5baafdaac65783ce79a5972791610a333224e61104d15c746d7cf8350e619f0f72cb73635f6795c5f00000000000000000000000000000000092e3c976a4a5424b09e50e6513a9e1f427356ce161e742be31f0e589e9ff862460d41281f0bb2d27b1837a70a5938fc000000000000000000000000000000000e0e51e92ac3cabfd999cd72b67cfc488e150b11b18f9a31b1c2338fd4f2c58937521b5a107752c342e67666b99fc42500000000000000000000000000000000023d8884aa3f556e98e006960293230ac966ad18f3f715e6ab31a6bf0872c04e6f115fb1608cd87ffb369ff31012a11705fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c34000000000000000000000000000000000be33bc145611afdbadc636e9d7cb7e3a9c92c32f6944a2b7b5f44c248a0754c174e3286ad307fcdb2ea02a3578aa588000000000000000000000000000000000457de1fa8642d302065319b1d32009c64e7d941fb43d1b3cf455248664b1db516379df87aee05a651c132eab8aaccb5000000000000000000000000000000000a711f3bf1bda60ca49271e8a3143330cf924328d3ac6f7a802c15be1d7413e300f398274f338e6bfd0225cd8ba25fff000000000000000000000000000000000a786c5c7b4f1701e292aaad9b2e47bb883409aae0c44ae813ba48f401f4e2146ea0b1d85f2ce862b6ac9ad3015d4b14d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee1190990000000000000000000000000000000007d03697e195a6b714fc9785b49e54e219694250cf5fe77553434eeced15422de3985f8c736996c1763d4b9248a7a7e00000000000000000000000000000000015841a70a168d2f356a8ad929e2d1433b782351f4833c51b50f3a1af48a85468c2ec02699550d21bd919203df73abeeb00000000000000000000000000000000170902520080c46faae2bf35de396d56921bd0279fc889f0187adbabb9ae52b849269d8097d5b3f331dd5a817f9b2ff40000000000000000000000000000000016846a000f037eaf5953b7c4b477e441ca4fa738895aa24dfb0ef01a4c8fc21a318d40a9424e151380084578ca413b3344c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f60000000000000000000000000000000001a50509bfb12040c0271b231c566d13510e6ba84448e59685f5bfbf5b008fdc64cd5e9456beabd23ac011b071e3a5fc0000000000000000000000000000000014a964c9faf1752170ca40cff1b9b4fa17f8d2b56a4c4bd7ffabb65798771cd624ba61ee43160e70731fb9b07af8ecc2000000000000000000000000000000001822ceaae7bd0a734f57b67e4834cfb00a6b415459d81c7d380a2e5b5c795eb1b6d63ddffb1131cdfdf0d76852c75a70000000000000000000000000000000000c5a1575b30e5470151ba055f577a0ea49cff869614c50194829e53a3e1a95847fa387a0f45d537cabef3a5925e61c432583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000018cab86a0d70fa30b4df3e05a91eef57f6505cbe4bb7284de56d420ef3bf315be9249eedfae92561c643bac2c92301ee00000000000000000000000000000000098ca598ccdffa9bc9d464d51b46ed8a8f22a87ef408cfa45fa7f78ae2dcb9f861d9d6a571f6fa702a71e783ee3395cb000000000000000000000000000000000c073c0a323c3051c302c0558463a5c030539d74b440fdcb16b42ad5ec097e10c16bd9a651d149dd719fb1fb865420a9000000000000000000000000000000000164e622bfb8ecd5eaf691abad9db38ccc64ff0fa1784d26db8c8fbebc929bc6d4dd471321e01233d55fb4a9661780b5506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000fa48147388181e8d0033004118848c50c6425f2e5f91945a17abcff4d11928d298c092d60184e75e67c7ddb9eaa8255000000000000000000000000000000000c535bc54df050c1ba8d858a346d3a644e03fe24873b7dc3e23518d44b06fcb3f52b4be6f11d3b66f0180a0a95dddf680000000000000000000000000000000015e279a2893c205dadc8e1cdebd9c85454cd4b5d7537f984c8f9d451f8316620279357e218fef87339f1728fa317fad5000000000000000000000000000000000316e343ba68c8a762f4c8f2a5c20f16abc4a7a8365556c1625df832219670619b6dc70727e9bd9a64ed491dc22cb9d57f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca60000000000000000000000000000000011dc72100cdf676e41f21015fa7c57897da8260609467ffd38c17868a4dcd2bd5d4d72e89cd0db2de83618222ea3b5cd0000000000000000000000000000000007e074f73287faf304f618478566b91c8e191b229ab40743081342e676be09c2523681cf7ca6f7a396f8589a4ae18a6d000000000000000000000000000000000ff753a16c16bf0dd1de9fa9316694214aea6f99b81f66b6bffd58837c00d7f5632ed5f8f4cdf32ec59c29241ed5e28b000000000000000000000000000000000851e26675814612bcfa639fe567633e1960578a0c8d2e6568418f633eebc109e6c8af97e77bb28ddd47c6bba8a7ba724429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c0000000000000000000000000000000009eb2f172db0fe9ac0332381d929fa200a97047f6e732570d23fe27f5ea3013fdc52fd0b5ee74a4387af44647b75f956000000000000000000000000000000001355f8e1cf45443855f2d62dba0fe45b2bfc4e0d06aa7aec7e4f7f9c4e25b33d9c46a01c224517bac9a1390a9806ed4f00000000000000000000000000000000179d47a62a5c847f47341b1ba58f2c3b073c5282f925f57efed1fc43db04185955075255e4e4f6c209757ddae59101dd000000000000000000000000000000000ef5f74d4b13754ceb3b468879f1a8befb8bbbdbb143eceabf2dc8e68fe6cc8e1ea4f3eca1b23a1175c9f5f5c4c20d3454a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a0000000000000000000000000000000005b70a4d5b91b85971aef26b1521e12904b7ad224f25e31ec6ef59856cc702043a3eb975bf21dc8e4fc55171a3865bbd0000000000000000000000000000000007cf7c3e75a837545b53ca3e175a275dc6fe42fb88678aad45910d150ea9c6c94eba615429540348bb2ba8efacbb20e60000000000000000000000000000000002eacb469f5f8ee6c9f557a6ddcc854e955c5b9203b4ca5dd2e097d3e021479e13629863eb5ff17db46a17d3b0227f58000000000000000000000000000000000905e66f3a051b304b110a8682169fa749ba0de7763d3af7edc3e40f2d22ce7b6aa00cd06d2c82d74f3a9709d955f44e13814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa0400000000000000000000000000000000060ac9ce51426d360eff0d911d9f97a86494340bc5c5ba31ef146b55ad3633ec57a700f04b0cb9d4e91e13c2cc5e68a8000000000000000000000000000000000df205ed85e27c25ce27270384d7c3e58c4e0a9f214d74cddfbc7904eb3115e7bf204375df7558c3e65f7a81a942c5160000000000000000000000000000000007a220d42ca8906013479442d7204457b3ff37c9ee70d64f9f6858ba788b7fc13b71d33ad527c6fc673ad8940b0f01cc000000000000000000000000000000000ad481ef549de13b174d82fe88fa57b7e31ecd8999bcdb0c7a8735ab619a13b1e684b9473f0c59c734567cc08c76ecd6aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000000366f604228e2dff2348a462c56e0043037d1b415ffaf155e72c559d185c6b0a0d125585d060f159a8cdad959af631f5000000000000000000000000000000000f69e829a0995914ac122299d4424b4e2e120fa4913939d2f18f9d1496e7255d00ff0829c20521ef47bb0dee06c28dab000000000000000000000000000000000a3efb4a376281a60f5246d8fc10bc23cbb9cb71037f8f57271a9b01f5e0340a562f9acf0e9a95b8c65ab7a5cd95520a0000000000000000000000000000000004a4ec86e2b04bcb35c7840d85cd1dfaa88e17ffb557ac591640ed8e563cac891793b92e349a7903c6c1f88d26a01c88c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e000000000000000000000000000000000f5bcc27c243ef65dfbfc0de6d431706ab20d6cf6408ca989a2bc1c52b78ab63de6f58b70bfcaf6878a2746f249b6b160000000000000000000000000000000016a4c9e8ad0634e8afa8606a1a7bd1d8cc0815dfc6906b6e6446e0ceddba4a4a2df979d27cd07b8982a12550bc700fce00000000000000000000000000000000051f8d972362caf0a8a39045bb468112f2e73afa392079f8a4dc4c3a3cbb8dc224c21b6633a5ffbad08796ba2f8df44b000000000000000000000000000000001825aeffda04705ded9c702ba30d24b9fe8eb7cb106ee5d4e4ba029dcb57bc42c74e74e92ef8360cf130590b838645429680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd",
+    "Expected": "000000000000000000000000000000000d0ab61b29ddea1aee0ca4e81b5369f37cf45be383f64ba0b1a5a74b790d7264016ee671959444c94b8e6291c5158ea90000000000000000000000000000000000152bf3709c56b3add8e3396d17abcfebbcfeb230529ea8144d6a120a0a6aa83cb284e40ffb9fd9a96f8a2f7244212400000000000000000000000000000000041f516a7cb2a7137746d028b0739c79ffd8f7535f20ba3728ede32504fe058baaf684cc7677967aa46777818b1fb6630000000000000000000000000000000009f1035729c55cf6ee090983a54d8c0574bf96342901f471a2e5380f11f235a075b0e157c38c456b6eeeaa10b87d3afe",
+    "Name": "matter_g2_multiexp_11",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011ada4731ae7df493e405383603a8d79ef77f3fd14fe7b8bd9d2afe068998cb712e84927d5e6ea6e94d7f10270fd193b0000000000000000000000000000000008a14eddf88826dc3be792a0c1f7395efdf91454cec7e26c89f6beda37b194b706dbdde8745129e821b6f4b4ea6118490000000000000000000000000000000011c29513e8a826e6b3eefaa20ad841605d04b813cca282fe02dca0f588b9a579b2195b0b080cb6d12c1a7881008117f8000000000000000000000000000000000689c67d05ca379367fec99439e3806f827218ffaae995bf38dd8e2919fb2e751f426525cc2c6ead3b9aff2e377fc99e1ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e000000000000000000000000000000000dd683a8e4ad54b1a95826a3000750c6e3cb250ab5d6add63c21b182d736b220d917d4e70044ec7101c3bf8ac620e1dd000000000000000000000000000000000f3e411cc6800b304fda1373ffa60c7718e20bf3e2e5f9784a81b47e398888b366e1f04f48f5aa070a661b5e2148d4fa000000000000000000000000000000000b0f8d0b695e000158ba80881a9256ed9dda5a7f53b550bf3b5c67ab160060fcbf5ce07fe38253ce037abedf4c6f08d1000000000000000000000000000000000bb92d407c457e9ea7b9851770d2743758e162dc9cdff2dd54b8271c046f642729cd2f10576013adac84a46d38623b932943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b100000000000000000000000000000000023e880685aa69b3480bf2b7f2aed1181e094322da9e79c9263d50a49ba4fca713740bdb55886fc81c81a51045b35139000000000000000000000000000000001707049fb8b7ad278be2949b9eae2e28bde9de1d9eb964eae582541c2d7a8afc4c1489624a0919047a167028b8c77e3c00000000000000000000000000000000062dbb2bfce2f67c32b87ec2fa01ebf7deddfcbeda2fcf0ef094b1be77b7411f657e745350b6d2da16fc83a96f6f20e500000000000000000000000000000000062daeba038c7bc379f56ac371745b91fdfd5b4cbbe50d9619bf1d077f3cde966f81f9b851ebd206f2609a780b6dbd681551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b553826dd9e2252c9da74c2bf1bf850df3f9c37439859f93df3fbceb7cca4fd949dcaa7fff31c9e06f41e51ae0b30bc00000000000000000000000000000000187810711ea5911a437a62e2ca483983bf2535ff9301a1cfe1b4d41902ef689f8d86f817a2a7c77128e4ce1ef6b037d60000000000000000000000000000000010170cf5f2ce08211cfc41bf54cfaa16584f833f7b97b2f6bc436eecc56ef44463690ea1f5c8c2a8f69d93a25206282b0000000000000000000000000000000001e627a68dbab6b0d05c85e49b966a769461ec38c38fd94992839bd0d46e06410fa7a48d418d65a8285f7852e8af4b318eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f0000000000000000000000000000000011ebe2edc3de58a57aa9ab4d6626d7b93235ed24efc3d75c1ecae376c00beffc5e89ec509d243f693d327f7a4551921f00000000000000000000000000000000088ca2fe0651e4d8f3958454640a58ea1cdd804bfd2700bb1bb8e26ac50f2d7fc8c292f94b0bccef5735c4548025735400000000000000000000000000000000154936de8932279cd39ae803a5d814864953f647a5334bad958222de765250e4bc847e02979689dc9cfe1993486b5750000000000000000000000000000000000c7ce07c9746c6d72dae11e243acbe12dc23423f870f3130b244eef34524d547fe0b2c4b704ecb6b2e6c32f5675ce67ff72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000031ea855125d75321a2a86a93e72fb3869dede7531dbcc1cb07ea2a352f3c6cd913275d0d43ccc370f4539f668f205f50000000000000000000000000000000006c4cadb11361f164f5899c6b57c0c6d8af365d902f4575c9d2d14dfd880501ce9ce218544b44bf07f0f04ed68e8f315000000000000000000000000000000000131332638026fd25b1a849c984f9dedd71e64fb52a61968666ba80238673077ac00b9e09817426ceac8c308f475303c000000000000000000000000000000000c7634af796e7aea4d4d83c9972fc822dad951d2473210ad82706ae0aa023ea85c1c467bdda68881094ad2a4f54cb33f331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000fcdbf0083065e13deee2020bb6e47cb9e482df3768ce569f1f7c0e1c6083c97d9f08444e67857c2dce40e4a7b8d50cf00000000000000000000000000000000010f246e8ffccc2e752049f638617e122773a6f10220cdcc0603d24f1a94ca7c100f8ee2d9bc7c0a931fa0385eee456f000000000000000000000000000000000f8b68941df75cac3d4b6b3bee43fb357c8f4e56309d8509fdc62620a085d7ee58f52c7dff28525a449cabfd3b7ab3dc00000000000000000000000000000000019f934ef0c7c40786b073d38cb3e4623544cad59cb63440d4a6e76944d491f6b982e3a5e84124996634687d4618418316d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000c0208c1f3653fb3a5e2acbbb42f2598b22db1a714d616ee6bb501c3338e80db34d517c7086d43ddc77e0134dc5a4f290000000000000000000000000000000000a528245342e44e36f8e02e7259749e63ecfb38cb0609075e871701f2b3bb0765277b78d28cc3ecb7aa8c9e3b27eaf10000000000000000000000000000000010446583a905864064400f9ef168a122d179d46a058525c9be8a65a5d2ac5e967d51185d4964f81a5571123717210d050000000000000000000000000000000017da91a1d0358271b11a0aa524341ba1ee8c31bed15efc4c9183d60c6e1842ec4383070a09914fda991a63d55efa8f2156be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef400000000000000000000000000000000176c64efbfc9958b9c8e71b55e9fdf525d4e5a0265ff01ba95bcd5c6093bd063726f8e277d00b138fa4d8c8f80afc4e200000000000000000000000000000000183eaa6c3c605828852ab5e8a9432bcb87411dd18d574cc2491f1a280e7a267ff9ccc80b06c22e95107a72f22ba2fafc0000000000000000000000000000000013319d3a8564ffcd6fc7accdded740127ef205e8299b390d21e96b2609cbb463569c878f36191d43927868b06dcb912b0000000000000000000000000000000000fbde0ad8e89f5458007ef6ba0f01d0aba04217e06745a5571eedaf544443150f59117b56937f533b4974e5d57c41cbaea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef000000000000000000000000000000000c044a5116e175ca1d1ae59d400de24e4f47132251b4b3dccdf458623c36b4d3d83abc644a2247ac4d0e3f195d12e7b000000000000000000000000000000000048dff6bf65f158b19b992167ff8adb5c858a154bd68bf0c84e41351bf47a8f870cc735d1be5d9afc62bbcda2fcdb1c20000000000000000000000000000000008c5539746d2610eea22e79b3fe5b33a47fd3bf9991d34c6f9d824a46458480b735c0051d7b4e4909fdb1f2a1a4e4b3a000000000000000000000000000000001936558ac97acd903a29d07c4aea399227ea13fd6dea820813c5519412c157e1a477fcfbab60a787c6b3834eac4522889ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea0000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000908ebe27a1bdf0b9e56325c00ea3814527005793ea97eafec541c01cf2d7c909d2521a5fd475589a31e297cecfd5e7000000000000000000000000000000000017e3c40c60cd369ce5a90f6c4aff14896cf73fe06432e71940bd8086e36c2353d6bf9dd414bcf92889887e2d49fbbf5000000000000000000000000000000000ded856e5b2b139487b3816351584f06582a933af2bd4573a89aab0a41af01ec1cb928a7d8035228302032d399bc7caa000000000000000000000000000000000833b77c5d5c98ad95a144c0f167fd3bd62b03f4ad721561ed1d84c7137dcb19521f781bdd3ddc22afdd52c75146e101f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba680000000000000000000000000000000005c6f28c5ebd981fff3aacd70eb18f134bffdc8507d1a3aa153e5787b68fba7f4a94c43045d2676aaa992754783ae87800000000000000000000000000000000148ff39e8062bd488accfead42a684f781c4ee579af6204b5b8dabad9022b029139b1f3670fc270710ced9a53253850c000000000000000000000000000000000ff50eca1a92f123e2534b3289f37ffd5d4e05f7678017ac20e35c2deca054dbe376c5529cddb5e58973f5c60914f251000000000000000000000000000000000b58298ba9496fe32891f4c1cff25395ac5a447205cedaadda4dcb929260ee55781916ef5e4e39793fa2831142111226d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a00000000000000000000000000000000184d156f881f7d10d2f196b7599db85ee826c9c95383978ed68918756f642a2ed1c951503251b0778dcc39598d79fc8a000000000000000000000000000000000952168761380e8fc90a4966e94b8d2b88a784f6e607c99d9af1aa902506f59d6879153339fdb7b8acda178b9bce4ef90000000000000000000000000000000009997621d4e17c76b7798ef2f99d3c0a7519cce278cf718789cd8227b2b1459af7fbbc93078aa0aa361167b1d1c9363600000000000000000000000000000000005369eb3a77d2e26f9907a2d930f39dbb87634346cf10525733aac8ea10eb918d4043d2a05ff8e80b9c69a670e17f15c629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b0000000000000000000000000000000019bdb390c66f7d28cfaa91bcb34c5c55bf93a9f2345ea396f18ed33ff2221a39cf68c5514fe091f7882e82470efb1fee0000000000000000000000000000000002d0b48d2c0377b0dffca247b7625f9901f86e2161626b4154bc25d6c643a48e9addd260298bedaa80e42caa5b9fc5b10000000000000000000000000000000018a2b0a760652e546eeb42e857ca48f59741eed91822c17692e9c41358b213c82537c9c6898713a13a241cca627a7dc400000000000000000000000000000000079c02f41fca45a56d9d8e305141b4fe8f98d102197e7864065d342e6b07f65b62632e0c12660f37de4d698c0df3d0f3c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d5000000000000000000000000000000000c0448fd4ebe9b5615653336fe0a618fa281b0fd7d72a8f956a5fde84f7d356b6be853bf823436bc0b61a603636db9ef000000000000000000000000000000000dc4f2b4d810c4290e263098576cac393fce137cc901b3be23507cecbda7d86d18022cf8e1a7df4b1298520ae5c9314c000000000000000000000000000000000a39413967b558dd8a6b2bed972687d984fb9abd0662a266680f8c90f1897e2aca1ba37b41d7d3fd47406bc5fa3c5b7f0000000000000000000000000000000000550fcbe5bb75afdd8d5f387798a8e83a8dbb6da4918c24eb2e5d2d8acd3512f6649a4ac9c8d3e6794e6f4f8a87687bc685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7",
+    "Expected": "000000000000000000000000000000001654e242002aafa89c6fdb9e8fe2c197ad2f8aad11868568dd39d68ca35919f94308a80303655bc83fd130de6f9723a900000000000000000000000000000000062b5a064840a5a28b4991ae949f9508586447ad5e8c463593503c0e5857c5233b7ce7ac03e555c2675f2e320e8cee6a0000000000000000000000000000000017d65fbd7caa69629f66be8b201f53baee5ef2957a3c04fe384ae82959105342b52483eba6bcc1442763c677f515f6cf0000000000000000000000000000000002ef8f8ed1114cc9d299e59003c61d62edf8971d65b1b621779bd7b270c4123eb629f56dfa2e2723501588a0caf1847c",
+    "Name": "matter_g2_multiexp_12",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001392409b92282bccbdaa0268e1173e60911754eb3cdc28a52e93f4d82ec99026f314dfdc59b39a4f988100f9c30cbd1e0000000000000000000000000000000016b3c555d5c196551ba715c6c334a668bcae80f5a17f038038d35dce34843f79968a90e2102f0faa22a93d3240b58d490000000000000000000000000000000002daf83727fdf45dcc1a15adf47de3f8a1724cf4d34116f52106a9e6b22dc24a288e89b940cc57e5a6bb87ee70f680a5000000000000000000000000000000000446009fa3555e4a056a820efa7da52117c15eb105af57985d8e9b33b0b22fde6aef9bad30480c2b8c1246519795f61fc067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000000372ead514d53007690843484c966361661816e0d3949b868176d7a9bea42064f49113a74f2572a6dca7afa0642fa5000000000000000000000000000000001199d3ea66fad87074e62a0b77d3fb962db17dd948f30c38f5beb0e44e1cd11d9172b878128e9a64a08394f13cd786f60000000000000000000000000000000018b7db157bb326ee2f72d4df2b1e0ddf0a90401ccfca1d4ffd6379c62acf5d6e4176a23ded2f81653038d56d848b4fbb000000000000000000000000000000000a932cc9740812c8bde33b68d94220690e0f55618b7e51d3e3fc29d0cb9a8d42b8f8e1efbba5984c3c1007c9a80fae408b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d0000000000000000000000000000000015798d10386f6d24caed3859875be5fb1a43ac753f725f28da6b3583bd9c0e404d36265c2305d7d194e2ad84bfd2bafe000000000000000000000000000000000ef2ea5f3b6e03e3c9693d6db60019f2efa4ea586bdb7623f03bd035c603e8996ef2ea7cf745aa31f60679ca04f93875000000000000000000000000000000001792a66785a3087a80c4b8652c1e4db8f602cf75c1a6955f480a977f92ea262965dad84061f6045177c831dc4a3bf8400000000000000000000000000000000006ea3862318974d6347639ec0d70afe748f4edf32b9e437fd98f38eaf72168a153cac180c2d67bac8a358e3a4d57a2b32db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b00000000000000000000000000000000000974da7500df70d888d5876e7c61bfffcdf830b49bdd40edf65a2ff476e9add35eaf9451a2166e9781805192ffd7ac000000000000000000000000000000000cb2e7152b5b40758b18caea356dd8e095f400282881207c4b79d10d741756e526be261b98b726d5cefb668dcf73a0a00000000000000000000000000000000014aeebb995d464f4d77bbb72f15d9078936b5ab68eb8022bdd97d050576dbe46e6010eb72250c8ccf2a59138efb38f9d000000000000000000000000000000000cf7162768e8eb50e21d3c0a076c7bac4920c70f334336037fb40e57e0efa91eb025356ac3f0988a6b127408a02eb53fe2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000b1d6214796b4775c2b50e634a549ed104e6ebc0e032967b17eece6cf88c93aac23059f263faf3c3f38463270320135c0000000000000000000000000000000013ffa3894a36226664ff53ba9256d39c6312303f5cbda6847b4f68c56134b7d731e74bd711014fe374f909a081a7d02a000000000000000000000000000000000ae4590cdcb1367392635d0f8dc6b9557abd16290fd1abca6da354646d8585a7c9432978dc616e5fc38cd71d55f139c200000000000000000000000000000000124a7b5574ef52359b4beabcc56d3286db8c8fe4ca4718f75da28d89a8a95efb878c18b48360dbcb6fb50a9f18f0d559aa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a00000000000000000000000000000000148ab0e847ecac963f0156da025dbc52e765cd8827fd55ba2969da6775649529226ab13ab8537ad0b89e8f1ebc8648ea000000000000000000000000000000001395b1adb6a56b91c3621a4ac5886a7b13ec00f1c74d5317eb74a766eae655e09e269ec48cdf740abc38f4d6fe52dd0f000000000000000000000000000000000f70f77f07ef2909033665bc05cfeea7df6ed55f2f0b1b87d9f247b6c07c7e22f516840efe68005c3953a2702573a9b400000000000000000000000000000000166a334a711416cab180cc498308487b281711f2d1b832c410ebb4c591af54b154fc8c8d7ac9a49a241f7a3840acbc75e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa0000000000000000000000000000000008c19bcbdc2ef26a30dd88f3e35dc7fbb3c81c0224cbcd6b12c90883f3973bd7089636f997e5f213fbdcb79514c551c600000000000000000000000000000000058620cba8ed5b738167e809cf71392aadfe8f384a4cf397d10f674cfa914e9e02bb1518e42f16806214fec52d880f6100000000000000000000000000000000048ac1120d26e4173bb33a58c0ce86329cdbe9df6a6f268c8d5ee4f1d6110f9d81cd50c46256198a2462d50be3e781270000000000000000000000000000000010af13ba791d554720f5075d46d03b55c0c1dccd679cef5a7d439ae868d3ff2780cc3ab151feb72b8b92905a205e630449510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e40000000000000000000000000000000005c30a126c94b87c54270d0f23a486c3b36a8b491bbd805ae0d5f2bea818a87ff5aaed2d5e6317b786ab5a23f1cb48da000000000000000000000000000000000eb2d4663eca7f8433f10e84984781a57fffcb8f9535518721521ddfc7a4958778915ea3c57bef399a453b8ebc10befb00000000000000000000000000000000161947f57d97a858e5b3e918dbb22dbf28629e51e81335a9bf105d0fd660ef80087c8d69d8db9841cc69fbb5e7f81487000000000000000000000000000000000c52b6a559928fe4ad984a0569c081f3f71eed3d5b0d3c14d1a23afa45594e0fbd94143348390bee178720fc603145ab713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000071aa47d392e1a7787b37c52acedbb4632d5549fc11b79919bab7d22f1bbf1c3a239df622b8824b07f6e35e627283b8500000000000000000000000000000000198e72e05388021919dfc1b2a58ca72bf7655cc6c9b62abe3b45cc782ccfd4a2334780e451b8a6b7c311887036813fe4000000000000000000000000000000000e20cbedbafd96c42612e146debae48c7fab4846b20ad0848c4c42c6aa0603e72f94dfc938ed9e3a9886d221ccbdef70000000000000000000000000000000000c861d1878e63e313e672bebdadd3fdbb691cff5fecbc24da895febce2eef0a3c774a8a9d751498e4fc8e2b71daeb40dc040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae40000000000000000000000000000000005f7cd2205fa2e17fb9896efe3fbe110e1fa59db1ae5f8d6b5f4510abb4da867933d4fe3caaadc4457dcbb35f1b9c62b00000000000000000000000000000000126f2ef6022a7211fa865c1dbdd5b84d96cddff424b06647acc462408f2d31f34ce898d76e1e124db7c39e08dab0bff6000000000000000000000000000000000987f916ad6f718695f3c40703c59ca93eba38931b45d7c33c64c9f75556f075b744dfff8a5f21489b3db6c3846ba09e0000000000000000000000000000000013011b8c72f3853738e22957f742b05ec428ab0da28901800f787b7c3678449acd0359fee93c40c69623aa4acfc0a81017b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a00000000000000000000000000000000014b88c0586fa18333ab11a79acab8e12c6257f82a4ed16d929768a60a3a5d780a22101c32ea9b0099aa2816f18a0351a000000000000000000000000000000000de0fde69efd2cea7ae08d6d2443883002e0b4e11da253222429f6ecc67ba8d282eee84d7f46e0ad00b039a2c2ad226f000000000000000000000000000000000aedfa0a5a8b7577dcc1094469233f8b07e6fc32af26841894d498d70c6a9a046ad636086def948d21e39833c5b6c5a70000000000000000000000000000000010ec6aa0efba4995582585bb67f997f60741648156324696312d17656baf6aeb3e2db0d1a272912fab2fe81d139e971cbd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf2575200000000000000000000000000000000004a847c06abc8ae7ce6e6ff0ab856889dd3e9697a75e3cd4d2af9e06d4c2fc48c0562289348ff52f4d9855ad03d83aa00000000000000000000000000000000075673bc79bafa9a64de6bb0e9dd9fa29cdc9c82e90a7348593eec673cbbf22b1eca436ecf767d45852ed888a3f23949000000000000000000000000000000000f3f8543d1e667404b4564dddba4d7c11d13881fcd8ad774c8eab8fc599f55147c353cd6e163cd7b9d5da55ebc13c2e800000000000000000000000000000000069edec7e7d26962d88a89dfad213daa36046bb2851e5d67adbaa227220f29f83ea67cd3747e6724f148dac28308604cf221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000c945e83822896974116663d3e2769f3df5a70d55b8392c1f6966e330951f3cc5688742d4588648a6988b928b9fe00100000000000000000000000000000000003e94b7ff7c71d633ce69bb44d0ba1bfc7c27a5ee618e703aef81a45ad61771a2fa8e3dadddf7c8038f1f65ad7513801000000000000000000000000000000001727d768c1b51066d2af87a9da3e24ea2a75b0f75b8ece70727f9f54ab77d841e7ae01c9c0760f4186d02a28d6f8ddfb0000000000000000000000000000000000a273f9395cd49b646e90fd2526d5c93fd46c7366b715546529c9edf5cb3d274c9947c21a03add3e7b20612636a6745ba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e000000000000000000000000000000000c996c16a16879bd3194ac366bbd11b5863123ce6fdabeafe56407600e5d49c92ba68ac1256e1515dc9256de14ac26de0000000000000000000000000000000018c584d8a4f14900b2fee70b50b700199ec2372b731dd1380f42ec7fd3d01f0c9a007554059b85946c1c4f4e2fc504ad00000000000000000000000000000000073d6c7d671762e5398e4c9d57f6b68c3d97dfe0d01783f124256fac236f03b774db58b79cb4d5558e1ebf18bb9e19680000000000000000000000000000000008eb2b95e17fdda916b08ff2819cecd2eb031f41c8299b308339b7d9836382ced75e8eb1514a70356882d3a43227a9bc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001029fc28cd502caf3ea3619f6fd04bf457e6a452b5cad680ec2d4f8222a5ac2daa92b880bda76016973494e605ab28c60000000000000000000000000000000002c672c7571b5d8e99de6e47e0a2eb71c6d9bd12baf2b083e6f88598b32c4644d1486aef582c5936e622058bb141db1700000000000000000000000000000000033cda383a77d5b3adbb0809e834993c56717f81f8c66ad2d97f2b298d5a46f7b29a74d35da09271b7053a05af096393000000000000000000000000000000000132da041c6e3e1d68bbd2223f8531eabde8e180b36b2cd0ed4fca248f255cf3eeccdc5f61e1c581ce54edcfb2b73e0787eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000009721f22bc49f68d703a4dfccc3bae791caaf0d73892bafa6e9da465ddaf0fb1a069ffdd55306acff2407da64c1c5a0200000000000000000000000000000000056c0a4804a19aeaf1b4fe52064e43de8e5d41a8d77de054e2cfdff078eaf468d123d7317818d1bad1bf3469c0070b680000000000000000000000000000000007f1f318aed043d9ad7bdd53eb6a8c3167240fca75925b04795210700463c93a66ed64851195df1bafbbe4227d7db5ff0000000000000000000000000000000007b8945e258311e7672e842b91b540fec9ef4a79296956a5cba3749c0ad95ed83d7b0b48384ffb3188459e997b86695d3bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded",
+    "Expected": "00000000000000000000000000000000086a1ab4c19c27f70aa422e8292752c50b365d6fe3eba21e8f2ed51f283df0446020834ad27c18b5c7285d1156049bef0000000000000000000000000000000007288f40fde69bd350ce1f4d0f68e645f42de319cc032250b76fe4fa305341e244e5b2366751d5311105e3ccd30e701c0000000000000000000000000000000011d0c487c4eceaeac009b694931f8eafaf8eecd6028f14a4de33d2940bbb747025eecd509564721b50b7186910f81949000000000000000000000000000000000366f0c901fb859b4bae006fbcc9ec7e456eedc7366c899f68090fbd457c37b03ab99ae982872c7888b65c1a056c134c",
+    "Name": "matter_g2_multiexp_13",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000072f3f03bb09ca30239dd8302b05e0d9dc4e43ea33e865864a82578c35eafcf6868bf0cd9431b92b76f00990b780ffa400000000000000000000000000000000170b76cfb7944ea5ea055aeedaface3e8f0fa4d0ff657fb9d5311f3af6e736da84a5e2bef5188e20f76fb42591267fd9000000000000000000000000000000000d85300009165a8da9cb8e590f7f8d372e4264df150b1551185c80e49dbedaaf872ef69c5763fc3713d0c087c89f21050000000000000000000000000000000003ba59b682174ee61630df95c8e2b1c48ffc8f7f8508c21f3bbe8f7bb3266521fcc06c8f90fe5126d872707872db6d59f44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000004e8f16480c2f080a13b9f2b66e6480132d76c4ef76e8bac995a8e33280073ed5610865260e154b32f75f527d89620b3000000000000000000000000000000000f9ca48d732a8055d22fbebf3d2bc1e1c9c815c184f594ad2337731709317ea6a205478ba05ee9271d35a19dcad4db5b00000000000000000000000000000000078013b9290284e7ad528a1bb9a2a64b3ef43964c7226ddff8ca16ab17b4a2e8a2a7d921ba924a718587954f586a954800000000000000000000000000000000004aa76bb1122116cc0c04d65265d8652f08b411632a732a9e66d7932801b77c4ad398d582e446968f7f4966e9167894de25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea000000000000000000000000000000000145de5d101498bfc7c57830eea2931663ca1165ec85b77654c866b04ba6a28bfe710c1aac9876a68cc6ca119708eaf0500000000000000000000000000000000096f9df9d5723e8379f2d09c76a3fd059be47d2c2ed8905d333b2464f72153c5f50b6345980626358839ac691c26c967000000000000000000000000000000001788ffa765c19758da6eb6c38e793190c64d4a7b116576f6827fc090b0f65304988f6a95cf4397f82b7691fc43960ee8000000000000000000000000000000000746e040d7aafdb06a31ba3d7b590dd28f0678badc261a93dc7bd9a605047ec67ba86b2b6dd72637a449872674d6b5982e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b21000000000000000000000000000000000d8f3cfe1cbd2629f3899313cff16ca3d8f964ec1cc0508341936a7b3b49240db1116b2c3de28f9bc45cdfacdb5fd98c000000000000000000000000000000000fa642ed31293e44211b34bb28bd5b389ae6d0510cdab46c89756f31795506fccbdacafdff21b0127e80557e5ba9afdd000000000000000000000000000000000715a8951cb358b0d8cc63377799a9a61ecc85dac795d726fe60e429d492c9ca843be2a2633c17f830f199335e5d7741000000000000000000000000000000000b88a23fdac7d35fc135b45d7565854bf010a75f072b32c57ca4d0979c111aadd84c71df6792dbdc8e975ecd46a15df2e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a913000000000000000000000000000000001171be5820b5a19c045abea399f2b8ab9905d2aa367c6c8c0f84eac132d26150b759a9c029414f1c8f7e4880214446c200000000000000000000000000000000147f0877321f2709183f0b617a7c5ce898db508a3ced4148cc9f7af011fe8040e90885ce817aa956d9f5d19dd968f6220000000000000000000000000000000000acb005c11481b214a17e3cca02c2af266e4c8cd928e3c4e221d866e9f296a2e913bf34c4e051c7503a5e4e7cd7449900000000000000000000000000000000125f45d0af1c010cdf8438bff0f406007853e566fa646df40a581f65496197755eeebaf4f0f77e1e936f399dc4c6c020e6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c40000000000000000000000000000000019cce3f872af5cc515ac4cd7825a5318ead5b464d50349909a70b415a8950206974ee0d4203f208d8e6d14690158f5720000000000000000000000000000000002e08e8accede11afe3e2d085f35c08d7d414c26a9caa992d5a090a43c9b0c0cc1471f3693f9d342a973da65189c888b0000000000000000000000000000000008a984ad2ca60c492cff2e95d541d71e33b269b10d3df107c0513dad5af511c51806068da6cc7226df1cf5e5a2fbe707000000000000000000000000000000000fcd3ad75bb0a5c046cf83be3d973bb3685bc717d7b8262fb8205935db6e632472496907f7c965fc6b52042ce69999f9e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f24350000000000000000000000000000000014ccaf7594d8ff6157f9439ba63480d3d07f44e62a86caaea510d0ec456cd8c6c4b42cf9e38713213eb4942ed45df2ca0000000000000000000000000000000015c2061c532cda006addd2fd6ebbae458197d55fb336f75ca7decc05dc6d421a65495b71ed11874aaf24a0ec13a7c65000000000000000000000000000000000101f953aed7f23b5b6208032f05b818e0147079b7764aa3134dd9e4a316bbef0309ac378ca3cff3bdeab9ca56cb78e60000000000000000000000000000000000c76a2bc721a4d3ead95af79ec24be9b7624bc80d7debc07e388e52ec621082b9a69f48d157b168af4aa73629697f784c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000761832bb5b530b80c668234ab5996bdc225c0c696ea07dcc61c330320404827ada9d58d658e230fcb39a96b339b830e0000000000000000000000000000000001198b85418421d96ebfbf436193b411a3a89c206d006291bd23254ed5fe12ccdad15725a34d962005c0ae60e202bb86000000000000000000000000000000000c1d7ab83b1d2ad57a407e248492773a357c06b83c16c6ce1490e84bc4a3cbae395f160181d2bcca3edc34b764754ab0000000000000000000000000000000000f1e9f0cf96d7671763739b6c37fd442f0e816c49d9c8e001d322397e9d6741dbf8769ef9eb83d08ab024294e279a02838cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d147400000000000000000000000000000000063049bc3282934e29f3bb3dee432bdad6193a5d2247270e88887cac565f4b986e1b3b2af5387cfca64f0d50bc0ee1640000000000000000000000000000000019f0f05fc7f8bf2f0b8ed375690b53b6dafd0a07c49fa55d36e040798334700a3aafc4995bb90de9c4dc0e077ee18b58000000000000000000000000000000000fbe702d148609dc8feb3ac11c5eac8e32a2f7221aa135cc33a585e9f4c97afa1658d8962fd96e26e0c4c1d5108229ef00000000000000000000000000000000061fe418d3b440e84728091a4996119b515118900f54a6f2da2ad5592f48ebc17bba50b59ecf435de3cb892a123ae9d18a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f80000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a4553780030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f6000000000000000000000000000000000549f6464b657eac28f838c6a8bcfcb7a189d6b3b9712e19c1a23503ac209da5f2ad4df83acd505b0231f00eb88515c70000000000000000000000000000000001bf4a46dfdd70542e9d8cd6d6215174cba28f9adbff31c02482ca38205cb4afa2f7fd65ecf57b39e4ee5cee320e33800000000000000000000000000000000012d04a693d565f96566b7c313c47d272fef0ecc828493b0841d58f6bf690a77cb72824a656442e288460ecca7cf05504000000000000000000000000000000000b33eefd5df8b098e6505cbe655a483ab5c6e417a4ed55420beab95e8614c8538dca9296a7848d6aa0495a173df6d0b80d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000000fdd9702ed88aa857254c3ba50b484bfc324e583659c57055e4b09eb1662af2f70b547a1eec139193a0d3c75b565d3b200000000000000000000000000000000193df0fbc5f24065008b5e98c4c4bf9f1e743a6ee60c3700ae4a9108639e540384eaf1f9d7a60b8b6a5d79e1f34949f50000000000000000000000000000000001022f8a254d17e448cadfad35b7a54dd2fb319c8f9ba219874bd8280a5077301ff4332d731a75646cd93bbf31331154000000000000000000000000000000000ca1eb350844ddd0a65a4ad56e1a96821de2c6633a4a45be976577c223e367853e2b1ecf2cc40b8595ba5591ae8e40f3969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000001f9cda056a0f8803be581634562e975223b5311f4752b189cb6bd6df1ca5e3824bbd2889b9b93da59e4f08d482734240000000000000000000000000000000009f43c25de25c5d76ee1a03691aa434de6a063bb3a1133b045797a279346fc938dd2636abf0c4bbcb528c9c28d3105c40000000000000000000000000000000012afc29245da8bcd3c0d96c4ee61617cd9ecf42a47c2ee822003af26aeb4e4de8e432ffb6b2d8241090b814401a8676100000000000000000000000000000000053edfd98742dc70d510f1836fcffa6a3ba9ffd4904c7f5559b48e49dd21071401362d0b39bc0d786b7ee2e84a76af0d957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000001dc3f016ea1a74ae50c21c1955ca1eb4a911026a1e72b316c7bbdc708caef63f0c1efecbecce8901d65bbfcaae429da0000000000000000000000000000000016ce9301888808323c9baf6402d7073fb85ebcd389334cc69d7947e345748ee44b2d6aab3ef818beb21b54a19ae4f5b5000000000000000000000000000000000c49817753eb6459cdb4bc737d3710b5f044bc544c8d92c8ef138ec9d83889664267e1a5691f4bc3fa235ecca2a973a500000000000000000000000000000000074a8450e35f1da18e6de05960e21b7059ece8972c36f000bba9e24488730a44ce3ce200c437e06703addb3b442a790a8e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d5000000000000000000000000000000000bc1f2e9af093ae8235c93af098e692e697ea0ab4c8f53019a6e950f7072b56d5eef6b3237710f1dd1cd1970668d06d0000000000000000000000000000000000d9a63f7a13ff9755c6a3832e3c4c852919514523092367fab7886cac317e564d57fb4042ef40e696edce868e697c45700000000000000000000000000000000129a30657466460db13575dca367105c27d631eead330319b084adfac591f5b3b94988925d778e6d4645d1d2816baad00000000000000000000000000000000005ad64d6e761a9a301589547929f4952ccbfead278cbf6658255a075966340f185d5f356679fb02ff2197468ed7de19a71a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000077d1e5b35c224e2cdc849c02e800c0b80d1c19f3d74d9eec34c40f56bbdb9e2b5d2ef274991dca843755f91a50826fd0000000000000000000000000000000014f3b653e0df0c608b75dee3496a7af04a828e6fc5604f16ed49c39686ec757e96adb0a667853006a8331c3d63ae4ec2000000000000000000000000000000000aae011375b337940f2a53d9091d3581e8197e79251b19c7fba01de987721a9d6fa694b7978f0abf877f46ec26147c98000000000000000000000000000000000aaffbd468a2eb86a3cff59e2e9b7ab88286d2bdd19c2e789b1a68810f0cdc76171a2661ab54e81b17643ff0275eafd72d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641",
+    "Expected": "0000000000000000000000000000000010a2434fd3150f6b9b491d3a51226bdd457504077ef2ed5a11ceaa8284900d1b84039a34d5239a863809369bf20a704c0000000000000000000000000000000007934f34fd50a98225fe6578d3f34ae5e5ef5e104bb9cb398b2ca4f09048ec39cf52e7fdbac48d45212e9e4c1dcc6e120000000000000000000000000000000013ee70f1b52cb8b07ad957a7565b3e3c56306392cf7b5aa29047b23e5b41fb3239ac3611bcb16ba7c7ffc4213e3d9cc800000000000000000000000000000000035840f8ecf56359dc3239945720ad08702b4ea8d0fa9bea3bfb234431df4618e960a1eea87da72ba4d9443f14bb87a3",
+    "Name": "matter_g2_multiexp_14",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d04bb9b75bc8078ccfa85e27d32e137ff5f05f9241b19ea835bba2fffc9255a4a3028c0caf9c32d3d27666e1394fe820000000000000000000000000000000013f59c3d8aaee34230cd7715a32e4a45487b9b16ce68d178f95461229a4d0fbe7d31edc7208a7338eed08e65847f8f29000000000000000000000000000000000d63ca2bafaa54e93ea54846b26f88b4c6749953f9cd00c670914cca279b794c1fb5e2664fce44b8c04f01c68698a8b9000000000000000000000000000000000b5188b4b7ef78d3662baa01b1813b4a0b0f855e11397584a460d56f594f11ff2e5d708a23a8e64d0ab337c7076872527740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000019bae57568c879cd743f7def43b6b994f29782c6a0c74734f35b97042a916da00daaea34f321481e6cc4749e23297c1c000000000000000000000000000000001853fd11d4688b027146a07edea647502e80750de4e5e2d105faad3f71ccc90badcc750f76f1b02db3bc0a1a635b2bbb000000000000000000000000000000000b1e45b90e6a7032179236f13f01ab664c32ee5728414ac0d6b9d79510e8c5bd0f5b62e6c59c1a3c88998bf45636cbab000000000000000000000000000000000ed16c2f88b5b8d29d7e01633e2876322caeb740251b034e5e898919f836ae73f0296c62253a0329ee8f71fdb5cac3a1d226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000001455764f99e5eb0e0371e89f88bfee1c43224b9b5202746bd151f72336285556acc5ff36bd8ff87378249e82214cc5e500000000000000000000000000000000007fcee74e5335d96714e4d1a7c6f5c211b1a460efa283e0d0578c6c1f56dbd252198eebf0625362973c40d95fd890d3000000000000000000000000000000000ede26cf87e604507230ad996788e85799cc07245cf7191a6c3cecf0bfd5747b3a277cfbe41252808df6da19f005de9a000000000000000000000000000000001855991a4dd78dfc6088e6a43a64b56c8d86a0278b899bc8a1979a40a287979dee567217b006ca71374156a96b79c176c133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000cd032a7dfed029af020bfa249e6adccaaf5bcd2ccf33736281c4fce9c6e2b2e87fa828cc20301269d8e0579ffb866a1000000000000000000000000000000000765c4d6c4062cfbf7e24f9772dcd812f7e707f2b0ccf9043faf10018326834934df121924abb74d736b0da47554794a000000000000000000000000000000001540fa51e4580ff73e58def90a6f19557dec3c8306e2317ba0c25ece3eb4f8c39beb57741b3c4b9b8554fd2597743ce6000000000000000000000000000000000d875c822d0ce50dd638254cd4aad5dea1443813689a940d72cfa5db9309b171299ca3d69b137dfd37f0b7538a0852750fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f0000000000000000000000000000000011c230689175cc672c25f3c56ef4eaf2bc5766ce424f6c596b40ab24fdbfa56a955205419c149058dffa4d86a48ad35d00000000000000000000000000000000078d493ce3a8038134541ae5f2a82b5e0590218a499dfd78c7a9c06b92307003fb62d6414d6c04b22f2877c3de0b65ca0000000000000000000000000000000001d53c22a622c5d91df934783f8c0cb7e370043ecaf99a0554987e6c5120a0e5f4ede023a9ad988d30d945a2132ba5770000000000000000000000000000000015b1f36a00fee95e13443c9f6e67935a840cedc7c3fb7833ece8e180991909922f59d4f4ecbbf23f16bf5ee7f0b5851b70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d0000000000000000000000000000000014adff1607236910597a951ae169a7f56d6a3b4e0f44ac63a247716bbbf61feff7865d075f79e4108cda6c0731fdcfef000000000000000000000000000000000d740b13885c268da876898b77914bf4a002beef5bd2a3edefbf366e45ebcdf593ec6d9ab21e983fcae9a0832986182d0000000000000000000000000000000002a0827e812e983898351d9f03f660317d41669b0fa378e5c7667b73df299ddc4a32a529ca887a53245d7e1f946623b3000000000000000000000000000000000bf09a2de1a8ccf24a8a65dda72adcb96535ea7235de87f05d27341738b0b4ab16afbc5b37c97e255118dea9bf180ec2ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d7210000000000000000000000000000000015fbbf7e8c26e2f41be32daee2c81390b9bc4413aabb053e3a88bc6117377bc16011e81ed167370b72f84f0e77c2b8680000000000000000000000000000000013d48a27d06ff00048b19879493a5f8ca52b7154be2fcb468b9de9edd1395750434b0e95ae6dd941e84fd6d8918455bc0000000000000000000000000000000012fd2bc91286dd46d68d87a3f8793db997ee684dec6b2de1c4202e5e7eb0e4a8a21222e3dcf80e1ae4a3a92474107d330000000000000000000000000000000004d8b71978c9025dabb3d1b1b3c7f4f13f166514b8b356fd064842269a36c6f1c07f150c03510af7d0913103afda4a68fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000007d679ac21bd4634b415ef8e0e3670a8a1d673f6a4f7f3786b92d55458af980b035e4dab165a3b773ff3469fdd9d5135000000000000000000000000000000000fdb82db6e1096e73322050f828ba41b3012496a4fc4cb481f11fee338243aae20b205ee06887e28f6ba6dad00445f9d0000000000000000000000000000000017e6894b48f60b3d9b4184d58ab9554851e285a1d445b4d97cb1a7ed5a984ade8b0f62ab11ca75fdb280cc0e526108ca000000000000000000000000000000000c03b61690cdd9a4c6c83d03749db72c8946c21a944fb292866cf3a2dd1bf3dcd95743227709740ce8124319d0a540555af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d7000000000000000000000000000000000289f850c4834153f36bfc4855f89e9437a172c35a856117f8b841e5ad4ef973d3aa33fa73d8dbba4b9b2101708006bd000000000000000000000000000000000700025f22c0460613c05f8941f8a79a4319325c37c2b8f099cd910df5c0c27121a9de0e40adc7ba0fda61ea637b47d600000000000000000000000000000000069e17e00d4d726e8eaca8235c88967a7c093c70e5a46b1863ad097acbe233554048838a0a486a72cbed7001c83a27db00000000000000000000000000000000016ce4afb84c1a9e0216f23bcd2dda0bbada6a4acca78e1e0d765a5290f6f4929f6d0eeaf1306fed3c9766ca7c7268acc7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f10000000000000000000000000000000018ca46a89dadcd3b54f60fdf9a7b97c95b9e0668ed9329bbe4121e588a1ba773c9d086dc35b699d65487f428c00ad8c30000000000000000000000000000000003ada6835a93310d0ada01bd7fd6778bd07e718d1ce05aee2b4990bf32322fa94ca898a531ec6e3b8cd7ae3bdc77e0b70000000000000000000000000000000004a8abd2b9f7449213e63ecdb435e5e13fe2aaa31a2c38673a6adb5e96f4dd383dacab391787f6c17579c78a1cefa5450000000000000000000000000000000002a8768d98ccda80149a767e9b5a3b0bbbc0ab4b5f696522c8f1c664f1d27f2f0a6690531672ba2070355c0e77095dc02d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a00000000000000000000000000000000048fb58924bd5952d3bd7b1cd57a1dae6c1034df3a420c1151737f88760e4b0e78fa3f891a0dc32fcb50f89e67b0f08300000000000000000000000000000000073e9723c80eae7685db774d3e2bced53a52f24504fc3aff98e2becf8d59c6e83373ed024ec1ca50101d2d613abd286e0000000000000000000000000000000003b64c8e9a1341bc6a444a871843b3add7dbf04bd1810e1d6da7d31c7c2b7a264c362ac9a366dc8d93bcd9392c6056f000000000000000000000000000000000064462d424e54f50e9849a2bba1b0caae966a8618fda0f8965b1a841dd2173872a44a18ace1e2aecc8e3546a9558d7013549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab71000000000000000000000000000000000c40c8da9281a8b43478c28b2fe59a3cbad0a818e2077d40cfe44624dc2e46f72d4489cccf63eb8460d02f895e78edf5000000000000000000000000000000000735d768f6ac999a47c88bc2f3375f01052259dc69011480e468d8963ea8eda74726c4ef32c8feba52878eaf5c0147730000000000000000000000000000000010adb3ad214b17b963586a10701934727edf05fcbdc94d98255632647d73536decd0c91363840e1b55f29f7d32f650410000000000000000000000000000000019349045e6fd25960c03336888679cb53409027f35a1f211b40d24ebf724866c085a978ffa3a91d989da1a7902bca018c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000a5d2dcc05e218b0633e0a965b6d69a3c6c1c7837e1fff7ff75cc9ee93a112f8e34cbc95bd9dd8fe6ed22f2e9221aa110000000000000000000000000000000017d2e5d2c0578b1ec26b57c3305b209c979bba6925756892f031a7462ec44e8a4a2527e6aa2fc13bae91dcacb8c7a30f000000000000000000000000000000000d437edb45ace50700db548db68b9e8376b3039fa00cb98dd00cd197c14d0f92c8a3945127c43b10b34bef7894fa43410000000000000000000000000000000010d5a2e442a2eb35aa85fdaecf094c1e1f307dc9bcc540693d7206cc4e0d050ab900f17fbdd0754b59bd2aae705c60149ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000016c36464b426c3066aead1aaaf65ca637e93279e8ccc9d838b9b3ff1aa7b896f36de506efc2b0864763cb6ecca4926f30000000000000000000000000000000006d88d5764fc854ed7d7cf1c0e210496ce347bd887da2a149a09679469e98c453d85115afdd2fc4987b64a88c4a6f0a200000000000000000000000000000000053edcc0ca4c205423ee6a7031939379e552bd2d2657f8f25370c9f0ea0a947e77f18b5f218f98d12d720667844f3795000000000000000000000000000000001292909190854cee4499faa602af99dc49d1354a71278b439e983bd89e6c504fa5fcaaafb6ea26dbeba9850bcdfc1f69bd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b000000000000000000000000000000000997e79a7549ada9ee0233b3bf9289df3ff797595f4b5eb2e7dda6977ca981c1c4a2b91b924812b95418f1b1d9d0cb830000000000000000000000000000000000256b830e80f238e8494387429d727a91cf5d323ea87f7dc143058c05e11858796adcdc677429d1db4dc2415cf23808000000000000000000000000000000000cab529c6b86beacc57c874f07108d1df7d98fbd59fce44c48afe9eb2dff823f4869b620bbafc121b4ead2cf244974de0000000000000000000000000000000002774906c1a0acd87de224a9450617db37f8f36a0a192f5daa2774eff0b73aa79b4804342999df761f8572974c697c6010bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000012abd02540073017011e186586023adfca36fae454350b2015a796b7991eece65b63964fcdf581b4b51dbd7ddd506ec3000000000000000000000000000000000ccd3f2d9280908d4b30e924e4a862810a92e1a880cb56e842a94a2a5120956e8713f548ca279d66d06ab23e4976e54e0000000000000000000000000000000000c052ed00fde2cab515694d8c004de910e62d07c462345ffcfbd3904a0171b970bc58d99c5833059315283004f3390e00000000000000000000000000000000008fc4860366074ec0c7aed2c6ffae7c93ae0a81067edd8911b4c53393ebc0f23243823aa7aa2b2e987cb510f6e0a55a65c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d",
+    "Expected": "0000000000000000000000000000000006ced307065868b6d082bd205bfbaea3b0a8cfdccf831bf154563b5a942154622b0d7689819b337479480d19aedd85e4000000000000000000000000000000000c0f04fbb26cf85c2c22763f3e78fe255d8d1f45ea47232ab58f5b785ad9f2458b0b28f3cdc25c4dfcb47d59957ae10700000000000000000000000000000000120e38740eebbc3eeea9beea483e70d6a9c30a5abd61b86e5f94bf65ffb40fb92c8d246edbeca425ace175f79c9c8afd000000000000000000000000000000000d5a503a26e50f9be34c2e64e4a80402ca6e17f09db1b334a9c1f6318f3e7e63b3847a7ca38ae6aa7c96ff94bf5de842",
+    "Name": "matter_g2_multiexp_15",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000036480931a5a585ea54b6dbb01759eb1d86804e3f03326188c71f859613722e662c453096431171a49eecf8653f14d470000000000000000000000000000000015fcd6a30b9d59a90d8595ca1758eed7d6810d2916638dc2cb637aa09b16b5ba4920df7d21fc0b923453a6c7d32f056b0000000000000000000000000000000019aa4d8e98808c2fc1273d383e836876b087ad5a7d01743bded01314bc62ced94052d75d312a18839c1b33faa9e2e5160000000000000000000000000000000015747ce0f1171c0d0ff1fee9dbb2e5673b9db0b0c3618cc8bda474f378db58ea42184f907593f3d6fc2fa215cabb7b2308e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b79000000000000000000000000000000000222640c1d64948daac3ff93e86ecc96bcf9c93559266529a37ef1372a81952431673d69f1220e07b8aa0a4f3164c83b000000000000000000000000000000000db593156078821cd0ce0270e8a444d0d204dce0583774496620bd4752839f3451e505aeb3db568048739c7e71d279b40000000000000000000000000000000019932ad2c7e857c2dd51f7846534050b9243e388260cd47a91444fa050a9154eca88ab4d29a37def16d4a11d35683f2f0000000000000000000000000000000004d15ec653a72256ac6b616e9870b0acc7d46286893c0eec523dc27bbcf5fe596204cbf83ce71c2690af67b3616794225e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d00000000000000000000000000000000180622bfa9a1c452f343ed21a3e9c6fdf76589cebfb9a3f0a53782a3e7c9d066294e10699c386b5d0525003289f0ec580000000000000000000000000000000006615ff63c856302dba6d4e25d1070fe873e0c4950ee5ba8bbbd4b94ceeb181f1ee450acfd22f21010b88f0b88375777000000000000000000000000000000000cfd3940b5eeefa92d775792affa34371d13f3098ede3007e06510344ac8483debadd5a2baebafb5ddcb45a9449768b200000000000000000000000000000000145be0107a1e3acecc89a116668f9887579ed7a72abed3f4236930edd3f18974465c99ada86c4980c88768824216170f1362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000820e74e6d0333b6b36590ebae78960d019065f1681ce68a2a01a2522496c840c668575a57f9fd0f50b87f928a41b0de000000000000000000000000000000000dee60d90e96019cf2bb552d016419e92dd358ff97039a61838b0a89ccbbd537f2b435cd11f7b6e75a4ec6675964e7fd0000000000000000000000000000000002ca767de9fbf8af7c73d41a07e1c0e38e3fc971472e11928b65393a27354b2d732012dc57f498f94c0b933565a7493200000000000000000000000000000000134fe97b24e153f0e9a27d3fe7b89999c6a19e353325e0746ead013198b8e00ca6472fcbd2a112aecb9ddf671aaedd9174d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de00000000000000000000000000000000031a9c29323196ef31030ba73827d228e56fd5209eeef0803a189e0c0e5b186ca1f342483eeac99e1e1b12cf490856460000000000000000000000000000000010deea45a01370602bf57a1f81413e8d3b337d7a1a33f9525e4ff7003454d1da2cfb1a9b42c4a654320f91fe7d04b6200000000000000000000000000000000002bafb7b7452a173a3971c2ba1768061a043307d2c32767056f18c1bf8b066176937876a87055e54675876bc1b2d2fc3000000000000000000000000000000000b5c77dba3b4136a7efaa8c2e28f39e88afbf26a7313b52ad6e390da4d948209d96e39aa08eb52200dfb890d7e88b46a389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000018bd1ea5ee8e39c43d442e9c6fd22706e582cd80051f18334c4db2ea91ab019f54bc0074c8f0e52e50367197a797e7520000000000000000000000000000000005c0bcd1b047fdbdff25b138248bf4da4c013beff7dd3030c348d6b2b8724a147cbc44d570db5c4b273c94d0b99bc2290000000000000000000000000000000018e033935c20be5940863f7e9e39fcbdc29ba031e58c10beea90cc48e9da9988fdbf108bcbd87948058f386928f81fa800000000000000000000000000000000107d179204db7b288315e8aed7b92ebfe53b7ad2366d5d7944b3df68d9d9faad023e477213f85214047645bc05fd4cde5f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000000a7b364fbd3bac7e2f2e7ee501db2d248bd73a76c2a12a3e51718b56ca9a8ded14b83b8cf0b5bd46f0c26896a65fdb15000000000000000000000000000000000eafea7128fe20ddf740a6396bf18ff5f2652a0317ea9b6e934927c3ee95b59c7dcd51f7c895b3989d40ae5f78ca508f000000000000000000000000000000000bdce57be904236a8df532c2c0072165b5cbd4103e9061fcfc0a45a67e4b25d11b9f816f63fc0eac4d6d3e10d2764c4a0000000000000000000000000000000012419f94ddbd8275054f8f89fdc27a74afca2eef314393236fca65705354e5cc0a470818999c96b5087997813823e9be0dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b60000000000000000000000000000000011e4010d0cd7855a92cd5d4954ad735363c0c2ab00053db5e078f34e772969d8c492892329cb95ea8893b4b7ff7aaa5e0000000000000000000000000000000013badc54d90a19b84d76b30fef8e3ad2cb268204fdaa50ae951b63e48aec9cc6d585751dd48e4a8d4659b835f38f8da8000000000000000000000000000000000460728f686b9b15cc19ef135af71312e174860284c3f0e7a84cf85a5c934e2bb6cadee8e482d88afe788a796605f79d0000000000000000000000000000000019a50c06ba307d83452a30fbd862270652cf5c7a09b150fcea858a8102ce3b1e9ec13b6abfb323d63d2c4edf209c7cafc579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b5360000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d69f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f10000000000000000000000000000000011982264c8c078518cd0adb05034761224e9063654904e06fb5e5a6eeb1f45e4ff3da661f1232693b79336215dcc0cc40000000000000000000000000000000010c96c872160d2de03a16e85f2828d0cf2dd16a3389effacce46b5b5eecfea1042a77de653da5a1c0380a84c435723fd000000000000000000000000000000000a4ad2d9956bd407c555b26c192c6bf59bf89e40d9c6f9c90780bba313a39db71a73e7633397d47a3f58f61c81edee77000000000000000000000000000000000a7f912530d27a7bf74e01d8e48890cc66f72d14950554991ed1edfc504062ff6bd3cb6941bb398df9fde3cefd33fc0676618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000015bc12aa9ecf417fa5bace8d9e5dc4a418555eeddde1da8b624bf7d6e1873ec4a257d5f6dfc058a8d9b02528e699abb70000000000000000000000000000000015b41567f8c780f83342449f27094bc20a839602ae482de14b92e40017e7acac8857db48a2d27f1f1a625883b6e5255e000000000000000000000000000000000cbe79ac0718555fd8fdc38b68eec8be83b32499d2654be44888e45a2d610b0e81ae12fd56550524ad85b5a632db32ce00000000000000000000000000000000069f46b5baf4357d8010869685b3828c0dbf6e2338598c9b42dfecf0b22d803f95fca716115f74c77778d414cbcbd881fbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a07876177880000000000000000000000000000000017ada4038189c544902167be958e43ee133730e5cd329e572dae2d853b694f5ff8032bd9ab41cddd11c51e8284970f810000000000000000000000000000000013eef75e6d28deec945ddff33128c199fa52565288d63677c824b8d56a6c29eb98d34c5834e84865be35d40c1c59a40c000000000000000000000000000000000e2fb4f9c7ba6bdac1d4ff5055be609abef7fecd7923a753a704da537c0ff41951552420bd78d14cf972dc84fa3f5dd9000000000000000000000000000000000805376b814b8a59435310d49a43081dd7ea36dc7dcb40d38068ae9085b3ea9a3b2249234234cacc76724d8ef84a2eaca0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000002288860f2d671c84c5239313b7f6b82e31c3976e6d310e15d3bfe1c566e2ab5d86ae6ed0df02530f9f7893ba419f1870000000000000000000000000000000017365bc096e260f8dd7b189fabe10eb66923783b41fff70a149251576b3b465c13230dd0af13cde562751dacd8298335000000000000000000000000000000000fa8eb9c818df27181b45a74b333ab481dc7212e417c4e12634816f9e177064f9e1101deff26156d26bc6574db9617080000000000000000000000000000000009379598bf02222e1ec37a721b9ea31a3adc33524c6a41bc58da06caa3da3bd730659f0a80f793a0fcb9c07b43ca929c2c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b00000000000000000000000000000000136870e08ff5fabf36410629ce5c23470eafbe73a7dceb633df5c1492e39445b86ce15c22bf4c421cfd0adc6518e78c30000000000000000000000000000000010aefa3cdf1225da09b796430d096807a83eb2fd5a58db3a4bfc5e500dcfcd472fea3077f0c059620f4ff708f37c95a90000000000000000000000000000000019ee2c62ff860338af623c535979ed31c42c0d0b2f82cd56c153e80e6d92bec9ce39bc8e8f285d1efd1c1e969521dbb50000000000000000000000000000000008ed69eb0a16c8a35d507bc3a50bfc97e18143fef611263715aacf5400cb1aa285b6d2ebf2ec219d2fec477360875a03141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c5920000000000000000000000000000000014abc4eec64f2611197d0c1322c3248eadb725049379e64682f2b3d7f83f7bcea11358d88f52711b3020924b6ddd84790000000000000000000000000000000009fd78c5d1d2043d83be30a88f046f5b633c6dbb11bab25fa3037bd250b6b9d9394327aae25d1939f777fea9f3df46960000000000000000000000000000000010f413640aaa16a95afba98660f9e1b03a8f3e0a7a3d7f2b971f71b5e3d09016ac2b410f97d20471f48621d5a363e9e6000000000000000000000000000000000154b5df93298a5a14a6157819e38db33ae7f2d11dfd13f7f2a92b2fd9b053fbd25f10a8c45db3026f6f583bd56eee0f1d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c000000000000000000000000000000000f1893df99adeff5e4042c4c5e8557e53f7c34efcb2a7953d5347f81d2f4a75ca0273a3845f54e795ac1c1f8ae7240dc0000000000000000000000000000000004856b05d58898be6aba07fcffe487dd895144c7ac8fa8bb1a37c61e73bcd062ff541d510e24c5bf005c8351d3ddf61c00000000000000000000000000000000178b22c2c698dbc4929b119474a741ef44d6275fff5ba058d9debe9475e71398e464aa14a6712c5deeb5010d1c7758ba0000000000000000000000000000000005ad09389c35c45f349e6dcaf1cdb3b63648b3df427ea0c2a371f45634635f9253957ba6987df4aca6cba4cd472308a31b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4",
+    "Expected": "000000000000000000000000000000001090d83d501373cf07c75effb1c85852019b39eb0d77226823aa3c1054d4e408e82fbf0f4420a30144c611fbb856748c00000000000000000000000000000000120a1e3795f6d5c4ed5b886256c611bdd209677f8324b7091cdd7cab11788b1c0f780e8b4c38b84d7c2ea528123d4783000000000000000000000000000000000d250df34d906ed421eec2a78c2ff4ed4eedb717358d7ca879d58ff5b4d2d72521082dba6ac5d10859125e32c2c8b490000000000000000000000000000000000476adaed9d80cb1545be505496222dba1f0ea85d38d5bece0663461e0e9d47abbefe95303c926db008d08b8aa162e27",
+    "Name": "matter_g2_multiexp_16",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e1a9066289392b0b0b8f0986366c2975463c9cbe7a74f2eafcb3b8b6d4ee3226ea5886aaae374341bc76b53b165e22a000000000000000000000000000000001557cd01b61b5f2361f6b558a87c67f2778e11c21734b7ed25f72a88cc62cbed396d583de4c2190ae6bbfd096c33bf73000000000000000000000000000000000eab68305118d7e7076043719ac1e13ecda4497df2cf392d6aae4b7753f114d30aae3e8535742947636901feac4b620a0000000000000000000000000000000002cfe5014446556b82d60adf874cef25e58eabd035deb4717c93bf0361f37a4a67aab70b95627326bd97f111efeed57f58fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000c8fa30f6055357f6b697f2115203428b8005ad03286d2b3c805bf3d4dbb461c30e6ee8b0973ef41f884b91e857c53500000000000000000000000000000000005e1c785feb4c4fb7e960233d431d51a4fe471f10321251d018a950374d2a686d52ee8cdd855a29e770bdc1bc565f471000000000000000000000000000000001158d31faab483832d39f5431a5d8aeb952d6a63b82ec019f235b5b2e5580df8cd91b46cd53d4a90b9db354b38c5a1710000000000000000000000000000000004a389b09be6fb7ffd14d7f3359b17991e93d92a1c0b9a89faceaf71f5ce77a1875aaeb7a0ec3b2dfb363c47dfc9875273b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad0000000000000000000000000000000010587118c5f90b545ee707466ea2c5f378e6795c260235cdf9876aed8bd753aac592ee05e23882ee77f4a13bff97f5940000000000000000000000000000000000a0344aed244b90c4fb9ac337edb01429e09f951062b06025a5212300f5471a95f28e09bbc715417a6d98423b518c3a00000000000000000000000000000000128457cf374e5b8864b8241f476da093f48553d609a5f30c0f0f235ecf7127231237b6c8802f2904a8304c7c237842620000000000000000000000000000000004d55ff04eb09b33ebfe90f2a0966a1b59cc224215c0359a4ff0c09e60f9fe7ad8342868184d8cfcaa1d8c28328864241ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000019e6992c3da47715bf379a668a15668508e7ad27bac647490be8e82759b9b79c996735aa1bfdc3cef217750e4ed36fce000000000000000000000000000000000828f782c5bd4f2de3570a4930db2c020f75f93adc98aa0e48449d29c7a3b0d5c349963d956bab7f985ba6ffe59c90ec00000000000000000000000000000000062c7a730d286e895c57b75907713ebf1d20650b5e621f270f1d22a2ca480d022346def4102a62eebe867210e4b6122e000000000000000000000000000000000d6c29462ad449ee6cd122e3dc00d56dd5caf17a2510e5305aecfe85626cf73adb401ec2192eb693158650893fa67412a691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000098de9ab41c289a05ba5a774eafe27d91aa8272fe9f81fadefba9a0cc0e31de20f808ff454a8647c44f5aa632742af9e000000000000000000000000000000000c96019bd5cdd62df1642656f0832ac8ff6aab86f671e18c1c7023dc16b8ff54a8e3e446b19682a23b73ccb90da2fdf0000000000000000000000000000000000178e3b4366b2517d4c19fb40551be6979d46319d7040682241b046f10ab88d269dfc097ae02952d46e69cb1cf159da50000000000000000000000000000000008341bfe1e2fb999f0c3f4e79523c720edd332401f9dfdb8dddba8d1342c2c1fb20ae2fd9dda92c7bde5a0c95ad971f80d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000001004d60af8c21f7c62fcba1c5c41b94fc77f64b89abcd23a218f0da8f47d2ae6879ddcde52f3e6feeae2dc7b2720577d000000000000000000000000000000000b8e8a7da87aa62ca852e2984b0f12b85052fdd03883f01f4496df0835d1cafa48818b5ff1e3cb0e9ecd66054540a0d40000000000000000000000000000000009c16854580ad8191e3e80a0afa8da759a8b2bfa7e0d556418b5c96d97e88a12fb75a91cd68c2f4336c3ed7ac99199fe00000000000000000000000000000000195ce9c562c460c7e715908991ea8b017b81561b45133427f63cdfbe8f65202bdc8e8958ab0977b3a244cfa32fb35f37c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000107292f77666064b7d80d73ea8f3b623170ef79ccc7c228b8366675a422a0cb8491586a2e4ab1a067c31396cd670a8900000000000000000000000000000000126f8136dd61d61b2a9c0f4af3ed44a3cec3ccdedc74821f341d200601a7bf0a17079c824de6cfe28467e843d0c74d2a000000000000000000000000000000000bcec8afcc7ee56b36d6d08b51f61454c8fb15ec5baee1117ed55af8fc85f68674250334f79b0fce632e75623dd173210000000000000000000000000000000016624d64660b63b70ed197f6a675911b02b0bc6f880348faa6ce4727af74127c509ce8535d8dc8db5ae2d71aa497e0756fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec5000000000000000000000000000000000f57747c20e1b3923c7e1d8bd7d877736cccc0e0829837a086d62d48cb54f323d90b57ca3339fe4b256df529bff11363000000000000000000000000000000001940327a1b319dc4212e7a553d3f49904660722c89636f6a38604d96771fa0fc71f57674b7aa710db4275822c2b89903000000000000000000000000000000001956b81bcf961d16e50c053ca07ae67cb8597138f34a9dad4d82e0e8d23a7e08b751682d588f229311bc63f9598ef448000000000000000000000000000000000208981064443e8c72987945e399b45b74e529a0bb75e99b7d6744728e5c182a6b0a10e449147bcb0b0cbe70edcdd845bffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf0000000000000000000000000000000018c35ca3a63053fec853e8fda5920b560f1be28431f2f4b08789c7a202336c8905a5ffffbf69ae4427f267b1e13288d60000000000000000000000000000000019de96be76bd93886cc486c2671b5b0d731b568638b1b830a52dd4c481b9a1fbe2b3cef14b46e25f1188ddb3c158da6e000000000000000000000000000000001813ab16a11c79eb3d3d47ae7d9a7c05401ee91eb1183266d23077ec4c0c8f3ac7188eece06876025dc3fe271d65d4ba0000000000000000000000000000000004d2a416dc874e956fd6d29a3fb96195019f4136561b4c127541ac171b5a6b229746af6d6e535a8017e64ce06709e52e57f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f4000000000000000000000000000000000623b7a8a1c24dcc603f01589e6679c74c4ed3452894e536a4cea69e99047092acc877dd0bb395b0cb693cb1702a64a00000000000000000000000000000000013de9dc75e42f12e905d729a52f25bb1a4125f5edb435734649281bdfd41083716d0797b0a80d842c2503d09cc61162a0000000000000000000000000000000006453c06f56dbaabd4530160bcd5312b8a148dbe19fdf9f1e44b7b047a73ee9ef9d981116d00269942ef73537885eb7a00000000000000000000000000000000075376135ff3acaecc0eeea32f8dc15add57e8f0297d053ffaa0fb0a8fc4418c5b142f96b6b9ce9eee2f949c960aed682d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba84000000000000000000000000000000000120d088fc12210c1f5f6cc3d1091563f9a37d4d0e0d2c305b479f4d7e893c4d5c8170eb164e34e4843a21c9eb193d11d00000000000000000000000000000000159de80db3b1f0ffc5fa8c93e1bd54cf8ae19cbc9018a5dfed86179cdbc976c1c312212080ab221806bbe142d496e7a7000000000000000000000000000000001103abb75a78220218cde4bc4c59ddb5fb647ff808754dda200bdf586ee9c47a09e03762bb726b085928ddcc998af3ee000000000000000000000000000000000bff4bea17eae0f2ff3e7f99bfa91e6ae8aea28f6f3fb6080eb644861defdefc26befbb7874f612edac0cecf70dfb275614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000012dde607a2d4452c6c060054c8adb6307743edea3ccb6ac34c275717f177f0e454d9e33d4391208198cae39d7eb6f6c00000000000000000000000000000000014cb4d8bc98060ee68a8ddbc44b83db5cb6d09f09b0d608357629251c35e44383e97058d0d68fe2df3bc47424a5dda03000000000000000000000000000000000c14fbb6c844fbf896fbd3cb3464a83aa4c6e9a7f0450ad96a07527df6f1eeeaf587f60a990bd6abe7aeaf5eb46f362d0000000000000000000000000000000001d9468774318ea711b79f16303ce86288cee312af296f1c9f607ef5f97c7d1cb48a7218775c8aef00c227ccb586286e7c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef728400000000000000000000000000000000181e7f8d0ec7a4a7858bc96b61484c24dbb9dfeb3746fd3a231a8e442369e3e83516ee6043b1c06e7e2043dc86f6c75e00000000000000000000000000000000184c1d667c0ece59f18fd2eeafc66f1ed530b7d5f4560a6c886429caa13255c63dea01c3e357e3408af58a39420a8b28000000000000000000000000000000000a8475ea694cf607246a1c50064cf90cbe50ad5cf8006934a1fdf1621ba38d20e70860a2b5aecc05acc60943224cadb60000000000000000000000000000000008afa03c2df8e83fb64523c57d0daa7cfbb7af6a4bf2960ebc64515a61a659b2c37ee661050cd538fa00cb34746a371b64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f0000000000000000000000000000000008b33a297c8f86f1e9d7166f9e905283c8e1581e582b879caf48585d0bca3608fe46d8d9f6e7c90855aee9d92283d7a40000000000000000000000000000000016962410d6b4b6f91437617e84bfaaba49de0369b8748d2e2dacb63b421e0d7de4514e7fd3e0dcbcfba8baa4915610d0000000000000000000000000000000000efdab72953b870d0e113efa7c183d99aefc100ce59791aabc72423aff70a5b74c577c06ca94bfd6a7722199b4bc22660000000000000000000000000000000013b18e31700987dfa4344384f9b41e72afe92c39bc961333cad3e7d0a5efd3842a5e849cff5655c4673f720fd0127dca35bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf0000000000000000000000000000000008b86f70c8d8b03b0e9a8975776d7fb0d08f95eded0a0124551d363c2df57124e0e89bd45ddd1cc75c258a4ae2f87916000000000000000000000000000000001120eef9eaff7c308b629deafb060d2c12b20b57562007fa810a2191d99fabe9c7d3c364caec1724665ef556de66b57e0000000000000000000000000000000007698bbef6dcea67a2c643342ab2a0f830c329fb6244d4a98512daa8a3c9d808cd2acc0cebbe3da920053ad73eb7cdc7000000000000000000000000000000001155b6beb28fd88d252c6b407bb9f55d22103257287ce77353bea580c90173b5c3d49080b319ea28817d67c52bead96f7980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b2800000000000000000000000000000000142b28509d72f9e3be9ee916827fc1a8dfc4ef7ae2b72eebad5db605fdb2dfa4492b50cc3e472df1b52baa6e2b0eff5500000000000000000000000000000000134d6821088ce4a8b42383d5a43a32bb0cdc96c85f304a2601292670633d5e231b9dc479d199829a9ba9f39c162318d5000000000000000000000000000000000636da344fcb0fe50ff3e22f8591418f64cfc722b2860b4a5047f973f42e4cefb93c2f8eb8a14b4d150758ecbf3cf712000000000000000000000000000000000e6fd06d5dca702cc9f199f7583add86c82f7b530d4dfb9faec36dbb669cf7c1cd1260c7e4f3026824eeb5b979e9fdaea256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca",
+    "Expected": "000000000000000000000000000000001684f447f8929ec0187811f66e985f0014eba46eaa87de2d4ac2347d10c0550e4044ec7792d9f315c50081dc2097ebdb000000000000000000000000000000000ee0c46efe930bc98f39dee8cc6a792744e84de4fadec035d25ee8ba82e1c53264d0885a1fb05b2b8dc9c6a1846c28320000000000000000000000000000000003a5ef98843099235a2ad9522c9cfce1908bef77b45794e7df9eb38a4854460031829e947a118e8160365fbec3725b85000000000000000000000000000000000dd205e195abef6a4cfa7da66f022a418235e1a1b2fefa6bd3ddf8a3851d8ca8c27652bf87ac644cd189ae55e3cc7808",
+    "Name": "matter_g2_multiexp_17",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000064698182f90c20ed6f6da7248cea32a291f901876a900d344ce4dc1b07822b480519cb8d891b5ee4f33b4efd90742cb000000000000000000000000000000000e7e9d2e79ec4b07015baf69a283f6a4abc8d7c1699f3356fdad6ea9b1c70e41e73bc14e500634d73749f9900eeb65f5000000000000000000000000000000000002ddbf40619ea5123c100e2d6553213e37883fb34f0f0f2124795dd971892d5c9051cd4aa78b9d20f196301ca9bb4d0000000000000000000000000000000017a07b32fbffdbf7a80f0437eac1ec5fff5a68f3b053482f034064992158b604bc34489dfd41a24ffba806ccb871fff15805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c0000000000000000000000000000000018b4de9c04fde8b708408efb3aa7f24b5f7bcec14e7d06fd5a5b36bab528e5adc0bbb1e378a5ff6fcbc95aea530ffc6a0000000000000000000000000000000010da98267770a47e5ed14ffb3dbcf537dd14ae5eb79522c772a7a2833be214690db0b4e86621de1842d88018fc0f348400000000000000000000000000000000135548e2eec9ae7c3d23618d8286db13a5a628fee04fb6ec9da980f3a46838899cf965c1cc6f562e71d5b5c7428cabc8000000000000000000000000000000001669fcee7804df9b7bef32e2ffeaf285e8501842efe87c9e827fce872dffbf92255d3c3a2fb5c382ab7aec0bba1ae0e5502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000010ed20c069bb300a27571adabd239e70b767af90b91c4d0e93d88278a6da47b7c12fcfaf62ac0a7b9966968cc9f3770b0000000000000000000000000000000017273eddc25cf41f2d7734a3866711e83d4f2823ee6a036942799f837d5ceff10dd6022ea25e3c1e28c7b14ed8f4e7c5000000000000000000000000000000000f201f314f66f6b2c6e1365c0fac7b187d31bc45b5edaef5243b5488e26581dee24de4a5fe493bee44165cc31d8d72ef0000000000000000000000000000000009dfbdd86633edfacad6b78d292141a1e653a1bfd8c48a96b2f6bf8271ed6033c0511628caf2ef258eb64cc8b63d8e5be7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a740000000000000000000000000000000005c4a4a5ffcb4a39c8809821ff275360ff937070cb97a791cc9ec45f429256a6d2d6127248b6ab0b6c71c30c4fe84ff20000000000000000000000000000000019fa60f481c5be953c9c7dc86903a89af0ca2b4205be3a00d793d6de7103852e147ebc7d983c6d6e8cd99e681241ad440000000000000000000000000000000015b3b2eeb0f81ff8a2624e2ff2396bc69feffeef62b1b6a1e73ca4b9e60506c2950fdd23a37cf56387b8794449d3237f0000000000000000000000000000000017021a69ceba3446dad9fcfd8cbe5b89b61372f57d43a8d2e2c8f4534bef6b91408409dfda9438f24526f7e6bf1f4240e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba0000000000000000000000000000000007c8f07f22a3412fb4638cb704751959cda4e42e4612edaf5b1f22c8f9ea314508353445114bab6c07ccbb4b0d0bfa6b00000000000000000000000000000000062d087155c8722d0102c8e5084f95f5f58ed626d48197297d21d2108ee05f70f16d595ef73e8e1207a3c0b013fe16710000000000000000000000000000000003b6652934f3acd4c91c6c521c2476bcd2594a939ff2e7ebcbb0f451fcf0a656a518dbd4f36f165f9b2f58054e9f778f000000000000000000000000000000000bbf21158227e0ad5461de9ad8bd580f9e65327dd4e23f1ad55618f6b0aec45aa6076fa88557953ad15d385a074bc7d96e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc50000000000000000000000000000000019e336d4d342f110eeeba9773b8e351f26bb56361c77fbf12fd9fc218fd075ae38b95f4a8a5ef830fc2cd92558b1711e000000000000000000000000000000000a112725046ca3b6cc43207e6b36f38d96ff98dfe3444d67ee3f4b0208f3b8543768dc9989f936637d7819e7dc5740fd000000000000000000000000000000000527682076572d8cca15e47a2faf62b129baad29afed22d32ea47983a8d0b138653c1353bfc6fbf9fdbec2efe36700f90000000000000000000000000000000007e3c5aff373b5154ae66f978fcd66d09cbebc7e0c96b4a4cf23c4fa5f2fa655410c7f1ce597a3f5f155017720f7c50f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed000000000000000000000000000000001865c265ed4606ed16056c0b28f953119751d7272bb33b9865eed312ba23b32d01733ad5446cea5873c2bbe37fdfce7e0000000000000000000000000000000007018aca1e7ac211921cab1cc6bb18874d2f39f00d916b8f3d46a088a378f3c9b49ab8a296d0aa21608f11b144a0c687000000000000000000000000000000000210561c0bbe5a9f4b2237e5bdf88bcd73326d395277deb2a883526978df90792993e6ee520c9d5ec0a6f7ef5c6b3542000000000000000000000000000000000cdd344124b7b5da556f64ac5d651a6f9b74427fd712007310d720f3236724e2284aab812d739a87f3a1bfe8737dcee7586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53efaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e40000000000000000000000000000000002dd11f4dacf3d9c46579182df1c1c45a364a8dc1eb7aa7d54d0141306f1c23bed85235783a22b8e6dc4adc35f9193ab0000000000000000000000000000000010d1c642fce533039e98712bdfcda86eaa62d2d69b861ec4fd835488732fcea414cfb6f3f8414152f9d5398c73a74fd2000000000000000000000000000000000c6759b75b1e3fe86c00fa124d09c5b7438ad61fd1bb71695743ed7793f39b7a0fc99b055201ac1e3aa07ccec61b24a80000000000000000000000000000000017580c9341789484fb31386eccc9c344539a09f1c4421dd124b1a0ce61f2d0528942f7fe8df67c6b2bbf782996def47b39d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca8000000000000000000000000000000000d9c48a111c8c74bce8cd78d127999531e46a411b2f0be3507226766bc8abd088638a237674ac62e0fb7dd4a86d09b79000000000000000000000000000000000073675bb81e2bfe6adb5cd929e0b7280f5d60b3dee7f797d65ffbefc2c2944a9c7207648bb096f13292ff4440c3f03f00000000000000000000000000000000024d2e0d5ba1a804520c72331fa23a2a326d461177fa527473240dda130f4ef893870e893e1dbf7c5dbb0178dcd29b3b0000000000000000000000000000000002a4c9487485ec33f8fb347d246ab0d41b883bec30d2a5e88cccafa676569f25ffd8341cdf6c09f68afae442a574f3334c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a000000000000000000000000000000000c1965a745e42853b4d54739b2dc507d68d80b330360a4020e4412ba5422daaae313fb9597c98575c66ccf351e62a527000000000000000000000000000000000844439e6f08a411e61d37b5b2b07921049432e1833e839b00d6cc11227dfc8770ad9ca06037043668fe7ce3bf3ce84200000000000000000000000000000000152ad6fabde2e0310c978404a5244209a9363cab1f3ac9f71339cdad6d40c84f8e5a8a196283b581d0209ce90e1e3c6c0000000000000000000000000000000010eb6af62c7dba122b0e24e8326dc906370bcb4ba791c47630f05f657a228c20e010c065b93537ec84fa14a756b199789af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000001febb2cf2d664e4a277cbf08fc1fbacd05db415a12329f7be551ed56d67f0b5dcc917d1b02951657bff3a26bd8c178d000000000000000000000000000000000018af160555292b2f7ce27112c1d60038b564f5427d62604387de97dcf48e4473107f91936b5e8008065a1537f7ca340000000000000000000000000000000016bbad2a7f5451098294a7cab2fe10d206741a99b128dde5eade581d02ca849bab3662fc3400fbe055dd93a418aecf0b000000000000000000000000000000000b1e9586cc1b357da6e58621ce09288e62a79517144f6c6b867359251baad6d40217578d49c1501f23206b125282bdf4c72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b000000000000000000000000000000000b88892250c848e7bc7bb7e42cfe1048a1f61dc546929211846f49501ad8c7c8817f5b5b99ed092d5a2236d59d9c8eaf0000000000000000000000000000000011680c6549f6b7d9d187a6409d40cc26554df654083f1e8a47dde826149d68da756adfb1b65bbd219f79a10d8454e881000000000000000000000000000000000f9596121dad98bf7acb3fd65fe7e0bdc8924e2390341c11d9cc9cbb0517f988ff79a5e1d60bd89449b5f042f0d0b0c30000000000000000000000000000000008982832ef53bafc23ea817be378532b95b5872217093e7c7c2f4512d03a9c9a6dbb7950563a520781c7ae213fc82897b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000ea5bc2f8bc2b4088d1fed7090ba389577b11a3ee0775cb3f0657ab5b07a6709d3a18fa5fc33554dea235c60baae4bb100000000000000000000000000000000196b6259b06a4c91a0bb0adecea134c8609cf983c2c87158a69c9de3b6768510fc56543a84d1266dda78d90c3b0516ac000000000000000000000000000000000d0222d8ef278cd0d85dc8765fa7c4256394a5ef61f91301af6c7422b4cb17889224c75ccecd2df3ddc9bac98b493863000000000000000000000000000000000548809ce26cd498816ef1222d062b1ebb7313a07e99e3aad1431f984e9b8ecfd43357ea57da7e0c6c011c5d5400f7ba986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000000b95455351fbce6f73de0345a195f91bf96abee361908cea6c4dcde72048a13a9a23991a75b9c988ba0afd9491d15696000000000000000000000000000000000305f29b05fed06ffab484cb065d4852eb323fda8c9b7c0a78843bd7143effa95cbe5e50c1a0c3a9675bb5381709b6550000000000000000000000000000000016ebcb25f1b8e8d7a8f7131455ed2be084bdcce40034e7ef24a47fc29e447f912c20c7c9910e025aab975cd2c8cf1a96000000000000000000000000000000000d84a5de7a5fd8592f6cc2bc7c3d93c06e26185787856c922d95eeee345ddfb7cbbb60b6d992c5ea4dfb33101f2ef1dc979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000001377d654f80e933c4598aba1f637d1e37d66a96680c3a89a762f412e187817ec08f0ae897b08206a73f1a423b742261900000000000000000000000000000000014b71954b9bc22ac22cb2d7d7f373c3238c923205b223cce6c219175df2bb6d7258ae46d6cdb019311bd386275499fb000000000000000000000000000000000a08ef83b67bc972a67b9174d0e5b1536af882d505d03464c9a97f68061aa319d612de9db84e1e7b12fc3015fc2973b20000000000000000000000000000000005f716d0ffc30005e4a744092704a9e29f58fb06bf7d8d6fdbb95a4c0eeb5c39452cf662721ea3e0bcc67f25931a109425ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990",
+    "Expected": "000000000000000000000000000000000c53f0ca8901f4751be4a478088b30dce70b9ecc382455049df9ce108eb0a8d2696bb325fe9ebfd7d967ab5b9b2c2bd800000000000000000000000000000000033460babd2984a5d8b7002409349972f518364e92648927e223d7a3b648e952482c06cc713bdc29ab83f2646e9398510000000000000000000000000000000007cb9dfe603dc070151cc477ec5bb5a2a949062e8442399597c5eff8f1decff538cd0aef1384256dec73746e63a6c66c0000000000000000000000000000000016b56ee9b21c533b9c464178d14ba5c92a90e6a54c3ed319f487c2082b1ce1d0ff81131a5fb3dd7d13e0fc1d9ad9e4a1",
+    "Name": "matter_g2_multiexp_18",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000104e0b91821c59290be48b97936458af89078b176b5585ca9a79070c7050309b01df4b0bcd84f137f58304d90599212f0000000000000000000000000000000013b00ece925fd17a8effc43e21d982553ab2764b13defaae5e5419cb9a23ca7436cfc44088c2aded63785e4f07b6e186000000000000000000000000000000000267cdd42febf0706675b60af8c0953582ced84dd5ae870815654cffa46eb14b747fb8fbb3b014e59c929da49c6908050000000000000000000000000000000011c5384d7c3e0f4fd66ba4b4c2ab60f6f78f9930e1fed233263dad25294814d9e2aaba6388ee9f924e2a323693b6e43bbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee9670000000000000000000000000000000018158ad70994584e6f2443b8b96c1e4772a00fa0bf74865c76000eae470eb02cff627579126cc465046d4e088782557b000000000000000000000000000000000d72979d455733756a0849baa8afd79e18960f3f6dc9676c33d1663961617831f3266015cb998fff28b78300c87c2a73000000000000000000000000000000000056192c20cbcbde6099256a8f40c78a32d3fd212fe9c511951c7523a3559f60662e070f5b5e5f87b1686be0bf6cc890000000000000000000000000000000000c7b7e8ab7486012d95af5b2474ce15db612bfe1508852b8d99f4402d0e4f075ba056c19df3caa3a93bb4db89443096143cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000005a2741902dab47e8d38992180a9670faf56d1849dbeaa75b2b4ded93ee5494184c8658232e9131a8b08ac9b5460bd400000000000000000000000000000000189077d5130b3a4d7d4c3074633fb12739f95b8b6ccb082dfa61d845a389e6ca7aff835fa0f194dc349e1584b3141507000000000000000000000000000000000f226324f242cbc5f616c4a897f82bc5503ab1963ca38f30070c7c9916ef6bef5caa7e2e26b3f9fe68a1d59f19a9831d000000000000000000000000000000000a999bdfa10e4838ca69694272b0187f7d0198d6db0fd85eae688424fb09baa165c623dc6da567fe034d7cf9f9a0087e733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b6000000000000000000000000000000000fe85d976befdae8fd0ad33a4404415304afad1c5698b91bdc15abb4f268807c906410a6ca827320f5271c8fd4c8d6fe000000000000000000000000000000000cbff7963daa20c1d20717bcd47b872b3ecd5f38de1a467ef50936f13d6aebd978116a736cb6c5d676c6a9525bb0b7fc000000000000000000000000000000000c3d20ba17a21bbfe873d88e9221571f1bae7f02f35b8e677c9c42907673d765150c737f0011fdbaf4faa883b0dbf0280000000000000000000000000000000013482c68a5e1084faf12e8aec92cd9f0692b173556ac8ac3c7519beb4bd75f847f41ab9432421c631b14c885c001dce25112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea71000000000000000000000000000000000f9091a0df2c989e12a844c447287b704803d1532a3ecbcc890e6f6a885a54b969c53323c105b3d14d12f2cf766b8ac8000000000000000000000000000000000e54f3a9def8b3a9f972726e606195849584b7197ab70a28cf5644cde15e70bb6e3044042b649825adaf5e37c2d5e614000000000000000000000000000000000cae412d8a3ee3c5af38d7a65bdf2440d9cc2d6348dce0791f4a7e71ac483d7487b6c789be0a401777de3f57ec65de820000000000000000000000000000000014df09fd2ff406707004f6afa366d06bcf8bf18f5fc4b444b07c98b3f358247c6056a6337f5b53c35db45904797fb4455683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000a8aa422e1d58fccc84615f9ca4a4743cf5efe3a1066c9819f05042100bb8784fcceffc8b3a739f549b42f34d62629e7000000000000000000000000000000000c737cf78b10e82fc0cc9823891f1a5f1e9229d61e8f369c589512d01e5180246db46e4f09e811464c6e1ad930226d390000000000000000000000000000000016017354434899e2285da6ff4b27fbaab633d962197d2ff4fa5f688c4a85e1817434cbef13a6b018df4e359d7b9ab7cf0000000000000000000000000000000001433c364428ac69ce4f5678aadfed4e6d076241519310686de01572da5cf78af4a98b3502519beb0dcf04b748d08cac5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000001f8b803f3f76aee9825a9a960cd2f9e8aa931568b32be6169036683b4e6d8c4abba6bb73b137c7c6d6b6ea92f2023ab000000000000000000000000000000000fe9edeab60bb55990ad2c85c8fc9341e81de54324652c08c615a745813f08153bab3849dbeffcf4073f087f7c0cf0f6000000000000000000000000000000001955289b1210fa31542bd89f95188d60751b32e8d54f1d4d280975850e57db7b151b872bd431c528c22fb89c9b8784af00000000000000000000000000000000079c8a56c72adb9fc9baa503db394635abb10264dd43c60f2c82d041d43240321ac1028688d92c4696395d8840d52f15f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da0000000000000000000000000000000000b0fd79e62c6129fa115d821b8f2a58a4564f5ccbb14088f59d5e6a17a64e803f32bf8e5a415aac4d6491612d95ee8f00000000000000000000000000000000008d837b6c70468e1e10f6b979b7c0694d65942aac48b5baa829c191579186314ea35fe440e6d843fded02b95f9816890000000000000000000000000000000015a05bbc4607b113b37dc0b4b8add23736e0f1bb1e48aabc15500fa6941b17153918d256b6442687a432dd9ca9a198c70000000000000000000000000000000003546953d97306266bdd359d4daa939e05c0466691de59d2dbe3584e2ebfd9a9e1516cdc9cb643c5d31731835dfb07c657a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000009a4366299290c3c6651b22865fb22cc972a05ca5981f5682574851e41096d531e375e981c4e1b1cbfebbc70a41bb6ad00000000000000000000000000000000001e6fe2097fca2afb8385a3100dbd5ee1b7ae972e06ef9f5e34eb9fbdc65455e1c822299e06a9dd5a3f71a0c1efd44a0000000000000000000000000000000005ad2ffa8861848c46722a7924ece68580fe44e03157c982b7133361e974b59dab7b75358fe498fcde9f68b5b99f23e0000000000000000000000000000000000adac33e0b7e6740c980a4f297917fc4fc13f53a71909f2eecd0067656c6f82c3b371cc638509151bf937f8257aa415d86ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb000000000000000000000000000000001462d509503d2c33829c3fb5380199b79b970c2ae7f944e54a6d0f0deab3571976916cfc311ea6ce6128c467665fbbd10000000000000000000000000000000017f6fe356cb0dd5bddd489c26669f0f365260bb48a5f862e9bfb778a7ff5392938b905759718d050f7d93f107236cc75000000000000000000000000000000000d9b3ca93c5133cabf3d3daa565bc6b51e63b7e37f68f3bcc43b9b3ee7db15f8bb33052eb7e332ae3e9ffafb17cb77d60000000000000000000000000000000017d6b898d9799385990c9dcc3f72ed93333486b98349ef106a230a71d768b75cf56cd946f5952075bc41f26dca9c83c003fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac3773213600000000000000000000000000000000158e5e008796c10f6050826c29523864d06e68977cdc95d281a8606924aeed0b475ab152bec5bfca8e0ec53691b307f50000000000000000000000000000000006fe8e75328c067546eaba93f4be2b15513bae4a3458112c3ffa457d15c23636816fb469f071889380f31870d713e949000000000000000000000000000000000b9b21cd58f8742ed094e9b770182f6f3f855204d869e53c02d0c242a133e957c53c9fabc827d6379b39541170be313000000000000000000000000000000000014eaae1f0789f0b1e8ad3b452b4ed3ff87bed49ffedd13c8c35c35668c33537b63050c06a5bf3d88d516cddac13b4c935ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc627910000000000000000000000000000000019f976b3584ffc188424614fd287eb79f060c55e9b3dd2f3eb99760a7cb5b70e2b62a0895b05e7cce2e390853fed61b3000000000000000000000000000000001117181241fead3865eba4804ec2c14f571aef5351d5bce29399113d007cd4e9c262af1c77daf9183346153e562864b2000000000000000000000000000000000f823f71035a4870be2ef20bc94e97d74d18c0a1be9895fb27c54df1f663df6f9e6e45ea5fe4502143a84c05e517b02b00000000000000000000000000000000141250f392fabd4566e0cd3a472a4b2971a432a3a5e1d9c924866c7a9516322bfa691e9dccdd5ef14c561bca6dd70ba204a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa008452700000000000000000000000000000000014d6827b9bc782863491bc7c544263f58dc04c18e08a87ca2fbb5799c4aa70bc039416a85dbba67dd83bcc27b70748670000000000000000000000000000000016c2816e93ea9d4bd6e42a9720cb89d637d88e00074da3300c6409be98a03403e9ac15f83167cdeb13800ad174ac47f10000000000000000000000000000000002aebc0116a62f93a6e86c7fce86745618e08f4aa9cebca7b520e9176bcdf1521cb2bf7eca7f7af9487fdc82dce76bb50000000000000000000000000000000010684e3254207c4ccdd49e4775198df981afcf7d9f89b894e204c5dd84ef42b89fe3e2f6b9278470e6cde4d3f4abb3b003f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c9816210000000000000000000000000000000010ab1d5494509060c9784b4744a0572a9466d6c374524a6d338ea12ac5ad89519217c462c3487e398325439311bea86400000000000000000000000000000000197568cb53ce03f00aeb04278f355da862be757366dad14ca6d30b3a537df9855a1196010773768a91cb4bb664a34f0f0000000000000000000000000000000001fee249315794d30eaf929f44b99e07927194c6015ff34a4530698d7d68239240c9cc48530d52ea06218a826a655cce000000000000000000000000000000000645b5d701bf3422228576467120935f014c754dd68bb3555b50aff5ca04001a26298982c97a64469aeac3432784efca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c94234200000000000000000000000000000000113e17730f8dd7258157085c30cd9d1950a26c848b55e3a8a55865eb567edecfb09f32ba27fb3e2096ea00c30f31ced8000000000000000000000000000000000076db9ccf8df9530b64cd43ef7b496d1f432885062406028901bbfc5882fd12533f84eb12aa2ce8b7adf9dd980db0870000000000000000000000000000000015e487de49f1e494ce9907cf0ed31fb0a159c5290538ad969b2c8a504986dc9cccf7c74a61f622154e928aa2dd689c0800000000000000000000000000000000195e887083a98fe3f50a9ff4b342e004398cdfee55c4b02a4db0f65a77d3c0b142a45201674726c96d5f79f8604d61860033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000d80c7e50973205585b20a068c64957cf4572eea40e32ffa8b759c38c6ad6f4468421f2fd6a6f5da1b0d008f625b3e6600000000000000000000000000000000009242dc1de055aea82b3b917f88b6232c550c3aff41241a7e54caab4c234d29b5d8138968846f7c754d73ab3b4e7913000000000000000000000000000000001188c31a9d8359d737576f4ce7a7900314aca0eb3b51baeccfdc9245bffec49143a11b3331f9126b01de0c307aa4e44400000000000000000000000000000000104ef4835124fa6b30dd551653aca25db5a544af6782cd0b1e7d26178253e0e33cda77428fc1dbcfe6114a758cab5c814c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87",
+    "Expected": "000000000000000000000000000000000e79d18633c18ac818786bba87d09c9bb1571e179d8769f8fb82e2e2b7a6a8695c1f4f06deebcb84524e8facdcb49d0500000000000000000000000000000000149d0231fb030a1bec170decd307c10e72cf1cca55c8a1b67aa94ce61e4c7d2ddfd0b8e71598e1abb054355dbcac1528000000000000000000000000000000000090f5be784dbafb0a8aab1516c773720341de6176017e0fb43a275d60de54c1189144956d4876d989232b362b90851c0000000000000000000000000000000019dba28eaa6706361f285b3abebef68f764204c74ee93ea011db01c19591ddc6f98799fb3026c3c223effe4489a7c676",
+    "Name": "matter_g2_multiexp_19",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018a6a982acce5693e632901f3136eded40071e8c7caa7887f302c32621c5bcf9478991ca519978b52f8f69415c0d070b0000000000000000000000000000000013420ab920c8ecad5b2f9aaf9b0074c2386b0b08c81923558770d4c4a6b206a865af8322e9755706cd5e595bf0ffe564000000000000000000000000000000000c0e5bf5465d564e3ce86d6b742ca687448e6952439b1ff44b86ee6461464e07f8039e8ae7a301c6caee7eb99e38fab10000000000000000000000000000000015eb8751b750af62f57971e88b436658758bd5712f98861fa07328d2b11e8725fb55a2a00252e0be06b0c73aac0f7b8cdbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc80000000000000000000000000000000001328927910ab502e573188271108706152f562b1d5f6ec074f8f9ec5eaecc6cd5e8284a060b65d26463d22c8290ea4ca0000000000000000000000000000000005a1fcc348122350981dd5090c865a2aeb851ba8b6e0443c32f48b157ba673ae5652a70390888b3458afe6fe975321700000000000000000000000000000000019edc749a9799c8d3df75d4024791943a8fa02ba0cac90b6819f0bc42687b044457bc7cc6073506e8fc19af37f224624000000000000000000000000000000000fff20fb2b554b63758963c1583b996ad450cfbd5ca9952e38f38a8994809096086ed86311f7d73a0a5898ac261ce09e57df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec00000000000000000000000000000000002a7005dd32bddf1031f27c2ab999604c048a37c39734db48a30baa86c61ef626cf82084651ae9ba8a265333060a408d000000000000000000000000000000000421bf913a25108b8f520b2becc6f8064029dc046d0d5effbef31f0af59eee71cfce83fec8dda7983d50c6d5cbc8329a0000000000000000000000000000000016c75708f1dbfbeae3b06e5e9a7fb676c27100b99deece14d979b32a9c3cde6e9e96c8560a00aafbe6e7decc84e7e2780000000000000000000000000000000000ce23c27b5128bcffa424fd1d181d21b06b77bd6549ca5eba9a28cf18bb9a979270f6a5807c640dde57a0cd4f3af8cbe2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e000000000000000000000000000000000690c7ab321c0c93b5ae4ed77843ff4030e4ffb504c685d28573e98836e8e56dc19d662ae9f496a346bf2a8be5396741000000000000000000000000000000000fbbe3861a8d202b10801cdd606b50db0ad6ec7b923b90ae81ff5443676c3399e249e9efeb47b72d2b0a54cb0594686500000000000000000000000000000000148a27016968f0258e5eafe0a8182c22091873a5a58b27aa2160674584e06d5b2f46fc57a00617af18d0688df75294cb000000000000000000000000000000000148449d00b3d1b5b43b08a0c6e909a2d9c66920b60224a2c6a2521f0bad35b99e3bff8be0effb2f7f34438662d7a4882897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a9400000000000000000000000000000000026b55509b81befaf6baa682a3e92a0ab423fdaa84d2897613fd31acd9e1590f81581ba0ba87d68af76b01c36093e183000000000000000000000000000000000c675e190570bc5173b8f508d5bd2768c83e7f56a08cddbc636792dd75386939942827617c4aff8628a74b74195adea20000000000000000000000000000000014f59f38ae9e77f3a76478ecd47f32200567bad11f191d303cf15d7801ae7b5a3286095fc8726acc9818914b27a776bb000000000000000000000000000000000da89fe9493b2d9d46596d80162f5831d4fd8cbb83b46e84e95d5d684eb927022ee62ebc3519442007fdc543701f97bd2f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000170e52a240a7ccf2d57ae92ea8dabe62ca4b458a5da42319ae89cad22ebf13541b0daccafa1b1d3cfcffe81b500c4cf400000000000000000000000000000000174879425f3bfd40fb74a88e3dc578e45b0e0eaad94da009e4076dc42d234d78248ec3a035666dd6de235f87e1a47bcb0000000000000000000000000000000005aee47acc3260d11fe0ca16050a29f92763b3cf8ac78da52b3b2b3e26d8ce7b6ccc187fcd81695aa456e9b94a84269b0000000000000000000000000000000005eb297abf35b51d57474b4989dd8f793005bf8e82e49859c41b786ae39217b2321299829198bda4aaa261a2723d43d6ce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a61000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e277b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000002d947e728a3b376de520bf78e56452930de42544241180906719a24d72df65f8250402ccaf14d69935b1ecbb0b4d34c000000000000000000000000000000000d5614ec77a9f31915dddb3e4bb533db001702891a45f0bbac49e73d9c19a235a00442b52d452d77018f883706a616f1000000000000000000000000000000000dfc6a73a8e36b7b2d0614b1c6f7bf1ae284ed740c768f08416c0c09a601fadf3e4d7b17a93601b1803d19a04ccd570b0000000000000000000000000000000010d6a8e4eca2e818d6dff13faf0fae44a7fb90be436a9ef3aab05515a35cebfbd53e9af866cde1745f0e2c3b045486dd2b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000d6c5a6fe9b4d4580f8e1d89f0510bf5dd04e113d6ae5db04af2553bc0eb3a32fb881300f638fb33f7c4bfaa10b063660000000000000000000000000000000013e001b08191707ad98e21b3e0830286c6f3bf587b971dd4ce39e55f06db427676626a5c31c4a67a996a5725ec8f402c0000000000000000000000000000000012f86ed85113ed1abe9dd3826423911e63df0dfb51ad3d1e0e0318ae95991a6a11150176cec77f9c83268a322cb7e934000000000000000000000000000000000dda719cd2cf1aa769f94c21af20ab076b8f024e0a4903e38ddaca21b6bcd6f00baf7e1ed23259f135eb8bcf9c3f97c44f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000195ccfb9038bf9e637b88c83c552ffbd562357792513b15f703bffbd373ebaed715a6772fa7e6e5678c2e6422811dae1000000000000000000000000000000000c5a110f31d71b12cc42974003ba39d99dfd91769c2e93393449083a9b84d31473e3a7dff7ca40164e6e7215b03f44ef0000000000000000000000000000000006233b2dcfed96559b565928a494f2a50c2c375b3d7c60ee6b286c538f4fd5ca6f8b2a61654fd04d679bb3e05b9bcb03000000000000000000000000000000000d42233b7b5ad809c735c89c455ba1e8fbd623e1602bc729c01d362368666e4f90e7b076e32468041f3f5665c6fddb0d0ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000f6d53de4f8b20de19b2fcbe8a6b8b8ec4bb801bce7363f89b133532ca7ce4925312e23c618a0182d158037c0d0bf07e0000000000000000000000000000000006ce094e24eb14b9bb1b4a1838d8b6da5f53b5c5799ab8dc8934b488cbabf698b99abeb016259a4e1b0f626d27f2c950000000000000000000000000000000000874aec7c8ac360e3980a6e2cbf3f7468f1df7a8d9158f8bdbb0f387d19f3b05326a081129576251ec41a926f670e58f000000000000000000000000000000001711c9b2ed7e2f789b29073f180e46d0c373d6e75c587ece67b8aaca1e9d9b43a96d04dfdcd42f943eca48e240b72ba8002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000e8ddf88269aebf190bf9bd7a8276de92ff6039e479e42a490fe4ef00f646b049eb8ec4b8e073caa000bfcd86ee8724a000000000000000000000000000000000a9623655c0121ea0575de714e53c9e304fa3309f00828ba0e786112781a38bd458cd67864ab17929448171b5937c1d900000000000000000000000000000000198fccc4a333322599697e904e9096240b9c54f89ee6db97475beead62ebf730da1a179409133698ef13abe1310689270000000000000000000000000000000017b059ac08a3fcebde5888bec4d7cc2c70b147b3b1483fd001330637ff1c036faebf292801204bf2ba49350795708dedaa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000a36274f33b4dc09e03a5ad648af0913e5ee95af83df8b4f2a158456aedf0a0528f9b4832b11162dd67e4d22b26e9f940000000000000000000000000000000008ce96d8bc0aaf2dea732dea188870d398b1f3c266b9bf019e1046cca05002416c910e02e998a1604a17c333c65c99a0000000000000000000000000000000000c1a0e4a80bb0331a94ed14570053f941a0438794e6f19d976cc62b3806a565697720ea03c2531004f13453991bd99bf00000000000000000000000000000000184bdae93abbe4d931a6a51ec85bc330d6181da2d34f2cc530e56b6803515ba87f5719fd6fce6a1a8bf1ee5a968bbfbedf9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d8600000000000000000000000000000000016c3782daa55312a7cfa02c3be73ed75f4b726df5592351fffae19121b5cba73f427d35d5a2df7c63e2a5c68bf57f3800000000000000000000000000000000018b608343616eff759d512c97257f2103cb0909afb4c24a1cc9d8204274b7c9ed51bc762a6280e223a6116a9b23d1f1e000000000000000000000000000000000c687c11a879ec285180cbae3d2e4219df4614e238d4cbdff148ce5a8d21647c489ade3bf6f738052f149fdbc76c8bf6000000000000000000000000000000000936b34fea3a2633b9aa32244329891e332745876d05f95e4efdef859b23ceab4869db562555e5c8edce87a6fd075ae54005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000000d4636a5e13bb59878319af6bb7c98e5d247c2c9cc970b9cec98027de2d4a8ad12d50906fe302c3d055c499a3742ee30000000000000000000000000000000002b0214bb1ee887a7ff10d458fe35208573456f685ee2fb93bb470762c9e27595cb00f2eae7574c8467e417c63c2a960000000000000000000000000000000001710d130f91861230562cd7ab87984ef45916af8e1168fb17b9765183d9d3f9b2c81c649687842de495a757471e28067000000000000000000000000000000000dd15fe505b1364f134ee77e5e3c1a497a20849b6ec7e201813677a1569a9f5a9edbe3df4c36bdcf9ada139b20e048ec893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd151570000000000000000000000000000000009a48d7c55d24ba49f890791d0f6a8a5ae08a19177575dc0d734fa37b52c3adc45b31b5e485a5d4a5533470c3549f5f900000000000000000000000000000000090f680c6fc1f0588add04ee03bf821868b1ce588e3ebe384dae657ba7885ef74da0bdc98d9d9594a9b979d5b50b93df000000000000000000000000000000000314f6aae1e99dbe3ea9ef85db7e1693a30869f48e05cdb073bf8e14865a671e75abb875d1b41f13d4eb74fc802299c70000000000000000000000000000000013c698b76dd68d1b9ab41672c2b07cb9a63168497d1144b51509b602c5acd71ca6cd049616d949214d95ab7a906a8f8bf685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b",
+    "Expected": "000000000000000000000000000000001747f6d3154e0717435fa023754f115ce2a2b3241b62525cb2833473d84a8ccf4c95e3ea030f2b8b0ccc61124095ac86000000000000000000000000000000001827ed7d84a61c21268857036e91c732b304f609f285cdc4736c951fd8954b10267a8505f25d8be666792358632058b400000000000000000000000000000000121ac61f59051e6e89a7c1e2fb4df4b3a5b7773f46495a99e55348454e1d9d42254e5e11b841a1654ff9c80b157389c70000000000000000000000000000000001bc60cd06879980bc6ef2ca109d31f12cac28ebe4d2a934076d720b12f430e1bc4d4260f40045cc7a862726521a69dc",
+    "Name": "matter_g2_multiexp_20",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000012a6984f0f8967c5ae6b13569a62095b5fe61ec607daff1845961bdd827c00fd56ef864802673dd21d90560fef6cbea00000000000000000000000000000000085ececa080d0f4c996d46c80a1fbad2ac9cff8b3e324aabb67182d79f941927050f025b633fd5119f30bb29b8e4b6f2000000000000000000000000000000000987518a5edfd5ae2616fc60000e117a4f1dd1db68195c3fb68d8cc639e4200945b2864d41ad86fb3e11c504fc1f9766000000000000000000000000000000000310939c7e11b93e5773cfd36fa70020c85396e525840742f994110e20019769abcd339db6881291639c193b987b68ae94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000ec6922dfc74009c3750ce2540558c7c1e05cb45a5d651b96427c615d8fc563219215a0ee431c0a4827e40b26c4f8d3900000000000000000000000000000000040a4189d002a0e1ec600e71303575e82414e6400f06b9abf57151a28835d454f56421a6dc4049902bfb94dc0e9967ee000000000000000000000000000000000dfefc7c163c34cc004e9d97d812b2717d4736d0d1c722b6bf1a29676a32c8b46878d05a2d137cb7fff5fed8c0f02474000000000000000000000000000000000e3f0c9cbc778693c8ba88af8306d45477493ed6be1bdd9c81c65341239eb510fc948142cc30b73f570819b38f13e20f6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed70000000000000000000000000000000013705ca4ecca16559713df65b376c7c5825b4f63d001ebbfce9cd1b592af5f2ddb38ac7c5ce3c5f7af4f39f909887e8b00000000000000000000000000000000179efff38ea1044e91ccad467cd2b49438079ccb4d0fc692e79e0bc374abe064fb9979c4a1f4b92c15cd1b042b501d5f000000000000000000000000000000000b6fda2dbf6339af225515681184843f1a9bcd72f7b1389f186f8d0e048ac16e20967c28e087cf09d7bcac597a85398d000000000000000000000000000000001946fca8c816e1e11187aabc40dd2436533d537ce4639eb2d08630ed2ce402c1806b6c2b3e04a960408fd4d2049849bae5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000001802064095d029d3897725eeb93ed6e3b090390769026120aab6977d0de264a262aa312c5777ba322c9eac29e5396fc6000000000000000000000000000000001410f17820941e6a67b1b4993496cdcf0d4fa2d4fda3d43ee985f2606b1408aa9c9ce412c80c90a0c876cb5ecb76878c000000000000000000000000000000001514e9b2c65ca86713447f2d5bb8395fe8552e059829afc68bc43ba9267ef41ec6d69d06e7407a731bcca77ed5d9716f00000000000000000000000000000000025b5bb18cad46179fab15b2ccef17858f9259a90ea4548852b8c6fca69f0ecdf0b175669bacff1625a7143e762514194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000146eaf5da57b6ac788f8caeb4b2ebf7c8999e03dd839977046ca834fffa7e57cd949e3fd44999a007b5dcf3c8621ba2f000000000000000000000000000000000d859632d3424ffe4227ae14856e05c4e750545cf276c97aa9ec03ebde334144eea670dc68e92b61fc775e477a2154040000000000000000000000000000000010b44279c0c80886e52fde5e71726422da2f9457ff86b21426d80356fad95d5ff3a7491002364d9de5ca99c2500f344d000000000000000000000000000000000851b769a691f0ebb53ee3693833881fed8dc6d9e5f1dfeaf4ab1aa7ad54e2fcac246b70d81110451ed78044a98d1547de4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef100000000000000000000000000000000131c9a76109929fc977a0a6eda0a7c71cfc744f5f3654e2221ce84c70787598e24c5d8049f92a7c4d78fdb869cbdd1ed00000000000000000000000000000000049872d2c7d472e090d2975daa64fd96f33e7f934e739633b1d7fcd5e771673ed8820752a0d5c8b0c6933318293a4f27000000000000000000000000000000000dd68fbb592a3957ef893180dd758f75978042add36c91b7bf87c4493b0baa875e1854fbc09e6856688cc241b76ab5a20000000000000000000000000000000006143699816cad8ab7583a72b6064fadb6caeb51c8625ddbf7b2911426cf438534da1bdd13e22cd545495c486c9733f7bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006c14301984607d569ad1bd774135e7c9e328be1fe54c3b543276bd06bc0bfff11f299a5eb43b5218c3605011d0ea6d80000000000000000000000000000000012f0a848022f95f4884380a5b8e3637a41e3c399a8d2765aada85dcf4b7c2b559122f792850430681a58ca153be2768a0000000000000000000000000000000016b4cb233e1bd59b7b362c64620eaaa5029c173a05e2278774ad6ed746c70a2f6e76c237182f5d9d790966ae69da5d44000000000000000000000000000000000c277d54a7a72c8528188f6cf29d934cc66471607e5e30d493cd11be6b203bdf734aaf37b686cd7101e8599b69446991c3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced432900000000000000000000000000000000169a3928266375dd5793b7504727f939ef0ed52d69e569b1b75a0e094698b37bc70472578beaeebfd0c3df4bce6177810000000000000000000000000000000008936d470dbb86db1567bb2fe7c09971c6d12b07208d9b1b403c20fbdc05ef8984dd576457fc6989470e40ebfe4ceed30000000000000000000000000000000009cdec9d80f2bf3ebfa9a3316e4250741d0d089245df2fd3c9bba4bac1c2dadfe212682166a0962f78c4bf25b618da900000000000000000000000000000000016521411286cabf3fa2c8f72ca62ca311738fbe63717fd12916a4c9e6af9b05d1f5d65cf60e84d9fc5f7b7645fe9bad570fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000006a827f6149a320a74d9d8c1ae8861c1cb963b3eff899710eda642dae6ed4dbc247a22131758d9f843c62710ce083208000000000000000000000000000000000c83a9fd96bcfd4adcfc6d5a47e84108bd763366e91bf06a7431c6c3a107cbe5647da99ee6c1e57c376d366b21a923df000000000000000000000000000000001604d5c0364afb5503b0e1d52226988d7f7f043ce95e7c0a09d7f96e24a58f089156f0e6d19022138170c1b4b7dd33560000000000000000000000000000000019a11c86f78ce462f46e0462052cc3d342596b329fb62a282a59bbd64c345bd266922b1540e40aac147681754643c2e3e538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000015635de295c16841bf44c73639f047f735175e8906301746837838d124bf0d2a1ebaee142393ce9a0d58107c7cb036e90000000000000000000000000000000004fbbd4252fb901d0737d1bf4da62010c06d690a9584c7631ef5d36f1d8c37486a83f2a1e2db21f05c993fd117c662e8000000000000000000000000000000000f4cfcec1545a08e0e0298753ebcef5f61bfdb7c1b9af71cb4c2f783e4fa3948945d357e8302d99aca96df0cb0fc01a3000000000000000000000000000000000f543dad6d4b797f6fe0b00215a5f70f6340ac6bf7cb0bdfc5bc7698dbf0647e4098413dd19ca7af01685edaaa190c6e30b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000ed552e94021d0912a0e7563462570cb572b189569eb847bd12ebf976d22343b9ad04d400ae98fa184b10ff36720f12700000000000000000000000000000000178727c3e6ff33be9894ef26347b104023ea0bcf79c1a33afc26ac0ee9879344964fada757118829214cfcdbbc0c5a30000000000000000000000000000000000b0a6a575afe5b0c1e287815612fdd3838ab39e8ee7795855837588614715f6687910c42217ad52c1b8721a9e1c908dd0000000000000000000000000000000018cdbf244c78cae1993400ae164b42c09dab4d8e3707a69e25ffa8d0b96b8270c022c0375f933f16f45c9274132a0a633a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb460500000000000000000000000000000000070636611f903f55cc9499481bc3415a6de62d5e6bf8bfa82a8ce665f85bcf01690118441961ff46ff701e361db208500000000000000000000000000000000013d22dff8f6f86f659ad17ef91d90a70c180538f03e10de20c445d22e637015d51a311a3daaed90712d04c9a3d992d12000000000000000000000000000000000db3535057db95fc262f8adfd7f08f3237fde5f0e2aab589d4ddcd9c23aadc437e13644dd3b3534dcb17936a7c610cf200000000000000000000000000000000044c177d4484c07fb04d1dd477b188a2c157973cf26075001d14d2b07ebb9dbf8e495dc23b32a2419621e1c129b08c5ac7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d56000000000000000000000000000000000e10fd069f2f5fddaa0112e70ae89d1ecf034defb24e2923731a7c0068780177c186fde92a3c254a1cbdd255111a4b7c0000000000000000000000000000000018363e01e86e2e922ba435651ad892bf9288be14b54dda821c397ae6167f9478c8132e92b1c2cb0c4037a4e020f08291000000000000000000000000000000000301b5ad2d5c35ebdcc7e7cd1ebf0405cf204d6f5e30ae6f46d20534eb6d7013682c5ae1bba76d2811124ebded0d2a590000000000000000000000000000000015fb3a8afad778031d04e094cbde5f02dcc89ad7b7d452c6c8f41be336a4c8b26e75cfc685b8776cbe5a487f09c304083563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe000000000000000000000000000000000a7880b00f6a3e959ff1bd207fa503eff6e7279e701e37b40735e2bc8bf49e355e92edcaf23aa3654bb26fbfb07b5fb100000000000000000000000000000000113d9b792f4e3dcd958664a8778dc4b177c430d8db9da7805595e40293ef2c0a40f7a843bfa70ec134ed89a453f9da50000000000000000000000000000000000d7f92148dca4a9c96c47a0eb284f1834cf3d141be7c0d9a7a060af6e28e45620d8255e465e9a0d8f78b2ffe17d6b04e0000000000000000000000000000000004e7917a8f3070c656d324c9a816236842fbd6147d326652667e7bca0666d214233ed136dd9464c4ac619d46c28e2393833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b500000000000000000000000000000000160cb05390b54151f6b154b396bb400a91fa83d77fabdf31fba349d1bf3b5dfb6476ad4d714af2a2963e41b077bffcb90000000000000000000000000000000012885f7ec8e780cbaa90a465b5706cf07d45bda7755ae3477c79adbd7956b926e0ef5303fc13f0b97349ff8b754dab500000000000000000000000000000000009ad7509e9e7f5018ae3d1280e881ec12129cbf825cb6606459211ed7b358a97cbe430e94dd9f5e4f6b74fb7287f862e0000000000000000000000000000000014d5d2ac2dbc3d5a061f4e52dbfa68e1eb1d3c818ba26686a3171e310c63cfeb188030b83407070019dc5c42dd079413d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000008ee93fc610712411634079be0bd96c3969b48955fe5478b7a31c3ba7639c18291034167eb62e6b15c16b0dd5145edf500000000000000000000000000000000158cb1731b71905d7b958c5407f090a2c8a9319017719da143a3f4f3fb3982abb83b8dfe14facb014321b4f5edb5e41d000000000000000000000000000000000a9f98f775f06055ac1f137cbc1f95f4afa0d1c4935f536ba2e0569d874d9d76b7b86f71afcea07e2e785c7a6ee1c84400000000000000000000000000000000072f8988dd1ab0fa8037d3620068b34848c65e20dfc90612d123b6f9dbcf9d9d699d5ea73739d31ad54c22116365ab983ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0",
+    "Expected": "00000000000000000000000000000000161203d8db1381722644f87b04f47e4be2ea2bb105ea0e67678bc8d29f8a8a3247f8c05e057af8f98032faa93d896aaa000000000000000000000000000000000d3af4842627a095a2dca99b52d802b2ef8c8f3d09873ffe39d224333fceae84bf74780956904df6c1dcf5ba31be218d0000000000000000000000000000000001c79fae014e55e5d0239645d618593bfd5aef665b3e636dac5d191a8b88949d207cf0ae9822ce8e1997de302b386b8800000000000000000000000000000000136314cc68b372b06e7771d82b7ce7bfd0e9fd306787e07629f831c7aee853bed80172121949a940bc59c4a0b76f0819",
+    "Name": "matter_g2_multiexp_21",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000099434adf799099f2e6e2fda4c905e1543893462133ba216aace25836db37b3dd5bd80af1a8c31c7fee56b5ecf9a0acb0000000000000000000000000000000008a6890e5bcacc13e116e3fe2d772ff49839803e4f81d6b088ecb7835b1ed44f2bfa04de1d46dd352346cdee71774e37000000000000000000000000000000000e94fe40225e863b7bdfab4cdc0c1c8d1399554ebbfa3f2c95ddeda74b3dff03d5cc78e295accdc9f02f3f89b4953de3000000000000000000000000000000000b23f2912fdc7a5fd1de69c1f479228f8ffc9f97c40845808cf17a6fd8131ea60285640d32bcd64c9be71d419aae82fb16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f510000000000000000000000000000000004c6f5aaac90132b2d0c6a4e70354ed2e724df7c3e6298eb9ae4ea92e3c7981944c89140c52e893ef2edb2773ab36bcc00000000000000000000000000000000021e813378be9ec30395b917ded5a0424fc7eab0abfdcd2328f725bbd6a1dace0a5aadebe40e10470df0c09b3f4b68440000000000000000000000000000000014e3fee16a833f8c543860ca438d763f764f488463601741a2331fa90efce9f6d54ee0fb7978460a1ab838039d398918000000000000000000000000000000000dec8bb882fe6028a4155e6e2bf48ffd314b5519dc4560f8f7410209214c4a8e37b2b36facc53f4db11ee63ff11f9f228c02014d5392d30863a12102d1c9315839b5611dccfdb489207f9186625138500000000000000000000000000000000002d107029bea087a2d53b6b371aae06c695fa85631450f4ad92c8948b09ed568b28948f80f1455cd22e2ad44697290b00000000000000000000000000000000002fab10cdd8bf17a633c8b3ee8ed2ce783f64bf978c384fb7dbd7e4f0da50b65eb9530365d982bcc17ab91a29eabc065000000000000000000000000000000001369237fb3241ac291a868e6f4610a5103d93aa915e954f18bcf348ece1560a12451723b96ad5fe162a6107dabe1c986000000000000000000000000000000000cb70b7064a2f94efc86060431ba4dea38bc64822efa73c76f3a4500ad23c452c8f2e72713b066a45bfa49559d14a719d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea2490000000000000000000000000000000005ebb9c8202cba234851cf5e060a4114c6fee0632f37e0c52aeb852637f362ce64403347d336c32617cc59f23cc7c93e000000000000000000000000000000001126827b6a0a8adb698854c0089276861e3cccfee420512f0966df78ea0d9c55e85a0536f14ad40e649b8fe4384c836c000000000000000000000000000000000998549680649b294d506c529ade746aeb087f75d62a246b7abfb69397ed67f0f2ccb4811219b35aa894b2f87e3fcddb000000000000000000000000000000001027b604f877ade32df8de6162251acf2751a9bd770c21f22dc819a4f5515bb276a246ad667fe7881965f0b083d1f76304753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000005d1484bad44069b16d1ef4e9ca1db70ec6cd82eca645c2fbd4029ab4ca33d79780ebc144d8774d82518c1fefaab38530000000000000000000000000000000019abc7063361ed64a5750b70bd59283e6a61d55d49d8c2ea2f1be8ea425f040d3865c399a66c253bf38355360f06cdd40000000000000000000000000000000010a97b13b3b579ab5f7fd9801d9e4fc40f3b2b2acb9f21bfcdc6b6a3168720fd0abc2f77ccad01be6a6e268fddf3759c0000000000000000000000000000000004126b5454050d761047e5da23c0b2f9370996589c04f255a1ce8ef37a3a7c8078788a0125e4aa86aafce8df33f322d3d1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9aab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da0000000000000000000000000000000016ca071d741363e7c3297355e49cfbdcf03d419813ed7b329cb2b2a26fc6a46cc52149ca3e9ca3ccd7284cfed97b985d0000000000000000000000000000000018da360fdee88e806ea1a61c01e86687f8e5359730c36c876ad2acb0297bbc1ae13d790d1edaafdaed65db9dac02a74d0000000000000000000000000000000005a46e4572f667b46aee36b8d377c249de25e797b31b822474aa647ee68cc7d40b083fd0a1d938e2b8d85508004c73f40000000000000000000000000000000011701bf88d4287c98996ea561c1ab2f29a5da9138338c7c7539a5fc8355efab6f58e240df4b0e0cb7f01df74bc8010501d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a000000000000000000000000000000000576e79e507d250eb4040197064b8898b0142b3a2551875935f91f22705bfec6da156c7858fbf77028d4a00957553bea0000000000000000000000000000000015d39a325181d6d1a809b1236f4a1ba66a9bfa6c448470425aa5c8ef9fd00b5481c51e8752088dad62e928b3180408df000000000000000000000000000000000aafabc2f68a4933c7d734660e422ba154e37dd90114272e948f79db4ca51d5ca75d504cf74f2dd0479871d69a08386f000000000000000000000000000000000b017c731f63bbaa8fd0b0d9c17140060429f515d2e85a938d10f6529deeae4818c29b9a628802d0ffbbff720339b7bf2cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d400000000000000000000000000000000028c4dacba5f33ba66368c19491f4baa6aea4f309afafcc8f464f2886b1d05b6397142d02f0295fd50825819621673a1000000000000000000000000000000000849e1b630e8db8ef039f280f8d401957f807ca90479745b68c3db1b5ce3a02fe2c099ddf9c387d7ed76ba75d6a9be9700000000000000000000000000000000013b43fabc3d4df82058db215a69776ed5dfd4c773d7a013dba3b4ef5cf65e25f79d7f76a06ca99132d6fd1fdadb59d400000000000000000000000000000000072cde8eb3d3e1a7f7e4a9eedb8e56f5e103db6de6ccf833f818f02a0706b2043d4ba0d5473bbb6472e8aeb28364e1d8214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e00000000000000000000000000000000007a33b95f42cb1d1ddeff3a199ccfd9a5d47c9fcb89dc09b5b3f59dde2b47d24ff29931920b76ecf6deacd70e83576970000000000000000000000000000000014c0a63e0152f06cfc32e6034b7829f9d9d09aca0a6ef821dc61ae8d99b77d76c1b2fafb2a14938a82ec72c4041ebd9f000000000000000000000000000000001433135cd913b05b3f58b2e9c1a3bbb951d2cf6c92fddb21bd5e1d9c44e464d5fe98f0791044d56e50b81a83ef6cb271000000000000000000000000000000000be12ce3bc47bf69a13762343b5e39c2a2f285896e5d1b73c55203cae2f32cccbb4f7b8230b2026a0c8b2f63db5e5bedc1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e2000000000000000000000000000000001494984d478784b2ab3ba27464109f99172033fcd5780a48fbd5a2144354157f6fca2d70b15b0081dfd306ab4239cecc00000000000000000000000000000000078aebc22025af53c6542abe56cf72ce5eb11d3f19212a0f7442d0a0df907c8aabe0ec01d1245ca237a691e685011bb8000000000000000000000000000000000415a1804a46f4595014ef29b12d99b89600aab1d98352437ab8342abf479bb2215bc687532e75f140918b3d030ad4520000000000000000000000000000000015e7b0dae7e3e80eee3c7a9ed4c739288ac2192f7d80b2c8cf9934cea5719081803b207623c771051d7694e705744dbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f74000000000000000000000000000000001470f82372e197a21aaf46cb2bd3c0b77c3428bf2ba073311e75eb65471a8164753ff1d989560f1ce477952bb6555200000000000000000000000000000000001645b5e5b4bcb5f6d34ac841e3a80f09a86a5edcb7f2a7e7bf549b022c0073e01be82e4c9e5c8e8de76ba367595639af000000000000000000000000000000000b43f6572553154e2530fb448d5bf20c3a182cc190149d3b1d75b60e45baa048f44884500fd02c434f9f7eac01dbe4170000000000000000000000000000000014adef5a52d76a267f87d9a8b5e9f570e7775ca4f6a55a5afbf80baea311b1866fa0689271799a654eddcfe36a6bb64c61ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a000000000000000000000000000000000345a2ffa21eb06fa1d76fd81b1239147688093c6a44a40cae37f2af26add812884bed3e8b4643675b1a45320c64f7a8000000000000000000000000000000000c58eeb5ffdf886d6319ead9e6e190300ceb91d58abfb79c0a322de3987eee73ab82092eea8e1249e83ab67e33b303e1000000000000000000000000000000000763a3fba513b6731fb501aab39a4697f3e4de89125c6884f9782bfb73e6e062f17d34555a04a8e2959ee4e1a2ee284100000000000000000000000000000000024180dde2d23cd88cd29c8142d32435d0db57b8ce8e309701fdb963533c1cdc2595e3bfc01d8c0d08d594e096afb34a681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da00000000000000000000000000000000022d2e7502c4d9587df7ecdbafcbb813b1812d76655cb7f9f57418d5ac83d4f60b84a0ab5b53a5eee3c3954aa9fc70cb00000000000000000000000000000000083212aa1316561a079cb8d027bc8f89161fc828d050c8837a24fca6f7f94b6dbf10d6032fed895a427f07827deaf3cb00000000000000000000000000000000021552b99dc02a051ea3af1b1bbd0a7ef64088c3aef4a58b18a29ca05e1f442f8ea2c8fdb3642ee94c5df501ff6898f40000000000000000000000000000000001015a7987d329cd1eb5f991c270643a05b8e1bc35467130e9f53c5d96fc3c8336a00c060dfa2d3165358b51b6a521e56f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000c19c3b9d7c7f520968d8531966cccbe6f0c3fa0938480ca3591b7489febdabd56a70ae55cc309e04d7acb3de6f41a3d0000000000000000000000000000000002ddc64023f0de2730d3affb695927eaba50ecb91cdf1f369a511a8cc8dae8913ada2d8f27a65e75deb9b8b648e4e2e00000000000000000000000000000000000311ef260debf2310fc31fb8ecc802200e11400909eba24b14d9500ff47c1c36ec540eb970c9262dac947b0c2053d6200000000000000000000000000000000199c19645375dea7602b74301adcfd9af259e1c7c20f377fd10d56b719f7a6e0e57d780c976124e0675c2a54aae3e0f5a8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe600000000000000000000000000000000123fce6b793de0ce2d31f2c7c4218fb20f9db68946a7d57914174ea773d6e6fe1fbb1de141c742e0a8154fa1d81a91f70000000000000000000000000000000019f75536e004a61c6d7f466bfa06ad0c9375a1028eb7746406e7c71e551dba249b5c6284f635fe26989aeea69075b3fa0000000000000000000000000000000013088eab16ec77c7ce7e84236337e395690169a4ed7e44e23d233d36d5d25e6afde794cca2bee88fe749851a71aabe24000000000000000000000000000000000e627130da43a6ede3bd6f2fcdf008c8f5c7b7b1fa56cd3b367d3096317948bda115d732346e73b731d1921a1da6aaa18823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b",
+    "Expected": "000000000000000000000000000000000e3b85a3d6628e097a3d962f3c9aa37e3c5be43faf2a12cd9830ab33c4a904eda23027735bba563d38ae5ae8b302864b000000000000000000000000000000000c92be62cb091056d981ab8872292017cc22ae4eeb0cee03a60cb5d57d37b264fbed2d752ae9dfd76c0bdde1f1dd10500000000000000000000000000000000019e172b23249a17924b890cda6a65287039d0c32b2c0833409816cb21ceb73ac95928234ccf566287400a2ed7d9de771000000000000000000000000000000001276e206235392fdf65e4ea6210d22eb7afd7783caa0777ff0af719cc1822579d5b82fb4c30f07dffe7d68c649b9e2fd",
+    "Name": "matter_g2_multiexp_22",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000a3d974770eda8c742e5d380482d36fabe18901b0217485141c3841003aeac4459ee28b280550e4643d0f50862bf2e2000000000000000000000000000000000369c2bf3beae4e8761f6c06d9bf5261bbedb177e609c81c9bd81ed0a17573b6e10e7f0512e06109cacc3d483918ed9400000000000000000000000000000000030253d0a050986f49c77ee20ea8e3e07de3ba72c39ffda877bcfe569eeb29598588f5a7cedd9e2e34491a059ac4e707000000000000000000000000000000000ce201f07353bf82ec894ec66c7012d17f3c7968b28b45e88f091510e1646380f902c1c5b036084f9497e9a91476dc2c9f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c54070000000000000000000000000000000018f493dadbcd93df2c614af310e5aec4fac9e502843b8ca8c3de739315d9e9a380f826e2470c96bffa8789133f458d0a000000000000000000000000000000001768f8c3da107b9ac30a12b99f2f3a0f21483c0be334377733cee6024d85af91b03c7ea1c548b42e7a7869141816917a00000000000000000000000000000000076cfc99c16c270d2f6e34aff84832f9ee6493ab757b6361cc921823fe9c30f1c9b1664b650548dba767616bec0fd5d80000000000000000000000000000000006c5f580c9556ed31847b1a3527ac0b5b5f15b9c9197d3cff061c1cf72dc5c96cb5fe98535a4dca8c4e20c8c02158466c8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000010e2d7eb4e874a9c72a98e4c36701a9fa11051b683ac8ab9ac20d14929d72ff7b92a9048a11bde92dc2696467fcb48e6000000000000000000000000000000000eb29e621e9d0af8f661eb1ba90b307eb542dd84a486568f85e19055bf7b8f0a76d34acf276897a01349eff2c36e4b43000000000000000000000000000000000b5f890f22658b207dea2d721d90a8f5991ea2c5ca06b8d1b293f60959ed424dbd7052e010e594a5ee0feda1e93bcef4000000000000000000000000000000000082cdd4d8452078e8b853f196dd76505ece5e98df3e6a8bbb21f422755af23c5ab261accea48d8e4193d6c884773cf6e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c00000000000000000000000000000000077501a457d5f0946d25a4c5eede1b7fd80d90d502bca47d8cc40ab2f9a6d827d7323e7d4035f3d32b02401141f0a89d000000000000000000000000000000000985410246c1db01b42728ea46758906883586cba5618b66c21a3cf58cb98e7c6e0dfbabc5505d1d11ca9d047fb6d25f000000000000000000000000000000001775f4008f688882d02355b6eaa1ab108f239890f71238b36c76228cf2f575cd15f15293a62a72f6ad0ff17d7e8ae79f0000000000000000000000000000000004b6967a5ee180d8b92e95c5ef26baa56d8509e1acc710237083d19269c1c5a1f2d1680e85f0bf040747be1d301300b0f7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe22000000000000000000000000000000000fee170589e8a3d3fdd93b536347af5002e59e8ef2ac8327a7e9f382560ee9bc36b3f636a3f99fba8be7b5ea3dfbcfc600000000000000000000000000000000032380cb6c043e3f9ef7169da12df1c6529d776b049c7061df660df841840933e514eb7ea3152ddac38daa2c52d66191000000000000000000000000000000000620ebccfd931eb70ec688110975ea24b7ee0f9937841aa1b7bf4f45af88b732b76a26299f0fe48259fdf08abefb4314000000000000000000000000000000000dee6bb8c198363fa4107996331aac07216b82208242c73736be31e14e4e04d97a56a1c22479dd94997acb0d32abd3b0557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000eb60e98d6cb4e4b3e58271d47261d05be892eebb9a37f6831ff19d0bf2fc235e655f0eb9b01494868bc082c58ed82d40000000000000000000000000000000007254a64a0d94340bcc2b0142faab2d73e8189dbaf52ad0c3a9206e802193168b8eb03cb18b0e4f1cc95b98b943910db0000000000000000000000000000000001e0051fafaf454072051d2aa9512ba2367778aa1617cecf6a7f989d69c7627c9070c349d363f56711f172d43f5730cf000000000000000000000000000000000f4141c8a45448fecce09908ddb42f7b5f6b5bb53b9e1ede0417bee327644af5c98470e8b5364642fc7435f84be1ab443313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb7500000000000000000000000000000000049c28e0bc677ccf54f4cb46e953a057ffad624752332fb9ee5295438fd5bd61abd2199a0bb729bb7678cf3077e32ec10000000000000000000000000000000007138a996356ca3f5d63bb5a36dfe901254459ed515e18ec8d91fa747a691b40a19878d9a6f1dc74e4f18374a399d38f000000000000000000000000000000000a621b36a3cf04e6a5cb699fe4ff7fb8b3361207186848e81972fdaecf667ceb35f413bd68772f7c1f77c1d3f43a3d610000000000000000000000000000000010becda5a06f3f077218d4387158e4a1ca5e0ef24d4ed304723ed5dd96da7cc9325f7e4ae16d9d6c348577697aa6017b8faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000163ee307e0d0c3b61ade05a022ce2bf315d820ee8ece60f93d63a150e02be843a2eb2240a4882c29be2c7403932c348e0000000000000000000000000000000001fc8e9ca23e8dc8457df8f255db3b434f52cddaf05819dba7df1c5bfed438f756c8b57442197af18bf83fe9ee2b765200000000000000000000000000000000109cbe5279ccb592bd0b33b1a482df53459c48cd0913549739b784ba7ad32872377c2e3924c4d45064b0cc4764220513000000000000000000000000000000000d789795d556a37a375d83120a022f57e26da6e6f9aa3e40e1f32ed04b50fafc4d40d0b9b20a26e4d230dd789e20823013994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000960654bd6e6a6b2f7d87c3c4d6e3fe6c684a50b62f7acf82a67075139a614c056a41cd49769960e229cf07468fc2dcb000000000000000000000000000000001727f2dbcc8d889127060de0079207eed1e094259b59a20fa42ab2783bfd176da00e61a65709dcd60402398fadf30710000000000000000000000000000000000c17805a01e64c320601e0ef521b6573e9c2eb354157cf0412e5c2b13f826759310907c4b77164f5899958cd30f78c030000000000000000000000000000000010fb286ce797c0429ad3385c709259b55cc962ae02c814e537e5261e897b7ee1b7c660273ec908110f997b166c14f5c181eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000015d96a0f988f4951206aeda63af85910db49ab817c83e218ec74cbbf5f34f81279d8a3f2fd1f3000f73b8c5550af3fd600000000000000000000000000000000186d2eca1cac226227d8981324859126864b84e8dac563b4d92357591c2416c93989cfd9e1ab6ad257dfeb168d829a09000000000000000000000000000000000a8a7247a3b09583cd2d4949721160573f1f88221e6eae833128914555a594f21a3fb2bfe3b1f01f3dee90f7772dc97d00000000000000000000000000000000132361ac1950756549c957c174cab9ef586eb2057a4eb22f49252cae032975f56eb0cb7ea70810afaf5716afde5b88015bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d000000000000000000000000000000001328e22bb83331adb09dbed0a8c58040a3564fcae0ec85794f26c077de69cc0a7555f011e028879cb3aafac4dbecab33000000000000000000000000000000000a93db348adb3886802bab1e993f5d7275360a5b0466845055d5274e44716f3e1d03a6e1796ed4de4c157dc8a2d92c39000000000000000000000000000000000dc0879a8e9556b7d9b6d5dffce5e648f835f10acad3afca7a73b0fdd5d5babaa74a1ca80aa4f6880d9b015501e218a20000000000000000000000000000000003f7ae8207de4a179ae48cffc8c6e926455e46ef9e109c08be3ae7401bd36e0876642ae9ac4fd75a74c67ffb7790e265a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d2000000000000000000000000000000001078f43093602a2dacf9b5dd7ec41d47bff02e0dd27a996b58c73febca06e3d977c2fbd73f63508243696ab5d8b97b980000000000000000000000000000000001841869086e850ad97b3122fa51c437113d2bca14deaef5715c354d3845f6829f6aebe668844352d5af3509c0d8da7800000000000000000000000000000000047c42e83194143b9e977fa1babf80d455fc86cf6cb491ef8306a1c32bbf8c868e11bb3308dd5f65fc2942b3e49ff5c50000000000000000000000000000000000872ce87ecd22b39b14c9036e971a562d51c5122bb10939cdfd1945dd1445ac9f5de06b70931aa5c86cd0fda51b89952c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b200000000000000000000000000000000157820de2a134081eb47b1800ec72630348583d77d512b4c6a8c8e581810471a2f57a8eb6b0af87a91960424009ff124000000000000000000000000000000000378cf11b0a2848b06412aa754ddbee5660730001db073724caf902d4b4894959f035a8838e28554b0efc2388f2b4f27000000000000000000000000000000001301d15f290dd11c3f8e53407195e02dbf8f13e4fe25fe38e84740753b5a0032f8dd07df3ce46ba424f6772b3aa66f4f000000000000000000000000000000000d166040d457187232f8f38f2beb1e0e0864105595764022c282867346166e46eb789786a7ec7c00b0446207e9ac1ec05e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf042275000000000000000000000000000000000793797c5bce4b1cc3bcd751c5ae1d293477af96a0e7c6bd392ab4410f806a53088cafeed51754ee7e60e61dc200ccb00000000000000000000000000000000019d595730af1f3039e37494b86a638a528d8bd24c429e3f8bc97076c7463e7f2618e23bd3f300bc7e7a4674f14f8295d0000000000000000000000000000000008e245c7590888fd8dd58f93332b81f48b6e3acd3cfcf5f3b28df654eae1172f52ef5a121707aa9cb111b0b402d1bfa6000000000000000000000000000000000a7c6403659e1a0c2dc7cc2e9b57a452bf553e96388676f4bf4a6e26b3ca2d3cb82006850d8340dacd65aaa0d20e6fba606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000054c37e8acadcec8a795619647d4cf1081a0592de02bef916f847936a1736e74cc3b7ee018717495def8b4ef1d098fc9000000000000000000000000000000000291d89d152b414fb5e7139d6d0bdc7b5b9de1fc44b49f895ae08718b631f7652bb4a895fa11149b9a9db30c344108ed00000000000000000000000000000000107b30992ced35e4ba874e436bed5d88aadf0a0c944ca3eb8319539017bdd652feb7483ab6c705aa17e845723b2cb46a000000000000000000000000000000000895dd8e04114fde4a4cf19925004a72f617f2ff146dd650a2cdbeb12977dd2b34ea7d655dee16ad9560b144b81212f5c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b85100000000000000000000000000000000005cea2e036a8ce057e4dbe2d9d786eb759c2a75934580480f78d2e228c3150a0a1d8c95ac5013aae3ab6e35f524d37b0000000000000000000000000000000000e18c18884209f9e4fb17431248a5f8d29c616a58af16e949f4317c2e117b80d531a39800dc70f6b161b98ba040a8af0000000000000000000000000000000007c42ce885d1bae906128589b72f2e6c18e4eeacb78c853e923e6eb785c073b6490b2f6b3dff2276916d96770ad5019800000000000000000000000000000000132d809c37c341eb0304ec933a6b11bf9ac0d2a13ead818ab6ee03ccc94160b405066381dcdb13b6ee3f5dca48ee10ef47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455",
+    "Expected": "0000000000000000000000000000000009406918e2dd6f06f4782ed110e29516a911f47133ad8adc58f5780de916a8973ad60e05ba931d66de7545a92f388c20000000000000000000000000000000000041cbd52cad2a5f4c8353c7153b5711ec23fa8bfa2f34f5e1a16d8a14cfd47c237766880debb992a05ba9ed0353beea0000000000000000000000000000000017d4211c827379b310956371129011a92d62d11f0ee5b0cbad9eea2d3f2a95d364717713fd0c544747338725adf27248000000000000000000000000000000000a61903fb81064614c9c6894c7f3954aace7611cedf6bab8e751f0c203bcab827d296016947c071d7b6ccc742e28ee9f",
+    "Name": "matter_g2_multiexp_23",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007f90813f8c3eabcef04dc1bc9bbafe1dafe220e2db24e4b714aab2b164d7ec9df3e6a3f903e8b7b96df2ad8297381d2000000000000000000000000000000000e34371e51c4c952a0f38c4aaa5fc2324971ade310af2f36ed511fc5fd7a602a551ef77775fcd0f1fccc718710239561000000000000000000000000000000000787edf7a6ed6b50afcd7c0d3876d8919273428bc49833e3503f650e48e788b15cd82eab2672f612025d796bb62d72bb0000000000000000000000000000000006b49e631ace4f72c959919df5d64c537537ccaa3d1890ea9a1d70f9eacbaaa2ec361edf2d4880c9810976c6073028bc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a000000000000000000000000000000000cb63d7eef2d6614d1f629756b3a619a221033207d1621e4ce4791db4248500649b91ff07cd2f1f06eae3a9be5b6af080000000000000000000000000000000019aafbe56da1569959019033e8cc785c9b98bba6b069603969e7ff1150f023706b461913ea7949306a44c3b7d199e86e0000000000000000000000000000000005cdc3a7004f7a7f79ffbf4c4ba7c5dc30ecc62f270a5c231406fa63d82fc64f45e94779cac851ff8443040fd3b2ea6200000000000000000000000000000000040f30dc98e8668194c9278b189e0c0f7b76a4c686ce26a4a96b93190938f07c5b813670e206eb6b5da29624a1b6314ba59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000a5a1bc231f803ae272e497f812ebb663c2ce8b43a366717fc6349264823ca93e29e30762c1a366d8680f81838907f59000000000000000000000000000000000a88fd59ee380449d632d7e1b926210d984d5298fa807570a63a63828cfa55c6e2f01b7745848281795dae36e562181b00000000000000000000000000000000025ad34537909e07beaaff09f22e91e76d93c668d1b45cf6845ab8ba0129e417b758e85a7100a31a9037e307f454bd370000000000000000000000000000000013590106126231b1c616a5dd7aa7ed6946aacdacec963b507907950d6ea11cf1f5b59f819a43eeebaf51a1faa7daa8e719ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c5000000000000000000000000000000000938d43b9747c926c3e2dfaca2d6f1e6d61d5a621ae08c66a5baf33d9241771509689f9ea7d75af607d76b66faa8fbc2000000000000000000000000000000001889a48a74966b9748f4a6128dc3d75a69499db1ba1bc9aa3a9428f0efa898b5f78a9e2dae942d3794ab3d1157a1d305000000000000000000000000000000001129c9bf343f476541980b85229c5c25289ca62173e29b75de431b572c8f01f64ec1aa4625dff9e7df535194c7f4e6e7000000000000000000000000000000000fe95c71f703dcc71cf409b332f66fd69c330758d41832236a510ec4bd9a28c4732434d4c3f97445e6301e3070153dbbb8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc10000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8dedf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d000000000000000000000000000000000b419b675ccee2509daf66e5da4031b08792e1181140b30489ae21f7925305d8cdd8a104580ae5938586d6b8e74f750f0000000000000000000000000000000012e070ab7118991a20b27f1a87fba1f5815665d76269f0d3d460a6b701e57ffdb4fed2c53fa63a3121c74f67e770f31100000000000000000000000000000000124218ca85f235eac3471e0acdabf73f79afdd4bbc159c1e34c641b97f03735e4c3430264f2d94f640486488dd1067380000000000000000000000000000000011c24f4fa1862779f22a628edf9d3cebe0f7593964b642f889201ae85e8fa01e00e48355053f5a7c6d920dcf6a7ee1d60dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd000000000000000000000000000000000266bf0d9d5a4fc713dc0fcc6ea6edae0b326e22cd97bc49c48a7ba398fc87d7a0c7141ba24d80df454de66c2b5a55fb000000000000000000000000000000000aa8f95c7cd61733b0a260149d6608a73d6c1f989afa8cb2aa4098e1fb5a66b4ad5a5c1c4d901aa79812385fd507f02e000000000000000000000000000000000a6b4929df13e1fe7f0a0cf699a7fbfaa97d7527cc3ea1f728ba59def2e75fcf3490199bd42e93b7d47985a307add07c000000000000000000000000000000001719321981d2085ba31c9fb131d6b79c7df5d10d6ad0b5015454329697860121e781093fdde1f19e897dd6f2c272f87a109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d37000000000000000000000000000000000431002c9926aa7d2b06412f544a868a7d48fb5f077dfd098febeeafc28b876c434daec809e5cbf50ff2395ae7e456560000000000000000000000000000000005a15f713b6eafb09495cfb1c89e9421515a07a99ca0f208883f11c430ffe6f2592dbc41bcee5db36385a26f67cd26bb0000000000000000000000000000000008dd30fdd7767486844967c5da0803b52282178287b8ef28e14f07b487132fea3a82d86d414b4d0a25b3dc538be11b500000000000000000000000000000000002dcee67e2d17b3106dcb9f4117456a037ae1996e8f7a09b179baab1ee8345c6d01eae554d3f40da86bd79a04702fbf76326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a000000000000000000000000000000001629fcc374e99fa8303a715fb5077f266b13367bbc0098b5463d3298c0892f83127d6b7f751446575b88858bc742586c000000000000000000000000000000001100783c10618752d25c235e1e76dc64db94adce05651fb8df0a5ee7c299d35b1319f7009b857892ddf9e90c91f7d23b0000000000000000000000000000000000ab6996e4935131becd5df288dacfad1e69b41e200ca7dc841ecc180a81b9d2ca14fc8a76a4e7bd6f924bb9f473de62000000000000000000000000000000000ae9b22f8dff29e5e0a2ec5b5641f53fb5e1ca03130b49d0c26696ca4b439a9d998d9a364ac9cc5ec52df699318cffeae005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d0000000000000000000000000000000019c557ae1c12ff8a7c00b7c9e4bc3d65c92753549c193311a38a84bccfc090052a2219461a9691affe2d67ea4357cdeb000000000000000000000000000000000cd35c5dd126bd4b90dd671f29953c5a49a14b6b3fe946991416edf235c3eb3d574613d27b05cd879518fa7dda3ed39a000000000000000000000000000000000224392063b0825fd332bbede23588c1912e7670a013a99da5507f650dc4284431698a5b4e8c180269af8bb30e4fc8450000000000000000000000000000000002ab8d3250d4bb8ceecc8ca2003f91420d0ef8a7dbc2361e5e7fbfcb59471a4c525856bf796a2c2608d219d215cf83fe3917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000000911417908c2bfe4f63a388f699b31b47df1ea0ec289ee3f96ffd0c71f3deade00d1841aa56b4bebc2adcd3068adf920000000000000000000000000000000005467c7e58e82089fa285c28ea22c759c7806d86fbdcdcc8e09e847d6330922a61bc331ae3b5acce777b7809ca98213f0000000000000000000000000000000010f376fb47933b1f701dd81cebaebb2d8d8f5510a26fb3e9e156ac5ecf2b943c5fa2812d52da542e6c335abad8ecce3c000000000000000000000000000000000dcbf467432acfa4eb9ba11a7cdf02f9110f44ac371128ff8f1f98fc70e4554f057a4608180bfa54d99fd2da010594f6f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000179aaa7119f6fb986714c03b6db16f25eca7172d24cbdd318bebb633bf08920f9e2a8136c94e3ec7c19e57ab51531b3f0000000000000000000000000000000005937c484213ab5b2ca8ed1c5c90e8d2a2f1bac044b88c04b301ff2fdbe67dc4ea42779d919ad510cabfa2ccd178cd9f00000000000000000000000000000000183cc23fd64514ead63f55d375a07af7cf2a56aca64a887dcc542f8a396468a6abc776170a5d4b4bbcd4dbac285e7ffe000000000000000000000000000000000ce12228dec2f84219904d9ac7923f122a99803a9b34749ca68ba385c178811685c19a492aca2e1123ee82a8a9cb90fc3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb4420000000000000000000000000000000009612bf9130e17110f8b15aa6f3317071daf3433bf6d008c383bd5c2fdc7ca03f25ff4cdb483de3c84c0ef9e579f38c6000000000000000000000000000000000c40172540a7e20eeedfe02c37aabac07165cbf04830f20fa76fe8b05c826e7762c9f7567a0fb972212bf736e627948a000000000000000000000000000000000f49e5b1929ad3ed5c07670c471710baa24e8478a50f72a5b7bbc23a66cff91d30a3d68961fbc2e6e8003d08196f325c0000000000000000000000000000000004ba098f915ba9e934384682648ed8d4e1cbaae60d596655fcd9c05f4b049ba0d278730dba5ce3fd4892531a3153bb955ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e99188000000000000000000000000000000000449b15ecec6d6fe5cd32437b54218f62527157aa6344c635fcec8f8305c8b6e44c93105984e0832536237606f07792e0000000000000000000000000000000011e40e8aaf75f5ff8e4040f725ac27693d7b24805a2539ff54b3a6e90c048875ea9609fb8fb3d8de63ca1118876c172400000000000000000000000000000000006ef2a24445f728b53cbf01e5b076acfa7761a84d8261cf1a1b99cc32f330f32fa5ded83d5cd51cc284207adb2451ee000000000000000000000000000000000977966380e772670447b15ad9917035273eb71a21c37607a761aaec808909fcfed50679769aee1573d73cd241de6624877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000074e475c0ff1a51a24be3c964c45c41f767f890dec82712d92a965be504fee43fcc6c0684b2b17c5b294a3eb7ceff1cb000000000000000000000000000000000597b7dd287f3fb27e35a9e4e1718b6b1a4addf9e95e93aeaa25aa34023669368b794a08fdb178d9bcda2738534d1962000000000000000000000000000000000a492d648393bfa317165ccb552e045fefce5b3444d5ff770f43a08a68efefe7fce1216114ed1495cd00f832538198180000000000000000000000000000000003d85cea8063828ff025ba599bdf1efe0412ed5ce06ad5faa841c6400e4eeb6aea1470d48f4e66fc768d7e7bfebedb37145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f6070000000000000000000000000000000008ecc3dd40da2a7a348b4817d9c84242f2f07c5d0ef810dc08311e9d4090d6d96d68b6c725ee6c24de076c71754bc4b50000000000000000000000000000000018fb3a1dc4e0dd9227fba310236a6db7953f0b716fa995b928a2a8de38edb97eca09fe2ab385037dfdcda2ee577e677900000000000000000000000000000000062fce7fe7810273a80760d9f4b3be9e7c821f38ed3e075210d3aac6aa7a763e3cda56465f88b34540b408ac850742080000000000000000000000000000000006fa94466cc47990a80ae6a310ea765590a0e646b5988925f03cc7e30f04fc0a8044b403212290b2fc46c77e84a9028dde4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88",
+    "Expected": "000000000000000000000000000000000f41bad0a932e28096e51482c646dbdf294aa7b91e0ec258670e7674864765c76989a936fb440bfbf4268a49f450d3230000000000000000000000000000000018282b76521db98f589b1c14e603b6f5d27af357553bca761189a38a944a11c66480f7ddd89d17e4aeddc8d78a2b3a0d00000000000000000000000000000000007efc4a90dd97f1312047ac78a3163dc014c42a44c7054daeefd5b72cd0488832cb6396e02ccff09e4171d790954fcd000000000000000000000000000000000e790fe8323fffc96705a42ca071532d5359641ff7cf8714789c9c578717a054c811cdb581df8b6a43729c6c3e3255ab",
+    "Name": "matter_g2_multiexp_24",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001304e0ce6a4baa6e0545fdb314523fc91f73eee157249b94f284ba7390b12b23b1c849c45a563ac82b62a2c48aec24e1000000000000000000000000000000000a2d0e9e222db70d49d1e85f587d35bdf5e8328aad14343d296f95b152a79c83a4858cafc350a5df1ad0194c49bb929400000000000000000000000000000000199efb09b34d0699eb4bc1c57fef9cc5d98453bf522c504fe7897e22bd0596a3a6c310eb351e15e3f6609b074b240f7d0000000000000000000000000000000016b69f12ce30ad1a65150094e29d4cd82fbce5dc343517ba9e5d89245ec083c44af9a3dad2169f713d3b01fdf70d20642576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e0044839000000000000000000000000000000000b3ce4ac12861052c602e71906a7c9f3e2186bd2b6eaaf222d8e80b48baee537065ce78372ed936e6728b9642ba1fdb9000000000000000000000000000000000e8186561d23515bc58c77769c93db76dc9c62bb715b283cbfb71462451120a6ded736cd8a292a6799fbad7617d9aa84000000000000000000000000000000000368a6dbc7daaab0a786257c813b1a25c97468732c27cc759fb921cbc3c9a37a46d7dd0298771c447d36ef0a10579ff5000000000000000000000000000000001348d5e34cbe54e3a6b357c4e651acb82d2dc40ef9ed8bb89f0cdf0882ec6a737998f4e4dd61e296d101cbaabccdc3e779f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000004ebb53c462239a78bf13f29856ddc4d78645c457a656f3cccec9d3c032ec19c26488f39e0f5bf0d38424f9e3a9bcc870000000000000000000000000000000002fe1949365831f7c38b1cd6cf2e22345c4ce40cd73def77889c214d1077d70e39578e8be4fe5998f59d47cca7917280000000000000000000000000000000001152f2df1512013a42ac056b75802bc35c1883efb345cefda8276c594b061a0b0f4a49d8bafa6fe251658ee76b2493cc00000000000000000000000000000000094f90cb386f7933b2ffcdba5e46e09cbd7d537c12bc223e76d3a88ce9063a7b3574d3306365d65dd4c6505f1dceea53300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f6571500000000000000000000000000000000169d9469c53e55768c9312680ee82ee581727e28cdb1d6fcdca25d0c03f3da2ad6572039f12c90b09cdb843bc506e07200000000000000000000000000000000174528257f6d3542f754ecbe97eeeea7d196ee4dd01852f6cbad87fbeb4dd7d3799588f17aad129a15549bb787468772000000000000000000000000000000000c9ab635bdaca1c488538c0830453ec6ab3b2b62447c03ff6ffd2712bf62e02a63c76c79d41644ea412e733128685c45000000000000000000000000000000000172ef0fda359bab149c8c04f583f4ace4d1b148426e993996d278f79ed2c6d3933d6cc5fb62ec4869aadc773d3084ca0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000017060fa73b58957d12b3996d67b7baa8b7f0943ad52e80e5c4f8830d33dc74c0a39e08594b647945b402299ca861f7b10000000000000000000000000000000001efdc7f783f9977392e2797a3e0bed222d5b661d056aa0c7e04a493bb9b18048bf72aded134941ece78d63df0a0868d0000000000000000000000000000000011355198320af05f2121939e6489f31e9e13b3cbb2cb30c9e675854cb8ec038f80aa2f4b6f995774b36f5f1b6a84298f00000000000000000000000000000000172e18c490d0cd5ba2449362c0ab296212dbe69ac25515d0f91941d300051320f067f946dcaf999554f55f1f616adc0f3685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae82060000000000000000000000000000000005854f4dba62d1dbbf3ae16f70792f1bb39f111309b454a6400d2916e619d4f70764ecfda7eae5c28cf1d178ad53fe6d000000000000000000000000000000000ed0bad1f5d69a0e621d137746a9ecc764931ab89f24ca827e0340ddc03571ed697f63e79cc58b946e8462099ce4b1d70000000000000000000000000000000011de76edd1cc2f9ba06b98593a24a7a011f2701b451ea3ccd04361ddb678e06d91a676e3f11b62c68cfc05242cb8a859000000000000000000000000000000000599726b5f5b93d414f9310383ed9414e4675d644f83ebaa63dceb2bddc7dcfcbc17c7aaaccd0ee32b0875952554b4e660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc25400000000000000000000000000000000031110347cbea2756b5fdd549d6c0b8f4036f5718d41dcd6c854a91c9df29bd464774be479d0efcb8a3f82cc7441a6c8000000000000000000000000000000000e24a52dccfdda3689c87395e45dbd46156676d9eb2cc09dab22ef7ff0acf5ea243ff117c82b147994d65aee8605b2fb000000000000000000000000000000000e0cd6ea0bffc591c13c48bca0782fecf8e128b0b842aecb06f803a223d32cc350db869b7a77f8e31b05f36bddd587ea00000000000000000000000000000000042ff4ab4596d610638ad23eea904a82701cdf61f9e2dc5832a70e11e717711a2d0e72f32f74706d385a9567426b4713addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001517efd853800946aa37868b525e58fb488bb69755ccec806afca2d21bd3a30ba46c39cdf694ad0ca92841760437c3c1000000000000000000000000000000000e5591c339e88544660380d6362f4119c5596f910d4ceb96ccd4c4d9672efc50805b6fedffa0a48d126aae69b241d3640000000000000000000000000000000010ea5babb0de734641f63eed2eba6124377b5c55e429987917c0bd109d7904766a10b0d2dd123413816d0fbabe25050b0000000000000000000000000000000000efc89ee2ffa56193129062ca55a3350bf50e8fc7d586fae3636a70e3577987fb0f8674d383def4b41225e490d3d81528416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae8000000000000000000000000000000000dae4277d62e3f3dfffb80818a5ba5c371a48d73b92d69a168ebab897ae8be206fdf776e9f955136d7f7f7b2903040270000000000000000000000000000000010ca635ee2e49cd6c951d75ffddd11557432726d26564239c611b139329a28812afe21f094c0585675f4f233233743050000000000000000000000000000000012378b2ec31119e508fd9ae0ccc4c2603b6820283284a278fe16864e5a18cf7992d850c1d6ebd1253103c219bd95ca4c0000000000000000000000000000000018cac4f0660240045214034cfd8a7e40bf0aa12f97a23c4e27db0e05bb25f4d755276a91a4e882a0be63437a522943ab78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000002fd5571c818322d207d58fe0a898a045a26c95c2490765dc9ac663a0de78ef5fbd05b20ea96dc5388d5b2ccf13a5e320000000000000000000000000000000006ff29ccb768da45061ba4e01c90459ededa5e79513917401e7e37151095ccd4656aeb9cb7c083cf27b69377295934cc000000000000000000000000000000000414d34eac47430495be735eb5c4b1a68372abeb43658f27613a9c8b78f17d9074174a8deeeebb1f9cda5d6198bdf89d0000000000000000000000000000000010b11bf63b8c39c1370e8fdbfdcd149fea88eaf1c0a94a51bdd061e4c41abc626a448030bf9ba880032e9f1642caabae871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce00000000000000000000000000000000023eaa08a44eebae674434b013ae9992c75690a3d0de53e4b05d1c0dff249feb24a12432bcb5defe25ee4e44a56b27eb000000000000000000000000000000000f146ac27e685cca04afe8fc58fe853825f5b0009e8831eb0d0121decec23b25bf8521da2fab1508a3ad8254865fbee70000000000000000000000000000000004af1a525d3c33e0b1629cbdb90c56a88d70a28037c87db81c59bcbc811c8f0b98aa9dd574436c9f600c0e8e2d194c0400000000000000000000000000000000170efb5e0e69e46a21ec3b972265bc04b9d5ee926254f61c0e18fed013922e00f1897cf69889576bb5d54810486e7f2776ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000111c832a96329d6db203fc8b6bb5b7db01521529c91c74d9cd71dc78d067b36cb7eabf1af80129a7a3f44b719235927400000000000000000000000000000000097339c17816795238629d4ca6c243a14e9e227e9bfc30370dbb9e1475f6d03020dc35559675121792436bacdf9eac4a000000000000000000000000000000000805870a1efd1fc34c9b576b77418ee8c0d36aa9caf9994a051e1d55b49275f34cdb55edc74ffc267c5776c8d0e113ed0000000000000000000000000000000001513afdfc2f000e3b725fcd0428fe72ab2413ff2aa91b44458a5249c9a160ee27bca01d2fc2e230f4a80454769961af95ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c4000000000000000000000000000000000485b3b1f812b4a28ac87d16f86d8d634e85d49d6dc460646e1224de662e906002c44a1a269c3bc011fd22afeb2d58df0000000000000000000000000000000013ba0752444a794cd00c99eceae51e61c382d0abb26e5e0e595d59321447400e8a8f7d97390bd217fb50bc22cef34b2300000000000000000000000000000000184515a36024d0bf71d9fa4cc5165363ff94ee9f8579bca653ebc0620a9d3146fba70a2f4a9f6bd3777101de0d32e327000000000000000000000000000000000e041422088c0343f7704e726d65ccc4216c4a1bde3668108983643663cf0249e992f9acde2dd8ff478dd26cd8d9434d06d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db0000000000000000000000000000000005bbb0c55fdbc59992c83fc0ff03f677e58b6de6f8649141d88963ebfead9383d692015a7b765b727eacb6de250351ad00000000000000000000000000000000183057eca610b8e07fffb60d21bf2eb87981e6e881bba04ceff420ca38228fce2f94d40a993e2aef09e209f3990dd14a000000000000000000000000000000001231bc55242bea6b589cedd1d82621fb71c606ca9306b268379dbf83ddb1420dea228ffc05cd8b67c38206f3f006ef18000000000000000000000000000000000f2c943e7a8b0ee00fc4e4ba912b94f68f504d2783babb90a3781b666b31bd161af2f97a77813eab9ebba76040b04155257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f09800000000000000000000000000000000054bd97b9cc979006f734ec433e215a4e8afe468e69173384bc895e10ead3749d991ff8ff203abff30bf5cc0d2fc8c6c00000000000000000000000000000000066b73a98d5f5ae140a5784c5594892c849aa7f2db3b5798643f755743d401ca745d810fad5f4a33e5b3cf0fd7d96f7b00000000000000000000000000000000007caea93ff5cc6ffc033717220a215ac4ed7283945ae77e62320a0bde13f2153dc8dd401297cd124b4c67a4f3839dfc00000000000000000000000000000000094568035ffff439e3d3201466f3a1d43414e3f6455627c5479c8b7c55130ccaa5007ace7ef6a2b3e2e5a4c9543dad9163d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc30000000000000000000000000000000015eeef8bcbfac04112931e186f6fd48b7a8ea891ab364ce8266c5fd15f072f08fb3655e324795df182a5ed1c917a5db000000000000000000000000000000000028916fcb3b30a7f95321a0998e544f9f4f578be7a9f866cf72d6b8baccd93f8935f105ed26aceebb3f9c96073a8be180000000000000000000000000000000012b11f356a7e32f3d9281a8999363aca0ae5c1a058724cefb51583e5f217257d47ca76d21e54ab62260796b95f9d3ad0000000000000000000000000000000000d83c75c36cc8dea4aab47823edd26b4492da39b93a15fa454aed4175f28a025ad2c576ef2d76a66e666bedae95cef1a7a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c",
+    "Expected": "00000000000000000000000000000000059443f363ef0c65973d36469ac651eec6e52485a07a6d28112f4d0711802d182b7e6fc56d4f1aae51fe1c549247d885000000000000000000000000000000000d22118a6f1cd06ee14c63f0e005076bfb061bb85ed184b5444c08ed9dc35f77217b6daafeac89a973f2c73f00e0d3c800000000000000000000000000000000180430caa9917cbb40e3ada2de8d685b4daa99639669a643b8f5cf9a4a55d6162e9fd7f5d4989a1a6588feb0273669b90000000000000000000000000000000015d01fba1192f0f1acf8fb32fe790f0448c6563cf8ef5505d9378fa2fdd38bd99ba938077f71bb8eaa91a99e787e840b",
+    "Name": "matter_g2_multiexp_25",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012d948b5268524659e29cd407dbbe8f529e608193ab9452f936b2f6fc0b81d3a63a0e929329e2d89b5475dc2d73ebd8a000000000000000000000000000000001219e20a081837f4d4e33bdffda08a946bb9cd876e42e2f561ebfd18ec439e0104b43de61f47b8b7a0c346c33e632be60000000000000000000000000000000000a135c72c45f254cc1c260af803e14cd0f89c2ac3029629a86b05acd3440465aafa4cf84e69551ae772bb55802a90ef00000000000000000000000000000000052750c3a99974f9044531dee9129110b99572cf283b61e6606f1137a87de7344bf01d2ac2f8a1db8d815b6d9e7511fa26a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c00000000000000000000000000000000136b2f21aba94bbc8e5235951b1b186fd4ad221e6ecbea5c7279cc8ee8b01edecedddf48cca47624ee9b155a4c167f140000000000000000000000000000000019852d2bc9c8abc92503f3e7eec9fb20df108c23643ba8a2fe16c2cf085bb4ac079d3f065a1241067daaf401b662288b00000000000000000000000000000000018bf1a4e74ac9507b97a990f3a41cbae3f32e263e9937a8a62679bee93296ee5cd25110833eb5d136425bae0e9dcb8100000000000000000000000000000000096ae4bfaaf4f18d3e987d9f287fdd3dc9b497cc84867e757da52bd5f58688403e1c9cb432a2eb87e239879d52990ab5f1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b60000000000000000000000000000000016ebc2ee18515354b7af5d924c895ffd5556ad088560f89c59a4ceec229279d4075f732b884a6ef2bb2eddc11d27572500000000000000000000000000000000110282084ab6f3e76eeb9e5e8c56749992913c2404b003df9c2d01d72751f879538d23f612c8faabbccff45185f4c6a40000000000000000000000000000000017476677ebf052d13f60ac0ec5e572c398f1a478d60ce92a3de88a74a28688d786d30b1ea8008409e45697db0adc628c000000000000000000000000000000000a5e4239d938bfc7c05f3b3a850ebd5f7784eee7aca48c861eb4bdb1ce6321fc9c6bba997e143aba13a42f69ea14937397fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d670000000000000000000000000000000010c703e31f2d488812a387596c797d8d414e406bd82f238cea50a459d842502e11220ad82fce5dd36635792ff5770bc50000000000000000000000000000000010c11caa640708850e1dddd48bae22961a45029971d823b53030979b7d8ef2eaf2ed055436105697c5b0b31b1a9d0a7a0000000000000000000000000000000006b98568b2b7f0aada97310f7e14084a14bffe580ec65bc8fe5d19c6213c45dc1b8e1da5c6c1b8555729f6c781575278000000000000000000000000000000000f2c506f3e41c28a748656d1dfd87e812b3ba21815637e497a30eca4fc5de18257846f12b67919dd2d739477cf5ed0ae8bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af400000000000000000000000000000000102b6d561172adc9316b3ec11f05e66e7affb1bdc70a364faffa57aa5938c2ac08863be8fe79ce3f627558fcb2ab1230000000000000000000000000000000000c5e72c271a1ee186d443a96d53f0ba0ce226c76aff2a7c3215c2110f96cb3301bd586f509edc45cd20e662756897b78000000000000000000000000000000000d546fbf485bb283a04fa05aa962ae8d77ec4d26f749d83b208f77247778e32a9a2f1483bd84488806e27b13eabf41d30000000000000000000000000000000005a42c6ce8d43d122bbf984e9777f5d1c15057f27e70fef44b97c2c6e7e2e303fcbad643027b7ff3167916f21a723ea98da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000001788323aafb95f8761f87f771fa05a8e49be71e397849daef5877a7f486af13fa651be7a93bdd9465df7be4ff65825fd0000000000000000000000000000000014b7a56f3f7c12e39be76b3872c1ee648f62f9cb6a1842d869e00a5dc2ac8cb4ecd96ec2483d5eade5b0f9113133bb050000000000000000000000000000000009a30623632b757ae8d03ced0c1fdd1877718f8d84f34ebb42426284f73bb7e8abc31a5e5ded57a02d08adaa90abfb2600000000000000000000000000000000020b47acafefce7f617081e22b2bfc566acec6d2cad5063a79cf33e02cc8931bb698b72184a11fab73e0bb0aaec76c61d6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000d1190466f0e8f03d2cac4a5e63a13d7c6d0cac9f2065295e2de818773199d731f8cb7b2be5f6ef0a246401b345a2d560000000000000000000000000000000007d9c5d187494df79c25b6292527b0d6d8c50b6467bf76a1a1316556e48159a3b5dbdbd9fb0bb901d857f61f423d15db0000000000000000000000000000000013e4401fe76e3f1ef73bd244189cdc81fcc152f71449c11aab24c4fa1d123c5aa8c68a2d10fe88c1c6631778dc0bcd420000000000000000000000000000000004ccffb4296883b8690b2f3fe17e4e9ab24390084ac917ed28fa1e04b9758373abd348290d24c915dfcaf0649ddf5a87e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b10000000000000000000000000000000000f4baa5e531ae462b95362292d5366daa89f2fb2707c58568c094c58578e84a8d253fe1de26b917b84635c0aac3a63300000000000000000000000000000000109057e5c5451eb9f85b95aa5ed2615d2faccd0539b1e4481923e04cbdbd2ea9290969022cfa508d3fd050549c74940d0000000000000000000000000000000001c3e147ad9c31927207f2344fedd541316f4010e3de194f924c4a1450a221285b76ff1894f8b1670731007f44965100000000000000000000000000000000000909cdf5c56dc177daa1f3fd7cc31d79a4f6dfcd462c07812cdf629426b75bdaa297b9d7e67aefdbb58175a21e29edada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000009b4c6bd1c460d2e93febfe523c1d54d6bf6af50838e7a10b732c1be8748a0752a517e7103d0ffa4507b086626fbfa8a0000000000000000000000000000000015bf2c13891dfa8dba35b5da1235563d4ee1dac33e89006f5c9fcf06f2fef7b31ca845bcaa8ac608046e8b01c8a61fd2000000000000000000000000000000001898dfd6a0618df821474b90542f261c1febbf2e566978b0fafca44f6dadc57202f88366b19d2c955e4291ac21beab520000000000000000000000000000000019287e1ac6b3eaf412e58511b40d87558e7cbf90dc8af2f5d33825b40fd2f2425d0be3a05d0a49076f4114350dcc601eb929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf0000000000000000000000000000000000c0993401c024d32cecc0d86d4cc52c200e59acb34fee2ae052837f467905e736a1118260ee12a963ca2df6e1a6c9d0a000000000000000000000000000000000103f78f0e7c9a5628a66efa91f150a87e67623ded2560aef278a8caab017fdcf181981952b450c67e3b4d3f362822a80000000000000000000000000000000000df01ff335f23652f1c34480d23c62d705572321c0e7fe92556e033dd3cf5b78a3d554585403a7f3c71744c20d17579000000000000000000000000000000000a0e2c9e2e34e5cb36e96b29231f702abb127a011c7ea3e21d59e5c55f745a02039a68d59ce8e29afac0752d1939106936999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f000000000000000000000000000000000eabff0e6ed9dc358881796441c48e722ea171f26011ab898c5a06758f61a629ae21d5a2595a22dc9855fd2e516b30fe0000000000000000000000000000000002732155a7a2791078dedfedfd3381281554c389bf9b5baa47593153a2acfd22a08557d7a1d49be298e416051b9137dd00000000000000000000000000000000116faa2e2a261e6a3e4de6ad80d75ee05aebae47872e2eed9cd91aafb94a706de673a05f1b86c0b0131cf148a90b2b7900000000000000000000000000000000009a04c09c2a4fce22d237bbe930392dfbbe5c82d480abefbb3be876015e2f5889a0922df6d00d4e94be0e9fb8d2f4a1fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000002a6402848507062e5c5d63b1207a1a41d3b941d21792391f2feff95035f1b4625541770fa5e0f87585cfca670976533000000000000000000000000000000000904095ce640605c957715e378ed733ddf1f94d3beb63543a50c8922ab9f8092755fcc65e2a1ed9232c8cddcb5816371000000000000000000000000000000000ec62b911b08d3e8618880c3784685b2c6cbb07a4aa4e348ab72e4f918152622ddd7748bfcd79f35675cb956d11fcd650000000000000000000000000000000013f651e9104d48a081cef2ae0648816b2b4b5f644a791514e94a8e3dd3001099c27d1f9860337ced1b177b4ad7cd5866c36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000016334abef2a21b9c1926b2086075471bc2d2d2f66b963a41623af91fd2fd50f254c008fa3bad6b53658c2486edcc94aa000000000000000000000000000000001063002a5d17aab2bbb5da49e8bde63a1f3c4dcbc8800f9487f47c6d707109c86d3cf7f9171643418b195e50d7483af4000000000000000000000000000000001213004f31fdd0b0df5d8e3677c4f48624691e2534c02881c6cc6875b9abaee56ed5739c2acd66cb1b10553ba066ef1a000000000000000000000000000000000fb7659081cfcf8beaed9c1daf9e92702977c37a54376597d897082a25f9882f1ae14e7724c0aeb9e002dee708c6b4eb0f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e400000000000000000000000000000000078f06bdfcbc7c0cc491fdc8069314c8a395983f9a2e5c2d1bec360f36e365da377885f897d8d711e33270e3ef9dc4d80000000000000000000000000000000007d43394d5175e020b3a5d768b60ec763d60cb1bb37c0343930fa82e92fb1becde0a178c4565df320824bdadd54ecabb0000000000000000000000000000000012f9fc96355721c35a6f5439065d89cfca5345622b3f38041b41c036b9bc6bcc980498ddc7bcf807e1b97831c099505300000000000000000000000000000000105307b482467b881a59eda1434e31dffdea531603fd3c460aa8d4f58d32668228bfa585bbba2dae7346141af59190e2c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba000000000000000000000000000000001576521fb3be8c3178549969e54bb17b0a3546ac4aacb470e935359e36bea4f43dacc06c151a527f441ab9616e07f7b90000000000000000000000000000000018dff940a21768ee9b9450fee7259663bb29af645bda2acb4d43f4e9d631e0127073f2db04293266e6fd6fd3d005e3f0000000000000000000000000000000000ca6a977016c1ebf52827a5ad52e5efcf7517ccc3ff40df8141f6335fb6c77c3fb8f6b0dcdba2596ded7c3838577e28000000000000000000000000000000000150cc33b55586fac30d316cad6580cee0a070900fe7d540167560b79f4cf9690a5e02cfce9946cf67a95dedc9a7d9aa35398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc",
+    "Expected": "000000000000000000000000000000000adf84ea7681c442731be8db9508a050d4689c42af8f7472491655356a86fd9a0afd91435bdbaee98db3b1d8f26203fe00000000000000000000000000000000090a7dadc0a14df481e44e2005c9ddc6e026ce2afaba7badd18492bd3a338dffc349b4a339f56221eb795314252d53640000000000000000000000000000000007390fbc06077cd167f53a16f347eaf50ce8c9d111afeabf3a672687441b80e66a66ba3fdb28e2ca8282f3ae3dc81be80000000000000000000000000000000001998f98e85285a428a2860c22a00d0b317854da4190dcb4dcd182ac249e13c51f5d5df5db6a0fd61d01232cbcacd5a1",
+    "Name": "matter_g2_multiexp_26",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c868a2cce65692f83eedbfeef6f9823ae9382fa5ed23395ff2444807e72403d4f6ac861ecd3a51db841889fe22a033700000000000000000000000000000000111c9aa53da85a63ce1870b963415f0d5f812e061aa6bff57425038d1b65fff57a78bdb963bf2450001525a93011a28e0000000000000000000000000000000011770810c16367d075c695981dfa69b072b82b034f8ac371f26bb157f9f9d667aa555a5c6baca69d08f421cd569faec2000000000000000000000000000000000df6146b29bc8226dccfc95a325d791b30cba8ff2495434d75622b170a634ec7995c5b4c689c73582ca861dd21d8e1e49f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000000fb30aac6502ecdd3544f1879bf1b3f4c19fb897de6c3a7cbf08f36244aa8e9dea8aaf781f7509d3ece16ca144a601e40000000000000000000000000000000012304be931a1d7440d67740f50b1a281468b412e8b6c54c62b993ec609012c7056fc7e62405c7530e8f5136cacb5926f00000000000000000000000000000000182320f5d9211c08f3ba5d40ccca45cb0060a6d362b4422084617b9d8212e94a9b878294ac176b8f0e959bc124a753310000000000000000000000000000000010be6678910072ed9f932ab01a2d72f7374a2cc82bbd86a6006a495272aa89fd655e6719ab8b3a0643d002021f7b7ebb4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000f1ffed9514ee81e9b3fef4162c8f4980fe0429e57bbc224a9c9976cef7d26ab61ea7b0cd42eda30da97e3f8f5ab5f0600000000000000000000000000000000035b9b349b531d85361a4618a172b510dbc924df671b3fa707b474d0d8b17d30dc8ed208d66be91dcb7632d2f05ce31d00000000000000000000000000000000010030dcf6695d44ad3236032e47f7aa25b9f55869f5207e7ac8641db8c01f5b59627dd3442a1834b8b1fc595e47cdcb000000000000000000000000000000000f91ad5c923572a75d32962567e7b1b0eb84a91d485c968b5aebf8b3a772c2f94e47bc1d5b333fe43574308a78e768ac7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000000ae11c60537bbcfa46a08cbc219122ed66fd0d42f90e68243c32010eb99942554c349c021f0e3635bb50f7ca3d106a3f0000000000000000000000000000000019a61254aaa5b51b4d354f444706ebb0bc3edb87ec2d83e830ffe0282bcaa3278e947d053d6678549a098129bace43da000000000000000000000000000000001100f48a07456f01e16bcc833ae0a2835c964e9b0aa850574dfd8b4a7f06d03059e9b4df8931740ce0621ec7eb31218400000000000000000000000000000000003072392a824c386859735e2d203c9d52c19796ccf8538bda3b1436b2f6815bc86d05287f29fd0bb0569a81a57f0c22a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000392760f98883f9cf6c0f0a324b9a645cbae12b780896f6a3eee918c44a815daed156248d6afb25901521b323f6baa240000000000000000000000000000000006375c6629f30b7a36785269d691772afe1b95d6e1bfaaba9459c31086c2697e4ce77d148fe2ea166cc330373583f4730000000000000000000000000000000000aa8e338df7eac5a7b070a69d3ed1553a0c52fcd894c2bc8d1b8cf6ed38983c6c392a9a045ffe8ff40b39d18e7c87c9000000000000000000000000000000000cbc73b589cba1bd47161282642fe6f51f2b3edcdcad6020bdaef369d3f2c11ea9cafb9a7fdccfb89bbbe13560d42d1d97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d0000000000000000000000000000000000e71692cc2342d1e93e0ce72be69013023d012dd2294249dfd69e1d610e2236ee2cdef22446f1996bd3309825989930700000000000000000000000000000000013a1bbd3237dcbe44e05234f7e41982f4fd951d3741a3e90345418af1c922d35edf776a27bfbeaf7a15658db67164bc000000000000000000000000000000001197a2ee5c2541e19b5368c97abf51fde3dd0b922c3d701d7d84552c9f47b38ca09a8aef8240abfdcb03292ade1ff04c0000000000000000000000000000000010ca3c22ff8a47b1c683a58086ed9d831a5c25b6ce5a1971989974b4760cc9e83a1bc8d819825989751405b242eba379efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000001306f8047ba1a3417e7993bba0dfee9077eabfc275af91d0b882a53199874e0777d8dfd29767186d922d49087fff38b20000000000000000000000000000000005371b760380a6d287e129b329e735413447969eb9048def44f5c5987a64323d2a5c81484c40b20206832b86a4af9c4d000000000000000000000000000000001552eeae620c42d0bc4593d7c8e2c8fb4d6dbfcdde68d57158a7dfe837a1870a73b45a97b02abdea174a475a7061331400000000000000000000000000000000033a6dec61540a5cd5773b76847dc5016b309c5a027639598f51ae5b1067b3f7a02f5ea11b0e1be77a3ac236cba15c929a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d4520000000000000000000000000000000012ad5ff49459fd3a7940a69e2a78919876e9b3a4f0c142499e7b5dbcadb5c2b5d79c5dea972f0f0acdfd10ac53bcdd92000000000000000000000000000000000ec1be9cb379bf1e24bd5429a4a91857bc3ad45095d15bc5537c2ba39407e9f2edc5fbf711ef4287a73ea466d4f53c3800000000000000000000000000000000173605df66aaf51810793db1cf2021de6a7645ae84a5d439ee035b917d037d9f9ff072b5dfe8b9ac69feab60fe2d70bb000000000000000000000000000000000d0bd336825381ae1e18ca37bf6160ae32b653ec9f9dad159006e92c24b661f22b5629ba323e9e06ccc5887a962ec23fe9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000000f5e593c6588add92cac2c9467247fc6d900f20b4d3216c258f88f3334eecaccbf3eacda227e2da46cf520e5102a9cdd000000000000000000000000000000000458177ad6c190222e53e054546413c13216286d414e3509b7dc794dc0704afd26bae93ff630c6157d05d46d805a04470000000000000000000000000000000015df8a7720d389e6112707e37694afac2f97282676a89964deabefddbb3a0f1cbc885d4c875b945b8303c1ed2c0f46b8000000000000000000000000000000000e3c7f1af7cf5923dccfc1d25bd86088706a3a44f5fa7f97171228e8f2a2b18e9631b2a63bd5a75ee0bb83fcc91a45c30a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d40000000000000000000000000000000017823fc8a56e6e5cb9924037ad6ad1b43237894a877572dfe3d3cdc1120fe83e01de112b55f7f334dcb5c6247c210613000000000000000000000000000000000daa01f90cd14d82d4fc40b60b463089fc6c0e567fa46bae69184d0e3cc5acdb1d759e3291e2781fe0b65c734ddde28700000000000000000000000000000000164e742b123c19e52e2d7a6727689181f323990a3f3238072f7cfd7fc0f55b7be4274c0df194d85060a81f3744d3978b0000000000000000000000000000000007c03a1678b6e91c1bfc66ce8fd419cea13c7cda3213856ad21823b06db94538153a15d43a9d4270edf77b9a5ed490e6cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb000000000000000000000000000000001865bc91e645e2e24c3efa3afab8b0e278dcf16b29831f75b3eef0b342479e997b9c5f8ccf67c789c830609b3cc425400000000000000000000000000000000018dda7857f919a6a49f6bb465c27342c8fab6afe6350c43b98e91a3105276f3ac27268454e9a9c6dafeb2218ddc7d3cc000000000000000000000000000000000b098258ff8b185a5c59b46150954d52db5a5f68bc7975234491406131e4f1286ce79156dd1290aafe688f936ad34e31000000000000000000000000000000000b294e9ce904fb9e243d0790147b6070b10ff611a06e3f639aacb744154d02016ac08f6769732d4f6944ce9257680d49e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac5768000000000000000000000000000000000e541a22a7a36adc06e445f42497596e1017a1d99de85bb945a195cb3cf0c14d39eb7a2aa994cf234eed77f6307cf6410000000000000000000000000000000002de753e41a16565e5ab1b61debdad54950e9930e04badc6e356f10711d7688befc6827040356c0f0a8ce4f8d7121b3a000000000000000000000000000000000f2202e34ca164f1a6c0afbe179b714b303d87ef14534fe3f4230180f709dc63af17f04487264b3dee6b24ec4d0a423f00000000000000000000000000000000004044d9e3b3a77d6a309780c870a65e05e1ac531c5420f6ed0056f5e728e2b83a968ca90d579db50c2dd395f7e40beaf26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac5794600000000000000000000000000000000166d7692fd30dcd06b9f01ba2101870ed347840509b3242f7cecf91fbed91abc24b08b08cc39c508e6499a2f8bc3637700000000000000000000000000000000076ce6dcbc77812b4d5b44a50edba5a082cc36dc24a5cc348283a4ce1518198b56134c9807ef850edc9e36e9a282b9ff000000000000000000000000000000001261d9412245abd7ba3fc1597f34179e54766c49306725d42588545e14f4e450ee1c7af913ad7225275c57680c23aa6300000000000000000000000000000000096602b4eee053998555ce522c060d5e04c7961eeaab0145d38c9b13362624f54fcc8d0b77f2bbaf8c312a3279f06e4eccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f940000000000000000000000000000000012be34c18145aac51a1494f4052edbeff14c2812ff494cb78198cd7d9db9e951aea80490c55c4ed926f6a96a2c337c880000000000000000000000000000000000536e46a63ec5ac0f2f4eaaad6df98322c6a981cf2fc8ef253269cef20a76ba1ad089c24cba4ad4680dc4192d66595d0000000000000000000000000000000005363b9acb66ee95713b63dad076529805c0dd8921c738e205e7b1d0410a3ecca0870aeb2e64cf45270d49b473371ddd0000000000000000000000000000000016749b2b09d889b883b6fdaf518345d4cf097a728b833e92c4d21b5c41c8d5cfc0758e895b60ad101a74bbb6be6ca0c5fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb34000000000000000000000000000000000067a684b55fdeea39a29252b355700a4810f083909cf2c07a80b362ac1b4d58f5900c68d266f7ad81ea278c0931bc1ec0000000000000000000000000000000001b1f78d194d77cfb4a2116ce9e29438dbf38c52733b0295198159d7cadb2584d86a75c24aedeb36234a0becf9d38a870000000000000000000000000000000011fced2244cd959872a25c0c7bb4af6151d99e1aac079c606db4987b9ba111261d4a16e7d82362b865324824445a946f0000000000000000000000000000000002659e7016ad615ed80ea1ae020903431b470bc0341f8e0918de9b8d2e933dd9f2d9123e9e9d20bfb05d49f71c3c454cd94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000008f3c5de8c94a98dc5ad7846c53980384f997d1657f7349ad9b51376d41f4b21861d212fb6428bcf2347d8774f44156d00000000000000000000000000000000110b245b1e788da41dcbf60a3ac4987c1925696dfca85d450107f654fa1230adb9436d60c9e742dfb4e453ec4944c56c0000000000000000000000000000000011043b975e01df36a36307ba9234a18b97aadb9da509513b13e4f3c80432b0cc5e69a3bbb3cbab8df41bbcc92cdbf60200000000000000000000000000000000120aebda10c52a67d23842e2bd9a897cf38c58fcd11e4e8c5614db5e409a7c03111feebfe2f1212ae753497dc59d6ae9122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2",
+    "Expected": "00000000000000000000000000000000021067690e6e001e3d0d01449a7257348c4ef68e66dd47b9014d7687d44749be1f33e6be95df6a204169ab7103dc2d3c00000000000000000000000000000000062efa0c36462ab0734128dab5da8635705bd1e1b540817c5805ed9417f176723eea92425db539e8763b1c79b9923e9700000000000000000000000000000000176c9af1970f026bcfa87e6f85a20ed498c28c6982e21bc050cdc29c0f0af832ed4424082e4862d985b78519cfa75b820000000000000000000000000000000018718b0d0fbdf4783cd0b01524ab153b891fbf08cad60241a3f3163d2c3496c36afdc6de62ab3c9a037f88ee408ce5f6",
+    "Name": "matter_g2_multiexp_27",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018adf92d8da050c76118a3a3b2ee43955ae8b14ddc8ed64f5672f40de475f7e0ba6ff60c4b6ca3e863d7914e6de2cc330000000000000000000000000000000013d1e19011a1ea90389480d14fa608985d895e05edd9c28fb34646f70fd7bdb7857fa785b1e3c8a2997da6c3b5337ccf0000000000000000000000000000000015764827d9838c2b011660230ef9805af388fd997cc229c939bc5f4213d517dd837328c45b0b8ee1d6508cb70629b7bb000000000000000000000000000000000d58fa30a2d095ee8d946e50a027ac4cfdd557b3fd9c82dbf1536ddc0f42491a176ecbdb026306e6ebf1bb182a4e8199b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb000000000000000000000000000000000cacfc8d0bc6f9db737c8a316043a6b52fd5946937467afc09ddd14e509a89f2445065ac8a8c56454d529d67793edb0400000000000000000000000000000000148b1b941f159d93170fed949d5f53bdd2603d78a49443ac0e2353130ff914376e018c3db3d12b807d105f2d50eded8c000000000000000000000000000000001382a3e98cfd072807214479900a8602bd666cac7f19be0443ba1354bfc05666f40384e9ccac314b5d0a2bec1c90ef0c000000000000000000000000000000000c12c2222f67a5adba78f2c0be5be95ed743e835857f4204cf47b67fa2eac45cd5985fd82c7a3904944e7b84737374b17e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d31000000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000137b12f731ec925dc51e20a9c90323d14e1497e16b3a4b5651135054ef0e58e9b18167da15220b9a4f7d81e9a7648fc20000000000000000000000000000000000b2d3ac534e1e5b2c9ff4092c2d8dc5efd99121de7df953e5426eb33934ef07e41b196eca50f5a04a936881a05f2b2a0000000000000000000000000000000004feae2377d950717695606844a4873ed7b5f6703d7a63dc8b960b99b68efbba710c2db0f1371acbee314875b97ca054000000000000000000000000000000000f49ce3061e7254dc1bc8af3636a05e098cb96d81fb31e25da97c6266adf3c41a74d46ff32f4fbdb4cb7e4a3f69e827bb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a00000000000000000000000000000000064ae3fd67250f2c6332e1e149ec09946147e12e0d871403e559b49aab68190a1454b3ae924727b6dcf6e1ab327c9d7c000000000000000000000000000000001131f91c7a0e1854bba3958b36083c27904cfbdb8b8cb3fe68cf578bd1cb6f7c6eff91d98e4b99086926c5d4272cc1f200000000000000000000000000000000071c6a92a8d460ff72d172c204c8a69d6b6752b8c1f731ec63f7f394c0c3a2a1bc15e865172f693f523c11cd4ab1f88e000000000000000000000000000000001193876df7f4a1cc9b337a41c9faebac2f209b9070bd75294c2a88d3091a1e55b51fad482fd2aee8f90458eeb7e981fb8902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf2062800000000000000000000000000000000025c20ed5572dd1c9a098f241d2965d8739878ddc57c017632afcf6e54964894adbd6d30f62f316c9c3ec7a08268afc70000000000000000000000000000000013bc3e930f4fd5766db8f04e1ebfaab2b67f620119c39d687c68619b3564f3e8b74666c9f8bed6c1f080a9e23e9c0f22000000000000000000000000000000000973a3cf19312f90843f1f013b05484064032557807ca67b2ded4a27fdac12d6cd0e1416c8998cc8635ce10046adfbb900000000000000000000000000000000108903617c78fc608eaf007aa13861c970557f2693b24e8a278920897be9694570ae6e6c7749c3eab84d5fa3af5164b1426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000005695975c140fa14998e5916268bde2135cda80a45414fa85193fd6e13c6b5a6486898f590d76175d8ec2629c923e33600000000000000000000000000000000033f58b1cf67e51e9ad817b31919530cfdb5db5ca4a537d9b006b63399da49b2a5077bf5c3b3b4fb10b2478f466542540000000000000000000000000000000015c532e40ec04d9143e308895b2e7e3d3daee093a5840e1e76ab528fcfa5be57d9796ffd58ad5ab7df6f88aaf34706f2000000000000000000000000000000000b55747d1e8b66e2b2fea67229f2b7b17d58ef547ca841bea8db5b53fafaa18390f11b8170c41a5dd29331917fa2e348de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad00000000000000000000000000000000001ee5ebf73bb40a5c0822350853bb5aeead3262380dc274faba6b04e58e7fb9d5a4ace109ffa5011e73e3d89ee6fd77000000000000000000000000000000001427659e5ab1f8b47edddd27c613b578890d4c66c835c0cf8e8daf19d0ae842f0bba5bc83ed7248adcd75cea5d222a270000000000000000000000000000000001d4560185690ac05e56c2d629d599bceee3ed2919c29e3d1ac54e80ae99b5eb2f93bab865e8c1eef7206f96b2bf4eb20000000000000000000000000000000016ecd3589e3703e5b0ef53790130d5074d2bc0fd5839d9c6ff905746a77e393f73edf53b98b99d9c87a1fee1086aa8657431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb600000000000000000000000000000000163850016261f34de2b831a0a8dd3f224adaa3cc279cdb40e0ae976bbf736dec26c55a6c79cb1c623870b62ea216274b000000000000000000000000000000000a79af5c054cd08608d4be1705058ef7b4ec38a8727560d960f0325d0ef915c049a89e76956d0296bcb6c96333c3470c000000000000000000000000000000000ca89379e558c7308edd25bf06dc05db857204e9351299ab66bf050c8f051341a6c15a02864c679f07373038de3fe87c000000000000000000000000000000001929f42ee5d9dbfd1f6656f61e6243ebf0eb491762b7f3608db3f3e9abf565ab1524f770cd2ade334885d7479342c92c6745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e00000000000000000000000000000000193b6cf7300e47ecd21a05a71b13a8de45418d3f67931789ce6111b8633b9f44063ca13ba8c8a598ee0725caaa3f277a0000000000000000000000000000000016884d982e2ec0fa7e59fb34ae8708d0bf4abfc260837ef4432e8e04474e504b85450db8af8e6809413c90268801fb3b000000000000000000000000000000000fb48a8331f278845979beb8cd21060355566af215ba44029455a03d0c016daf0f6b7c5773d1a99e893e76b4411a53c70000000000000000000000000000000007056e30143058eaea89a3065e1de768d49860b170d4c364a28d38475f90711fba62c1787adda90dd2d347da72680f4eed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000501fa9af88e28d4f0c0590a2624239bf1724ac7174b0f1d5fd7527cff1de9971d6aaf28ba4005e88e181daffee6b20f0000000000000000000000000000000007af5e30b5aa9ad206645ace12cb2b36cc1c6068e604184ca8bfaac5a4ca327f7c43a74d43417918da7df84e3bffd282000000000000000000000000000000000bfc0538d52f277d54749ed0b69697b4c60ef0c5483d21dda76533e15efedc9e2b2ef07618457d64bae8ef922c0b41f600000000000000000000000000000000048935cd352e999bffa613e3be0a9f9a063d5b5eb46cb5056e41ba214e87f871f216ff41ee297aaaf2994a7b6433f58d81633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000016ab1e8b6f41891e0b65f14397c0887b27ff27e7463333e0938a7a1a181dec603056afbefdb23b41bbfb2c05807289b8000000000000000000000000000000000980d0ea9ad5c87bbe1aefb708061f85faae1e1e3b01c55bd577631e5bea2b5ffaf5e2478f5a8df89447fb8a73559729000000000000000000000000000000000784d0c5fa243bf0125cb2c83a4040715197e99d507d71a3bd9ca396074cfda652c1ad0dd95c3cfae369e68d3431ee7c000000000000000000000000000000000e533bb33e6d269dfdeedf7d17c3e0c19f694d151e8eef801c326cbcbc463a42558f58cbc330bdff0d8d91e2974eb4cfc6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000050f005b00f371a7308b5d7d7f67f7c00bf15acc518942607f32686feab5eb503391f964eb7ca711aa6c7b4e494d7eba000000000000000000000000000000000e2ee5092170ea3da0b1397023b2386c65ec8b090484353f2e5d64694aaeb8d5410ae22c92662fcfa21566d70173ef36000000000000000000000000000000001549723160fc7b8f5ef9a84bd1803f18b76698aa7a663d9c107c9ff6c6d02894edc80fd00d436f3a942c05593c5464ad000000000000000000000000000000001032f49e3527cc1f1355c65edb21220c6afc88919ff67ba99c65645cd3b8ca6662dd0146f6a90d92558b3f54815a361d6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000170b317e49f1304570a3a3e6bef78fcf8537a451ebcfef5afe3eac4aa1aa87dbf95d0f870fd3372d37efc9e663621cf7000000000000000000000000000000000269ae0677d71b2537078e96d2593482e4d41b6d1d2cbec755f307735faaf79c01fa27f1103cdfae1a9bdcb665f592c9000000000000000000000000000000000b115d5a9fb9fd9361d0573a8d68c5193f02edc1cf3fecf004c6603f118f28ff394220f6a9e1051a5d9d4b417290b7f800000000000000000000000000000000107b45614b18c2513f8c42a0032cf0f3f300157b39d2969ef7b126f17a9b5e8e9ecc5a61a2ed4db92134b0797f6a0ea35906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000003de39b056f8f0248b138437db1536b7bfee29af00c37fcd14c25c88f0f051eaa07c763d94c8ce497696311736c0b7140000000000000000000000000000000002b52981e828f8dc1cd371e6821d001e1f96d57a865a3c0a255298c43d52741b18fc60903d1a5ef6227061dcb243096c0000000000000000000000000000000016b5335f0f9516f52f2ed45fe723ded427206ba96af0879958f1f22795485b2867e953de3d9b3a9eed2c37f26838e1540000000000000000000000000000000004c860058c7ea2e6e4eb2a65c1dfc20b3070f89ff58ab99bb51a4eb9e7f0642f7b32d1d9f27c668a36a9e053a8d585f394ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000019cbbc125ca1b89330c21ef5b42fe0dc1e795271ce4a9ecabff04eec9029f756f180520f0e7b84be2e9fa4af395536ab000000000000000000000000000000001630cf0c4f3282689a3e01b5c8f9be3803f60238bbe9fecbb0d9e8e49f4ec9f6123c44840acb8cf55f8f6bd15579e6830000000000000000000000000000000012afb848bc0ade8f0c25c6c342bb651a7481be065a48944bbedbc14c095af8a4a048fd1e776126e2128f904afbcb17ff000000000000000000000000000000000dbc984f9ff907ce5553bb11a458deaaee0efea49d6816ed7abf1dee7b70cb18cc669d4808e75678bb898359c7ebedbe6feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438",
+    "Expected": "000000000000000000000000000000000be6dee62b8c85e36a216d16c5477a7c58f03b992277af83d9b53b3b2169414b72bcb4a97e3667482e888738ff17c94900000000000000000000000000000000067337c69c37ef6f0ae59fddb84c46a2afe7fe047ddb57b3b80437609f1a21fa5a73420fa5b44704ca1cac6c7a99d9320000000000000000000000000000000017fe6f37d2410159e533374ff3812714dcd07610d75a53a5d502cf2f51e750c48858db1e109f6aaf724292c1402382f1000000000000000000000000000000000b8ecfe1f5f5d95777b0fe5d94fe81b82656e6e5a62b7591788baccd251d93e4bbc6857cc87cfe6b4ed470c33631ae22",
+    "Name": "matter_g2_multiexp_28",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000126d4a9ae3550e31185aac9011e3f086517cf79a279326c264f51bee6615dbcc730d78055489b5602e91b08f96d23882000000000000000000000000000000000aeff5fc04fd06c26af8b048fb2d0d493525ba5c2bde30664e7371812d529ec7dbd584c056b05fe02179b7eefbbc45fe0000000000000000000000000000000017c6538d2801947cbb646d4ec8b70b1e24453f7a984db7ba73e3a5dcf595bdbad9703f2d846ab02491e5e3a5bcee0762000000000000000000000000000000000badf551dbedcefbe7c303a5c8a52151b5460caa22004028893af4d8a3fac30cb1da1e986f9124acd5db7a634657dbd0cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000bed71c7d878e7ecccd8233e3e604e564cba0b1ce75f726f846f3a6e2f3b4f5b12a28b8638be647f5c33226edc2bc7fe000000000000000000000000000000001914c20aabaf1f6f82063223053809622ad82a3a54668bd600db1aafba22aeee5c8a07584e263c91cb0fc5fb809da63d00000000000000000000000000000000056d9cd8f79a90d16b36bde77e546f8b3064ba7dd0fde78d6bc538bd6ce12a4f32860205d5d396bab3d70deaaaccf9450000000000000000000000000000000012f7e420708b66132157a80753678de292998cb6c4f00244d3c47a6077b3401132b73c7f52369aa2a6a90892f7be4ed913a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b000000000000000000000000000000000339aa1471eddee8cc0a4e4db5a29c3e4e92cfbabe023995a79624614aca522cd459dfacc0cab346b1cedac347e1df100000000000000000000000000000000016cc4ee8cb72fe09e65616fbe9bea1a0077114ca841ae335f1f9eb5a0b129a4bdc77cc6dae8727d74fe21f0d870a43f2000000000000000000000000000000000098a21da6e983228ebbed0ec3704c9d2521e935506c0567e3bbf9b9c379ce6d33c3d0dd8f5e013b431f740964db634b000000000000000000000000000000000a7a38abe8e282544ec6c8740dce8559fd264393d0a5c9af9813b2430bdb92b3150eacb6732b9cc278d0d0e622b263ecace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b7740000000000000000000000000000000019ed305bfe8d8bfcc20794832b3c117715b6a658c0bfeb629e5989f265cbb456e857e53d168932589e4ed2806db7c4b4000000000000000000000000000000000e2ffda25fc316a38f556b35a7a3acb1a2bfbc1f9469a1b6427ed1f216e113a379932b0547f5370be1017a1fa0266cfa000000000000000000000000000000000ebc493c9a79b8ba58f48b90b9d287c74f505dcb484eabda79ada987d63a4df04d671d4c4ae4b32f8ad5db6a1b80f37f0000000000000000000000000000000019fc715d26c0c7a0c291ad8319e2e8f2920c63b4d4ed3f0e2f376aeddd4f7bd9269175ac8d0f421b001e2e48634f3f238d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f536000000000000000000000000000000001285ff533da833a3daae7d815b1b86feb6f20b7592af8b0eb76240f390ea48b69a75547b040e7282b71779f450d3510c000000000000000000000000000000000813d38fa21c1f3c87b9c97ac03e6aeb8fa23e0340a0dff4e3892c774595648743d0b8980a7bd21648ce9b16a245ac3400000000000000000000000000000000020a69dbfb736c64e4cbc800aa415729b24ec05e901f2c7ba38e49a21c3851dc03bd4f7ec829d4326fe6c13867069a07000000000000000000000000000000000d518f3944053c8f74c0aea1d054d89106312880de4479b3dfb45b00945ff8bb58b12f9a489fa9fcd87194a71475d0a1abeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000018c82052cd483eee7aaa421c2b998ab0b4b32326dadba03c1d923726697d3940b40d5109ba34de09439e833ebc19daca000000000000000000000000000000000e4feddc3eeb3fd1eff8316d5b0cba554714713e8a605a55909889970ea2c8c58bb6c568024709def73b29a5a76563c100000000000000000000000000000000098da4cd0281a16e2e3e542ebb92269c8208a3d373394b0af92dc8a2676f9f0b6e85fda9161e32558e0569cfc7b1f3df000000000000000000000000000000000b7b54b51821fc037f02167d2e640f8dbfd1472407278b4bdf47b958da39f28c64569c3199846c293bf60e86aa45f205adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000003058abd4e3d49c86ffac9c95b1f07b66a22c42654dc4a2e3b07b87c22024a8bb0ee084a558ac22cc9fa286861fd77ff000000000000000000000000000000000fc9a89ee26c323df22add487a6bb278ca3f4c9a91eba4e067d5abc9dd3afededb4f98263e10083cc7ea224f28d3bbe100000000000000000000000000000000058eb015f1e14da860215d59165e12feb8d1317f652eeb76b3f08b38ed943c94e632dbf8145233dc93755e44e027553e0000000000000000000000000000000010897d5c2b481f9937d830b333e7649931e801a6bbffb7d9a3ee28ab1e27889691a9f0b9616a8437c3cda942bf07282206e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f0000000000000000000000000000000013b88963296d8c8197cafe160846ee11365b7a991b35cf5613dc57714aa48307f4dd9c6ff9704b29905c18a41a48010e0000000000000000000000000000000016a97fff65fca5ff282a818deb8100104308b8d9dfacddcae32fc2b6082331b44fa70580018930fe1ab9d9c1b13a59a20000000000000000000000000000000019cd2038acd84c2db1f0fa1b7eccc5f7ae3da803cb72c4a1e8390d49e0adff1d88a85696d9daaebce9c6b8a2f861fb36000000000000000000000000000000001271338587f06847770c72dfb3d9a657d05f8c7a012bec77a7d40a98cb1637ae99281c82668486119608b01feb25e6dab3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c600000000000000000000000000000000173ed7c70f4683102cc6a276d192a8f3b189197d5ea5dc813c7d0162a1649e906f76a1c9a1cb1ace6e4d937934b72338000000000000000000000000000000000936d260b789b1a2a9d04388caab364049395be61d320aef66ce50f052eb462faaa2017731518675bb0e4a2f050e4f7900000000000000000000000000000000070bd1254cf4b209ecb40afe248f2e53c390636625460439952ca2977be021d93fbec264c31ced2a810e8a5e54d750230000000000000000000000000000000016ddc3312f8ed359792bd213d086a0ff1540e3e5a2dedf6c450fb96a9b6d1edff9bde31fbc04de382cf44694a631178229b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000000aa4bc6e1a3e6c3c45a29db74b27af27b61856e2cf385ce0e5094ad53db4d31c4af45b5b234c66a21bf15018c13ece8000000000000000000000000000000000188affc993bf6c99103029c1e406bb1a693e4f1dc650907809ba3de1471d41095dc1866578962c72538ca85d09fcd22d000000000000000000000000000000000e487a7151916694b980e62b64ba49ffc54aaccfa0b0fbc5c14fa4a50d1bfda55698df5cd8570c07030f145c49a4ba9000000000000000000000000000000000084a05dced107d29a0fd4cf817ab67017ca33018d5c7302167d08c64c45c5c455fb5c907f21c39b8a86d037a126df4e76b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e000000000000000000000000000000000ea7060a07dacd84287007a05b494bf19a03e5a759b0ba67624c54cac3562c0ca3fa6e444206614d00d6d6684b86bcb5000000000000000000000000000000000eb2f332f4481276f931d2192c1a9f6d7585e85f248a8ac95aed398cb61bda05230bf8b9c041c6f78be3b34668a9c1a0000000000000000000000000000000000faa038219f844e379d8cce55cb8f0fe2b55548a0a0e1e37e25ba4f432e6b1a6451b8f081c171490bf055f81cbfe5f8600000000000000000000000000000000037c70d4e8befff257c4bc98a4726a961f3e2e68e7e02f9f2c94aa8f5fc67a1da44d41394dfe376a6c04240e4cd5825f3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f789700000000000000000000000000000000100a24d21c0ddb20d76b6d9fe642da5ac1de28afd642ab5c08574206b8b64d1fd822d295476bbdf2ca7e9267138034dd0000000000000000000000000000000000aa7e4f2f77acfe8b4c8f3fabd56b17415ee9bb182bca1db15c399479ec60382f980067b9d4c4ef7556d621259ae9110000000000000000000000000000000012f7a7f91a988fa661c661013736f0ec92b40f571ac15a47067bb847b09ba128d1dcaf8049b941a51cacece5db4e1eb40000000000000000000000000000000007528b0ea66b6ab8d5d318f5e4d1c0e9a4f504057dbb0397b614a1adb160032127f2ac35a1a98da70f023cd343a35ffd47944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd0000000000000000000000000000000015d72b8d4e71cc092c2875de80f3d12e003804d980a4b1dd13cff34e9336397c4533b6ae3a03beb2f09312a605947a270000000000000000000000000000000005976027a98f7b0caf4cc7d0d71440d3e4fffb1ff65fbf32dc890b275b646f2a32600a6215d6b2f999eaec8e58cb6d5c00000000000000000000000000000000111583b7734be53a7d4d090486070cd3d9622156c52871ec79c83ca024880684eada56a36b58cfc3490e65de41e10579000000000000000000000000000000000fb670b553c2ed4c81962b149efd4b0c77edf6ee70eba88300cf264dda98190e550540fb9fb95748599bca3abadd752030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac0000000000000000000000000000000003900e7cc0a8e891dc4dfc45f08d97e73ccbe2021a560a92c493aacd9c0614ad100294b5d7ebd634ffe4e5ea301a26170000000000000000000000000000000011ccc136127189728a7036e85d233fd150d5483963c48074f9d8ff83a0791c950da380e717f2bd0bff8fc115e9e886290000000000000000000000000000000007d3e76bd1f22679d228b4ee50a60cf1bd1fdaa171372cfa34bf4136a091abf7e5ef3c6b3446fd41d5de68b563fc7ff3000000000000000000000000000000001107f636d9187155357bea75c943dafcfba2394a9300054026b46d6f9db31eacc06d1f64c2b139af297dc4783026d98f4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd9786000000000000000000000000000000000e3a4101f6af3cf0d5d5aa5a0ebc26852dc69f91c06e96c5f1c7f8e4528c3dd92cb6f629620136ec356f0657fd9ebc6a0000000000000000000000000000000008d34dc3e1fa8bc22258e23b504d442a11938370325c101f1cfa52f313724e0894be722646195fd078c1a49720cde8c900000000000000000000000000000000163730996c79787e7ab89030de2c26e26188187762fa128ba4378a398ebd906dc56d99cf228591f394396248665c196600000000000000000000000000000000008f0a8b3d003b6727834228798950fb7a3cb6b931bced4540693445a007b474f7459ede17f87158e932e4c9c094ab904d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e000000000000000000000000000000000cc2d30f7d3869abfc34719f40b0ddaf00f52bcee7ec09a16de51785d55531fa7fe3ca1544d7103b9caf7105d60d9e930000000000000000000000000000000002ebd8af0bd3f82dc9dca585feaa83071534b2bc2b3d2aadbe0d01d759ade77ecec3b3f7b72f82087365a14dc205add80000000000000000000000000000000011aa3734a4b9168d3c46944cd726bcb203b94b25a97437a6aaace9c84da708bb073ee10585f28bc41e0601567863c193000000000000000000000000000000000ceb4ae5a8b506d31e77e2a43f3af8ba9459b887a927ca5287edbc2ba7c7cbba85a6e4d35c099b7ec7bf7eb2814cc38ae140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b",
+    "Expected": "00000000000000000000000000000000192eb406b52075513584ae3c6093fb534270d716c79961d0a3c4bbc44096a2e8d28228363e2c8da54857945f1b983569000000000000000000000000000000000ee0d95748b13b531821ddd71a15fc529a2ce2c99a66f14e28f97478c3c2d524cb7c4cd7e71a1027030765554b8f50f7000000000000000000000000000000000610ab3e064532ce261aa2ba4f78721ac4f78661cc13fa09ccc279267e6f703f1bda17265a5eccb0061ce24d31e000ec000000000000000000000000000000001966a334b16e64e4dbd66119af97bd2b8d6afec0eb1b8207f437c00ab134ff369b3b3c1bf51b871a7fe8ad1ce93dca4e",
+    "Name": "matter_g2_multiexp_29",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004c22bd94b82ed3b106532a58a0253daf51f579b9d746c624bbc6b58603942eb139c1b576241ca8fab5bf1c457112bd80000000000000000000000000000000010c6f7551d758d1128add57b110227296e060074e4cb934132368f079a794770ff406fc7717867df0f461f5c9fe56960000000000000000000000000000000000048f88afaf6eee5039b76c0c5b4b49671f6fd04f38bdee1b1c8f347a9dd4e6aef387b742c8f9a8aa387ab4d01fe4267000000000000000000000000000000000e7be987d0411dd7138e47ac00f9f07c4737d93aac501edd16362ea5a633c9071a6bf542d4db540d75edecdedc3a8f0ca57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b15300000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae8fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000003e33b840426a6bbe15b23fceba829bda9a5ab89d37e60133874f61bf1b10e05d460bb5d228cb178cfae2a5f41035d32000000000000000000000000000000000a9c5460c6443364d9f9440d101d92a0037343789ca0aab6dffcc2bf81e1aed312299a21556d16e55b1398334d9061f00000000000000000000000000000000015db251708253f7de13a5eeae5aa76fec415ecee1ffd88d882580da5da8d9f96c6ff90d920b329096a103dd71e7cfa580000000000000000000000000000000014c3a004cb6ab8465e05d965dc720b37084d98de424b160062f225dd0b67a8e62ae11a3c7bacaa129a568f3a243357ebb061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d61000000000000000000000000000000000c8fecac8bee21d916cc47b96a66b7a522ef4fea76fcc86ec490ff44b46fc01ac0446e3885e36ae7ab62a409ccffcca60000000000000000000000000000000011676ccef54bb27ab7db0b5ec025a9d1f29217030f3686e71564fa011d9fb598f44a8bed3da8fa7fcd10d01e3f66d86500000000000000000000000000000000093aecb91956215980854c6f19120777983a160e16026560c8076bdc4372f53065f9fee0f5830ea192aa5637590a745100000000000000000000000000000000035d773ef15d8d99b600a6a575eefd661aacb49d6540639223a454594570d0f00ba37340b63a2c8a0d4e53ee7dc2dd91355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a0000000000000000000000000000000007b2891e9cea2a464742c7f962deb1566c9d4f9e4e7cbee1912a72c5b064211c39801bf42bd888bc239e6b4ba71d700300000000000000000000000000000000169cf5e706dff2945145d5ac14bd5fc8f7e7c3e5f7ce733c865e1882d236926c71853efbea26e13efe4eb0d0e7ed5db6000000000000000000000000000000000de9ee19c4bc2fac36debd4c91317e54f57e761866b134ba9a0e84a8d268b11674110ee8f91aa8a6b80eabee2e5e75ae0000000000000000000000000000000016d91408a670e4ee43ab8e21cc341596709113950d22bdf5073cd90f520667699e94f64f76290f1bebfecfd80a9e051430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a7720000000000000000000000000000000001982744a15e8163a6f2ee681bf27a68996682216037d67d91993fbbe040e16ea21a9cb600fc6a40e7289185393544c3f000000000000000000000000000000001131d7dd5a5b96ac1f4c4aa210afe7af8d371cc16d32289aad38c93afcc1d3be53716f82e9d14ce6b1c833f7f5871ad00000000000000000000000000000000009adedaf19fb8823ec55b803c9509ad98217730bfc6424c8b69a071e99d026492e7c8c4a06509491a3bbe5893988c357000000000000000000000000000000000cc60733a783c7df76541daddef2245e6d2b694b94649b13c21aaffdce124c1cec3fd8ed5a5d4d4eff3115ac933e5df989a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000b366a80247a8e3797f1c711aebd60c99ec7caffda34514a3716154e900f2387c46f87f81af036a383e3f9234bd1b50e0000000000000000000000000000000004608b7cea13d08724a2cac691e61255ea7472537f7ff59894d511af7fd99ad72f0a7406271576300a7d1d56aea17bdb00000000000000000000000000000000141abedc914d3d1ed587162acbfddde60f7dbc1ee5e07fdb5f3515b87d1a29024c9e19f24e4c0e3979bd938aa4e798270000000000000000000000000000000010e72c6c0510495dd2c4ecaf13c1c6404654e1be369d1ca485c76d8c2304d60d69b90c2e171f18bf55668232e747825820b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d56200000000000000000000000000000000081d5a229481fd297363e8e217bf1f94a00f54eb6e8a3f95f4de30081bb2b9edd82d53cf287e37b459afabcb73fea1d1000000000000000000000000000000000ab55f52ff7dc578ae8267fe3fa09bdb8174dc30bb835cab9851dbee7a1aeba82e83e07d5e79aafb34643d9fc9a0d1c100000000000000000000000000000000195245c7a762776bc1e81d7111e3b814088f1e0e7d686c3ee3e500cd0a7ad4015851563a1b8b592e491e00078187c66e000000000000000000000000000000001850c1e8edb0d6dab973a9975833cffee8b5243654bc4ebe64972e423799283707f9ad343bfa86548cd2acbe04ede5da3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe00000000000000000000000000000000000007e9191fa9057cd7df8fb83d497ad774735c242bce9bd34cfd21d3f8f2a8e37d1f38b592a61ac8a8d22a4287fc5b0000000000000000000000000000000010e36db1460fa65ea229402f558397c6fc57e9c8a4b0b9e85d9ba938196bfeffc951587353cb7c7d84479f60c087e3660000000000000000000000000000000004d86938bebb850fea82acd336c3900b241757dd937f831dd909ce548325955f103dd57611c0b75bf71412a6ac3d6ed30000000000000000000000000000000013990c82583007b693c1d6271c1e5820d7274c4a729da21a76eccbf7abab1f2bdd6c5d26e78d51476ecf154e4fecd1b87fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000009104610d5887fb7cf6a866584cae30cfeb00e1241083b017ccb82ddc9d72fdc0d2b1d227c22ff6d8497495f44828efc0000000000000000000000000000000002235f959b071f21fd63282fdbb46b1dec27cc193f3e9988def691c73dddd789b6a1adb977a68e2661fb41d62280f229000000000000000000000000000000000ccd46984208f183f0b70c9152c01fdb8ac078ad1d85f41e3a24819da321d9dd9321a8d70103282abe6d8b981447f202000000000000000000000000000000001711057042a54ca76b0c3e7f36f2fd49e339b76cbd2e053d93ec2838848d359865fdbbeb9e75e408b4b316d60ce2741ef0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000001481684941fea0f66c78faa40aeb4b5254bf78c44df7e37b191c095ff12fc94248acf01d2aac5637e9536e73a82c9f0000000000000000000000000000000016b72eff2830f49b24b1e1317c95143cda8bc11b9dc4a91ff22a24e0bc1a244c7215ab1040fcfbc292ab236ac73cbd3d0000000000000000000000000000000013535421771fdad616171f7348cdf32bea7486bf4d836b8b95c69b71ea9915c099e256287aa119af53cf6320ad86664f0000000000000000000000000000000019ba0f36dc556fcf09f0a4a6cee53de485d03d846af7afb792d16220551fb5a42a4261f936b008babc096e6f8f68b63af8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000014b2da0add872d6e61253d6022559f668bf192b4aafe0acfbbf341ada55b404d42b2b31182c1ad50c73673494ea5b7d40000000000000000000000000000000018b76b74e9e6cda8466a354ff66baeb935b5645cf9eca81f4b7342f7914c9bf35c57be402458c09781e66a89cba6e67e0000000000000000000000000000000019bc8c1f32ce934b7ccae6d8ca39a263939585d8f94414c3880fc7bb5a0a27d728708e7ebc42c5a935f769adcfc083f6000000000000000000000000000000001636b62bbbe34bec06253887b78ad5b3ccda1bc5d8baafe450f2d1a8e07334ca79a40c5c4a50b58aaed96408749e6f68285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d325820000000000000000000000000000000013c0fd7a8441b6eb2dabfe8c152aa480015f81139c46440741f3da1c50d18c17526c47e8b8c2fbcfaefabbad5f8a0b000000000000000000000000000000000009da839802e7c6759a87eeae5a05146e1d226dd828d4ef6d908b4a0431008f352539f3abcd3e4c532a3d8204e350a8510000000000000000000000000000000014709634973e4554d2379e439d099e9be8bc7ef031b6ea36a7a85d2ff5090b0e0de7cc1c6b6a004465edcf868ef5fd5b00000000000000000000000000000000146779393d82bde1eaa6205e69907a0536c782fa7fc6e11e5e62ad5468f4422b3688f2ff4da2af396741ca5e0f97de3835bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e44000000000000000000000000000000000ddb7d0380370830803a7eda2e9b694af71381990f182b5d1223992abb5afe9531bbef8b9dba239f411fc422210fdc930000000000000000000000000000000018b685009d012d72193043d09f8968f9a41ce2fed598a20536fe54cb26db1733214add38f73148e754e632f6d78f524d000000000000000000000000000000000b967a7b4ed1bcd9f3da16584b08e0c28d967cebe7a07069abfb3bbce94d26b6d95d8a807879b24fb1f5ea00091d6dc300000000000000000000000000000000039349785fdb7d38707d8136e9a8f650c4491c50d7425388b75fe30da56147992c3d662f22131ba7173b2550e613477fa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c8000000000000000000000000000000000455d7799cc1c2af1e219b23e8683113fec126bad1dd7a441c5d113b064b552ccb1e7314dfed1b11f42a18acace706e50000000000000000000000000000000014d2400aa3e2270714b656bd755c4bba55866d6e313f619e10f94de6d82b5343ae9a9483dc10c1a72a5a21e619a20a8b000000000000000000000000000000000a6caa6cf8609d23b7873c908e5321d064a9c107b5492d296d04f92c308ee705229dfecb1f908bca0024ca56bc125126000000000000000000000000000000000b31c384423c84316f65e03ba9e01a8f626236f76e4df4b8ce2fa053c1c1e6a9b8f0afbc253db8c9c5e2ce9f9dcf05c71155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a6400000000000000000000000000000000097938bb53db8d0aeca3f2bc180039a5dc5269748e9cf065cd88e59b30733d527e54cdfa224e9690581e8c7f0881241b0000000000000000000000000000000002d52d97d4dd415fb18348f4de78c65e2933fc45d5e5e1d8f0f0ca1cd52885704ab12609b91d6d2d1ce13eecc7fa0c2d0000000000000000000000000000000018b926a37a8e0ad836846d06c03a9b84db795fdfe5f15d1fd3e0f8fef1b2825b29ee3a503ffb2f75765cca49c2b3d4cd00000000000000000000000000000000073bac093e958a3a09543e060c81b35b6598521a8685629f77200cdc73b372588e66c247097e7c03492c0943bfac4d6bc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045",
+    "Expected": "000000000000000000000000000000000f79110c74f0e983f3d3618869af1d9b96dadba61f1d596294ef8a9412f946fa26cf63483528a57299dae48f85ada81e000000000000000000000000000000000e1a9cea3af1debcf7d6ef6f7b8566b5bb52d5548d4caf85925109228d7c9b50d65a1b24f089631e75a694f8e8dcaf040000000000000000000000000000000010efc1081f079e841eaa5a65cd7c945d4f37acc92c4ace9ae6c69a9a95d8cf569d604376b1c7e63558d022da90d269fd0000000000000000000000000000000010b7f55ffac8d57c89b664c36c20b2988a493de32f5a956c91b16ff67cb806298a59adcde12ead42d598b6ca3e1b94da",
+    "Name": "matter_g2_multiexp_30",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b139e5dddd53433362c49403838b3e2ecdd850a8df12d4dfacc0bb98f79d40966d62dfd0da1e721e7c0f298457d590000000000000000000000000000000000fa35e9c2e37bee1020ed99516174408ba2cf443fed115fe3a964ed86b5e5369e40291dbfbab477e339003ac85eb7405000000000000000000000000000000000e8fb87794860237066ed1b7ae7c2a783c48c52c2267f3e7295d1f17598b96232954e1eb6d6e80e716628f1db8afe48600000000000000000000000000000000083521e3a6d6e3f99570b747498520db5c89092b0077519c8421f9f41772c7a6e177c9cdca52f89a26c6036cadfafa8b32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000b1415e1dc2d4c1f5619b40e616d258867493d8624857e41d007f82ba8dc53f7ebb36d06f8348b94eedb794899e97df80000000000000000000000000000000001c01656fa47d62b4372361b80ea61501cfda47da5534e3e2aaa27b1e3c4de0bee0aa322e60c476fd4345340e5c00e130000000000000000000000000000000010caa407d9d265721d55f01dcfca52bde851ebd918e8fc4c752a41875940709c64599f36fae5e3ac7f211e1f67890d1c000000000000000000000000000000000b54a86474dd5f410290e4b4ac738fbba5e88c6debec17e38a52090b17ef371dc8feb0573e76c4b61d7688547a89f6a36583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e8630000000000000000000000000000000001649c78147fefa91100738e50034424244d22d8e1bb6a2bf471e4c9b29694a5c9476f4b129912bb09fece53aa87deeb00000000000000000000000000000000117a3e040c1f54b96c2435891a45fb9dd95774b5a55cfb306c22517e4ea72172332d893047f7eaa665fcc58dd21781f400000000000000000000000000000000105e8d80d46e6bab2bb9ce0525cbfc82e8b3320ee4a8b9c0086e21cf2b5895cb35abffedd1b5a9eef21f62a0a1dc48e8000000000000000000000000000000001437ee33abadc8ef6bfeca16c3edcf05480c3dd97db06e396e10d5180472f50074f43f9a031a04dcd11d803462fefadc5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000003b51b10efb54dbc2973e001f0bb634e36f689264484eb128de2882d6600a43ad548bc7d1def4541f0ed88a1fb37f3270000000000000000000000000000000009dd80dbfe6663ab04656856f192002593df9ef7f792dfa81f6a51c658c4c9ce5586a5edaffefd507f51ccb7e8c8101500000000000000000000000000000000144160d5ca6b2ad626e6a3424ff5139adadd3319940afa9bff7dc409ac1fc3775d5413ef4612b27fd22c02c1fe57bb86000000000000000000000000000000000e375ff490a626dd1d933a5c751c88cbd61803986fa8dc089ccbdeaa0a922758afbcdc30d29268fe0a34b7b79d0f76c139c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b000000000000000000000000000000000997ade20fe9c0d3eb79e61a66a5c272d02af668b0f3c8201a1ea071737f3d2ee3b0764f859480e95be75ab8845b407f0000000000000000000000000000000003215194b6a363d31ece09b18700479e6093fa3472a23ef0133e3dce60a3d56b6fa984b900162c4ad56a6899aacf35c3000000000000000000000000000000001647647bbc399f40124c43510469cf613732d0919e22b478b2603d7553927584cd4b3a407e3ec6387c4a93e9e5373178000000000000000000000000000000000bacc8afdd70e927e21521b3f62264ad4f22adbc872439ff851d3d169a1c79a0d02bca2aabaa0b9941ab1c71d092fac12196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c565000000000000000000000000000000000b0c5981bf6ef5b85bbc504fb0196ba442fe87302346688165aa7df8cf2642548760e11daf5b3fe2e37b43379afbfd4a000000000000000000000000000000001086828b9560aaee5e28bcb50db8153c40e632b18c61ed4105bb7f472b6a69ddd8a2836f6605102931ee66b2f07e441f000000000000000000000000000000000f4d7aa3d1a281af6f8afb3d886774f4e4a64490232f63dbe16e3b8c4f626e9d07f7c668d09cadab3c92d6fe852427af000000000000000000000000000000000d92ea3318779b532cd81c9be44b1abb179a8411319a6f8fbd7e3f158bc970917d3e0b25f3f3f6c8e0764011f9bab0398df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000000dbd83910f304d0fb2b6d8619c3a308c719f6454a357d9ced03b2882a50692c06cda7f4331f54eb293ed5aa079121fb00000000000000000000000000000000019c33ec829367dfd2610ccef9842ffaa5e4f35809657c22134fb09b024e07949d8370ba8ba1e9149060e9bd3babc19c000000000000000000000000000000000ac468b42925d2daacb8574d40064d393caa643f08767d20e72ac0fad1447a64d8743523312f3a91a118d3e51e1f52d7000000000000000000000000000000000202d1971fef2938cfd10bef5900b91cc4811939f66f1f5578a8ae0eacb2538d2a51c1e025449e1637b5173ab7fa3b6f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef00000000000000000000000000000000008b6709123b9bd501360fa463dd08076c59177dc0e8035c49fa2f541eef3831e4c584c5a9410c68999dddda6c86fd9d5000000000000000000000000000000000fb94eb34355c636dca909cfa71f52471217b9bc241cd3e98907d4a5c7eb67d5bc9cdb0c73c1369d7950a014fe6069fc0000000000000000000000000000000002e2ee515a5dc96a664bb1f862f21a8d3b7f903fb87f6dac41c3541f3d83633f351ba8dc4661607d24b912dd1ab097da0000000000000000000000000000000008bee545e00e3fc283185a85511e09fd0253e191f52d5c0b440b10228041800c013db3c9322a835e4927c0ae0b21bc1e8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000006ba635e74538748c29aa7c5690a0530f2b1970554598a432d4ea6d2713a4d26786b6e80f67b2f39e218b19323654ea200000000000000000000000000000000133ca9e5e0d4a8200d3522d8e87dec3c72edc1cf16b7305af4abd466aa7a0e30159388d34c36ea030450ef45b7940ec20000000000000000000000000000000004724239afc773688ea92296bae8845f20793c05807a18d6f35f03bef295da06f8ac9dff438b720dbea7ea93f3ea9c4500000000000000000000000000000000149c12922fd69e1960274a8b91384e929fb354936c020911495e6e3c49faf16899ec0c6e87713ee2f0149bf808ac8abfcd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e5066700000000000000000000000000000000148a4fe6ca67b6c785d5d8a784d5e68fcd2bd08294ca37f296b6426433b805507b554eb9f0fadfa9d293e8cdb8547d4c0000000000000000000000000000000003700600c2b7bfea54801ac95ff7a2c069bace31ceadab2947a0641462089fb43f0b9697acc005a23007a923ffe97360000000000000000000000000000000001705a769ce3c9a7a91283e4068c602d85808980d6fb457345a5f9b2499ff8fb3ec8383049b9b7cae96bd2ac6106a07fd00000000000000000000000000000000052b1f4e8a48a5eb2b2580614c656393819b4f0ffea874be899e4964c7e32d54757f2d48ca7b50e47e8bf6d6ab8ee7572a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000012ff8ba50d587765e68f95d276e364c8c40c00b55abc929f9ec240985269eb096dd3cef5826cf6269ecf54bc67773510000000000000000000000000000000000959492d74cb34c8c9ca4a21ddee97df99c8a6e627db3ef72200f39e0402d56f0a9709596189c80aa3aa50793e0f1a68000000000000000000000000000000000f7e5dbe884597054d6dc5e80bf4d0d333025bddebc1fdb1d61482cf15bcb4c8a95ea29cdd0925b5b816cc0bb307387200000000000000000000000000000000194e940c041d71f43ffaa51fbb31eb63c23559069b42dbf8777f35eddf14edbc3f7762c7b354174a584507ad714948234a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee2800000000000000000000000000000000101a8e54d1fc2357df60b0ef8872b729295218f29ff63f7a7b6a70b3ecdbfc6809eaa8dc1f62a664b9987e8e86154c6c0000000000000000000000000000000015b5ddd012b42e1a600d738e05b551d91e7fcf3cb36018ceda9b689b92022224990c11a6fa0b421d5610b7e59b7463c30000000000000000000000000000000016130be17fceab55387d43179cd943c85ce1ff1881c07c937b2cc0645ec9ebaf0e10718ec7fe0d720f49bed2b8caf15b0000000000000000000000000000000017d73650680856bc11619e6acc139e137f0a06476f5f8979b5ba7fb8123d85916915da60d1f2e8c84197eef518b350c2a62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000007d26bf37a97d532ec93a3eac00d9d39b064ecd172ebd5e18228b1601eb7a2c272aff9d88d63781b4a587c2c8582eec4000000000000000000000000000000000108000e850bfbfb02d7acef97592e15ca721334eb51197511b0eb2bd3bb647fc8f07713487b0a0bedbafb106992de4b000000000000000000000000000000001868c0b2ba732731f7536851f8005e8bae7b16545b39190251eb2bf93dedbf0803a42ec24cebd151998b690c38c0346c0000000000000000000000000000000016faafe909a1f926333b12f5463231a71058aec31d73893687d3169c4c3588436f6178447eed307b642490199c507d63a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a6000000000000000000000000000000000331338cbaeb8e304fbb9257bb80aff5d3e043d07dbc476dec2795347e4c25248caad06ad14f56183d2b6276c49ff98700000000000000000000000000000000167e9578304a1162de73914b02791468e14faa2e0f161aa57818b8a169b5933dfcab787ec0f4b23737011163dcaa02750000000000000000000000000000000010aadfd5cc781e73c31f2fb64e7981b2e28614aa18dc7b2d96d2bb4ed8c2ee9089d6ebe0cf85479b272cb049e934739900000000000000000000000000000000128d7ea54f338064cd2f041f42a1a1e77d8b9be4ee55f568786a36f87f965d8142207e518798061eb3e32fe3b0f1541d1f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b400000000000000000000000000000000004539f22654b3182d4fda5ab8d4bce6f1268d4e402b6c29a4cdff3b5abe0618d33db55ccd1ff12b27b2cb0196ac53e0600000000000000000000000000000000177e80ab6aa8512cc9e4d65b06b2bd76e33bef9038cdc1ab97fbb9d896ae2ad884ea16407490653dbe972b14e9c30c0b000000000000000000000000000000000c280a4431e41df6515979a694ce292f220278178f7f36e23c8a4cb2b8a7ebc520901ebe34c72a26b2c8a60aa1a155100000000000000000000000000000000006a0b80538a6c8093f3655905af1c59c235567d22192758c28dad1b715045189a412e4c1edc26e1d8ac95a584277709b3bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000eb091007672a212dc4937b314576963d7561657cf1103820ce9bc34e4d46c24f4891a4a4ada648f8cdd2c30f670b86200000000000000000000000000000000166389a37e6e3c02317d68d54f29cc98d1d1df5853940555161d71df791cd92c483eaad87dc0e765b12408d6ac344f31000000000000000000000000000000000affd0d5734cbc27b192c0c0e464db48d3d76799d2c6a493b172127ef2df6ea18a33898828effeeaceb7a203e35ca41800000000000000000000000000000000155708b9756752c9b44048c91d71970fd2cf2a4cae6b0baec00629c81387c8261150e78f856093d81e816be6403f1ee91b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d",
+    "Expected": "0000000000000000000000000000000007ceeb14945414d96088a7900c1120ff182b2a93b09943c2fd1dc2b0b223f684b0d4c0b1b5803502582f2daf16d81d2d0000000000000000000000000000000008df450fb25534fdc456a8f41cc143a84729ccb082aaa2243c8f37e34a6670f5195750f8547444c49f7a898aa8567d980000000000000000000000000000000008c12d360078d5645b0e095c90d4fd37eb20f0ebbc6fa93fa5beda7e7c78eecc06e0d839268e2c303422ab1769402e0b0000000000000000000000000000000002bd594a21153d7c458b9f804050d05caf2d90bbf9d18def79eb8148b7f89e3a3ac21f84b87fd13c39df5b91cf73460d",
+    "Name": "matter_g2_multiexp_31",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc00000000000000000000000000000000090b834a587521729426d5b134c6058bf7999f4d4bcc0812e8d8b3ebb050961321b5e93356e87171a6f12160749394ee000000000000000000000000000000000cd5148c7eeac4aaea4288b38a02b5a901a6e2805e2b1695ed98ed86cfa0d259d87b65bf3cc9d00b8548100a60a371d200000000000000000000000000000000026db1079b85411dea0b9fca383956af50b938a465f35347605c01f3b72b297630ee2fb5252da20ee0d8ba5071974ed70000000000000000000000000000000012ae26c193e02d7ae4a7a01181551085dec9fbcac811c45d5cef19abf736ca2514e1259811970af5913891abe22a75ecf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda99438000000000000000000000000000000000ed5d8a609aa4f3c65a89b8dbc9334bd3cec6c7763bff298acd6c260e4d3bec0088e15c5d82618571d13b74a2031eff1000000000000000000000000000000000c28f92f018e6f822912b6eccfab37432ab0ab9acab751f848401791bd2f16e32ac6d97948bd8a0bed2ddc1917f0db3b0000000000000000000000000000000014083be2539d914883172cdc70950512dfe7be8893b1ecf085d837c2e9ba7f03656c5a0e15373e04d300869620eb66d00000000000000000000000000000000002561b77cc2658c54d29f8d1988dd7448f59c80c02ee9256404d8ef5536ee50104cbc11b6ee1ab9ccbf0ca55e53c52aae45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a6700000000000000000000000000000000000fff05aea33a9d1e8f7b227c80ae87c9e7589ba2804904b7d8386b24b0e5324e718f29531251969a972870a30c310630000000000000000000000000000000016ecb8f27a369df13e122c981e7ae37882b36d5492fccbc86d606aa1198f3e4ee7bb7ad0555e11949e6c1783d8f4cda100000000000000000000000000000000187f425b675cb12719a01ee3b78ea73d88f70805f72d6cabef6372ccb9d99008bdd7da54f155454c4c59f041deec86f800000000000000000000000000000000151c272d5cb67b3f801e103ee901deb4b3d3bef76ee4e1b2ce1b5e663ed292845ba012c732d38f9209f82e77f1f73cf354868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001791bd59815309f2aeb7b07df8afd89a288eb6f19c7e613f394353ac5398267e1388c97b17d83104446e57f94581a79c00000000000000000000000000000000154cc72ada5a9c99dea06ebec143a14271cf332b57c631725ab30e2d308d6b688ca08a79efb6fce632cb1216ac3d077e0000000000000000000000000000000012b4c6fe8c17274ef57539563a736c2f83c4cb473e9d075a976e18e193255057340f45de373c7d6e3fe5e08ad0dd97d20000000000000000000000000000000005aef16e11bd4e7787bd5ab4427276ecdf9c6c134b9fdb2ec39e87ae4a5b3b674b5ceee29bcdf804ebd7e83960d8d7ef7068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb0000000000000000000000000000000008e57f905fa202c7640500746b590791cf9d0f160a77e5eaa5a30280e513e8e801c4b6b04cc3f80d9403388571d180ba000000000000000000000000000000000da3c128ae234bc27824062832ac10aa9cd4978f37855a8b4cde3822f5b485fddb9a475a9805e795519d7f138a8199cf0000000000000000000000000000000000ec11b7e07710161fc557a56e04337f71aaa1a0f070cd84525965e53a1fe445c91ac07c618ec349997890ae893c165d000000000000000000000000000000000406b0eafbb8782d11f5dae2f6214282252af9ae9ebc5c17a81d4ddded40f05d0b534d14019bcb6cf4e49c4c182b90f00042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e000000000000000000000000000000000b1d456e66671dfa72ef3a56523eb939146226111fdbbeb697983928aebd5f50b0518db841a3d48912a7a780785c1f180000000000000000000000000000000007a15b2253496b78d270dd55b80bff90583a95283a89d40f6df71fadce56d103f0d365fe79256fa4f93b2d2bf4c06a2e0000000000000000000000000000000010829223166d38fd2c3041dd5643c9784da366a2ea8cbb3abdffb5fe43e975318c86de0ac9ec77c0126ee75bd209f7300000000000000000000000000000000004b124018e83e1e5e77bad42eb831798d450f8ff4a79c9b14f67f080047c491fbba45db79b2cf6015188f9fa6329e8be0a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da0000000000000000000000000000000011119be42b90c7857079a51695dd5be08e59374b0d1c7e12d0ffe870202e1f0c62bff84c9691679a82e610e788b7b5e1000000000000000000000000000000000c7a64524c5dd1bf10d16da7f15b39d05c9ee1620d4dcae79c60316a1f522b238e7934d1be897a441d0c8e621b67d44c0000000000000000000000000000000013045613a090d05d07310865d977c8e0bb1caa713b2249d6676e7cfd6f4e3ba8e667deabf9fdf7fd527685f7d251b178000000000000000000000000000000000dfee7f8259701b5726b6439a7ce77b92245499906502c7dfb384e29cafea61f3b1f21fcd7888231569ebf29d3035a61ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000006d77669207bb2d064824cb56fc786c631936d30db630be3c08e18d7e95b1c26e2d4e7b2eddc2f946fba6e99acb2198a00000000000000000000000000000000168bd8f291f8bcdf8b5e9fa915f7f24856a62803bbbeb9bc38384149008d4e3129338035061631f1fbaceeccfaeef4a700000000000000000000000000000000146bf2dedc262557dec2b4545c94a37434e20e4900b1693e8fa9bda9a94dbd07e0a3bee5f3bedfa42148791f4951db7500000000000000000000000000000000138467700fd5088c76af2f77fea4b746f98701fc0578571997b0ac2fc343354ddc8b2dc57d5298dd4daf767573d8bd3d5f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000001536da0df7c91687339fc93608eb404c5f46adf4b9122b99b1e5cee0012e27ddf30934d8f669bd39091f8673aa3b3c490000000000000000000000000000000002deaa8f9349e7c551e39751b1454a00f8f7896d63110e8e42607e8023ae3070c4abc9885ed54ee37a82f6e5c68451e900000000000000000000000000000000079a62eb17f7b07d4117956d3dab5d16a7f90e98948d5c3caa124fcf755c73f060a90d002cf880f5246a87342717b4dd0000000000000000000000000000000001246f0f3ec2af7c0250ae14cc67b5a1d42309f06c6f47b89178ff7534c47e8413a26a43f27454c0f946c66634563d41cc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed216500000000000000000000000000000000046e4a08785de985c66c7417f9262d363b9acee07e250999a4a7124f101ec4d82e3e4b2b0d9736471329fd61d0cff13b0000000000000000000000000000000017bf1e20ac181780ced62a18c78b378fc0dad157cf30d6026680560b681f5755183bd30b4e454764c08edb93297590b5000000000000000000000000000000000a57cbe93254bb0796eafc0a57330e38bfca37f8b94c4d21ba656e5616239e1e18ba6d632c0129d30291736fe37a4ac90000000000000000000000000000000007f31df7dbe9abe15f4024d8f6bed93c92ff5bfbd7835e08e870eb1bc4a6f62b3809b922c6d5a7350e2e5a978c80a67397b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba5916727000000000000000000000000000000001577abdf6e915c9c3b3fa50a4601709cd629397f2f91784528e4cdbb140065fc2a6ee3830983dcfd49a928e78cf530aa000000000000000000000000000000000d6f98df9e41009837cbb05bc3e3340d38e56a448fe396bd48acf03f061e7489d1402b36a84b3c56eb859437e9c406f1000000000000000000000000000000001912afae5361c3d8c6141755deeef26d1fadf6b0036b9d05b2e0c4d50f42328741f0423ac772fc66dbc922bd4a837ac40000000000000000000000000000000000616661f049b5c784ba05334b2931509e1e033bd203fe17f04cfe12e80e73eb7075beac9d379fc1c457bea1b6adf365752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000f98fed7e4d67a513c746d2fb188597a605165d5d299072aad6d621e077845f93804d575a5796bfa726f529dbd90e014000000000000000000000000000000000adb2d0b6c02e4e8fcab11c7c8819e87f73aab673ff9dbc5c50fee751bc7a6a8d386c8f9fa830b5545f94a73ce6e1f1f000000000000000000000000000000000f08e05ac40655cf59ee3ea9f10fc900315c6f06ffd3b80853560559f580ecdd65aba5ba660c729e0bb9576eee3703710000000000000000000000000000000009da46469f4b8fcd8d2b016e96f6e6582fb01c75407c36c7f87b4a1cd8f08ad06e962a0ec2138ed6fabaa1cb0115f97e2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee3000000000000000000000000000000000047b45ad2ad4f7b5b72194f98b98b2150b5d73a9df2aeb2377beed9a1275a882fa2d849037ddb56af632489f892a48a7000000000000000000000000000000000e1b0d9b52c0c5324067857ba4701f5f20eec165be418871fc0f0adbc3a0bbdce5a33277a33b79013109b81e006c621400000000000000000000000000000000179c471e01e340d8e6fc0f737ec09f0180bd2dd2a86d0817f753d1e9a9f8cb18178e9de68c596dc6a824e6c3c151d8b80000000000000000000000000000000019405c1e571a9b200ff2949aa74647dae59d92a8669d4876ba23f1b4a12a1f9412412503c68acbd619cae3ff056bd346f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db4458000000000000000000000000000000000ca8566b9bd088c471fd33fb7b1bf760ee12cc8b0cfa9ad92b45012cafef5c0772d9bd3bd9b266d6c3e3890c8f00057300000000000000000000000000000000055789839e786ecee7fb7d10f3876359fcc1bd6f2c5cf25c8337aff7fdeec9b43ffbe932cc4936bb708571a59e4339990000000000000000000000000000000013cf827bd57d8179d105f34c147665a072714ccbc114aa4e878d04ce66ca78bdabdc4867b3968c75dead147257197c6a0000000000000000000000000000000014a8dc5ac1858442ca627eaa194e1ba64091b5f9ace551338d770c92fb49ee12449dc200c8c35d70f9e0652b4d9b90da1c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc000000000000000000000000000000001124ea2b97a6d73c81387a51e814b9bdc951a773db2a32d50691be60f1d397cd4aadd9b06e4f49c32b12254e9f824fe80000000000000000000000000000000014cb365e9780feeeff3548f34a56548302ae0dc73402c40317fc819969ee9c4ea2a181381b94f82dd97a236671b456a000000000000000000000000000000000064b769c4b785d45472038aeeebd3ba9b28b3132d72023640ab2d7512cc6e31296c5330be5653ad6902e4e15e57e2c3e0000000000000000000000000000000014c7bfb1f142d69c17f73e23011aee0063a97a99d982d25ff72791a65c7a68941a80fc216cea8a49f3df2d0748b1f95db0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4",
+    "Expected": "000000000000000000000000000000000fb1227806c750e0eec0b865daaaf51afb74a88589d1c035c60dc1913f05c8ab18de24903ea876fda27b97a5eaa2fd7c0000000000000000000000000000000019903e1341f0285658164f9273b5c958060bf836264502b9dc298f75d4104d7a43b8d5dc0bb934a506ce1273ba839d830000000000000000000000000000000006e791347b54057195189e8b9f10fd42d170f37d455c0af5e92cc6a12e2c23990253be6855f4be6c84a708852c19a6f90000000000000000000000000000000005b72c361dca430fb2414b9d5a326cef8b77cfe5310153d6994dc1f8b9e74e8fbb43079e21956f428ed8aa48d6897e32",
+    "Name": "matter_g2_multiexp_32",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000052acff88605f33a0cf1201e8101c95ca0befd443c087265821a7d954917a026d41ab24d29bdfd972bb52ff4ad6de14c000000000000000000000000000000000e134b2aac3f6270e43afd994302426796b1e362031638fe0263c0ec212b828a30d8321af34ef7bf260652150cf2293b000000000000000000000000000000000d6628f675008099e9a75e1294803e86417ab22670d36316798680289ae61a26821693f2f9efc8468721a1097c3bceb20000000000000000000000000000000006d66ffad1a2e0f39488fd3f6e0214c9407528c8bfb8d1ebe6d75596a3e3cc844d00fdf46ce7ff6cd6d67732874a24a484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd000000000000000000000000000000001160bf0f7f2915cfc64e12a5a91b7e2aac78d4c2ce362e7677dd0e9c0172b37fd1b52222a13c65819b87593ee32a9ba6000000000000000000000000000000000c8be2cbbd302b31b1ab6dcbeb57b4ad428447bca9159fdfd007f5375218d121673a010f2c7fdf83fb45883458fb068e000000000000000000000000000000000363d3d492e6e6901756bac13b5c32d55aabbedde878115aa41b57d27b49a0f017a61fc90b13a20e009989374b82f5dd0000000000000000000000000000000009302fc26e6d750ff9441d7471903cc296b320128de71f86c4eacc80ce0725e8eea6acd2af056abde2f61e0a349f9bb5bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd1000000000000000000000000000000000b12aca17efc103cad500b3d773e43cb24df6be651963c0f30bca489f1dd7911ffc7204fcaa4511e161c6f75da4a5ff600000000000000000000000000000000179a36e9292d3f78a5fecbec1175f001bd4ac0ff3610f596aacdba29a12ea4844885a7c465e66d3883c7fc99d4a7e06a0000000000000000000000000000000016bfd0758b31f54f90eb8562bb693c45a92a297a3d302279c9e3cb8263efc0f31579a3af8e8f5a091d9a6a36776f445d00000000000000000000000000000000020f6c66fa554a5cb610ab05d194e7583e6798a113b8fff336c986f7358bb9fa6a7aab0b04be9b5c44a6fcfdd21999e83717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000004bf40c1d2d3574ad7fe128ee376364591b6f647f939b0b556ac3fdb5a92873f17c007e926b8a39a97c814728f355bfa000000000000000000000000000000000b8669e10e0a538a421b717287455620b82574b96ed09f64db444ec73a67a3227503e1b4fd6869314214071399eeae0b0000000000000000000000000000000006ddea4adb703d7205b6d2af436b41b4bde3a8c5dbed9dd161c9b3b466ebf06beced64fca25c3bbb97f232315daa5565000000000000000000000000000000000d97248a25ddf0ebd0200c6abbcac9ecd9775cfc5ec8da91634e77488bb592e5ff277a9607fe721990f403dd73f746e622788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d94000000000000000000000000000000000135bc4f28663a6d7d995f6b876ffb8e6ef1d2d0f232388aa5f390c57e8c48cb84d370ebc4bc267eae4466a019c9ed56e0000000000000000000000000000000008b6a9d13dd9d7014df6acb59f80b335a751fa2ba4dce63467aed18f68358f5cb743718112b3cb2d0b5add34bb6989000000000000000000000000000000000013a5389dba4da195f34fbe798b254403f0bc5632ed98bd6017ef24fff33640ae493c1bb7a77a0d3c97649230e455eb51000000000000000000000000000000000a69803a4cc237ddfebc51df2d90fa1ad03359f9635ac1646bc942546575d1558f5f2c3010f6e2207849ee697be41d093c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd384000000000000000000000000000000001939c2431f8ac4ab19d2735f122c0424af2ef18c0028e155611237e86648bf1d74fcba3008f5c6aa30feb5d4a14a3f3f00000000000000000000000000000000174473eedb54aafc522973244ec2feb3f7e95e50a1e996d1100c8da4fa59428c280f76e9e7364906662c4d2802235aa5000000000000000000000000000000001021d15f8ae2f62dfd3862944bf3be88d86d8113f4be22544ae5e925d450044279c5bfa1bfca44cd5934b42a27096b510000000000000000000000000000000015e0f20efae92e1fe8dea2222ce808a7de9e9e861c333db139f8ac11d7c4fa9ae6e49f51095f6e16bc738dc6d094b4cfccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a0000000000000000000000000000000012d70691721f5787ea2e2a652f9c65edaf763637f95c285a62d32dded18579b7257493e01eda19631d00ecdd4e27a9ff0000000000000000000000000000000014da9ef6076e646e7d5b52d1803d3a6d897664851c6de2a9b330e48695737e05f0369224c3eb357bf557625bb94e6ea2000000000000000000000000000000001554f68124a91be5b9f325394db23ed5db8f6c46eb46cb50e57947bae00819b151afbf4ab4949290ad41625499f42dc00000000000000000000000000000000009fc0d459e28cd1239d227e1d2f7d530b9d14ce5638cd308569300a791c997a51dd5a98aad703239a23cfe7cef7f47f6ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f000000000000000000000000000000000452580d6a37a07038ce3564a12c1c7391fdb002cf27a6df7e194b38f3c12a3026f2a8acfe5e634cf89140da256d0a420000000000000000000000000000000004b73c9a4f9d41b8b84e53de538e4b15198f50247e75c274c14f136d7d91dce4a62c5346bf11a105f035e29ccac3dbb70000000000000000000000000000000008a8a3b2705a82b551f8913853f682253e7f1f68c8e42f349337f4f1eaa5103f59430af0c4a124b6a739bf88298c5f6f0000000000000000000000000000000012f4220609899e8610809bb3a4da46e0688c285ba2e8750b4bf44a849cf15fbf5c016e8e8f9372239bb562e7f38916e921cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c910000000000000000000000000000000001884558e709635c046bd6ea8872bda936ba4d5ebcf7a0208cd0a4ee08b69f36dd2e136ce655ddfd89a5b1cf8e48f5ef7000000000000000000000000000000001357e2dd9fb603e5190d7b7ee105668bca2ed23ec6a248aa71aa430c2b2755747b8dfa3b147eb51ea644bf0354a61ba000000000000000000000000000000000009b0b0a76c6980e62e4893157b85f59345e1ac81e1aad1e48acec44c4803e2a9080f0d193fb799e0277ae6f1058839e0000000000000000000000000000000014c984ae4ef5d9d319fc89895f34a7db02747f57b206b0b30e8c9757d4b47419e6c0c8378fdec5aba364936a3b1922ca2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a050000000000000000000000000000000003e1bbb872db172a1fa615155f81aa75ee9760f8602e4135ef9f1640b7f9d54bda911a220d211dc4bb767bc2b5e6e23e0000000000000000000000000000000008464f23cf693b1d4545b6ce4aecdc8fd182cfb288c5ddb1f78ca578e9b16342c8178d886cbb6b8086c0fd1318c4ae09000000000000000000000000000000000af574c4d0fd86087e23daf6d9ce98765e1e445ef9152dbd68152fa4833ada0be440de4abfe7c07dbd4ee67f1a4aec9a000000000000000000000000000000000a8227b982f9286b03c4d49766687622206213d88cde007360df9b4ca5916c44ce44dbe6443577998b4b0d527d22593379cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000012e31070a501a7df7be43dc23e23dafa32ebfbc10ffb4c53f5d36bab2af69db5a05ad64b9ed116560e40b71f9217189b0000000000000000000000000000000011cbcd38ec3c6a6d49df6a8d6e1029a0412b42bd3fe8b42ed625adeb5a2f631e97bfad302de82ae34f715962b5ba0289000000000000000000000000000000001019b1b619fde9fb885d3c5f03a4373358107af7509754ce1ab2deb67df536d05e07ca7d60d927c15b549502750054f90000000000000000000000000000000018f1768b7140484105cf3ad2daa7c565e18eaba834db3f6bdfc9ee37445f2d6f7dc2b4c986b7efd5373224d2c92aa5a81973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e4000000000000000000000000000000001847b14146cfa2e1700f368f414b6a66ccaa02ca2a90b40a8e2be2ee4eb66af77ba563d7507de63362fb18426b6149610000000000000000000000000000000005c028d2b344ccb6400b53134bd179028b8774000ace89369bc655bb9dcd1643aaeec830407ee941df5432ba27987e8f000000000000000000000000000000000c4a680e2157dbdb53ae761209d505b4cf6b18fef5aff1c5009ab41295e0ce2ca23bd7a4f983fb9d085e1d0dbc75ffe40000000000000000000000000000000013c0cc77a5d771f1df99d1530e65ba782604c1ecf67d08572609de9f18405b9b817c2643226cdc7c9ad35beebf87dab0a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000009f32f2f83c21875963818872d243cc8c70b75234f53490eccffbf060cb3b9c53545c1c32025b271514f500b20b00ec10000000000000000000000000000000002491b571087a9e89dbdd039ccd2c37d5d8d25587495b2d7b0066e9dcca02d44b2c134b0128a9a1527396729f069df83000000000000000000000000000000000264e9c47f72b639597de8f26a42ca7d77324f8c0db705986fc3b40dfb46f47764b69c70037a68d76a5de49a278779a100000000000000000000000000000000090614b3bb302ed9fb78b8756524fb78d54a4390b27136087181342571f994b1a93faee28256d765a8ff4f448cc357c199ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b0000000000000000000000000000000012c716ddf17fca0d974e8d6003d99aa90f06b201fd141c74d8fdf1167030d14dc732917d3c6f736c68fbde9df50c098a0000000000000000000000000000000000261ef2b47de8e1576aecc6e19ececf80ddc1f4e28b2ff27953a65199f65a6211db7326632cfe04d543895c727ef8b600000000000000000000000000000000044fd6b9b4a1bacb8b7d4c53c106b025ae78f17c3baebbccca4e18cfbdbcbf8b3ef88ed5bd9bb36d9aea9e24f4117e760000000000000000000000000000000007721612515fd075811ee804314acec9d389900c7ef883e866f71fba00c49d5c4dcc7a2b8e2366f5a93f4577926ed171fdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e900000000000000000000000000000000122581659ab1712afc23c23c2986394de8e155bcf722e944ec05e7e42e05acc366d9a7abf2136b5dc68a8dcfd4a640bf000000000000000000000000000000000188842cf4ef54cf77c145acb685d3187cd9c842ba6705bfed846ace83dc4400c45120fc1d6a633ea879840d3d0c902f0000000000000000000000000000000005c8966862ed4458a753155ffe2c64655779860149641ee5511a46ec576798fdb5cd9521528df77bfebcdaae2f94b865000000000000000000000000000000000cc10d888d2b7a97666de99ac14a501b7e2171f074d30d947efd67d85226c312a7977cf923ddbc88c533f08a99f2045f786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f285d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6",
+    "Expected": "000000000000000000000000000000000c9be91da9bd8774f18efa3ae9248e4b03d11c49b377c372613b7e745882b2b23c49d518672e58eabd4d9b510a25d8fa0000000000000000000000000000000019687b9eaf5d68b0e795cd57055a74e44efb3e997cb038b7f1cbf08ca70e80a1655cdb04402c542a92ae4e435c22d0b90000000000000000000000000000000010aa1514402ce348d1d61b8d38b53017cd3977a84dc14445db64799cfe822b56a0adbfc5332093ce7ea1f0f438bf15590000000000000000000000000000000019ade30ba0faffcaede95aa272be042aef090f89d9ca25cb825846c4bf9e4c1dc575f8968c88ada51fac71f26fb01517",
+    "Name": "matter_g2_multiexp_33",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a1346771f8ba25fc44323d5290068e46b3f756de6d97aa934d511979a1486bc32173575639a7e54aea8eeb60f32a8c3000000000000000000000000000000001958ae7fce87db47a65a03402313b99f659ae02e8b62db3525d48dc9075aacc5e5abb50156e704701f3ceb18747e0431000000000000000000000000000000000f98778311e28b4081aa76a3f9546b94c29d86fe8e66b905265d74ee21928dc3ac463049f70d355d8caee5b59d65e07300000000000000000000000000000000185cc233ce72770ae26406476c1779858523e7c940d69adf2750695cb12440440686b6b918f4adb3b14aee9aceb6422119582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d37000000000000000000000000000000000ae2f565a44c8e07f2a136368798a44926cffd3c3a6d4c2fbf91763c20d2bd959271343b80eccec4d59a84394c7a3ce70000000000000000000000000000000009481a5fb276c938801133adf10dde3e7da2087d0bcecd3c9435b7de544271eb3b07a69efe7e168869e727868f24b0d90000000000000000000000000000000011774e0197866b1c8b3428d353d2c9f6326a77ab30d5595e2402a0486f03ca6ebb1e8dd335a60a772dfdd9a3dbdd3eeb0000000000000000000000000000000011ed2480d79f73a67a2adaa6da3ae4f1e1c28feaf0e4cb9aafac658901960129e40f6415ec80a31d72004899326f714bac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d1000000000000000000000000000000000a73b0d8c31af2deed481faec54095875639233bb09f31b1c7c745cb54778d1c8bd0a230e963ddd2ec8d178d31fc14740000000000000000000000000000000015a889b16be93d0b6dced01f5e2278ffde1cef0576d0b04b49996cc5252854f879e04b1ffeb90e222f4b9d5fa350767c000000000000000000000000000000000b53dc4d72e90330ffad17012bc7dd2e497cf8aa6ec73bf25c10427e23fd28137631249eabe9d0308c956dc7a9e92047000000000000000000000000000000000930cdc5d04ed2d1eb62937d9f72fdd733c07a5a0e392fd5216100216b1a2e3cde7053bf766f046cc470d92bebaf6290069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000131c4e590400b69b3657f7c67272b1e3491983997993ee87c94043001d78e605965abf3c1a8c8c39cc08d5a5ef05520000000000000000000000000000000000124f71c136dbb032504da910958e8a7949f1dc5c061f21d50e439e01e67919891633b3bb84fa8a54c69b632f78560ca70000000000000000000000000000000014a4b1a05f1060853f4294e669a20b91f939793a6eada6dbc84fda8ab11509b256d8b785b252a3795f1d2b99a51df05d000000000000000000000000000000000be2489f1f91d7adff356236859679c46b6bf8c1b375e8bc8bd1e97830b5ac223ffbbda60ecda168bacc2c0b90ed25d3be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c00000000000000000000000000000000111ebb796e8770d5a69e724a8d3ca62ef1f13778baf4ba12bf462211d35e325ff8e455c85237a73a3046e531f2e2255e0000000000000000000000000000000004308b76b06067e0a07bda143341220809b481b40b78edb2e24e83aa0f003d209198825b5fa9bfd92597e27a4054d3ee000000000000000000000000000000000de74485713f5c95653e98b96aeefb79b59911a610c2a848a807653c19d50394fdb52178947c779134d24b6d396ca36800000000000000000000000000000000069f47a71ad765591f6335b962e7c2d87b556801e1e6c25b449edc83432612fefd405c952397a704e9aa5a924769ad4e9b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000a3f89408ee43c0ba6a7c9c479327ebab426d430e3ff212c65da6364b16195619d27eee83d701a2ec50bd4b7acfaa06300000000000000000000000000000000092715831af983f740ca2c673e7c9c47727d64165c59fce19dc3fbbdd0b6a7be66288ea1f033ebb5ae2b38b3762edaee00000000000000000000000000000000071ca6fa9e546d4bce965b2bd0f0fb97e6833f05cedcf66d43ec88aba411dc4d6db9f1591de22f493f49a1dab1a2701e0000000000000000000000000000000018f89932ec032fc28775d34d588169a1435bf4ad7e2ee11c9d6934dae31324ddb96b3ef88f95d1bb2e52c3c8d9c01516d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f0000000000000000000000000000000011b5cc382164fc21c9a72cd85acf61c2a78d00a16a2dff938f0b36bfb3bb7075845a1616001ab53271a9a257a38312cc00000000000000000000000000000000139ba2f27e545d45027a0b11253532e28fa691170e08608472ce3b3f9a3e9398c5ee76953b1a1d01a5e79f194c32d1f5000000000000000000000000000000000d875f44829555cec695f3f4a28078b0a6f168bb0985793d003443b75a141936f3c7c633518890e0f7238416d46573cf000000000000000000000000000000000675420ed817ecd24bc5172d3e7df60ac4281b24ba91e8b5ca8bd6a8321f5c7312a6ba043fbcdc467c8a5c957590a692cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c400000000000000000000000000000000107bde844286cd3958cc7a1314127322251699b51d8af8e6b57495497f21a84e05612b1569b54fc5639a75e9f9deef750000000000000000000000000000000002355b1a60e24e4879448437d2c1b12e58f02d7eba88583e96e9634f7e2c8c6886132ef0488918f665ae3f7b6977c7c4000000000000000000000000000000000fed531e437b70bc4a19ad63c61ccaab49afc50fad1f156b1c8ecba0e1b703f8aea61882c6327d4d8fdd072df9c4e73500000000000000000000000000000000182177409579ad53786539514753c696c8757b8c4d9b8360392f24b591e43ec20e84c0abe468061a9e5e879c5c81314217f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b40000000000000000000000000000000001fb25395089228772d6000025cb0356eb510c964bf7d0c12d47a6608fc18cc448e44880eb5ba8475cbe6418fc9d8fee000000000000000000000000000000000f3b9de9980e5afaebc59c56e02fd75fdad13013842ac035f8d5569a46cc67f0cee461a939aa5a3d8fec2966294207930000000000000000000000000000000009a223ac0edb164845eb8397e0cae4363fb2c8c996c3c5d722cb50be56cc3789c732763cfd4b61470886dc991be39f57000000000000000000000000000000001909f17b229eb351dfe8317a8273d846edf14ad5ee0ebe8cc2b595ebfed19b73983035e19ebaee3d05b1dea35968586961ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb500000000000000000000000000000000081fa9eb8ca7d9db52380e4c408e6d5d668471bafbafd62ba9023fa08f6d300a45295b583677824c29ddc3254439cadc000000000000000000000000000000000e2e613043b1566674f791dca9d860a49a75dfa24dce3fe18f544a9b24ec5266a64e77386b672c93fc4d079eb8e76a01000000000000000000000000000000000f471b86ac5783d720e7d73e8871474c8665e8a109aba27c1172ca24217eefb0f66c53232df1672dc0af6ddf9640e10d0000000000000000000000000000000010667cb22a6a818fa7c729e40a7e70e1f31b0ecd568b54a4d352d5c9df8cf1072ebf2ef1e612efd96bddcbeedd8566430fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be000000000000000000000000000000000a0ed87b01f27f26380c6285e82bf2f12ef3016c7e7f3a13041d465825664573db47be6cf099cea615e21f6a5d759b6a0000000000000000000000000000000007afb2a1bd50fa0fd3174d70f1c8d5c229627a496bc9bb89d4f52d47b1862e14d704dddd80045e58d00336e898a996eb000000000000000000000000000000001698f30f824ee5cb71b3f2451953c371987433d2eda570f2a13262ff9e5e529e316b06ef6aadffc152803b076f22db9f0000000000000000000000000000000009eb1d5f3da7cfe9b40a70e1b3c3dae36436e8d068a79dcaa283905614676645c99a5a165630ad46b70bd6be8b1f21a8d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc40000000000000000000000000000000005c4390b8f37cc3fb9f248470b505a5d9502d44e4a4459d1f56452cd9aec89d114f1402fa45935930fa00888a4860a9900000000000000000000000000000000163b0ca84b5cca4f124bfb5a13a4a3efa677a84dc89b6a61e69d0aad34fade528614e549a7b2326d1f6016bd0d35465a000000000000000000000000000000000bf450dc8af483a9f993a29cb47d5362c9f5ef38afc2fba8040e14514eb834fec6520a413fce5868aa9a2c7c3ff6617a000000000000000000000000000000001063619f384102949fa1f8353f0aaa5031234d736c54103df6ef6fcd0df02a19c3aef471f0413a1e19febed6395459a0c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e35db3017963d3a9d62b7e7fbfa13ce4f5fb46a90c1285ddc0fa481d9379b95a77e8cdd4aab5c33059bfcdcd82473fb0000000000000000000000000000000004fa27c663c8d21f041d15cb199d31cfcb96a56cd673b730dd111bf03cd954cc33799456674ed4d58e8e0dfa826a6b26000000000000000000000000000000000e0df4e7f943db5b5c27bafc7e1ce099b2caa64642bcd6336ef926352682fbe81a1945b266cba7eab52b16f4aa63eb8500000000000000000000000000000000020167756b8c68f535c4691b1249ca1ccf0a539f7274623ada824d0ba789ef44ebb20ec1ba51d46c0a42da78653d287e26cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f00000000000000000000000000000000178037c6b5fd1c6c396d8aaadb712863557feb744d2cb9165ae5c36376d2c066f7b1648e083f81c2c96da6562e0b3c20000000000000000000000000000000000b805b4e1cd5d45d8b6ed9d4f604ac0b40f336b8123f7281df43a6e803f8688bd8087fc4d5fbae695d06efb0fa35e18400000000000000000000000000000000000a947562dde45f613ee1d15614940a2edfc770d733a60374f8e9188675d4cf973a5c1081c11fe5a1d93bbe85e6f47800000000000000000000000000000000059473d80c82c6ca06b4aa71d072f4751b3b053b53ffcfb4a84906ddfc36ec5918668a62f07054af1b241bdd4485edba699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000008b9d0916a9f5689b8fdac84bec3a49d0224dbadca6329ecc156da633e1332bcc6735ca3ecb228c22032dcb7b2f372d3000000000000000000000000000000000cac0c264add10bdc1217384a7379f65b93cf822418f7e4e2b48eeac45f068a61f805cedfb1665dda06e04cb726d245c000000000000000000000000000000001578e98a40a64da59154b1c3d757d8f1f8cdc500482c7b7d65b9997576f745442fbac654c19331977bd210df440372970000000000000000000000000000000015ef69f82e85c81d28893d94927068f14c6516eb7d09898d5d055cbb7a9b55c6d7f686f067ab164160e6d6a8f91ea19d40e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf95230000000000000000000000000000000000a6a27b498285085139b8dd0c37b700997134337e696c84b5e0cf70ea3991cfb40ca3a3098a3b3a2fa31e91aac78eb2000000000000000000000000000000000bbd7ebf4301c5eabd4f448b89f1b227415cede3247a1c8dc56a02247efaa99dc78cf370f644ffc06cd2158fa25197dc0000000000000000000000000000000004535a402540474d53c084d4fb6d9e12dba6716ee13286ed758aedc1ef911b55c572640180a54cbc084ff57ceae8a4b4000000000000000000000000000000000759de2a9e0f3c04b4f629a682dbcadb2140e5b935845cb55bd267e230e08c6e8cc5426057473aa03ea2196203bbf6dc062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d",
+    "Expected": "00000000000000000000000000000000134c29cc5c33c10f04b6c09b5db71b10304028d06ad6acd4f4b39b16823288085a84a0380a1549f04b3dc692cb8216d3000000000000000000000000000000000a0a9379d63527ab9b5f9c00be4acd54e5fd683a0a2f37c85ba570171c705eaadfb0f4e4be1a8836c9de86dff46138300000000000000000000000000000000006ce78f135dda5af34a0e069d7ef13fd589cec5a6128512bdae7f45f28b09c6e4b3cf638628c9f4783097cc00082aeea00000000000000000000000000000000141e710ce7a979dd1772150d0cb2d5b269d5cda50d1bf7bd0cd827b24f9cd8c1e2775f495cfec0428519627b7fede464",
+    "Name": "matter_g2_multiexp_34",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018aba8353cc470b287a163fdb9b8b4cc46071543ee8261f928a7b856287946637d9b36b728a54e1df5f185a47f1556060000000000000000000000000000000001129541b2e3b2e1a553995b603dc3eee44a5ea440e687739ee9e1339dd79bd96c67231ac753d483e0ca96b27054997b000000000000000000000000000000000e1cdf3591aadeb56dbd80890ff7d5639a64847cec771a19c769df7da732a6d3179d3a89ce0052bd7c982af0304408120000000000000000000000000000000000f5f5f0ebfd2b632e15381ccbabfa88eb774f2c61801381ca73e6970965ecd54f5f3a9af7c152186af8fb75ffb5bc25764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff2000000000000000000000000000000000e527e40c311edc5dcdbb4d0b70497eaee14533aa8ec57dc7cbd7d839fe6c6ae62b1fd0be2346a038de687d5cf5394d60000000000000000000000000000000005f9fc63027dbee5e0d55cd6c57daf5df7af0d138393a2dcdc71ef9aeeb204ea347f7d574e71f2ffdd37d8f05dc7979f000000000000000000000000000000000f8788034c9f1c9c2018a52326c046cdba8997199732152963819b663c6e58e9d6a0065289e2e25a97ce5627505900f20000000000000000000000000000000002a747bb3bcccdc6ea0af1bf1d0ce55de3f41b93060361b30c76063346b606322a76ed7eb260219c83aea0806ac7d8583280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000002148c043e065132e978e89f018a5b728d278c95c9cd1a6f276bd13f0cb708422a62fa22f7b377adf33055fcb09a6a8100000000000000000000000000000000024c4c721a0574e53118bdcc3fd41f73176bc8264d2ff39673210525166bb3513016b5c9af67a47a7032b74a62effcef000000000000000000000000000000000797dfa8cad94896916b7d55fbbb3eb0eeb74f70231205388d0dda69dd8abb436c22addd22c1e3689093739af957b65200000000000000000000000000000000010dd2ea2d45528de8bf1b5c5dc3267fe8951e48ff5987e67ec52d58635521cf1905f1688894e3e23a659764880b2301d4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb11177000000000000000000000000000000000eecc0a4edd3cc3f70d3e0e43ba56b04cfb3f1ac23c657048a94318e622217572b0f929c73f545d6f5f5613920c0580200000000000000000000000000000000137a098ea8d3aed32c197a2d244a2e18753045b55cfe16874f79c728c664b7f23b10476f20dfffb2f80417c26dff4f860000000000000000000000000000000004a7789b02d7d95a2ce0c7bac39d5b057509200393450a47fd9d087a353f866921aa11185550537b98f3073650d9a1370000000000000000000000000000000006ed63730bae06403baf705da0e30c6c00739799eea4a312d06b8d7dc35cb43a4f1e941a69e55ddd7ab8ce42d8629fdcc66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000ee7ddbf43f17f722dae84d34d26add8c1d732918b8c75c6b295f2f584075cea0c655911410b32c06868c1acf36aeaaf0000000000000000000000000000000018775682555d9f5a576cf9462170910bcdd083671ae2e4c8c6fa99a702548f1ce9afe90e681b00d194322b1a2a3be7ef000000000000000000000000000000000f3935bbbf58b91fe8176f3e25ad3fdeffdd6b369ae70b704d4e54d4fe32fe5987e73aa5aa975e958497340274577cf3000000000000000000000000000000000c088bc439d638d86aba6bb1e6e9f7540ac2da3b96080aed455edd1fbabfc141e26f125cc3a9cf72070a24f298dcc3ef4b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b8450000000000000000000000000000000018d8a47d1a13b9b8fb5a1625f9616ba120d5c677bcc996f694b7e15d251fc4bc938b0a7cb5b70f22b8e9f5b416c513210000000000000000000000000000000003d0646458bbee7ccab27f858b8ab0af0cf583da12a40ca5a954d7eaf97c897d379129a63d8131036f29c30c6e644149000000000000000000000000000000000d5466b50975c5a2dad96e4e24339eafc8c85c2497a6f19e12d96603596498654cabea6995a92c91b8319ce06f18d56d00000000000000000000000000000000191a96d62139f8219b9e4369a783400d045d72ab2dd83fd229e08a4ca73de59a11a5add86c739cb3bab4adc5e9f79685708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a00000000000000000000000000000000032eb1f7846b563e98fca0cd44ede4909b6e16a893f5ef01eaccbd7d8aa11710606bbbd0ee6480f7cdbdc9ffe66c3a9c000000000000000000000000000000000c31bb6fb537cfcbffe815d86ebfae1f5053ceb756818ede8a58cd84cb34d0eacc70ab9095f9db1691e4fb4bb816d570000000000000000000000000000000000a8fa1dc2f28277a4bf8fd9665d4b5c3baf1352d89890d4af94a3657cdac7fd72558da1e65cbc5bfac142f0e817be74f0000000000000000000000000000000005ff65c22ff0abfb33518791823c5f2202ea5f7258c0a507ab84460335ffc2cc8d7c7f670752a7647d6a6487ca0c9adb85ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000190379b7629f74bfb88096dc9ffcdecebae0d653410f032a35a811a09022679c9be19f3790af95c3205a396819e068e1000000000000000000000000000000000b6f114fc277ae8f0b5374dd349985bf955dff7fcb0095e0e1e137fb539814be78c924074bbab54f29dfb42f3e7df24a0000000000000000000000000000000002d86b0507c147142d03d3461bfea4c3af7e57a6edbb372387de24a27cfe27c44ee4b9571325a1b3f5e83eef450f2fc6000000000000000000000000000000000ac3b226d5e13c36c3a8ef0c8896d9af55bcc0cb67ac1cee57a5c6519617ec77af9af60ed44e0a8284a2d59816ebf848fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee3147800000000000000000000000000000000079cd4511e953e4d1b3f4f3bbbc66a62772018e809779fa39aaeeffac737cda9a6116293848f967577f03017f33231d2000000000000000000000000000000000ce3cf48be423a2fc0188b94f2a22579872e9ba140798e560ad107f63ab2b8c601831f89d06a4bb8e7a758cf836ddfb3000000000000000000000000000000000a6a90f735f215a79216fc4e7daffbf74775f38824952af72ac38c38a77a277483e34bc95031679494d76f109c0aecc4000000000000000000000000000000000d55fbce780d885cf817cd2126e7acf115ae9c72843af23c376f3a5d4307d1eefaa0f4691e7c09b5da1707aeaa5b675af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf200000000000000000000000000000000008924efbdb46b9324bfb79b922ba8b7d83f5e5e4b3b736105e5339805838171801bcf17208f3dfe5c7475d4e45b6ad970000000000000000000000000000000007bc0096fd23f0c93f0dde8a3974ea3105574e031202f6316d5940c85164c6d6bb5b86078a0c68dc822c0fd1b3dc8cc10000000000000000000000000000000017276b3208b347388a5657b10e3c8e4a187b376e42352f76ee3ee88873217b6b8185022c93097cc116abdecf3cc64467000000000000000000000000000000001915ff932acbdeb52f07b664bcc47c3a5b096c6cec32da4d7044326dfe84358e49539fe50782538a901b99428446b0f50ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000009e295d229b543a17db1cc85c846111b7097bd169d19b410de78f8da9684e664922eae77c64b0db430aeb422016cfe7d000000000000000000000000000000000e29aab30a1da56b8590e9df67171cc1b9c847696b51147cc57ed6c3b55819cfa0992c67e15e4ca6de2573c9e16231c10000000000000000000000000000000007cc9990c6722645e320dd16a4be8adaab41f958f769ba0d22e235549a7457778cb9b14aa6ea5caa9e0bd43f8d04cacc000000000000000000000000000000000b2dab5cf37ae8e76b71dd8748c86e8823142792445fa0b140de31957d35bb7267e3d94e0dc92f4342d9f8560c5d9d86dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d09953900000000000000000000000000000000128e2aa795f8479da3ea2a4efd12aa90a6fb019d4da89fd372e6848ff7ee17da689d766c9e49c88c962eb4f682c56fff0000000000000000000000000000000000fd68bb80d6b2200297aacae1174275f864669e962d85c9105032d7a352fea548e9fa0629a6749c789fa0827a40190700000000000000000000000000000000175bc3918dcc972fb728f1d8cc30ce9887efc6e0b254d8d22af87f95cd4182129d494c43d11b028c4b9849f5520a4fc00000000000000000000000000000000007c5363f507a01c0b6935fee0413345bceaf1336cdd20f69060bdba2e411521a61a549e6159b2e006ffa16e3bd77e998e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f90000000000000000000000000000000000efcd782b89fee74ebc037160c6653ccc104260b5f8989545b40d51ead6ad6ce6252e1232281c813e3c883af86e68ca000000000000000000000000000000000b68ed21f76ce131c089dc454dc48ef948cc7c6d5fd87d647db954c9eeab2f7f76ccc51a1cff8612e89bceed16ca03ba000000000000000000000000000000000cd776670d5171610046fa294fecefb42f9bb4d71baed4af65a09018b09ad9341789abc23c9feb85adf96b4203b0c0a0000000000000000000000000000000000ec4ca0091a28b73c9adbe7120f2bf1a84a62ebba1e86b1948389b1a1966c1de4c632a5e245ba634b53cb932f5847f6ecc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000022f319bb5167c2b945a69a438f712df8975a0e262438ea687e2b0d824e2d1d14bff1065f50fd6ae92494f6f3aa9472b00000000000000000000000000000000198ce9e4ddb6b423788dbea82d75513f43cb43ecf1b27c8788f041248f01808644f60fd823e5862cd7afb4f7e8b6b6a100000000000000000000000000000000119dc1be1bbb7e678319db73055ccb88ef7efcf6119f8a9c43c69247ff264879a627f653a10a950e0dbe69155ebca4f1000000000000000000000000000000000692a0ef5a75d42524e3fd52ae073b0f2ddf6378f18a5dcef05af4868a899b93c7f1d2691883e5c85f97052ef1f4177d09a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000001423d1707e49d2215f639df75ee0e13bc724efc7d099259179260ba0f17157c4efc4276844bfdc46c61ac2185f64beca0000000000000000000000000000000019ad06d215d3c819311938f89609ea7cc63fadaa11bcc86cf5f26370a966eaed1aca312c18176674b5aaca3ed8ca876e0000000000000000000000000000000013bf3f13e87f3ce29f0524094e2ab8e39679566add32e779256006dc92ce09f60d5bb9cf0452b90ece71a5f6981d77f300000000000000000000000000000000112e4901efca14686c30a883ecdafdc389303f4cf46345e229885c76d900b0aa084a957076009ce22ee36d4e285d410c9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12",
+    "Expected": "0000000000000000000000000000000016d1fce53fc4cf40acb0347c0983dda46383e4828c16985459ac89a2ce8d3c2a26cd9acfaa2ec899cc63b4c6bc351f560000000000000000000000000000000019c9626363b511a79f297dc79c5a3b7a2e5127fe49a2fac5bc43a4376f170404f044f9f84b82cd09a306012fc81e3bdb00000000000000000000000000000000062e324f3d7c5bd39808b762a5b009cb30bec14a9591477959339bf2de9ef27eb42a0eddb95aa5fdca9bb9d89b278cc20000000000000000000000000000000000f05225a4d3bf910b0ac0103594a90684ffc0c09e2c21744032e30470d5727be3c27621dc2377e9845ad78be67b856a",
+    "Name": "matter_g2_multiexp_35",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c64577b78ff306186dc44131cf8bd946a68e65502c4af9613154a7e3ffea4fe4e59cac4a500894b470a74e66db1834e000000000000000000000000000000000b4311c295bd30174f17b9ab3544f1496b9655302a4b6026a113b1aca31b555ce7b2d812bf8fafb6b075f67cdc59b77f0000000000000000000000000000000012d7dc3db10ae6b4e3e99c655aadb71124a0bdcfa6e30ec13c7c977d39f83aba4979a1f45881813a3a68e149a40120a90000000000000000000000000000000001b958c47cfecd619c05a2c54315585d15fe377beff889602ecba6ea3b29d51f6480f09a0a8490e4c754045f0bfdc3eb7b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d00000000000000000000000000000000010db66653f2ca73e55d85254f62e8b558584d546913e4b8f530968088d90cd5c4bc164fdb77325fe0bb2fb1a5683896000000000000000000000000000000000a1af9bf84f0b351c8e8c082a79c7ccae768bd2ed40eede38666aab1901a6eab0cff258f440c8c8eb4854a824b9aab4b000000000000000000000000000000000444fa654afb57f1b01d10be08a330f23667af58e3a40c630a2e01c1809125b3ff8f76e8a39418688a023293ff1a45e90000000000000000000000000000000002ebb117ea107a3598b888dcbd5918dd0ca307b21d65843c6c28b3efab89d0e276e5d92283bbb345b674d4a300595425c6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b48000000000000000000000000000000000105818a11efaeab4801d4fa7558b09bd56294be7f58e3e54fab1f4f7c243ceaf08c99a14c3128ccfd866b6f93caf989800000000000000000000000000000000091ca092e5f83a04e872e621e71d54dd6886d088718f60ed5b62d3e4c165d3ff2cea1e50fcb94fff251df0f5ee453cfc000000000000000000000000000000000b42051a1ef52f87536b9bca459fa7070ca17bf825950b13b3bbe9db183ef722f62af6c05c174c65d59b722e1d2f5b0e0000000000000000000000000000000002fdb4a5480418e43aea28e5041af6ad55a6c226e1eea1a0449a99b5a937375285feecabea95c2652da5113dc17d8ef4410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d000000000000000000000000000000000d8f93b589678d4e93bdf8da7834bc8edab648ead731b7f5f0cc299488f07262877ee9bb1174ccc106204dcd3f1f416800000000000000000000000000000000160f740ffca48d3a10c43e591cf46c129507f10e65d76a031fded2930d6c2dca4c79d7813f63e4ff71aee09d672173680000000000000000000000000000000013c768a4889315faa3976c8e43b4d466ea594bd94773f270a198f2571ba9662d10435d1e541a492055c333eece9bb29a0000000000000000000000000000000003dcbcc9e6a0cd5741d77da88fbbc269202e8f944a5df5dc4f9145758654934d5e1eedd596325080382458961ed3d21ed97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b3000000000000000000000000000000001344fb37c1d7dcab01a4bf6fa50c6bb7606f7db22b85a3888ffcc2e9f229f196881cd7c82160730727e49b9e6fea04320000000000000000000000000000000010c7b15a6355d3152eaada7a606031f28809f278a1d0e04d264b563185ac7d9e351295191a6a90ffc9c6dd33995265db0000000000000000000000000000000014438086226a061a1bd557dac24d9333e14cdfa3a7bb00ded4a450e8889a3028b174bf38ae1347e6aad19ebc1cf5ff7800000000000000000000000000000000105165703c4592cc4f1f489d78426a56434dc77327c13221b582dc25306f4c5bfe596f3e47abcb741ab553fa14cad374bdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb36000000000000000000000000000000000ad8b55a198a5e788bb54c32112761ccba9863cba16d95ec9e30181376e7eccaa2741660f2c5f708300be058e566ae27000000000000000000000000000000000b9bbca7db413964d2ec113cdee2d7a7bcdb712d285655f6b2897dcac61456ba4d08e25e8c28627231824829bd7d13800000000000000000000000000000000001ae49c10675256651e3e038a2150d85993fa6f2a97b9bc02c693ed97ad52af34015176258b3b2546b81010a4381d85c000000000000000000000000000000000c8f9668a0a497420acff5207a07cf780e0b2ba78083eb0ed8eef76beea996210541bae2e64d825000f307d54cbe3b2b43da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000012990a66c132a31d60d182f729b52d9b57d9d1eb1988b6f0b4d9fa2847f26983078ef9bbfd0e888e14bf7d1f92d09e54000000000000000000000000000000000585215ffc2673a197bf9cc6c6424547886abc6ef5c6edfeab2ef0c42034a4a458fc7155c35c84a8e9e9d89fbd4aa25c00000000000000000000000000000000118fb4fe0d3498dd2b55e62272e95a1203f9fd22314921d3e044f1b162376aaa7e8154a4e2184b66451aba98729330c0000000000000000000000000000000000364b9032ab9cd9f599979c8a93acbdb831707f1f84fdc92844b58bc7e4d72472ca5b09c51b1b04271ed9f0e391552463c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b6805893000000000000000000000000000000000ddf9e4e302169e195f4f88fed06e0c93fd1b542abbfeea5da5d47c662ad9a16b8f4aed7874354fb9008d073920e1e7e000000000000000000000000000000000043fd1a4b781f25e8747ecb3eec45ce90760e0d5dd701e8193a7e726684ccb8ff21f0219ba15e9e777d339a3d87a1ee000000000000000000000000000000001117d2ca429048056084e4847c7169b4c8ddaefe1d48079949f9f14e4d74f0e0b38a95d0f17389f61da7b2a6d8cabd1c0000000000000000000000000000000007adfc7d87b1c533b4439f6b30d38c694b65b3b321f3eec13cd7d923f1d5f117005be6c3ea901a9479d79fc553e34e6c51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000115a81aebee0329b174c01458f8714b13ea3fb2dbfb051b27b29b940652f27e01a84e522626d12be80da7e1039e2baf6000000000000000000000000000000000d9e37d2e5e7160db30acf5593d1c282541a0d4ac0482f0759fef8704b9ec3ab1e3ed248e37c6be285e890ef1a520d0b000000000000000000000000000000000c198a22c2f590df2902c8dc2bb1ee427b33e9687767666140f9d3b51d73fef18a259d43d86fb3559b1ab0abacf547a70000000000000000000000000000000017e705af54ab76145a79e747167a4fec6ec3a16f3ceef86b1ddd1be144e616ea7d686bbccbd1c5c258e4546405be023d6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b0000000000000000000000000000000007c4c31287ae0b3bb90475f84abdda36610f887aae311d8e97bf97bbdbdfb11d38c7de331cc9dd022926678e5180c0770000000000000000000000000000000017f4afe28adc4b24d16b9cd97aacd171c2104b13b079c643d664a7c924151a401c352832c4967c0e5cecec5f1d1dae290000000000000000000000000000000005a8aa8a3a91461e0ba256e44af56875f8d07e24628e738ffc057249d8380417884f40c84e76dc6ce5816ffc05c0d686000000000000000000000000000000000f84bb7385a6936b519e881a708541570a31a9d7897ab8b348a350adb0d30522567fb917c9b6db661b6f53f98b5e68aaf4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb1000000000000000000000000000000000a8726ea352582ed52ab4e440102963891f059cf5a3f4901615733ad560a99930efd8692f3c30256d58e5cfc4f0803bf0000000000000000000000000000000016a623dfeae872639d99e3b8209748642f20af796325630596b6ab6146783bd4f2247c7ae38d53ba9a3fc0cdd6f29087000000000000000000000000000000000e40709656e569e4fe90eb85d8761c6ce44a4272793ccb7635ce75d67d97464e8dcd8c32bd4ac9a36fcce706006915b20000000000000000000000000000000019e64802756896206a9600f2d888f3d307ebf958492b1b5153c06a11231e2d7d2f1786368447f380093a29117cc25da9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000019c7409cda6084edc6e559da9b361c96cf207f1e2cd63cabc9b86c5bcb07a59b43e9c6ae3e90a61c872f168ab89cb2c9000000000000000000000000000000001101bb63a452b766a085fb788937f6b751417dd8d905ee50ab5bf96cdbb9d7b68c1735460a71eaf9e9bf662734f495c20000000000000000000000000000000014a103871fe523cd01053a992eb9884ce83c6023bd6a8c2cd9ca60b8780118c88502c6980904f2d2bf9ccc9fb597d535000000000000000000000000000000001929f25d52ee6b9a44333237c732a63ce2abc80c5510bd67faad1d7adac96eac5449823f3a52ed18bb90b93d9640d0d1283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e50000000000000000000000000000000015b0a46692f57ccd2b7f53040dd75f30af0196aa3c5499049eb172b4d927f96a59c42a129117d6162a1bb31d2e8734a4000000000000000000000000000000001366dde2d9070a2c057744fffe78effdc328b122e356a6aadb10c3fd2e8badc0ff70bc6d18293b3c52428e2ba78766600000000000000000000000000000000016fd48b067b949ed75bae3e4db29b5785bf672bd01032a925d653f8a605998e1eff6c77ec39dcfccd417f1e0a9defa820000000000000000000000000000000004cf22bd706dbb1cf8b97187ed97636380871402b3ba9de58f174bf50a7a0b528749762c3f55f5f835a276e43b46e669d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b0000000000000000000000000000000000ff424ae9372af46de34210bb0bd670eb173bd49076df5caca4bc4293e742121267a20506f931a4ae77cc36fcbc8df4d0000000000000000000000000000000015a6815b47966fb84aad5de62e6d4280f9135e129f33fd01e667f4d6e1bf7204317fa7741f3cff3682e251437927131c000000000000000000000000000000000639dca43483b79ba8043130e508e91fe3f43bc362fd1dbb135a2eb8f3b94d5cc4af70f1101c790545a0eaf2408706e1000000000000000000000000000000000045f0a04a642bb6e4db34fbffc8adb19a24648554f36ca371fb1a851384a4516a57f1850f7d6be59ff67029ec4002de310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a99000000000000000000000000000000000e796fd500cb1a25b834baf7335641f34ccf04ccf60f82367f0e5c8c7fce8e3030e7b916752bac8e3adc01cbf4b319ac00000000000000000000000000000000142e8bbac9cae69ba3dca48aec045e0c4d7028f73c254433f921b7240761c661cf8e774a21da249f7758234cf7607fbe00000000000000000000000000000000045a3d80767d116e89bab0e9de812ffe7ffdbc41b61f5f17ad16be5bdc9968e34f46b937c5f94f8197e21b358f44b5240000000000000000000000000000000006978b93018bfdbaef0d40f1278e831a1fc50b44fff39b7c93820a284d90b699981b1f422f751a33094ae7b5cedbbb2691b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab98500000000000000000000000000000000003c3561f5d255cf1f83cb5f4df8e3b8d5655d965826d56867ae66da631f8e7d489f733f5824c36652ab00586d9c593be0000000000000000000000000000000010b3adb0017e2cea1b71680ca33aee368429880759660dce2d3cdf57b6cd7339bd8853e5efafb9a5aec3f7e22da676c2000000000000000000000000000000000cdf976e4c65edb79ff15178f6ec5bf0a77a30d97b799e433f216a2fe3eedb10bc6ecbee2974167128773cff43f1922c000000000000000000000000000000001599b60ee70d927849764880830b2e7355daf95eefef39ef61569a2b83b2bcced4dfb28047a1e5350cc87ef3cd5cf1d93e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8",
+    "Expected": "00000000000000000000000000000000123af49ac2981e14a490a4a6792f21343497b562266a47487cf6c650769c810852e357445bc68f3460e7822e8cd1e3f000000000000000000000000000000000143e79853e4bf6d031e1116dac2c6eca4991b8a1f822fac1d352d2cf1b88df239d82df886f0b24b3e7f305286cc1257e000000000000000000000000000000000b621056a9de2d83c946b1e2c7a07f9beb8e053202407323e412c0d8f60123cfd5818067f57034fe1b1b5a3d1bb285a50000000000000000000000000000000001642fdff2c52d58d38201cf44c31e00df47ea1439e9896b5ac5e9372482f4ffcc698be46c2d375d57a72fc677a9fc8f",
+    "Name": "matter_g2_multiexp_36",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011f78204afa05c3717d3908dc7e4356ef96c426ef380b6abba3a5616d9ee01addeec3369967ed42e030c4b8ff9939c4e00000000000000000000000000000000175a19c86e7eff0f4e809a5105503ed223fe327ee4617f7f51257426fe408373899f39014821292a75e4cc4eb9f7f31e00000000000000000000000000000000052130dd3cd17840385db424802d869d7eac781365be25ba401b7b0e4025353c8dbf59e5997b5aac74c252192061299d000000000000000000000000000000000457f4fc7ac5d7d4fa07e8ed125df4c4e950e6ea926be9c04b6df3c3699a10e99af7ea8546b8ac70c3003468a75c821ca2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000005182a3af2b52102e09214d048a1a29d1e10b7ace6afbc4e6b1ebf16790be372dfb6d65cb8fe08c3dbbed8c5435a59a3000000000000000000000000000000000f2a1261463c09a88edac443ec3cea8aaa19659e8b7ec2e8a403dcffb1e50ebb3d07217a9ab057d8d097c075609c13900000000000000000000000000000000007d6525fea8fbb685fcf89bd772d48c406aff7377429fb199f27c3c3337f11f8e24c4d81c9026b469600d11e8cce51be0000000000000000000000000000000004b6d6102debaec16c34fecfeb444e7ba573b13b83ec375f14d2c541a0d1fa528ed6599a0eff4f8ca527c5baa579f762fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af000000000000000000000000000000000ba1f2b2c3f1c57afa0ab647d32af5d036ef18069a4abc9795dd9927ea274a718b67373230e337cb9374ef73b5e2303900000000000000000000000000000000016458ff2f5d600af9d2983f535c965a2a8aee48c0d95095bee642bb7bbab8bc87e3e7c3b52a787c53f0d1e00cb4ff32000000000000000000000000000000000d11324e812cd4fa65d20cf58f88a9bc9407657834d7a92f80bfd32c7085ffa2f9f78d7e18c1405a03de939bd0dbb06b00000000000000000000000000000000144a0f4d50bcb16942d22a12d28bf34d2b4c51512a3f11c130f1566aacbdb63ec3984df5569f41ef621f50d138883d0dda609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000000d7b138fe445d7b7e130134db653022ebb389075ffb62ff9faa544cd0fdb9e78e313d0b1cb19bd812421d38d1e9996d00000000000000000000000000000000084411aa2719b729a1e299fe8a710f767009060f1b2becf2aaa92efdeea8c91239aa5d2504c6e7ad2e3f39d89ef00c1e00000000000000000000000000000000017e86dc0146c9bbfa5ea1e48f49918167dda13b31ba73311fd5cfdc12845b95b9e90972a9a4d36203be8c5920f8de5600000000000000000000000000000000150e4b6fa9cd9a609241d1de8a637c6ab25207bccf8e5eff4a97ed633b67826135172880b118037649407a3e1b1a0661bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000ad7430b7778248d63a06e26119e5600ae97071fe8827b24440587e8bf6887b646f342741af69d20e243c9b45d7dcf24000000000000000000000000000000001230cba1a5a66197875240fe00c59b796ba1db5ea5653cc76bf43d6adce0db3a109168593beb39bb45688c1d124b9eb300000000000000000000000000000000144652474c58413cadf9b31715152052b7618e7093e931367a7ed0340e66d84c0471b6ec178e1730cf10e749e01815780000000000000000000000000000000009abdd0210f25d12146f2911a60035867f59cc341b35c73bbdf8f7a5a90d0bb6566c6ba0e868a3d62d3557436190f3f63222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb40000000000000000000000000000000018ced3cd0c169693368fcf9c3dfc49fe248f0b9b5511e9407b8634d8ed7b54ae2dc4ac6ffde8b3dea70ca86ddc89449c0000000000000000000000000000000002f6b227e699dccf7ab1e0b1cba4cf9f05c4dcaf9fee6cd94bbb79f42bc9598fa23eb2c653a7654db73feb511b24829f0000000000000000000000000000000019785959766eb8b00ac2600d87240f2876e049725680f4504f59db6642ff8f82d4e1b856929643906c3be7807a2443180000000000000000000000000000000018285acdf25a475b37ee4da872debba4297fc8731eede6b22be3b0dff12117634de44b84a18042852ef419c3ae18a46b7431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da10000000000000000000000000000000019c7950b01e15669cc1f96fd94957535f32132ff6a5ae788f6f660024c332593942bd3e9603f862756edd4f3ab17b20b000000000000000000000000000000000bf3a6bbe10ad91d687a135f4863ba0332e9b04271d437a6a4770056e6b1ca34319dc895f9186482bbbc815aead03392000000000000000000000000000000000a3ef4d4f7a15da04a91ff079cc40040993a90e9ea21f53e31f7dede52dd513a97ece780374c5f3aa8c8b2e525ee31d10000000000000000000000000000000017749fc7761b06432632ac686d93484f08407504e58b04b3890cc2101f15d21f46ec0dc1e9028c8ef8df10f9ae929887d474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c730000000000000000000000000000000005a36af876edfdf26175c185c3ef005530e02474232ea659f5cf251c5de5721f1b44a25714967d283525632789331d2900000000000000000000000000000000130a6f5edf94736477143b1efc316f131b36d9658c484821be08e7f5b9c93f60cf34042858664db0ff0240addad8782f0000000000000000000000000000000004fedf49e6d49c074dcca96c01607da2105d8053861b4c677a69cff0f82e66a2a63f32f3d9fac8e6c844a1f77055bf31000000000000000000000000000000001528541de3a9d4a216c0e60c31d2b7c7cb91b839fc31307cc70f18e9b87b92bf5b9a9dc4eaacdec6e6bf7791e547d8a2976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd10000000000000000000000000000000013faf7b2b8514f77021d8927a3b63bb7c57785e581f40ca82882341c13a9daf062a26b668844e58291366ea6ae2f179f0000000000000000000000000000000009060f9e1047f15f175fe95cb0914f4941bcaf071f24e856eae6f36263c812689a9217da277613c10c8e254a0933c80800000000000000000000000000000000154619e4ae3901789ed3ecdfc76069d8026a3e2cf142a144e8b58482233380690e378de6b81af0ed9b6536da1cc2a30b00000000000000000000000000000000040c1bce922503699e1fd5ac67725f11d7f9bb6903ff9204412f65355be69d73cd7330a3f7bfcacaa9b078ba6b9a9f839db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000103d91916d537379d6d8717b17ac5b7e9fedd98c24890b51c027cc086458259767d989b3ff9d6adad72bf977e4d378f400000000000000000000000000000000159c01ee371622378339518217dfc0570178aecc938b4a008dee1a6661ffa605c0f1472c107558ea791e0959d7dc1c70000000000000000000000000000000000ea3e10cbc3a55ef2dc7bde7a2e80666557e9e8fd9ce77e2e92c2c70777afe43c23072e263e1def56cae4b6d3772db96000000000000000000000000000000000cf1db638331c47f9080c04117ddab4ba79950563810d50e04af819f14ae0981f6e1e94a635fc90226c8d7beef0844354ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce19970000000000000000000000000000000000095c82c58182ae9a1ba14421c2966d687f7225ccd192b24097f997b471d13b46a048202712cb2d8b1be0ff40755dcc0000000000000000000000000000000017410aca05935a06942f673d1937a593423cbbd226f6707c5922306d28a60396baa08a941122dd4c583331c9481a734f0000000000000000000000000000000002c1d3a1262ce8aae42a6ed10d8020c31a468127e1a59d57d2d409ca9d14143d9fd21353b260edd8b387840580698846000000000000000000000000000000001512e29256b6b9f5a7ba4f79dde2c915b162e4881856258ac2050f02868842381518da4ed824243692b131710d7201f8ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000ff83bf1d50fe35bb3d1bcb07b02d78a1b44d2e0c6bf82c600feec3897fae8b93c0ef05006c1322af0a732392dad86e8000000000000000000000000000000000d70c4957cb3615027cb950e4224d41849b9ff1b435ce936384fe17c4d7bc2883fdbba5123ca0c0c010651500557e1be0000000000000000000000000000000008b16fe9af45fa913aa7e5d01b5b58f946004eaaeeeb493759a5bc2b192d2dd71af24ecc5c6838b5e267ec2dfcf5c17d00000000000000000000000000000000038ca027c985af3cf60cda13e770fbe4919d3a5b413763c8ad155cb4903312822366eb986f2ec9e0804594ad4894e468275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000003a313d6d41f1ebd6b98b2061a2d85943e52d89e4b8680611d41ea182385e154da24248faa1563e6ad79172f91a8763f00000000000000000000000000000000038d9388fe9169710e1a205ecfd03f674b47ba2275794469dbd5f193a55e00765c8eed026363b41afda417bdd8910ea60000000000000000000000000000000007a75f53d9b8e5eef19ef6f5fe8ce5d5308a1a7d02e0bf46f91a1e0cb22555752d82d8471c123269050fd8f35a272f600000000000000000000000000000000011f313127a036403652fb2f83c5122fd12c362ecba2251bd6c357a964dd758eb2a2c3053dc668b9a4bc071898d45cd46ec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e450657331e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f70000000000000000000000000000000007860c3403607d4e13f738357e18bbcc4df10fad4aa25776f84d3c2758624a83aee0996146ba17a812384e1d67a7c54f0000000000000000000000000000000002169148d86b1f7a0ef75d9bd19b6d7cd66da4293fcf33fed9241544dc2564d980161a6bd959f3b43569312bff7a23cb0000000000000000000000000000000001897c121cbf5e82424cc50078ca7143a0c670f1217a9180cd2a4700e06aa895cf84c0af94b7c04bfce047a7d1f8443100000000000000000000000000000000040c1a0c4257f90bd83fece3c9372842a148132d2dffa956729e741ce996d229aacb04387d51a72630329230020b2235f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e00000000000000000000000000000000058c001ee1343c6cde55bbdc4c538f5d14b0e8c199fb822f080ad96ee764bd1908f92260ce60cd521919f223301ba1220000000000000000000000000000000000f8943c35e7fb8b58963719f1b9820153e0831cf81dd208176af7527781ceabcf6ed2e2276cbf374e0525952bace0c80000000000000000000000000000000003b43ea8c32a13c014b05326f7b4ad5b5fc1bb2367866a69373ba91402f4b45409c6d034898e8b0ec3b93c2878d59b72000000000000000000000000000000000101c371ab4d57ed2cf17dcb731117b1986bffc586529fc1edc630de1c6f4fdff1e10b0895907bb81d2ccd3eaa96c04a67b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f",
+    "Expected": "0000000000000000000000000000000007152d538d0f750901466c1ea34a16e7b0e1296a2a3740568812587affa5c0c76ca2055804e24f3415a403f06a717c0e00000000000000000000000000000000119c0c282d22a01524d87eb850789c4816e7dafdb2782b57c32409b1016615beeee2067443835466283543773cc8b427000000000000000000000000000000000d68137c3df081a519747c044950c3231ef82295eea5b7040843668195d4549c8ece4a91447e0ec89530bc51277535fb0000000000000000000000000000000000d81a4fa2d32ada3e08a7bd4471d45a6afd2cfad5bbfa3d378b1df2e0749f9b05b465be61cc9d1a0f4abd56dce03dbc",
+    "Name": "matter_g2_multiexp_37",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004b153d6554c9879359052717457179d8318f8127eb73edc1d6ac2efb1ea9643c4357cc813d1df49730b77f995d6d449000000000000000000000000000000001533a450737b4bf8edada15446cf21ebf82aa7bec7943025dccc4784e1070fbce430699cf3a37a36a3ece692ce87639c0000000000000000000000000000000017b8afc300bd70a3221120f6fbc37a8e6158c48b476f00992c6f41808036765071bd0a76f7c641443b97ba523153947e0000000000000000000000000000000012f686b4759a3d5db2325508f148bfd6217e027fe261d3ed7b8fd0526036044dfb563e1c4399ec266e140ca372120e289f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e00000000000000000000000000000000061c8288b7bf2856c075a176d1086fc49f0359ca3e7c1aaf5f151e6462916a4e1b69b6decb18823759b620f7593079c0000000000000000000000000000000000877af18cfa0d029e7c9a5833b346c7fdec06e54d9641d3953d3cae0e8912bac7c990f8864c2adb6e576442c634865b6000000000000000000000000000000000331490f42993de3ce7cdd53afb4b310f25881710a23f601ed95961bad4e9cbf57d3077803908a91b65fc32d1a84130c0000000000000000000000000000000005aec079da804fc572bb8eb867acb93a24ffb6611eba920d2ce799c4c80cd8e73b3cefe989885167ab685365529b4f2a8fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f3000000000000000000000000000000000ecc7abf4f6f9692cf3a118cd01abaab4754c90d1a59468d402bd699992800c2994f47b2094878604bf7825f125133e4000000000000000000000000000000000662396427cc596458e880bb8a43dbe046deb85601e3c64556990de36e8637e8ea3b142a8195762910a83609ca311c3a00000000000000000000000000000000198b9c52be68d073910f5b26bdeada9e9b308e4541561a8ffb21fd8e69ec9d93b01ec966fba65be27ee53d4857a43e120000000000000000000000000000000005201975985cac810248e333ca714cbcac0ede46bb915d8c857837c80c805898d0f9ca0940819878a26d269faa02cb86128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000004e61ad4818ea3c98ed3c0d247798a1a6ca6bcb35a1cb60bda9394272ec092c385661ab93a42af439f1b55ee8b9c0cd4000000000000000000000000000000000ee0b71ebf39e4009bdb310fe3b555cdc860abd47a67bf481ab36b5ed0c00bdca8082abfb75691d45e10c2f2d777be19000000000000000000000000000000000e9582e3b5bd580f3ca7ca1f58e39379918f2d04b82b418a91e133117a9703f7df4aad30d48a47e29aeaccf5b8e33559000000000000000000000000000000000113b4c068fd040cd3300a2d1ef658955b014e571e7c77594edd31968037c1fff241da88e7a88669a569462564e28cd7d45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c206000000000000000000000000000000000d8b0bf633072f19db61ea263a1dbeddb326738396caf1196e31e2cbe99a68e8c70f8db13cfdfa4fc4494e54c1ef28210000000000000000000000000000000009ceaf2a0c63604afb8a903195933fd1ada0e5314255be3d74a95679c7a7845785e22d2c0c206f3afd62110ac9810c2e0000000000000000000000000000000003a135b405f46ae3f5cbdd63f4964cdc5014c9f3405c2062ba17423dcd22b8f2011638d520ce0ec7bb0cb5b03e8ec01700000000000000000000000000000000066eafafe1cea67aa6de267c767f49d4a3fd44c28d45a920fe9b3cebdeded883d8960f5e9fa4cc179246918942b1428d28f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c13220000000000000000000000000000000017e37a2f1c892fdc58ac3f72cc5a5e2b7c0c87333afb06de89f7a84b1267695bcd452925fb2f15f8b7b20aaa85a6b5650000000000000000000000000000000015b2919343962337a41b54076d6735a093190e1965faa33eee800f5eaa43c35f349aaf93f19977b6fcd19360b27edd6d00000000000000000000000000000000161afb1494482f953007038557c847e2cbf84c57c5f5b806e3b0178e71e3238305f733943bea7ad6f2bc290778638e6a000000000000000000000000000000000c27a2170fa584863697292e626e2539aa15f3c8eee65cbf1f1b7ced6297248d059fdaeb9c955437a51cb016d1ee97c3e6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000313a30edffaf864d0f1c6bdafd7d1e563cef434d45e71489e9f9e4cc6700e44991a99220f53f0cf5e7de5f6e4098bf20000000000000000000000000000000010429081ebd2ed6fa07de6ab0b7bd559a26a43df99fca0a2252411b4554dc69821ccf3df1b05114da84a616ccee0a9c800000000000000000000000000000000131a31442f80da4621f7691664e9f8b467988fa039bd086a2d64f9810878b557614c27745b2e821016f648ec36ee797d000000000000000000000000000000001160cef9f5e4d022baeacdf10b3bf9d7ed5e50627a99e29df1be3667cb872b2af333f803bf426314369b490c2eca642aa83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000b20ec53bc643bdfda1e3947b3773d748cce1992e2ae27c6b7460d90d48e08eb9240879a5a7d3dc3189f486706438dbe000000000000000000000000000000001024bae4a7f71d3d2fb8246e82d95664c4ee8bca4a380c293ea084f749911f984aa4c6f266ecfff69c4f57e20c0660ca000000000000000000000000000000000b58472d81a9f16d2fe7af87170ca0c8c51dada62a4b2a713cae053a0066fd268283a785ccf269e05d8873cd686d2f4a0000000000000000000000000000000016b68177bce92fedfbd90cdb752bc332f46fde6673911c016fb9cff4912d79d3267bf629c33097cf8979f2b913c0936d4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd37000000000000000000000000000000001030d5791bd2a27469d242c62403883ca167303d907839e608acd827b4118b752840a4eca0acbe5df0b447d6651e517800000000000000000000000000000000106d65f922581237f779ba3e66608729dfddb2c487bc927f34e5e39707f2c8a82e8c96af68e3257c7a9876a05a1b01d800000000000000000000000000000000115bec40b8fa914305b1d5a85b65f0811517d36839494ba69c929fc2422f7e8b85d78df4e1687ab0087287eff29c65430000000000000000000000000000000018d78a75ec057cfcf179fa2ffa7dba79cabf6525dabd69ab95b23dc8f293aa077e46e562caf447dd0913ac9dd60ec76b29d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e600000000000000000000000000000000023f7736d6de94b08d9e9efb6f32f8c17cafb1e1b9b1f3db6e58df72a451c3225d11f4304eb0d702b07a7966f95a11fb0000000000000000000000000000000007b3204f258c873a6fcb48d0b36c98ed5f99b424cf4f92a028174e0e93db2af549648ea95fa8c7bbb42b2a10eeceaf8500000000000000000000000000000000165d6e769b7df91374dfb44e18d43e03ae12ee10d8a618a20f67332cf96492ff514eb7de06ea53096e823770c686c32700000000000000000000000000000000012e69ca1e106411165c06ca15988362de583c4a05425e2f4aba4c14cef6d8d04c52c87b4fb26b1557801f55b02ee8ba971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e4000000000000000000000000000000000e8e6bf1c8837c31446959242285e9b85978a5349e1f0b3447e380a7bcd6bce758bc6192cb880f9c09d6ad4a0ee36eea00000000000000000000000000000000199b361ad0b435d7a66b46a43d06e5898376a6c260b68c965f7b186fc75d2f321bf883646e7551eaba03181907d3aad1000000000000000000000000000000000a76e3f399f31cddc4dd4bc22187a68fba31fe2371291ab515d22730d320ae4240911c755750f687c7d26aed09da4210000000000000000000000000000000000cbc8dbc004b9253ba91b2238c92bbf7883360c7ce39f6e15592a8668654950a3fc5a94cfa97f5ddc60add40c32a3630659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e0000000000000000000000000000000005448b623604262a9cf1a9a292c36738960e132bcf0ec8e61a510008c2ae0b51b31da25f2bcf0d7c0d4ce15b1d7179fc000000000000000000000000000000000b61df56ef891bac07a873571f68fe43f79438a31038cf8ff97393ba44cc47408e5a6d64e9ebddf0195bf914f141e668000000000000000000000000000000000d196ced22ddf11132bbebf6c85bb3006a194cebca975d74992ecfcbac546f0f25a39ed5d6100768c1f1a791f3604d12000000000000000000000000000000000f727cb947849d2d7b046218f084283e5513e8582229085f9f98fca522879543429cb8ab435aa3dbf01b68ed258d82c112ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c6000000000000000000000000000000001044bcb16b3384a1f350cbd62bae568c96932a364c16b34d91ab9b1035ddee93a02920ab4dbde2c6f254031909dc3a450000000000000000000000000000000004a29aae48210289e5f588aede0756ddf60724b8ac54de5d9159ea834d5da98b7a9d09a6f37bcaeaabc559dbdde58b6800000000000000000000000000000000112ca953b5ba652c715fd20e3b85c5bdfeaa7d577aa49aa4656d142c9c2afa3d8aee151338f59a199f3c0c3f6a430d6a0000000000000000000000000000000001ebc7a17da7809f9e744cf7f13fc437de34d3472f022493f58bb979e2282368f989ca0982098a7c377498f1d8d32583666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000000e7c3d6bef4b1723479ab6724cf7858c221993357b194e5055db96b8168f8d78f72aaa4a2046be17ae9a7eb00695ec510000000000000000000000000000000015e85e85cec08133b86738e1f7a738de455930ffd5073997a1f1692c28044ac00b634b90eb24938cab56e286ca0dfaa400000000000000000000000000000000164646a4767ed69f9280f96be9a7f988d17c187162554239797436a0bf4c4ffa7e4f8387c3d2406a7528c021f56081df00000000000000000000000000000000197b1080bf3ac3ef7bd6123a55f20f1002f366d4efea9e14ed92fd2ef147e2b5d9251a302a85172235438bb2d35943a740a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e40139000000000000000000000000000000000db7cfdfd58a6ce9dbbcd5d65cbf22b5e1a81acc70f1c85651ba962d61fbd7ad83e5524fb9aa019c6bd75dade96f7d4e0000000000000000000000000000000011e269a390fd15ab1d52d38de78ec97eb6202604fab02c4598ecebc7635ac91ee564e751275a485fb43b933678f11fd8000000000000000000000000000000000b8597a00d2401664405dd1fc7d69786353c86cd4699af981fe869f266f9087b00df22a46ac34883173bead870798f650000000000000000000000000000000009117a49b3f2a8a850a0179b558319bdd19a5f1f4a45af0ccad0890e63b222d028536e9bb612093cb3f1068d262af90d662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000002e1311abb9df5e4d76959276b6f725f13728844f8c7dfe5e25469cb95c6937a822282b3baa38817e24a6219601132bb0000000000000000000000000000000012820e6ddc50e19a8f98c15013ecc38901a4ef8ec2b79b85c7f7913da24404afa1c79045f1318cdf271028126f9420a5000000000000000000000000000000001794e653c5673e51a3ace631c1a1265dba07fb74235506b2149d42b90eb16afc26ec0ddc54d03f7ba2dd6a2503971fee00000000000000000000000000000000112479bdabb9dd057b325563c666910c01ef66adf47aa32f5a41bc9cb8234750985c266fcc329ea3704e2b8d9b15bfeb59401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6",
+    "Expected": "00000000000000000000000000000000168c90045dcccef35cfe8eb642924ec2629db886366fd9ebc082019690d103627865f0dc39ffdd2167111f68d8d03c89000000000000000000000000000000000b6f0928a32672983664ad15252b3f145afaa04f11d5f43a6169a2fbdc0b0a04902a183b25e38987c45579ac6d11011f00000000000000000000000000000000195c4d796989630f85df4594eb8353d44bcee76d82b73ff7a57069466337b49b875b3c1418d22d79716ffded7e704a6c00000000000000000000000000000000032db644ff8ca6a3b1ac7bc51ff783ce0cdb7bee8b2c21dcfd3adb56a3e210390756211f22feb3dd4f706e13e5cc163a",
+    "Name": "matter_g2_multiexp_38",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c675cb5e90e45300619be91c752a5831ec47b4143c28330422cb57139882e776c1c5f000d6032cd87c16ab3b1c08ee0000000000000000000000000000000000aeb4e78724d46a55e5f101564bc05e0a1be1d80a781ce8a19607322e82c7ee59db9f53ec34c70bef0766a5b965f54b1000000000000000000000000000000000933e8d7c2420cc553afb1c88b5f64c7a39f78272b34b5611972dd5ced3f639ae2ed2aaba470abe829be6ca6d666ddaf000000000000000000000000000000000ac0a9b46323ccabf4b2024e3a5b4717cd8de9ed7de8a78e33a38037f802651a4b43380a746890d93289d547d94b61bb9c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c000000000000000000000000000000000196597114fbaefb8108c185a85d0fff0f6bffecf056902b22d61cc70b49a747bb35638f5b663830a8d2ee15df9fd5a1000000000000000000000000000000000616ed44a5fe69da994e2ada03a1e09065964223333229f5f30ba7a452830848f599ee21810a95e3659cca495897bb710000000000000000000000000000000012d0631e524ee9d3c776c79137499f8c9fb752ca93e92497d89973033d60971da23f672f140c1a753b4d00d08a00babc00000000000000000000000000000000111159e95d131c8cbe8df75853fe9b3f24013daa083e57c5b716e77f6fd3872dcfe0156382c9d2778fe886621be19973ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000a7d253487591fbed97381b3a430404b87aac04073e5931ee0bfd9ea6e0d38a41090c6dc7f6a591336fc58a97a3bea8d000000000000000000000000000000000647c67f1816ae6fec39033c3169eb1ea89e5e20e755cfdea33572d6397e7e87635c7439eda4912361a32de313893206000000000000000000000000000000000e0cbd54634d070aa3c7a503df1171a5cc435d050def17395707bdf7a61cfd539348ee5a4c29c7845cbf0e5df0531f530000000000000000000000000000000007d006601dc1e092a616eb470be35b5d32742dc6a2a4d71cda8865f071dbba9d8a3a8cb10b486253b1633e4590e716dddfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000c502a19770a892b2fa1ba59900a36c0ed054a8bfa0c4e32bf471b90d0da9edca6c06b133c8f12e233b104262a81dbe00000000000000000000000000000000011801f159086d07833a691182595a42645513d316c084b2841445c4a63c6bbb402664a9a9a100e8d6436337ecbf398bb000000000000000000000000000000000f2b9bfd8ef6286bc41e9f47ffdd3efe437aad553c9da02b3c22ca04b5578d634c0543a07bea966bedf345562218c2190000000000000000000000000000000010be5ffe0cc9f580c74e027aad09c213189fa4b7aa92160ce813b8d398b2e2803294e1a730cf5c891cf1546c6bc91414f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c300000000000000000000000000000000132cd5220c125759a18c31313592eda774247f97b5134111b01ef28dad5c3ba4d3f13d1af9076d663f7e217258a6fdaf000000000000000000000000000000000f06a5b03daaf8f92f9a302f06413044ca0dcf2be81d9cf016120312fbd41b273650fbb542d419595fd2815a809c4b960000000000000000000000000000000001b11acf12cf46e40554a1d6a833566cec1b2750f3f72ef77496477831d5933f477d59463ba19c03dfbbbd02fcbb680b000000000000000000000000000000000b2aaf91827ba923c8a1c2fa1d6fb92384c9f48f8f77273056b94245114d1f3cf66fdcab330673ceb2e9dad6c1aed0d4c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000051858339be99d1271152bb390e9a2ec0c0760b7686804ba072c46db3cfc4472404a9f87d868a28f2aef16c9e989d6e90000000000000000000000000000000019a33f21d0bb8303f540bf26816f145360bd1e9a8229dbbe7981f1cb5b099e814f2691fadbeeed8e4c4b772bbd27e60600000000000000000000000000000000073eeb49aa7e601732dc0888ae6b0f5e8dde3d97b818155221f5ab8c599eda75b25c86f15ceecafdfb9ee4abe3419e10000000000000000000000000000000001507073b97d494de26e70f18bd1723d931cd2a88903ab6da2aac3b80fea78ce75caaa9b99375780d759fc4a1683950bd458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013b5422deb0e80bec71309d03fdba007eed33c3ce0fc6d4f9a0d063136b3b85a6fce90ee59956a9b91e1caa519f813e8000000000000000000000000000000000829a11eb50f3bb1a47b72cfeec9d1f63e02b9f7b2592174c481ea7b72a121645ecb36b3d1964b082bc6c7efb4483a180000000000000000000000000000000003d3aab53814f55fa97285af2dc6d32cfcf5a08032d2c15ac83ea036603e08a53e0d2b8d93a25dd969937c113e78064a000000000000000000000000000000000c938a68688138149cda64f168ac1466c401196eaaa44a464d9e345c422948767ad1e25d1ce4cc5996ac5d5dab61516b804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000001ab96f0b60213855fe221fdbe2fb22da6bd6cad8bab8ecb747c9528d3511976236ffefb34afc462abfde13a99503cb900000000000000000000000000000000182fb121778cb002be3f90e2d6837a406edbb609bfae8fe59837aea6f5f6131a10791f92188958b57059b7b9a9d3a24500000000000000000000000000000000159cac269098d223ee6d145a4489f05875b6a546767c023dbea62b3cfba9f8518c9f4d2594d00ceac325f3d8ef551369000000000000000000000000000000000c0d2e4e7aaedec7e53bfebe8f7fe5115720e58768469b6673cee3473b08fb8cd1ebd0514689ef65d78d008889e3ed296f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000079c40bd7fd2ce0f48806dd2e88850ba988e5adb0cc5120977da8110b07da264318fa034c0c213590a2616f0ebe40f21000000000000000000000000000000000905f41389be39361fbfe7641394d30870a079f230dacef89149fdcf81a4d1e0e10b9fa1c0c3ecadced9aaa19fa9dcc800000000000000000000000000000000192f50e08e497f902403df40a504a1b4b82f1957572a9ab7ef97f5ab93c6fb876d8b08f318244cba95ad5200fc2a6e34000000000000000000000000000000000be7ef45a14871dbb344a69c4036af4f994a22ef14540377d1144a92978a23c2d678cca47cbc18e8c036714112d11f7cfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000018d7410f0105ff03cc4ddd87a6e0b65ede4abd4609db5ae53720851c90255757e63c6482de4651eb1d3669b1e1a2f8d9000000000000000000000000000000000d4223be106693a672da890b64d2653135119983639f7052eb32051c34113022080ce2355a93a2f64a75d8e0578b2f95000000000000000000000000000000000764780391249d0c987270bd181a44f6260ef82eb00c06585db7ef09e8b069e46c4e0e659a081ab0fda491534b71b0ba000000000000000000000000000000000a8546031e6466ae43643462b7617703a63841d6d4cb0c09ce63b2fbe2c2ba7cc35367191d0313717b1daa665bcb54551aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000001922491dee4e0f29a1dc090c9b48fe8e6d70c3441e532021985932005b22cedaeea7d9ce1796808d756b740ec63f8ca80000000000000000000000000000000005b34dae0e630be6a59ccae17b44eab4e7f10be2ee700bea15f9771a724f0979798617e129540901a8aa023630a446f800000000000000000000000000000000095bdf612289258b31cc79188566ceeef6fd66858b4dc060864d378cbbb69f951e9c6bfb3d1384014507ff29f9446f410000000000000000000000000000000019f06f11a833c06c1c9227255e3a1d74172e73b06675c547844065dbb909ad66bbc150ba396fa1ba22b7183c0fe80e96c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa2890000000000000000000000000000000003de8082f828ec51e23c864a16147546ff60b5fa71897ff4c120556af5c6616bde96b6e53fa673cd1f8af503070bfacd00000000000000000000000000000000093013f75b6a19b5433b3b5ff044384ddfa258420c80fe81e0424e3102cbf9e550a946e56ba9746423ef745e33da51e7000000000000000000000000000000001227cfc3e9a8d6a71738c514c05766ed4f1f4605198f5a3ad8309c0a49499e4ecd34ba1ba7677d6d90203e54d7611807000000000000000000000000000000000a635221d514e58170ef299eb7f5b679050ee24c589cc7e348b2905a3cd1b7bcf2010cfe168f5aa60f4bfe15e59b4436e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf0000000000000000000000000000000009e8450887137cf45b04184b3c6fedac6676cad416a7646e9980dc99a6d6b62164dbdfae7cc20edaacb84432627e6e550000000000000000000000000000000002acbd87ddca9dc775da01ae026f1c60f1cb5974ce40caef80cb0d2eb7839777c1f61eae0472c7568ec9d0ebb2ec7dd20000000000000000000000000000000017c295c458a9dd995d848e3ba585f8dcdec4185a953e4b8e3ca760eb3e815e39a8ff60416e1e6f974cf7e7b086ee4baf0000000000000000000000000000000003cd8725e1cadfbd80585bf5a19e086abd631d6787403edb4bbc785d1a81f6108f451ff642f4df17dfcf94dd6107352bdc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e000000000000000000000000000000000b3cc99db523b3647937b694fc23281a74010079351b2c7d1ae4cc9167917f06c06e627c4ec44af6b09f2886ddf309b800000000000000000000000000000000001e2681dd123994627adc92e6ddd3ffb006521d8bb03040fe1989e4f709e4797d143cd0bb749de33c8109933c709e970000000000000000000000000000000017df13f532bc9894be932e72c609c0386d32390dee95dda45821bedbc1067043d46007b39b6ade871bd36d39a17dd04d00000000000000000000000000000000162db4d1e956fa5b5f9ef244dbc0c6d27718eca7dcc512d1d7b97bbfd2bd00cce7941d1b9a170da6341891773a729e9ae4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a000000000000000000000000000000000f0e4b606ba0a175bf57d4478aa286640ce4b5507f9f9e354fd96c45443333f6889a93012d663d78956bbfa7c645bb9d000000000000000000000000000000000d85dc4d733f0498fcb10e1e814eb61245203d6c1a46181e5a388fda2680640a1271a68d645f8fb179c0dc3107fb788500000000000000000000000000000000185b02140f6314cb62bd7977042ffaaec41ba8788d356047488004d609ae680c2f0cdc94e59a3cf90b6651298b6a81d000000000000000000000000000000000038ce717d08d367a9f882f2241ae4cc0e8a31418498bf68d05805db2e162d053a10dcff85403dc473598089a78dec27e03f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c",
+    "Expected": "0000000000000000000000000000000004cb919a72e67c31b3409c28dca1d57833a5066c240d2889f5bbdd5540ab2a49484c2462b25da197ec8d93dc8f26ea83000000000000000000000000000000000e1ac1dfcfe22ed7ac52c701a7221b542ce72bf59d62cc49f95f8ba64c05060671098d40c83947dd1952494833a19b55000000000000000000000000000000001331f6ed8ea5ec9b9e1a14997c2c9bc9af2ca305b313e2bc5c5bd35308b7b451a362f8ad61d636dbf77d1b2388702d8f00000000000000000000000000000000186b85e656e45cb5ac9a2a2009353e45749b53dcdcdad4f378431a0e4a317652301f834617e14dfac9836c3c11512aca",
+    "Name": "matter_g2_multiexp_39",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e6292b4d3031fcdeabe62921f0c562606b1ec6139b9c43938971d7851da4945cf69f39652425396ed1b2e70e65b9f55000000000000000000000000000000000e94bc63f3b8944ea6bd7bab811c013fd61303aa7713619faab85a271308bb220e2a94b26f5c7e4136a3d2761dffea610000000000000000000000000000000012313ef65ba41f8e0a57e9b810c13d23241e8430c6ab967a1a9bf5bd6308e89c135e00e789a5610694d146840fbd877300000000000000000000000000000000165ce83af7edc9e701eb57b332597305fedf4b939f3a13a95a0bb3d119c2a9204a4991388f7fb344ec8f15d32cab0eb5eb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea0000000000000000000000000000000005a88477765bbc8290b7eb137e6de78e62bbd929ca511cf0aa701f926440f21d33bcc6ac8f2ca5de57ee8116c685ba38000000000000000000000000000000000738074a9365c707190f882780b27dbe96179224103392b86c628b601e33b092a03e24a89bb6d1d1024862a9df6fce8d00000000000000000000000000000000188c713945046771bf852155ba412b4222173b6dec8320ffd1c59e9b36943c2c18b0dd3bd551b7b1367dde3e8031201a0000000000000000000000000000000017222294bacd664ec37e9b214407e5325eebe9753b430589de2eea13360783be52a479e2b0e9c5dc4907dd5f06a7fa822b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a000000000000000000000000000000000c97299d7e18f41e538b91b75e962c3ce4e068202271b40469c58cfc477d7820e90a0e91d647e8ef5fc0cb822daefd29000000000000000000000000000000000bd1e11a3646c499a240bad708f97a49acaeb653aa5bafdcaba41c1c9d32d32c516c94a3db8816e0a43d1b1eceac7243000000000000000000000000000000001223ecf82c4622653ce84460c39afe8a967cbd87a2d75cbee1609161837c15b522480c4731c9e6de9c5c392ef1db18e10000000000000000000000000000000016c5e98d3d17c723548427868e3e6d7ef4bca339e41acef19e0710459bd4732de4a556b22cbb49b823c4ee656fa354f1babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db000000000000000000000000000000001125735092842142cb5387f1ef8fb69a3535e1f0ccce59661183d3104ec1ef79dd87a7fb36159bc67bd73ad403b46c1500000000000000000000000000000000162caf579539574199d56f4e756f1532c66278a55b4f67f4f4090368260f46023543a8a18d49e8c5783cb65f93d750480000000000000000000000000000000003accbc87996a220a625e36d5cdf05d8c16fb353068ad819f94ba8223cdf6436f8d822719153bdba620a07c5dd955fe5000000000000000000000000000000000b53c8a4b62466c998327e0c5ad65818ea383650bf0977d98a8a94fa9653fba276f7781af9f5a4e99052ee3ae65c283d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000001264608a59c0ee9a26568cdcea8801cc8cf6616773bcb0971234b2d987563270c7b2291fa035c8f2069ac99e16c68fc0000000000000000000000000000000000e839d8d982d6663ca4552527f4fcab6ad5e0a444e7b5921055c774871601d342a151133ae15bc76c023b7ea643182ba0000000000000000000000000000000012ffd0696b7e29b305412fb840c596b66b77ac2eed936fdbe0562541e4de6b3166a9991dbdfa0f79b78b4b86f11291de000000000000000000000000000000001777ece357f82d7303aa816237a0dbd3a1398574f4061dd2fbf6b32af38a65abf5ec9bc53bb8ede932db9cfa0842d53a1a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000002b41743325db9550c3a84af80bc20c54b8b0b685d7f84d05d14dcabed2f450b91675aa8c5c650eb81151bcfbf1603b4000000000000000000000000000000000f3d3e69d475fe1d4259f18f193cd84a90b91589a6502588106f0a577d1c1dc4b2feeec20a4fc30b3e403d6ca9e03894000000000000000000000000000000000c10e2bd1335363fe958eb50981b99bfbadfd1c414830857b5257bc8fa6e26b50989d9adb5b3a2fa610b3151f8754309000000000000000000000000000000000008c825371319f4ebd684f76b567c4e9a389dce96068c101568dc8cafcc10896e3c20202b591a344d9a1c1be02310be9b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa45300000000000000000000000000000000101d3f92fe64af93468229608007f50e3406719572acf265fb8b2a7051525a9cb67cf2e46fc8e098cf081e73f3b20c770000000000000000000000000000000017b1422f8208c2521e3896820b22a65bb2a9b47d7fdcd2ce57196123c1ce43c1db6d00f236d7582795d00ef33ad6d585000000000000000000000000000000000e261500a9c64f5ae107d6ccb57fa9151f5321ef4e80f0e271515f1eaaa5e3714c59bf97b39acca41b15d90c0505ba9a000000000000000000000000000000000c08c955b6df18444ce3726711d29c2088721fa0aa6e317c52a05f73ec7171ef8bd61047174c74afa1dea804c68a28e33b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f646000000000000000000000000000000000985cdfb3934e0484805a1965984028d6c459654a3eea6ef66e867dfc737e1bbcd92e31020d5a4ddb7f8091cae2371f8000000000000000000000000000000001998c5682209153a261bf981e16bf1f7a6f8e5e566c1b0f975253ea62439e5b36c5e5060751f21941edf0d348bafd18a000000000000000000000000000000000c8822c1d6412bc45fea05faef33c65d5a6dd13aacf1279b9cfda2a2ee34df3146d45e3434ce8e5f242e9cf7d3ac27180000000000000000000000000000000019191b51d6664a3047aeb5590df2939b2cbb115ded70fafc2de4c2e8c2a955a957375314081a8838bf89d5a140b7b915f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b00000000000000000000000000000000156fcfffbf01ff3c8a97e7bd99e59327d38c6f7f1083d068ae158d1901808b3c9ac96f95c2bcbdf5f74b36dd8ce58d7d0000000000000000000000000000000014c64256d1cce124c01fa727482caf8ccf007e4ae00e5277d984f31a11ce584e7633565c61d47bc8accdf7c28bb266b200000000000000000000000000000000052dc9f7fce4859c852d3d9e1e77bb7887ffd35d4d550726632acab3d4303ecf8b3ec7f4114dbd590ac20d748570899f0000000000000000000000000000000017abd1e5dad7ee06116a8131c05c9b48defaa92efc636ee34a2970d701c02b6be0345a58cd8749e582ebd105c02f10a06440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af0000000000000000000000000000000018ce0fb077dfefd57f7943d432e12dc9bf92dfaa30f8341397ff8906b1abdf0c02b599edf85ba1e5bb6287aadc72d7a50000000000000000000000000000000019e5e9e3b0632ec10a26b7c1ec40248a9a8b230806c38aa24e47489a8aee5abb5450f6e5679e3f13c6ec7a79560689050000000000000000000000000000000006e257a74f45142817ea8044f403e98c99db8355d626c59e1d11c6859eb0dd1dc8af389f07562259c1f85411be6cbfe2000000000000000000000000000000000f463e345b004b1364894c6e8ab5d35bfbdf6b7544a576ed6b5c5398ca2074f67e2d80af1ff5b721fc126d3afadff1ef43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f00000000000000000000000000000000174231581338fc8c461c981d4949d18f5b753d27184ffb41568f11e178a271bfc69f8c73f2daed0fdbe5bdc7fdf8ef56000000000000000000000000000000001532474399d6a73501801e5f3fbfc6f13bdaff7a3ea7634568fe82745752ee15af23b16809be18788d295e044e29c05a000000000000000000000000000000000912eaef94ab1f3b3257b26c5e8bbe3f99eaceb8c7ae8da577ef98e24f3308abe6e6005ff674a2af01b4242f8ff87108000000000000000000000000000000001925cd635d0ce770f4925a3117721e96c316dd96708b096901ee04ce02e7b357428e4364cd488eeedf76352a26cc1d10a40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e0000000000000000000000000000000005c4a7f26ef0416f34750badcbbb3bce075606435ee7f69b3589e21e37491f0b4a7a98c825ec222848f5e29618828258000000000000000000000000000000000381c5f6511c9f06ea1a76ca84adab4a26a3cde13e0825b3d81899d6ad3191628894d0f57787f854aeb9e4c57fd15d32000000000000000000000000000000000bd706a5b5ef0d4ee1b679a0af90c217ddf9242b7c39523c39657962952dc14e5e07d02154e05693bad08bfb24a2b19a0000000000000000000000000000000009f28a84aa5bd39eeb09f13fc8770fa7e2e053b6f5d7e6021da77f48b9c3807ad917ac671de88b28dd343c2847c5e8eee03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000a8ff1b15ddcc3684b4d4ecfb53473497feb8a04660350ab84e5719fdb0618d61acbb555174b0900b32341154eb7bec9000000000000000000000000000000001464d21df798c0242ac6aaaf3c579eb66eb8cd53eb1e5ab2727298ca61ea8ca4c7cf815bf5c9f94c2b76bf659a4e2da50000000000000000000000000000000003a25752a4360c84e9353b7f1ce74d5106cbd637ec5ecb03dd0752660fe5c7622fe2d0475a4db98f785307c6961f14b000000000000000000000000000000000163601a86f02900d214ff8fbd041934189503438c557138b6ebaca8ce3c109af50ac28074223fc81d6476a3a99559ac565cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c100000000000000000000000000000000189597e6d618a20ecf9a87cc70b3e0eee69ffa4dba75056ebae93cfc3c2ebb368532b17d9f6c06f09e44d9f101397b2d00000000000000000000000000000000086ba610e490588e9385c8b6944c2bad1eb03058e927fb2f9740dbefb779bdf669a51af88b45985e8345b8cb168c13ec000000000000000000000000000000000db8b9cdd4a9bcfc9f7de144da0b33981e4dd53744cd260c4bf045d643a4ef5f25aa19edab7be0c7f8f5ab74a4b7f1820000000000000000000000000000000010198384a646807b16e2ed9186aed99ca3197b05964dd0348086f446d3ebb847907624f4e02f71a1e866d17a125e07e93ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000a293f07dc3f0da0da4bee671951175a4480a719d44cad3d627878ad2f17596f0dfbd6f43acc7a1f9857c5d1f453e5d5000000000000000000000000000000000be6382cc7a00d590f2aada3b4b75f01f8538caad2ade90227ec71e5661ae353e68789807a13f28b23b17dc0dafc19b70000000000000000000000000000000015a9ad5a6f1a511ffe1891ce260ce442996fe4d8515ca593e3e869cab9b18af57956b1daa43aec98a0281143b0c319fb000000000000000000000000000000001807a4ddb73a9aee58b54bab2b878bea8429cdc91384c8fa533a8c9d15c966350e892bdfce16d37a4048a763cbf25d71e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a30000000000000000000000000000000015aa6b865796f88ffe770bf25612ad27942213131c566a446dc149fcc70a018230f1cc8b20461ba2c55300fd27930bb0000000000000000000000000000000000c39c4f229b23c0f65ed720d655121eab50f695864959a2aa49771b848730494d14597eb85ba35743f64eda897f95917000000000000000000000000000000000ad44cafa754f06e45dfab801998c40e5a9f56e4add5c8add1d7ed9e05d12459f2efe3f3367cbcd161f524c714f7782b000000000000000000000000000000001437b1f1a1399ce2a860f7c6517b14a2db264b2602c1c57b8eb04e165205842b483497e98e6b6f8a62e25ab8b0e722f04c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d",
+    "Expected": "00000000000000000000000000000000077b81fa5997de07738e1b34d8e17ef4a9bde516577a4290253cc759ceaae745e10a457204b9ed0069942e0b487d106e0000000000000000000000000000000015e79be67a752a46dd0605e9d07d738c5732b2b605457ce056deaa1f7093b0bdc91b4c81c4c5462a51bc713a7fbb86c3000000000000000000000000000000000cfd2e6043263bda2b97798e1a7dcb24c63aa7197f2749f92524329e97f93dcb56429d82c1d63c067d7ceb38e6c65b5a00000000000000000000000000000000026f352d2f93e6407c59d58742dbd91ced464a3746dc1ad9059e6bb9c146dc1e74235bd54b1d90bb3399865cd3102f3a",
+    "Name": "matter_g2_multiexp_40",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001387fb972f997ed0cb97a5ccdaa759dcc3c2c7f4f15e5cc4fe74685e42cba75e778772d795847b45f274d32cd4960de600000000000000000000000000000000150b1ad31a3d434c1cbef877fde2e105d4a047dc34e3889d21544c2143e7b41b8e0024443a774bd1e09438293860a43f00000000000000000000000000000000065033cee91f5c4d429a074be3d2a8b001892455a11dc708ea73c0082bedb1cb8e8b567a6ea68a1296ad2b80e4b5b08f0000000000000000000000000000000001991ff6fb57e8cbf9d228f1a99697f785261ebce9d3c1f592389fc860b8d7a069896dd48debb8cbe0c43175cd2ecfff5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000000a0fa2d39d868737b9a0526296335256ab4894cc58ffd80bc6334e80d1314bdf017c8226b41ea135f6adefd07650ca1c0000000000000000000000000000000004334f7985211061dedc794ee8931ded12acd39d7e6a6ef44a749118d19ce8204d07935fe62fb2a8ea4f68f99d7c5f5d0000000000000000000000000000000018850a3fc8c851a06781511faaded1ce0752e7ef66da82c2464eccdf78c32fae306da3cfedaf76dad371cfbe012f2bee000000000000000000000000000000001296ca0b0e368429b122537b096fac77d6367988956a7f6cf70c7193b7033ce42fa0cccb8b84b9c78b16a68fd5f4c14c2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000019deb7de7fa5254fdf5ef34fa616651ec70548187fb0bfae9f512e0bfe1f662783f06a9a99e434ced84229deddab9d240000000000000000000000000000000009c199ef916e6f6fe0677ab07beeff221a5687fa8da3ed3ad99a950b7f27159f857d1b561006bfffab551d240b764fb300000000000000000000000000000000148a211fb58b38072cf7c417c70d3ef92e9cbe22b31b2b626198add01dbe1ccfec32d333abf42140b9316312ac48aaa2000000000000000000000000000000000b551b57045365d842133e46814d5d0084248904960f8d2fb28e9623660bcee658582928703f86261cd70e95cd20cf3a530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000185959a297a8f434cb9529a1f7bf9009fc1af3d09efb0a9dce1b9e7d30699da64e4b1d32cdb05b068621db092c1eb59c00000000000000000000000000000000106ef21e9031d108364e93ae4b5d21b0d6d78c2e86e0f8a7af27ed3d38dba0192954e8c716665333e5dcf21387d3f2b1000000000000000000000000000000000185d21efd7d613c409b6ddaa66eed70c235440974b2a9154f3711e3969061461f8824b4547c65e9db09ce875512ca2b0000000000000000000000000000000001aa46b22451afb12962bec5c6309feeb4acefdf3c98c1ea14275409b7111aacf7c92a8e024d01d4dcbfb1c91fc445a1d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec049913000000000000000000000000000000000557370d81bc3da4c50980106b8e65ca2edc757a475194cef201c9edc0f50363cbebcf2750acab0b67e1020daf5660e7000000000000000000000000000000000462f1c1379be9bfed97a1a83a00428de63eadb6360393ba162af3762a99d7eae8549d0cee218e469e4997ada7b35cc00000000000000000000000000000000008aa5ead309fc703f6de980dd43c294530cc2b38b94d5281e9cd9b0d09f82f747a7107b700f1437f3abe36c01bcfed1b0000000000000000000000000000000014110a19d574f26e11e2163a981c3388c04854c5693e9033a474f1020d5f980666d84c60370950734c46663e194bf0ec42a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d60000000000000000000000000000000015beff8cb3c79098bc73dc1ea4b240a4e0d094b3dbbd51592df6adc9c9847beb436ec83df6c55666e296fa843298446a000000000000000000000000000000000943aca2a6e57e9897ec764ee2911d9ff0a59d9e903c70a8494340cef2143895e79d3e6c03af2d6461ca199dfbd0ca0d000000000000000000000000000000000b812ba87c4989af07af44f3dfa87de119fea28ad598cb8e52247cf41bb8bd384c0d8913fc82e4cc2878065e797cc581000000000000000000000000000000000410ed148d1e354653f9d9d17c50026957fb03fec64964f2bee5eeea966b430e77f7b3538d9f4700a673fa07d0daac6b7a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad000000000000000000000000000000000de0a9bbd63c59767938b555c7f9284d0885ca23019818c213a7d4f1594b028965da871cc5818240d155c05c69e4e25400000000000000000000000000000000079dee5649cc67700e9338799a9810d352a5c68098d0676e42e00bac31f37513944dcf47408288cb7f1cba121506a10500000000000000000000000000000000101a650e84352aaf3817b400da0aff40907aae3d2fcf16739f8ee8d5bfc62c2a0dd518201701932728a41134ea3f6278000000000000000000000000000000000f1f9dcc0b55d0ed327f667cebc052c4b6116fde5e3076dd6e447c3214d4c8847885be9547f95f341c42e7c7fa7e2c71bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b00000000000000000000000000000000148f706b4c93e739324e5db40d42025535cd33a32bb3f211add618c0e2022068384a5612da67150746896a2813a664e80000000000000000000000000000000007204ebcef495ca8232078fbf1539a4b46e89506a09dc008da457dee2792acafb6baac4f6cef2de15cbeb48bfd12bfd6000000000000000000000000000000000bf8900e48a4a56b653b1e02c3b9a7d81c2045dbf6297f1ac2acd69d1bf9e06480ea917e3a616243c3a30235abbc426f0000000000000000000000000000000005ebe0ddf4cd1aee76d0b3d03eab754664c8b36fb20ab1060900909e0e0a4abdb45bf74a0b1d40fece9bf73360f580bcbef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d0000000000000000000000000000000007824d1c48bb2cc0f406e356f6e52b66392f6203f49dca7ce03ae6302ce3e8055d071cd812f97481acc654b318d6cae2000000000000000000000000000000000ae89f9eb1abe452efb7ca48f8f939d835f9a79e05211ed9f4abee06b93e34b17d920ddbca3d8bf18b96c3705c1a064500000000000000000000000000000000119ac787a7f3e9b7ee34070aac1a769430eaa8cc838f1752b573ac7f3c02a9f490de9600c856a55448598b149f5392e300000000000000000000000000000000193a3655a80e6e0b1278730600fd4f645d54947d193484131176b890ac197702333ea847317568230ad8af1280864096392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000012f66629836f0f57bdfd9bdeb2c9b7d6d5dc55c586e15d76aaa04aef06722bc8ca156fd1295b3063d738a85b3e8746d900000000000000000000000000000000097825c5db7289b1b9e640d19ecaaa81ee59e5b9884713f6d312604d8ac367634a264c316d73a9cf63358c8fb15f8c5700000000000000000000000000000000181133d027b97d8e2bef308a93b7ea2a35824dc7d01a3ed2f404fbe12ba3b3e51d94ec86cadf3da7dc9ecbaa23b411cc000000000000000000000000000000000a28a609d0bb015e375e74c087ce426dd3c20fbd8b374d3817c626faa81469cfd11a2a4e418a44f4d7ca621d0564bc4920a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000000feca6f7e3cb286090fa3df9c5ebd10c06192fe14af58d46b827acf48fbd462f3f76d9d20670803946028437410ea52800000000000000000000000000000000183dc7085483bd05c27691c25588e33296fb610bddaac253af5b2262db38091650c1c3185d71a69d1a63770f95f381d7000000000000000000000000000000000189f9b9ea528bc2377ca3354fccf440fee059f5732dfdac320fb58541e74e444dbdcdc008c7b47681c05502f0b302f5000000000000000000000000000000000906162085e0e299a07e41b9d62668d4810b97d4be317bf376da537de7adb06de011f5f40af834593761b774771a80e4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c47258800000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0c8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab0000000000000000000000000000000001d04065373ce5d1ce47e00476f07708bb028040edb9ae7e8e00e2c6c460e1ab8b730ff510a25a3c8114c1753b7bf1ca00000000000000000000000000000000001c87217f150694a84a4e5aba8d188ebf7224e76b078dcaba4a91de6b4ab317966ce1a9267a5a27ce556c3386b086620000000000000000000000000000000003c8422590826e0999e7ae3ecba84edaed20fd7f1eba02b9daf1c46c2aec74d5fe63319047d37f5115f243ae0ddd4ffb00000000000000000000000000000000136ae093c3bd55ddaffc2494f3ba8176947cdf2f1ae408e7e786b23b6a65ba8c4131c83cd890386ba531b8637b3b042c8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef2000000000000000000000000000000000d09b92a559b8efe5224184fb4f43779d0b8c8f23587f4f74e2fc6fb1f94e8d2e0d591eb0702cf51a9eb402e79b46a0a0000000000000000000000000000000014ec2e4702f1ee1074cd1ad29791cf4903357e62570d16ac80c5e8ff73b255ee03a5ba070091cb2f984b2139de06a97d000000000000000000000000000000000d22fceaa48193756ce7331952a2d9a8057b67bede729e07cf8422bfc79f9ed2aeb99a9227af256deee9f8a6f227faba0000000000000000000000000000000015d9322c3a5a7ca404259c4cc7cb93dc3d46dd8dd9475756d2ce6fea527642f9230c7e94a804ecb0b4adec7963fa9cdcd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d000000000000000000000000000000000e16fee11affc6714c7fc8fc5e7cce44d8afe645861dd2f0b8e58aa93d4f0de9b7e73020a1537bfbb0e2c8327c4aae03000000000000000000000000000000000b7745a4aaa8ab4593daa61e375d55f9043fbc7385ff229889fca514562168a4e769c5eeef4d564b41cff28b4efdb7bf0000000000000000000000000000000017f6c5b1fb00746b50ee4c7c743ae57fae2742617e5565241d012a0ef6067d9ce59be749a99886ce9836b648525d2e92000000000000000000000000000000000a3be81720e80f6aa0570c89613c78efe95d87ccb374e7f77065800590bc71d23ae097516ae1e97b498cd233221cf717b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000008080a7d91caaf2470f9632575b43990a9523219d75994f1944979ed5b650be1e3c93eceeafb0875f66a40651f4c6dfc0000000000000000000000000000000007a19c4a6340e39230a33b12fe63e47bb0d1378420ec9e439f216699e512e4d70571a1670eaa6b60a5c899ac63360a250000000000000000000000000000000016898d22b2c123003480e3a01965a72de94cdfa39b20898c49e451dcf6a4727a1ebd629172aa1a1aa6897916cea192b4000000000000000000000000000000001217a373c78de9d3005690023b9e56bbed3073f13ca2408a27a3480578d8013fb9d3ee5cda95c3cdd091a5cc68d928da7b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f",
+    "Expected": "0000000000000000000000000000000005829c932c80baa420602bf841ad9bb24fa25c61f33f5d88693207b81271c94eef54bb524aa830fdad8caf8c082bd4990000000000000000000000000000000000b8d184316c2471ec6875641ea83de4f9b7227041922415b38b07a0704d01f2585ec2701bb4ae0bf6a0c0522efc0c630000000000000000000000000000000001dd81e075620914254b38ca5a7287eb56f2f31f6f8fe02fa51488d45c7f4609bcf49972d0ae5ded76eed5a4c096939d0000000000000000000000000000000008067feba36999b58342ac54e48b0fe28245f8ac2498b60093082822d19854df5c3168dcd55ccb6b2cb397b77e712333",
+    "Name": "matter_g2_multiexp_41",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016ed84856b9f41be9bc6c025a9b79e2968e2ee6bbc27608093256c541096e2c9eda1159e6dcdaefe783aa59d52f28ee90000000000000000000000000000000014aabafdfe8c7369f93d5472a9c6c4d426e4b02c943488be993d04ed24aef5477f6d455f82b4af78381b8bd16f42b56f000000000000000000000000000000000af34789c6c923103633e5b1b9fb447b671ab05265c16488ca7224e49db21973487a5d3de4de40b9d8a97ac9b1966619000000000000000000000000000000001123a6601c5351a586f27f8264d4227f5e1df868a03e0c3df5c148cb523cdd178f96fbe52464fdab210564dfc22b29536f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d7410000000000000000000000000000000011b82d78cd9b53b8e7e5c14a7371f34f08546896bd59d1e7d8be15d21742180aacdd01b0d08da2cb24873ce75e166bd500000000000000000000000000000000161ae0d724085a6e801edf73443cca87995c2d6b37e962db5719f4c480cb830e379fa778fd2f29e75173e1c31daccaee000000000000000000000000000000000a2c2b89d00b7d19f2b0530889905c30cecbd4ed0b56ca82208d666e7576c32a6e90cf867ad87f19e4fd367a10c449a2000000000000000000000000000000000b65c0226743b573dad7ff25bf1885e3dec686cfd5da2862ab300fde4fc8fa9b587d0f2d11ebe1f6a6770bcaf2588f8f5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000012d4f299998aa897db9e3194244fdd1dfb95225e3271383b5cc296bbc51c4e1af52e849d8244f82421cd198158918d8900000000000000000000000000000000110638a2f7cdb7104de8fffe29be32610063bc656e13168921501e1614f282bdc9fccff4eb3c479a42b240a2c8014864000000000000000000000000000000000b0adbcbaedbedd376efd20a417bcce562b87b7449cac1e90d44eb05930e6f558b35ef755457305da012a231b5675bc2000000000000000000000000000000000db6fa926c7e02f633730569732fd9239bbacf2042599e79a4bee76619872901c6f4ec4d4fbf3f84143a0d17b167130ebbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce500000000000000000000000000000000166578f3087772545c0f47fe0b3efe32874d26463e4f262be65a3bb6b0fad7d0f779808f69362f3fe63c72f24ed03d70000000000000000000000000000000000a8e61e8193228fa1825cf14e94f68a5eecece9afb48b44871c5ad62510ee1fc4e9c60d5f2529b8685e6aa13ec91979b0000000000000000000000000000000008d25d81bc4bc92508c8cade33c305c11d71a06bd46f184b05dc406f0939f0e0967b02f15b4f7f6984c9fba0644ca8e800000000000000000000000000000000113660a7d2152346500a1578641aad4dac2919ce63d01d8ffa6dad72f524c888fc2e9d2876859859e47d8e884f170f86607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000160529ff217934c85cbaa8b347151539e252dbb502c015e8e45c128df2b8a737866737d5cf0eca6f76e4a16790cd02a200000000000000000000000000000000127f7b0e4f9351836db9c204386a199293955471dbcd7b4ee9186f0434b46dcacd1edc02fb46b4c377c4e62cec10cd6700000000000000000000000000000000094abac17b11600d7447f7ad0f21d98c14e439c4a4a6572b00c90e14d9fc54e85045d0576f74b054d384179afc0a70c80000000000000000000000000000000017165c32410a498add8e1dd55ae43f94be234ba3859fc6b4816d7436746add313f42b1fb49e0cb6c4b7341f0acd09db841c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000060e503ee1c5d3eae4bc0eb30fd86303a5c48c10cc7b4736d17b8774c78a8c97ee05b40d366b2cc9bc7781b1e4a192f200000000000000000000000000000000034e7012414edfc6a8f7b2c6049236b6fb77eb94b05d55b218851fc1e553514e6ad388fac08a24c33bea63ddabdfd8720000000000000000000000000000000004c832477a90683d417a00a698b69c643d6dbf82f5afbb83eb3946f8098d80de6f2d457c0a06d0051315f06e93b5e13b00000000000000000000000000000000048c3339996948974f2bac14d8a6b8430897644ec8e9cff9eb369557003aa2827a4f3fc3444c4df73663ebc9325ff317c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c00000000000000000000000000000000082466944ee7c62788b6fa77816094ea623d03c7aa2af249cfbfbf78eed26a76cff8c23c2295aac7ee1ef8dc84630003000000000000000000000000000000000a8f88adecc3f50d8eb329492f2c031e722f36627cb3b21415781156ef44954c5b8529ceed5978a37ae1248909d38b5d000000000000000000000000000000000e08f628aa014152b50a85bb6eb947d53c596d82c0d03594ed3b64c486b8630c880adf43fb1575b02e4eb8174a04034c000000000000000000000000000000000776844f28958d3e12a5c163dbd039e50df44b1c6215429381790175a609a339621475a5b9a06c3276c9177d2dd2b576c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e9590000000000000000000000000000000004477f153c0510d8e50bfdc2db69182c05d5ae9b94bb1880de239733e380e03d50001378432312b24b5bf0952c38396c0000000000000000000000000000000016663990dbe529a5658f2b3044bbd390ad430adaeffbd5306f758d86bd5422391bfa1d21e88c63300faad55e6a2d1d3200000000000000000000000000000000188f701658558033ce2c41101a611f74ad6d3cd075c195476bd2cd59a1a9dcfe937020737250fe418b4de435f8b3a0380000000000000000000000000000000013f8d3625309767841603329f56686a99e196d697802cfcf31f8b48f9c76f77a321276a0158a22b94e91d6907f6ff451e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd2000000000000000000000000000000000f184d22f3c0431b031ee0ee7ae9598ffb511a2a56f5c9f15c9a4b0c53af2a10d22a311805786e303e234239326dd74b000000000000000000000000000000001062725b8c576e79e314f6a56ef9c41f05a65d7d0d57d8414e2ae9cb1a520b16ede7e418d3a9413c9c1660dd7508d5860000000000000000000000000000000012ef02fbe96f9a191804b6c4a0b65b6024e3e2b1f8cff986f5a950cde9a32ad50d4f7a72804b2d18b93250a63a7ae97800000000000000000000000000000000000b3b0333d61fc46653a7172f5a813d13ff5a48056f9689c78c4b18b8aa3afaeb7cec305d98dd600786351338a2185a651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f2690000000000000000000000000000000010c91e1dda48dc528f618f01abbe01db1a7b6dcb0d47b83c7b7db3331f7156f7b2d0f081458241467b0078935a7b4a4c0000000000000000000000000000000006f87f782979d2adc02e65b56a4906e50430cb4e0913636e9aa0364535c9d7ecd3b9433358e00caa8e90e84b7705bdfe0000000000000000000000000000000004635089c7706cfdb5a22ef643d1a9a5021847646ef01ea559d1b655299b65cd76a73b04149adbac612e7aa756cf30060000000000000000000000000000000002d83d82bc9fd66c558e00547a8c25633899584c9b855195c00eb3c8742d22c601982f244a03f8e0c5c21caee24405481a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca0000000000000000000000000000000000848f10eeba8ef9c7fd0e679767f6b6a2392922092916da8f13573661f84ec97c65717e55c65526cedd59dc1e096f0840000000000000000000000000000000013781974518487de12661bedfca5fe72205c51cab461b5757ff14f319d081e7845cf8e099892ea85470039713e8e48cd0000000000000000000000000000000004cc1a27d1aa88484fed40ceef72e6bd201e5ee276b5ec27624286dee112ece767b37c6f1f7846d71cc0f4042f04dc170000000000000000000000000000000004f7335d6a1463976d9fd86e2baa45d08ec65059b14449ebe4aae99971c5666cdc6e40cf0510ae99dbce97ae8b4598067db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf70000000000000000000000000000000003c1b1e0848bbe37e62f1ebacef1a574400d5048f1e09d935af2052da29140dc4074175e4d6ceb7c2c071331b2f3d1d3000000000000000000000000000000000e1c84d6b20553ddc5ab09049ec488ea2839c5818e31455a7b231cd0455e2945aefcbdc6c1979821a80bb4f77d46e91e00000000000000000000000000000000199ebb31e8800395a9c2e103c9340444c97004186929b52de33cb8d9396e7ab8d5af3fe6035d4463701ea41e341f577300000000000000000000000000000000081b3882bfdf83e67d2dc42b211069a4e93c0f173263f9f20579128391e7f2de70335df949b9c0e9b834b6e574f2f8cc14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e60000000000000000000000000000000018aa45c6b3898a5fa618f87f9a08a7234c1b94fbe38e2297a1f9c7a2e9de0ed83023deebd56560b1928c012c14dd7a860000000000000000000000000000000009ab80da6c519aee8aa1fa68c35bd0fac78b55f88d861e8fcd445f629054325d63cc4241f61e5596dad0d54c94511e4c00000000000000000000000000000000105f8253f37f5538a2c25587fd33ea61fdc744a7cdf4ff23a55e2c66a39040d4de5eeacb7e11c0d2a483d59e7c3186aa000000000000000000000000000000000f6b10cd6522a1e34c87c702f58a07858cb753d67da9625155bd433020775351a9ec4ff879f91a43f63be1c969afe675ed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000001618dd5de43a6bcde91a6a03fcd88fe59d1c8c51d3d85cd44a1920dabd2608a0b17a987b76eb8f5b20c7f1dc0abb383e00000000000000000000000000000000198034b7ab8fb8ff267a52a9423da95bc587eef8684f18639df5db44e50bae7fdea5c5e5ef37ff14937f86cc948a34e500000000000000000000000000000000106d1f017da463176bdf55e3ada78ce70da4486be42dd0095e3a8a0f6e59ed503324565b717b45ee38d90dd3ad13c10600000000000000000000000000000000112d425765fa2fc28486b95e49db63346188fc5a6bd0b7dffa4430dc82703eb44d98d726edfa4a275aa5db5028d01ef530fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000001326581ac1a1a960db1ff2e8b89b1debaae46d1e2d0aa6ffc6c7398f207abb699ac59186ae7222b5cae3abe64cb61c93000000000000000000000000000000000218753594c63ebe5fe503aab4dbe1e944b24138948542c7c43d92ccfeba5854b7bf1bbcf8078d85fb0b8701b8b092fa000000000000000000000000000000000c3ce8c17f75e78a8c9980e9fe125290d377a32ac46411876ef011e169e86e1458ac5e71cb4a446f6c640cceb8d5617300000000000000000000000000000000176966eac1e20586ad2a03b4a1598b4db1d7c66be70b1b22833e4afe0e0b3783572f791ddcd4eb70a88f4acc28b6fc7a980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000099ac8430fa411e74082cf3282f9a456d3826a7df4f91ecf621e645a1abc057e1bcfaf9ee73f149bc447cf4230f2f6c90000000000000000000000000000000004e93d7fedc9e2d7423c9e111b4674a2bd83de28dcbbcc54ce4b324c96318a11603fc9ea385f1c02364ab1f6b5458481000000000000000000000000000000000bbb29d70fba5b12fadb02a24bfe3f6a5362c71fe5f964dcd0e01442781d0462a873501029192858027d612a8572e9d30000000000000000000000000000000010daa9960005562ca2d18eaf4b4bf081f194fa824cc77515c81b2c836627f21b732448f367e2cc1830ad0fa4ceb928e1619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1",
+    "Expected": "0000000000000000000000000000000002a61fead6801f41f2f27603cf34cfb4b917f2f85cba1f9c684995227653c9dde559e1e8497234fba9b2e4c119cbd9ec000000000000000000000000000000000085f73b8e835a10bcb9312931eb08d916d2b93a1da843fa2f4530cdb26e93b5dc95a555dbe8e50ca465b162661ce1d3000000000000000000000000000000001442fff9019b5476c217ff725ad95c92c17b42471ed7bcc121e8a0506755ec279d4e56d770a322d56f56bc6a6b0a41160000000000000000000000000000000017e7710c4639d51c4a79c5a2791101b52742df202b1827192872f168bd21020bd068160a587fc501782c1301c231a0d3",
+    "Name": "matter_g2_multiexp_42",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001213b5d5704c454845824994769c8b300676e75bafdeb95202001161aede276ab7967ea7362d38e97ca1484cf9c342fd0000000000000000000000000000000008c7c1fa04bebe5a1fb8678370563db63e7a10b30747c2ddeb4aabd4fc0ec93220d578b8110c6bfe8a3a6ea2820f0db8000000000000000000000000000000000c4061a295120a00de52300ee625ac46566464e6702489467316e8c182ca2168052c50b5962ff47285866c17d213fc8400000000000000000000000000000000086c153169a9ed1aba10a6cbebff4911b37907d6398c441ad47da17988d512d822ee36f5217355b93c9d6dd8dcbc8e0b119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc0000000000000000000000000000000017929edde8f9940826ed739bc9f59099ce76e85950698ab0140784647023f96afa064aa4a49b9728f496515a0a807e5900000000000000000000000000000000198d98f430384c1e7fa9e2403d9c3d2f81873fb7b204378cec95b97e674e10a1a43af97db0488209904469989ce80a0a000000000000000000000000000000000afc9b5138999bcef35613e38bff4f81cf532e00346f5205405470b2424622826c746ddf0369c7bdf77467dcea5cff290000000000000000000000000000000019ccc05724b3e9966bf918f01312c80e8422b697be89365b6ca00eb31b0bd08fae942e90a75bf9da1b3d264e416060f1f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001568e52c2760d895874527d1ac8597730578176bdcfc67aaf69ccda253f6616230811dac59bc27cc1e57b94b5743cb3f000000000000000000000000000000000a4ddeb8b56f105ed5f47a538052f3d38a23c0ceaa2dea241554e6508f82f47d32415ffeeafe5ae5664c936b78e07648000000000000000000000000000000000b3b335a390aa0090bfd6467d6cd02eea1ced347cdce3c9ed85dd46e38e9f2ae9642392c2875a27618ba8f2c555d5b190000000000000000000000000000000012baa4b29d116eec749353b7658af70d4d216189133db707e56068c8483af43ba86583862e6b39df13b88058536861b9e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001888b83ca28c244a6178377b4ee6844dc916e28c3f56312ecc0e29d08e6254dcda39a36ccdc317d1908303db3c028dd000000000000000000000000000000000f4b73d9316fee42d60f8de402a7d07765508b84d8f2c1be1f3f9e802ed7b0c6c5fece3db95d5287225026e73de98ea4000000000000000000000000000000000f1b48122191e1bc421881de831293a80566b9a7f2c9836f7718afb69592d59d2a714cfddf88945b94fac7a50b743eee000000000000000000000000000000000f1c6b052dbd03795433d7ad122473f109484d50245021c8727d252145e7db7dadc015265d1547f9c748409d74f5aa33f7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000011e8613c3a771a177b4b85f0c6f97a53fd7900cc23566aecbf115058d2863189c21be36dd5dd736f6d0ffbe88182b400000000000000000000000000000000017b2c4e8d8aad0a12fd7130789188bb63a08f2b243c8f7700599dd33d7e176f70f2b1818e56540ab3fa507878d96a46e000000000000000000000000000000000e2b5ad5ed3578dfdffa414a4a2142846b1232cd2de468725283e3f92b536d8ede74bacc236993f6f68a16fc6a7828d3000000000000000000000000000000000fdbf06ae4cfedc462f5913bba9bba2b5c86ecd0e298bf27a21317fe74af6ab15014c62cbfb617356548cf808599caf4b1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef000000000000000000000000000000000b48aebc6525620b99cd83979658a35afa233d17849bd0dcabffcf3b550f875a386b6c0b4ddacf18a23843629072c0150000000000000000000000000000000010432e5abf862d3be10ac5677b9f296ccdcedf1480e45de631b6bfec42f20edf62034f7205f659f11fe5a6aa9d882c7a00000000000000000000000000000000011702a3590e7aedd6948bb94bcc874e0b8d77a18126ed4ba3753dc98953ff941495486c14c6d801c71fca3564ded9910000000000000000000000000000000009faa427c0a7da26c92b451c61f5b5e8804fe032a4cfa014397e430882cbfcff81bb22f9c15a8747ef455773c1ef65b0a41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000061a1ee841251bad461f89c52196bebb1cb4463298e88abd62cccd21bbd325ddb33d1306ffedb2734be76c18d80c8dfe000000000000000000000000000000000d05a5ce6372ce34b0bf4b19d8e05aab74abc1cedcc35a2d1d4db38813d1e5c1375d63ca0e8bbf29c510a4319d2aec27000000000000000000000000000000000dfc57aa8de28745b8d28db3769ab5ea26b5115d3e59e51ff19af8ba37efacdccf763ce682cfdc77685705781c3924870000000000000000000000000000000018c17d87411c4f8e0ca51b3eb4c3765d3846e0d1b75574f8e511b2f3e8c5ff53bf7618959ce18dfd9e4c6285e88f094f63cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de000000000000000000000000000000000a0642094b89dc9c6c7c11c1e57ed542982bd246112884969d424a3e091ec4fd73dd40a5ac116f6c68216fd1e733cdc7000000000000000000000000000000000788c7a63eecd1cbc26ee6b14b09d0a3b7a17a848fc0551d50cb7497bc98287da2d9b898260eb678a8a0f446eef5c6670000000000000000000000000000000017a1298f90022ddff3fbbcca180e3f4da8760218dba595a067287a2473a6e10b93dcd54154cb64b6c078b083b42cd09000000000000000000000000000000000116e999b808dcaea0566c0fbef1807e160612dce91756b2cbcf4883b04a90320a0759bba21b41e6f4d8449b52e52f9a96a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f00000000000000000000000000000000064c95bc9c0e2be48849a349f16713791c37310f71b5d0613cf0706febeea3a56a0f0f1ac6b504524eba801e8b759f2900000000000000000000000000000000007088d2f41fc7e1147b92a2ee7062b9bec194d3a47eb9985ac1ceeef57e1a006571e7247a13dc95afcf9905be57e2a7000000000000000000000000000000000e6a0770f4315acd9e410fe58395ab8b20a08240a6948b762dfbbad3414bfca0ced4ec9da982bc9b8798b60dde78a96c000000000000000000000000000000000a70b53a6d71c83971167afe329ffacdd417bd7b228766851c3b43701a439f253a8659312db7e83a398142fe19332b527d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec000000000000000000000000000000000002486eaf9b743d3aa6a1f3e1174c5f213bbf3e3cc0558d63ce40e3c03e1c2f6e8508248bb649aae1bc92f3eb8118a2000000000000000000000000000000000042b03959b40eb0641d39117f7af50dc7ff048697a57b80723aaca164e2dbc647ffe78fea0a6a4c07671f7db6d5b2dfa000000000000000000000000000000000e141eab29f52b9bd0ee44861f154ec1bd30abd715935a7958a19007e789a41cdb0f4b9cf7b3fac0b0d4d77637b510d00000000000000000000000000000000002cc2eaf89cb7a04d425d878a30b5e2e9858ae0b2a2ab28fb28a6db0c7283ad861bb6a92067e969e5721b43466e857db8bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f63b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000007ecfb753be501d9f9b7ae7ceaabaa4fcb7b690ee04fa1a711a15dcf67e4422adef64a0f8118f93e67f24a2d1a2bcb36000000000000000000000000000000000a459e403d85972f7132641c05bb842416a7135009ff46b617bf0918e65cbbf33f76b98c10d901936e589bdf5de31ea4000000000000000000000000000000000bc6ec31a3ff92b4fae07cb73ad7bfa8423044048337b0ab9add09bf10fdf190a5f7996d157483d29fb29a681ed585520000000000000000000000000000000004c622e2bc606fefc8bd83c4a32f7353123205a6d3716b581c2c71360e5200ab069f60c256dfcb04b466c53cd61fc94470de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f5315000000000000000000000000000000000125688e44f593c5f585765f30e9fee5e4f15247cf33ac78ed1744453385f49ac61128e23b1569ea33d74b207a5e72e930000000000000000000000000000000009d77360ea37298fe971569230159967012c4991255fb5337ca6d58cecc3cd44a024a9a044ac98a894cc97dea161844e00000000000000000000000000000000056b2dd9569f0698c732367cfb217af90a3d6dc15e2555ce0aa845616e4067a7fefb304f6525b539555a0a685f0ec5f20000000000000000000000000000000009acb138abacac351e03f7589d4bf29cbd331e93bf538578ca9466b759ea070931c786d35f74fad42261e2df431fd00316732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000013515b0022ea946a8e679b9c0eac6cd67dbc4efc820f0b3d8984f12b7d154c0632a8d7207747284d49c498c79b6bb5c60000000000000000000000000000000004d6765ec6aa8744225c1e652ccddccc91fff7fa8182931c8648b3d8bd33b2177a9af03b2906da02bf117bea59aed3040000000000000000000000000000000006f1d858c4b223552f0aee466cff35d14b3ac6da35b8f482417e8f597514b065be315aec6662ea5c7784d3a9e2184090000000000000000000000000000000000345eaf0d72b9c11fe72261a2fddea318a8dab92a67ccb9438c11e61fd298a333cc42084d4ca127e09792e346cfe0f004a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc2400000000000000000000000000000000029ad10ed6d6d5bb591771cbd597a3a0b841c2347c89027126bfd1efee2ac403933beb99d08721232ab9b7354fcf9aa800000000000000000000000000000000198400d4e026c2463a07ba5a3974c869ed8ceb1f029bfc7f41b23dd7076cf4a83b17c27ad6506c852cd2cf7c4987f93100000000000000000000000000000000152bbf74cefb77fae8e825443e4ce09b4e223242187f563a236695294d0a5f540f0b29d6f93a54cf0a77900e936e61e000000000000000000000000000000000079f4759eaf044a80417345a1b4029f8d4cfc7e00fc625e815cb7daba2243a97d21e42b42ec968dc8647158fbe467088fa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b89000000000000000000000000000000000cf3148d0c30774104a097562cc83456d5d18643d5f7ad58aedd9327bf8e9450feee50ee893442b1cde87acb02b62045000000000000000000000000000000000011d4037dcc15d0c50337d71816a2b77428b8ddd530bc3b3c8550606229f88286ae94ba03578cbb5bbaf118916dddc90000000000000000000000000000000016160c8ec4e2fb780748aac279bc248b2e2f1092262f86d368d2f06a78ebcd27e929930c8f2be124e9d92dff5c6c6f42000000000000000000000000000000001980375281735390f48ddac9d00d4c6ee7312ed0797333a26a1684e09c9575e57bcecfc4a31b8d9597a8ecc703835e22c4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515",
+    "Expected": "0000000000000000000000000000000019ff32d2901b7732df1a924eb3c099a9d36bf36cb32ab322f46a72d99d81c7942d0f2193a4aeb55cf079a2cc1707c7aa000000000000000000000000000000000193561d0433e1031fc51829504ca70e92e19bead2e0bad655aaffb6b41f5f75d18f04a162db7714f3f23da891ea91af000000000000000000000000000000000d010c36acbfb38d9dc2df6e6e21bd75deba5708fb1012eab23d06d78b1244d4daae38aa4f803d12441d91adfbaece7a000000000000000000000000000000001459ebfe65c3b2c9b2684042bd71201869db1a0248c740a54fbdafcf18fcdbcc7b677af43abe803362b462369237690c",
+    "Name": "matter_g2_multiexp_43",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005b9860b565fc64146020647d1902e2a2d2fb2002b54bf5e21b6601124edf14d6e8836f938843fbd8c02ed8530953dac00000000000000000000000000000000104938181f16f16318d223febec3be3877bc57067fc23729d1f5552099125558cb21ee0eddc32ae0b0cc3555219eedba000000000000000000000000000000000211f809b624c4992a43e78a978ea32accf9e61fccef6bcd05155e52adbf4853340dfacebec9fa87e5417c045da25f9a0000000000000000000000000000000019bfe94a18da9ab4ea744389c17870ac96218d02178bf2ad502f166a3a1da8c14e3fc52038021503cd24042cde8f306d0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e150000000000000000000000000000000004fbb396eac2a1de9953febed9fb6e158a3b5a366f783d2105b562e8143031d7a1ef039e3fdcdb675b3d3aa4f4dcbe4f00000000000000000000000000000000155e23b5b70f1ce34fc229ad5c8bdfde7fb5dc0eca19596c658c1f8c38716a0a7b5ff59ff19a7a67e12760fa90eeafcd0000000000000000000000000000000002cc82cf87e7ac05be236104c1e668b5573674d9bd741f2d91d05c8a11af1f72aaa1dc20c73953fea38e6e069d2a43de000000000000000000000000000000000a7d1dcab00db0e7c0a239511d630526fb120defcd9453fbb57ee328f974a98721274144e48d22558edf25595b8ff4cc6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a270000000000000000000000000000000010c9293b3c58d646a95c620a0e0a7a0a55cf43b4abaa0de1d5570fabca8d97c91afd67bd45aa234273715457da5a2894000000000000000000000000000000001454f8682f3736847cdb3f784a098f7c9e488629efc3820d49b36a2e928bbf736dcb3e1b30187c2c0090fff290dbf97f000000000000000000000000000000000a0fe3c635a81f20258db4f1031589afc8c7fd07f2fe1e5cfc8f3c40d08a958a3dbec537c51be2de99b849e006870b6c000000000000000000000000000000000728876e3fdc42273e8d71953de61dd5c03e7c31ab6ec56fc03cdf55c8f0aa4b4e5c8ed88c23c28568be0d864df026af4e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000c9ba542189ec1828c397ace9639cf2ebbd1613356d8fb26d3c40dd00af1f43f5bbb25032561aeebba7b874bf39cb0d500000000000000000000000000000000175aa6e94a9e42cf809f48f51c48d60e74d61388dd217b55f3f63612c4565357581e5c39751a65afc3b7488caf5151720000000000000000000000000000000014c880d35d1d31793145803182584a8da003b0ee3c29c978b64bbfe4e1da82910a4539587ba350d393e1bf3169c5e4c70000000000000000000000000000000002a063b3fcd77180de632deca1ba89ec4ceeefefe9883ed9e7e06301a268bdf377c3a6e30859e5a39419e449dd27ebf5dc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed700000000000000000000000000000000069edfb8a83760e09726f6d1c117d4bb3e499084b65e1e830ab30daf1625c37f851ac122f9f5c795912b5b6f7907ffe1000000000000000000000000000000000eb6e9b55869f65ecdf3ac46d0ee596d07c573f88724bcd802b4429392b9a56730a217a03286deb5103f70aba7a9bc46000000000000000000000000000000000e2803e1a646bd70c51806b676591b328cb20359aadc8e79d59e7c31e1ce2f1473b0b19f7a34f23aae09678b11b37432000000000000000000000000000000000b3c9fb5a39a6c40343259e12ff4fe5058f25619d145922e1d80c3f5d105a7495dd9a4da329a2e78afc31a87b2c5d5e2f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba740000000000000000000000000000000019da372143e30307a71c7b96ae0703301ed723814a35e270ef6a6b0c57144f494df1d3fa0ac369f59f3daf534070c9120000000000000000000000000000000006521d89d810c7542108de26bbc888482a3bcec8cb9b542db42d5d4af30d6c339a5b4e959da4f98dd6ef8075554f4017000000000000000000000000000000001387d9c684a0fbf615e7023c0f3ff47f4d2c5a9f748f0261656a09b23066c745420df0eb180c9716d6d0743aae7689a10000000000000000000000000000000014271b9d0b21cc69072333a6c03493321b9d9028149d24964a3773bcbe5045875c457aee11ab0682c2bdd44f098f363d80eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd51070000000000000000000000000000000001dd881f3d2063adcc5638b4b3813a30e75fd308de3c9f42e5382fdbf097d5796ee9e03cb44752515b2459f131f58bb90000000000000000000000000000000010f491f4594dab938115343edb47b0087d1cd1bc12ef908e150ecbdb3a54d8dd51ab24a0e10c585f235ed99fbd3172270000000000000000000000000000000019d1665d452ce7fb6bb6da9782a55dcf12a1d9abdfd50435b8f2a1bc5b323c004fad35ff7e9aedfd414a9b68fb1eb1860000000000000000000000000000000013828087beeeb85e43e8540fbdf97af189878f5ddc1eb35c95aa06a26923330f3b8a2b43f835186865d6f5f6afdb2b9b067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b100000000000000000000000000000000014964f3576b97c00a8c5f4372e2501944a1e4374a3c30e11376ea62e09d52d40d428887833bc2f06279b859c00c98a60000000000000000000000000000000019ed533a3bf469ce5b3e4e9035af177efe9e4f8b0a0e5dc9721dde49a7fc66fa31c8b1c8d5bcda1bf75a532bd2be356d00000000000000000000000000000000064ec4ed48d63ab62373adb7898bc904d246bf2b3790c3cd850524e50ec38e7fb4a364344a6a1dbd26f2ad2d0fccaa1600000000000000000000000000000000134aa3c6b72d39bedd8f9c619d206a295cbc05c611147d38aa7304e995089ce34ab1fa13c2d6c6807a88797dea20214b34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a000000000000000000000000000000000a2a4c8b457d0d2554a2d439fd3b74b18843386aaa00d1b89a1c2d8ff7192cdd1d3a888994376bb7ddda4d16bfcaae3200000000000000000000000000000000155a7dc763caf6f0fe1ba9537c0f75d3e455c2d1c749dbb4aa7242b40a9740fe9e8e88af6017e8f743a9e4c5dc6ebc23000000000000000000000000000000000a693da3aee178e2f0489af77f671c734423032f30c0b7b48debd3b71e65dab7db12ab1e0e72d3ef686d6c1922aebbf700000000000000000000000000000000109c3476016884386d6206c94073c628375a02c8fcd3041e06b8b413508188a1d26ec5ddf84a77d059e9a039dc5470d08d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e19000000000000000000000000000000000a07f457f5dee69e9ee746dd67f982914a2182b5cb2609d273d4122d57a32c195270c956361d78eb65449cf5e13907ba0000000000000000000000000000000011f149ce84c2a11ff818be3ff0f86c1b38a9555e169a8cf791c79828207b7aa89c84e8012a0c5d8cce4e89d758b90e22000000000000000000000000000000000d636e5b027e41809d7ec8bbbfd4bf641a56599a63a7678569404ec8d45c3b88c1d2969e6101528d4edf1ee9d8e793320000000000000000000000000000000011878eefa5ee49be83ea1f7a9cdcd4997ccc59a9669778b3f006429e1a22d3b2a051924f371a228856523e3a09bab59b29ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c6986930000000000000000000000000000000019e9a1950f663b258474b24c334bd256d3aedcd26dc971a745857bf1fe007da0aa00777db5c3e5d21294e99862bf8ea1000000000000000000000000000000000329a12fa0add36f259e401442bbe6e5f9139e4a46d5d091a2110d2561b5629211a1c1996f20d19327d1782340e7ce4200000000000000000000000000000000032782c94c6e45a88425438324f3a24ebf37f0be213424b1be52c878985633950a022f57f8d64af1470486aa3744f3f7000000000000000000000000000000000631556d52fdcee3529023cf20d46ea09ab3c642a7f4eca2878e4af88801d21b80b829c9aec9e73317252639c148676c40bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000009eb122c61ec44afb56b64929040058a804311e0e97d3fa513a162748091304233480bbc883f6fb66080b563b308a24a0000000000000000000000000000000007d1d810fb8788b9f0cd04235771d7adbbdf8c6e67e8538b2c6f0f278755cc5e57ad720515ca558412ae1fe2cd40b74d000000000000000000000000000000000955496bdcbca8716245a130fe6eef44d13280b2d56f15bfc772f8ca66a52ca0a742e6bc273c28cfc858a3269f59beab0000000000000000000000000000000000b27aaa0d94633912c96f00ecc021773e5cf5e164e20c7a7222a58b0465e7baec4e67fb56ffe564c7a2904f36c265e61574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d70000000000000000000000000000000012e0572f5c84f6082dd05705a3fae738920ffff840c21e444f0ed002df16394afdc21c249b6f1837389c48719539f4c5000000000000000000000000000000000c26bb3ab52e3bddc219dc223daf472247547544e3a9ccc31123b82000b17ef325148935621edd36ced4e702ade1ee3e000000000000000000000000000000000c13a8f02dc3f209e9abf3d316fa843be9c4dd98ce1ca2edecf757bf2bb498750f6d96c28abd45d9c6cf5b8b6334b63600000000000000000000000000000000157a50d9034024dfc7b0f0db4ea0f45323d76c81bc844844ff9bdd0c13f2059066ec3060210aaba61bc074afd7ccaa286dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001054edb092a7053eebc542f690e03139f2e25a0098c665741e8711c8a6b9582af47e467f74fff9aeea098b7732be72d400000000000000000000000000000000084f919e219de15e7f9ee122383c772415741e5b86be6ee7d2193a4f6be5c9cc9b2fe5e8beba26cd768bf2ea1b6ebffb0000000000000000000000000000000001822b4e8fae5bddbb36f5c226216471862af238be770d33c4fc1ec2777350db2f42e33a7ba468c317a128e8446ceff300000000000000000000000000000000130f704596ddb28ec6e335d9527707a75c97298407ff3fe17d3cba0cde4c21bfcfd1ae46272018c1db768c036f215182d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f42308200000000000000000000000000000000194dde06f8c54de9ab0ad72ba0de2241fef32fba30fd6f5e83fb7750bc120d51c461d75e495cee0d1e85f0f39aa9d3620000000000000000000000000000000010646496be02c658c82dc68eab86a4f784cf64494bd8441f884e8ff384cbb6ff3a4bf5126bbacaa556aafd652397a8a800000000000000000000000000000000109807bd4b6613acb3eb7d386e84166219e52e841c41185a269cc7cfc5f34e9ef5cd1fea29877749e0cef93a3b44eb1600000000000000000000000000000000020a388c668c9339e7aab15d03108317dea97720dd27a94cd3bb59b372b268d1a7d7d7409780bc4912c3f95acd42a57619d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb00000000000000000000000000000000161506c4a2d57c852fe8c3dce63ec6673f05f99c1e032c8e591239616ef4469c4240482ce5985fdfd4a80f54dfa7024f000000000000000000000000000000000486c5b106393e544852c143c5ac4a882c79870363858b2c910ef4041d8803876cc55ef59cd6a41869bf5247f0db2c0a0000000000000000000000000000000000fe765ebf3c4edd3035c7bedd4aec918426898339d7aa004fd74bbf0e3236deeb7d2bbba56c31fd447816e301100a66000000000000000000000000000000001917c9cf16032e22cdd3f87f098a532a33c9fec560a88f9d4232f96cbe0fe945fbae6bcfdd2095cafe6e0b21071d6ec53f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6",
+    "Expected": "0000000000000000000000000000000015a88bcfa39f444cd66d0d7e15c4040561154c59b832c5ca895f8f8077659487581681cc8f13be136a35b4a573551ad00000000000000000000000000000000009fb6b87eba1edb3d1d23e566977eac68e8f1a28386fdca9d484c7e341c1b210390787418e2f2dff7a228e1cf10962d6000000000000000000000000000000000978de870dcd8d094072897707313b9f1a18d525e60a7cba2b2a395ffcc9d0f97f84e0784df36247d6c98824aaf3ec82000000000000000000000000000000000fbc6832c324d40f104bf82c8cda941212105131c26f630af1d3f7040ef43c6eb4486766b75a81433e46966f79953647",
+    "Name": "matter_g2_multiexp_44",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007517a941bec38d0e84d21508d8bdd6778a853d9fb4c5e953bcfe3c8fba3732ca0b7f6cb5c363f33d7718b1b1a68f8e800000000000000000000000000000000150c0d975481422ddec2a58a55b3d917b6b7c0510e75442c81ee856e267d7efa09641c6b79fb9e699c6b473cccde7f4d0000000000000000000000000000000009d37bf938ac30fae1cb3ffaa971ff3746ee4090d4bf8b11dff7710b3f2e4cc686813890e03643fd56fc99e847ae5e940000000000000000000000000000000010fabc4048e0fadad73d0481e290c81884f4578cfb66e0a83324739652ccf273b62204f126696a2fc6469ede39e00a9fcdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c900000000000000000000000000000000089b167ce7fa997ca0ad3f8bbbb358824cb41f525bf60352d5df99402af62cc6d768113d2c1ebc7fe8190c5f732fbfff0000000000000000000000000000000013dcd35865e27bf98f1f6508b32c7e9a989d528df0626228087bda0d8b456af3ea2f4be6732edf1bd8cfd0ab9576197a000000000000000000000000000000000333b0612d7068986d21e1cb67a1c7af423e98cb14aace2ce02f84d32a38f97bcdac465f2b22e5fafa6aaf0d40380e4a0000000000000000000000000000000010de7ba4f50b6654fdecc4fc6c41289bec50cff1be18be9d5c9d1f906ae843189bb43f144aad4d2a2cdebbc2697c974918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd000000000000000000000000000000000fdddfceb29fd79c31b138ae8e41507f324abd5e3750da14f4f86176126a06380d53dd5f7efd00e7f94bc1370ac9816a000000000000000000000000000000000d8371c602e393a4be250583c299d069270a344953f7f07a5fe27f8617cbd3ebc91f423dc176b272339bb3bd8a9a348200000000000000000000000000000000193a260a417c9c46da0aaf139e3bbfbaa9f248943048396d95716b3be0b8a148a3f0ebcf7d6f9a318b16d2d850ec2f5c000000000000000000000000000000000be4d0f2bf6d746b930034eea8a19d73377617645c29153b6ae6d3ca6fb35a704b6a0bb658282cf93555c998f6fd054a650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000731c0a5d076d6addb15c1e5d3143d41371f4835d77756418bee452d2f03b1e603230c59f87905fb67d5eccce65a45d20000000000000000000000000000000013bd198c023009190c65686468523eccb57c5fe7b159a1c5ba30c662a275fb24d69338ec9c023ee6a10a8ec9dc7968210000000000000000000000000000000018fb369923ee655839c7d996e264133c49f102978f18261faf2f8eef376eb0bdcb5af375ada2bc783e50df16737f78dc0000000000000000000000000000000009ab8e16e1d0b406adbb37e950bc3820ec13c882ec4483528ebac836726ba202bcf796e84abb3c16dbf6d1131b3854cc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000001401029d7cf8e7d2690a27c01b32008e273b5a33842dcf52d84f77dfa4b2a1fb290f56eb4ccddbb420b27e06a7a3a3b4000000000000000000000000000000000c464c6fdba702f2fbf4232b34d615e66dbb5bdf80233f695e9103272111a06a79f8972d1034176859d0e29400f5a9c10000000000000000000000000000000006cc97a29f4e694f0cdbb099278fa94140b40147f4f911de96a762f2bd28233598a892899a6329cc3cb854b56076787a000000000000000000000000000000000625811cad7c740758388f330c4a56ef30429ea4cdb9a00e2cd1b7f310184a2e6ba36ebdca57c87cebd5232f52c34d92283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000d0a16b5d48eb062c71b91d74a0d25eca0d4bd7082de25199f33a9d3d598d137fbee2ac36e8f877c157be7438ebabd74000000000000000000000000000000000bf93533bf677050d9a77a5dbdbf7cf084b5d934d55318256712ec361693738d48ef27536476fdc93dd8e81f13d67a8e000000000000000000000000000000000696fbd8841e60300602aa5528391aa8b196d8c186d6124c842a0124a8d8dcbba637502f330c980b2f5a900be8e04d020000000000000000000000000000000017b0c51e699d2252f35619520af71775f9dd8c57c2ef146adeb72640bec2ca02a59680153e5c9f66bd513bd8559b9d66145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f472000000000000000000000000000000000eee94b5148ccbb3642e582cf0a517b72e6ea019676a13b1484982de7f4be0346b7ed22979ba7303f6367294a3eb2716000000000000000000000000000000001502bb3964f6b3e862279e15fb105073e876c4e48c55c42f3737dc9efed82b10fe8e39438ccd39c933f5ac3c6768497e0000000000000000000000000000000016cd8c4b3be55474aef7081cb969b75ef5e7cca9bd0f9627928fe9931c6f869a9a49d0ae2cfb8346116eb3ced25d4a8e0000000000000000000000000000000012456eceaf32cbb6514e6211136475a750889caea18ff4f9d5ed7b378e6d1d265721a646715aee6b9f2098e954a88289d9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af0000000000000000000000000000000007b5633f4a7dfe62f11065d44581f5060210f8e572d960eb85ffd0a903d8b989ce10449fc90b7e5646784a9f6df28699000000000000000000000000000000001710f252cb35d88f6bd151ed596f2d6455f050c5e25add394dbaf60fc036016ae07a5a8ed494b95875c02df3c523186a000000000000000000000000000000000bee19779dc6430ebee993f82a054fbd42e5b7265090017e5b2d2f1469bc96a5a188adf471d576a416f6a841081043df00000000000000000000000000000000038f9fb4159e4e6f596a17ddf45a00a9e4aede63b062af5eda045efacd3977e8dfd61c307834c08bb4c284638696e92ef262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e00000000000000000000000000000000197687895f22c4a639bcf2f494dd9e5a034610b0297528235f1d806cf032f5a86c5248a83ed6b12f0de27f5c6e6f49420000000000000000000000000000000011ccd5dd6d6ce553ade9b31503a9e6a6119ae329178706f051581e3cf0ee9d6fb527b340bab8c79fad1cd451c7edb4330000000000000000000000000000000011e9f051aacd69c8bfd2f0ecb566e6d38eabc43f276ba7a1b8e8ab093917dd1c672c61d6dac4651026823b9938d3601f000000000000000000000000000000001362c3b2e6fd9b3618df26ed28f96530c1915f0a4ecb647658d1ae4ccf4c000f3bd1797696c9ac5c5000dbe58dba8de44f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a8000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d000000000000000000000000000000000db02fcda340fb27a3fd7da468c5cbed9c8dce8471843a8ddadae43dbec9957a0479aa52855d7a6dca99e7922432365c00000000000000000000000000000000163503d24f9af34058cb5afd8e9d5aaf29e141c8521eaac282f138466e834f0daa9ce14e0590b501680d5b47f866aa8c000000000000000000000000000000000fc9175e6d20afd9d194907f2eb311bf8134aeb96da72f6423610612f2ed20a074c113fe8bb632d9ad74b2f6e7e2417d000000000000000000000000000000000b4621f5e4465629648b62b7f2b77afe6470f9706f9bee5b3ccfa66c596842cbae26badc689f7f623360cb7fc1d416b84ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a61630000000000000000000000000000000003c77c7efdab9a9e71283b034ef581a31faee417febfa99be3c18e8ab724c140be684ce719bc5a9ac5d3855ddbf3651a0000000000000000000000000000000011889b02b4a1150fc2b7191a95c5ee767f3c9b82a3a53591018242fa8685ee3b3542526dfdc00695a6cf046033b8eb760000000000000000000000000000000016d7463159c4e3cb635f24bfb944bc518369e894218bc49d7b7f0ea99240259f7ee2b4c26c6083dbc4559ffcfbd392bd0000000000000000000000000000000010a85df6294fd6406ca651f15494153e9802f0068bfa149e87fe4b1cc3071ba74940a21dfd55a8a77e7e2a193468a3d2eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a0000000000000000000000000000000016beec57d3049c382fc039ac96b890412c5e8075afcab599fb877f8639747a587e82241d9a8059a0bb45ad49959777d0000000000000000000000000000000000a70fba1b061dcf587f133035a3aaafcdace3b1e771d71887ae914919e5f52a99d9933307ec15b5f0a1623b9592824500000000000000000000000000000000005064161136c04f9f50e42a5cee5dce3fa0ce1dc0655b3785a852cb9741927f6c9b357ac1010d7212533d1593c83dba70000000000000000000000000000000000d50b992bc0eee37a15cfd32eda2c591fc4c4203ef84232d1a1e7a9888005bc00755d76b9d0345bb01ffa7525f2aa1e9d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b0460000000000000000000000000000000007617e60d8f67344ce6d2fb65cfd5b423a1fd091626da837dc8a51d6ffdeda9712864e8f30e45ae8df917e0e4625e59a00000000000000000000000000000000077c4aad14f870ba24703397ff0b33af2e50b026f3e0f13f3ec1aebc9ea3af98cc65ab56cce4045538ae6e5f410196f10000000000000000000000000000000004a31d0eff18afa87f9a53098cfd5d21e913c7519cb171f83d0b73abbf3e893a3ccd5aebb9f2bbdd3b0eb0326d37fd1b000000000000000000000000000000000393052e6dff65e01e79254af757f12eb1931e0b386f8cf0fa0782269f962ebc5d9bde46f5a4ad3806e88330aca59ff01e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f962200000000000000000000000000000000033aa108d252e9107f29cc7da79585d4525ff2a35d31479a099c7c011a9c4414d7bc5f8498f8a204134b2d14c5fcde5100000000000000000000000000000000121214465992bdefb970d420face6db75d531e67314a021d2877643ddf738fbe57625d286bde7f40efc1d329a2e85b6e0000000000000000000000000000000017e14f6cdd916b1fc949be8ba3ef9ae6cc16d64da4dd498b5458ea0c14eb7aab8f970f030aab26397110331da11a232d000000000000000000000000000000000c56ccda2a5cca61025253407e72967c767f0e7f2aa0b97d4e4a09420dcb882ff35039ae504a9c62b3f9e7bb0c2e7bbbe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000010fb3396b0674b9285cc5d5a4e7a41ac002f2b43332c20a56f428d1e19e1d1bb6f886d3bf03f7b0fc509e52d75965e15000000000000000000000000000000001196b7c253c50da10815bdfd7930a69608187fc3ac5fbcfeb35b95754d3017a094afcdaea867c2f08346717dfce7bce8000000000000000000000000000000001021f178c53b7d7d2041a6419203d12ee162f27999dd8f79baa15c37a7401e7a6df6aa4192a310cc1a23bdb0b427d63c000000000000000000000000000000000953c75910165f11112583476574f3987495d33e5b1a5c650a2b30692592a442d9de36da49255b0c01a7bacaecc9b81adcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c",
+    "Expected": "0000000000000000000000000000000014da1d424c936453600a4acbd3666c6188493d4da8b34d6bc508aab07e59e3680a9e3488e69d42a724c9486d70ed4fd000000000000000000000000000000000048c637348fb9a4c631a82ded1fa08d693cfa2cdd6cdffb8bffee63d1bb2ee8676512a1a8d375e7ab942b6d6bdda45c80000000000000000000000000000000000443264e7dfca91f17251c33cf72c56b045902b4db2eb10d1fd856f79b4130afa6f29f3283af7d3b8b2a9d8dd63718a000000000000000000000000000000000fb386f875190ac7a49d4742edb387f72c1ae0366ca5c71d5b7e385c11442941ce0fb9fe2014fc624fe93ab86ebc7aff",
+    "Name": "matter_g2_multiexp_45",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016a539a21320574fc25ffbc0ff10c821d6ad20674413eaeda6f4a31f9a028e21cbb3b224c225a2e3bc3dc221cec084cf00000000000000000000000000000000104e44989e2fba9ddce8e309f5d3fa3129f679d6456ed11137149b50adf8b22c1a148d47154450853e6797aba2b006850000000000000000000000000000000008b33b8cfc992efdf7d733803a6d08a4102e27fc4960ebe6ebdb7949c4ff5af76e55002d93a4f7204eff5f2dc4e37ef10000000000000000000000000000000017c35411c571c302c746a9b79cae892e988d50b4660564660de960ee09b3937b6f5b61fe37d09f1c02528f554210744aaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b60000000000000000000000000000000009cd32594094d4744f59690cf8d7fd260b5ffb2a22945d938c035151861507ecaac9ea553e7b44fc4b3beb03b33783540000000000000000000000000000000006f4de33731b9b13b9cb395798769e54a0679d272c2d5175455e10c790debabae4ee02b6df08975efe806da9c4a208b20000000000000000000000000000000011859798a8383b7f994a1535bc0a96a114b90644d19921f0eec774ed58dbaa899dd3736cd1f4a4ff9bfacbc7370091d7000000000000000000000000000000000376c25b0f70427d4974c4fd1539d40996b6847fbb67822fa01cfd541cd3a3f8a9f3fe9f7ddcc3ce920a6ecb27dafca0c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c0000000000000000000000000000000003a178f91a135d59dbd65eacebab293a3817d30e734c247f56a08812aa540a5c80e3f9908d86ad787bab27fbddd21517000000000000000000000000000000000672b3544dd2b91a626f37dbb389aff073777164e3e20dc572b18a2e5223bd323094e41bdbe2dec9bada227efb37dd22000000000000000000000000000000000f40f2d279c66f22bf0fedd129e02c96d8906f9f1ec19f5a5c1cbd5beb10942a066dd391b69920a0a697138f627a1b180000000000000000000000000000000016ef3caad858d323b752e5c437ee2043c8f691ca0f1862e80857f7cc478a689df97bde5b1d1350892c1adb03c5d2373ba1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000001276e133fc5e708a3265646ef0a0122048ef95d7fb46f78b8dca57dabae0164ca986bdc74e581604ff31165f9f28dca50000000000000000000000000000000008a77611be0502d2ea7fbcf73774fbaec68eba36038e2f34f79caf07f2e4b7444efc49a4e85f88af585fd28a041f26c800000000000000000000000000000000181ab176e391190b1cae2e9b4105ca14cc82d15890b0ec127d8cdb46f30b704a089ac69e76f5b50575ed66176950e1120000000000000000000000000000000004031ce77fe9ee319b8db8f220ef4480c81568b3f6e4043c8710b559d25ad69dd38dda48b2e11d5aead18db0d1cc09b98810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea000000000000000000000000000000000f26e6d71e206c88dc81b8b8a5c05ee84a9f185e7b7f155253aa39104b5de5be7bb6cb6662df4f8e63b37fd1682721f20000000000000000000000000000000010058d13637c8da2e91c8cda7dc2cf1734a2f14b12b798e5c563ef9ef3624255a6e1c7550c37b547c35c55dc736a17ce0000000000000000000000000000000019ed470bd514f8bda8fdcd9c64f7626efdde0102907bd31551b1d1972aa14e1d361e1d58b17948909a669fa4d99cf3200000000000000000000000000000000013277afe1891807e269c22c9aa1598c12081809d888e0eb2513ca3f81308700893f74f176858ceed9c7955dcc0d8fc6893e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a00000000000000000000000000000000083664daa965c4173d6028e047794703a16e52ae459d3db0534d13c72d749d603edd668b9ce500677715e45216367c63000000000000000000000000000000000f4e87a65f4720cbfde7868eaadb34ec1916925ffd84e5407defbda0c39e1c7afcbc90855b275d528e7b63fd3707bd4a0000000000000000000000000000000004c9f689abe0d2dd3d927bad4b39ab44f6704014ef9a1dcd1966777129e1c72515b43c1b92ee60e9611245454683588b000000000000000000000000000000000ecc57b08b45037e62498135643cf077f01d216b5106551daab391446ce7bb37d40f41378c830081bb6a326f0105c2c4eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000012b84341bbad1eaf7fc8ebe56f67598821017365b6f3b4cc1f2355f868e8d55f9c0bed2943ada202a7d85cc884d8e6a20000000000000000000000000000000017693721988f73d77f7a41db108e428b0ba781ea88eab463693ec352cc13d394101b9a2792e0f30c77bebaa395a4776700000000000000000000000000000000093245e2919523cd57a0abd2e8a9c5cbe774bee957f26d3cb502b9c8c06483b850b031461dc2cb033d399651724f4fe4000000000000000000000000000000001530f7dbf6a0fbdc8b4f7a4d298b7824c15035428cb8df834907e25c64b8985186bb13f397b7b99ea7014ae65c428b12abe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000f323f01f2a63bc6eb1b565594ded14043c4ea5d1f0fbf20f39299052617c334e6126afd4273738aeb153c3561348b8a000000000000000000000000000000001525d1e1fa65f1b674feef74f6c81c82c3eeb709e597aedabbfc2b3262271b31d93818613ecdeb49c5d3a6a64f17a5d90000000000000000000000000000000010458c15bf46947a237dd1c61882b1561121f64890681bae5db6fbd24ef6c34b7fcb826eeee1fa328d9ef4d859faf238000000000000000000000000000000000e1f29275fe1805d02e069082d5e9a7acf69be17013e6c4c351277408d49383fe06f00137e777ba4aa49c29c25c6c0ddc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000efb7373e11694b966d0182a9b01d1e52ec1e89cb18275921294e2d36333460b1e49fd420f1ab781b000d1491ccb0b11000000000000000000000000000000000cafcdc2c58fb3fad713ce1a38deadba8636c384243f9971e3930b961efaf303cac4eef1e8e4662636ff91eff1bf52a80000000000000000000000000000000007ea7441e1b2b0f1e42bd511c060b646c2d00bb3e6507beb5d17ab93ff68515b02f82c2dd43ce035ff660ddb0c104a77000000000000000000000000000000000bd04b88caf9dbd0ef5f89d12e72aa47d64212332b0ed871b7eb96b16295cf4810f6f20cc85fd4d1ce72119f80697c1b37d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b000000000000000000000000000000000c3d2d978e23a690e8422fd54f36fbee1f642611b6c3b2c2413844066159bdcd3703d1a392b030446af04b654f8f73b7000000000000000000000000000000000ae652fcdbd8e467ee9b447e61fcb811f8b6aa48840476c92daec3285785a06a81c1705fc2896c0843ab48eb92555b9300000000000000000000000000000000007088e6441cb85aeffcb4a9a0c81ebfc54a61f35c542be3870c2bb94d7081353322d4745747b0dfc3e5db07f9e48c560000000000000000000000000000000006c11f3e0941ea3bde0dd3a562dbbdad433f0b1e99ba34879e86f7951ddfb29b9e04ca62d54d7552a74e8cf1c3da3e704904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000000c665f4417d0163820ac96c83cc2f09b1b3c000023d827e2690aad7357ff59e278832b992703f5f0016051ce0a4510cb0000000000000000000000000000000012f4b6688300b253fe868b3790f6d2f4fc16d81a49ff7a2edf821de16dc992d79482d66e443e0abb5da43df69f8d648d0000000000000000000000000000000009e033750a118d998b136cd671d0e760e3a617f1d6a994db8f6dfc391619f408720cc57fe550785306184b0c824705620000000000000000000000000000000018cbacd471e528535e22f714a841f110fb0484826e30f97842d65072b2790dadf0bd7b28df96bec531fbed1f3f93486b68911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb49000000000000000000000000000000000c42b6fd52cc52034b04078a6565af2b43948695851393596e05f37f297dfaaea931a33f5b4c25980c093f8a742c0020000000000000000000000000000000000fdc7aa20e63743dd6ab32c82d2d6992b29779ec06eebd452c17d844159e90a7f3221f3e0e6b5805dc0f42dc3836d90f0000000000000000000000000000000003a2342a1bd528d701c2a6c72708a16df632f4e4b6cdb3ccc224b58b57af30b44556cc968ba3c0396a5e3f11568a73710000000000000000000000000000000019ccf76462668905c5687b7612a0bdfd4aac70f291d8b772e84fd5d4bcb591556317426471242fb5f44fd695c7d49279481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef000000000000000000000000000000000c8fd4a171c5fbf584f567a1c10b20628e7e0d5d796eac4a9dd2376f8d488da25b9219c7c70709999b5553f8bba915ae0000000000000000000000000000000005d791c907984f2aaebf903a0ace52147745295f0c5e85964999a8fc74b64c8871dce358f26ed1b4af6c6f7f18e8f4c500000000000000000000000000000000110a453bbba72ac171876e0f6b4acd5b178816301e02586a143c2bcbfffcdbf593655408b9aaa4141b2a210599f452ed000000000000000000000000000000001025d5065f9801fcc1c1ebebdf67923b967ce985b5ca27ab5db8af7057fda23561a46b84fac5e793dd9af692c4d56cde72780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b00000000000000000000000000000000023e5ea1909032676cdb79111a33da7ed788d2affbf4029b932eed843268f355dc92905db283d6617fbb530da3d704dd000000000000000000000000000000000b46f07de520aa17d597586cb0a6894a356757941ff9bdc2976f620e1bf1eec1dd9801d6baa2d7efbb3cc7073412ce8e0000000000000000000000000000000010022940611f418de9f9210b1be919d7506aca468fe5853675fe159d3e58685bcff6cbc2c1cb9e7d45a7bf305fca0eaf000000000000000000000000000000001888b5b0dd1648d9a27345f570a1278238957de1bd30c195d554750ea4b119e98b3989b912c4fad531de416c1533467f84ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c65670000000000000000000000000000000011dcc334a5037719256e514b2c3b0f36396d8cedcd77f33545842c686fa0f35558c397562a7e245f8cc412c776a2b3930000000000000000000000000000000006efd32c6afc56a07c813fe19e71f0248666c87e1df7e79b7afbd70178929e5660e85cea35d1c6f42b4c627a94ae0d150000000000000000000000000000000005a5fc2010798c793c1b407a577da0bf0e04b0478f19b7d0cfeff8e4e4fe2d581461831db165cfd17146c49a732c41460000000000000000000000000000000011dfe3b62eb87b039113152af74ae74137cba1762d4ae62d3cb0746272d1c42d3cb4a8fccd845a519fd0650a23a897a13256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd00000000000000000000000000000000110e73e44734b7ab63f021727b75e735702f1acfa6669e0dc27111794ebee371734764bb165132af3a7e02f3605456480000000000000000000000000000000005fbcac7c7334cd0e6468feedebe077b80390833eaa4c28af80d29e75d692a10cf13058526fa5e5ab0fb635335ac8f220000000000000000000000000000000013f537ecc28685aba2cd60d0e3e787bc8104a3373177cb93107b63d39919c583ad3ad7a42e322249d7605ef035fe1af40000000000000000000000000000000014791f94aff42bfca13ab328a3e47b06f7da52e13436ad477cf55e53b54108d3aa531f0a5d73ae5ed7108d5cca1ecf7a575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b",
+    "Expected": "000000000000000000000000000000000bab02defb32b7938372d656feaebfb5431de1484361542c02519d20c6a500f0b0b112c331fe6f4eac3ec7f6ae4167e50000000000000000000000000000000000796b38c67df1361115bbf3a4afad2651664ef55b1ed02d3172f024f90a003fc3631753d7142aafffc64c6f6f57bf7800000000000000000000000000000000080d91637a93a9025e8691a400254af37cfde67eff7d3037d428596a808a01d9bda8025b7246fb00785cd1068b2752d400000000000000000000000000000000182a97624249f0c6d24672f04e2c93eff63fbe76cc11ace0f7193facd0655cc1e1ccb2d89d9547bc352a395efeb95afb",
+    "Name": "matter_g2_multiexp_46",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000115b14c4eb9cc78eafedd2072be4555a3db9e61b5fe0139bf3e40a92cc37b4936c68576fee5692a80e4a9aef05a9b7a80000000000000000000000000000000019c828ea555a3c8d28cf0981e98609361b5bafa8b62e860d121c0f6d0f0dcef544784e8a5fa6a9f1d1a68b30e8e8a6f8000000000000000000000000000000000a2ef5146d2658609fd4eb98fcb5d42f6c6aac4fe53597128bbba3ba3539042ee5824f381c41dc76e2c6e4dbe0665657000000000000000000000000000000001807a12ae5f289ecde8ca0a913647d44209b13fae9dd6aa8fb4365a3beeb81652ec17cf92f6784c9ca5a077824ff6dc31129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b0700000000000000000000000000000000186bfd109ea2369818ae2f466953eddfa763960caeee9d6f1ecbf6a3f854163342c26b56d3844bfedd8f227070f75546000000000000000000000000000000001877077daa2ea074b2868e86faf14efc6ed35a64161a77aec54624d9cb916c45de81b40acc3797c6e3338fcd7a42bb0d00000000000000000000000000000000054be1650d9bb6cae6a1ed08879668e4aa4cd139c8b07ce21d40fb1cf37f11de730ff13814a02d2d6d6df5eec4afe8470000000000000000000000000000000001612b5b7c613cb66d4134aa867d985682f6a544147e7865732887d4fbb191a9f5bdc27bfbefd397f38cb101a2d68b192dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c90000000000000000000000000000000015e2b23aa42f1e6a07b0a31dd4acc27e35ce1fb3333c3f330f2d88f112375cf24e6dd5afc4d245531e4e84f1f82230ea00000000000000000000000000000000193649f3b7efb346e0c1f7bc05b0910311270cd44b5803fa16e06655d6239f609363344bb7c16c2105e20709fb5ff0400000000000000000000000000000000002a6ee30841f471dd2ef13888ba03c9cb93c85cfd0f1d0a3927205e3f57fe291bba7eccbd2352f25cc4047097fbb63860000000000000000000000000000000005482d4a47d6e381f755c4756a761f0310d0d981523afcf288e47a1a643d6be62ac6410521e0f25828f469af6150f41e5a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b01000000000000000000000000000000000178ca460993d503e496633fe5230d895bfcdd0696d817a23ae94e529bb145a0861e4448d3bd48c55907be762192a8c4000000000000000000000000000000000015d2db77105a8ed6eadc05d3d1f26a54b3d1b812a58ab49a889f5b7fcf5ec08c2eea6ad09484fda29cc007037a1f6c000000000000000000000000000000000fd5628d61cd0835fe49fcbd2f17058f23d0ffaca4474923a2c0706d9333d9881125efa2fda56234a82158da3eddb5b70000000000000000000000000000000010ce4a0bcada5f92cc8898dbf5c108c0897322eb6a467662be569d9ed0f6e2c808e214b83969ebb86c84d38f67d20754579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000fc4ed0ca43d5cb172deef02704579187a480cc977737070b8ed2dce48d3f3141619f37e985c220a2840ac01dc5667f900000000000000000000000000000000047a15e96760affa4e537a45aefaaab1e0e18052f63514a9f6544136c87b7cb4a5c8dfc0d9544518adc7932ce9cff5f1000000000000000000000000000000000c9be55c06f81e87de58a5c1df8d16174cf4115f81091937d98dad6c4780a9b8dae1081f8961fadc4f994ef62927664700000000000000000000000000000000165f9b1a8f23831a91be8077b18563c7648e54caf30895983cc26580241ca7c86b9b30408a9b27776286ed9f07bd8ecd4be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d160000000000000000000000000000000006edc0d62ec31b14e87b2ccff3a21a7c8d38c3ba0ec48bbb8df27fb1acf58e1a87c4458dc2b770172460adfb9a9bd50b000000000000000000000000000000000ae80063df8d41d45fc43f3aa0881364ab5fcb9ac526ee22d3870f2edb0aa379e9d81780b0ab08a4cf308d819338deee000000000000000000000000000000000e0898453feebb51d9a1cd2bda36a307ff2eebf44dc8f4c694831218c42b51f723ffe521b356ad4e5f0dbbca9af9ab47000000000000000000000000000000000fd186dbc046dd02217cec3c7894972f71e5f00e00a40fb1521659a33e079b7a1f60b026d9055a50ae18aae5757ab8490983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa0000000000000000000000000000000012134b433877e0a7858e6c3b95b2a1dcfb0548b290b68c209642dadf550db1c636598ac43d101b13c2d8d5ed9602a73800000000000000000000000000000000102b5de123c449a078f6f06935c9537efc791ed8e5475ffc2d9e1d098c814abe56d4b7fc6501c315edf7e64a431c5183000000000000000000000000000000000bce703ba78f45a1c59c69429d3dda18243ba2413c5eab46d469f504da975c434eda451c85357738d6c7054755d5cec1000000000000000000000000000000000387724937bfd817a65c0e0411678cdc78df26ebd4a814d92b023710558701163349b56b80e6bb68a4401f2662a0525506615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c0000000000000000000000000000000005abed9305bb79a0ef1cc70e7fc2eae35a8580cd3d1ffad73d3bdae541ad546b8f74b4ca76f8f374e31dbdaf1bd14be9000000000000000000000000000000000199b29da8d161ab3061a18debc8b7400415caf029ced47131e27d81a0f7f79b6ae5e570f34a4c74fb85fea1411bd6ea000000000000000000000000000000000b8a7c42f5289d20b1a55a42d53d49510d3871b6efbe560bb4d87029b85b930f787c3a42e225006ad62c68d5f96c2f8a000000000000000000000000000000000e74aad2b29a210cc316181863e71a1dce8866a088a072ad5972af57b813a2e968a5b16b294273acd6e81e9a5be2961dd77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba1812272736390000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c41f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000001611787ba658b64467b4a28e55ea24a3b230836af6c2a7072231045ee4ee38e02302a62688d6f988f76cb5e50eba40080000000000000000000000000000000008badcd59d6d30f26ca674753ae9257a853dbcf49a5641999634a9a35a97096b6096b7b058360bec2f9476a51eb0d781000000000000000000000000000000000d30154440d8bb5fa6538953a96ba404658817be8047fa7a3a86493f02399543220758e649948b804f2daf84fb86f7580000000000000000000000000000000014fbdd62f761fa675e4cbcb61083a910bcfbd1f8e37f1fb1915f60929b047c970b87be0730ddc20f9716ed8c9bea7f19c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a1600000000000000000000000000000000143d1f811644e3a51c735b708cb2f8a2a90311f9971c90b9ec8e45bdd6488638b6851dbc882205263887b4dd5dfb4e120000000000000000000000000000000015692a6b06e3bd3100e149c6be3cbf1566fb24531eb29036fc48f85d5da83316a38af4e714a17552024c1ca4a5e39d9d00000000000000000000000000000000172b9c88ed9a1fc2d5a7f147d034fa243d420b129343ff92b79bc4d836e380e5a7e388069e9af9026485e9d3f41a7aa300000000000000000000000000000000012e8453dc64f72653c4e9b3f6f43fdd01b896c642d21604f992dc5591f2cadf71de4099e1075a4ca4b7539f84dd5a908db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c880000000000000000000000000000000003e9b6d23809781f50c0033e53d245dfebbba9e0c4d9f676ae61b80fb6e774509f62fad854fd9ea841d9905d48d943a30000000000000000000000000000000016a1ba62bc684bb1848b0ccba59597b19973b56fd9b1d9d06352de44aa79c6bf65409dafb54f859d4a7c32e188bbe19d000000000000000000000000000000000e782741a4b16c5838a8f6e542135221ab3c6ad180c85c08742992ddf0239388e273735eae76c656e61614da386ce2640000000000000000000000000000000001cf6752e88990c221af94e18744790c30aa6a158b10a1f6a56c2ee3c3f0fdb2fa7213f16764ac9e9f4f65e99e715ca170751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc0000000000000000000000000000000000810b0175d781256053c3c9188cee4f55620a6624bfbd2f4d2e70ee68a105bc7b60bafdb76794a048e9f25da976390d4000000000000000000000000000000000716095f8fd72d9350ca62ca3ec34d2228cb563d4e89b19b152787d42fbb750435aa6233d0a97196a9324319837be14f00000000000000000000000000000000178e939d87c37d4a2f49e1e5596945879f2f0f64419e3dfe2afa06bd58098e1ba57a9b60c32cd6527481ab3b325ca827000000000000000000000000000000000aed480a1da482e40ae610a9522f0a18399b0130202f9ca79e3573987f5f7ae30724feddb52fdd05817a96f7937aaf7984bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000133adb236d9eec3544fc91852278abe37a1da0f32a84477c0d93927d64af613b7452a5f64ddec7447779f42873cb157000000000000000000000000000000000f6bd940b51b7ec5a0d92ac77a55c296215e970e9a499793864dd69c3a8d583403e95c08b719b5d8eb0c37a8476d3b960000000000000000000000000000000007d4444062ae06e65b45c6105af53c487f6b275ecdb36f87ec7b71d5861a1bdd6d735e9a1fc5dfb476ab8c13a98b570a000000000000000000000000000000000e043cdc87c67157b5ea3e5ab1b243aef479b23861f8cd823bced140ee03dd1f8bc6cebb4bde4683ac3340823f4d55b8d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000018c35112c27caa6bfe9cf8ae55f51755ed349ee7e7141c99069dea07c21a6d8634778a91f4dc3d17da04966a9eaccab5000000000000000000000000000000001800c8a9b146dba27050ce63e78895bee2016255c59acc34fd5e6cb926c16a8fcd2e8a579fa02559b3c571cb08011bea0000000000000000000000000000000014afab23fd4ea54b1ef576a12a2a62d42b493612ef466483ee8c4e62908486c038598e72dbd9256166960db73259def8000000000000000000000000000000000899a99ae8b10da4bbffb6590d79aa33bb2adb2444a11627f05622c732b70f90cbd2779362349aede5b591e84b53a8a06940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000567d6458d1a3e012c63adc8b9dcf32254c98c0b7021ec6a8d579dad47d501715d2e42a0837def225515d663e663c4f000000000000000000000000000000000178daae121366ce025c1dc2d3e72068fd40ba9d54b2b3724f7a2071a59d4f17d4766a82364540bc31a46398c66d0e7da00000000000000000000000000000000147b2851311913ea53662082acfba785d21915cf00cd154b1b495246e109ac37c3fb6c63aabc4fe71a0d37c81a40148d0000000000000000000000000000000000122b7b1a81888aee37fdd6c23d31c38e79f28945cd1798cee3f4d674e923fc68311eda8ce45a561abf9c5f0bfeb4297b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e",
+    "Expected": "000000000000000000000000000000000f5b941cda417cce69a30c1ba4a82cca71cb4b953d06d8e545c1b792ae22738dc006627da02b4344bb8be93a5a0dcf07000000000000000000000000000000000eebf4ac30fe0ffb905f81577466889666f801d4d6efe0fb8a663fbf1cbe76b2167243edfc6cde3f49d97d3040a9507400000000000000000000000000000000007ae6a99b86dc7ea95801776589472547ffc7a623009a592403a9710ca365510d85bbf20fa4519ca0e0ca208bf86a670000000000000000000000000000000004b5abf778c72bcc5b887855c582c042a4cfff489b0548785e4c1b735b19159be8a3f4cecf34c769a34cdefa722ba783",
+    "Name": "matter_g2_multiexp_47",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017b47384e6302b140118d0a9247aeae2091607ebb413ffa232223bb42d16753b2ae48e5ad0265e33616b25f0c4234be600000000000000000000000000000000167be566292b835a42ac7c099d80e8a0b5d4ff91d842d4ff6026876aa1570ef9641e9c0cbd44d8578f6a758717bad6f10000000000000000000000000000000014f692d195979abd9c55ac132d0def925d4e158fe946fa7b0a010c475d60171a0951d4b68ae3c463bf1136600a1ddaed000000000000000000000000000000000ddba1f4236c5200aa52f8cb7e15fac1f20cc66dc65ed180745a3eb8308f2c851ed6c1e27e1507d3f902ce672d6f8d24facfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe243351300000000000000000000000000000000023d08e255b244cffe911e43b9b48408f9fb3562edc2c27f405bb657731c885a58392ebbde9fc80cccee2404cc8547ec00000000000000000000000000000000088ef289adaf206afd2b72c93049fca2cf9292bf6471133c64ac4f42015b97bb9a23f6c34653e0218fd0abdefa56bcc60000000000000000000000000000000015cb78c1440f74b17125c547fe7a37611f01b83b91a351664c696e0f647bd2db3ffead880b96a327780026d74c9abca30000000000000000000000000000000004d1a63607b9a5c9ec31168d85fbbee77cea0ae93e98c8c1dde14d0baa72f91042b2b7ca489958344916ce79bcf286456546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000000ea6cb7ff6a7f4ec38ba11e9945eb406dbb8517585fef6cdd64edc970efba244b071fa162f7c8e184acbf71c5d1e12160000000000000000000000000000000001ab80c0dced33cac8a6a085efce71dcd7021f6255684bb631cf5c1716021bece57b900b819e6eb6f5b755b74c677b6c0000000000000000000000000000000005465fdd51352cbcd8b804cd509526c3b6232976b8278cec3b7db7da14b77f78898c6240c30943d1418462cb7a5abf8f0000000000000000000000000000000006b6caa6a0d5f2d671b10217c0ce5b3962b0c3edb4f2918497c316ebbdbe1a15c803d7fc3413907346f0e7d03920005aa9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc00000000000000000000000000000000170db23154805a04013052a388e14b5da00e65b35b8ce2dd967213a74735dcbfd28782cef1ffe9d384be3ecadd101e9300000000000000000000000000000000082dea309092976408a379f1dbed9d8cf91f768e2921e49ece458859c80a1d9efd4d5e588470bd669c777d16f9d2e7de000000000000000000000000000000000adba8ef34e197689228e6c4e13be75b3d4732872c99b865ce7733b7a42034d6d4d7520ef7ab712f60f1ff87bc4d9d8d0000000000000000000000000000000005df0788ec39430fcd0625f8e030d917d8e7c251ee6e3b0e79fc6fa5f6fac2ad736c818bd833e58ec61cfdff52c9c6ee2c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000000922d8b5db6e415aa3acbd0d6065db1b492c92313260019ef1bda0fa091c4bf091de95846af1edb34516b1abf7d278e0000000000000000000000000000000019af4ecc4f278315ed90d67cf4d22ed6fc9af5c0d0ca654f6a74a3c4bc98588bf5347b4536f36ca8b4750c18464f9b7b00000000000000000000000000000000021eaceb11638bda8b4293991983f11cc60c1daa2287f4b4a6066374bac82d117ac3ea4ec73afc4372d254bfc433b8c3000000000000000000000000000000001037fe26a10305cc5dc11a65edc705be5a0082656cad53e63038ee57a79e16075df54331233229a129483c34d6dd92ec9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000011e63dc251a5a1e2ec83741682d90588b6b185365b33dba45458b1f56324a4900b04d61af155a0edb0bdc2971b7aaa210000000000000000000000000000000002dc1bd5448a2ebb9a02509af8777616ba9657bd3be65519233f0187df77c49fc931bbd3ec0ad5856b2ec0dfde476a870000000000000000000000000000000019f0cf8baf100451313711bbb0a0fa318c14224933897e74fb727b585cc8620b7d741c9ca2f0d3cbe14a8749aa48ef3e0000000000000000000000000000000018448fa9e05f87d4991ae1c248413edc9a8c3ee789c9c005e691bfc9003191ff469e26db9e42e5758fac79309a62942c5e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee200000000000000000000000000000000109b71c19cd36ef3078bbae25ce6d0e8f7b58e129407fe68ab09aa747bfb3e90c04ab804fa6b7a223c172146fdb14683000000000000000000000000000000000d297750ba112da88beb84b8bbf74ed134b59fc9496da3045aa6dbcd97c68425fd68b75508de113733602a5565f4c8a600000000000000000000000000000000149b8ba6e05b66d07b353f46ace4e583bb61ed18fdbcea0e941b8d9805d3168040186d1c961add494f98e4e7fe68824d0000000000000000000000000000000013a6877bd46557d23b9aaf371ee5a101227d7938c64503b04b39cc6cb4e8ddedcf5cb6865439c9f8b1bfebb807ce52e24a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a200000000000000000000000000000000073f440c2704fae6c86aca3cee34591ec03c362c2c5153a5e82c7bcdece2af0c58a3484b448c8bf4da851800ead959df00000000000000000000000000000000075a2c26372b482a2420bd3c9952fdbf9e5fea906dc8a4deb9691f8745372805bacd68a4838a3fefc381a2ce946ed1780000000000000000000000000000000017575b016435782cd09901afd2ea6773b11f5a983bdd19d14668d75362f95d055b76e5bf6966b1bd7bfdfbe9a939e4b60000000000000000000000000000000001569d74258298fac89d0d91a9945780f4c08d7af7b942d06255ae590db6e8509c908c16bd2c2bb634279debb72f489b5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000017180e36b925e2ce23c46813d96b919ca181481efb5d1666c4a4e9c8031abdd9521eb8228c4e3f16de0b33da4c73588e00000000000000000000000000000000138965bff7c573546d80ef7efb3d45e87ed20f59adb0cd7ae148d09a97da7feaf1b0ef2455ca19381762768a7d82f486000000000000000000000000000000000360bd29c3f07c5b560e2ac226112a628839da9db18b052991eb2d9c54541c1b5ade9b3c2d7f446ad50050531228120f0000000000000000000000000000000007105978bcf13bbe2bf5c8f7d165998c3ad99b6a2794c90f5b61fb7bf2472d307df8fc9f4afe7ae1e40e7f0eee8ef9466e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000f4434c5180ad10cd45dca62b8da790cdb912c255c0f33950f7039e3885b38fa9e9297c7b0a875380545839d8c4d4ada000000000000000000000000000000000d0dd1429e512884ac209f788b5832d31649a78a8966d3348a93f841be23c8e4e42d6ff0d6c27e8f43daf495c9582935000000000000000000000000000000001307377f55dfed30ac1a406671af1895218a01d063b025d25bdbc53f5f9d535e4cd8053c09b2cebb25d3a08365ab8ccb0000000000000000000000000000000004f5c06f505ed15aa7661249b7edd71855bbf47237e049aa951e1ea3ff88f98591518bac975ac628e417892f8e9e5523428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f00000000000000000000000000000000083336fa0b79691b4875ed27b2bbd2d2586992940356f6ae5ddd2021c5ddd87f07f0a5c1e8d8a2654b99182cc2233e84000000000000000000000000000000001880f3824f7cef95ae5743de2e17191848d8d30f0469f455461c6559ebc75a7afbc86dfa3ee17f5470f74018ec335edd0000000000000000000000000000000007c2b26353e86223e5dbd4ed6d59f1170b9cc9dc600fdfbc6c73b96f2c667a82128b1ae5af0542b11a7d1efae87c75610000000000000000000000000000000002427b7eeb497a20cf15c10513cadc9ea612f3ae94e2ae833d281734e7b5d1d50e240659ac01da7864a95b4cdcf88744ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d97000000000000000000000000000000000e72845430ebfb84f8e3cd3dd418f6dc528bf521aca4f9dbd798ed903ef0ea3cf21dd1409aa3759351be32b21d8e8cbd000000000000000000000000000000001457ad87f0957006192dff7d99815c35adb3635815e5d157542b9f52f1e9f8c0143a21a3be4dc1aea3a895689f4a316f0000000000000000000000000000000007e8544b1037ece2e5a9ea387e0f43b72e895e9c2ca4d205f12bf6df0b35ae62a4d62756221d6fff65b928b7358f48b00000000000000000000000000000000012c5c3167f6ef118c4044c0aafc85a337d305437d694a7bd6fb406dabb7364d9e90d74a8b327aba971421a5b3dd5d06988a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000001995d7cb79da7b6c5a0c8ccc5ba075d8d6d8ed3cfca85e8ecdd2b589986fa58c4cd4f045983e9184d79173678d618f310000000000000000000000000000000000f9f7f6bcff0f6fd621f3f8fcfebac132b3f0d52a34af33bb9830bd714d2982f3cc6674ad6ca668131a5062e5589df90000000000000000000000000000000017699b298a46829020e0299ab89ab6411af0a602dffb0e149053ff40ccaec71a908da02c8e611723cd06c16a8e5c0f2d000000000000000000000000000000000523b287383c1e47a6f31d397359941fe0bb8167aa11604ff8569969eb5ccddf4c4f432d2b6fe6f39204020e850d4f2b30eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000004760120239593cae5bdec813735ccc99a88129c707686cf43efbd48fb08d8da3086879a6042bf118879fcccce0736bc00000000000000000000000000000000105b8191431f701b365c66680cb4eb267681ee4da17ba55d47cf26d21ba1c0c3eeeabcafcc79dd87b6457bcc91e9fec600000000000000000000000000000000126ab502f66e732aabe02fdb2f7a665a9a43f6b4ff21c22fa976e7e434b08b606e9cf0f02459fd85f5a80a332fb3a62e000000000000000000000000000000000b2ef01adea6c00250f2f14c98ec6d6083c45019f3d166419e3a137667324f80c34b6b72e991daf72e2eaf9985d0f9287e8dcbf708682225fe3f71b7a687da23de5ed188e40585be0553358012132577000000000000000000000000000000000ff22a0db4f1b1679bde5853a7c2932501f191f4a9f25eed968a796219cef028e26070851a9036a05a04abd73bd6bd4e00000000000000000000000000000000097e9310749f52a4b645190069f4d52315f0eb2ff9cbbcd31f1781a68b2664bbbf27166e6e74fc2be2e5b1eb3f3d77a00000000000000000000000000000000015ca218d7d128095bd4f4b4f7bcf7666e92b905e551dd22745bc743ad0783b6ac44b841f87d3deac44617a7c9a341c55000000000000000000000000000000000a1cb723a4c378e5db2775f4dde9a6887ee3313401a64130a78b90d65dda3a5d9c8bcbc1a0d78c310c869a7fc4889954532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000018b30cc461a4e1fbefe209a709a21ae201bc6094b2d15f0d6dee5a55dd84ef56b62ab1b6bd513b27c84c638291f4205a0000000000000000000000000000000008a6f2082d6d510b280a270c09044ad31fb18b851ad2b38859138c9c2e4870fba6b607f682a798bf21a13bff116014d200000000000000000000000000000000150ef352d494a97d0a7ffe44903aba1611c8d81fa2788c0f42a6db48a71101e12f07318da5ceb1f0af3aa10cd4c26341000000000000000000000000000000000ffdf3b133cc926684e4624531569bfa09b1658e29ad9c3efbd5e9d18353ffbbfbf23a2ad80ccee88f8fa597416d47173ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5",
+    "Expected": "00000000000000000000000000000000134f45e5409998e657923ca76ce92b7d2acc932308e0694bb22f121f8324d16bfce86f96c67111c8080289eada4b4fb40000000000000000000000000000000008d9063b7845ffc8400c0b7585e819043884f92e28f7e3ffa47a39e808cdbb034ef4230b6e19bebf083e939b6b686b0b000000000000000000000000000000000e95f8fcd6b5bcc9e00a580a99627d92fa7486ff5ea587df5dded24d1b0bb76d339f6765a5a2058a8e227f633ce36e91000000000000000000000000000000000393041eb33f2c63df3f40d8ea1e1a9eaa9eb0a46151294845e542054d503ef69b40b0b676b0e4f3e08f4d26c36a5d4b",
+    "Name": "matter_g2_multiexp_48",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000042a220276f12fb4e81c40ecef3a0d86634b4d1c0acfdc463df4e7501289f0be385e03808d44be053e6ac98f403a8a930000000000000000000000000000000004e3dc92e155aeebdfccaaa1d24f49efc8b02e4d9ba8500a5b953a96e0fdd58519bbf1c279750eb8b98616e6bb9a3f6d00000000000000000000000000000000086bc212a83b09c7361540349767896058d5567d4342c607ae9c07fe5f123d9aaac95ded6cdff0825edb5acbce3e2b6d00000000000000000000000000000000062598cd6d5680a155b6349cd51d636c1350746d17fe0fff5195f164ba2fa51cf52f8662f43d555f7be6bb8bcacca39448da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c00000000000000000000000000000000193e2c749e5bbe87dd5c306d822740969cd69ad6e156c323d217c08b18bb3f97c85aad63aed1e3a455ffa1b6d2a670340000000000000000000000000000000012a3dce63a88ae32a746e3812833569959021e1dd9518621793308f8f11d04829b2c3d0e0ec39fc48dfb8285b6852746000000000000000000000000000000001235488d01c380e91872fc57cbf618c3531a6bbf6cba9dddb9f07168cd459c9e866e44e9e5336369cffd8cc3a36cddc00000000000000000000000000000000010d85f85d6242b63f8421e92f1c37f64d33fed67e0cd3dc4b2b2cb4a7fc7637f9e049fc959720eae6d1f452159d48b78def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000010889825c752d0ae8445a1d0f3510135b9672c30a781788698f637c2f535e35788d76492edce8ea091223d016b5cc141000000000000000000000000000000000577175035c86c022e634ccc9a5beb96a36aa068cdc36e5a4fc2028d5dd099c5296d30a916d3b720f2e051e7d72e4d490000000000000000000000000000000017b46e49ba08a0abb9394479d693d8097a140094d0ef1d1ba7761fb601a686b0b2b4d49abc2e393a99c5cb760299992b000000000000000000000000000000000820f8e52c1b09986a70bff04909b044f671c3933de43a6bdfcbe3712310274ed880d7adc4947490c7de095ea651e578b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000001776b92f683069fdc006904fef8e91f716d9f6bc46306b042228088545f0e11a41b40b60722d4f0483250391febc0ed90000000000000000000000000000000010d5052ef2504115e9d9d4ca81c7080c0868cbed605dc7673f7f94f5959c793c96aa5334175e58499102ff76f974ced80000000000000000000000000000000011d1e719bb69d842df4fc23e8dc4393067d00f6fa8ee42d89b462a546414b91f68dda5378fa093b3ffa764b5fc63b1aa00000000000000000000000000000000099d0784a200e5d2d38773912cf1a49e813c871ede8c50da03abff58ec1943d2adefe66bd2feed1c57f5a80253e091d0a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000d7287adbe0bc3cbb35ab8bfe69064faa83e3e64d73a0c64d960949e10070081a99c35d1dacea5a3b9bf312745acd6e600000000000000000000000000000000034f1995eb8631e080378b22a51ace902ebc9da4589c89ab557b6aaf685fcc74ec1fcf95f6b9a31b7a45cfc5a1610c640000000000000000000000000000000009f56712a46c0fbc199c12d5eb7abd60e660e2c6d437007c34954c6234a0496ad0adf68cf759f8ea30980c9a78175e1a00000000000000000000000000000000073fae1cb78c776188190a4d7223f7cdce9a36488193dc06898919ef4d5136099c3185d352028760c753e9eebb52ac240b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000019944fc459fb601bddf10a3a7eb55f34713d396c3208a10089b8f21f4bf0a5e87e95ccf73e0bd90474d3e043f37a72300000000000000000000000000000000158445bd2b6d396a390a0bd5e26256587f980eae84d7a592b2b4d788c452d312b854427185a770084e1b4c7898962e50000000000000000000000000000000000ba44a1b912645354da7d8d9c694b1d5a9ff2d642fad31975171deef3adb0f8d92b2d3a8bef6ecbe0b8e90470b3938d00000000000000000000000000000000012a040a72ab035684bfdf57bf473ff59cd1ee01ec949dcc6066e5c8afad775ed55123859cdd74c7016a092bade7f991b571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf000000000000000000000000000000000317ca8fdec8c7c56fa3812157f9ca8e9bbf91013dfc7052c0795a04a1b4649b2147d9cb1a61f2c114a705e5133729920000000000000000000000000000000012b893d50fe5ea2eb528d1a04bc8596b10d4714a0dc38bdd5f0a275c07c846970106c3f7b5686685f5c809e93c57e2ff0000000000000000000000000000000014f018a0d13c4c494f4a6b7e836f0f2f46c4b7975d91adb93616a0555910f53574add03b905000f8492465c9b5488c1300000000000000000000000000000000146eb4ef1103b525dfb5c31bcb98e550245732fa252a814824258093a2397d1489df8ca0228d4f5df0a00d473d1566c454c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b0000000000000000000000000000000010e9962dc19aae8e92abf32fb9c8eac44d77f587159af4e3b3a080748322715a958d953d3c057999839a47dcc840076a000000000000000000000000000000000ccafa9761e654ba54a46afff51384f1c6331264082e23f94fffd6c31a1b1b568a391eac79417657f40ce2fc9a154a670000000000000000000000000000000007276b111c94130b2608827156021815faf2be29ae42c454f3e2f95de98d2f5b98cea3eb18335a8fa00e5464f8089cf300000000000000000000000000000000053550896e867e237086098f4493caa2520e8c97a05e14d0ab7012d37b7fbbd42a90accbf0fe2ac99e78ccf0be5c9c58000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b0000000000000000000000000000000016b5bca8537059362147911da9e69ad3ecd3b4a7c43ee7d6d809f46c74c16bc7d69bfb5d7c727b4d5d8a356a0458b59e0000000000000000000000000000000010f3a7eefeb3033a733af7d20c3c5caff4c409305de8d71e08cb9cefbdfacda41bb975c92c5e5f2952c3c1e2bc6ca8cc00000000000000000000000000000000148f5b2bd65b71184ba6974678f709c5f9e3f1a020e3d4bedfa5f5f66478adac47f06ca2626c4a759b5eae09756cfe49000000000000000000000000000000001301306d1259059b5567154ef6e4779fedf98c29ea967ce34b78147c5730f202e1c12d5b5094219bf85fa62834329b45909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000001741f739459f5d462fc9ab55c68101a5a3f2741c05b4c3eec6959b2aa5e12493a19d1b33a9aa89337add642458089eb6000000000000000000000000000000000300d8b7988522706c0690da52d0a67ae41344e43cfa05d22feb91eb8635bdb970810e993e0ebf8fd63ab8fe3e048d660000000000000000000000000000000007c003cfba125692b88feba85e7288bf61bb25e04b1462f7a39b4198737010224ce4b73a320c81b1f70119af34d381d1000000000000000000000000000000000a4870c9de67517f4353de23af21fcfadcfce55365ced33a61a19e5de52f98721b17c6eb382970e7c4acd81b80a7bd2f6d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000002eef52fc72d5aa0456c13808ba548cb765e11cd0bfd0599544793f57c8a27ee90880e6577af1b76b3fe32c4e71f4104000000000000000000000000000000000ea99a4f6772f8114cfb3ae9dc20f11a34880a86088511e5b7fe521d50470148b43f866eb5bf4f67c523266bb55117050000000000000000000000000000000004bd802b889e6d18df7dbd65f39a908cf5889e14be51b5ebd423ccb63e4e5b35e429eb0d4f384b811b47975143ea2ef60000000000000000000000000000000018dded357c546d709beffff2da0c08e8059c720023234c7b53d0ae85750b3e166cde7faf340697b546b8dd7c13b1ce7bdcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb28000000000000000000000000000000001405ef521bcc60c55f8551fb2e2aa7b10117b2f96c03e8535e5bff48ae197b7e5fe69a40eecd25a67f430ca02edcc9d2000000000000000000000000000000000477d85a7dfffcc5a2a1048205362ec42b268e5fbd27ee7c8d4ca77b5c9db84dba482bc4b164f92db2c15cc518b3d32800000000000000000000000000000000060988548ede00aad3682fe827d1e993ed1cf118bec7cbe6f69bc160f030bf87c299d40047a4fb5ee27dc2814649a4580000000000000000000000000000000006b9e0579f82fcb8bc149e40b1199f5897ea48ae5eb58abd2002c923efd0f5275d24a579bd904e49b7447c4a03e3fbe423d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000003cde2bfc5a865cac624d9018c37c1b5746b5394597d79c171b25f84d5fdbc76bb90ba5cf9db14b3b8e62ff91cfd79520000000000000000000000000000000017596885262075e45db62ca68ee5b99d12223bd476e36ed4ddbf5cd56a0c6e9db5d79e7f95b96b1bc323d7c9fc5447d800000000000000000000000000000000018333858871dd41cddb7ad2f179f1f341b2ef20bfc7a1d3cb235e3a1a181e0da7251911886f0788e0f868e16520c5a200000000000000000000000000000000098ce44092980cb14e89faa7efc2906051c9a51cf7b2757dbffa49fafa3a9ba145f809f1212c27aa620bf062e839f83c2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000001180d554fd523a51e0decb92e0134c6064a17dd3aa7b11d590b9b6022f76763b1e20562da21e836e65374efafd78b77e000000000000000000000000000000000488686f793dde899a3f4936f07f9eda7918450966ca85b4715d6fee978d9d091bae1b5d2d04943365c076a849b3359c0000000000000000000000000000000014661fb2d305ec9e63d63e9951d0f081aeba99972b094c922d2797a1100759cfe150812821411205f563e22f01ef29c50000000000000000000000000000000013dd681200608466853cd3bfd20f146a6383151931079654962684d6c6fc3bd6900bb049483c1ca6d2819da456f67e3be3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e0000000000000000000000000000000016c8c7a2a1a76ec05770f2d6c8df35003104c034c76323fedd49663daa759caf2f4fefbe8d44b3abf1dadfec2a06cb45000000000000000000000000000000000837305004aba2e322ae29e8f0109f1c756a44b21c72733019e63ff9886a639464090770d12d35553f0002ad028332370000000000000000000000000000000005c8f82ca2d4f6785e2d76ca3a3d1ac67aedf78e9ac833c52cfda6289e6f5d7a83befbeaf753abce12376889caec312f0000000000000000000000000000000013595cdc9181ca70845c613663367ff774f073774688dc58edfd0c58de5ae12df5acd04a673b645371940d7f7e1601045d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000010f1b8b39ea8ffcb6a96bacd1c00b413c93d3f8da64dcf9257a7cf0264831be23ca63ab8d3d1cea21ed8d83ecaa3a0c70000000000000000000000000000000017a9030fbee573cb71330007900723f85e9e82530283f713f72e68c1d9a5ff9552d0da469a4f38b66e30df1514f922a40000000000000000000000000000000018b9020986a49213d4f3b4b052cf2fb65f82b9bc2051f20b399f2784b984ccfa2752ca576d352c7d65ab218bb8d5df870000000000000000000000000000000015a375a3711f5e9f85ad7266b2d307cac09ace9ea36e149dde5e0d5acdbac3f62e1cecba8be51d88f2143c3070eddaf0fa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc",
+    "Expected": "000000000000000000000000000000000b668f602b9f56182b74be413d36b03d2266d7165386a7f1f0d25d336d06d2bc5659e80e54dc71f153883292df1cd8940000000000000000000000000000000013151d305bba39734538fe9a2717392bcd134ef1f8c1879740c8cce981a2d70c94b23f1a71a0596e7ead55a55eb186c80000000000000000000000000000000000e5e7c268f93d8a9d3ce45db2a95be906483aefa3831ed2ab2aa357eca0ca231927c0e5031daa200784cba33b96e51d0000000000000000000000000000000011d57d9a9123123f9fb56e990626346e5c76bbd1a4b3349c3f7bc44f05a899f9c4dddd67ce5a788f85b4fb172385faef",
+    "Name": "matter_g2_multiexp_49",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006a7946b50749147573991c91f13cdef4e9558be37736e3d990c5750d31ba9711721b88eec529ea4b1dec1c935fafa9a00000000000000000000000000000000078d8a565b7f58b915c220882a73b6aaf100f2d54cce2524cc3a80d9b526c3058903668d17427a617ea045c3322ec502000000000000000000000000000000000733c6562cdcb28d740c64f50ee9d204af4ecc8de2c1719fb73c20f2580fcf01e1e494faf4386764e03920a4162049fb000000000000000000000000000000000421365fa828affe963d145d318065933d4d865f2a3d24469ab0db66dd09a574945f8a8b622d079a7ce1c6fb6c795a8f6e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000a4fdc68bda287bd819ebb0a296212ddd19cc76b042e134f1637c894ad64bcd8431392c9791f2eaaf94f6c8d189846760000000000000000000000000000000009d974fcb46fe81d81d62b24b805ab5108c9450e162454c3260ecd0d5356b7c263be5f78f6214cc7254e461166910d23000000000000000000000000000000001081fe3579cb4d8a7e7d43ca8cdda28e1f9ea8df83c6069f4162a2a0e68e0d5876b283193649018e754c5c8fef101f53000000000000000000000000000000000ca4faaddc4d14a6648e3515a8b9028190c17f771c7de086fe4624a3008d7e6e374c967f303d9511b9da1a95409e3cb3d51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000117318e376f2c130e5bca89b3d700fe76e9603adb22a5ef353bb3b5a8f641c85deb4640fbaccf94e025a59fbf2a41370000000000000000000000000000000000433428497ed89a43ba07d816df224809a827194ca899924c3844650a3800952cda8db82f2f8e513994ed9893fac747400000000000000000000000000000000064889f1cb7d6ab216fcceef7c4abf89be14ef93be2d39bbba2b74d06999dec5ae1941b507709d093b28e030700cf866000000000000000000000000000000000957fcb8658497802e78b8250373f77acc4ec47fa2c87e78adbb2daef70240da640a7945895940f76bbb80bd36b4ba24fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000013f9771c105462fb6b975b0b2fd20d0accdd2d95d879c8019b08db394cd785ed9f151d0eb1adeaa63bbc2686d1172b0f0000000000000000000000000000000002062a5f2db0a01114a1c6e8c739f80f598f4e905952101a244853078298eb443be6839b59d4f0c7745b739cc89ad8220000000000000000000000000000000015b5485439f1b94fbb3a8f5ac6197f0dc0577863f39c44b34d4c5437b6a82a704dec17529654b3037a9ee1ebf14c8d8300000000000000000000000000000000154d750e2a660205812d428cfe79aef4e1059f4e231024a665889d112af37e6e17e04cd7c926b6240bf2f616a1f572dd7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca058129949000000000000000000000000000000000231223930956bd2d36a89a0a0a47aa46b4763919455ad3a3581439d25a82c176569698fd5ca2b9429793ebb16c98e50000000000000000000000000000000000b5dd675af51c18d2dc76e3103da4409f6e8c1cc719a622d4a33aaae3f23e529c78b63c55b67fa999bdcc7095a4ece300000000000000000000000000000000010c971be55cd02e4c97031d3b25acfbf722e47e5179beb26eafaa72d4bd5f47cf280a99e0c3c4cdac05bf1572d01fcfc0000000000000000000000000000000002c1370919e6445994df1e25ff4a79c8cd8805f12e5d8c781e58f04dff68a97424b35d162d875ca2b3f805b4cd6d1fa641b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d6500000000000000000000000000000000169938b4d3c859f97a0627bd1a83fde725eafb7ab77b22cae06d2a776569236d834702081e78d61386999c938c0259b900000000000000000000000000000000091e922f00828488e324f9fea652ce1edff83d9f479e843ed4bffee27805a5025e7a150719b354b5e61f381ebd24d4ea000000000000000000000000000000000334ba8044d7d47795b59eb089502808a7ab8f18e3d5e1cc49acdb5020b3973fd21d6d82557afd748dad88e45a7623730000000000000000000000000000000002299bf949ef249b5057c103ecd149444635b4f636a2fd0d073484404c1ff4ef71820260ea6529bee6f5b07f2ba94de35615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc00000000000000000000000000000000076ab7838db87727fd653a3b561a2a5594518f296284bc24a7d215b1fbc0a6492d425078fd98f92a414dfcb3c92cc1d000000000000000000000000000000000022b71fb467dbd6d9b130763350bd06f52d20ff2cbd46cdea5e8b1525fd73bfd08f5ff171f9fa28050e9a3b296d3e9e00000000000000000000000000000000007e917cef0195fc589317d4a71c14022867dbc0db26c653052e2e382d0dbebe67a0f582bc0a27dd1dfb4703c545d0da30000000000000000000000000000000005b1d8651b86a403ad993c5cea4b6b82a0f8a9f8a59d4b94f10e68e9538a559efdde2007736aa9d04f585851a89af88fbcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000653c5f5b2d97239821d173036929dc716e78d835a80af55868dcc3e218bcebdc2a052d31f6a573572d13f3bbb14f241000000000000000000000000000000000cdbdc3cf52239a6d4bdadc273b00924de8730c03ea82bd20ec1f04375daa4497fff3a1726269a736706355e72be83870000000000000000000000000000000008e0285b177fcd768d3519062177fa1314c4370f872eaf10f3e0dc94e716dc6a67894d887f40104552336cbb5ed614f20000000000000000000000000000000000638db8269ea4c2fecd5b45955609ef6a1c2c6faf6ee5a8d777e0b38f16d1acab2da7fe7b6f6ebb315ccb345835a21d94c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c40000000000000000000000000000000005b4f74cd099eafa6ae59e7105873d4a46e8e5985faf2d26ca564125dca93b1c48187ed7afa02cde8b52df878e1aa618000000000000000000000000000000000cda7f9eeadda16ef757ee8a98be147d374d3a1d40790d20a1ae42c9ed38e4fe22be76ec4f807cf93fff5c6efdb50d1c00000000000000000000000000000000121219b0b0d236a89a857c02249cc04c22299d041d95296dd235b3639416337f5be4a2ebe92a50d192fb748d5d4dca0300000000000000000000000000000000112545a4677ca7d60645cb8bd98689c4aa85a68bb62dc68c0affca5a17ecf0a08fb9b91589d08712b5af4aadf31caad2dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f2000000000000000000000000000000001342460712b73ca0ef07d953c32d280a3441e108abdc2d133265160608986481df3563c5dca20f209ce078b13b49707e0000000000000000000000000000000003580a5b4a7f6d6e066ad9073f7105f6cc1ff35ef5e79a0aba7f48ff2b732c7aec72cc9c5f9233fc9c267d8aa37ac17c000000000000000000000000000000000bb7f32db8a4e341cd9f8dd3b5677dc650cef675f0923bf2e5c8b84c33d447daacbf68631c2388eac5698495e1ad5a3a0000000000000000000000000000000015bf9cd1aa585eda2910128f2b452569abc1c94bc8bd308ee92b6c7315a56fc92d6cba03334bc36c137c14eb1f198b07d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c100000000000000000000000000000000091bca266255d692cdfd10929802d79b474706d160033495decd11cb0758136ec3ae7fd4bb99081e44dd7f25224e009c0000000000000000000000000000000001fbba1ba796416ac22c92f3741e3b268d89fbf0307edf0f25c7c12b5cd230c41582ba69465686ffead9f8363dc0c297000000000000000000000000000000001139590315fc4d81e3e747a53e63ad856635050367ffc143c1422e324d5fe9e4fb90631ff8bba764a87b8077b571aa0a000000000000000000000000000000000dcbba28afd445a57db762d08338a26980b4efbd11668e4050d18234ce35a909d6b563a5d3e8e72892514431fabf0147aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000001322b1f1388a9dd2853829bda1a5120250ed08f07c84fa398e59fa2577454f38f0a76a1e8db897bf15b4b50ff52a847c0000000000000000000000000000000017020d7de1dd424de53992c168d924c42f26231d184ea3cd9cfe64ad9c82ad067540b2d9ab18b0fd28477ac792a80c4a0000000000000000000000000000000000fabc0769b95e6feedc2165bd6d324b7d16247b79eebc1f09d849792255136538e628bd6ad9b86af7bcdfdd991fc31000000000000000000000000000000000144f39f792bf5585f4b49dcd3fcdbb61cc7ef471e08af4c15cfebb855f0ac8d5fd057c9486e53e8e1ee4f66bd5e943ad106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a000000000000000000000000000000000c27ac29db98fe3038fe5f537d5ca6faa240602abe11c6f530d9b18d763d6dda3fb25f9538d316e6527c114405ae54f00000000000000000000000000000000017ecc872183413d8065a99a2d1a73b70150e2c1fca2c13a731a39b52aebc6db79772e91f115a63f7b23e5fa231df697a0000000000000000000000000000000016b9715ce820b619274202b52d7e7bee9a17aaeb06c2ecab8bc77c670bd4c714789e4478178d94d2aad57e7bb0b7a4040000000000000000000000000000000002d0723a3386248d8597d2b63289300de6a16011a38985170a1652ff81ea70a78459b3ef252cc5ed26ff1ef1ecaf6a42c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000eb1ddb7306d8d2858fb57dac71f67473b813f37f02d73b17f375be86028176cc1dd84347f183cb7d427b861be34c3d70000000000000000000000000000000009a8811ec77eb21f2b33a591f2fe6d7b74b40c5045ceeee275912aeec664838f332bb49bedcd958ede0af0d0232e76ed00000000000000000000000000000000156e28ee3c40c6f18c6059e06ac8f7b39fa23e5962f640ef3afce13c169346a4c8e5c2bcdac8fa15921a4740cc5a0f2300000000000000000000000000000000084371522a6ebb1925c8fad3f20277c34e657aa71abf8ed7d323a10c14cbbb1a9e0e54bace32eb845e6709c1c58afc34a5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed49000000000000000000000000000000000be2ef9ef38a5dcb42ad31b1415c8eceae625850db4306a26a0598d4a567936d75b701c81793fa7b42d158df2dcb0d5d000000000000000000000000000000000851b82b59fc15b89e33fb618c56d11a07116ea35850583a07066ed97b8a864f3766c0cf921d007a6cb43931ad4fcf8e000000000000000000000000000000000ea8bdfa3c5f000d7cb1b5cd69537e4104daa15ffcec06f40a91b972d8011e5fccfa911c55a07383cce6760c145c39e4000000000000000000000000000000000652a4165602978004ef702103ef18e8fe7decab1522a76486c742d29103e3bdf6dda2d3cd64ff1b5d5a76f4823bd363083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a4600000000000000000000000000000000015ce5b43e1fd950b77e2baccae8c99b82f38bce09989fdc5d402420e7931a38b7fdac5a254b0cb9bd8fbb488d02493b00000000000000000000000000000000018c5b3ff46a04ed114bbf56399738e5d594ef8dd1d5e2e8dc23a0097893be3da4fa4662686a6dac04418fd2d344e36c000000000000000000000000000000000efa3e970a5cd0c7bdef6a2df3be9be18cce63c10c331a18d628bbeef30488ef73d866f3c8804acb3bd375542e99eae6000000000000000000000000000000000e966d9e2f2d47df5d661a89fafb6d4518fa1544ab7a56716df511cbcca99098f944a981c9da569cf95debb455842006d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d",
+    "Expected": "000000000000000000000000000000000ef06b515addb951b24e5d61f6e6eededf5f93f9f17455e1b563f187f73394457b3b7c1b90ed454218f8782d2bc848be00000000000000000000000000000000167398608a87490fd17506166bf54636aa4dd6d3e8c4d42995bcb0262268eaf2a6d828b295434f45e3e53703aa67cdcf000000000000000000000000000000001602ec6519e4987a052f97eb222f505e241d99602c08ea9c41bc95796675ebf6a819aa0bf87319f29dfe47f45f3c8c7a0000000000000000000000000000000002ad4291ece7ea0fcc9f4440e88eef693b8dd53060ec847bd27d74cf71218eb6210a71895ff1f1f4537a901090f14de5",
+    "Name": "matter_g2_multiexp_50",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000129cc9cf82aa30671e969148f058a0b8d275bcbb1c8da66e52998d9555dfaa075e2fcf39fc18f305940fbc972cc5c0c0000000000000000000000000000000001252482c1419ad72229a00d90f1c09d464e896f47db91e9680efe36822e99e1ca7a2b5ba8b17b5929fdbeff8993bb18e000000000000000000000000000000001287d5054bf5db038ec2f7b7a3b79848fcc8ca42f9e19d5e21d36d2256f97e0edc2608d19c17d3714024e1e9e86ae264000000000000000000000000000000000f6262669e30a5db67550cdce8a4611b9501d02cd4e950aeedd1c87c4f0f63c74f35c802dafcf91c988a154dd690103c67c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000019cccd010df3e668b1c3ec053e76c46e45d01a4fe02eb074e296df2a48e0e4eb647b06c40cf64a0048a8fcc2b0cfa6100000000000000000000000000000000018e07bc50657f3bbf736c38518933e91af29e3bafd776243296cca3a1d975116e8b428b050045a61069adb23baa22d3800000000000000000000000000000000154f51badda1b828346986264b01fb1be4c7e9b570ab63a5eb15cabe9412a2f9bbc6d5111c638ff5118a4f6d08ed055200000000000000000000000000000000064d4e607a8994c0bb65770a14a14ed1b68c766ac2aed45a44c0f7c7cd4c3ecdfa077206812cf9b24e35021060a3668d2d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba94000000000000000000000000000000000b9bae5f720d9bd3271a71d751e4c35c39ad30fa8a67846107ff769c455e42465b2a39dfa32861634d5e323878f56f4f000000000000000000000000000000000a1077046cf5c7a66452cec2193ee21c1ace50dfd79f707c9297737f13806dc05e9e1cc5d1fb4c87b250ebea5a4ca6c40000000000000000000000000000000013e1fbe1a9b5f2ccff51120590ac0cb00cab502726b43a6fc12459e27bad4aa41537d6f3cc94a81a170998768f6a0fc6000000000000000000000000000000000a02c551ecec1c7a415256caaec1b5485a42f9ca8d897cf26546ace1f2bc8c2d10a353b8b84495b8dab5e3c60881185a58c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e700000000000000000000000000000000021b9ab3ad614816d7006efe688e1ae8cd99b0c4437d4363e557642a7cfda2000db6503b32db36b6d1ffe40d967c5efd000000000000000000000000000000000b7fb0ddd9eb2be9cdffaf8f8c593a9296c4c7deeb1c714c11863d71dab1e6fd309b75c41e25de3cb6089726b43427f0000000000000000000000000000000001277065ea9d208777d0fb7a6726e11c8330f0b3ed3c6716acd559aab19b2fdafceca8126c9facc43b9d80534c07035a20000000000000000000000000000000015e8c12065d601dd5ede75bcfceb7300bf6f9fbcdf68d2f093b7654d80d3e565135d64137dc401d691a45fe052f58a6850a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c32000000000000000000000000000000000484c4f9652427d0649c33e93d666dcb15bc56669c00980c53357ecee874bcdbaa016236df65a4339dfbd44e4eb0823c0000000000000000000000000000000013836a7275c29c989891c94e756cee9d6c54a8f634fa570655ee44b7c1e34137edc33323dc0d1f3a0218039fd6f7013d0000000000000000000000000000000002e88c7d5fd87e97a0de1be95021821942a8004115fb4fdd9ad26b7e0fd171f9c7e6f962eb179bdb95ef960cf9396372000000000000000000000000000000001636e351a0ed1a260ffe0d1355e6da288792fc97a7310b040cb9fcd5c550d85d90572154d58a9847dd5a8a06456bb2e43b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000007579785c14fa012cb5d6c116d34dcc15dfc908a29e90de3bbfb8c9b44e0b4258644440d7c78d751a007c10f98053bd10000000000000000000000000000000009f023538822ceba0883a0e3454121dafe8e5e61d4754b54e6417c989efa998334641d458591b3076b615937de065cfd00000000000000000000000000000000130fe7f2d5e0ffefa67ad3378690c53a6e68de5504f3691de0df3a24c309619bd3a345bc2bec4dcfb4b77255cbfe09980000000000000000000000000000000015bf85ed997eef4d97a81f1d75825bb4409cf86b8c8e5f4368cf1e4c803f9e1e23a2a96f7b0a08e5cff55a78761ebce21d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e3921000000000000000000000000000000000a95597e4402bbd17c20dda088f0134d42f14443bd519b3511b28fd8d395a0e50758386498388ea6ad0e7634587336c1000000000000000000000000000000000079f348d3de505875c5192f795cd77e2f7385ed447b06f2dbee18e85c832386b201cb3eeb21aac3f258d2c4b0434d48000000000000000000000000000000001895b1891a08ea42eb1f68698abc20394ffd66bf0c32979668950bfec5cfc8425314eec2ac17ba25f29133a8becc9f5e00000000000000000000000000000000146160336d881b24c6258a3a86c08d346900680324632b6d5d4582ee0865a7e5f2d01677e5e49c5a4179f8382e49d1566fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000013cadc6c394efb2f93e00f3976dde34efe75adff34bfb6f5e1a150b79bb5baf6bf29fa149581fda48faa68653cb61e300000000000000000000000000000000005fd25362d87f9581a202b186d2786d2859faa9966a1ceae747dd7a48749abd424eb9813e44caada0e456ee8bd12e99c0000000000000000000000000000000018e6b279e2b545acf1da29dc0504caa5982522546f83d4d3389e1fd6cb5328d4a167926a00ccaca402b3a3cdc67d757400000000000000000000000000000000089a9992a36b476fee21abd50977dfee01d7c91b24b3e26d7c15b2301352069dad920f0ea93a3e477a48029eef35605f06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e1188620000000000000000000000000000000005200b78dba7e423bc23e87c5937b464e97405f6461d05bd9d1d0fbf8f3c8e64a39081f9e43b4ec416198dc44db897e7000000000000000000000000000000000bdba1ed07c4a570359863a1098a73830818b3fa5b222316a3e0692a4ec65e59ca6b4bf5f72f8c1384e73e807d272d6e00000000000000000000000000000000073fa3eef473707b6aff37fb6f829f0fdb7ae808e85ebba4d4924a185c3656eb2856896307b671080347cebc32e958bf00000000000000000000000000000000076b56330f07cfc0ab34e98e2fa0ce4702b296a00f6ffee07c3ab523fadc048a047ccea7a9003c090951e4ef698d14e5fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab0000000000000000000000000000000007252747c8275f87b21bbac4071c1826d166d14e6205095e5299315d6b6a85aed995f9ba59a2163ce2c51a8e60eaaeeb000000000000000000000000000000000460a000fe29cca24dec469ba5fb729edf3e443bb032d488cc99102a614a5251915267db003dbf395132d815ba78f262000000000000000000000000000000000161c01cb4d0942faf2303c108604babbd4cabf5d3d30c13d7db9428a445c7f72d96a7405e22e4e451058a94e20068720000000000000000000000000000000010ccf8a8ec4e6515b20e07057fb8cbecc5defb87480f3e32a1bcd0cfe239e00daf6a390c4815ef6b85be1f07a4c4bfbc4a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf000000000000000000000000000000000e6cf781162502d2a758d0f96946bab887591b7c9ec9f67a1b0b962e74ee514e84c14bf67ae3c0a9ea2a3e472b7ef59c0000000000000000000000000000000001542b4e97f1e8a64ffd51ca43137b0660f897f6b3d5c6fee598fc4dd03932c3658ea55e1e9e73376e51df278ddd3a3f0000000000000000000000000000000016dae882ba240343e752eac68122424320d1acb1fbc4bd26c3983dd91325f25e1b1f06213e0e06c142997a13fbeca597000000000000000000000000000000001138b71c95d4de320f02e68dae9bb0de3e5b317cb596532c5cc18ca588cc8566c21551d7d55d685591126b9d9e466455cd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf000000000000000000000000000000000904f6a09f3a5f5baac902c702b059835737c06f62c2ffe9101bac32f854e4d72f74031f5410a5941612b1aaacbb50920000000000000000000000000000000012f39e7022150b2be12cdd621ae23525581405021b21cb9e55972724a22b1aeb2e15b135ceade132d3310e050e607f65000000000000000000000000000000000a92b1daaf23524904d74c3f149fcd2c98e3a4c257113533e7cc59c4656b785aacbf0ba6b9df0dc17cf7c25f1ca698c5000000000000000000000000000000000a20a5d7c0aeb16ff498f46bf05e512784d120b9c3c8b2877411852d7da3abde9e83a6d00213bf69ed88bcbb051a486daba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa00000000000000000000000000000000153918807d7da07ec7014154f00a558ebe0d5fb48fba4c16488d61a826a1eec28e3828d6744300c04207e8ff1cb61211000000000000000000000000000000000a755480457896c5a3fde35658e73fae821151c43fb92e9ffedcb05fabad37cb68aa24e029fc33a2518398d723c4859100000000000000000000000000000000148798bdc5b14b90aefc38946db93be1754f15d78762f38971b1e64a53fda92b96b0a70ca2548baec882887ae7f636910000000000000000000000000000000012299fc413dbaa77cf8867e331bc0602c4fb32fe44a150217de9e6391374a9ed83781034e5775c4933e13cdfffd25a9e6ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c000000000000000000000000000000000fdfcbcce1603198fa344487d2d4838b3ff23fc0a73a76222707d9f8623f0b87dfc816be8717b0b12667bee460ef40f70000000000000000000000000000000015036dff68139419db619912e2d19b7d2a2d637fbb8bffcd941aefe2eb4d24c1f7dc32f4f53d4cfce67785e7c328d6c4000000000000000000000000000000000fd575be9bf54128a9a1cbd366339c993ced315a840d60f8b77e035352bf705c01a9def713e8cae3001dc1062cc0723b0000000000000000000000000000000004015ed456125cf0f46fe0093b81ff9315d955d470ad756a9303f548819f339e137305c58e6f4d8db3c8bbfab90718d4f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000016d31e68cfdc5823970c8c2ebc53c3d4517792c44e90c10f920a819e72e4a6966c59a691b905c8b0b612065c56d86ca40000000000000000000000000000000005096d516e416fdc0df552c2688c74f1c067a3e5e7fd782479bfa468096e6ee3e601bc23d2e38ae7500325765483250600000000000000000000000000000000092c994e9dae287bb6450607a4263bcf6267f0f66ba3e63436292af7f6bc8e4ba794a12792b6af49ef59b5fe50ff6d3400000000000000000000000000000000175a645988f33612e969e1d91b2c30e47ec655ad655d89cd8dac151c3bd194cd5a8c28b498b1cc2f2966b7fc37cfc8c532b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a0000000000000000000000000000000008d09ee15c80facf7e32b15418fefbf7e80400acf37f2a1bc6ced88b1591bcb8f86b45b544646c5fafa71b5b103b927400000000000000000000000000000000060865ba68ed8fb3d0a05779c278352b22d4244edb7add23d985a2836d2772dbffc3c82c3134916e9b0900c9db6ead8f000000000000000000000000000000000dce53bf8aca1ed44bee47096dd988689c1e32e1e65a5f8dbabab7c4edba866132ee2c036aba5648d0dafa9a26405fe30000000000000000000000000000000003319995785be720860bbf48692d1507185d898187993865648ded74d3aaca45df939c6dd986db42a51bd13579a55b8f71a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6",
+    "Expected": "0000000000000000000000000000000010643af30c3cdefc30144c5d7cab17c9c54adccb3294ae79fe5c69376011c159be1e43940640bf5d9012ccdbc997e2090000000000000000000000000000000002a22b08904ea9ca99103a01caad745dc2afb7b6d23e666770e81a97031de921f9d4d1c04fa941c433b8cd9cafced3a10000000000000000000000000000000010808e5518eb6cd61eec8820b9f279dba2423b1a3677e21fe3a0ca2ad49fbab2995de1c5adc9ac867de79e3b40ffddf30000000000000000000000000000000003ce1270644d71e0055345c7463d72dc119495bfa04a818dd398d944ca46deb0aee8c7936557754fa18225522fb79564",
+    "Name": "matter_g2_multiexp_51",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000059234bb6d1b66985b79e6a2c6cd158d37fc50ccd6e50ad2fe3b6a02ae2ad35b8b2be92139265957ea698441e780532100000000000000000000000000000000066adc7083a7f3dd75a8e431a36632dfcecc2527f261e961335bbe8fc8329d992880736bb41fffe41484f68c38fda61a0000000000000000000000000000000006aa0794c27d3f60debbee292c28b430c159cb2874b9467312cb857a8777058580f8a2d3b9bf4b8b732edb185cca6ac10000000000000000000000000000000000d81f222ed6acdf29437adf26d2b785535cd6d61b329df98be04114c3ae68bc6854e275792fd48de3eedf6ba7f3849a2e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b20000000000000000000000000000000013dab8066757b8bcce2c9965e600c31b792463623cd5561f7f6d55c5a52c22efcbe48b8684fc2dca87e2945765bf565600000000000000000000000000000000198a0594b5e606b18201fe2706bddffe7bee6c583147513333230715d18295714055b984cd3ff8d1127f9420863e3a67000000000000000000000000000000000ef77ae1e991daea1fe8338cf236ba959b22df4b24f00c6c01483b6956c609b805ab89712f80892bc0160fa3775907890000000000000000000000000000000004d30f5a866a100dfe469d4d0c47872245c4cdcfb18d3ffa0de422691044b52d2b9335682dcbf67aafa9275712ae3f5512a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea77783559470000000000000000000000000000000008010bd5fb5e222618bf4f919c203dadf9a7b7597bf90e16772020614481a0963a8e8b1bd244661bd33e0d147be7663c0000000000000000000000000000000007e21f548efb869a28d6fe39b999ab7fedae9cd6cfa531fe608476ef30c8703951839476811838608dac1aaf9cd87eed0000000000000000000000000000000006cc674c464f80cacb2156fc1eb680938cb38cc166a99f72daa50f9d2c40f10ff07e447d7bd5e59b6b22f0dc407dd8df0000000000000000000000000000000010b160c58ea82bc3904302b1b4fe83d1883efe3c8f52c4e05a3d8681e604eabb1b7f533e61c51e9a172987383506e7b189a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000000d4f8372d1138489913654a7567735be1eacf8c7fd497c2216bf77a94f483bd4a7daa2c8232581d6815af9898a7569b80000000000000000000000000000000013676a1f72cc2ed80fa24f70fe1c52aad9ac13ad6cad1f519649fda6ea3787d86ea164d9ac86de96436e9db4fb4aa44f0000000000000000000000000000000003f7644f7ddc9276ac36aea5c36451f3d5d6e4c508932b16d5677977108f04deace5e8cf0b3b3dee88c328b6030f3567000000000000000000000000000000001953cf03effb7de9e62937572850e9fdeecb74fb4aa655de1abdc6e065920e6d2e51ff28880f33341443b5e6652eff4827dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c76a5bcbd2a61172fdd53b351d143bd30d460e398c9d4b7094a604ab2c0d46d6112bd8a5483c9935f0bf6d84df04b9500000000000000000000000000000000116b15825b780c49fb24617dca620e939e2528e10c49f34971736c82cd35fd3965088595deb86eaca3d3239c6c78a84f000000000000000000000000000000000a0cdaa541dd96fefd46b303b88f1dd4d24257b910a596817f1d946873cfd60ae58a88aa687ba573832331e8fc158db50000000000000000000000000000000016259f7285de159a2c6d6d8687ed348ab97e8cf329ea5de49b6d708b6da5b806bd012ca3641c50f479d85921e20fbaefca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f84000000000000000000000000000000000797e815a98d362e1d7e2ac1fdcb477ccdec8ecdf340d7bded36856ce30e92b661669b38ecbcfb0896b2fd75df9b734a0000000000000000000000000000000017916c559db6b4b28b798c2027e2c70ba1b940212df8a1649b9f6087120660d698bea81258e2007edd4aa7d0d535bccb00000000000000000000000000000000167170a76db0783a8c3228f8246502b15d342b019fb44a46b514f4ba2de3ac66e435941adc3d91874371561870ba87150000000000000000000000000000000010097a585eb9264ea96904d8534820be185d8d9e4b1616439a926c0ff8ceb6f2bc082e5712454690c9c05b8018a996235aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000025eff57e1f37903056835d1b4133ce064c86947f35859817b2cccf1a5c3923ccca766b3e0affd20a4a6df62a45c31000000000000000000000000000000000011158fce4ade070629162b2b6cf1924696f1f7776f3d623cfa3d54c66fc17fa0299c6650b709a1472262fc0abe8d9557000000000000000000000000000000001828a65fb90dcebe25413566deacf0677a3993b39d68854b264fe7807097fbd3106ac618545d3a6a42e197c65f0d2a7100000000000000000000000000000000045eb8164b6ec874467286dc3626fad3c01be61f6a8a88e5f88797978463db648a9b8a1e1a2589364ef2879cb5f75423fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000011847bdf2f67b40aac3426716391da488a8f0462b68bd35a8c1c762591e2f426f48f979a646a094bce16bc99cef7fcfc00000000000000000000000000000000092d61e408120b1549fc8d2174572eb7ed3f679327cb89754f326fa72fbff79e98cf5ad9c94c14dd86135e9aacc98b98000000000000000000000000000000001440e2f4ee2ba254a780a31b02babca093a38e5a1ac09ff388080b6c60918ae5b26e1c0888ea0976527ba103b257d02d0000000000000000000000000000000019797e49808b756128866fae0d6aa7e755a1d6f07f7e6a877bee150fe9adf0cfe612350c5a0e31d68cbeed226fa56f2a28d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b62240000000000000000000000000000000016d723a64ee06a7a631509c6e64b1c8bbe199952da532dd92194147bce9942cb4a76f2358e6c7d263916fa36e2c0c09d0000000000000000000000000000000003d04ce655cad1d63748f6eaa9912d6474a34820986835f60c812aee9980d3ebc18d6fd856a6de9546be024b2e95126a000000000000000000000000000000000ea840bd7f76f8e944f95146cdc9692d97e6a2d7d16d4a7f054f81888472da4d60ae5faccb72d3a05781b399536ccb1e00000000000000000000000000000000155a1c43c39e9dfc6d96e01c981662900fadf1a46aa1c2fdb70bb34e94dcbe86c4f255e259c771ea8ede50b388ceb2f61208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a290000000000000000000000000000000008f189d97f7d82aad87fb71d090a5c99d94079c0b74beb3dc860d440c0f46727fc49104d671bdcbe5b9551552e18afc60000000000000000000000000000000010c4edfb64b8932a617c316820cd27d3f6ffa89b471949362762af8e10d25265b84ca2aafd3b14f33c39a4b533da60d60000000000000000000000000000000017ef3bb919b087fb6745bbf115e2929394fbc9c89f65e7d591f15da93ab785aa6828ebb6ced99d3506810647d28ed814000000000000000000000000000000001591d8213ab349017cc93f1fbe6aca6765dd33ac1f468621e2c79e30aa73bd7606a0e5ba1d97ff03e0029dbc8ab1c5f4d3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000019b3a2df3a9571b066eb451e34d8a38c0d90b6e365862bcd92ae76195956c21c59441f0cd03cc69abdf4ba069759b87f00000000000000000000000000000000082537ef7f4bba5f32db4443abc8eabceef643b0878ef83860d75ba508369a3b459cad96f1cfd872df99548f656b0f9b000000000000000000000000000000000b2fda5ba0c405c9481edd598181ed8a59a8a18462508af8c5d66988a7a58a5c9635d93b5e0ee310bd35e0091fcd4986000000000000000000000000000000000af7e15e0052576f82e36e7e2b614dd835a290e05f2ed9dad7f508b4c04e8d437e7e937a7f4c88b5e66b06e0beffc4df7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000017a1d5add5601010d138263b4793149a02e8f4f7cfaafb69fde7b843a51cf5f0634e26b6e5e3315420d44b0fd205230d0000000000000000000000000000000013ea863ebe1b1cfcf4164d78dbe8fc809d2b82ef3e5a2589ca1357e48dddf2696e910a90301ed910fae77a1e462a5b1000000000000000000000000000000000012b40d9f25dc5a61454ddf1fa9c38e87eee60e55938b411bff9cf2161ebd7d3fc930131a198e7e97dc90cc245164e7000000000000000000000000000000000054f19ed8e2682caeda10c252f11706e7f3b65c81e7ae0a617469babc5f3268fe5c0ce2e85d44fb6731e8ac132b97c3ac5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f13000000000000000000000000000000000ef0378945ae4683666099be36de3e60b5bae9c3137b702e5e4e35afd5c1e81d033c3d6b1debf5bf36bdfc4e3af37759000000000000000000000000000000000c37074af84ff596ff2c7ff963d96968464d6c8d88b69af64ae883457d02ee9ec80720661f39019230a6531a0f2952bb000000000000000000000000000000000454e8aaa2830f07d86eac7aca1d7589fb06aed646146a1b90f4959b5caed73131ab231313b50c15213f89566ed87a3600000000000000000000000000000000143516cd7a1b8da41226cb828887a0b3314cf4f87c207d1d84e9c49f0f7e548ab99e635bd126d49fd2e4dcea98f3adf784ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000019384e15a8754c6d85bd298ed550a26b51b714745bf2980b4920d6e73f59e657d85d3e86baa9bcf7e971233daff99d02000000000000000000000000000000000229d233d605a1a9f060605ae366a263594d8fa2b7797358ffe4c62431b9718d155d24d80bf5af1c806f447b92fcfbab000000000000000000000000000000000bbfb66cc0c7bcf251141c540f712fe9a359d1ed36d228379a1f3791991cccb7dfe1a10d40667ca062cccd55c9e6b08d00000000000000000000000000000000150a4d7a003cb81423604c13d0c5175183ab5f459b96842939f5c4cfbb9196db4667bb4382d2d5c92b70800adf384569a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b50000000000000000000000000000000016352fb8e2751f126fd0f889f2a62a85b95c50d6bda7704112e4487dc94417218b0daa1dd6b998662af2582c44b011c90000000000000000000000000000000016bf4c60eeaca103c90643fe0969c2c261e9697ddbc02279f0d5afb5c905a984ab2396db93555cc2dd5682a1525446d00000000000000000000000000000000014be742feb1215cbdcde21e974c74e23c7bbc2cbfaaace28cf1d4f2b5a77dde2f3910aea74bc200277e6fe0475208057000000000000000000000000000000000bf98dd3e3a8b13e487d8b1a35615b0c6b0f514f9b8da7d6402586f113974c8dc9561db797a96f4f8040c1765518d175650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000362935e552dd01b5fc5a15a76faae937d7ad086b0a67e9cd3558287274106623deb85b6410bb4e64c424d44335f3b1e00000000000000000000000000000000096f23a54cf57aa3306df0a0a4f45aecb9b09bfe83878d551a59c53e18efc5a9f177cb7fdaec1648f66cdfaebb15c61d00000000000000000000000000000000135271fbe0cc0987e82f3430eefa8e3cdcc1be4a441393bb3fac0b8e8f78dc47ba2b833d9dca4277bd60befdf33275cf000000000000000000000000000000000dc1b7512fa5f9d4ea3f4229d947f43d7dc46b7770aadbd7351b6d48d525d0144183f2c84293c63c68d5262851401ae0282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57",
+    "Expected": "0000000000000000000000000000000001c11610b63eeaf9e00552a230bfee290ea49bf9c93cfea1b6f684c9b5a07f341b718a0070534e0da9e6ab1239d800830000000000000000000000000000000017e8107113714ebb1743c34d83be3acde096bfb6cf140e943ecd0831ecfcd097f58d25a45005db61551a01d9da46de10000000000000000000000000000000000c2eff6c7c25885c514aadecb8f0465a0fb4385eadffa082e8d4f497b10df2395be5e7760a87bc26772dd78701146b730000000000000000000000000000000011ad4e20f5c1518c72f75d67a897f30100dbb83365ef7729c3501c6f266d6002edcab8c8bc1f449c30ec3624cda13809",
+    "Name": "matter_g2_multiexp_52",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010a26eda028649789d9e01232884e4e5a6b8e0b17169b9d64393e2568b09ac4d3e61a5996108655c24e76abe9e3fb7380000000000000000000000000000000015e4e36bcad524f8aac7f909fbb884e879caa735f80fe9890d7874be919ee727beb2a074984c047dac1d02b8712afa3a0000000000000000000000000000000012458946f1e853a7a45861a92d4ce707e5aebbf69edfe69190c0bb130141a10869e2a73e06785b568248d5b1f647e63e0000000000000000000000000000000004d8061f25edb5a510a2db9e1df850518156138c78ace50f4c9ce47734a0b14352f5283083a232602a070c3ce94c7bd93d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb90000000000000000000000000000000000baa40ea518227b007b9714ae6eb5a4e92883dc75e6328caa780bb2ffee7573dcd7e9ac47821ac449187569986bd2980000000000000000000000000000000009d43d61f070ae308c5c285915600dd9c17b7de63cfeee6fe33c9ba857b3c72e057bcb4d4ac2b492797e7d785997c18800000000000000000000000000000000185215a7fefb96b3ff9229cc3239c3ce5202a97e275ea9b1541d7bc0a2931d7e3b01942febb45c6e96e66e3605744afa00000000000000000000000000000000103ae58b8066dd62c46c14c593c768fda91b90e4840b5560c974ce69b86bd6d2c13f689b72cf9619e57c9dc8e3d3fb15e71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000075585f862c0e0e031efe12f31e159f2a8b89825ce80fdf65906474f0155f397fdc666292f6a7384cab790f071335c49000000000000000000000000000000000eba3d37a5cae738ab99ed9475c2c7fbb88ff54edb8490017162dbb16c8225102158a266fd4ba7570ee6d5ff6cf3f5d400000000000000000000000000000000135a0b0a38c036919f8389eb7bdc505a375fd75d513eecf0cac134645d60fb6030a437ac6a0fbbd167b7a77a927b3b0e000000000000000000000000000000001688fabd4ad751598ca036ec5ef6d7b314980dce7d8652163e89fad01b233af64defbcf352743ec478af42587f58177dbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a00000000000000000000000000000000062a74a9ba0e2e8d95fca478be8d18fc716243b1faf7365a55387fd7188021f53bbe780e973e7d16c9db236faff176cc000000000000000000000000000000000f949be3fcf9b38995624570fcc9e7df9964d038eca189336ec39d9e0bd05148ff7df0b48436a2cf6e249e52248ee8a40000000000000000000000000000000007472e7c366419a0cab844522c46356acdf6a12cffae941fae3d3b78e7a83f0446c945bdf7b247abccaeeaafec49026c0000000000000000000000000000000006a564e6860b97feff368fc9a349282112e591a7a6987fd10a2d4de8ae4384ce229b9db9a93445f727eeec55a6fe5a9def0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab7000000000000000000000000000000000c073d2885eb125d3e7db48127178bea2c5bb0f09eec7081f15bc6fe6cba156914fe1b1fea6cf14a21a328d831523ec300000000000000000000000000000000010d93564b2facde13d29dac198c5f5fa314a0398f30c6fb7fc9575bc83d4e97edcc1c1d34f78728729442777718f54600000000000000000000000000000000136a4ffaacf0b4a607c677ed343c1ad41a1eca49c7c48fe73ab2f74084a07cff18f07f54a7f8ea1bfb7fa3667863bdc8000000000000000000000000000000000fb0c007a907ecdff7bfe2242097caf0c5001124d112689a74544fe4fd85be9771632e7267a1cc7e9f66d7e4bb4c954c3ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc000000000000000000000000000000000032bb9f20fdb19f578fac3008396f5dd0a70860f77f8ae7771fc6253569d47b72751cd56bd373dbc5eadf55b99578861000000000000000000000000000000000c4a4bfb5ca6f9c1bd69d7377c6da405afc3128338dfddd9aea19aec5e1e0f547e3febd28445af5e27469c87c4ac15280000000000000000000000000000000003b551547af253d07625028db4b9a8da2a857bc925620c5d561bbcd3e063eb460d9407cd4d4813800551e5d0d23a2ea40000000000000000000000000000000006d5c69a251e9a042c66bd4ee92d4f3cd4e79704b1b215c15b319e09cae0d798eb201be24f407340dbcefcf2cb87da5ae5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a5802e388f7605bbacd0bb65ba96689e223379214fd7a92de9a313f55d66cc71ffc9ab3f9979b75edf55647ad3b6c94000000000000000000000000000000000f86f968b5c20a81f18074803e1ec55ebd73bc87451c48d5bb61604ebae46538dcc9d21cce062abc07b4b9e89c85bf60000000000000000000000000000000000f9fceddfa8fb5bd76fb7c8986372c32ab9fae3c26e9fedae892bb55178fa2f3432e6eab5043496dcebef46b20bf5824000000000000000000000000000000000dcf7a118881aea4e6a0e4e305910d4e4a5f3d0a8800f52659ac26f122bd63c8aa2c5583f1121275adc9af1800a007fc97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb59260000000000000000000000000000000003ee0ba2b1de438abe66769124b97a951ce18aedc8d9ed005628aeebd90efd316e7a3c60cb5a103d6f72e7a40ed8f44000000000000000000000000000000000119597c99a7a16d8d35937ea15539089741363153ef898d6bb177d9a9b6c5bb4b79728155eacc5d82571f398ac6c32a200000000000000000000000000000000116184ac845a28c4f96641ec19a07e1f8326bd45e2106148f40277ae6fcf200d64e326915cf5c927222def8deccd4ff8000000000000000000000000000000000f890258e70b973c0d69492b2e7d10ccb3997798503c0943af4255c13b3856ca4007b18cb9d638d5d9cca71c368cdfccc54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff6735200000000000000000000000000000000171035755bd519af04efdd477d407267c5a8108bd32dd6d3f1b9555f15f37ce7598c096fb5301873809f0c000457a4a2000000000000000000000000000000000bd35595246a8337a426c50c02299f297036f710b0979c7f981c6909e835c0d9556cf64e2676baf952a787e10d604f210000000000000000000000000000000006600ff240aaa026941290f49ae8968e72293ae7c2af0df1b4ebb9373199b95fc91feedd2782ce819440286aeb2388c50000000000000000000000000000000015b2bbffac097c27944143cfb22e38ff8e50e79f2336e64c8496b0b25892834efb18a765e26f1408df1d64f4b9b78fb947ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001064bd04edf96a3c76d2ace669ff72ee5edd87d32592213cb5a6a4a482154c1723bc19c7c530d164c31626dbf758d43f00000000000000000000000000000000176ac06390e3629bdfa282bf825c0bca9bc4e0b8fd90fcf2d4ee456d5bcb3ac2882d8406d2fd59faf10c8327b1962124000000000000000000000000000000000b58fbe4e14ee0af03d9aac4131abfaaba43c7cd92d530802516cb67343b382a6d2af9399d93b43d6e05f7ec827d5ae20000000000000000000000000000000000bfd241e3180cd5ce9de831b24ca50db23685bea7e008be0c6ead11abee338618728968c25a8e5a916cef8aa516667214ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b00000000000000000000000000000000183ccf0ddeb8573923694decc02b8f02162037156a8f6523ed178c13113d094521c3d9257febcfbd8f15acfe3d5d5c27000000000000000000000000000000000cf716097aabb07979ee435cf57ae36a3034283eeec0771bea24c9a1a15ea106201af8606d3fc28ad8ffbea2cf274458000000000000000000000000000000000b962565763c4cc155b2d9ea104e754e5fb4745303240688fee7e2256fbda82dfb515a51096be5ba0b111637b1a25438000000000000000000000000000000000df04aea745b9df2df0e34153269958d3640c1596fdff3fba696801c96371420a3619c5ace9210af7e0de4f408b09a7729b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e204000000000000000000000000000000000e6be3275371e533a676f8d075bb2ab8b0216642ecde13425bce4ffa8ac51cb1b4c5c789d82387f5355c27f18da556400000000000000000000000000000000009fa3a3df5195203f967322cee54a15d1e0096922b6b881bb3bce54587fdb82931c0b87de7a9dd1a21b4389a34d161ba0000000000000000000000000000000014dd5455deaa5ea4f9b5a6241c2e8b2230fabff9e1ac08b359f029f4c7838201cb88a92a5b696ed47819e4866512fff300000000000000000000000000000000181085d630d1e24ebf79bfafa134c08c0e75626dd400ce500392adf4462028bc714ca07b28b8b8f15c9cf2934a299c3092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f0000000000000000000000000000000013b60e3be9d7d43eb42f7cc2c0a7efc81c175b696e82b034c87d1238db2798d9ad6534b86992653d86755b4f00cf989d0000000000000000000000000000000009dbb325624e698c76b9d697e4f7f03e502ae1cd43b49a0957fc067858e20e8c7ede3577f336eeccee58cad53eb727560000000000000000000000000000000007f2f50be2c6fbc500ea347cd14ca195af08b835814ca515d14dd2f6078eb6def2b9475c2ce370780acf394065032d0400000000000000000000000000000000109803d612b9e27be5725f162d061b9428f363493c17eb39c097032039387d96d0939a06466470ab62ff507ff762fba78f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000008a9fcb462412c1065dc7c3623ba5a980e6f86cc813b5d8eca6b1b8a302ee4176cebc233411f2c9ff171332c66a0d46e00000000000000000000000000000000058d2e7ee02bbd4896b5bcaac0f2b09c16d1664209710945c1f7f1a53e24496d7eace99488debb32afe10d7fea442cb800000000000000000000000000000000084d7600bcb68d5e375457078672fa07ba2c87c8ec5f9eb7b61a0232988b197aff052e7125b33c6657729ce8a1c668e2000000000000000000000000000000000a07c42468c7c65fcc984bbfc2f05bf452daf17d57e669ee5992ce67517e1c93b5f7f4c9434d40f3b9bbdb3446ddb982a9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000ec380d15e0efd71958978b1f9298ced4cc3322e472d03830ebbaf2a4601c8371e6bc1cad047b0e1e429ecf6fc628208000000000000000000000000000000000b278fcc53b7527545ae1340c24158ff662683919717c220e7d2838a853fcc84ce3915f105a932872ca7f64b7cf096ba000000000000000000000000000000001520798dcd146c0b39ee727e8276fd998de0157a68587c2fde56cd82a9779b6ffbf745ec151210d1e9143856f24f01d600000000000000000000000000000000175d53b992d750b34f9daa39aec918a0ebb2f539db8057eff1409492c90f79a00f14a4c53445c028bef5d6372c9f80c680b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000fb3863bc7b468f1a0ab0e4701ea392bd820ec5cc2d7d86b58949002f24c972f51f0f82400fadebef13b750884b35f9e0000000000000000000000000000000008fca1b30d4e01991811679f261d11723086753e816239c8c7ebb60ce9ac0ea207011a69cdc29e3336e8f589b71bdfde0000000000000000000000000000000010696ff9d78b48743abdc6c1f4b44b4c960aa516623a24da515206d95e65286e453a8f275d98aaa09fefea29e71b5643000000000000000000000000000000000fb4b5eb18b6f6f8ee7dc734e8bdb625a403dcac6d0cae363e5a7f3a834c8eed5f01fbc4dc752e228c41f3f9d992bbe01b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61",
+    "Expected": "00000000000000000000000000000000165baa8b143e3734169986e68a848739ca05330786012de260148cfd0810ffd5659210855f19ca92566ea0d6c48086ec000000000000000000000000000000001225672112e0476418288f381165292a9aabd009b0d9e44d9f8f00469b2c56698f5f985ab6292c9dbcf73bcf610080a20000000000000000000000000000000005418cba24a43fc7edaf2fe77422a0b2e8b38a45415e13654c6176c8f7cf6bb2b80401534154cd3b23e977af589eda9e00000000000000000000000000000000067126ad59105621cb0931ab8f386570b54977563ffd69c2231c56e7961f6df2c5d7b114e0b1ea176cbfc1d657127286",
+    "Name": "matter_g2_multiexp_53",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000139cfd67c3365c5b4422063d7901108c9f33e233bf6413ba2e5b2ad62d188cb50dbd3dac0f298aef7c1d621249d4b0c50000000000000000000000000000000012fcc0d5d09cb3d86895f76ac3d3e9fa9b2495110b0276e7a039d7d2fc2e48fee646fe331c1d8e6f019898ddb43dd09b00000000000000000000000000000000159356eb3ed0d4f146dc929aa6c77057be5ffbb064432d3fc35d346f19f6c1f8552c7079e27f3188bcf29941375e62c9000000000000000000000000000000000fbd4e9a57aaaec40ef9bce8b76b529bd2261d373f05fd69af58d1f23c089497473e44e937b2617a92942af1a99d031f10e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f66318500000000000000000000000000000000022ce2d2bee57f7567e9b52ae8e913c79e3b2dad381802ccad317b525be0b503bdfa92722eb0c21fdaa31fce2421ae300000000000000000000000000000000001177074350288dff9dd85dbee758fee1400cebc173793198a96c0be3bb810d352720e94b9bdcc6f5a8951b3a86b2a0e000000000000000000000000000000000179e21de58ff76427f5ed7c8ca3058d0e5e81e436280aecc75a3d989d1cf11d41734de22bda74cf0dff175ac789532b0000000000000000000000000000000016abe94a49f071fcd5e24b5f3a837fe3fe7c7dc53416f59d0469d71f144f71ade4569bac3aaa202a8479c794bd251645cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000018dee638031a3c9b1198cd4a4f267cdd66849e2b80e3d670897d9e058bbe772936d827eaa4e78283d42ecd25eb4b22e200000000000000000000000000000000009c04ef31cfda7c086a31341434a1698c1132fb5916d359a523b98d05d57bb38a1e2e2bb779d4762f9d4ec24fbf2564000000000000000000000000000000000a788450652e0bfae66889c66b0dab8d1972a626facb690f8e4ebfefc7e1a7b2b58f6eed02c1f10a74b140a49b6c5de50000000000000000000000000000000009e48b52f2b0548dab1c0d260144ad2e66a22e0f1781f94071b5a3a08311d11dcad6963b4339fa63bd82b4ff0dabe685290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e8000000000000000000000000000000001319607058637d4b796020cca79d62af5862b1c186f32d99c0ff53a830888f297ac4389582f9fd010534d824522e6fe3000000000000000000000000000000000608ca0b4806f17b59a805a3f9f75e7a33ac0791e05050d4eb19f2d4c845fa4e4c738c3309e24a4524b6bfe716949ab5000000000000000000000000000000000a6a6201ec077e113995acb81d4d07d0c4a085d367ed740d26c4a0c04ddf28697c1cf5e648b25148888617ba77ced5e50000000000000000000000000000000003eaff54800dfc8eb3ce647ec4ae8c1aab6a87d4853a1ea061a5e6367d8ebc94243837d4752a1933f7eee0ec1ffe68c8b71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff5700000000000000000000000000000000122822c91bfc4f761b65f4066a94c0eb1f53133a1355c019f04003e84edc5095523b2ce87ff24bb42425ce979743ce31000000000000000000000000000000001928bc315800ae9936e5b763bf29b19a9aeb71268cb47706494598e0ea057f9dbdda6733d9ea165acade87bd89b3ec12000000000000000000000000000000000a87c1ee17bcd7d348ed1a5022bbc7438bfad06172584dd8e3b51db4b3b09645290382ba991df37db0ce562c950c0e6600000000000000000000000000000000127c80da591c3ff8d300bbdbe27e0aa21b5edc1c1fd8a5da27f58a4dec3971b3c4f9631bde244a7072d9c19f1c0a46be250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000004bcd0a0321c3c7e6161cd53254353905c27d965f57c9783c3fa7cd5c55a5820116415ce45491d5d1ccef6017ea4608c0000000000000000000000000000000013a30e19c43a1f466c0c3ebb5cf1b57c44434892b18a7fde18a2a29b09a5b4d13d26cef871d689d9855a73a43d22119a00000000000000000000000000000000066d6b3c9a949049413300ec0398d605277911d7be327b1d816cf25543d1b2d7c31d912f426021e612b56ca288b462450000000000000000000000000000000008549f4dfdf018073cc4e32ac930397659ae7a59ef42ca4f864b26e4635c2b7669186a107e9e91c35f04674d2be46051ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000015900fb486bd2c066cea98e51d30424681fc3347a1cfaeeab65989d1adba104a362837bee51b8b953ebb520feb49aa6c00000000000000000000000000000000198ccab1f94fa910f755936e357a92d358e00cf406894b46adcfc301918c4fd7cf7200a1ea515343d577d920680c83640000000000000000000000000000000018d9380a8568adb92f8f9f67c315f2a837d542b32aa82d9bbf5db6dfea27260738bd0a03683a9988c6c3370563e7bb8f000000000000000000000000000000000528ad42f23c4e21a687f2303f495e962b0a90713d6ef3abbdce38ed166ffea9c132e50c5b002b2ddbbd4933e9a1aedf6ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000017eb2587aef34b03943a170d91d99aa16ceb2a36df3068663382ff4c135083c998743f9145a2fd5dd4ce3bb8b64cf3fe000000000000000000000000000000001256fb29c7482e5469d64183e3e848e5bf32f9c495cc495c3f8cd8e46f71c3f9880f875cfe429677615a6803f849952500000000000000000000000000000000146e2f329f86ddf5b0b17c37aa2905122f457c2c812782bdc15e132468af48c49b715e3080da504d59414ceb367596f100000000000000000000000000000000022a8e385972592430e76bd952a700df8d35b32deaf06c60173d0048d6ea22dad95cc62300bc1a60c6452c41b32b504a1271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000009c80b3191783d235814fc86653bf2f9a32cb7938111408087b6ab5bafc480583e7a2a32c6bee0ee4aa867ad5dbbf77a000000000000000000000000000000000a09af60eed6c47a6c2615cbfe62025530b35727b42fd812032671ca1eece6694aaae259b05906faf7fbb54362ea890900000000000000000000000000000000055c5f0818f41e5d73e8cd5f70fa77cf477cad8dca2a88b8970a3a25c8f38382268e439642518f1974c5b470cbf29699000000000000000000000000000000000834e44669043aed8ad47cccaaa7476ad830e38fc1def66aa7e8207e889ac0fa1a931eb1e90aa6e1cd694bb95056c3e63ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd000000000000000000000000000000000190f8da34caaf472ea9b0f41851f808bba402b9be4baa5d02d1bcb2f66acc3172abe78a49a653cd24dea402dfb972f670000000000000000000000000000000019931343d0e59f0f0a060bcbbeea92fc4670db510c017fd94e0650ace68c2925c627f373d8e755813c199b79c70369f20000000000000000000000000000000013ee811cbc036d2786d8ec0339627d6134b10517c8858f6c6db19a9319636459ebaa217649825ffba32a224175267de90000000000000000000000000000000011039d587f3323ea9d3c50027c427fbcbbf7e097533d8a5f7a61520f3eb548c399e401df0f51884395ad6a338c0a3500c6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000011d5791e9bc632eb63bff86aa433e6df463a84570b779c913f67e77fcfefb6af48f3df2174096a511ac35eff64e0e5f3000000000000000000000000000000000282716505907931bc93748ba1729777b959d65aec5a78c9f829ae6f2a94a022116715a8c2a653a832a62625473a0cd1000000000000000000000000000000000f694a16ce7a69f0261a0ae19478003dcb61bf93a2ff39f940fc4718a38b9f4b6ab13527c5b438d22499ba29c0b5461700000000000000000000000000000000031eab53440757e4065804896e9e811d459665598546796d67472054fa60e5da8685d8e847eae342e44730056757c6287275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c61500000000000000000000000000000000138862ee422bc0f38ce3e27ed3c1b71f71a03d61cc474d989b0cc824efc512ef173ef17bbfb2090997eb9435f4d23e0d000000000000000000000000000000000fabf1fac2ffa25d9c8cbd49b3db5dfdbee52adb947ebc1a3423c9fa2f9d3d29329b60ce0c1c739c7fc6d5a5d3b9e96400000000000000000000000000000000090d92e8763d4df49b8121a50affcecfcd632923b5fede480a3ee79128781f3f49b592d8f65d30adfc75d8a1922c41b0000000000000000000000000000000000074456b341565b13ee3862bd87b72f9d01754c7715751738c5b33ee85e3d8a6f731d7292bb485b5fb59bbf3ddf9b0d0dbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000007b85110889fed72b3654a8632625835cc041ff0a827f3e1b86c090d816d98cb3b4be66b6e573b3dc05b1998f2772f0e00000000000000000000000000000000160524507679ee021f4307e5a9fdaf01459cbb9a3fb9dc8be5599431e2a8bef38bf8a05d601580085da503dfcf57aab7000000000000000000000000000000000f98e2e7ae9cef2b1d954b7f26fa1755258112c496605c3c77408786d4b210e51c76f10870f558296993e0ddcec3d76e00000000000000000000000000000000068841825f5f5d8f622c1d43bfe090d11c6996688589c3d644ff5da47b94c0638128878d51dcf6d43637781f0ab21a68ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf0000000000000000000000000000000013ec97016dc3d6a3cf41edcc18f88f58b1b88cb2616bc2a8f96af3e7774ec1aaefe86a86135a20ab7592c874a33a8e1b000000000000000000000000000000000021dc7e4be6462d64ba6c09c2d326ca0164305dbf5ca1981f265a1e50f1a646748ce66ae07297230325937faf60709e00000000000000000000000000000000121bda2855503ef11b043301cf331a0fda6e5914e5ca657890ffba2542d908f8fb02c2c93cb4ac4fe5bb92eea757ca7b000000000000000000000000000000000386fdda56c778a7552dce451a6ade55cd24bf9eaeb837ebef898e2e868d05eb5edfe97bfa8eff8ab7cbfaca3c918910461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000763746ba87e8bb547180b0bf18699ff74f11154a06cd77a76cc9c264db7c48286fc52e3ef2d30ca914cdcc5c4ed46ad0000000000000000000000000000000018037afcabd273413eb4a712f5d1888249dc987a6fdb8befb92c02660604bd11deb33f283b37f88880cf1be2b2e71f1c0000000000000000000000000000000008ecca3d1652be4764720ef13a6ed6164a3ae89d160cc8c2c8c37bcbaa52db0fc0de84fbe2a19b93b8100556fce0fc80000000000000000000000000000000000c5727babfbc5c36c1d57b9f69c5b41823882e0196e9e0a89d5f4380c4257818d90b1fa6d782e774f2424209bf2e6b5fcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d00000000000000000000000000000000142d41630fb9db2f9630e4d5f9c13069242fbcaf1dd02f93224174567c3f944fa02b9791a409d9236d89df6ad785e8ed0000000000000000000000000000000002fb5fa0b3a7cef16e5638f217bb946085fba870836c618a7db9b4394da9144850572daccbff8208f14c8082aaf1ef6f000000000000000000000000000000000a6be9b4a6a9b96d2096eb3a95780f11be1e13bcb6e625517191822403935c52cd40481bce2e782c42b11321cff2cb7f0000000000000000000000000000000019e2d94e35d608a50b5c8b371044f6410dd6c1988ec7a677016d4b52cc3f21b82fbaa7db897f7107d81a177c31f8e52467de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda",
+    "Expected": "000000000000000000000000000000000a6f3fcd812e3878cccc6967d49b104599fdaa80cb5dee7298c3fdc80477d277f2c68f1c941f6e03441eb176c222a448000000000000000000000000000000000a4007cc5586d677e7945dc8a5872b4839d5b256999166e7fe8efe4d56895f93be4659f43aaf68c6070babb6d3328168000000000000000000000000000000000cef5304a1077c8f31d72e6f1f91ef5a021d8ba64719b4527225b34e615af388d9b1391f65511eac209ff5e86244039f000000000000000000000000000000000c856e7847ea0b4a8334d124417b45a8689d5d9f113b99ebbe3af3f9aae1cefb236d751c40488a861a8f0e0326b42c4c",
+    "Name": "matter_g2_multiexp_54",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001805009a7fc3d1705936696191c163a07ea992cbb4bec66884a2d58ac3fc0e16b6e0d2292caccb3541f39b7fd6098100000000000000000000000000000000000f3bcfcb0c400d3d06184563204bdee465de167c7d17bea2e2150fe12eb9bc3285f5693b222fcd224181f8d193b7d95f00000000000000000000000000000000028d60b7fc3790aac7f6b3ec32c4be626a2c64c6348fb8a1f39e58ee56b81469e04886ed9be1388958550c02ca9a75b9000000000000000000000000000000000b60ed8052e43e99d3c10a4b97ac3197ee3cc04ad857c5cf4d8ea1df2671084d02fb683f28f5d499910351354d5e6288624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000002ddb1a9a88e3a0697540cb008bceb075e87e2331f6e9b68f8ffec48752d93cfda5fee121155ad2a142c0ec42808fbc200000000000000000000000000000000144b694018840835fa9c50fdf62c2e32261a8350d2ef074dcf7d016af982316a0c6f9e5d15d29d3a54d8d25aac5534940000000000000000000000000000000010a3765089ada75e9eb61328756ab9ca7b8362cf86cc82af3cf43f390a0745954f28da72a6ea4eb904a040596795639100000000000000000000000000000000056b51dbefab453012b35fb6e06af06ee92e4e84e92a9967b379af760fdca4a3f10f938684a646fd70a2188721c92e98edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c23500000000000000000000000000000000169effb324d60b71dc7ba975e3d5f18700b34cb9017f482f64be37c4df01fb66ee9eb5870e43649225c9a88a0d499b890000000000000000000000000000000016c7ad9c5f7b65a9423f642d87621a5192d7548e1099d774a99a34dd4ec9623aa1168b9adab092b3cf450f369bcb627600000000000000000000000000000000123b35bbcd791ce0d00148cdb3d35ba39054a7126ca5ad3351fef1437461379ef639896b271276a9561b46e270f7501400000000000000000000000000000000161fca2deb729fc55f1102fb75ff466319f18510fc66d6cf95a8256118fca618682f00318b0a5297be873a2f7af1915afeb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b00000000000000000000000000000000073ad8c2f713288313185c3b2455ade93d58e70d5df6b8dfaac8eccd990fca6843778fe42cc8aa6f34ee44aefb49397100000000000000000000000000000000012eb9cf288a366adc58d40c9ea5f2cb5dcc5b04108e3822266ff20eed71f56bd74f1a2727f20d55917adf20b6c4d6a1000000000000000000000000000000001463db177fe5c0dcb899797f89da963731dd4e9e8b2eb77b465b98415dc95f6d5569df51bd2b08a13838f4cca4b62fcc0000000000000000000000000000000009c0bbadad98361209f36eb23a9eeff98f6eafc7d5327fddb6bf43898a2be704520a005b84c5b45c6a68bb7c98d65d6dcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000013c63973ce6549ca3dfe8ea8e3bcd6b0bd88f7c73730834d9ffe2076cd4345090d0364d161ae8998af1048d102f22e5d00000000000000000000000000000000060cd24eea4177c9a5c37038d4cb62aeb709218fa8e64b9084e002f53a0c4c411825812c20df282345bc4a6aabfff6a100000000000000000000000000000000106ea864dd52933be02c1a79cbaf6dc81ae9a2d619bb368c4abc36226104f3b74fadfab906e36d4852a6412315223bdd00000000000000000000000000000000192e45153e4942c88bcce76098fa51782a81b53abddb4c07bd79a2391be68858e2d278969b9fe75bc652d02fe4db1a130b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000101ca1625e9d4a51e08f5eb81387b361f6445eb307d9bc92acd29d62735d4e5078b1a9b36b94e4ea0a314703a85ac4cd000000000000000000000000000000000f134c460c6d931396a0aa397558975ee973e642f1c4a32a3d397051fe250daf4215ff5ac4b2863d570c87f0e32c8cb800000000000000000000000000000000008eeb127a38104351298ad77481c32bf51bc5d3910b03da0cc34062dd2a8766adba6891cb9fc579672276666e1242730000000000000000000000000000000010c896ecd4bdc1ce010da81a51dac96409079853635e57e5c3a5733956a5f5a9c3ea6838849e286ce0405dd54d7e32d6730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000be489a1c71246adaa1c1dd6d2ddfae9523fd1d58d00d4f189f56d08632dccc694e63b371db6922a7f3faa05afbf487500000000000000000000000000000000174212b6840a797f0fe9e209b41f55aa5dbf169a2e2ecf05de48c44e608f6cd6d98ff5269e5412defb431caadc8a09c3000000000000000000000000000000000f4501715c0c511703f6236caa82479b3368de430f2c2d95b39193537be0b990fec1ed8e4d94634ee6233cfa359b043d000000000000000000000000000000000f3b4712f95005004d99fd739affc532d2c4c45970316c1a43f76fa9b57f6676c709e8791c276237b92750f5bdc94492ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000003abd36736fec3e8b89863670666365b169d8510090a89007c7ff3a82fc62ed371544013a1444fedc4358e92ceec62470000000000000000000000000000000008229855468fc63f4024938cd6f41c6e6a5653319cb83f38ab7efb9e9d281166261e7c854bfc08f55a0a9ca47e54dd42000000000000000000000000000000000463ccacb341fc5874f6ba2d44efb5cd24e9409b2ce7f43e9d39466288dc833a45988261f45d34332f416a68c5d10ce80000000000000000000000000000000002baa086177394203a04ce1b46415983399e60986531967b690b1a13cf8ae039b56f0a00bf9aff357d51ac57f8fac8b282b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000000b25a203268100df0510e4155c594a144dbdefbb0ac95e02bb4b3799aee4e738ef4c52f03c6937cdfa7275c28f130778000000000000000000000000000000000c432347a2534e86e90ca346a7b8b40f45075727847fa3ae2f2e297baa14aca88ac6e08342f0d248a92e2c272841fddf00000000000000000000000000000000057ec8099e1e30329762ccf0641b45e1a226f7b66b80644fd551d6fb1f2136afb8e8ab5c6905ffc7c24e67d7f21863e4000000000000000000000000000000000a9e472aa993bea05961affd6782efe8f50d746928efb8fbd328fb50a254db861c90db8df7faa7da8266ceb47fa1a13a2396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc00000000000000000000000000000000025484652f18e2b32e2bbe79916c8bad42902db5528fc45993e04daeca008f3c2ff38fe4b48c292f70a7dc57654233400000000000000000000000000000000008e403f472b60a6046fd190544a1d6b249dc97cbd8641c62613f4de0e0fa9f5456d843ece4ac2b9f4ffa2c0278e61829000000000000000000000000000000000824e0b9b03198597fa54252b3df9690df678e9c6d82301848939dc55ab25a7751bcc2b99786cd31960ee7030bf68ac80000000000000000000000000000000018d1d8c7f2b20f0ba66db616322e48ac8f1d6f4205f228ee8ee6cd13d1f64be9af338c11f511859baabea3e15d165fc09a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000002845c4255819ec6e97abddf4c9db7d91658dd1d55328ab0565144b377e20ca0743d93fddf68acc985ceb7f7431e30b0000000000000000000000000000000001577a5691f2425e65ffd59071c2bb167ad05a8fe23c11c7f7464764442ebb2f7a75a8d02594d4426c1ff022f7a6e19360000000000000000000000000000000012c6ffefcd3964362f1373348404d04d1849e98ffbef7b5ed5704d74b9550869e30a4df26e74b5304b85c7503f7487f1000000000000000000000000000000000faf3dc42113f27ac27aae36725221d04fb1ab46b59e16277be0758b8fad706fa237c0c7627771d8e8d3ad610f63619bf20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb00000000000000000000000000000000044de166200ec06bcb88720e57b84cd8f9534d1fe303a26aca08cc35104ffd7e81a6473c08b28037118dd8a61d090e910000000000000000000000000000000000f4325ebaafc67945de2418c81f5da92da4e67866ab5965eff0f392cc527fc34ba4e7e16b91c26aa370b27eb6a07f6b000000000000000000000000000000000e1d77ccc1c196cf1cdf0dabbee4829d56e937372e9f5613e261ca07e19b3fcf10f7a45c490b98b5a64b955eab5c4f2a0000000000000000000000000000000004ba2e81f901b0da1ead004c76d43278d372456c0c0a8c6752597823d44994177734ed3f355aaa22f325ea36b7c9eba1f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000000c115e270ffd6f2cb9bbb2a62e04c3bf7be9d7db783d292bed272c297773b39e9e51c75e5c79a6606ff7d0bb9ddd040a000000000000000000000000000000000a57b637126b16b23bdaa6a7cf2346f33778cebdc0c9943eb2985ba5c4114674cd596ecdb6959791139c36c22148ab8300000000000000000000000000000000177c7ed16c29d99d3d98c6facca9cb5ffe72e6aa63959dbb51d9382f0fa49b02a1652a398eb223e093516ebf134448c4000000000000000000000000000000000d6bd518678828f582fbb3b1bef725e66f442c4d3e6325fa571e13db492300d03c0188399a2ef9d5687a76e647873c0f52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e00000000000000000000000000000000085d4f90336987f99d250067c2331e7de8f09a80d71fef0570ecfd99e409c1f405058bd3461c9f8ac5ccda406db89bca0000000000000000000000000000000015f310660ca6a0c06b458d0b840a5c1c476d5175d9ff6dce6334466d363d319939572a2b00662247be1ed0f4e6676f8b0000000000000000000000000000000011e9352c0f81bd3857806db678bceb2150848f2224ddfc43fb0c733f0689ab4fffde50d5ce04d54055d27d7702e5d2d40000000000000000000000000000000005d835d04dcf4199130d6a16e86cb97f4ccff58c496594b83524dcd88f5570212f06b744379288f2a737c7a82e897cedd32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f000000000000000000000000000000000f7c4e621c37bd3068a972b9d4211abf9026e438ac7f8cb341516f7e6aa4d8bfb3536389e9155029ce9e8d5d376eec1c0000000000000000000000000000000012a46cab2624797513f2acaefa26fb22c4bf29188881690c350593fd1949cbc243c9d1d7d27d9d76aaccd347359a45660000000000000000000000000000000002dc383d4f9b75907f74bace1769bb5bb1b27a597c9548310f2b5f90098596fcce6b5fe0c72bc8be9037fbf31050d74e000000000000000000000000000000001900deff7ddc62ac302c941e1d2a28a4bd2351edd7700042ea4c4a48145ef91688666d8d7de503913ea259f0b58809f21f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad26940000000000000000000000000000000018ac8048d58f7b1a9407d3101824e3640eb20633f8ffdcc97d43d1b25329a2a1e91added42801c03635ec904e627eb690000000000000000000000000000000000b499fbdbe2ed41dfd6c454796e1ba57021f355a4de8f60964c78dc685e2ffe9c90f5a1f6c9677514ae4a9c95c8d6450000000000000000000000000000000009d10e5e2bb69ea6fd820778f75a2a60627802a49128c3f999d8c1cc2ba56ed18acef354a2e06fbbdfa7e7a4ade7529a00000000000000000000000000000000082839d66a18763656c2ef7196a1d83bd162e1f109b54c5a6095cc7c436e8a4888c4001696958270f54f61b81b00b32d83052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692",
+    "Expected": "000000000000000000000000000000001463ac5e269d286961036db48ae33fb868a28b0dd828c3a66592ff9dc115303bdf3ab78a8e1f5df68ed1f3b4c6c3f2440000000000000000000000000000000012c64ca0ac10ab616fc733f75fe6181814e9c204f9e4eb79487ba49e3a9746b9b7916a1d768f2ec573a4c4e226365f48000000000000000000000000000000000a06b5b745dd92adbe1f4cf30c79ce0c48428b3e3b05af1585c4ca12eb2e763ffff46b55a060913e1f77fc9b0b085c9f0000000000000000000000000000000006271931ce9c8b9cabdc932297f3c87128a5af25a9f77e71ea4e588f1e88686638e89a8e212c92f6472692be2e05fa5e",
+    "Name": "matter_g2_multiexp_55",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000001bf5b74c1ac89d4bab4663943e19128619731e315d2d7b39675f7c43493b338020190a72cc7a6edf0b8838886a7fe6000000000000000000000000000000000713f413bab7919cd57c2de3349394121d6bace3c10df0e41a0ab895433d225b05cdb1587deb93ae6e56ec26a29c39f4000000000000000000000000000000000dfb11c9c0bab7e4d1ee39941d5f6b932ab473567be2329c94bb0b146c46fc1c2cda25dbef8ff9b0066bd4ca3b6da67a0000000000000000000000000000000014e399169243bd619be7f2120b2cae5d19b2f04185aebc7d948007c4d3345a9f45249273b6290c2e86448648868ac552c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c7052000000000000000000000000000000000a67bca1f8a0b386b2a67158e80262f025b225535a294394584118f9a701e31e91b2c7eb8fc7e28538966b967c139dc400000000000000000000000000000000185e8aaeec9b9abb9f0d6f34e2480e9abc30208eb1c6e023d4986d544b356a387c323c9edb5c52f5a2f0bd59cca7df98000000000000000000000000000000001877ce1ca6e8b30df86de688d950755f2708fd6f933c07ae45fad1b3e43337f1a8454ca5d2a80940e8fee98fffe953a700000000000000000000000000000000117a0ac9d27292f967ff5bff2ebed5d2ddd9f453d6aeadd9106eb52b53447974561b621fdc1d973c055f1cdf824c367bccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c00000000000000000000000000000000009fc4e9e816ff495dbfd4f745106fc90c023d95bc64b809801d02dc7cead905177ede5016f537243660e4b7f54a02ea200000000000000000000000000000000180aceb6e9851a11a1e34502897299e7db3e09f4970337612634fd9848d1de2bb3de8ede690ca051a75add5810ff777600000000000000000000000000000000199f3c43d429fe8f73e20f81ea00c4e78294eaaa29fd67563664381db3cee2186b387b880089cf96fb99c2e22c95449d00000000000000000000000000000000040b20ec4e685f104be188d0f15a79f27cf34dd01f813275f6019a9ffac56e234b6c967c80745294d9fa46e0083cdd907ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c3963100000000000000000000000000000000189fba635109ca215bf3a09c3e44ad65f7eaf653e0929aed39042e3b9c8b1132c5fe7cfafddfdd0646514aa1f9e7e1c0000000000000000000000000000000000c28f598c80ac262ec7a0e0d1c867e01ef26f182c5df9ea7f88fdf8bcf3a5d2f06128526b1ce72cead8ab4286a0b8d030000000000000000000000000000000008051be3328df43b79dc9040ef0a0263d474acc0edc023f300cdf7c13088d1bb21b5f37ed81b38dcf8718bf6441605f8000000000000000000000000000000000d2d474723c6c246dc59e683be147b1a6bd6e7d3cf12aff7b636802a99954e7a13c9ea429b19833a985ca5649b1a998f6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf01840397800000000000000000000000000000000058bf424fd68aac77c42a046f78a55729e6b5b3fcaf436d0d98354b426a95904b55cdffdd9a8892c9f56f170ca8811a600000000000000000000000000000000142c1ded08928fd155b89bcfaf9c8194f4569b4cdeb3bc7286f4dd79e822f5db497768220533b71be8c71d121e557020000000000000000000000000000000000a9c753686534bfcc295eba0a617f86d7f9e78d3fe6d52f26cede97a5b1f107210a757a2d89361645856b7b20e89185a000000000000000000000000000000000f745541841cc4b5352f659c2b7cfa8d51b07f91b0cb8c787b4492bb4b94ea27117695416e2806e57c38d7e565b9eac67fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f0000000000000000000000000000000004445d4464b51d6b12f164a49ee3b610f11738d60cfa6e02f8c33b168d9d5db90e6cc558cd12c56069571567d91183a30000000000000000000000000000000009e4b96c2b533a16803a36f8d1f179313b7adbe6c4b90716855474ffb2fbe087df3fc0b4ef14cda7d958efc5c92574ac00000000000000000000000000000000104dff7c859eec61a0ff8e0d831bf9667226d5bdbe298400b4f9e3159a64b1bbc7cb9f4ff9604e3ced40bb0de0455ce300000000000000000000000000000000134bc2461459ed6f0d96aca02b62e3110c2009e1ba7d3258656e9cf97c2a1685faf1f61733ce6ac3af7ef4d73d0b43b1dd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b00000000000000000000000000000000104e5709f8edd71f50eac1770ff1c2b21f5ee8cf5a310fd1201109d1b73cab69913bcfa2d27a8ba16d974e9841586ebd0000000000000000000000000000000003a4bedc6277c61825f6ea1f438c058a1afd494c384689a8479195646888eecc7953b8b8aec849fb5f19a20071261336000000000000000000000000000000000856ee8eafb9b3d25fde7e38da4acec624d1444337b87b0b1a660bf497ff37929b1ef9aed8e1fb0ffc6cacd8f0d1a1a00000000000000000000000000000000011b52192c88264df56de3d7b14372443e25183bb816ea1c0346f15a1f324527ef8531e27aac3112e2a497a0eff0d5485b4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000a696c83010719161b6624aa7756e6e84980518416554ac045a93b63c2561a68ca2ff2fd5b6d2d667822ae4e3b3a2ba2000000000000000000000000000000000fb8fdab4f177b0dee52bb5ba615b1d548130deb87b14d05d427984ec148a7a94efc4674804b3660d0f7aae2b49f7b1e0000000000000000000000000000000004914c0359c8e23a7e431e517cb83e5735cb2876e8b53ad45abf1e9eda06e736378ce03ff75002374d47f1bd45b08e8900000000000000000000000000000000139abe340c2d773cc45cfc75c47ff31b2dcdce27ada3e6d6c0823f37e4e693ca30342fe41eb96dde464d14668eb72c5e457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd100000000000000000000000000000000180345fc01e3fa349c45b1a7fdccde5f9ee70d7d65510e8b4bce654f2541fae7641ad86f9bbc1f02e93e94422433f8b40000000000000000000000000000000006cfe7026cd423be189c5ade8de197aecbc9aefd4cdbbd2aeacda816247ad59ae06a5c49b0e29bf1140f400d46845191000000000000000000000000000000000cc4f240a317ae9ce75b44fae87c92fe9b6de10e1191cdebdcc37ac200957683849d8a957216676db1af51fa0a2a1136000000000000000000000000000000000ba84d595661e5d9bdf9d268a3cc575fbb6b0d469b58b3e43f80694c78f4e9e501c4a4f9c42ee4518ed7189a1c36ca0c19f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000fdf7e2372b01b5d926a18ddd06b4573248c02d7debf944312dc06f76ba08a7be460c451d296b71e9e81cf0956b974b80000000000000000000000000000000018326d0e1bfb4a62ab6f772b47ed7188035a62141e6b2eccf53a299028902a172771e8e46c0b1ac4833ab12045922b3600000000000000000000000000000000072107574145c6afdfc7d618f2dba2b8bb01d92007dafd476e4ca62e6053e5e9f2e34243ec2dd16ffdbe3488b925a0f000000000000000000000000000000000070e8491a835ae96087013b0f8da267a7ca5b0a600d71b8c76fee35f41d8b5c1ad82c5170b0e8d1cacfc7b7b13938e96f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000018f4da37ff63f66d68c875def8c758d9a5adcdc408f0c12b3a60ee4a285e6702b1d5b9326c61f443dc71ae83c7bd21e80000000000000000000000000000000013a665e430141cff62c25577798473a645d20321490bae7689de6ea223a434c7d3b16ad004b24a82e2c62879b2408cf90000000000000000000000000000000011b0108562f53bd47d9f8ada54166854bf758ef3769ca1c3b7b006fec8707107fef0b6c7e59feb727646b74c27ec699600000000000000000000000000000000028799b52107d8965066e2f629b30c0edb490a0f4d0b6cdfff89a9f7763afbe6217bd42c2059042397b6c0443465fdc050aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000c42cb42e389f32926ef09584516249ae332641b573ed29bc0884feda08d35c1bdc6c3d4a69fa15105de95010c6cc24600000000000000000000000000000000006c57fbf93c7959c562e0f3ef59966c1640c706fd18a6b539dfd711b0ad79643642038954bc866d42d1c04be375b95a00000000000000000000000000000000039ca3ad23b71693e02af36a4abe6ccd0dd4f4aa709f74d900b9fd015a2eaed55bdc2bc0749c995783a7615971e8a1f50000000000000000000000000000000009a08596b29da34466c8a7f46b805f1b6f2e48bbba614d728562981d3d4884de9a3c1980d398eadcf69e90c851d48526d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad43400000000000000000000000000000000123dde5bb9b7ca11da9e08a9489cf07d147492be8041a5ad0b70715147e21d6017a58af23c47d77885a7830cfbbe5e0d0000000000000000000000000000000001527cec3c393d03e74ee8a7b1d6a8b6398945cd284b59a93fade9839863f0af591c287e89b3b45e6048f2f9b518208e0000000000000000000000000000000017ac3a2d9458bbd5f38d584b0fe4b35f3a452e22161564a7582465d2068b3ba4dc5e1e24a996596b1fb553d641996a4e000000000000000000000000000000000ee5ed5610a78dee181750e35a8ab91c001446f04124930c2ed85de74c6167009af45a6cbc3c59c4915334d7853ee12f85c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae0000000000000000000000000000000014d60c1d436e4486f35ec85bf2655ba6b752a36c86fd9088c0ce46363e75abd636052f876986fa0f4a59152998c0e4a800000000000000000000000000000000083328e38373f1de1049deaba78f568db818b1dc38d981ae92b968134d369ccc399bc3bd55c841755beb484cbbd60f4b000000000000000000000000000000001788850a5508d81df9af1f087356bf8e63b3c8a4e209403c4de7b3adda07684a08f9de6f1f8fd8dd4b2bb9b75be329cf000000000000000000000000000000001506a37d222173f0098f56b7c443e04ffe08b376e1563344e7bf22b1c9df0a1292f70ba51cbe554843fb93a7f535a4aabacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000d5f153952defdea9309269bc996a7714deab12e7644f8f8344140fe53034de538aae6c3af7b06687684edcd2c5dd19e0000000000000000000000000000000002da67345153c87ca65012b8703acbe777900953abaedca4770fd893275948d150ca3d6694d58bbbc9e62904448a8d2c0000000000000000000000000000000006e8c95d22f01fd9d56178d754f0892f46166282a27e6b02826478cd39119636e811c03fd835c714a59bd2f7da5ce5e1000000000000000000000000000000000b5ab6233d8dff50648d89cd65793640c06ea784d00aff329e882ae04fb466506cce3fb6c381b4eacef8b5305953f7b6473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae000000000000000000000000000000000595edc440a5c94506a79f3b3fee818256d7c4185be40c1953b46765b2f925ed16a476b07a267570c727592dfc4a0d8d00000000000000000000000000000000079ad05473fca57f26fd068ed659e4aa4919847dd96e683e7d4b3a731cc9ae0562a693abeea4fd550e644b43b553118500000000000000000000000000000000176a9751dbfe727a442797551254cf904862c4d590892e019a54b72f6a5a124d268777b82e19d557690ccfb81cbe949d00000000000000000000000000000000164ab74c150cd151b70fdd7d63d0404214fc9cdafba3bc642aa798b1c301c287ff6d05ee7b3a3ce997072b8189d54aa62774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a",
+    "Expected": "0000000000000000000000000000000017d73e29f1d555a10272043ac0900e80883c185ff7d087ee7f5a3b762213e658a42d1b4fdd435d1acb9d5587fa7e8243000000000000000000000000000000000ddc440795d0e4308577fe8439d43418641538711972c9744dfc8a4c206c193aa17958404bc387c7c2fa30bc678937f7000000000000000000000000000000000d7e43c0f99adcb02db99974e7615b4ca0de72117792ea515bb04c4bc8680a3fdb0afcf6a3bdfe16bf54c1d7336aa185000000000000000000000000000000000bcec1d7fc9f2210be80e90631810987801fdf60890ce197db041b6a62682fd7e181c6110956c5f5e9c196049e39100f",
+    "Name": "matter_g2_multiexp_56",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015425cbb7075a97dfa9409d7b014127396056dee6d4bb63ea285309fd91280fb691f9cb9572b544b332324f6cb3b1276000000000000000000000000000000000c5b9634e6748d5819396051322d9b7e0377554613a7fd8dc0c71cfb7886dc0ac29add7265af84087a9df5ae3799ae30000000000000000000000000000000000534226ad7324ed5600b5438b659c7b1e96f27ee1d77163f2d3073418f7ded5c613ca4b1a686764ecc43ce3388e0c32600000000000000000000000000000000198267e2bd474dc0415f47f5c87a11fe0945a91cc0bfc37d504ac53f9b9b0d087cd5dbc9b03972be03d4b3f9d2123945d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f02667214000000000000000000000000000000000389a084d95445af6e0afaef21d3676794e45986b9520035111ccdbac4ddc1b23974a686a900616f878f3a06eec90db500000000000000000000000000000000064c75d1129753b5f399c1a5166a0f6a8f427d65ec2fd84d0c7339218e0a396681797bab68b33653ffb9820a6005fa7500000000000000000000000000000000147e199e8c08b9af38cb457b623d0fff32242b11e695f2adc0f136c5596db313b03c2466fb58e37c94704152e5c8f9dd000000000000000000000000000000000e8fe5436baf3470a19891b85d15486d1269e1b13098d837b0a510e71b0e6260700ea85f0bc6476217cc73615370cf003e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000a39a2032858a57ccbfb940741f4ae21b318a56d5567cc0088ed52dddf1e0d5de60bd2da9b675212a9a28ec17fca7c0600000000000000000000000000000000039e2a4bb1b417f8a94b02cad60a3e1c4c4bc5a86a23def7cfaecbfd97d89a5104e0cd13870c9fbd010dfec3ad9b1df9000000000000000000000000000000000bc29c5623f9f18ec2af5bc651a65d89554705a349923ee15a9bfb82c114246b404a1dc1c24d65c8749e7c9cf62d963a0000000000000000000000000000000001496d76f7b8583a64c1627151589af876a2f5e7677611ea15f14606538f6052c56e9fc3ed145c313acea69a51547fb6145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f0000000000000000000000000000000015f83b2f998691e504aa740b4db38f5b0236ece3bc1ca933b79999d55b737bfec51e590c2127d57625a9b7c2960c06280000000000000000000000000000000001b7b117f5d722e320b7e90307ac1423aec5e30c29602d314bac9e5272ad3990d31999bf3f516ac78b2be0e16c0375d8000000000000000000000000000000000fa7992cd7fb679eb5f9f9a9febe9c3cf41a717c8f6fffbab5748572098407174f09457e13468165f1c7275d52f6c84b000000000000000000000000000000000737e95f62aacd12f8aebc288c5cfe052f34c4d16e7b44df4497d9a713b77485fb0efc09aef11c7b86eec4d0cfd9b03ecf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed000000000000000000000000000000000004d48d72ad4e77954ec6a5a62299f0472bc52b556cf3857019f8efdd694758f13029f9d6832ed672cc210f32033da8d0000000000000000000000000000000009b2394755d0319741d131b012ba0ece7e2044def20ae73fe73bcc276af9d807ad75be79202963f9a5c512a6ca53197800000000000000000000000000000000128f856fc4790d9fa68cd2a3c152d675453dd81dd64f0ab084c6dabce456f78c2bab0e7f315439b34f86e8fa61a33ffd00000000000000000000000000000000173dbb908ed617ffffb6aeb212cfe6c03f7ee51c84134fde67de2ad9561a897e28a0efa66257ae0c21ebcee3fe4fa68cd9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000bb7b84476d4b17f4ada0b6f50d34dfaecd611356862895c8d2fee6707c4aedbf565560d4207e43c179c5cd33cbb739000000000000000000000000000000000112d8b10c775218d318090dfcef55a903953f7466c50417125ec0b2c20a24fb50bd172331c0377d4f47aec99bd87a3fc000000000000000000000000000000000cf4e4b3c600053f45f350c8860e47621f50f3849872a91ab115f71a2b04657991217e2f0844b296d3a6bc33ee66e6a80000000000000000000000000000000008f625da164bc9d96be3e78df63bd1633a2951dbea0b98e359c6317abe6ac5799c4bb00bbc2c5d02048539e753019a6241733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced1575867000000000000000000000000000000000028db057ab9421eefd1fd481c91153b5c1ceb0f2dacb0097298cac986f036572c6ab0c8709325b3bc25bd494bb46c55400000000000000000000000000000000024be09301c9be4f726fbf7796e8336c50897e8534614c25f65c37bcfc6e724d530c2782bf483668fd08e91ad09484af00000000000000000000000000000000037bfdaa11660111ce0a9c3e18b5da74c004cb44882b1aea4173e18d3a17f04fefa3b319afaf4af9dbf3d4b9ddb2c3a00000000000000000000000000000000008f2138bf621237a286229fe762968a224358b030f6c20db58043c13727b516097b42d47781bd0f0df2b155197ca3946b21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000b4e2b058d6e77cf95be093375233e5c9c8ee0cb2a3aa93172c08faea111df81b9721a506180b7b45bdde4b58b0b7368000000000000000000000000000000000f7025cc33424a7c11eef47baef888535d938d50c0f40eb83ae86791834770e5dd95b30aebdd2c13eda3447d5730ce3b00000000000000000000000000000000088270ef05480ef8aac5c284358d8e06c3482c26279734b8513000019924cefeb396ae79f5d9bd863bdd9b22e3ac3c54000000000000000000000000000000000df75afafb138fb06bfd905c87035bc5d18c45a29267c3965131083d7e0112e10556d7693d424172a53e8d3120f0cf2aeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000122fdd3c83c01c7cbe71f54d783181860e7dcf8406e3966e910f4d0ccddae3a245d6b1f94b1182d1917fd63960cd75d400000000000000000000000000000000043592e5797cc1409d6d42dacad628448799b24320acbda83f6ea9d232968efd021058f540e3bd73a7f95761efbb5fc400000000000000000000000000000000025b5a8577ec1064b5c557415a50e84c2302df97eb65860f979e5b1e261f47c0f305461681beb07e521cf03f0e21fd030000000000000000000000000000000017e86f3ffe72bcb71d46661a1537918d52e886e362d78ed756140a6b5083a4eebb5280b9eeb8a25251dec43a5cf509b13d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000122f26b4561d1f79a70bd0e401f25d50891c0fa0320579ef21aeed7c191fe1c75403a09260c3872cf74b798eb1587ebe00000000000000000000000000000000039a261d9f48b9eab6e89046f333ac328cea287993166057e9b99fa8a7d7eb3e7c34ecbb353b7427b235084f47f45d1100000000000000000000000000000000015d5e297317684bd0169c795d9dcd209452d024ef9a450c41beb0f6c7e6dc5fa0f3ae24c7cf2d7eef97bdc51788188d000000000000000000000000000000001487564f0e9d3e0d2d30ec9930a00f10093e29f2f195344f567960be323ca21231efd8528108dbee4d5ae4de3930ddedf6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda4000000000000000000000000000000001965ac3a520c1ac39b86832ecbe226ae0474b76659076ccbb550a0daf41c40d424ceda084dd991f22cc53779085828430000000000000000000000000000000002e970a4248823049bb4339d21583fdce9540ec103d6e9530b89e39ea875b1c333f7f5f859be39baad34b374055baa770000000000000000000000000000000003460eafb3e54ec03fd5cc1d460e1359b97f5543e6231d61614c1225ab7545fae079ac8e65668b83d022031a7a54746b000000000000000000000000000000000321394863e7c70df3934d874613b7c9d6c331e59a599be593c82edb7a26eff9bee8e4befbf122240d2deb2d527bd38c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e10870000000000000000000000000000000001a5eebe200ec041476457f8585cb4ccdda936cca4977d7701c44e0d4fc5d9c206682a23348013a055117028c16914400000000000000000000000000000000003519bd1dea70245e521988336eb41870599a877380c0a9eb19301f9b2caf963eb559070e23eaeefa4de0173bb1fbd8a00000000000000000000000000000000125707f5a8e26b28968dab97ef4654c315b0a118c20935e38a5a526d9ac0a0e18355d8c9f3f58c082de98691957e2d5e0000000000000000000000000000000010b58dd683f73a16d8bd5557b35b7003a761bdf7d90ef576de8acd420bc74f5219fe7f9d35667feeb3ddf1d568b56bf1f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f00000000000000000000000000000000018267d8b83ca59d4efce7ee3d73f7b984f09556ea4fa5cff5997a1eeeaeb8bdc9185176d77ad0f4d86f2e429f4015350000000000000000000000000000000014114344d6b7c976cdaf2418d7f72c120c2fddcc65c3ead067482e7073e2a3a239af19f862ad247e3181b13f5236d1040000000000000000000000000000000015db961a093b248e83deea0ceeebfc3dd57c7cf8b48cd627c5c566a4f9bea30ff0ef9cab9287a0f520a72b02d9092a0c0000000000000000000000000000000015159439fbfb91d1e24af611563aee3eb498fde666a1014a9f645037995d72dca0ed5569da7ecd084208b7c228e8a2b2ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a313000000000000000000000000000000000e1780b32a7b17464cf514efc4bdb02283af396ffcf6d1ae023e07fae02becdcc3c467f89f8edc9173a71aad27b200da000000000000000000000000000000000c3e7fd95dd823338bdf3d82fd46c265a3f794d4065d83873b1aca66da5f80c5962c9dcf537fc315d024d8cab7bed89d000000000000000000000000000000000e4eb722080e24f54fac7eed4b94e7b1eedb081c3edd7aaf5433d00829929d8bdef940aedbdd7dfb0376b3ad5544d9cf00000000000000000000000000000000158c1ff057f7ffe6492097e339cc4ce56bbefd39658ad55e08d5407619d1cbea7c83b977a1583ee48897a5e9c0d9ce3e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000138e8bc8cfaecba9fd1322a3c1682c9fc1286d78e5b6718da00acc69f811fe9f94c9f0dc9d80e9002c0022c6dfcf156a00000000000000000000000000000000021da679a068b2f5f473ceed588f07adc7f485003f7d2286a18c07b09b835881f4ab94c7d4ec742c33a7cf01801116fe0000000000000000000000000000000018a62c2f4a02b73f5a91f503b53332304afc9cd8769f236259789277599a203b8b304b38993835a87d7cc970ad514d2400000000000000000000000000000000179396865f859386df7c1b8fa84c4ee71c14daf695fc0841c293618e6f8c87fb56b924f3f91a273b969e8635d7f90985d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd710020000000000000000000000000000000004a8cb437297722c0c1a9471ff083ce60ec40c908af4ebb570c87133df705e725e3209152bcff26a0d6e4602030610d3000000000000000000000000000000001832e55a9e703d727156e4677ef4f82b86c6764123c3ed1dd94ae3b46d7eed459114993968eaf8e21cf24c59d042f41d000000000000000000000000000000000f606d5ee57b188636334ad60057cec4008ace88f14ea06324edaecb26da627670b44b6ac57b9fa2717d03096010785300000000000000000000000000000000145bf70f90a9d98f56ed38b3506556a48a1340ca6161806d055d7a1382eed54e294564de7fdbf525b0012de3d25ab5c838f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0",
+    "Expected": "0000000000000000000000000000000018ca453b9d832f029ac8c7c70df846be97b530e6e42de3ba6943a7d0dc00296942f88eba6a9cc3352900ff124efaf7d90000000000000000000000000000000002e4514102aa3f772f2659ae9f1e2a91c7fb749ea590a3cea2c1a2e0f7236f71e182374cf7ebd2fa086dd921c29013910000000000000000000000000000000007c025696cdbf403494c5fc7f9a10ad0c549f84d1e06c5c4bb22f7a039486909c540776224bcdaaeb3880ae9d745dbe5000000000000000000000000000000000b5b5b70fae8b3953ee6661a0f4a1be25596839482d78710e584d3bcd93dff2b0bf4c8b20974744667e25fd8353cec0a",
+    "Name": "matter_g2_multiexp_57",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001265e90c564693db716f17d1a8815a8449e43b5a2d5446ca65160d864718cdfd413d5aa024e7581421c7222c29eb452b00000000000000000000000000000000133a6558baa53a2b8d239198e1dcd81af1ee46d55137177be467a99edf282edcd47b7861a3c822f9bd0df2e86aeb5dc2000000000000000000000000000000000d8287564bcedb1e57c3d74b0d484a9b475ce3f5b0322bda0e980de8891e2e8663abda99744b58032b8d7d3adddbac9500000000000000000000000000000000013cc35410d7fe07eac96abd2b35ff656e17b6b1eba2bd1d75ce5c87c5e76755ef9c2cce70f05cdec15d1bc44bf902d4cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a00000000000000000000000000000000193f0cd6b4051cfd89f358cf6643528f0f042ae30ba3627d297b4fa2c2936426a9c1b65145b8192f65dfaad1f2fbc358000000000000000000000000000000000a92ca8943e64a391aa39126f093f2b530f556c1e3ea1b55bef1c264909dc93d260eec6420fb7a4e4a45f932d57951500000000000000000000000000000000005c7dc5832f744089d5fe034bc93e0bcca042ddd1b221cdd5958be86214831906ddbf82508dd91dccee467fd1625dd740000000000000000000000000000000011b11b3d24f44bcafbcb9baf62cef3f18b56ded696b73577375dae8108dcfb663d437e4cd9e44b7e6bf49741e058f8cb9a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000073872ce0d74ea368df132897617aa8f941b67cf3fb395ca6c2f5bb2c551f17d68b0c6ef11e742206d6559796f06426c00000000000000000000000000000000156cc28eece7bed943c8410a44af112edd8576807e25701093eac0c9726f93da68a19c1d7b294f3ae6c84e32e7c2d5ba00000000000000000000000000000000050fe5987d5fa678be3d34c50fa6c5296f883e65ac3201c333b97ec0de00dee6187d2790c357a3f8822a174a534539a900000000000000000000000000000000177fee6e2d3909c0536acdbbdfc716f6ca19b6bfee7920a78ac9725c85114c69cd13152467e72270e35006b3c6caee8c74e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e000000000000000000000000000000000dd147bec9e0d1727c9d7597dea4a5b6b15c0a603dd1b586835580468148a502289fcc38194b2fccdcd8fdf0d8ec1904000000000000000000000000000000000186501fa4f3a20e80bf297e8ef1885b7d157617701839a3b524d61f35b2eb843ff0af13e253bbdef653a83e07a5871e000000000000000000000000000000000023eda2ed9d34aa253c8bf2f3b66b3c0c2551cc0e74f43dde2e429d9dea113a62572d245b44708bed79d662d9cba487000000000000000000000000000000001041cdaeb244803556e9b20db95f2a66830cbe47a68aea262865da50ab15ba658116657625318fe46fef393eeb6f3e2ec23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000c27b1feeeb38068ee52b0fa440af2e3bcfd16601c8af983d259f2d15316b513ac3e89069bc141f02b934f2e474253ba00000000000000000000000000000000183f966cdb28f344ccae4cfda63ba6a6f29d00ab942ae7db7572cc09305e4f80c11305527b8ba38c40aae5f23165cf9400000000000000000000000000000000049cf59bbd6c26ab3e25b3cb94878271c73c0b4436573d612311feceed0f1668f4d79aad92360c1c97d60b540239ae630000000000000000000000000000000015f35eb8e4c40cb1297f7128d99b109ca75944c1943abe9158813432145a4a2a5663b55dbabfa48bfd9dd01907e1e8d3972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a3264000000000000000000000000000000000e5d9a671862733804f517dc9cae2190ef0005f26394e3161fbe771b9a486368871f4b1f10f405e45048362f437238260000000000000000000000000000000008100c6f96ae7af5fc86d9d91fbbefcc1bf5873dacaba9c3adf1b2833dd529d87f303a55e5d4098153377effd0f8114500000000000000000000000000000000010e4863a9b037d4ae6dff827a34be04c7f1627670b40e5cafb1fbca2fbf56af9ea6b24548db58e3119db64553d18cf200000000000000000000000000000000036a298ad5e8b32041a18e3f6c5847eaef20a5b63ddece41bd7dc4c4a54deb9c6d7002e6621aa01d78d64ec9991f68fbdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000f62c0bad83c41887bf1ebd2644cef0577d793c2f3d67cbe43974f460a4afaf2e412fbf9ec97404e5e882ca0b23bd1a400000000000000000000000000000000191562ec9ace63ad2aae1f7fa977b9e0606e1da9775a978b2caafada4f6b3d9104562f2055fe037cd06df6093123a08e00000000000000000000000000000000156702c3feef1baf5ba202a25b9dfd5c1fc620e837501b0c5bcb85ec8b6e3e92bad1fc842bd1a0dac363e4bdf0fac87c0000000000000000000000000000000013a4b7e869ed9bdbf9671a5d8ca9145a2e97b6885d2a93b33f378e649e0e576be65bfe849119381057337315363bab2f4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd330000000000000000000000000000000000a4b273438168494f0db235f535bf31893bb70f4119dc4741aa3c5e63e93b9a8bc001faaca10e37f36e130ef53853900000000000000000000000000000000010936551b148e16249dd934fcc83dee55279495c2a70d46dfc45945a69549657c3dd7cce00d8136e28d64b0c800344cd00000000000000000000000000000000115c053ac0b68573c3abd5f047b8fcd897e3d514945c5fe6efebf1921563d0079eadf32f7428ecb703d9163bc7811ebf00000000000000000000000000000000162e86af01daf552589b62be849e6176d74fa5da9b214a5cf2285802dbc44f346eaee5cc3d93a085740f74cf7e1b17e1dc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b000000000000000000000000000000000d77cb5045f7d4578621c76bf5b3db076661c72174508279280de3e92f0aa57057ab50180f0f908561a87d412636d964000000000000000000000000000000001853f9cdccf5e6e4b87231b153ea5257f52ff10dcb24cbaaaa95426d0231dbb355f9c47475d125ec1079b9bf26b23b560000000000000000000000000000000000fab825e06c2329a19de853a05c4bc65f16fa047eadba8e79607bb31b84ed6541b00f7f14b15687d67cb4cae0ef9c600000000000000000000000000000000005deaebb5f31a62fc0bc1af13da63d0af3c716df8c9bf00f1e831af5882b88974c49e8d35db2545747c85ac35156bb668af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b0000000000000000000000000000000016faa5e91048badedcb33e83684d2670051c82b7a1d0ead0e28f4dddccb141a8ed1fa7606e4b6a3a893c55344263eb4400000000000000000000000000000000019b2c8758abe5d339afade4ad0c1d44d651f185f8a0030b81b136d5972510b353d43cef616ce04827d56255419831a400000000000000000000000000000000124b1e87f343a890fd690e384cd156da57f4f0fc5b1ca99c73bb0571332ec4c12d3ebe955e3ae792efadc1d5c0c67a410000000000000000000000000000000014cef10e4a9a41bf117aacd2fca5f1364a46b0c4aa0723a369fc6ede09dc76dcd8cb67fdf87ac49bd4bd9981a2e589647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b80000000000000000000000000000000002862fd5f38154dd452f65de0d3c1d54403cdd2a397ef416fb92e570913c543d3368a95fa114fcf48c3bb4b68895ba33000000000000000000000000000000000e7185443e5dbb656fcb9ed100949f8f7052ee2cdcba4f5c687a65a1b45bf66ede5c60b0c04845b9a870e004f8af8450000000000000000000000000000000001817be6d13cf2a67225b2eaf073e9f1614f3bd32cf5572766ace4a91f6b6be56f498b989f1c3dd3dbc9a819c029431dc0000000000000000000000000000000001cf41fe428b088a17b8ea93a653677705d5c024db530b8300752c6b100f2abe4c46dfc24afdaa2b3d53cd8ce0df1b6a70cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f8520000000000000000000000000000000002eed272430ca3176988272e6157a18df7151bbfed5b90979752a02619ef467af8083208dcc9c7d926490b1283baa21f000000000000000000000000000000000a644f6137bde232c3a909b742d30bba096ef88b711ef100144276d0944487f9ebe8331483978a47c07d3a42c441310900000000000000000000000000000000042c67cdc10efa8301ae95d6d4f21cf152f04b235bad2dc5a61724cba64083f690b3158676ee6ef10f52dcc7061f7c7d0000000000000000000000000000000007018d0aed5abb744cb998f84140331fb2cef8d9e09c76176def48a85370c6247c2ac6fc726eea891b2041ad5edca7f0244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f00000000000000000000000000000000141c0edc966b7c845d4e68272c6a71f8ffb7fd8d56b7cabcd556a98422f830d7a81d123d701ce1479e84047328ac1f3100000000000000000000000000000000105c1164d721b6dfb05b6b69955b2f25db0e9fdb58600a3229dd516076087aaec05b837ade68bd2a19917eee7b9a22bb000000000000000000000000000000000da3dd97e693948fd6955ae52d493b3a2d2896dd4ad00a0b549d4d392e81593472e4f9435a8b7977f3d58e324c5b9af800000000000000000000000000000000068c531ddb26a2299cc584b5bbfb0235fd774a2447134c06e7de8b94993804958bbf1ee80728cc6db647e8a244462372ad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf3000000000000000000000000000000001881432f4742dbe41bf774930413c98d49a781a48d6c64ee1a18f3076bc6c0e1214f92d5bc84ac65ee1c586c437d697300000000000000000000000000000000067e0a95f3eb826f3efeedc1882ecfa30b8b96c92f626aa324f4044ee74531fbfd50a221b1b0e0182d759d149d51427d00000000000000000000000000000000173f5be7098b756ea84f030e374973feb4f8811118ea6673db1db75ec6909303e571ec5a1d55a6bddf32fc80480cf103000000000000000000000000000000000f28540976a6ddb277df5951fe58e7310861af837cf31fe31c24f7b979f72ef1549372e7ea1ced15b655d24293dade7854b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c600000000000000000000000000000000188f296e218719bb9cabefd4f33d5728a1d280bc59c3d826a0f3b5338f92e6544a4cf36f1a493458e0adb246c01a415a0000000000000000000000000000000007dc8e4222c7ba78190a8e72ec7e6980e2581f51a8d6c41669b6fc9e16d50a2bf4d422af73398e76b2f39705eaf8a6da000000000000000000000000000000000b25a44523323301cc01b50d58726768c2cf61e691203dd34a0ce8d58fe4f72c1c33abfb2a56e0425fa9b7e2fe48e870000000000000000000000000000000000c6f11ea269d9061d2f462ac37401def1b2b28c47b84344d04d1f026add3237d99a586e3fcbae347a4ecb5646c8c569fae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e49000000000000000000000000000000000c41a02e937f8cacc0be5d9f2d9fff0d6d4302fd252f32145974206463854b3a7d09b3b147cdf2d7536e970dc13613ab0000000000000000000000000000000005f9367f4e31f7e4d6e21664ac13d55f501f5368c1ca77fc439db60e1846861e6c4c3c44909469f88e02cd973499992300000000000000000000000000000000131fe6df7fff97f132bfcba1d2599a862c1feb514a05b4b7b0bccf49e00aaad043edae9346bf726e2eee498dbadf2067000000000000000000000000000000000e59044f0950a741da3881282697f4a1a522b026e493f6009227da4c0a963de622d5e421c30e0023f4118c9a036274f859b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4",
+    "Expected": "00000000000000000000000000000000144433ad3afca0a9581e7e87220a4944e26ef2eef6b887ce77d2a2559ced058e7349b36efa66c492cc75b014b3448ef9000000000000000000000000000000000267b90e45d7001edae01fb198d16dd37c43cadcd2ca87bd7cd1f0f65a95148144f5ddfe75d344eb4573c1376aa2728600000000000000000000000000000000050ade28b09b0394b08d128c089808021e4c65dac49d9fb45efb93792a4faf210230b650fc3ce810fb8d11947e9af5060000000000000000000000000000000003b1d7dd7c6d944d16724fd1bbfe0f53b6b50a70e133dc5998c82b51f817f489bfe1e0c361be36fa41f5af7c1577f2ea",
+    "Name": "matter_g2_multiexp_58",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a081f037738b0d812da43a907e7c624e331108ffb72104d82725b9c14dec8449f5ba0e8c1a3f1379cad2c3e7aa99f70000000000000000000000000000000000937fb5d8b3c258b7b28555fb59620f114816f0fad46818a5f100bf7dc3332a03d285eda18e31e4047cb2606bc53b20c000000000000000000000000000000001574e355b7570043bf36ecd52f9c4d9ff556146d81a1e9d088444805db9b3b678fb55774865ad34d21022afea2c154590000000000000000000000000000000009f70a5cc658cdab280ed65e13aaa319049b9534a222217a08168047ee2491f25a9d2620c7343a6426bc54a0700bdb4fa53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b460000000000000000000000000000000015a76e89c8938b8a27e4857aaae8c942371b6979605adf774827e9438ef739428fc53b65d32e4e152cbc6a4de42b8bf30000000000000000000000000000000019494030ae0507eeff20b69b4913596c1b9ea6927157945c8295e273707013ef1f2cd08c058f6b469a6c99ad73acc28700000000000000000000000000000000122ea7ac21a27ca7c4b00207538bf561f688429999332c45de7545046acbd6d9e96d31f5f6a00595eeb212918a28d2920000000000000000000000000000000018b023e7da67cb8d9159746bf700f9e151fa60ba8f5a28b3739de005822929cd28c49b9dbb4ca8a10729dd24771730ff4d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000007811c759634904765029e955c3deca648fba6a9da6433b50a6d2086a59e65811d52d41ed8ff2e9bd63a4c0828bc702c00000000000000000000000000000000182c86cddf5e20697462c829f41c7b49e7976880311b01ed4d12d7174340799f19db0f295263a2617182bfd1b49e0d1b0000000000000000000000000000000011824bc20bd1b27876b4f48aa8fe3063f826b6b2c3dd777fb8999a25d9139f218f6f288955274884ce96ef2dc6d34d120000000000000000000000000000000000dd310d5e141e4eb13380db828caf74f62878959b6b2df998bebf9306965f723fcd4dae7c25bf2f79ece3e8e9b92de61b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c740000000000000000000000000000000005667d8c4f8dc3f4aa0021d1026a1d0dd0bc3576c49339262e84d20198fffe33a389d28ab1d782e9d19af761a2f097b40000000000000000000000000000000002803d5ad6393d7072e149f1f2ebf70cd8961ba3bbefd648916a8ac5a5eb893b71bb6015e201dc241537ad5890024239000000000000000000000000000000000122e1d0e0859b04143f23c4d2d2ffec09ca2ce5eaa9429dd0c047032d180bcdb10c106071d9f9701c006e5eb8ef88130000000000000000000000000000000008347a7bdb3b4f381b58ed3a128134c09563b345380ec948943e738347de5b5737540b57c28d00b9d060c60942446617ee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a5000000000000000000000000000000000b26aaa46a279c482fb395ddb84d5b4c9c70102c336cd565ca9eecf62cb96f59f634adf46af748826590fe65beea752b0000000000000000000000000000000012cc63256a9f73f450e86ee38c54ea78baa5bf87d3bc01320f7fbd85bf11e19f75d787b9b12b8f2c7634368a9023de880000000000000000000000000000000006392fe611835f6fd50229725d71d435f704f78cabd1b5569e1c5a89d4b11f911f0e34ec034369f972a80eb407938b97000000000000000000000000000000000f4ff2d6a991fde9093000d7bd9cecb289383d259346d83bc9bf5389d4c39c82a0e1d7deb84b90ef370e0a19fce28d2b3e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000193752c40fa0f466f7c8bd26658f133d0283d2ac3b02eadd27b3e9681329307f91a1512fbc53e537f9e1025a3d68a7ca000000000000000000000000000000001106d751c9e1637f00e51e0be856405e6b69421d81bb30b9b8718cbc9cfdc36c80d2848bab0d5246da84f10b478fe48e000000000000000000000000000000000827a83f28678c4e39c4963e95c2404a70691885788e5457e149c0c45d4e8c74eef55223ed15cd75fad9f7209a6ecaee00000000000000000000000000000000072667f02b781c8e0a75d0ed8f3d55e668ddcc8c61937c80653e240c3a744c961055c782ca41b15211c0f1e1ba800bf5ab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001419629aaf0baf779feca264d0d9846b987506125b0049ebc8b307c4e3ffe00da1284a94a012bfd60456a4a937b2e0e000000000000000000000000000000000119a801bd0a5a1c1b25cebbbcccc7d2bed9baa4995483f4ae94121a8c6cd0c3f90a26234f51590d66cc38b8bef9020d3000000000000000000000000000000001125bd15fd9814ddd15be0997a6961b6f1c05ce7944514371f10c8e5bde271c4b936d6537d91ebed740fbefe6b281a0d000000000000000000000000000000000982a2904a524b1fafc50d540506b8fb07c3b4978310bf3cf53ce570b1b05e746981bcfc06d59a78d170573b09347f3fc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000b1da333e508ec6b0329747fef35cb926d922091d4a45eab7cb5358f20496c66e17e46874ed9600cf4252432c29aeb07000000000000000000000000000000000c757daad8f3ed7dfd64782548eedfe904f7ef3bcc11eefc4781fb37159d07825a4c9f3fdf9cb3d8f3944277bf25f88c0000000000000000000000000000000011160e21503d6fd61a2ca0212a7d48317186f259a987a17cc3eb04a6d9251736e4a66b739a8f3095684b7d91ce6f79730000000000000000000000000000000007440ec0f9197352a3148f9bb3d3dba9b1d5add903e48b50ef3f6879859b22ea0e31b46ea4ce566930d8853520abdd14ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba00000000000000000000000000000000070ffa4d522df8b9f62aaf36132bb1b857e177280a7b6d3af6bfc79b73ad3848241df18ca7f8993ae3d67005ead9264d000000000000000000000000000000000e32b65bf035bcb11f86c60a334622d2367797d0226761b58a7db8c7324fc4bb498a558eec509c2326fbd0e7bb8d3d19000000000000000000000000000000000dd291a760393c6e962818986727e5ca5d46544dc47eb49dd828c6f74caf0599e88c4293881714c425b0697944faa861000000000000000000000000000000000f7ead0be081467f3371ab92c249cea73dedfefcb6aa16a162c06e30605e104844c3dd194b4a89ad5230f596bef64f19e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ca545b53836899e507880329799e4c1a1acc17275f5d71d87b9e41ccd7a090da854f9936254448c988ec772a813bb6e0000000000000000000000000000000016c9b03fd01394560497d6a03add63c034f96744d96a13a4ec92d28719018d1eba1465e4332e53f37f2aec4d93d4ab7f0000000000000000000000000000000007019f5201dce326d5a6a1ebecf3fe50e22335593bc9d3e62256351c591f0a1a577d916055d79c0b4abe191b6b8011fe0000000000000000000000000000000017acbe72fe30c386e463f3e9b35a474b902f6712b30af88ef340e6fc6ec0fe2e606c7e26432c2a4de33a12e35ce41868f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000057f856ae648279f2b6dd17584e1388e4dfdc9e870db48ee6ef5f58389ccd4ba17e074b79ae12b728c59e2f91bac5709000000000000000000000000000000000e0f39f4beddbf05fd700458448067b52c11e963b22603f10d697d6b6286b1449b1663e032bf7bea48f2051d8ded923f000000000000000000000000000000000022cfadc1dc399ef5f12afe1349d9274cd595a9ab6ef7ffdd68f8bd2d170a4a783ce0a7303878d809a16bb8073d79860000000000000000000000000000000007e301565124eb66d59a70897f2ac356e7b0c1bfd4e3b57e508ba0cb5c9c881f9de86b91fd5133aa2977c8e81138d66971927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000000edf3fdbfb03bc07871079aa4aade538a97e1619b54d0692a7f5f73d7fbc8abbf680ea3a99325e03c0501ef174deedd1000000000000000000000000000000000b8c1b5d3c926d7da6e0583f67d981af5286a04429e857b0aa4b1120604f9c8c93f04e763da169137416dc9ec4839a910000000000000000000000000000000006ca2aa4c7109f043da9cd90bc801404685db802eb8bc925d9d098e7af3d9f95ca490790b2b1c77995c050aaebb935db0000000000000000000000000000000001f40a2090b63f94f93e8b61b5ba1ac62a37548342ad81a9bd99ce8339435a7d7477c3b9cee9b531a1ecdc85a72041555ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000021464dded318cfa86db1e4329f302bbeca7095d910c4260799cd2a60ebb20e60152868e67a48b86f44000f267d11c33000000000000000000000000000000000ae45fa46fc8e043c3df99bc0d87ffc5867208fde0eaeda782230341a8624b101346f35fa24e1dd67ab200f5d6fbc8a7000000000000000000000000000000000795b9afedbb128a46c1eb25c52a71375903adf7d3520535372d9af5023dadb1dfefdcc0cb546e9d218890123252946d000000000000000000000000000000001852511855bb368cec51c54d95b430259f05dba6bae53b5c42d69f31371c30cb611037fbd81393a896cbdb6240114549d37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000113b883c6bc41b0673145bfeccda414af45efe5710f436977712e7227f38911cbae851dbe03928f38e310033458eed72000000000000000000000000000000000853e32773ef1f95a3936aacbca50cdd5eed3d08dc467d7ee834487e445fbdaeddb0df394bd0c91fdb06d2883c4dadd60000000000000000000000000000000013a7f9cdebb2ec37fad172d31a717f4b538a8ee74432c5a5e6410460eaaa3b5f24d223b76bde4277097e93087b7136330000000000000000000000000000000003d6f141b56e1e2e400fe821524017cd972678a7d64f660c313e6a8910b72b5ac04328d45945077aa2946931c8dbd11706d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b40000000000000000000000000000000016adbeb3530f6b451d870b2d8292a01143986cd9890c79a64764383575771b8608ea61beb2de87bc034d3b8a085958be000000000000000000000000000000001125d7cf83239e4341c286fe0c8739e7013b234814b26a079ffbffa329ee4705da81fd12f34f49d821690a11b8f83c5e0000000000000000000000000000000005873dc5c0baf0f3297d884ac7b652c749abd0405b96ba60fe396efa179a79fa55be76924b0690c9a528c605ad4f9e120000000000000000000000000000000000fceec23f479c72e0fea0d10d3394d7121bf1673250cf1ebe72eca60af82f232fbee342e2c8705434394d4e519fbb40f56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af0000000000000000000000000000000005ec966cfa28e105f3496f977a2f046fb206a190fce1a6062df0fa1946f274cde9f6fa8a71089af8cc2fbc2b60746cf40000000000000000000000000000000013c77ab66fa92a2411391d366a331a40accd120db1c6a656bdd92858826fcbded296293c13ee189ea3f34635de56732c00000000000000000000000000000000162795b6feaf6a63e6ea2d34f2bff2a4985ad26463b8fac69f8525eb0a005bd377fe7ff4aae820d361592d2d88f98f5c00000000000000000000000000000000044c9d5d3bc0d99693f5a0605ed467cca8b5dc7c7093294d14015b59bfd8ac6bd479b73ed52fd30d8bd891ed971912c571e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c",
+    "Expected": "000000000000000000000000000000000c3bed2f51a60f9afa6655853ec2f0e9d46bdc1277bfedffc468d9f36cfc7ad9e70365fecc84a5a40d863dcaadabf22a0000000000000000000000000000000008c5894a4f93b02fa1deda8b556798fb7d71f53046ccc305588bfc00b68bdfc34b3f0bf154ce7cb50c9536ad45e65f300000000000000000000000000000000003699501ebb9698e98dc998fcdac54dff895457d2e4e0a0e2d65d275b5798dc016e921bf1f65fec0f284a563aee66ca70000000000000000000000000000000010389c73de7f6d860c972c1f09dd24137c898e92935c45c10565ef3da3406cf521647ef80688f6e799eef4879ca9a6e8",
+    "Name": "matter_g2_multiexp_59",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000114b9c33bd09899c684e81a5a4e620eefa4e620c01c391a4df5caa75be462ec7ab027a9ae2c31d6643c48e3d75b6ced6000000000000000000000000000000001925084d2a1f537329e23c77b8a820c385ec5e12e4a145888882ec611e99b05b789d79bcab48326db4424309c24d1688000000000000000000000000000000000a1dc78c25cd16211a38bd0c70d24c84da1b83adb219e1b9c06fe6a6669d6e0281a155b4cec32d32751fff653aeef1990000000000000000000000000000000001daa74f19cce1086a87232464ba903938465da5e3e1f9ddc05a4b4dc13f1026e1b07af7254d515d2ad6960ea62dca1f77f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da0000000000000000000000000000000018810adf0cc793c21726e9a27b7c558aa16b81af73f22629c478d293208a107fbfed4511d9cbcc25fbc2826bf004e7dc000000000000000000000000000000000356b25cbc7cf65107438125c930dff24b7786cbd7eb744d7f27967619d5cc02799451ac8814782eaf9aa331e6f8dbe7000000000000000000000000000000001164ab32ddbeb11c2c8baf7f311ffb01bcc367395bc7ecbe5642d344a8e879c74a554b3f9e4b6ed7db4ea0f872cf96740000000000000000000000000000000017704b1dfb111807d1f5d90c370a5b2968008a5ee9fd72262b6543c93fa168285c04931198f5195f1abca648722ebdc5630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb0000000000000000000000000000000000a336a04a8fd8e18dd9a582da897016983d9beb0fdbcea6c88b7c0640620be52bff32afbe700599e3c08669c457b760000000000000000000000000000000001765fe4faeeb13fc2c007682c031ea7ff2899090e16a9a11959c5c3ae7881a1dd2c6d2b7f5f708a92349a2b0de4b92d5000000000000000000000000000000000e7c57db660133ebeadc2cb2054ab4ed16355466932685d4d11038e1e1f47b0349b68bc4e918dd48ef8e1c5d7cc53f7800000000000000000000000000000000169b629ddd7add588b91d9866a750570dec58662e43409031a5e25f1b2913c5c5a7a7cf666953c99835431f091ab1b140969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a51620000000000000000000000000000000017d03e9855f3bbee719a15208ae24324ebf1879972ac134b027c9e03444a5736863bc55604158e81b38c7fd78ba4bee7000000000000000000000000000000000468f7c5478cc0faab7098dbcc455bf18525b56272c2d02cc1febc1825579a613edc6b455764ffc71c903a0704224a4c00000000000000000000000000000000067104ba5366e7e11bd4d516565d9cdd93d4390f2af3c1ef2ea3b1e84ee8e5c0e0fd8ac11ec9d2553e4cc13b277d473e0000000000000000000000000000000012e10495ba15b29c669cb9683b2fc7a45fe7ddba743b4a39677fbf85aa738480eb9da967eee69b02ef14137e102e240eddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000078f8c17427847ddaa1665d206866231a5f36d3a7b4e8fa13910161566163006b5aa5d9696f423d0c44195de65326f21000000000000000000000000000000001613c465b65940f43c61b5e3c93313ae49d92728518d9cdfc57b49d6924479b70e281e724e04fa5f165b5999f1c1ed3100000000000000000000000000000000031741b6830c16d730619457d42767a51037fb4118e00bfd6cfcd8baea35ae76a5159bf1f4639fc2951f0b57446110e70000000000000000000000000000000011a618ffbafe4bad0a435d04084233495e5f7fbeaeb66d0d49a8177f562329b52a5ed4fdc680b791f273a7b0d3d4b349191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000043c09eea638e524661c60ae3704fd1c18c46443ae134a0ab7b9a98cd398377febd9026c28b3e1e50de98766aaf0083600000000000000000000000000000000105918aa1476cf52f91b9ddb7c23ac18af3bd5269dbafc369713687010720affed6b12af9414cecd521cf0c7f5416c350000000000000000000000000000000019ab4a3eca904a15782f560bbbc8819dc09275f1f6d7c3b8e98aa0a96ec33dcb528284636b0f42ad0d503489d17161ff000000000000000000000000000000000a2abada18e79c548d5829991a65491ebcfe0e1a2c89a1e05f06a0ecd197797c5ffea0ae90b61f54c6b3fc844e0eb3ddaa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f848000000000000000000000000000000000d457cb2c77acc8ba4b19ade0c724a2b6b0966ecfbbec8cbea745439b9bb7f3dde2febf9fcd6c5e6139fd7175e57b1720000000000000000000000000000000003154466283addb0d0b5d86a9633f8300960cbe8bf6a1405a3a040472542e9da63fd4f79a43d641a47c2b69a31298d3c0000000000000000000000000000000006599794823797f8ccea9daf0459b9d26e0d207f5fb95383c6b61eba38516b272e8ae6ddff2a9fa791e69c0eb25f3e470000000000000000000000000000000018be316bbe0416ad7deced1486d4e31490f5dc7e379c17542b7d3e9dc77bbae9c992e657c884db320cd51c2141a4abd2049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf10000000000000000000000000000000007ba1ec5293d169b88ca4d2d92eacd51f0b8cffdb403632ea8ffdebc37f3997baf736771231335d12717cb45b51be31a0000000000000000000000000000000013505cc24222fb2ba9e25f5f3497653462f5b10bdd0dc88f9b16d5643a99ddd4a7749dfa6b566f41cd2da7c2b1ae93d2000000000000000000000000000000001465fdced698ca76d5faaa7e4faf1260cd5c4fd2939b16d3593e3588c92de3d003540ec989be9632fdba4ecae889ef180000000000000000000000000000000013a20cecd5e8f161ac70e40b8e9ca4c23e2b267690a3abea941c293b03acbbe4fc68a1e7b6d35b79ac46f65edde73a3e0502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f000000000000000000000000000000001936436783f02f3a5307bfc0bd8c0a00ed8013508a440d040ed4f45b37a4e89986102964a328e93fabde6d9dc7ca424900000000000000000000000000000000000f16408b869303181b4b4877b554353b26a7b4750b711f3c41cc4b6682b2113cc772cf9bfcd0cf60e59ef29a5d0814000000000000000000000000000000000d5880e2ef94663ead736687ee725f7ce98fdc594230c1ac9e8345d39754bd616e261076aa5362776a6026129bff105c0000000000000000000000000000000006865ce3cdb5081e86535beb990d95ec3d75f67c7e881306607e4876c42714d627f8d548849aece4382d1c8f2b693bdc9787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef523713750000000000000000000000000000000016d941b6a0dc023fa2699c836b74e16c31b4cd51538f73fbb271d163519d4de1cb0f6ec2f8efde22c74ffb532c576b16000000000000000000000000000000000d10a7bfe9541a7b22d455f1b68cfe2422a83a070d93476aa0844670f02aecb36e9f41b9d66e8e9d0d67c0ba85c99f44000000000000000000000000000000000d7873f96d45fa8c9ba9cb4913a7b01c8e38876b6bb2a05506d23df0491bcffb42983ef663db85bc3cf755f476291a79000000000000000000000000000000000c22fdb83f9991c85b3577d1ed5a171f28460d79dbc6167b0c30b200235c512f999066eb1fa449115aab55128f8f2dde10b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000010faf9cb9d0fcb487c9e86a2d2123105baa8691d82ebae8f5bb7d5ae7b7d8154837120eea86dfcd35ea5482a7ebf7f8a0000000000000000000000000000000014e40640eb6e8e38651a2eac05165f6cf5e0178b3711f34828766ff9db951e1348f0cdc652a78840dc24ada8b1c835c600000000000000000000000000000000129db7482ec62873591018a8399a8c5e4bf00e8bd9dd78dfa3d0b4cd1d93ce5ec7531e56d58b7a1cb3e58f062f6895ee000000000000000000000000000000000d8db3b54b6e71497faed107b31f5e44f328780cf01c62cb5ca00f99f10385ebb22a367cc89505640d1106a9ceec98c4072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000011ae9bc3ce04df2add17e57f260a72f88f19a1e44b0b074cccb7fd547035038d19e5f2228db46843343a69823decda370000000000000000000000000000000015ea64b6147ef76212bb5223d6d5ab9ca866799365683720866d8ce1117f60bd552a8e9981c095894258ca3c1bb5150500000000000000000000000000000000173bd5cb455b80b78951b15180fa7f8fb4725c1a12e5c53df1b9b31b45a29083e66c7116741d9aa93448c81b5e6014610000000000000000000000000000000007eba059855ab058c2066c643ef5268c864d09ec9962537d65a1686322c374eb5ab8eba4c4260ad0919dc18b4289a694f3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d6000000000000000000000000000000001598471460ae082c2e2568602c99923193c913b9e803cbb7a4503ceff369e8c4bb3a19ad245c08192e12a2e9b3e75c4e0000000000000000000000000000000013b289bec9d97c529382388f7037749c10a64f915746d23d8f37e15db9dcb173b3a6d00bf45e67b8c70959472148321d00000000000000000000000000000000094a99f9b031a51b7d54f7b8865621b204c85d23fd66fe8ce007f0b852f8b5b895010745b2fc469abb670e38fbc41e50000000000000000000000000000000000e36daddab2134f65696ede36c50f90f9a1c56165e09243cd56fd3d9902d3c78cd85e7028f6dd466f6a8655da62ecefd393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca17700000000000000000000000000000000050a818ce247367e8b57673d205d6bff8c650bcab7bf794dd32494669eff865fd4e05d7b4d35eb579eb475a3a0320ff80000000000000000000000000000000017ae5d612bdd46e1351dd1367c08c16ceb002a29832eba75e48d4c82e364f17c58525ee653a0940955b874da6a5bcfcf000000000000000000000000000000000eb2075367b42a0b3dfa30799ce1ab327eb583316d15b8cae21b716e6c7fd8cab96c67bc39e353f5e842e74995356c070000000000000000000000000000000018ca4b533da1baab37f05afc3ae0afe976e4f4530401d2f97176f5c73de3eaa75b8a34e8c6c0543ca0a08aeed28e478bdb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000b3f4bfec920018663bb39c5520491da5c538f82138f03390c768e088bbb2880287196af937f1f70e215edd49d1872ea000000000000000000000000000000000037e7607a60cf235d8e4ecbe69d378dc02f0a8e40b7f23745e15a73fdcfc971cc8707d55a8c5b91d9a5f42c2f49c455000000000000000000000000000000000467df75c2703ccff1a01fa5bdebde210b61b5f3fa33e76e55be5dc953f4758c3a2c499cbd42b256ff5a2005949d9bbf00000000000000000000000000000000010d574c69050ce9e909dc23a76e9a2106870e8d8ce2a0e30d42cbfeea56ce3167535a9af1d453d4d8e6a450eff870638e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf24000000000000000000000000000000000907fb825f247c85d93fca36dcede9c22a409fa82fcf540593e8247c17875a1385fe009f0ff43853c404f6c96e2809ce0000000000000000000000000000000012bff10bd4162207870f6363342f2541804adc6a4e3f7b8be51d361be34def7a85fb39357c85a4e8df670fe39233bed00000000000000000000000000000000014f7e61ccd52bbf6d050c9d506751e03c8771b320872179a9f0161ac5736edc13bc133bda6239abba1ae09bd6c16f0c3000000000000000000000000000000000ca78624563584f8929d72668da70218a2da12b42c4b894108e6b103201372554fdd6b3bbbf2d94a9d0cf4053eb07d460940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c",
+    "Expected": "000000000000000000000000000000000de8e87899b294575392d523ff6e153a7c2038302ac74574bfae7fb222558f2b4c9556be1bc2757b83ebc180ae710187000000000000000000000000000000001881c7688debe3ff795788c90397c3fe3d6d9f56da9221778d7b12f5a54d8c0a00e1a8d4bb9c0b1d578dff862516b5dc0000000000000000000000000000000014cdfdffbb956a20d8521ccdb214adab14975d22ffbac107b2c15f42e97bb823c6a3945a5b299d3226e2044e64f8d1ed000000000000000000000000000000000eb769b301cb7c0c976623badda4db8ccb18dc7322472b5fdb969393d5d82b3ce94bfa59dae06ece424bfcb88e24207a",
+    "Name": "matter_g2_multiexp_60",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000164227fbb787b2d47ceea93faf1cf7890f48107ffae3628192235aa57658d9a2861db13fec0e58c347571c2ab0cd11ea0000000000000000000000000000000015478417b6758826b1d6fe0c562d43451e289dd50de31ef365ec70faf961ebb65b510c4788b6c7da2dda9cf56d3c8a74000000000000000000000000000000000f9e50d802ca8cbf80caec6489fbb24a2761db1245d9f7e820e6747bdd0855902ff211c427c00157ed9b1bffdf39eea900000000000000000000000000000000128f69ef5dbea5f80dbb9558a25f133b9ad77492250e0654f8fa5b55266f2fd26826a5c373afcd74990ebf768d6d8fd20f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000005f7cfb31492dacae51caf4036d99d917fa13b0d2353bbce4e6547ea744b3a49b162deac2f107149ebc2f79e74828f720000000000000000000000000000000015ed4627efa9b318cbb52f518b734327f5d1cfbb097adc6184c5034620504181a298ac7e52759586dae2e107f121a9b600000000000000000000000000000000023e832638849599d9d7854d3ae18648e67e8938ebf606a7c86c3a7ea21cab8d4dd5d9cda5c482e05d351ea3ccd854710000000000000000000000000000000001849665396bc36d0301f4c9adbce81fd2f2d0c7f89925487d91a25c6bd0730ce31678694a319666cf42162608ef15a834680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba20000000000000000000000000000000012c9b607e29e35f260f3c4617b4217d5dbc6953eaeffaaa903710195e080d593972e7794897eb176aae3539401a483b10000000000000000000000000000000019cdae8d1d9035d1fc4b4db09e7da3c20d3b8777523155d407cc6565a71a6c951eca609d328ddbb165c2b5a3e6b081da0000000000000000000000000000000009c4629b67c1c50e5fcf316136bc645e9e62ffadac8495c084f97e32b0a3990b3b1019261f78de576ff7ffc89e36e2af00000000000000000000000000000000070a49e8892c5b523f5914e2341dde63127b694eef556de6dcff603da109a53b342363d9a854dda3d2833e25afd5b57eefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad0220000000000000000000000000000000018d3c9259f70312c803dd6bac6488541f92482f7eb61ead71fa42bd5e2cca9338218d62835051bd308799beeed3b422b0000000000000000000000000000000005e0da6859601b6ada82b1826a455a846f8b4e54d9f22c3c639835a8a89e17ea2d76e2f49fb151f519de3e9adb78f0590000000000000000000000000000000010113d2fdc1e8ce0027b651cee6f9f6832b531d843db3ef7bf209aa00018715c1c42c68a82c53247a267929ea3c9363f000000000000000000000000000000000e7d1152af6448aca78aa7983013395f0dfc298848d86def6f017780e9cb144bbb21540a14a4d47b61d7a9b8c62376fc2c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000014125c81d4d7a8ea18004d798311f0d80c41c8e3a08366f686145e867192bbb13244f9f77217559cae72a150faba12a6000000000000000000000000000000000fdcaaf79c0607ebe9c8ca309d29d32284f3567a18dbbd23da9d96bad7269395ec2445d153711df4c883e8e7f7b02ab2000000000000000000000000000000000d34dd6636ef18b14f011fbeb62d33ec4358166f96f38a54c36b8797b51c1bedafa43d9f51fa4afcc2acc0cdd991997f00000000000000000000000000000000017337fab49d545caba55b763c23ce9bb3d3cc475f5ca37a15322e94c37825fc800cc7ee67bdcac66f9b5c22b03bf6558b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc930000000000000000000000000000000013434d32deb96edafc9a0e855281970b7c748c92b3472b34cc758dc3c17c4e6fdcf3190c910fa54a0259ef8bec75a3b300000000000000000000000000000000137df92ec14dd2fc02c0ec15a4e63547492154b4d4809e25f3ebbf24fe84255babfd6949770ba61637cc67e8ff299a2b0000000000000000000000000000000012fb20ef106e8cf3c79173e15dcdddb216c25a4de6797e411fd11d5632aef1304b36f8135c915c8c38caa2d778788f060000000000000000000000000000000014ef5cbe5711a815b9ff845e9201745f4117149b54ea3c6d1606060a192d513aa8ffe73425e37a42537773796b6fac8f202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000000b1581a5def94e95e565bfd402cb84f2f21c181639c047d8f91044da84bb7854f5cb4eb3a6cdeb66569d99410ca3ec6c000000000000000000000000000000000d8029828f4ca245cafa7f396c25592ef08f6768e1a5b806450be6ca5b548cfb212d8c4787c3f15fe922f466dbe518c0000000000000000000000000000000000f51e01a044b6da437e3850349476437e4ff8b94fa190387099b17e6462040918cb2eba3b10d6044ff2123242005bd6f000000000000000000000000000000000991201229a856f88348381e1f2e282f0487e7daf1e5a4ac3854e66fa3d1303e3c20eb9eca605859e7d46dcfdd7615cc8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a00000000000000000000000000000000011f1bff5df413ade311b0bc3b46c4ecb11e386b886b71226987f14bc1a3a4b986412c2bfe8a4618ad5d70afacf4a3b4000000000000000000000000000000001972f49fa8b36d11d9c9d4ed6197261506b892ce6dfa932b87e686cb197560dfb8718aa413c38ee1bb771a5618c17224000000000000000000000000000000000e563bd240f5e18b518a792750c00aa5dfbea1f79b80a71369238ef15df9885d341d6901fb9168a2e74249f036e9a688000000000000000000000000000000000670e59ebf6e30b458ea505075840ed5348563efd536c31003d8d0bafdacfec7ba1ed401c616a3bab431a0fa71bb6188ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a7300000000000000000000000000000000071319574a93739586eda876ffd3be5d982e6fa04f5667873dfabfab83ddf603513394e0dbb9f418e725b02d2dc7b876000000000000000000000000000000000c6a8e0261da2ab499bf9a639a6e261e8c479f3f2b2d12992b41a3267e034c25373d4da4645626e6343e867466bf3626000000000000000000000000000000000045a0312dd5fccdd19edb65e24d5ba50e44689a9748ed9ec208320bd9eddf8d606b9340cd34ebf983e69a65c242fed900000000000000000000000000000000090b3dbebc7dd49e9f764e99c43b5915b67bdebd00d22c80e36e08873e5c5186bcd082dbce94f4f230b237d60cab7107b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f60000000000000000000000000000000007b04063dc315025b8545cef11be6b601fb4ae02597d75979b4946f3872764ffdbfd309f5ab3b36fe47b810f8320c1b40000000000000000000000000000000009361927d02192433a8d3c3d7871d76c6d88361774913067d16b68625aaa60f5a4ca19b6fd4140a5a11f92dec57d783e0000000000000000000000000000000012501f19b73fc6ddb4d194895e5cc2b89ca84defb7ae94f3170f25417965102fc195f38dfb7a2d88aa4b24e4a2fcaa4300000000000000000000000000000000141d0a0be60c32247f6cb0e0114251ac68c90fd43651d58c3108c728601ad6efc27c27a331a2f086d55aed54b3585fd1b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e47200000000000000000000000000000000043f74a82ebfbbcf4abf3fd02eaa4483108a3446c9cf041bc67f5078d1774308ddcb3f918d7999d1e2c0876177cab6790000000000000000000000000000000000da7d4fa72dabb314ad8f68b61fcfa38627d1d7719bc07767f596671c58cca16e005d36e42413d03da3c643eb46b1eb0000000000000000000000000000000019f3f8f1a4008f9db1b604373d3566ae7c14a9147f80597a31839b83f0f8dcdfd829f7fa933fef3499b671867c3121fc0000000000000000000000000000000018bba4bfcf7629fcfa47935e36462cef4fa3751c7affa2ee2cb2fe3e3532d46ca1d247393ea190fb3f48077270d6a8b22810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b89363890000000000000000000000000000000005a1e0e3a023f67aa7ab0109814f130a05c8c739036b98c70c8a8ddc1828d2cc4e2fcd16de4ef038a7373d15c78e81f10000000000000000000000000000000019e2bb467409b3dfae0b06244b4140de7f75cb105ab897d1ffb999c6b53bf3b60a3d11354815621c5d9f07962a237ffe0000000000000000000000000000000012e745499d5ed626b4762b57923bbfae7f1209408e7ecb8813a545c4ece0ec7c48a4015e0e264b47fa08fa82c39d3a110000000000000000000000000000000008acfd3c2a2e17be41a70ebbd1ca2cff2eda8a359e0969a389ab0a6fa51db5601b386dd035b26232be08d704a02033a7175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b646000000000000000000000000000000000fb464af51161f9c2758acc09d16754d4d8ac52a37baf2fb6ccd3bca3058bd3cd204de6c8a0bfcce8822f16ecfcd0601000000000000000000000000000000001819075eaa6d9e3f0568ecc2e507370f938a65169cea1ecc40c9cb4d02c83d7964254602e3d041ba0f93c24369fdf3940000000000000000000000000000000016c179832739a8129d2ef184f4d1231d24bc8d4093670a63d73771983152ec322b6a8c954565d61c2af76c4f6ef5e8a2000000000000000000000000000000000f6623578a4fa45614f4b74768adf65a753a35dacc84af005fa4d7328d733a09f12f709a7bb7f89060f60d4fac85780ae7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000189724a2a0723e7727d224ced126e4288f4743f6855b035722f2aa36cf2f0a6fc23f6835c25222b670c15248884451b0000000000000000000000000000000009a57d85140f31ca58e38b4a99c4ef103f0a4af0d5546d416134fa8adce6ecca6588c3c56ba06b2f59015acc1a081099000000000000000000000000000000000dfc67b7644851c3e928ea33aaa0f745a18983edb7488b148736e81ec0c62345c11e3f0dfce729d893dce27ea249860e000000000000000000000000000000001712009a81e06a85a225a46fac056b139c8da05e6b72074ee4079316e490a06f51c62241e380909b86239d867d631be16aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea5000000000000000000000000000000000976746ae4d9325d5e8300b57ce99650f28055b5e020700ee5f124fa76ef3bdb9923101c3a1f46b6985b8203b4e8c60600000000000000000000000000000000057310c3b6cff6c849938f533b401b0cbe10b6ff3736c79a968009b2c0b90708b6b9a98b8e594cce09c579a64ead846d000000000000000000000000000000000d39511e47f33e310332178b8a0210e76e4d4c7408ff5c2374f5e7bde8335525e03897cb3e2bdfe59bb76b21cc6411df0000000000000000000000000000000010c46a621b7fb2e7ceab8943b3371475d3d6f132fb658b8c6bf299888711f1b344ebd4a5793ffe6a7a7eec8c66c80303985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000011ffff38891ee56cb1fc062d02f6c9993100f991a556445b5ee1b1b0d56d8e64bc6eea4d7f69a6b6dc55ce7d8b4ba300000000000000000000000000000000000d6cdd95d1ab2a11ab424d7aa596cc7e5de025c57217da0da143887d7dccd6fda0addae7c2fd9e0996bdd0d23128e807000000000000000000000000000000000499b3e69214fdb4db7dbecd619ef9c6b5c8343c808e4953f593cc89adba02b5cbc56a5e7a3046c6023c5cf305e54e85000000000000000000000000000000000d267e21606c16479065e47da8e3c058cb59f55a1316a87117a73dbb067ec26f406eba6a40b30ecb00f506bfd3c32f4da3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010",
+    "Expected": "000000000000000000000000000000000650fe9f3cb3620e0bf1654a7f1dee503b79fe2218739bad608dba9f1e5330f325b4fb7c340f118eb10dd0776fbfe63c000000000000000000000000000000000bcbf1c6a684dea5ad6c1a540b3525cbc64c7c431f37213bc8b08c8d8915a331c07bc899d3a2ea72a9a4bb2c539cf56b0000000000000000000000000000000008fca1c364333f558c7284afa1be486e84bb035b049a2108b0df99395149de83549de153a784e4df2b0134317c85292b0000000000000000000000000000000002784cc1d11667bbd0759bca35a16a1baf49a21765c6c2c3bcdd4fc9697ef20f1274be5caa0f820d37e843bc38c68957",
+    "Name": "matter_g2_multiexp_61",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000051646993c3aba532988d7baa07eaabeb8366853436b8b19c0fe3e14ed45fdc65448d749adf745291ab5ee62d4e824880000000000000000000000000000000002cec01290d8e51ccf751183dcad20bac20b8231804a2b6f87f886aacb61d31b14f2335629e97af0ae0546a17a4cca49000000000000000000000000000000000762afa7b94ed580fd07d5141a8e1299c6ec439bbfc6c1a4d695d9aba4ab5d6dec93dc4de47096d72e5ad87d879eea190000000000000000000000000000000014769208ce8a9682c8e0340f68a0290a7782c2b04e3c13027f0b23966eada2ffb2156f6e20539738535fa0ef097f78d6709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000acc4e3ccc3574285c19d2545839d1da9db6770b078aa399262b7c91a7c41fb4c83fe7dd0aad19f4e3eb2b56273f664f0000000000000000000000000000000017851c99881677b89956fcdf1b8c5ca5dd0997d810f3fb89f7378dbf7964926cfde315f8722531d6d715b4932179eeb40000000000000000000000000000000005e374a4c7118a76e59cdaadebb1c4e635b4dd18665010249f3bc78d559455d27d547856573e264c98ba39f6f3abea69000000000000000000000000000000000a532979cfd5263c774f629027f7624799dd0f9d6a77f675d790a85fccccad6e93c00ff2e5536b8e9a92443af14611e69ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e00000000000000000000000000000000056f109801b7a4a36fcadbee7219c06ac74e4a3f7b81616076c33ba2a71d7ca0776b596fb25d29992fa26d416272a4b4000000000000000000000000000000000c02d7e6ec50b778a7ff36fbe5751ba32beb1c2024b17bd99b46239e6dd5a708d2fc689e8e8924902e0d80287cdbd6e90000000000000000000000000000000016f18df97f48aba4d1b64e71eb894904d02ee7f6ba425e58f38a08542319e2498cb0dada8dbbb81bb398c9c924ae44270000000000000000000000000000000017dce98b335f536909ce01647aaabb918942ba2468d9a07c5516cfd347e1baa02029d39de1b2602932630e4819f2f00f391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000005b23d6f76b8bd4f334e91771383856794d1dc65b365fbc0c94f21fff049761d7379f0d512c42ce13f878e0661712d100000000000000000000000000000000009dcf70c16f524ff540f132b35074cec6ed7dcc1f319432a0dd09b3ded0778ec9ad0f05d67ecf3ebb7947951fc4b25d000000000000000000000000000000001075fb15240d532a9543dc59cb0098cbd03da77c3bf85a0ef8be1560958f8ab57d3777fab5836ba98d67c721a4a8cd460000000000000000000000000000000003511525fcf6fe224eb87b13999d2548b6b8bb8069fd354f298a025b04a33f48be72d8e82a99b9aa34ce5ccdc1f1a59c94f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000019039c69d52a66330d2d8572a1308bd88159f0383c041ee7605d0aa86f1d0fe3e884d0a2ad9c72405149b5fd204ec3db000000000000000000000000000000000942163eca08672af3827dbd876b9c1adeefcd5ae74a2768fb55f1e8b342aefbf76bc6546853a2b33e26fa866e60a4e9000000000000000000000000000000000c60c6bd103ba5bb5323b5107373cd8d706038bf5ec2b367a43bab72411523bea35985b974c756184c346626ab2622d30000000000000000000000000000000016c4a2fc8a9b3c54f65cd150c80a3bf70ae8dbacdcd37128514b4a881239023e427f0b0c8984ce219207c458bb380da970f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000003e3df9a8ce220be05f15904a3321a6805ab68bbd539479be56b2a870c3d61234e9cda8190bdc89f48e7f0dd9374e1d800000000000000000000000000000000040446db3ec43e3e67dce62efd741a4157e8ea2597a143f7d6273b66c7045daf31f72397b4b9d374328520893157c1f1000000000000000000000000000000000c3a7dde5b02df5f7c1e750a9ee5314a580cc6ed53d326a9157b507ebd6c2da314c37a7f1837f7fcff7e8754ab603b7b0000000000000000000000000000000005e617ca4eced853f8f2e9fdefef810c97eb27d5c8bd06c5b4ea50c03761c01e8adddfe27d2d72eed8cb25ea7514a4aabd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c00000000000000000000000000000000104ccaee210aa8196010a6478702a54cb7ba49c80a98ecbf5c0920408ff8b4a7568212bfbf3561b6a7790520bb73bd42000000000000000000000000000000000870ddd51dcc76c8a97ac4b4f23819df48dc8a8798df0450d7a45d273f830c908541dcaab7b066bcd668b289c846ea000000000000000000000000000000000012fdae32b020a346ad5edc3bab360fb5ba55004ef3dfe5f437e841b5dd7284ddb3880051956c8068e49a3fd165143ac50000000000000000000000000000000019081bf768dae314fbecec408d687df5b6ecb32ec24b41f9febd583c05693f80345e6b9d81322ddc72616c1cc39a86811a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001316b5ce5bcc168d76d2c862230ce604d02cd3d242c51c250bc6b6fe5c380c9e83fe7041049f2272481ab38f44648f4700000000000000000000000000000000079acfc2b9629da9c9f3394874e64aa00527de21e726f02db180f86cc0b9a97138c2c567832e287635721ca40469e00c000000000000000000000000000000000e11807dcd4ac69fdcea71e3e6a93dafc27afedf12c2998dbbb2e4f33e37ea736df73af791eae69bff84f3bb212bab47000000000000000000000000000000000e834a34fb63d9df68d683a26d79ecf8ff67066586e5f760d4468ad196c66d4ebf8605ebfbb7bde201f47b35cfde3a5d8ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd000000000000000000000000000000000990f115519d2125d47b925b613edc3303110e9040fa705211e0d772edb2e0f7f88ce521d1738a5f65c9d158e9d360c2000000000000000000000000000000000bb951a16decf9be8381d0c88726b53d90bb32cd8aeff962d48e43863e4eab1839bd80d7434c7eb808bbc0e32e92a4290000000000000000000000000000000013dbd5bdb7caaecc42ffd81f14be0ff3d8fa228ff121ed4f2f3ad5961fbce617d7cbc8133fd49e03caa62f7d1567541b00000000000000000000000000000000195fd9b85e19d0e3e1c93bab0380cad6f6f3bdbdcbf5c6ec32b7de7972421d0065cf0b265f6250c02eada67e95284bce26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000009ca977266277bdeb985750df47353a6b81c5f0c473eb3369d25a01df67610bebf66a6de5727a465131404025e90441a00000000000000000000000000000000054410a13287ecf4aa18f543916fcd65b15cd5d54617433217b0a2b91a79fea764b511b3b270de3e8985e8f6a2fd8c380000000000000000000000000000000009a9802a03a7c9fb63c1eb13972cd42ea2df614a0972b914c4015c2e8630af319d12fc8108b4c88db9508a9a77d9e57d00000000000000000000000000000000094d83483bca296b20b7bee124f538ae9c659a84541f5c9d9fd22e98251d2b48051ac55ebe07bcc9d2e9109f526d60a6c470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb380000000000000000000000000000000003f2d93ddb6d5983fd5521c1d1726addf662af0945aee54788855037f47a013d2fe595231792a05e1259c5e5a8c553a900000000000000000000000000000000004f4f4e7df5dee975fb440b5a217c27d9d1eb83a5ae280a2b147896f6bb864abe04459c17ef56d784d3c4a0b7ad3f3900000000000000000000000000000000069da36057aaa89cda458af4ee27fd9ec969c8f7612cbb153da0e010d67bfdddadb2941cfbdba8c43019a9f1aaf9c296000000000000000000000000000000001545b8325a80176ea148a3d9301debd7046f33a1b419b4ed01916a3d0a072037fd617d96e0bad32b208983ac3be7dda4e53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c9269000000000000000000000000000000001536a81b203df2640bbe7e695b5fde186021d21685f24c25966cf11dde554d49bcefca64f16697509a9ca86e58b75eff0000000000000000000000000000000014348a2bd4907cf081f2f7bc944a98d3fac671abde029995377df190f7f60319b8de1698b99be39c821328e32a449c760000000000000000000000000000000000e18d4da3823addb2a6cef8336c83f99f390e23d7129365d57035d4363aac7e9c4da9f8000f086f7d2206666f990dac000000000000000000000000000000000d6ba54e2af9afa57ff4536a35e9b61c8d8fb3d431b653a0c66a2a4b8f11d9b5c45389f894d64485233d4183895921f3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f62000000000000000000000000000000000d88d5719e07e2332c54ba41f330c7763d2b2b7c4140d19b8b0972fae6ef902415de5f2abcc2342fce24d3ed8ffe156300000000000000000000000000000000163aa2c768eca58194fb76822deffc37cefe04ceb70aba38a51f507be7cd64c0755abdc2e49e7db234cd5d68575c2d7a000000000000000000000000000000000e443d9953468b8cea4eca4f5968e214888e2b95bc20ece39483ac551d4e180c0b0a41c4668c8ddaf761a0ac03fbcad3000000000000000000000000000000000691930530ce86a1354d73cb21ee32d968e6d89b12e5a09a7991c7d27dec302348af7f49c3e0de91e1a1838aa11651e795f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000d7c6f9d4aa794f34596bb9af4d62363462d9804898ebd7c7db7544be1f46b4bde488ec59004adaa0cbe40aef525ce3f000000000000000000000000000000001094629b1428c4c284b7a64d0623e10ca0c4d395bccbfaad89d1a737a3887c10b714541f2681c33e674c3b99a36b7a450000000000000000000000000000000000d6812fad9c5ea365a64ebd3150238349d88b76d041ccaa7e637fdfa6c715d9d6dc3d3315cb95fd6919fe419d028783000000000000000000000000000000000eee5cb772ce02fe2a4883008f17570aebb902ad7c40b4024a5b24ff75b3aaa2b54ace6fb4601b1c62837a20204194dd740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b0000000000000000000000000000000001026d21e075fb8921dd849c98252a565d39ca9f5a62a825e7e3e77ab5be6620e76e45047e51350c48d9a4cf98a1222a9000000000000000000000000000000000f6459a8287bb2da77404a515dd7a35f46a4aa49ef72cd2cdefbc5e5242872df5f7b7aeae6848d59afa1dd142ae7caca0000000000000000000000000000000011e3545151d4e0b034b950cd2f1a3fc2d29e9d53250ade2482b7ea6075dacf7e8e777afa1e8e612b45028205235265970000000000000000000000000000000017a869d75144ece603c04d39cb56a487895cc882fec613f40f6a66601bdbbbb7748ec755553257d654d1558b1104a981f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000011602a23c9b5cc091a700114e5d3557bd4857c4fc44cb8628ef327ddeeb728927347438f123e2011f9cfda9b6dfc42e4000000000000000000000000000000000c4fad264ca95827e9cbb9783e36cb0b683fcc33038d47bc7ab6b65998770325588e5b910e811cf7d61fce13c3378d6700000000000000000000000000000000009b4711aa67e84434cabc289a78fae48ea86641a162d48b79bbcbfd56237705dd2d1e9ba3a18d737eec29eb8e940e58000000000000000000000000000000001160fc9e2a488ad9385140bb62ab48ee613c2284208cf2f92912e1b973ff81a5d3de338d9aa6881cbe437907890258fc8390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9",
+    "Expected": "000000000000000000000000000000000cd0d8c746ecc8d92fcf2232793282d7e0e17e0ec27ee851487eb7788f590db3487296061075f36c24f67cd4c4bbf36f0000000000000000000000000000000010c5e1d05070c27f19c228813051c6a830254542eb71469664c842695b219670dba8ddff858e2d147019847866f01084000000000000000000000000000000001799ca7d8f2637da761622b793a3ed3317d50b902a1cabefdfc776b0d0ef88b707b8a5c36786d5ede3d8a381de4e069d00000000000000000000000000000000129881a3b56e0014bf1dac4775f509f309c33406f2cf22df9a0ccd15c87ea48a868d4437303923127bf580b8d6ed0a8f",
+    "Name": "matter_g2_multiexp_62",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000d087c1b98f8c67c1bbc4389f21d9dab02faf46ee4223c609e7b9eb399132ae168bc12847c580f58edbb9255dca3b000000000000000000000000000000000065ded24bda39d2b830639fa511bce8dc770eb95e349d6874ce63b3355d23c1da3ee9771ad44e57c6c661b7453076fe7000000000000000000000000000000000fa3b2ef40a7c3d41f0c3a5f86afec252c6ce89bd1bf1f2192026e22fa256365360589c788753033658b1ba151797feb00000000000000000000000000000000105040ff4dc2bc435c2a82e1174e2ee0b94043d69074f01e8ed013da8c431f33c94a438a93b06774411780cdb72abbc8414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d00000000000000000000000000000000129c8c1db08ccd0dadd59b04df67a91fb6547d97ce23e59aa57cd3d38458e6baaa67285800809856e7e264d812e584390000000000000000000000000000000004a0be934248b4e142fc51745233b6d0ab2c46f53a8f9d4c84981e5eacff146ee6227de289c713e4ce24a4341572c9d70000000000000000000000000000000005916d14a8592af57a40418b10376e8e20f70929d2ba568c1fb70e343a1dfcf3e63c791cb639bec49c50aebd2f816fdf0000000000000000000000000000000018682c66a461a69b11d7c32f7aca07749e05a23fc46547bac121752aef64e9bb98a274d15a14faa93af8f284790acb9b99eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef9420000000000000000000000000000000001552053742eb89ae3d0b95be919c84e53919c898ada92d3eaf05605a19ac910091fc08a65e9764f3108877c837d478c00000000000000000000000000000000118e5d22f6df0e6bc7447177ce06659f94315478385372046b649fa6d39fefeeb492e6623e0160bc47233f4d3143e326000000000000000000000000000000000dd02c30cfdea5abd3550a9f28b546d82d5b3043f012de622d892062945847748ba820555fb811fb3382791ec43ce1f700000000000000000000000000000000050373898b396d9a641e2f2ed832c7619515fd9070852b891b4ce0b5bb5ea8b5e24248297d53e9db7cb946e76c4433fa49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee0000000000000000000000000000000006b465f4b9d60a3a14e119c54a7c35172bd648c86a7cf331e80ba849fc87b9dcd48410e3c9a07b634e83fc7dd71e5b9f000000000000000000000000000000000283ad9c77f549042f79c47b8a69e72164f0ee77aee50c20519d2b89029c63ea86dde2744cd21eb5d37e896c3abbdf56000000000000000000000000000000001668b08a87787928afe92d941240e503da07b646a34cf82ed09d4c2f4d479aa24358c8475eebd9bcfaa6bae17c430cfd00000000000000000000000000000000150e5b28bd901f7a2a9af44bfd6b78cc84900dc05e334de306f9a45f1e67708adddf4dcede8150a39670054f97a643436e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d00000000000000000000000000000000060d75764a92e30e80e7c1a6df1482585f4de901bbc36dd9d8978a76c12c739f85a9ba16741d0b19ed480fe2dc331e5b000000000000000000000000000000000024fd15c9e5b8872d2e9dae9ae96102bfb0e31d15e92a24316818862dd8ca7a6fef271d499fed5e0db6dfebc4c72e0200000000000000000000000000000000058cda551e1fcd701c6a3880b276a2f7536a26aa366a6425a1c42cf31eec678551f489a27f23ed5dbc76f19b0fbfae43000000000000000000000000000000001152e2cfdb584295563af8120c523a9f4c01cf72da64fcbe0a90a284d693a3089f299bc760166be062cf9f8efb6a951ad3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000116fc24e980b2e7ad6bf17bcd7c4f06e654bbf766ea0238a66d738bf3c2d41c8c63bd52f81553cca5fea91f5f9b74a2c0000000000000000000000000000000001078f19ecf785a5e0d3e764b7d6ea47b2d077b5eb222f4e6a9451f134ff0d77a0b9a3b53caf599705d131e3b17b6ca9000000000000000000000000000000000e44c07f00a1f198583a8ffca43da45d8e54e1f2a85bee7afff6c1c733b5d0b5712961c4b6d344869a8e4de3b34218e000000000000000000000000000000000083c78b3568cdf808b75d9ee2b03b98cd516bb16ca8cc35757f53f12119747bf6b5b0605bdffb2f079cbc69e99ee0bad6de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000087ded6945bd6fae7a0aebb1ea68d3cd34588035531a6cb00fcf1b83e06f7ec21cd3486580165c1364027b43e238e34d00000000000000000000000000000000005a2fe8a9871273bb60cc7ebef44a361300a1033f3f0230a731f5723fca124ec9d305cfde45802482a45942154398cd00000000000000000000000000000000121eb94a41f9e133adf082ef651272c178d780a1c31ba8797f60a208ad36b4c703c9b6c08be845f8844dd14d6406734d000000000000000000000000000000000e5e3da7c91ab4cca1c9286020aab9795e64e667d55a5a700241f9589aa3519639f168d040a0027ac057f334a9f740aba28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f00000000000000000000000000000000041f9117b426938acb40c905bbcba443c043bb55cf9b876edfa2ca051b6354124f0fa54d6a88ea172c3f5c10c6d921b3000000000000000000000000000000001828dc0b9533274db6afc802b2fadaacf57f28126094b6b9038ed5f6bbae0112c873fe5eed15bc49b970461abc2f5c3200000000000000000000000000000000107df6da02f106ae47718959aeba7b4fb4a8f0e2651560e2f2266a62566e13a5af86430b8800543f5eb6b1e96be79c69000000000000000000000000000000001628fd4a598813133de75cd7c96ff3711b6bc826806b96d07e5a89cd549592f0f51c84aa9ee0642cffae5630ca1ebae1a3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c000000000000000000000000000000000e8fff44163cd9c2a4e148eef3cbbee19ab8f648da1a8d438be27d2b0bcab393fb7d49e096d9a7abed3d8f82c11c4e03000000000000000000000000000000001274335d8bde3d14924f8d7ba18fea82bbc85427892f18fb741c8ecc5f2d6d7bee74c68058164c55db3cb8da8597bfe40000000000000000000000000000000010c7fc728c094e47569f0e75446c399d20a1239b511e34d8d6193dd32df607dfaa4377a1825b3892a9f74ff4efa0d9df00000000000000000000000000000000067d904122a6581b5d5a60acfe8156dcb6c10ed083840e506487b5dd9117927663e0ad883fb91b4914778ae082de0a7eb90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d000000000000000000000000000000000a909706e3ce45c86f2c30de5e820c8c9eefef207e530fd504511827f5e6422714d3f4224afa6bbba22ffca533d647390000000000000000000000000000000013ff61472ddc0d70207692648087c283763ede668ae380b0b9d6ae6593498b0adc9d4e4fcc73b5cce250e7563f7577de000000000000000000000000000000000a81db69eca785373c4dcbafd8635b23a9f41265e91152f309fb2945622937e65b5c17656abf8aff042a1fd1e5e50341000000000000000000000000000000000c66269c3ccd9e91766d1a640789bde6de752d08ffe3b2955df8dad3d2a0b6cea9013af235cbfbccee8271a7242e310614f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb51800000000000000000000000000000000181f3f4a16696980bd0eb9bd10ff1084ffe90bcb65f12f505b25f0a26dc1d4e16987d486b2c0b117fd6f2e356b83a5250000000000000000000000000000000010d7be6788da3ec56c87acee68ea8a03e7d467f816060207bb163dfcf8a4e7721651bf2bb23d5bc390d50fb1ee6625a900000000000000000000000000000000196c1ac817493f51d9ca891b55fa65ad5192df83cdb63eb1a634ad54e2d627f7feaa68780418f5354e6cc09cdf2f6c5800000000000000000000000000000000190f36690b8d36f2e295b9625f23afef9d9babe87c1ba0303f60c6d44ec952ba6bf8356469cff9d952f8e26bdb86ca06e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a1000000000000000000000000000000000f77df606f0611856c449c58393f4ee7a6225a5bee667382a48f59dfc747736a895d598f90ab26002dd0ed3a5a8f5a200000000000000000000000000000000012aa50d0ec440884fc6c2f7a0e8db8a5e79160f0c482209ae1a1aca2b9dfedfec6d6ea09252a373ea57905130220a4820000000000000000000000000000000004773f46165cdb19cae49cc42663316df39586c62be5b827535f138e1fca8dcf62ba42ab60ac6dcec85e8496f32b9eda0000000000000000000000000000000010c91923c2c7b3eb2cd9aaf0455c0eb035e38e5352d218b07ea23f50040ea58fd548b373c1bee9113d3d44fcb25f6ba08c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e0389300000000000000000000000000000000044b95fd5f0e049abfdc2adc699646afa5b0f64464779efacce85a5279477697090615933069992bf30036c6ac70dfe50000000000000000000000000000000002778e7dacc5566354c24ea1144613a5ce8a38eb56d53d230ca145ce83d5ed88596afe243df22cba10f423e64a7c103a0000000000000000000000000000000017e87cd2752d8674c373c557ab2b922e02620a070aacf6f5b3d3d07ca35d89ed2666da7246b800717c0e4763dc35f5f6000000000000000000000000000000000a3ed312e5f309eafaed486629d953970cb73f839bf30f506c2f393df4c283f299d6c643ae6c229430d919e8aeae8bd839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc3652000000000000000000000000000000001082a0edac6267151c8ef11fac7614b74cf58b39b72fb71e4d66467ed4fb3264b177c691e569230f2a13a64b4a48c6fc000000000000000000000000000000000073a8d5f96ee580741bee1f82cacb6139d962fec34c44c648c8fcd0322796429bbaef083a11b4c8fa376d4c00cd79c00000000000000000000000000000000008d41e51dc2822e0f14b992511de799fe4db3783a05ddc1026a53faa89af000075ba5aa830ceb7551e51f0fff144c1360000000000000000000000000000000006bc4bf0bdf350af417160d06e8aebf2dde02c9b50be39b0c4dcb3a045f9e04f1f041f6de10328e287df6121247dd4e9c395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000802e7b71127a15a279a629e89f194b51d19c4f329efd8ecf9fe69d340dd06068c8467da6ab39be25c194077d3ce2428000000000000000000000000000000000250172c787afe866b428748be8359d8e0bad161832abc108c850362c5839237483fb38678d77c94696260508907726a000000000000000000000000000000000d46223c1666f314f9a1e32a94f83d8150755d71252e19af91a3b460ab0ade2db2364d8c6217cb422095f0d9a1ed648a0000000000000000000000000000000002fc2849014717d1c07935efe601325e1842ed333897222f6de322dac8b50bf4d9859eed8880a34676af0d0e3277639053ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e10000000000000000000000000000000004151d78d65b0c9eb26822e20d90ace8fac209a1f08f62ce722ae3effd7fcc476f4c0179e71b09fc181db96fb2ea4eec0000000000000000000000000000000013d17ef429483be98411947ca0771ce671fc38e27bd0aa4abcfd5ddf1af9e138404d86f4c2ed74702f80a573638d92f500000000000000000000000000000000178f2a7eb43b9f88acfa892b5868d7f7c5787a399c1c566de39ecedbfe88357fd5256ec57e1ba12e9784382c14331756000000000000000000000000000000000253a391373974beef746c4397654a30a68992fe9163f9518ff0ed9b7be37b858ac60c95259ab894bb6acfd123333b7fbadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa",
+    "Expected": "000000000000000000000000000000000710bfc39e92b0b9d15ee9bdb4959daa3a78f66aeae29eaeb50a0aa0460f3ff703c86eec8903011b4b61a0dea725ab08000000000000000000000000000000000856fe7a074d37786237cc14ff1bc53c735ee8133b231dd3fc63dfa0dbd1979304bcc7b55cd1bb66fd7529e15d15db5800000000000000000000000000000000014757f1fbfd4fa7935ebfe65e150519d6eb4f4831890df4b236dda98804b79862fb6699b587c3e568fd6de1e582409900000000000000000000000000000000000f7b54e4961dab9e94b1c4b897177dfa74be9937694a38207ddc9d6290dae1d5e122cfe4c8c31d853db3783999a7f0",
+    "Name": "matter_g2_multiexp_63",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003bfd2535c6d8ffb44670bd02b5aa6f050f5cfae7266fc3225865bc3f34320820eaeaa952f80da51671f6d97b3df9d4f00000000000000000000000000000000026c1adc0ffc3fef9ccf018ff9a647ef5c69c5133fb4a6566cdcbd3180d9ee784f34d667edb1dd54ae292253b45576b4000000000000000000000000000000000ee90fb541becf96b4728f1859aee5ae74e30ba9193b90569b66b0e1d087eb81f30c21774298cb06e7dbee8f8aafb1930000000000000000000000000000000000a4361867bca952446f64c273735764e141eef43d156d6cbb6e68dbf3bc940e935f7bf3a77b57fca4bbc74bda2f26532c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000004bdef85b0da28e0531734016e5953256c75c3620937736cf65de5f05b8beff294677668047a3b74f0f135b846a95bd6000000000000000000000000000000000b754df2aef855b4a0eb6f6aa03115ee8f38a31fc852381deef2b59bf23e2c885ae166030ccadd5673bacc35482f81e9000000000000000000000000000000000f1d760ac6dfb65b39c999211d4e4c3623c3fb8ea59cdcf926249a07285a8e4da1890327fed20ff07f12359f6d9035980000000000000000000000000000000009f2698239c8b452748126ffd83abec768edffb83dfa3dc7943fd499c8980e2d9aad76dc38b336a4a63eccf5c4150ce0b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c000000000000000000000000000000000cd724c51fd56528dfa688df46f71bbfc9144ff98958b559fca8fd05eda01c38c28630ee19579012b9913a393264cd90000000000000000000000000000000000aa1e55f2b6d9385ec6a9cbafcdbad157f7ebc06b2e30e2380ac54e71db5259cb919e17042d6ba6e045f1358aef276ea0000000000000000000000000000000010181ce9ffe235b6b271d570b3c2d6e1be60c53b4a98ef5e8d7d00b463e5bbc9d8d96dda881e58746090983d6f8edd35000000000000000000000000000000000333deb8b14f499319ad675f482fecd80f9a69ba369425decd441cd2ff5c3c77f11075f61bb1d90d0be850ff657d6b7cccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000001581a5440fe892ee6eece5fc2227fe072dfbc440e0620a1e5fb505ff0b16d9e6033d83c83576b4b6ff87a807dc81b88400000000000000000000000000000000099b070a0d7497f33c1c478ac424d5564fa645d836a3d572d98782f08713d8e425b571433fee928475688db2b3a9a04c0000000000000000000000000000000011e1cbaa09a6361aff9e199e21bc52e98dfacc49ed83e732d4b4f2503b3bfdf85d029dead4412b6f3d7ea447e20d669b0000000000000000000000000000000005503e151d620e9a5a142e4f7940ed88375e7efc1109214141c191e9f38a32a40d3a92d6094584e763e0cf13cbb54bcc99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000007c780f119bbccfd658f3f1b69ce9c56b1f5269bded713b6827d97d32b2a6deadcc02c410138d984d977527f3609cc2c00000000000000000000000000000000095aebacfa33928a916ca7b0ceac699c71620781b35cb2f3b254bdbd1544b728a2ec1fb35416ed7a8a3a630bc07ff8720000000000000000000000000000000012194abf7e411f4961b6f8a1e2ad052c27624ded863d7a9132d9c7ecd3b4074ef0060cd86adb73056323f4227ba5fa9e0000000000000000000000000000000002fde2be9ac1e8265f258a09eec85a70112ef1eadc3a91429c9206555933e2b89aaf7493fb833e33e5d61be28a12a1c2338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d000000000000000000000000000000001586c65405e810e1d5b59304bb4555ca43c04a593671ec64d5ed2d2e626b1f8a89f48a4b21d38fb49909b8c614209a460000000000000000000000000000000014528cdf994e774b8fd54090cb45b68098c1ad9a351bc1f36a9393f3b4364f5beaf58fff6e5f8b21a85b67bc427c0e920000000000000000000000000000000000b48d8713aee51d80c79109fb8b4e0c6e32e25a7ca24dd3e7700f8f3195730375208b241b2c722af3c2295a1704cbb3000000000000000000000000000000001913cf6328429cf2966a48117dc74db0d45be7800f93cfbebf597fb48a8bdcae4fae2df7835f9536481f67261755da2a1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a0000000000000000000000000000000017e45a481449f167fd579accc896ac65aff6f1f7392df47d006b404de3cb7ebf6cb59d0913438f3a51e55a0ae3d446c9000000000000000000000000000000000cf4b7db343bea29af6e244a71880538b41b826bfd1d06a21512d00ce58f5d7500ab1ed77b446b1e3782df736bf3dbb6000000000000000000000000000000000525d08e134779ca7614784818876514e14b65e799b7832f61a63601fc491c8b9cb25430547f961cc1c22100170a2065000000000000000000000000000000000450cc2156c4716d0343f32aca82fd2d0712389b1aa984b31d51edc2aa0545c88ff52e470b15eb6b2c22e30f79864dc85202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000004147c105ee8b4db68482b9d7f6a716ea1474b6c62efc41b9444ed1ef9e92e2b7010a1c1ecc59038ac37b385074a6bce0000000000000000000000000000000018a600a85c5c38be835d2e91a35cce4b59e5f5ac3b735fc007bf5498062beca9befc9c8ead58f9f21f6e08266b149d800000000000000000000000000000000012a476fcb81ab66e3101de2364cb609b17e06eabdff5246bf736eb9d5c87fddd404e8867578262f07a05731b04069164000000000000000000000000000000000c54a888678c28766ad17a18507e4bf5dc57dd394eb6e9b69abaf15e645cf4779bf6ccf4314d2756584647cf27af089ba2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f9722000000000000000000000000000000000e809152c44cebdd8b40f0d22d57c3b31f29700e0cbc3e69f660bf7270e59093d84bf7ac358be7e45e799a75cf9c13df000000000000000000000000000000000c6c61f98bd4e3b7095fc7f1196baa98139087df00fae2a795e76544ca47e453f75929cab07c11cd3595de6ecbbbaff000000000000000000000000000000000171c70446c19fec3c152741925c8db28ab0d140720cb6a6c45e9bc66c012a421d12271889ea43fe1524944ff572fe6850000000000000000000000000000000006e4baa09b4660c69cace151e60320b771e56e7460b01442bfcf26823c17779034ac241b9365dbbfade770d2056eeecd892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf000000000000000000000000000000001917a23350e94963e3a7488ac1dafefe9ab11856d405eff39d655e31ba808f02954b63e822613d3c6e5f358be04be4a4000000000000000000000000000000001620211b06288c16aa02f4404192e9f57a048e900f0ec5db9b478475f13b142f924c6de720031b3fc12cf869b422af470000000000000000000000000000000011e8ded9ad57e46713e7ac0044ee4edec12689cdfb98838a74adf1a35244e3d9a4a34c81323b089c10422abf26b044e70000000000000000000000000000000006f85c7478cec590fe3355a8d6e9557c5be084c161e090c72f1281be4ee56f36aa1e3c9c844eb45d9e295c15c4cd903efdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000d555d9f23de97318dafb257cf444952bdd3e844e9ed5ce193c10b76f5179f0c6851f93af1553b128f34d3a7e75339f3000000000000000000000000000000000132704571a12a58f629dab48f1a3956392b40f801c2b3757c15f7be46ef1d9115d89920c460c0e2bb062b3cc1aaed7400000000000000000000000000000000152829eaef900fd2f19d6fdbb8f7eb3b02df35d218b494d075219b69016256e572eb7f555f6fbdbe17c59a666d190055000000000000000000000000000000000fe5c67c949b7c89a867301528f0ab24b04d31d6f18f575c475ab5a6098f7187eef20a9ed6e810684da9afd8de96ded6660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000128bf3cbb5208d84dff719ced229921a889c9a4d02f5a508187662f03852531fb8be1f4c2aa9ef01de7720c352dbd19d00000000000000000000000000000000158d89a44b8fcf9ca8c96a8e516e130ae8af19ed71c2b8487ae300c3cdb546e248728bc58fd9cfef21107e0dabf44fc20000000000000000000000000000000012b70b42c8af4551267a94a795fe18e8d054291225438adaa33fe2edafa87742fc3709abcc7bada5d26e3a14649cb47f0000000000000000000000000000000015a853160b7666ea7d64aacd931314497ac7068a4b8bfe3a7deed85df2bb8dba277716a9d1ee50c56b2970016ada509d1ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af840900000000000000000000000000000000087c7bf08e085e19f0cb301d2e36478357e835620b1cde6e132c237ff6fc63e6fc16a8753550d50fb93a0a1741302cf9000000000000000000000000000000000615299ccefe4da879e5f4b01d6b6ef8358bb59ed8a2b365ec72003c16486d3266243db81f48855d81b6a25440bb861a0000000000000000000000000000000001498fd20640f39dbc03a474f4514e5e283256ac19468077af1c9ddaa40759dcf93afe256de1e49be6469fa106394193000000000000000000000000000000000cba50fc4919a29be2f4e74c261487dbf855db1856e8d5d008cc3f4ee5eb3babfdfaff878adae49b96db99d424bc4dab2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000001369dd82ed013474581ca1ab2d2133341d7c1d52065060d72b8317e899e79e9077bcefe6c76c3c7f67e54f76dd3c246c000000000000000000000000000000000405aa84d3ceb02bf8eae989a9cd65afa15451443af6f3cf5e70f5cd7bb8d413c57ac3893a7e8b888ae93a92dcfa2b20000000000000000000000000000000000378d003988f3c6c16d3b12ef47a4a49e2d3d2c7c67e384bcd510939581770aed92e06291ed3b7c742769f0d1ef740c100000000000000000000000000000000048bfa6550711a17d52f48377821baae6f3de6ad99ccfeb8302466047dfddee8005240cdc65b3ab11ed85b11f128624957f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d0000000000000000000000000000000013370ab697da0ff0a0efa8ebc7589b465374c983c13daee7b5451e8b299933eb5a4d255ffe4aa46782ae0916fd3990230000000000000000000000000000000002ee77be6e0b6fd260ad660a96100bf3259329faf2ff9796102928e70cd52c2bda8d0d1da1d484d7b023d3d59725d12b0000000000000000000000000000000014482fee88e02e61b847c08e61d7ae6fca2d993bbb69bf1653138150d5d7fed09cd5cd4097cb4b6368ea8023383477cf0000000000000000000000000000000009d0380d0d6fa39c9e242b9a67336d86445551658bc29fbd594239a76d7741ba388450caa244fb186afc36d35c8740e93017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f000000000000000000000000000000001537d4a47247af8f60f77d309666056c412ce089f3f011457e894f74fa4ad5168baafd36ed3294f5f61cc9cd8f87554500000000000000000000000000000000119e43382a846c8945e58dc7723a0f24b24d9cd487d436a156156a6da97795cf3f4ce382d21435695949b5137a2bf1d3000000000000000000000000000000000be5fd015998bd6043f124048c82e4d848e1b8c87442d0021390cba41c294de17648a47dacc06268606ba73cc95ae6e70000000000000000000000000000000000e05a3dbbf3da8320c40d51ac44c6380d56ecb460b0e7094819aa6af4d7c70d1541d4bc1fc5afd453b165f3d48d09a708bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd",
+    "Expected": "000000000000000000000000000000000b00b5c14685ddd17ee99c74598e6bfae5bb1c103f8ebfaec3a620ba57312f3093f9ad5eac820d81096dfece90e72ef8000000000000000000000000000000000dd81552160d449cd787ac27c76685ea0dc993a9fcf8ab182f1ff5d8a484a47c14c1c1a785285b44336c7f6fc0732a0c0000000000000000000000000000000003008b6d97a12868554d294faa26e2ebe2920add650f841adfbf0ee89af72fc4da5dc23b45b7ff191a58c17971b50ae50000000000000000000000000000000013f438d927f35b04bee8fc55693d5c97229c8548ff9de39fae6e26c26f89623d3b0c810b9be8dcf0445910e8eac5c58b",
+    "Name": "matter_g2_multiexp_64",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e1f825b71cd9edcb231c178e160e37bea70108b369afb248edc7c6a59114c22e843fb5541e0f26c77a2b589ea88fb3d000000000000000000000000000000000d65d777e91920b17400955a4afecf82f67cd13f3e7c5d9c2076c4a4d8f7f26383d22d9977dfd0987f219a625c8a621200000000000000000000000000000000045716092850318c343f0dc5337df1a72f8c74dd729831d12103b46127c9180fb50cece34986a94fee6119e72d16a55e00000000000000000000000000000000083fac698ce800786719d1f6063c87d9f728da03cea2545b4ad8831f6c24bfff73e80f2c2fff1532f6d1fea60e7d438ccc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d00000000000000000000000000000000171d60b76698d4d3f14b4eacbdce9fa66b8c3cc7ecfb989439330fbf0d051d95f3007c389113346e614f5ec8cd170a2100000000000000000000000000000000151a96beb250bdeca3cdad1b07322040bf1cf2105dfa854bb24fa76c8abc25ef4fb924ff995da641244f9daccff2ff970000000000000000000000000000000007e5818778a8331cdcd1432b46abf1efcdf7e4aa8907fd42d5e7d14b57dbfc48125246b57587755ee1571be8b52d2c57000000000000000000000000000000000693eb562e22fa8ca4a655b76e43b50fe487ca1d65cc3867eaf793e50496f0b4658bd92199104c2ab92e4ac53c44db6f57b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000001059d23ec6e472937d80829256db506d2d2deb37d4b750a980568cd5b0db085358a4d610d59009b64db1a9225f9f6f5300000000000000000000000000000000053d9ffc47672f1058856aa08e51aebd469111dcd129ed542454d6401e7893323f8a9c63641f499cd8617c7389518f8e0000000000000000000000000000000002b9b30a5e37b18af4bb02ae8cdd56f6a87820716ea1522a174a0d99c3716295ad0ff2daf663697cb56bc6053c9dba610000000000000000000000000000000019d3230c0bdc228fa0cfd5e0d8bb88be959e70e59d931d9f9e3683d5e65d8ba0d121fcea329b23c5905b80dac34de33b03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000015a619addc75f425596f9a51c6cf2259087cf32afe9b1f07e346a2f4e1f8caa001dc10098d1287b89837f426d073982d000000000000000000000000000000001660598fcd3ab6a55423138ee72a4ca7b57277f6ce140f9f992dd9934bcda78513516df0d309a0e8ea151b2742dceddd0000000000000000000000000000000004cce7d84e0763fbbb54376833ddd7408afe3f741bc2b7e42fef3789a005134cc5540981a15a9f256e0e541ac58ff3b10000000000000000000000000000000019c20a0064f89d37548e06d63d8ff4fbf3584d5bcc2fc2757339b7c89db6d5da76d43b31da7364259187ed602e79bf4f976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f000000000000000000000000000000000d7ec5a27ac44daeebab7658011624c441e45924cce97d5bda354f1daf9362f5bce2ddf57151fa07f78740a7db170e8300000000000000000000000000000000121ee325f4252ae5cdd3e3495f36492d68d9dbe13249039d1185760e6e48a789744b2a9946a3d6478a64b378f76b0de300000000000000000000000000000000014c6c5b98c1e214f78b82f1b3be4c32c5013934b1231fec942b5591d3f0440bf63b1505cfbb7a8fa78a85ba58fd4aa90000000000000000000000000000000016aaea3bd0ae91b9d18ff89a40ae27b68d74f3a227383138ed737d59c19ac578da03df83f04c8d962cb9d6f84a15302f3aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c8300000000000000000000000000000000188e5aed425a768f89f5ce09b2cc909b28c6a0165787c8e3750fca8e8162128ecf62ef0ff853d206d23bc076335008e70000000000000000000000000000000001cfd330da0d1b5b92b6533cf5a8b6b70bd93daec4373f28d669f5e970a947fd813ab1d1272b61afbd2748922b87c8c300000000000000000000000000000000002aec750fd085c99c3b9c3af62b6deddd85e49eba0293e6e8160b26a3945af546a760b8f8f85120d6a51d22313cd33800000000000000000000000000000000162a109abce2edef753ca6351aaa9cecdeac20919681c672dbb183b5b26649e885ff081b9d3687f802dbe20fda43462af9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000f7ad6a1dd9f8cf52bef02ae1e82b0d20dcacfaa5c169a485bf8becec8b51373fae851ca29e64385f0b7024eb0bcf9270000000000000000000000000000000010412a7a710f842fe836414e2729d0ff2e145709d8f7b5e3964af3e0ae267ac53dac3db1e6d2b7f7671ec34b18c844a10000000000000000000000000000000002d3b96fab0e3b8fe44e316fcc5e35f06dab83f2c531a777e162f7521cdd5767ad0b6f877f876f73d2ff663d9b71f462000000000000000000000000000000000c09a98bf623e82a4d2d4b63fb867fab5d3bb1f85a0669c4c11cebaeb357c0717a0f246a9ce4064b7351dcf1e77cdbd393be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000114270d35ebff55c0341776086d893513595aca3b200ab98c8b586029b19a360a04f2e77e90d382174296443ab8531d10000000000000000000000000000000008b88849c3cda9a23d37ec9f4700904edb24be95fbbe6d9e20ced0d52208b597d44bb9269830a1ac5cda35d0c0a03c9e000000000000000000000000000000001144466b13427c10ad7679567067dc47c671107064fbb9bad287924c9bdee653c395dc2654caa5b3013ade932fddd5e50000000000000000000000000000000008e14e3cff3bb57f0d87680a0c09d745c7272bd3c216ff9fde7c03df2caffc27e0bfd9f99912855c156a787200752c125d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd30000000000000000000000000000000015cda76d42de9fa86f900a5180ff016155f31b9276c617ef664202848d2efd2876d412402516c0c3d26d49f71d894acf000000000000000000000000000000001307fa2b963fc19583b7e4ef2e9dddbe93e2505e8f4f00ec52db26ab411002136c1f646b1cda71e19480c767906a6d03000000000000000000000000000000000ba87b08173c841a2bfbe424584d4685c39bdd0f83f278f9fbafa8111102aa3acfad5aabbe032c7123631fb8b454255b0000000000000000000000000000000016c525c1dc247fdf34344168b7cc245579585fdbdd6fd783cbe60b727cd11ee97b87a86647f78dda207c98e65c2ee7e6854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b24000000000000000000000000000000000403887fd4429f44f8da7f17ca072f867e88ac046922ebe3e1e6c4f9d8e174399e7648aca924a557dbf7b29c540db33f000000000000000000000000000000000522324700fb6b2c43eb5b39e0da94cb60e234369543f530ea47f4aa510ec0fd79cdf4dd3ae046e21d78b9c0e35107900000000000000000000000000000000015e946b90984257ffe3814dcc3ef065fed1504f0790f3564c8bfad4e97cffdb61c0d73bb0b1dbe78c4266c773abd56b500000000000000000000000000000000078f604630074ebedbd836c463f3879cd5d4a2c947da0e47740ec369112f4fedd787ae59bea69aab61b91f05d92061036f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000f722bfebd55f75f3bbd0a55492499c3a3f637ead0e54270042fcc88853df5bc5f11a3677efa26d31c28368e00c8713700000000000000000000000000000000182618bc8a4b3f6556d79848f90efd6883df90806a8358cb6852bde465a27a70644ac5d5040d4f64ec355763f1a384990000000000000000000000000000000015f717739a1cbb2eab30e7b1bd9b25f57ad56f36016b59128ea1f2089f2d1dd0128b455b1b0e9e3b320f68a38a1bdfac000000000000000000000000000000000b855788d6b6a7748aa923dea3163fe525a7b43f4619c1eff3f9219ec3d98ceaf34b97bfd19aa6f91f7fcff728728978191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e200000000000000000000000000000000120048ace47bc1ab3fdc07713b91a9223fe0fffdcbeaabc8a61351d756f936e18177f672c5a4db7b9dc29bad16bb7c4c00000000000000000000000000000000101275492a6e843306f2927b6ab540d7a5ee925bdab40103b4ddd885e444e6a6ec2d6e99c061284a1967797d8a2e9e700000000000000000000000000000000002c12f17a5dd2c56aed0d308367f37510f83c94a4482e5f632161dd0517dc2d4f46a90bbc13034c63dbd04fe4c616e320000000000000000000000000000000000e4b9089155ce2178f26b058f4bfef57b73aafb83b0b78138a01890a167709f79100a1e4d797c5849473eb3486cffa4b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000018180eee7e72b6a4bc2e60555236da335fe05fcbe2b3cca4937e73a550aeae6274122ba84ace78eff84d323b4196f58400000000000000000000000000000000147659347e0fac7a16c92950ea5fd115416072f339d7de3cc0f00ef369f5122ff050d8515effacc825c807f7e19650e10000000000000000000000000000000017bdbcae7f63052af9a7d8bd71dc98b6eca7ecf5eee7632959fe56ed51278099690c534ec33be4ace4612b0f516794aa000000000000000000000000000000000d6fa233be4d6d783bf973cca3740cbaf0f719827d7f9310f38d1dd9d1c1f125cdfca6d12fbf6a8e8104f79bf30b00647d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000082f3beaafa575e86be53b4fe7b93835b00759f921933402282e5bb0e643a812e0e4b676ad51ff2c6f5332d777641cc3000000000000000000000000000000001760b87bc4d2c13122fd7acc6d629c9f9db9bc9a2c49634aaf33e258ceb3106bc2755b227c6660a1df1d92c60067cf5a0000000000000000000000000000000016a819d7109c9a12199eb98537a730908a693767cdb35a69b4c7329761939afea766f0b91ae405e273227330761a53dc0000000000000000000000000000000009d14d7138440349e83f5ded46d18b886ef3cd63e0e5bfa0a8b50985142b21a4733813ec347e40cabe28e6ec1e068c24a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000001676d7f489219b56c198f8494e156fc0672ae28dab20021b7a6018436c7c0f107efd2493ddc2a1cfb3ad490ef146348300000000000000000000000000000000001106e89fc098ce7bd8bead5d7f6432bc54501370ae6544f34cfd996b3b610f9cfc7ad366751ae1211b848aad7d93d30000000000000000000000000000000011f8f0bd037365b5427e76d57b018c1c644034b28d06c8f68c59bff45eb4a2c4d761d066d96c13f7e73dfd80c81704a0000000000000000000000000000000000fc826b5957613f35bfa36d3ce088dfbbd06c8f2e88056a22a9f35db561e06fa0378ccff29ba8b81cc12c7a504f8c704c0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc0000000000000000000000000000000014f0f64acb0d9638a68278099abf5b5da3aa087792bef15192cfe3689b69b7ec1aefbfa14e659358b5410d98d2eedac50000000000000000000000000000000015ca79c92e98cf8314a2f6319520e1eb7d4656ca6e51278710cefd9c768a25691fc58e983aaf858d3c8d0ed73e2beec300000000000000000000000000000000007a5192f1dc906693568291f163e9632c53e1f418a87cd25656064adffbf31863680468f3ea451fbd22ac990dc870b3000000000000000000000000000000000131d2e3f6956da8941e8340259b8a15aee9fc6f23573f9a348ee9a51bbca1308dc54e7b4675357e3a9c5971be3a5c16af833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94",
+    "Expected": "00000000000000000000000000000000163da4bf7e159e05eec90318a8ddad4a59fb02d7ae2fe18795d38c3ccaf797188fa16577e6a421ccfb12ba1ed573c4e6000000000000000000000000000000001256654eef3352b09e0027277aec042519d99eb2567fce2cfa50a0c251c12d3593604739128171bfc13b3bfd1ce8f9e8000000000000000000000000000000000b8a46123bc863bed525f97166bcb77504eeeb66d2db207eb8342a3d18f7f5a99910fae3e6423c6e84e437a2c4b24363000000000000000000000000000000000b73cf08023c8572f48c132add67dda7a15def638a01b198361b9d21a4634ba76ceed9819b37c12e24f148d255483856",
+    "Name": "matter_g2_multiexp_65",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003113ba8b216d933fe0c81f23f75942c0065d21d8f009d73b1f698281408874e33dd2750fd4298367b81827cf6fdad34000000000000000000000000000000000b8a921e840fc665a786d826f83ca5a9c8f00e7c802bce5473a7d1ebf63e8bb6cf5c4b426153508d874064d1f1dade09000000000000000000000000000000001492ab584088d23d3b0d1283904f9a8f29f9efe47950c6e9ffb9db2123f3f9820b906d672fc7f97f0bd38b8fc0ef44520000000000000000000000000000000010d321c2538f92aad4631af44ae39e63dc06becd2460f0cee0e526328d167fd6cfbcf4edfaafe32d13b5fe66c009533bb16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000ae75d01481a51294003041afc4802326ab878a3a75eafcda43cf873cc65e300d28aa986fb82a2d1d649e5be00f956820000000000000000000000000000000017640eeef8982250f88a4d187dcabfcc9adc3ee9194dbc3c04c741690fce5bc7cb07cd0b7c3497191d9ed8558fd0d24c0000000000000000000000000000000007527fd8dacb81b8d1abc746688db6a47211fa71556155d38361921c4bdb2a9e9921a3a540bcf55c6dd751b84c04a1040000000000000000000000000000000008de9109ba354d7426a5313d66cd747a54df347f0f86a3c0f99e9e4b68fc79641fcf98ab39fa23ef6f1a781c48f53f76cf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000017331b8367f07756e789f7edce4d22f6886656fed78ddacef6987a2751dd3d5d49011a050e7b2a3e11fc8d90266c9d710000000000000000000000000000000016959c303e11f23392f95c1402d1d1ad7f38343c711e96f18d03f832f76e3e81de789a6eaff797ae51079b13571334d40000000000000000000000000000000004266fd13db1ca80196a91263c79d1583b717fb61fd9ce5113e4cd94c59e605152b244e10e364b468c5a561c6fa9715800000000000000000000000000000000026f67cb263be83f3163856f091e9346651c29d4634e242da53b22eb6e66018d235b0f30f8833310dff9f3020e5bd3811cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f42129000000000000000000000000000000000114d8babd11c81ca2b8a7e193afbe0a8fce426b83996bae6f77201870e51c9355c319dd86b985272f73e0804c0f53700000000000000000000000000000000016f5ea7610891d0e72975816c08e6e25a75c7c42500655f26efdfb384241bbc825358a21caff347d00c8b2391501d15400000000000000000000000000000000199c8c74a79ee90c3606906bbb8cc163c214259e4d0127cee3283bcd9c1ebe4090ca7d7b180201910d3f6f51566d3bdc00000000000000000000000000000000032c785165ad4c1a2846e15318bd7cf5b42ce8b675cb18fcc4232e28701f225f1ea384b276e7a38b2c9e2e8b112f1911d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf0000000000000000000000000000000000842c65006caed9b53add048a2eea89e1b4584e1deb4365e3dcf8b9ecb02f337bccbe5d6929ef8c20461847f171fd4d600000000000000000000000000000000100dc23e6c1c6f6756419a9bad3133bba052f408a424c5239b8528ad4429a2bce64b72f1463625f7599ce43865581e9600000000000000000000000000000000125b4d71333274a16e52829ad5eaaecdda5c206063473dedec5a8ff4424def70e6f650926948dd2158b403f985a3421b0000000000000000000000000000000006a031e3c002702837e4ad28250b85cd94d42cf7b0d765b980fab95edded7636d13bdef1be63e66682c4e297d0cb2b0302e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000003f7091a25da7d5afe6fa6b254604a1abe7a0c6ea11cc1a4167f5f648aa973d888383bc7e987b620d23e688868d318360000000000000000000000000000000016637f888efc3e057227cbefecb3037aebf8e330c3a794e51d691e3bc064237b98351beb746868aef977a83d1fe163ac00000000000000000000000000000000126d2884487984f851d1bd7d61bdb803321f263918e88e0677831563bacc9f5207358d1e9c76a5a25a66f0294f459e3900000000000000000000000000000000125c61b387a4462fa3bb2f06a4cfbd7df082d20cb23ee974aece2ec9a3b0c084d13a7ea83725a05d9f31b8033d2888ef9b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e0000000000000000000000000000000006cf2bc7c691c4f8a64d0aa1ca3760d715b3188a2dd299ab09c723315acba8b0b4bbee819ba06cc564f0c875a63a415b000000000000000000000000000000000bded3d695e471f30f9d723f55826eda112eb0e3fbfb9a377cfa07d6233ed84108b92a79bb491a2971e9afdf83db8e9a0000000000000000000000000000000009b0e9928cb267508d4f9444c6ac3dc6f64f49a70c82c0bcaf4022e97854e5d9ec2612a2cd4d67642dc0451583bcb24d00000000000000000000000000000000009347dcfebe93a2f7674ad02ac48794e7cbffb04dd85b0c8c192fc85cfb9cef40fd11def6f63ae9a923960424eac6a02f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa040000000000000000000000000000000013e8215c7bbdca445555c9fa0ae44e1905703334bade3294fc047ec262b9e4903880d52851967339eeadd666200b25ae0000000000000000000000000000000003b0bf4498103ac03601a8594b154b59a2a93d663f98ff8dbd2c85a1902e572a9456c629a12651aa87a1262102e1c770000000000000000000000000000000000e8bfd7d3fa0f773e6bcfd0d43a5c436862d1cb6a4ed715093c6782cd94699090c4bde597f65768e963fd0f8644e09b300000000000000000000000000000000064dab4d0d0c6b94c58b067337f2fac7d0d922cc822562b6bc941a794d96aac5ddb83d1d5844440d21d0a72a69303b8b2d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000098dd9a20f84fc26e78993a9de4d519aa2f8d343fbee501af945e5943e88425d29beb7ae54481b04175a07bf69b260a30000000000000000000000000000000007ef43e7a56e4e7d532420e152ce566d9055eadb4ef13d5698c49da905a4977fa8a7d3f51c8f5275582e1647984be61e0000000000000000000000000000000003755ee4432ea90f2197c7cc2e191dbbf7950c52a2c1b723f26d2aaf7a38c1b97efa29a312fed599f1199cf186400adc00000000000000000000000000000000150edc463f0a55fc70c2ffdd1f73a3abbdae459eb16adf79e96d18849ca638e6f41c6805b73755968be5cb110d81faa4efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d8000000000000000000000000000000000f7149602cbb3e5f2c5f8edfe59fc0fb8e1f03f89ea192bfe3990d87ccd28d4a80d7cd3003a8cfd669e1b6ff7e3cc5890000000000000000000000000000000006ffbc965bd06de07d8c0a9db8db5ab82d5f11afa1ad8eb92ed4453489f5899cc8c46ff02743956bed81229f64cf6efc00000000000000000000000000000000164cd3271ace4809eadeb1c0f769094272f3b66968690339bdb5da92e920cdc80c9d577ae4fa5b6426a5a6f46fba80bd00000000000000000000000000000000098f0a14a511ff424847d2b4d1b80a049b1f05ecd40af96b7a81def54486e4969011c122ca7dca3444029daeae2ecfc79848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000018409c0d0f37f4932cca87e24eb4d55e75dc98f938420ce036d43689fbdbbd839dc608b21d12a8af1d0a780aeac6617400000000000000000000000000000000109f2294669422a4946f926b1f106c2887893a042e3bf900559429c7fa484da4909216c8dcf826871534981021256741000000000000000000000000000000000a1ded19846e603b958d0bdcc9b554beca784b017d2a35ba117890fd0dbf729428bcd9823c7a378706220377c82a215c0000000000000000000000000000000000eafc89e30e4fc0544497e27674ec5b37ec0849fb382e608e09d0c1c94cb78bcb96ef4ea48e374aad1038881706fbcfcbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000e02cb3e099792ae7508321ce7afa323fd499de90c4006621ef5ce1054d0c934ae058a97ff8aeae0c88709c4d8ed0adf000000000000000000000000000000000e19318f5890320f17d5243adb4683a97e3e9763102c4fc93e3c3e3d24f4f61e0500be916c249dab00094b4ab048fe99000000000000000000000000000000000989faafcf6156472368b282313e076613cfe7ff135eb131b49e58932cbfafecf6585009d1f17ff8941d7f871be23e9e000000000000000000000000000000001167419d097ae8b96993b2e67da79b658adde1e12e43c71f27835845c7077f385612158d3e59fe2cb32b9418463e672679eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d63000000000000000000000000000000001579b7d03d3d2c8a280e8ca113bcc98afa6a2705a5d228d92807a85cd5a1ee97510f632293a478c3fe0bd383f4b69cdd00000000000000000000000000000000107cc2e6bd02251bfd565b4b848adaa84babe9d4f083e827ceae6bacd9c9c221f0dbbef53278175bf27ebfe5949fcf8c00000000000000000000000000000000018d187c566690e4edd8d8abe5e0a448e352f622c96680378051228b6d081a4914aa51383326aedf45e351612ad6c5d000000000000000000000000000000000197427117a52f82aa6e931ecb0c5ffeec7f73ee8f44c5816935d26c06cc8285200ff9240d98cc244708e00669460f98b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000019bd947df5a437a7f1ca2340bec628f2783cc1760dbc4a97ae10093aedd9f64e25ba79d9f4ce678f4fec91a3b1eef2d7000000000000000000000000000000000770e0c39988c9d8eca076464a3e10e274b06b1d2f6230e6dbd8dd59dd9c062f8958c6870c44ff196341bb9f65b8db38000000000000000000000000000000000a1833ef19e2b8e31577e5cd26e0a7fa46a5d25355d8b3dc0605f53714a60423556f3bcf17649745695f68f26570de0b000000000000000000000000000000000f449aed4120f3bef05506f2463f4546c7ea67b9e9110d3942dc256400d063dcc571305b1d4cd2bc3f18cf25319286e8eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000c1f9688ea64165f894e85b21761a9b2bfce891070103119ae71ff7acd164a57b0e054319631180c22f19eab8607f5b40000000000000000000000000000000005ba18dafcd3552af464acd469b133896e90c9ccd7e3bfc6e05db883f3c6aa1cc4610ec47f6354f6a7cff4385c56d2b3000000000000000000000000000000000fefbd9d78f48683b378d2d6311bf7ffaccaf7aa73a0bb4ce019a0c1d2e1673e52c724bf3a782729ec23d258043efff5000000000000000000000000000000000ea47ebbe3e858c5fcbf5b0cc9017d6ea23bda36e235d2aecbec827fdd2e4b042d1108d5f645b6dcdd786304e6bbf81b52b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000a313e1bf72d9a176bbad609631192c779e94c293463507edcd1c38bee8f33cfe6104d7169457ad5ffd9f045fce1cadf000000000000000000000000000000000af8db18938c51742b351fffddd74bf1137092ecb50a7e749391bacc9c1a19c7b9cf235b52ed577e7855d4ec1fadd940000000000000000000000000000000000febaa128de79274ef11d3e6378809d5b319796c653604723693c335eda175014b645604271429e3d449e756c85bcf6f0000000000000000000000000000000006adb29cc4ba053fea56d07225d2f7735651c0046f5cbe4a350dcc20431ed9457651d46a5d23d946959cadfc5500b7eae83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384",
+    "Expected": "0000000000000000000000000000000019a5b588aff8853adcfa959afc5135807d00196a75acb3536ad4fc9a9df3803d919a2de7cbe9ff762913225577ebdbf6000000000000000000000000000000000ac8bde939ba2f164795804d96dfa8d3a1c4d9e4eafb000cfccd956c24f4d594b30bbf961917f625c86270cbe164cc5b0000000000000000000000000000000002de09fdf52aec0b91bbe99fe2eb9043b19975c6fd503815264ce030dd5e5444f0f4275ac9a07a49de775335d52ea3c40000000000000000000000000000000012457bb55876c482e5b907c765b476dfe6ebfe8e588cb7f630e58f78942bfca57e6c0d5d7b0ce80e48960e297863d212",
+    "Name": "matter_g2_multiexp_66",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008f3f1f04fb80a23d348e3e25dac1d732265fd4a71ab8dad3718d268e49c79578e8e1ad1720e70357439e57df0791d64000000000000000000000000000000000fa4c15c76e395fa706a55d1909ede2163274a68b3e7afb8d2e0bb176f60c06f5a921c9ace35bd311bd79ae86340ba5000000000000000000000000000000000173633369e00c8c5528bd5ccf95c6af8b012e5a31941c134ad4541099c7c33c5ffd29a5a31e18be720f7ae85132cd6cf000000000000000000000000000000000800f5eaf7c8b1dd2787305ecc637a0bba8eac807a7b449410e48aed3dae2b4645b8459fcdd477fd92fa5ac6291b800ea4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b000000000000000000000000000000000ea8057b2d609ac2130b21e0b4a41f0aca20ee7751f55d816ea42cfa4612b67c3c556b01b0bb1c5912a74c50a420407f0000000000000000000000000000000007fbccf8ce8d1a92756fe80b15c7d9342af4e166d3c1c7e35ea2fac34851cfd983633270c877224749365720fbcea54a0000000000000000000000000000000000885e173b73118721d28fd26f3a9c562bfbb878ce71091d7ae4b37c1f2625777d67955a2b7458af71077db7557171f2000000000000000000000000000000001754edbfc3f2af94c92e6754d6bb096bbc4b39bb1128dc6bba8b4d4d9fac6649598be90b06b9d5db44c4e77c0cd1537cbd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000008a1a751b5f9a08e2bf5b2a58f62f0af6b8773f88e50f658ed220c0134e83c7031a288eb50a8a35016d2362b431d809d000000000000000000000000000000000d7f04d4a6c36cb3d105dc3915cd5d57f56692132681b3abca4b00e078c700931848e34ea1b7ec663f3886ff766fef41000000000000000000000000000000000a06c3ac81d6d0466e1ef21115150d04c8bd6dc3e4078e46eab213203c3226bb0c6500ae4fda591d6b8a791de598edb90000000000000000000000000000000014d849ddba2fa79b6a7107efeb46e9b6231d65384c69ed183acfb922d55b790d4fc7546afadc190b76f7da00103ef565efbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000014dfcb5fdb38cf09c1ecb284dd4f2de0c3d70f90d7c167a442d84e9a29bf43be62cd319b2dafdb6ead2c6596443a00090000000000000000000000000000000006220fc05c53f48e7e4104422b0660ab67fd88a695a201366de570f0ac0ad30421d5e37a1575e6b5ba35f45b441b297200000000000000000000000000000000077cb8ec1cb83c4974f6452ce0de630afc82e283eeb55d3b7e9969bb44bcf0404deae617393f82ac228b836c3cb6f95a000000000000000000000000000000000e2bdf539eb45a125112836008effd104e881aca397457004fbda4a40d152817801bd259434481f0509ab1838cdd1fd060d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000a09843630131cc6feeeee8aa8214408235655e4733badd6fe20c5cf1e45f6a61a5216e0cde937799437962706d3bfe2000000000000000000000000000000000ff518501614ed4a199ca9e9aad4e8efb8e9cffa9b4fa683093a49cef4669198a7893db998d5777f2cc8f4bb130c84360000000000000000000000000000000010ea66fb5224f4508ec100cdb611be133c4895a8de1b4c475b097494ff0f1ecdc1bf8fe467c630233cac2ddc07935fcf0000000000000000000000000000000009d22c0a45c82b0a19beb94eda0b93cbbe1f2e5f2d61279e1e1c93ba073cb766f5637195e6964a4814e588e44bb03f03386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee80000000000000000000000000000000015ca795fc7f0d169ba8abdafb1dee80b67e7dc616e824959f84c61284d6b2e0e8b9f99b414f5bd96d0e59b66ee706fd800000000000000000000000000000000042f473d1fa228961aad526efd003461935954abaae347dd6c9bc7fcd68b5f5138e57ab2a160cb19d1983089b58b51ab00000000000000000000000000000000188eb160cb968b4b048ce14bb72be27c228df1a6c014fa7dbec09a30aed8c71e8da59d3d5f8073b6a7d70d94c0e59dda000000000000000000000000000000000d467e6b05f033f3923667a82d5b489a5c90c99c5f68078aec700fc67a83d9bb4c09f3f00b9fc2cfd62bb098f885fe295ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d0000000000000000000000000000000003bec45d94f3073b2ca54d6332d36fdb8f5c801d9f70ccf6e3666b66ee06c0fdfd741f74cde1997aa205fb0318c9c4760000000000000000000000000000000014009b777b660264eedb35ec2e13ea586aa9438c47b3fbfd095ea3d8688a89c85bb4052bbd3edd450c19acea6372d0070000000000000000000000000000000017f26d3cfcb40fd6b4f3f1acb6d47a9b54c232aee484c7a8992a3d1accea794dc384fccefb0418d43e1fa7b399bdacaa00000000000000000000000000000000153c6cafbff3c53114c96d8caeee2880dc063d7db5edf5f14157117387f368c76b739553542bf6a9bc4ace3694de885a92837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c0000000000000000000000000000000005d2481438c03493efc9f1e8e9ae6ab05b7430f7fb82e108aada0e886b14d769969d54b17b31e5bbb63d40836748f541000000000000000000000000000000000971deac599b2161a4baf1178feb81fd4798ad5cb063b1a0cbee7cc33b8fcec6c3f43d1d46d9ed45555187db636af99e000000000000000000000000000000000222acaf8df647744859e04104a5fcd546949feff6244e192a9031fc838f368aa465a3799779c637ef0087183f30731d000000000000000000000000000000000b8e8f1889816f89401b070db687aae47f7264c9be192a8d6e485ee71a5a688070d57ad8928d09d9a4925f1050e2c69e127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000b8a5b0dd422469a8d6d7603e9f3179f443ef3fab0016afd94e93e2ea9e84b332da4b59f23a5257b99460efdf7d2aca7000000000000000000000000000000000c28e7068769c3a79bb8d92c3b89eca5d6eb42e3e18c2a7154f43a671f8670f878c4b110990c2e2b163ba4d1155319fe0000000000000000000000000000000001804302246fd07d86f4bb23f610af38deba8e324cdedbe5e61cf0941281cda8fb5dc211fbc0ce6fddf30aefa9563a0500000000000000000000000000000000015813fe0d6bbcfdc8e7e40b6141db21e1b490d846ffe82eeb3edcd9a024315193259612155b0179a4971e205738af74ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac614890000000000000000000000000000000011ac1ea4dad0f650fe0844ac3ab9434ebac6eb70a5f77c8f9c892cb4cb06639a15c63a9b820ef8f7a720040ae5b9e49500000000000000000000000000000000117da7999552e7886a25a939ada0944cdb15b5c468e9d1c3bf5b6af920e470bd648d24f3cb7f91e670f57a52cd65f7b3000000000000000000000000000000000a24147ef5f2b8ad888899c1db8df0a601eca9d76f1b934b1627e7eef3efe542f51205b96b5c00916688579ece81336900000000000000000000000000000000151863d964b12287ae4278c905341124985410f1ad6a72bd5c62230b7d8b0cddbea0c62cb2a7147afb5bfb03348be53363da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f0000000000000000000000000000000001d32ea5faa6303c530790146df7cd5cdee93c0933b4cbc1c2b8030bf0a8d2600dba1907df1756152625cfccf8cc7fa90000000000000000000000000000000017b05f549751d090f42ce8a3ac5d959cf988ecdc485f51734d52c40a3e22a097917345978209fa74a0a05be0a66e5c6d000000000000000000000000000000001481fab7750380626b174602d9fcbc97555c516f4410193d2849443cf25ec22840e4fd00b225f98d81b38619e8844ce90000000000000000000000000000000001d56434066551c5bfbaf8c9007874abe57a6f78de9355a297bc117f2bc5e6e3f44b248037f400f7caf83fece0c00ba0ef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e000000000000000000000000000000001667f1400973598ad3f56c2e49dcb5b556cc38ee3e5801ac4943f3c4554205d8fa69831e582a084aae1ef584feb0a1880000000000000000000000000000000003f0bb26ea548e498f05a5bbda8b8e536613f10e7165607ab77565b193f794664c8ab0a5ae2368d7483b77bc1173d14500000000000000000000000000000000176d8d294b4d975629c6a89bd6d45f9c3924a621259ab43d33a3d5aa1f423b68e3cef96dc103494bbb9036436c170f5600000000000000000000000000000000002f8ed87c584e69de59cdde02b6de9816c31a6efbebafb6ad9cecaf266f5bb9c8880f062dbc9235c91c668bae5051f4bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f00000000000000000000000000000000085fee95b859c52e44fcb2900a9aa590b1a5c2f981a388d6ad7b81ffbfe033f648c4a84e2119cb0484e178ebd3e220d100000000000000000000000000000000171e6ca074aa97981d2c2ab000a8bd12cbd5f5d574cb83158a6ed734e8f9b7aa4b74aaa43b7aae31b3f4fd3d82fd30ea00000000000000000000000000000000004fe6099a52fb491a0624a8d787d95617f6c64d16d20d1b3769f60d4721f7af66d7e3e905b3e08b2946ef7bff4806ed0000000000000000000000000000000004d3d1a56af91377ae6b00e192ad64fce6dd43a37592fa8706c9344b3d96b1f930e03be85a5ead3007f9016255d2df7570363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a00000000000000000000000000000000155830eff04ec2f4dfca4f73403e408a68830bc031555433fd38ab3ce1035b5f882bcd6032aba69ecc43625546b4a3a8000000000000000000000000000000000ed5b698b1ae23769cf5b6dc2e39f8500fd8a881eb43452d67c6b84ef9f0b3c7d81db1909b646e92412acc7365923a940000000000000000000000000000000009f28ec2f949cddee9bbe2fac12c2c029f4e472afa1ea56d0edfeacdeb9f43a4a43b79ccdfbe8957b4cc16bbcac1857d000000000000000000000000000000001474b435131301db9e232ddf54569ba99bc476200ceefc15e4aaaf1a574c1de8bd2d63c8280e23127a7a036acae223b1997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e8800000000000000000000000000000000168475854829d47356d9a8dc13a94e8d169771ea0070d9ef45e666d5378dd676d603c2eb57a3cda072c11e0926b02d650000000000000000000000000000000008b493a9f4c19831341782fe6285db2f7e8250d72952351ddcfcae6f22a2ec0935e29d396ba32f72dfa4067d0e7ce7cb000000000000000000000000000000000d9e72e22f2a1522babc5f2e8dc7857ee690f60f7843ffe15a080d56bf63db86f124cac039cbfa16fc8ace4d6268a1180000000000000000000000000000000008f3db1f6c0e5e7b3bb27abd34bd877cc3c373c681a3abc88eaa91636924ee477ba5032801dda091dbc51936a90c84685ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000004e34bff7e9e3ede02df950aa0e8c5f4c5f85cd3be89d211e957a7de95b8e321cc11400c3dd5b2ba0d1a3008462cebe7000000000000000000000000000000000fc1047097f01fd2079e6357ed379ba39107ec41ed6c6dc17fa6248d52be2b1cc2593c9735a6cb48e6d6e0434028f755000000000000000000000000000000001896fc5e990aeb416cf21ccc73f02c41d019d0a2679bd533d0811b7c16ad3ad3a6988170fb2db030b5fa7c3e4df5acf4000000000000000000000000000000000b70e14ce1b54d7913b9f3782b2b8ff249967a6b871dfac7f54f959954febb2783cf20e20d1710e5526ef8aeafecb3d603c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26",
+    "Expected": "0000000000000000000000000000000008056d4dfcb593c10a877cc8a4accbf58f360256b76876ed2b33a07be3110f8e295ef459dd6fb10d12bd02a8276351f50000000000000000000000000000000005686da1a0da89074c6b13fe9913f5cd49e0ecfea46e06493510625f1393ba4cc2e13f023fbc7ec2e130bf9a4f7483ef0000000000000000000000000000000010cd660001f65876db5b2cb1a56d85171d4cbf037f3bfb0e01bf4430c479237cde5b6cce5839a4fb22b406846e757868000000000000000000000000000000000809d7711211d37df76cd1cf71001cbf02c76df67c83e4eccea3e05b11d196b5d52ad7c3d0a00d9f0ef5b018717fc3eb",
+    "Name": "matter_g2_multiexp_67",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005d6cf50e3add0e5ac3016b394ec363d6145ed66ef56b07bcd33c90600e83b4277558695222062e02d1e2b0693858e73000000000000000000000000000000000de8caaa810d4ac39258e3d1656bf7f2fb7853a5963ecb989346abe90d5d35d3662f6e283cec7bc386a6a8638ac395ed000000000000000000000000000000001849ef86eec16b0612f214c5ed52c0d50a90bd65b623402879f2654fc578ab680d49af9afdeff546702304597a20f1fc00000000000000000000000000000000168707730c4e74eaa4e85e48e7239b9ba3e8cb74c24b7126a685da0fcc963b9f9180e252adf7d8c521deb1a2ce0099582849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd0000000000000000000000000000000007d4fed2fbd9e9dd19e0af5c52637b2cd337e0bfbcef0384f182a56189a7e7304b9d2144266ffa79044be90cb7ede1b6000000000000000000000000000000000baabe8c23a10cfe85494c693d1b09fc8e43ef5f233052d5b6294dae14b4ff9e5ec240a1c00a16a9ddc27cf7b53bcc7c0000000000000000000000000000000001c595f193229da9acff04ef67ca444b0cec75db5b2c1921502e37eebdd2bb43ef47290fc6f1980abc75ef4c50034df00000000000000000000000000000000010fe7f3110ed3a240366ad7ba31d56ab993468dae2dc1b667a46c7759baa37b865d02834e14280a2ccc062af5bb2b7d6e6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba69900000000000000000000000000000000150350d8a771c79268606d6a5e1c147dc9d92e63fdc60b20be688bd52eac697aa5d90fe1b7b91321b2af87c47ac0d5060000000000000000000000000000000000fba8f4da448b8f2bbd99014bee2f9c581f2a974bb0b54f41a84a7fb359e9dbf88ba59a705504140284d486241e94e80000000000000000000000000000000003bb92d6a603bd93f8e987071a7385de68d10cfbde389eaf01ba6480caf1ad8aea03c84d1889b7d5b5c5f72e62a2d75a00000000000000000000000000000000193342a9f15109367030724946342e564507b26971caf75190e0b209e429a948d8b21ca16041a01010b68222db66a16b8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b000000000000000000000000000000000bc4a46eea57231cc64758560e3032a8ad8f1907b3cebd7a8faeb98c4216cb8a0c8fee09929ecefc4bee7955f4e799ba0000000000000000000000000000000009f9486257ae3f94a2ca48eb203e2ef44ecf866ddec7824e1a4bb3b89b320c38b3c46de8202480628c53c415433383a3000000000000000000000000000000000d8e2b5d0825b11344d16dbb2cc614c6b84eb1cb43f70d70e272123867b731775b429aacde611318b2700aa567a84c7a0000000000000000000000000000000007f720929287a70873e9f2f2031b66693eaa6e604668219aa5aff3f50e720b34c5fa3f5c66eced5c3e86e8b34a81b984c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e4000000000000000000000000000000001537ed68e203e56f31498efa314322694ebd74cd1dcc3145d534299fbdadd4256f20b9f74b895931a60753bde6ff9030000000000000000000000000000000000935c6ae847aa7f47bf427988665e5e18a32aa869e196cf9d5bac1349c650219a8d20e01bd8d49bc7e4bb8d464aee84300000000000000000000000000000000013e0661d7254428861cc3ed47c3fc9daae8b86db35d1c64f8ced3bc18a89202825f13163ff94ac0ebf046a0a99727e200000000000000000000000000000000039a6b0b2cb91e460d50eaf9600c29fd4f82a81c283ba4fbd9a7d103efdaeb1e82947f5cc1a7a1112ae6344c51119201650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd500000000000000000000000000000000071a6b0267806f2b9e0ba493960fe0e43f135c739a54c8daf5ef9ee348a281f19876f80c0dcea59dfe9457b49809c12a0000000000000000000000000000000009ac83690c30a4afd78f94b2493674668da4efc84007d2a08fc78bba271ed1f43e2a9e5909149bf0811c44dbe07c52f9000000000000000000000000000000000f5d523612fdb2e7dcf5da56720057dff6b0b80707cf5924d146c0c072edc0635c73fb04256e06c7c9355cfe77a7af0700000000000000000000000000000000168431fc569869ebba9b4a72371e3df232545b5fb95778baf3d9105930d9a89b4cb7eba430e9162a5589c7465e54ca3ac067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ce836522b983fe3ef6a502a0de4c599fad8a36a60d914218d5d2cc4d56d69eed8d27b2d50899639d1a0ea9dc7597f900000000000000000000000000000000014110ac048ac4c20e53f2214df8c06d77f0b3150077d027691cacd3715d4630a387d5819ef58eb1bce2e8669be330a3100000000000000000000000000000000178e5cb42f56df2f1b255a028a00df96c02eab0a79aa0ff3e9772fbe3eb62174728259b3a15e356e6d9666eb65fd6b7e00000000000000000000000000000000045197f136649b61d6e0e7b9a56674e769e2d26716ee7a63fd2b83b767a9ae96694e9cf81375d0377a1b27ea6dffaebbb448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd0000000000000000000000000000000016a6a58301c243b0c59d6934bd926d6440b87b49f004f411ab0fdd924480175052f63f594c18007359055dc776e7f2d300000000000000000000000000000000176db4845cad46a13d9dd0f4077cd22b3458f64084c7325e9885f8ca341ce3ccd4f634f41efd6a70f16e1f0c9ae103a900000000000000000000000000000000068ba68f652c4f072a64d56618f93a1e148274b1b835433be878c06e11f65ff45b7cba0f67fbe80327abace68396da7e00000000000000000000000000000000047a699487964c98453207c98cc91c980c1ed37dc26e17748e6ee88e5f4c0ce424d87c82ca6db2264dc8aa9e437a5f25441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee35000000000000000000000000000000000bf7e4a9751d4e3baa7ce9906f4378764e5384136944f6d3f3074dce66ed017759783c64fc381f0dd7512d6f6e55b4aa00000000000000000000000000000000006ae2a4fda156818cb5ea6120edf7ea39370eeecc3f306890f47a6dcfaffccbb69fd21f33fe491b7065838b277ad2b2000000000000000000000000000000000d3ce00c2f5febfeb232dbbb74fb0405bab86474d1d9c545c93b65c7892bdd58aa56225641074ec9b428efd9063085d00000000000000000000000000000000002552a8c1848fbefd6b039d6c4bd47c34dc34ab307163c4f6d337946f1d1b41aff2f7e37f5fd94012f0ebd21f97d18a83020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c00000000000000000000000000000000105320cccd67b6ea78e96e66425a10a6911d2d348fac3231af583146273609fcd7fd27a19d4614fbdf05bcca0f92b927000000000000000000000000000000001204229ee1f66fb5a5dcc4ee978327e35d703ea310901be9c100af824e39d24a028ef8fce42370e5d734df02a26c145e000000000000000000000000000000000dd21f31f116681c1810bc36141cc18096cc113faee7db2c189abb7a746e398e272fa0cc61286aea0a5ec4008c8d03b60000000000000000000000000000000007911297718e98588844b9022c825bc4b37f2af30e1fc2d9cfb58b4500dffc8e9949afddd051e971fe78d4e1e7ad1b4a82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000760787190048e6ec8bc3bfc368f010e2f8aadd53164693a62b0d7207575bb2597bcec4bb382c57fe9053e90fe2f7159000000000000000000000000000000000ec525abbf13da64a8093c5d3fb800440f4c1fe798bcc71eb97bf2e0aa9e8be4b08afd2313f9143260058132d2607141000000000000000000000000000000000aa12c902084eb843daf7b351989bbab7a86acb62eb54eff0c7599bacaf44653c9fbf53f47f6ca72d22ea1671842eca800000000000000000000000000000000082f330d9a693f2bb9386fe5274aa79ac73a17688821f3c705120fb2aa76903627786a8614053f21a93e0aeb555de64e338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b0000000000000000000000000000000002ce7f08b8d5052d8bd07090744ca067700eaa1db61dad3e5086661850337bcab485c15fdd36c309a9e5169fd2a2b55e00000000000000000000000000000000073fa834cb4dc4ae120e738059749bfbd86b9e64fd71b1d372dcec8474f3341137ce8cb97a38955e9081f9bd5e07ab830000000000000000000000000000000001568df6806d8c3cfc9231802ebe5edc5d505198747a0adc24d0ac59f28d32b7b379d1f2c6b8352389057c7465692ded0000000000000000000000000000000004fb4b08a4fbfe197e924be3f7213a769a2bcd24109ae69a32a197b6212c5f50dbe8f46f5ab6044a4c779cd3e09d13bdd29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee75021000000000000000000000000000000000c07ff656904a47b0c7bf77540abc47cc6eee3e76b6ff0983151de9468ce3a860c427f3d5d489d096264159ab0567cd20000000000000000000000000000000008cce094ae1d9fff246a0e76cd67dbf9808c94554372fc4aed4879487ef240e45047dc201dd8bbccb613feb9c4623a0b0000000000000000000000000000000008a25297940a1bca1267fdce450b0cf43105eb4a21ab14562116039bc8379b1a3f58a7c117e9ba735bdec40f772465300000000000000000000000000000000000ae17a9b1fc3b0b7803ef48cb26643e8e78ef133f94bff5f87739182e662e2641e72383efab1f3ec58fa20fc816d56c675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb250000000000000000000000000000000002357e5a04b0dbd7f9a1709bce9b7afa12b10c7274b440b4dc3bf51a801d483804b1b4b9a096c3205a0e2aa7c0100c6e0000000000000000000000000000000002ff20af67f126c80293e44bb3c9ac74a94586a2de4146588c7ee8503530398eabc30f7e89322727739618087fa55de50000000000000000000000000000000013c6d06ce509fd557946479f2768f62474e6db04b2c92c5cfa86c023f79d05a387bd4c9aa618888476d4ecc93ba0995e00000000000000000000000000000000000fa477870c952f7506b879b17fb0a1c31771ee832ce0ab21a513fdd91b7a2a78a03d297c55558b834e255462b15520544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff700000000000000000000000000000000181336b8fdc03c02e23cd06ac975855caa2bbc1fe78a2fc7a9d0963c90a1f1f9330d50b88bf2526db6132d336ea5b8e6000000000000000000000000000000000f2d94d3fde2c0f67dae5a6ac12f713ccce2621303762e01961843eb9924d1d3c732b4c977d8cd0e5668adcd7dbf7dcc0000000000000000000000000000000005ac9ecab11c3368c75b0d396889dc34bd43ccf550d817c1dcdc7143c15d5c0e241add37328a7bd8556fde87d75d67fb000000000000000000000000000000000184704eeebead43f85b32d7f3efb9b9469f3ae10b73a2f034bd33e6e66da0bc36597d8e29ef5585443a655e24ffb68fbb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a00000000000000000000000000000000046751743f8f747e378738c265c1df3a368cd9570a2bd7636991045974c34039161fb0eddc6b813003e0908915b402170000000000000000000000000000000003341bea6cb81fc5e7baefd386a518d17a6f752c0e1ace5a9580a1b1649f5501c7b4639ba0cdbc33808d78b025a31f190000000000000000000000000000000016e3b9e8e189df73574a00a721440379589a7a6df09eca9a790e04c729400323b2110f63d547d83664c35227bd15b5760000000000000000000000000000000005ebd94e4640344e99e7e0f1619c6288665c985b90d99921ee61bbfce921265c4881a7e1034bcd840a665bae44467f5a94a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc",
+    "Expected": "000000000000000000000000000000000d993522760839abc960e99d62dca1021b52ddc8147929c4a064ec72570ffb3793205598cefab8490446453fb6da231600000000000000000000000000000000105db1e83fdff735d06d34574f962e70d84e2c1ceef4d8a8f14c2673633d7dbc7b97ba6dce9013f06fcfb134ffa2ef98000000000000000000000000000000000363be663cb0d36b8eb076df283b075ab9e568e460be804f197c51cf7ef611d8783ced304407d4c2540f1a4a04c18467000000000000000000000000000000000ab2c00473a2267682ecb356422aeafc893fab96a3bd27ae58d9b0786624c8fde446cf68bf8a003d9449702e345b1ace",
+    "Name": "matter_g2_multiexp_68",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000a575d896b06c5ebd7459a70b9321cd0de082dce7dc0ce7e39581751d01b7db810bca80f39f521df0bf70ef642bd66a000000000000000000000000000000000ab497a9590deef40f6fdc0d4db2ae7b6ad9ab59f112a5a0671b48581f1f2b6a71602c73784ca6c0effce66a0a9c6500000000000000000000000000000000000af3812439e44981c91633f73d1a92298ca1ed426c98cfbdb50643cee36affd5fd02886349aa608f4b8a27452a51a96500000000000000000000000000000000013126db8b642d33dd988b745b07084ef86a228767f7e8bd45aac830dbce4136ca5febca5fda9644d3292203e27439d9f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c277000000000000000000000000000000000cc12f75fe5e6d6f082f9977dcce64c7858f3b6378112e7e083caf0c4b33b5811d62a1130c595937983905fbde8db1fe000000000000000000000000000000000308b803bcaf4f63affaea0206aa9f4770c21b4d191890602bb4151b80fdb42af0cd9f8dd2b1a3adfe28d0e49712d2290000000000000000000000000000000019f83af5cbee858fcbc9bca0f499222849b9e80dde7ac79b7c46785a484fecf274e0d4326469eca647cb223068a183d8000000000000000000000000000000000d0a8334171571bc63054c032299824523bd2476b1150a67eb17b84bba01d8a65295624202c3874e0302159951734702dcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec088000000000000000000000000000000000141cc01094391887f46391bd49fdedbaaf524cfc94d741cc7c8cf081dd7c425d81ea3e407be48127550012e39d2b0580000000000000000000000000000000014db31972eb242d6c2912b418ddf416fd7911f13aede9194559b05d1c9e12056deaa1e56c155cdbc231b39f4f9aa91ea0000000000000000000000000000000007b361beb6c156b5c8b92b489e6d6c05e32a4376d20ac3e1a54c94e678c88480779bb789c3e1ff7a021aa6d872c98551000000000000000000000000000000000215d270f2d3c5c5b9fa99a873fdc337f4edad6889f7a55556d8ccb5ee86b592453b74a720ef6a907bc342710cfd9cf91d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c670000000000000000000000000000000017f5d31987655f8eaf046d6ea4025444924befa51c319b2bcb02dcdfde4d80a1c48049514e0b580e4bb59dd2fe40bb22000000000000000000000000000000000141ab771c08ad7c592725630aca0b2564de1ed8759eb3afb10a4bf451eb21d25e8d917f49bd5f7a06894baafdebbe790000000000000000000000000000000012dd82703c939cc5e7dd5bc3b924d744f0ef1a95fd0b9e57617e822e3fdda05b2e5a9959ec48cba0da40079da2253cc7000000000000000000000000000000000c53ff34d875fec4c7095af324d15921cd775873a3ba67740b2c123d6d482263b1cf93585dc810d19c68965cdbd9e102310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a16000000000000000000000000000000000a6514331035d42f58abf98b805f159921d8c4c935f88bb5493c580a6ce14a65e243424b41b3a9188e26a7f0c912a378000000000000000000000000000000001351e48b2d3f619887f4e83823dcd9dc15afb2800169ab78a2cd5ebdf25dcb6310f1051894bd2b549e509c55f5286f600000000000000000000000000000000007900972b84b6a76b2e686fa5757e98b8395bfc99da86eca122ce209afb39e8f3b07603cad92623774ed54d637e350d30000000000000000000000000000000002c68c42b3924b89a67764990478e48fc17aad4b5543bd38bcfee34fa1cae7535671f3b885852aecac53a30f28b0d4aa2fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000152d2c0e798d85e4dbf35dab808dd29d724e9b6c7ca7f53ffddfe1aef5976f2d3079eb1d3099e91b37d9fad7f1af5750000000000000000000000000000000015059423ee4e7201aa65e39116a2a49ba715b15e4b9547d18a0efd355de6f5a0159bc9047508bd3649407758d62887f0000000000000000000000000000000000e5a823fdc69f3928b22c542388f982f8131a978b08dde80d44e51d9eaed2ac4a1d5fa7392be6c7edfa33e833da4832c00000000000000000000000000000000044285f4e4ce526f96f9f512c5be754e0b0953744dcc04807ec6f041ba5c6fb9d5d395e93317064d50e61aae26810df0aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef0000000000000000000000000000000019c457e369dbfaa130ee79bd33ca70d00a3797b6cf62126baec0c5d7c3fdcf5ba7f41195276dc412b6862b71560aeb77000000000000000000000000000000001206f67dee6521ede85573bbd5784d675fea42da16010544857d4e2d81b720b6f85f646fa23540880b44a6cde9a39f5d00000000000000000000000000000000142018ecd7c7acd4f4ae288e1c6a66594f1c7f31bdb9bade2b4dc4c6455cdc685b716382c54d67373831a19100185e850000000000000000000000000000000013b0b57463a3e4cbe063c0d4f4e998cbeb132a41c2877106ee60e83d4ef7d339a5432d30a3c149a42dfb1da9d61f34030227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa80000000000000000000000000000000008a463003900194e45fc2610fb461fde538b17c4fd516919000d423f5a1b582342ab9ec20d8eb6fda8fffc6a898e46420000000000000000000000000000000010eef0f7bf73e35dd75fb924bd9759c09aded9cce46b05e5d3c5eb3e93e5d5032ecc459e2220aa529d2f773c4b8b8c180000000000000000000000000000000002a0247f82a25468ee74da555218cdbb6405871f7097c24e89db3f3eab59b91ce48ac06e8eab2c049346436c846226a3000000000000000000000000000000001895b58a50c025e46a2cd0c59d5437f6eee75fac949adb7ee12d455c96206a33ec9ac17d5088fb773618fec131981ab6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc000000000000000000000000000000000cb0f13b0680c2f7de522a59f4e46fe1d4af3a64cd3ab97a2523ad3c3dc42f5e6760e06cf48e4db22ee64c5ed8273dd90000000000000000000000000000000016517038ecd2799d787c5b6ee93079c93f78de4a96449bc82699ddd6eebcedaa1d02981ab47c529652cc21663f1a665100000000000000000000000000000000067ae1dc093d4aa2ddd8b7127dc60745ce9c462a066106b099a7a07525597c72e4920bf64c2ea8a3fef3de51c703de8b0000000000000000000000000000000016374f51023e2448eee7c64115d85794996fadf4f76fd4266c45093c266f35be09e861d07ff194f3d15e310385705f0e1a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec0000000000000000000000000000000017479396aeac06bd624a47e75b066d6daf5a37dbe515650cdf3e16be21e7d3a1f52a695c1c06382589eb7fc869c7d9250000000000000000000000000000000015c31ff36ed4eaec4d3927e62c111d062236e19fe6514236e6e3f7ff05ee96e3e4c084fcafcd21049a81faa1f84b7e7c000000000000000000000000000000000341b440e6c6273515fa7940d2f77018169bf6362b70a7b0cd6d66cd332ccc30e3ac48f7581edf47ebd137253a9c1369000000000000000000000000000000000cf424de046252efea9320b32b79bdab58e0e04f2916b4e8ef475da7b8ab85d8d5fc793a45ec6e6c035b6331a895d3efb206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000007c9111a85a6acb851e9cbdadf182096b720913ba3fb357dc2cbf2b8e796e9a8044b6df3ccadb740c73a16c3780c640b00000000000000000000000000000000059543a955c84a197d23cac22e15d82363c881026e41c57ee924da2a8c044f3021b29918d1db7926ddc2fc7a662ee7ab000000000000000000000000000000001355d8bcbea65a50c9b6ab59881e48e8e5f5592cee6aa69d5d01b033a84057cb6e74d911769bd2ab5f9722328aa204640000000000000000000000000000000011232571c95d0cbadf8e70454c851974efa4b326370249238db159a1224cc6d34eaad690e1840ad887a875b667ac1f193a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e00000000000000000000000000000000088b7cbecf91721e01e5e4a08ea3b261febb58cdae3056d9316c3840b3e5720a289739568bec7b899f4b1f4f5372013b00000000000000000000000000000000001f8835d4b0e3b957e46b718b6bcd81acdb50ab85f10bb70c6343a23970efbe72bef89dbcb24d66e6a6be3eb55665a200000000000000000000000000000000046500afd292a31bb5a4a9bd7b5bd0fe608bb1265351edea69162e61f1623cf58e34e8e1a8ec58ca166e8203c86f84c00000000000000000000000000000000005d6cc367ff9c88fc8b6c35383f147b4f9e3eb21268a5a7405794441d449b3e1b44c8f66e30783e5f6c3567adf0d80171231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa0000000000000000000000000000000015bab57412cc5c7ee0147b0d2511b7836a14a82df06b4eb2b1baab102840ed04cad81da6e920ee000751e0727091c1460000000000000000000000000000000002f725e61e82980e6164cae7a2e30a36dd7245402f4933697607640d53fab2d5db57698be33a0c9b5dda14aa846db7c90000000000000000000000000000000007fdc589448887f6986efd817c63954d350511401333cb0df89214317dec0a82b06259ae9263f260fc7f21f98ad2630f000000000000000000000000000000001324e3bb46a1c69fc550fa8f2ae2d0ea74bc2d7159bed03c13a9d232233449e271ad1c3922dac5d84aae52606f77dcc0393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000010adc26d73007e3b1cc58684fbdd7d197550658b4c66c702e9cd0f4e481f23a26c94c6798cdd9763110eefdca3d802050000000000000000000000000000000009138258ad1bdf6f9cdfb943fa32b42c4f1d834be536ed365d00126227c78b0df2776610fe5cf66a937cca3e0b088861000000000000000000000000000000001991db3a35bd2cd72377cd459502a84315422bed92890af906fefcc0acc4515fe7cacee1e4f360ba24efb23292482b8f000000000000000000000000000000000d10dfb682ae7a78b23b37b081efba32ff2011fcdae7b0f8a794a6ec33d71f5d6055f93e3b68a37086ab190d7d9bd7aed412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000182795b905320ee69281de833f37e040a3295e23be05ea7ae4563bd49d8b1fb02e95782c5c19645244633951cc29c5c900000000000000000000000000000000053368ee1412723b5c6465ee5ebddcfc00812e0e12e940f8485f44bce475c8897b324eaf7e66c0351ce9a6c92758c337000000000000000000000000000000000279f26c1e76e5f5d0fe1240c0956cd6025f6520ec303feb383b69525ebb6b2f199808a578a91368c3881a4044f37be50000000000000000000000000000000000ba4012c24dfe1038ec4b4565e1b321bbfc174cb197f0b0914bf1c126bdac9f423845f6742129670b7f3dfeaaa62df45b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f10000000000000000000000000000000016c27a3a950fc4857fc775441947f7ac02af9b3df6422874507b11f7b005c61d7d6a4a115d3759fcbd64633a8ad95611000000000000000000000000000000000e92954034df4f15450c32be31d4e146c4b0014a2b81e2afe755df79aa962afb05ca4d03577f15980fc6d8a34f2cc50200000000000000000000000000000000032db3e3c3617c16ceb1c8fae83e806744ca40cffb56bf9b79997cf48c55e5fea89db43b368cd922cd7ce30dd3984d82000000000000000000000000000000000d153fadc3854be49b2376ffcf4e5a46b9dfb4f54e580986767db13127e2d4d10e465f1ca932d79ca90f1971ddc0993dab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5",
+    "Expected": "000000000000000000000000000000000e1968e131e25d3f911284c369eb63aaf264ee82f4d9bd978b7d02470feab68ae82aed8509ffba875798a292f012c9180000000000000000000000000000000011488365570d9bff018ce6aa15e3d7e078368f09873ed5e0b827d1c25ef369d6571899c8df38a3df3135d0db814c87a700000000000000000000000000000000161973f4949bd72b9f010f017398778e0d7f0c8f77e12a87db1851af516b4540e3f4df314381f295c4d167fd9ac091a6000000000000000000000000000000000ae16f0a4a597159195aa47862585839485615095e538b745c1081ca73f202115a438d279dfa45bd3aef8d4043ec67c6",
+    "Name": "matter_g2_multiexp_69",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000046eea8e5af344dc8600ba7e506e923f6c356f7ecb3b78bb3805c4561e808c1f570e122c4fc5a1fbe433b48ce0c15d510000000000000000000000000000000006f1ab405a46c825e104bc963d2b2f573f0d345bd2b08a952d8793c0297dce267a754b802ded4db478399cfa88e7e255000000000000000000000000000000000a5fc4a09019ac9649c07b623d2cbcd9f0cbb89d28c01b170b62544d8da8ba3f236ca3172ac754175a3db85d9b846cfd000000000000000000000000000000000f7580110db2549742f69bbc2850e4ab35a6e415bcd1b06220b9b009c1f4c99152289eedbcba2aa653f38f6b8460386b3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e00000000000000000000000000000000026907ccf4d501265cfe67bc1c0b06840e9dd94a614c873d676b5416457d98a1dd744322887f1f1f86176b11a27d2830000000000000000000000000000000000cb08e541a5b32fdf51acb28ec64d3ea542c7bd75179fa3f74e9588156815bda9d027dcf5597d714aa001b2dd8a9553c00000000000000000000000000000000103ac1c03c16706d5936f216a6445577c96acd3a00a3d8a9c2c66e6ce568dd84a4c4db187a5fbde24e6ce60e037f53a90000000000000000000000000000000001da5cedccc02d0f8d1dd7e4d81c3ec47d432e81e941ea1452b112eaf40748a6634957c90f32fb0385dc5d642bf65acdb3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000a0b8dd68918b58ca6b113e938f8a00b2595351777aaf32dfbf703ef3884f02c798f1b5bb78cfac32f196c1fe88aecaa00000000000000000000000000000000121a4104e374566f8d582f75a3c9b70f09628f116b7ab22679ee13a1691b0b0bdb0d737833fb606c746fafee5859f1ee0000000000000000000000000000000000b8bc89d718572ebdd6e3100769f2571cabdd79ef5ca9a4b9bbaa432b1a4dd752f9af9d2a9b1f1f32d76d4ec2d1636500000000000000000000000000000000129f1d760a12eb1a75fec1d2ca438189c933e87095b9fbf9a0371d64eb205d8f0932fde9ee2ab9f36f8b6e5d4b5dd31021953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b825000000000000000000000000000000001499e5481ceeefcd2ff672df24e8987fb60872ed106c496178d71c68e9078409a80016e1f9727ed0d5922c93e821dcc80000000000000000000000000000000007bfb606c005c7da6b4ce2d974f9fdb2e3710c8f51f18257ced7663cc341ff81fe2e46308a2b62b13408965949a6f08800000000000000000000000000000000003fbd951e860e3a4724b667427fd9916ca4ba511a0dcac7b1125b14d8a4f4da82ddc0b0edab8ea50e911b0fcb5c200a000000000000000000000000000000000b43195a5f0263307e85408ae4eb046e06ddb1295a490ac4e0e654324de53d0dd023b8cc159d86b861dfcfdf7ebeee4a505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000000743bed2c17bea1ebddf750da504fe120f457cd3b1754c9413757cc48f7aef07eb4fa0572cb853cb72d68427e875456000000000000000000000000000000000102ddfe3dee27186a9484f74b3cb3aa366a79f0d2e36063af6e484f6a459e9168d7a4a6969bb720ec694a52db7ab34b40000000000000000000000000000000009bdf5b86aba4845adf9187ccf9c74b1fcabaa05764e41fcce4b38356b4a0ace8e7b16abfc7f7b96b785ad47fbf8e90f000000000000000000000000000000001934fa903b71d234c4341b2f49f8177334142e7c401553dad38e66a2c157fcdf7637165058955b7798a59051846dfb8cc4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f000000000000000000000000000000000ddde953f59b8591a83b0cdfce780ec23d052037c26d60cef36522d0f984f907315d7b41c8be9a9632f2b88e0ce950ce000000000000000000000000000000000b8d7bdd94a994901a434e6ea5d03ea45dcdb859e560833d8ea0bd9d20c7db9c16b2427eac27d8f1eb640b7d28a530fe0000000000000000000000000000000017b5b3a3097a74d9c1f1b23783723235b6148023b6b060234dd9e2f6fd05e38668167136c999d91249963e224f9bbcbd00000000000000000000000000000000133da0c217c31ca052800315aa8a3b934fc1f179e6247801904bcea1e28dec0b65632ab2690bcca3606bb1461aeb147b99762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb0000000000000000000000000000000012778a6fe79b1f2b768432df036543cade95504bb7735ff547969faaa8db84e3588046a074838c9a551a4fb48f4a66140000000000000000000000000000000013288a3413d7e7edebd118463d5eea9f9ae2e10f51965480f9b5c244b05775d04079a1dc75ba0885aaa9e2e4bae1ac750000000000000000000000000000000005b766ad112b8d69f1a28079688942ea146f8f31616611909f539a57c58ec5e857da9fce415d683c1c6dcb5e74da9d17000000000000000000000000000000000907e5c3c83d3f12a68d6bf812e310f5a04f1417094301fab7d4f41007b9d01fc1bfbf739dceddef756417367ed5b1d0298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000090be6ce5ed09e45a6fd9ea3a9223fe43a835141c1c29d6b386e085846869f9c5798b80c3bddec8bc15171906dd417dc0000000000000000000000000000000019bdf67eb16f2708ca55fd20af8deca66e2ae270b2f2f9736fcf49dbdf7cee034cc956f6fb799f0e87c12f283a11448e00000000000000000000000000000000124a69c723cbd366d52919a72dfceb7e4cd9ca5b5cef1784bfad3f125b11d810328ea1c849602536af500261aa684f5b000000000000000000000000000000000bbf05318ffd81495efa4f4c271c8b1c669041a6446501788f49b8739a934f09de9d976fe7300b0ae861be567d35c992177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e5730000000000000000000000000000000010833a3e7329ad40c1a8cef296b015f6ac6542c612038ce00f13a99f673783cb7eeb14796485c168d21cc169065d051c000000000000000000000000000000000d3b1416b23453b893c92a6c7850cdc0e4a395459140391b1dce11055da10fb68f318c5561e1c12d991a28f3f544a5230000000000000000000000000000000014721dc58eada80f2d0574fb4e2c1c94c45fbd90c2d2fd666fd618a96f4736a5ecf34cab34fcbdcb19b6cf7b44098922000000000000000000000000000000001905d34029bf84617a956d1edae090853dc1b622f560c5289251447ab6bcea5700bdd80d6ffb2dc12fdf3b0267e74543cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a99000000000000000000000000000000000b4d380f4f4eb976e6121b933be8418c536f85994491b0b93695d50473615e41547ead326bab795d4d59524a61d607cb00000000000000000000000000000000104b7f4058c9b355d38908d715c311a53169b42d2434de0876f1c4ffce1c39603c4876b33fe3076528be15fe42849d3e0000000000000000000000000000000017e2fd647e7739366ebb606e8a326daa5c03cd2b726cc4cec7747cb3468419f1907126d7cba98bbbc659478ce3afee7700000000000000000000000000000000183be0a976dbb3b5385b544c194e111729c7a8d5aa98eba3fe1c0a5b69b5fe6e5d0164e96398cbc61eba5b86d91b3c94e03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f50300000000000000000000000000000000023bc7eab817fcb9982cdac242cb6cc0ee1779bcefaecf144dbe57d5ae2b2ebfe9088f39f416a56de4b4dc04d4bbce7a000000000000000000000000000000001318e728c271746905788dd8f5ab22a3a10edce3fa063438e54ebadba22c29e461b2ed78a95a8f26a65b47022291b8df0000000000000000000000000000000010aab000b9c5de56623f18861b343ffa80da5ed4ae0d7767b7ed791bf3dd507fe7286447b6a07ea0fa12c19f2e4d8e8d000000000000000000000000000000000770e2909b5795a08d98dc66389655b1718e70b93c5bc6d805c3945cb5fc0092a5b390e6497b550988c28c58b6e016a3804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f1000000000000000000000000000000001259a4e36f5bce7d5f97184948d57fccd458cf7f2ae0c9e174f537bece01d744fef544447959cb73a678fe2c378ce3c900000000000000000000000000000000131aa575b2b94232e06879fa1f6f145a0bf5dd12456b698f731a72bc587e6def5054b3b2afb6dbbfc34fa5249dc673860000000000000000000000000000000011d64b923596c316b097a0752043efad8b61fbe068c58bec7a6766d9bc90ed965b3419dde3b96679426f72184adb8931000000000000000000000000000000001653af784cbad5a804e3f72716bb51e0c733014d587952c47395f953828566cbd7da811a3da1d48681998d569db00a7bed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db00000000000000000000000000000000077a9ab830f7683b7fb46676df09f72d773b65286c5f5ea86623306e5de51e63851c18d192c4c3b20af582bb7f017ff70000000000000000000000000000000016dc185f4158e249939541d35ae8230fd749988b9174c40c40b8c932aec625a7e94beaef9a07f492445d4675a01b7453000000000000000000000000000000000c107a895bfb45d33136db6251c76dc0461a235fa5d1ba7a5d216bfebe15691261b46c9816315c146becc328acb6b8c7000000000000000000000000000000001151cba240678efe61e3a36e169e314b3610e9d4df6650507f53ccf635d8f1277a80d86baa85a2d4c7e2af73934a7299e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd506400000000000000000000000000000000090a1ee6c611980e0421b72a122cb39257dc38d1e74ee41b809ad76e440fe307cf45e79afddd8d40b94382d48cdd4c450000000000000000000000000000000010f2e6e610eec7b7c2b95c1510af1af342ac19fd3b01dddf81b8961ead2cc57a8eca36c2f5747238eded5914e484c52e000000000000000000000000000000000acce0789cfff975b09d687ef79535c536f3b799157d3ff731915ea5b323ddd9f6f4750dc8e00a879d4e516bce8cb3e40000000000000000000000000000000008d8203dd13aee7363f6b10a9e1ae9b713bbc8b8fb2c56f05fa71e8d69ea571384d150e8fd01e855b1b0054fe7967a052f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000b332430c518d7dcd120b346440e5b6b48900b5c3656d84840823a96e5bf002816d583a989898cad9e09ba978ebc58a40000000000000000000000000000000004197b43877b833de7f69cc1a43ad8d6d3544cd10d42336d4b19a187f31337a37b10cbf48e72b77e4d8e1a1da68e5e4c0000000000000000000000000000000008887d5dd08f45034584f40a2a68254baf2104f9d6a4c2637ef79c5ff2503c246f7adc36559758a0c07533b66c3637d40000000000000000000000000000000009343819dec1d4569683de4596621c19785d5ed14ba13e57d94b1b1a108aa62cc8c55c58dfa18c06883ce50cc1364b95e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d7305370000000000000000000000000000000003fc7f9a0804e7f1664f8cd3ca67b70ba128529a611c24214fd09674072a6b8d652ccd37bf5d4611424688213a41cb3100000000000000000000000000000000137a869cd7bde696035bd9353662e0d37d2aa0731ae55357df3bc43536b9210f360324cbb3670362cf9ef607b1919bca00000000000000000000000000000000045d9d39c04e257fcd912c54e57c86d2d4304e6a7cb95a83d2bff07964d0a5dd8b4e42bdb91a8b245e512395e6749f1f00000000000000000000000000000000120e5e4b04b8a744757812fc331e7c98b35624faa1cbabfc1470e4c0804248bfb0c53a484107a677a7d3f0d2b533e7530cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5",
+    "Expected": "0000000000000000000000000000000002bed414afe9c7a630441e7b163280be10e502cf877e94b6521d14baca0087c5dcdfa39ff4a51c8376d99855e1e6f36a000000000000000000000000000000000dcd54727a7729408e682c6e213005687ed51fa7935c522312793fc58cdb273eec9c61cd8b056a26619fc8dc006b066800000000000000000000000000000000137286f4086763e6ccd5ee82d3bda712b26814a17c6a71006a3e6dbdd919e469bd0e744bcdb2074679e78a1e7d56ee7d0000000000000000000000000000000012d75de1310199c0e556d61d6c0395b406afba0f13bfb37486c05d66b446809e8b1a024e8fd2c55f1b82cf2aed99a5e1",
+    "Name": "matter_g2_multiexp_70",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008b83142b22f6d6496cad0dea23c71355e7c5d98659580b5ee6e97eaccb9fbe523f7e3b925abbca3a38f67426f3fb35f00000000000000000000000000000000035f655a1b2d22ea21cf0081e78d7140bad08c4e66dd45230a113ff3b7a77e39f0f1a72991f85e2b00ff58b27d5cb54900000000000000000000000000000000105d04e38243ef1ad2f734a3c97e91506c5a7c5d95e9b8771b7fded8908f1be933a81a5769044b633d501c0df7b5d7fd000000000000000000000000000000000e670ae4af94d0df34a7f2d7cfbfcefa6eebcf2a6b2dc5b82068b023fe02ce8a279e1bb96d905ad4f2ffbd8214e47d702063b046a71c2674e35466657a85d8e02253b42517b033619e31a536659172120000000000000000000000000000000009051f1e636309016c5433cc7eb019c7dbb75b3a4a5b27f6927de08fdd9577e8eb9e12919157ed35bfd6607be7fc4de5000000000000000000000000000000001953b7a33695ede6d0792eba85567aa5052b8a58c1bdc94ee82b5001893c6b996d3e8f7af8b8effd6cf50656d8b85554000000000000000000000000000000000a2f769f00679b610bbe212c2f8045e7579a96dc6bff80899eb7715aabb1afe79421ad5000f2c7b85d4e0904e335ddfa000000000000000000000000000000000ec962a3d00fac14d05774adc49bbabaf46ae78325083c0020587fb85eb234387aaf6506f503fa988df8e9ecafb4a59992fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000006823056a4da801cae430fb9e3a8663fc8f46bb6c180b743b7f9c7c7e3287f3feb1aad4be0e98409c74ff58004f8732e0000000000000000000000000000000015f7a3f692d55252fa5af5ec952f581b796d54089f13971fce2ef9062173664816dd9f37174294ed78681d8c8c5a9cd800000000000000000000000000000000154743c76f7de590a31cb96d46a0ec0fa88008b7d6684bd8f6fdaec70722afff7b6e88c1f0fb048714fb1072d30780e60000000000000000000000000000000006f3191946d0e7c1307a1a0d1ea9a26db195ec98ad88f9b8f08a03a3d48bbff1fa53ffc920f7db5ebd4c65911392bb834484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df8000000000000000000000000000000000355018079cd02dfcca15fbd2934a8e47c5ee89e679663488499ddd4abdaba7679fb1c9d2102317cf2798c47aff1ceec000000000000000000000000000000000c417d489a224fbba9999300eb65a23749194bf5302fdfaa33ff7daeb8d896e387e56600233038d5c5eb59f644a99b6a000000000000000000000000000000000f5a62e9d711293d4373bec1bc2637802938eb789c828939e6c42f10062ec171ac6110261165bd179206d649713f6fe3000000000000000000000000000000000b11f9fd0ef8dcac2e21ef09846ffe9f5a624ec246e31393b39082a47354fc9523dbd247f0059b6cc740d7a387b137f0fae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000fbb5521cdb9c3a69d58e5c9cd7e4a50bf5469bda2603f5119f3209669eb3e374d700f851b0c7ac5ee3cc9de79e6a7ec00000000000000000000000000000000131ccc37581e64f6f9fdf675b9b63ceb67d9d5844bf512166f39b5bb09d8e031437c06b0ca01caae7ad6d8c9bbb9fd67000000000000000000000000000000000531cb0557fa18ef054dbff2e7e994f1af08aaea7557602a26fd6ff539ab3c0a73f1fe841177012dabed4a1223ffb5a7000000000000000000000000000000000a180e7a345d2b635be92888934608e8b6c17384c48c560f4cb9809ff995f8e70d83cd4cf0e96c458fc414e1275d2a993168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a200000000000000000000000000000000125c83184f63dee35ffd2c0c7dad9010cd6a9735675099f24b465554ab3db727ee76b5b7ea603ead78795d33e37689a400000000000000000000000000000000141bdf7e270dcd356993327cdb5dabe38a5c5a9b53470d9a4aafc041c46fe8bc841089e337469bddab5d4f7fd3d6ccbd000000000000000000000000000000000f9613f6d05f38e3073f14d0c2557101a4864a7d6d0b5a2b931d0613f020adb99a1ab2037a39fea6e99fcfb47929827a00000000000000000000000000000000192d812e05a17d22c60b78c53fabcc55a0eef3656f8e84132faf16686ee18ab4d35767db9a384d42f392c40c7b0fe1c0f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda000000000000000000000000000000000b6e1960e82586de19ffcf29a8c5f16cf2fcf5286bf42febef832767919abddc655a0d1bfa240cac8fdfaed5a1e8f389000000000000000000000000000000000fc1598454caf04414f1930f711d762f0d72f5cdc7a4053c92b916c742b00dd0f107aee111976c1b1218c4577deeb006000000000000000000000000000000000455d6e9e9bb848e0868c9d725edca1f50b279d0acef8c597927eda72763e3702f46b216919ac36b080b4865249fd961000000000000000000000000000000000174463cc7804796b4a6d8ff28d2e8cfd8361b2e38f368de30166cf3c20c474ea0a1e8d94749fc3e6468924a7d1369e62d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d00000000000000000000000000000000144e4b615ddb871bae85484c308423adceb5de387d0c7ffffdd2211b4ea28788eba9bfae96ffc46781e6d6343e2f501b000000000000000000000000000000000046e39cf43fd707ddc4b7ce9a8a22a2aa1e55aa63cae1eb23082f7b4b5dce49f32d2ff887b5108b40f98062c02d5613000000000000000000000000000000000b75b5460db2baca86528569b47209b5ac24930e2545cc6aa08c401a87ef2c4e233de537e5a857e533d0ba0981b24d7c000000000000000000000000000000000018f53b83072fe7daab226c831a89da63a0930ea86e301c97e639d0ee1609e298e2789d1a347bdb4afcd355fffd16d053bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000185b296e9c7209a9abcc3194b46be9a545666527ec9b0634a3e3be579447cb52330174c19e40e1667124552392a7a0c00000000000000000000000000000000158a053c788e5b914fcdcf1aebb4e21cc8bbfbcc20c4d692256b2ae48149f6644e1578f98d58b3e73d9768d0e7df643b000000000000000000000000000000001318ff4150bebd8fa612f4e84f89151d5c56c272969bc1f31a3c1fcbd8ded0e298914e98e1ca48248e9023cd12db0fd300000000000000000000000000000000076555254f382707fdb7419772a4978808a7409f59d1dbb8c9e648372e19c44573f5ce1888a2b570a83afc20e698ee44f944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e431000000000000000000000000000000000f4395e3f2e301ee3e18df3c23cdd142716c7fcfc23caed924f0561795948b0bfbed948a6f7c415ca615ee0ba4d5145c00000000000000000000000000000000176ad308c7fe8c3a1aa350fa82b8f8ec638f77bc703afe1042a6da22e5385cd8473ad789247f205214c9980532b12c7100000000000000000000000000000000092b0ec86c511992c66f320ad46c9d6d7c82df118a9ab2ce1f2c5611ff4e5cdc9193a39c3fc95f18ddf96e139688b00f000000000000000000000000000000000b4f671e334b7f22bd8d89d8c4eb8a52b04bbd4dd1259cc9caa1872093736680618930f3a469b3af4a00cb6e44b573f27de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e100000000000000000000000000000000123b64561ebfe085238220eb1428b3a203acb01846d1e4428f3759db6cff4ed3c1b9d436706f28b77e3b92e2e39ecb41000000000000000000000000000000000ccdf1973693e4b43b6133563986f6c96e2b924895c813f8acdd0f39585e4ee95ef26c0d9d51d6ef88bb62305e51594d000000000000000000000000000000000f51693bd44b12188131ca84801bfee0ca853640c0a8d5b20123c97b369c98299ac04beeb27d75946cc6f45f8a07b5fd000000000000000000000000000000000804c6597810d2c75de94484873a67eae258fcc9577bafa778e13d4814ce099a5684b1cc94e0df5a59acc7b19328fb8bb0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000009b02eea05c78a24adfb0187defb6810116e21894d8782605c1d590f8bdc10723bf71a1e5e5004b181504ac2deb142cb0000000000000000000000000000000015882389195128e20e50ec4f8d278e8b8791e362341be93c475064d640e1f8bb1c92a6c777d666f8644d471409bb9aa90000000000000000000000000000000000d89295f845f989e0fbc6e86e97400b08e39b2968fe6c9a141d1e92ec9c838a3d8e1ada5e44bb08189a5d514ebfc2f5000000000000000000000000000000000dea05d8e6ab50b8f8dd9632337948a60568724d5a03c7914e4a03e2af572dd8153effef1a7d5c2cb27765ef2c17bc5b4aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f8899000000000000000000000000000000000bacd14447ede6af0e92e19b54c4f5b6ebfb94207efec3e9f385a4c84a7d670514ecbc28ab686b383e239ae7f9bd673d000000000000000000000000000000001698bc92d146049174b843dac8c5dadcee12d1d503b2d0e46ee68139dd43d3aa797fd5bd06e2b214cc9ae3647c98394a0000000000000000000000000000000018d20cf6c84446cadfa1a26192a04e16d2b2a053705a89abc51bfbfa35c2b03cd58021ad95a35364ae1e2da5d233208300000000000000000000000000000000113268e360006294fa0203ce58cbfd05d05fb625e1f9474c96c89c0ec1ea80fe834030592c2f1c182ef8a3d5c32caf71adde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca0000000000000000000000000000000001727b6bfa9c601fe84a65c54f556887c4538cb5383a288156fec87420ae7f15da395886e1ac0e10b8fbbae8bf040f4ba0000000000000000000000000000000012127cdf02ada71f28ed036a417971b87fe443b8c65b7739795dc7067082cbc9f06f7bf10c709969281cd072490c06fb00000000000000000000000000000000134f1fa1d277d01e2811c118cf10e2de6324e2ba14efcf717a03c1a10dca0862ebde0f6328839da63d7d85f573e8501f000000000000000000000000000000000d20a036b715d18ac9e2dbe009dd0063a4b13b3ec6fd060a64c4ad2b98e05e069060179530410d154caa575d504c63b7b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000000bd5375e7f98d3972b93420a39fd6c31da86d0d9349ac3774bbef15c2240437cc0761b2f1245e805d2538cbca6f778600000000000000000000000000000000100232139641c8cd5bdaa75b77e1e1c8e33b3f9554e2ae00ec6315b82cc00a6a70d576d744e68938a299ee2b451558250000000000000000000000000000000004224691faacb007bde3e37db6c7486aa5d3b4259a24c8b7653238e7522604ef4ffc1eb3cecf719a1b7f52ff00c34399000000000000000000000000000000001156ceaccfe0396374c6dec5adb39f14b6f08a32b88ef7499756f5cc324a9f1553bf5dc106a97469f2c49be5d563e1100cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e0000000000000000000000000000000000010a549108e77f0ddeacdc795517ccdcb357f909264457cab22fac2b982d10064756d66d0e48af02a59f58eeb1e8ba14b000000000000000000000000000000000c68703ef1c1e93c78faebc5f7ccc69e39046fe8af92e12469e9fd6baee62a2e8cc06fbbb3def81ae5cc57f488fd9c9100000000000000000000000000000000064ffb6aeeed432629242c3843f8cbea5bf7fe78585763926c5c45dc3cb4d1c79b3715506d7cda18c531ef890b22a1f7000000000000000000000000000000000e0eeb69f28a552cc6563f5fdc9919423c4358a2b70ccd56b048c22111454f67107513cda2a5aa0efd2af25dc74a1c47e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7",
+    "Expected": "000000000000000000000000000000000b1913c672760f98fc6d4e96ad1ef200f99add6f233b17291036e187ac6692ab0a29a4083dcf86a532dd06efb3d9b8c6000000000000000000000000000000000323b703abed59a9824f34d97851546a5e78441accea4e3a933b9270f92a9dd1aa056858ebd1739659018a0ca13b96e0000000000000000000000000000000001603cb3ed75c09ae5da6b94eea6017dac0c40b17d9aa8b65b78f2ba17de051bf3f21109d9afb214d260a09391f5526c10000000000000000000000000000000019f3bcdb8f16d9a2bd11e3f9491266780aa9110f447e19f12f7a2d62dc4f2c7b5fa18e13792007f5f361e20656c8ffdb",
+    "Name": "matter_g2_multiexp_71",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b7d06c16c77a57b5ed74d829ad6acd665e73d20f1d9285ebba67b60c0d1571e1b02cabe5dea44499ce6d40a83288aac0000000000000000000000000000000007e6ae768ee3d149c7130022f9883ed51e4fcf68c91327ac1fe85aec8083aa61a37e9afc25d3352e144aaf888f264ab20000000000000000000000000000000016f2423478e0388e8495a898c23d63a0929a2ee7cf848034e4c1adad3460c7070caf47631eb930678d3c85aaba044dae000000000000000000000000000000001587e63cdf50d6e0b6b3d7652ad0a0a2497e70259d73432831781641d3a76db4ac7cff1bef165fd8ba29200d7320e43475888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001181bebe3256dd6ed754b8a1d77ac07e9a341789b3f4a4988599c7c60a36f1e95d3e3cec52c54c0f0abe312ac358c28700000000000000000000000000000000189d224b2904bd45cd1e8fa72570a1e35c94680d03d30292892462664f9d7aca3cc45ecc0773e66a10248df28ba9a9a1000000000000000000000000000000000f654f4c8b02a891e14fccbd5a96228afaaf79ed8306c7c1267715bc934e5f2568ea06de2bcdc2a55ef708689d90108c000000000000000000000000000000000c0a413f16e1aab8b91a87e7027f067ffe7de65097da37d67f604a184c7e7a7af6fe59ced8c03fa32ab036862868b35018ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c495043000000000000000000000000000000000dc972d55b7e68f97191d988ae7be5f5301bce5c654b323d4c17bf6e070f7227c0789ee38af3ccc07b04f0793090c6130000000000000000000000000000000016288c405bb42b4e71d12fd0a798cfccc7d33aba0500f939f5fedbd0e071166169d3072befcc5549cc6963b6dacbef4100000000000000000000000000000000171ea4f6607d6efc875cd9cff203bc62eb83bdc05c07f702143c23ab2770f50f42738f748e6bb3bb5d6f51f40fea1d910000000000000000000000000000000000fb729cc9716bf2e9e30a598ee7c4281163b287422ab66b414da85b0b960102991c24cd023791e4241bda5b0f6ddd3424a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005720bcbc598c4eda697406dbb390c2aaf4bc22c794b4b664e9b85b7c2079b90f7678e69496a4a5cd3b46580b90a7a30000000000000000000000000000000001159788c3edf619cc5e6f77c4aeb4860764d46afac4cdce54cade63155040c631eed65c2fa11b9cdff14847950cddc2e000000000000000000000000000000000d61bf02587e2c61544ae8a98b4c742c26a3d6ca49c6ae1b19a9d69c7f8eca43cefd555c973145566f8332902217cec3000000000000000000000000000000000cc0da96623432a2c170f07a3aad2844c1c2aab9d1bb5d2183928c818e681c66cb3767be372be4ae65fa40bf5483258ce4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a1350000000000000000000000000000000016efffb5d4ecbd01567c1e6099c0f06644d4312c599579b4cb780fccc8a425f3d1263a5f0c957dda4508202a354162f600000000000000000000000000000000115686a37624ffa8272ec7dedb7a632ac568245918ed91be6c9116e0fde290c26b5291e5f57ba6a779750685b0f126ba000000000000000000000000000000001852662b92fb49b2f0359255db8a7a2d20bd37705b7994cef1eb8e318aed70fc37bb7af9fc0c32ab3efa8c0afad640570000000000000000000000000000000017a691c08724ccf0e668f2f4eeda692e9ac21385fea243dc62c37ca73421eaf51c3a60771da3fb3e3cb578de37d2d45d38573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000006fe4276e8f2e23127853eb929ee4e0c6ec4a190d46ac222dceb995c2e1d8fc9f822806a877e6cf87cf579cb2862c25c00000000000000000000000000000000044dc671bcd516cf03ad98ccc55266688856a4e4e5a59d6a6bb93e9ca77c596b5ecd2db6f3cc6377a0086c53ceed5487000000000000000000000000000000000c3ca83688d20519423b2b5547afcccbfaaa088a90523272c7cdc9a9b0397340350f2a5ced2a8153d69c81cd79732bce00000000000000000000000000000000069916c468f22bad174522d7fb94b4b7d2a664743b4689daa5423f470014152387a29425642b50f9e50fb679ddafdafa02257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc1630000000000000000000000000000000001fd452b8685b0806545e09783947551bc5f6446c9d15d079a8968a448a6fd6f8b7e91974b71a4b2c50954be299c4885000000000000000000000000000000000f28bdab0b0fd3e05d08ee2c51f1bc0d30380e3a7aa18d6e04b47018d6a8d2b35a8f06df3866ccb95ffbd9c5333ca94c00000000000000000000000000000000035f3aa1cff72df0bb10f1a6f8414aa2ad0289cd15f56d84061a7cc70562f1f12304c402c388e48dd3f34082aaf79eef00000000000000000000000000000000034730e3ad7a3373b97279a00dc7a053aadd088557e0da61b9aa132c5b402fd9aef73cc45dc1cb7f2076cb2ff27ae2fc76b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c80000000000000000000000000000000009c91d800cb1d52501520b3625dd4c20173684bad8742c7ac4b35c0ce028556b6529de5cb7541e3c146b13f58ccae57800000000000000000000000000000000124259d345bf2f8c16215be4b6b7922f4e2d6b32f91c4b1c4f1d4974918fa9e6fcf10e46f0c0b55e2a7210d1a5336eed00000000000000000000000000000000072e6231244ed14aa0f5de06e2f953371995a567684b00e459113380c1434a8faaab8b28a0280336ae35bf1f90f1d4d10000000000000000000000000000000010289a63e0e5f1f35b7af22137e117a85df27874ba15df39b7c26801c169667a3afe9a14663d7ac0c2956f4eb70cf11fc9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000d0f22a9bcda47ffcd034618c15daebad8160f9ab6b3148f1cacb32e713df2ef19f706f14131f8ab1181b7ef7598e3e4000000000000000000000000000000001680314cd79fec583c8bc0842e1750b1318f94aa7700c6662aabd4c592ca61ad51a6876b686ac3fe3f508cb40192c31c000000000000000000000000000000000a172bd8e49637fd9eb611b590c68bda707931e403db35cde1c10bb74c389ed725aab54dcd7048285352c56c8bc5fd920000000000000000000000000000000012589683ff3f85ecb006c5c435ca7bfd9d5a6fd06eb625bcbcb18577cdef610d912e783f3986c965710269b1ff79ba972467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000009720c2b3a0658a4aba8e76e196a558bd155ff550b3e41bb5b43e7c5946bad803b1de64e342956a11627e7f24f69fef7000000000000000000000000000000000decf2262e8369d6a2b1ce07fdd257abe1c7610084ae2f347640c0cdb98c7cfa732dc609c18b7b6a51b47ebe4b07a586000000000000000000000000000000000e8a0158702ff6d6c3a7ed9fbc774bc329681130840d86ca3f26cf6642cb49e5f14ad95fff1c94151457b1d5a142bb5900000000000000000000000000000000035ae66137629e95539e09ee99b001d5b9a6ede79727d7deedcbeb5acf081cd05ad469ab06c265a5224fd5236db160b62f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae000000000000000000000000000000000413d82d0b02ca706f0266051445c04f3ac594ad82e2f1fb4e8e0cf23a6c1087c29383238ad3677f170e99259e2fe93e00000000000000000000000000000000070af21f84895c0193f0b8174cb20b11f45c845a8d782b1f58182b149362e1368ba076ba702185fc54b5da94c3172f5500000000000000000000000000000000182e124ca29d66f9f6c370f6065f60928b6a8f445a74800d59209219add6cab0d1b79702c31d60e61cf56874a4eb6717000000000000000000000000000000000b94b733f76067a102cce9659292f31f3df2cf2770e3a83c1524536e29d0a84ea5c4883cb4e849830384dc7e157d8715474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000004b2feedd5badbbdff6fd0f33a4bee17b38cc8967fc72206246c349e1017ed0407fe08e0cd9208fa9e4e21eca4cfbc2a000000000000000000000000000000000df0d74d5cc17ea94457c0ee26ef24071700a0fd6bfc762e3ec69b8f1c096887f679e312f07cce8340686eb2716c9a96000000000000000000000000000000001878edbfff2efc5af64aa9a23589a52d63749b7ab2970f256874fe0cc15091c4511050b0a243d421dc6536f19b5977cb0000000000000000000000000000000015951da3b20494a266e4d014d0ec70fef4586c8656baf536a0ea9a48dfa041624e8154989a2fb106189217ca979ddbe8eaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec000000000000000000000000000000000657fdf40c829719db134acd6c2a9ff904681b1869f28512cbe2a64d93e5b62114a73bdc5260ad9a1f24a3ff191b7a3e0000000000000000000000000000000004e77bf63eb9c4741028dffd0591b4f525d533b455d35e51cd86c7884d63419a162b145752bde188d2a622251c087f870000000000000000000000000000000016cf02af01fa6750b4d862f0cdd5a87a79da7c3fbedb0fa356ef2e7419e25b3a2bc8cbfa97463d463d0ab349efaa3f2b000000000000000000000000000000000ea4468fe6a85d36ae990d0ba959ae050756805c4c769c829de475a2990ef1c46de20d5b466543978faae0f6045023e85158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000018d42a2df8ca475be6bdc468a82c313599239b974ec3d27e8b8c534aa4d6b85d4ee9aceb15c38b3bade2bb1706a2c2cc000000000000000000000000000000000124d5dc60527faf48f5e9574308f8a328b410de1cb49f2cc6f76b8a1f2707f2d1a94bcbca0a97bc38f24545a8013b250000000000000000000000000000000018b690b3d1e3b22946a91ace004e1d8f92eb5beb284eb05b52ac5ba003d7bc387540d33d088a02711522e3aef7f74f4300000000000000000000000000000000103080d8bb379d961da06bc4c148cb5b056ae115b3a0e33f0a8c99a7fb7b7ceda35d3902e0733156d354dd0240e4bcabd66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000f4d068354cb5b51e5a86163978386533f8f9b6e388c5e75f7d9ff5e1ab6d1637717d251f2b723b7d683e26a274d610c00000000000000000000000000000000001ec5a0d408c55f247d62ffef172ef26e45c41029f1d04e36f0dbb4fe8af414b0f7fe7ec0cfda66a2855b58592486fc0000000000000000000000000000000000cb1b68045076f457746621cd415d743701bf3ecae8d52dd5582c3e0bfb38e6cf2651a5ebdf521afb1ec5b8066444210000000000000000000000000000000010f5672f813470378fa806abdff90edeb0239b00d85ff23a3fc6798779f46d6b43071d66f7742897a4e53ebf6c7dae719acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df000000000000000000000000000000001422fbaf1bc2908be5900968af61ffa7b3af46e7250e4663ff321f42e2db057bcfb2106c433a9eef8fe20f7138b71d280000000000000000000000000000000002176e68cdb0ada2d7baea437bec8754ea293d14afb85a811f7a5d740d645a53e511b5605445b110174ceb5e6720e736000000000000000000000000000000000a69e992b6f4f7eaad2682cf9ac2e58faee9b3341e852543c2aafbff390ae067a641b2b5693319618fde413fdc64d6c10000000000000000000000000000000009440317af8f5c753b5de4648b06212256a39b7fb03678f1913b0a3d402a50e74e2da5d29c211cdf0b292c132759c36d0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8",
+    "Expected": "000000000000000000000000000000000fa31d16d9625200c13a415fd61b7552646c62fb8db307e92c1ac3d2acc92336765a1db42407ab0f774ccf01291b9ee800000000000000000000000000000000156a77678873dcbe4832b9fc7f516eabc1a10f4a6576cfb15765cdf999a771a6a6d41021897dd783e9beb2db722a6fa2000000000000000000000000000000000ee4599a6ca9642cb4cf38f5f2af23271cc8c5bc6e2cf6bad572b618bff9f8677837104b93ca8942843fd5db5c30dcdf00000000000000000000000000000000138986714a4053618e66a85835d105f4aa2ef38ad18e34b2ee7ae30a4282f7e543c80c94bd12c244506e7fcba25f4c1b",
+    "Name": "matter_g2_multiexp_72",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000083c515ef8509b12ab85ad7d0a816d986bcdefc14778efcb3bf7c2ab61991849f279ae6a9f5342880837c0d0f4a4eba700000000000000000000000000000000020cf5196b5d567fc429cb9ced7b55e4925e18c914caae216a736886a8d886c4bdf6d704bbd0ceebdc1975ef530c665a000000000000000000000000000000000f3d0a217c224434604d63cef559eed3864d2da62ac00d49fab8c2c6e22c688496adc30c8d591e21bc0be404b62083c20000000000000000000000000000000003d0bf7f25bab0bf2c768b44e10a6022650f7d5b7d568d502b9d0b28209ee69b1d952ed848572d3e966e8771c20becc4b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000cc4c4b7eb7e358d4133b65e635fc13b8a92229706a6dc5867171a60a99a8e343045a794c368f1133ae6cd2788c3a7db0000000000000000000000000000000019508aa39fda9c3efced287d2571db97045f8b7b0c7a9c9d51796aa8017fc0e5abb8fc994700dd5c9f755edb518e096600000000000000000000000000000000049f68b0ac142715cfb385161ee70e453f0e24e2e93f3f96c3d69447f3a28b180fe76989427b2e392c7ff939011e04ab0000000000000000000000000000000004903c0f8e0757dfd3f5edb4f54a0e292df15ff70757df7b0b04c99f590a3dd13c6ce7bbabf3e14daf9f3ec60e2379aafee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b23500000000000000000000000000000000041128064ac768664f076116247e0f8a00adaaa824cd6fff33bf524d0c76e61203408ac13b294aa41f5c462cd42d3cec0000000000000000000000000000000005e150c27979ff1cbe307511816be900648957624caed1f08d88347061cd783179c615258fcf3619bc4bfa53d2513c610000000000000000000000000000000009d2b3d97d29386b93d7af014ea8f1cfe2c1db5a9aa0c17e8430b0fcde974a4e7b8b42ef041e9a7b1a8aecb97cefb52e000000000000000000000000000000000d86096ebd88b2cdaf5cda1e9ca6b7f12ed5def629354b0570eb084bc7139cf20bb8ebe4438f87937b8b554e2201344c28728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb960000000000000000000000000000000018d677cd67e96b10b671d2ed9234d7708042ddfe6fb804d2e9371a80ad167004f9d6b92d26b3d3af34ab7caa0e03964e000000000000000000000000000000000e34a6c85187d328eb33c2d5b2ca96b5210d47a779ab810dcc380dcb7e6b3c334ac8fccd7354aa9108136e4f6dd4ea0a0000000000000000000000000000000000ab8f7274ee3fce1511c58661625c766ffb0ac68bdb835a948b09b7510bb573d49000000e3d3cea772bd71d79681e1800000000000000000000000000000000135ca42f2103905748a1c416d82170f7d24b49ff3f859d6cb7493cf89bbae0217529a9edc835be1f9890ce105877af630fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000007884edaacca499491580c8c7194c0d60ac6eba95f7a81f63742451c8ed21a223ca545d5cc1e648b9d2dd05016b4fea20000000000000000000000000000000014c78d5d1a93760096bf6da73bb41631e94d6a1b251ed0be7bda93e4c50568420bd4d49e4a46e5be4bb204cdb6b0ad5000000000000000000000000000000000128a860c23a183c5bdd18b4a1853cb53475f1a893420bdf3271cc4a65a827eba6b92e1f9e8ac0d10c73edec5160c640b000000000000000000000000000000000ac14b2170042ee6561c34f77fca40e1bd2d40d01798417dd954905135ed9b7772e5689e6d4e543d44a4563da8c3ca40c14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d0500000000000000000000000000000000144df2e76821c19167f60630f50c939b66867a82c2a5f807e943676c876aeaa2aef2126bef7fc431f0c7b39e648542fe0000000000000000000000000000000005e463627bb2d22c25520c27c05cdc75e1f2ee3b91e8088399ee42ad13ca217284596e5404b4370995f71fdbf1c1c7860000000000000000000000000000000012323010d6aba1bc6b1d6e7f7e8c7bbc0838564b279d5ae6279f7f7d3cb5d96273e27e7096e9a8540463ad16deb3780e0000000000000000000000000000000019102ac6bb33bd1c5a158a584ce32308b6ee5679dd6d2acdcfa4b9c54674fecad7489d1e39c05b1ded88e4ea93620724d81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c6000000000000000000000000000000000faf210330693663c8a1d1fef78e211ed2542f7ffeddca3e19be3ba77ef211da1b8bb5abcfc96b692d74f8c7df40b0ce00000000000000000000000000000000134153a252fd8ec5d9aec08ba09a94c4416f95ff6f4ccce59bd400474c836af5bfd941f03384ca4bd5c56fbe81d96ea2000000000000000000000000000000000b4532ff1ceab2a3a177cb83a75c16a833a2ff28df447def351134ec4fcd608b2b75b1f8035ba7d40a737087f3e8c1c100000000000000000000000000000000127e3ed13384b69819b34ef8705fe9a66dd01b275f1f74c2c724420546b39c70cb7a8295a6c1ec4075ead4e3312b8b603a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c000000000000000000000000000000001351d0d5d531a63a5f56aaf1d7906b7ad2bfb4e9d823e2659bed4e05e7edc9179a7bbf13405ab5cf410b25c7d476c342000000000000000000000000000000000f0ec96128e058e8bfb6e0df1331887245dee87c4f9721fc7f1d20c20a2feea7a7078a4946803ac093477707598d59b70000000000000000000000000000000009399034e4aed13cbf197d8c4753285effa72fc53493ca316db11b39d5527b009aec6350d579f9dee22cd6d4cabd88ad000000000000000000000000000000000002f41ed0dcfa2437cad7b12a94501266d670ed6956196c438241aeb90474d17214eec5d5217090d28892d95f4e40055af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000ae208978a751f8921c6067ebab4190ac8d3608dbdf50222eec59460095b8ab2abadd97616c240edd0a9c53dd006e38c000000000000000000000000000000000905224b317a1e64d8af075b6db9de46ca4481458ad6bceaf726ba0f63e81e2a0322e79e70a5a82034abf00d47fccc300000000000000000000000000000000007173c3359f0c2e315d11d646a76e6f500c0922401e4bf9f4ccf2f0801a567fa653f287fdbfb878ba0d9ee12e25396ef000000000000000000000000000000000161d4cc71621e5df13d121c77105af195c2adff5fc6b656b0fc1dd6eb2518f474444d8bc526ae16387f23a4ab3f342f6541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c60000000000000000000000000000000000266af9991c393d3b55f9e0f22b0967d47dbc5b0c97947125e220c4bf9f4bc58d32ebc7bfb02b2e329c933ce41d0d8c00000000000000000000000000000000004cf5748aae8dbc1e4778dc85da575de2b6d9d346f5dc5ccbfd82513166384111f5e5f2f1c2f7ae367a22146d1fac027000000000000000000000000000000000095dbe68521b2cf51283a8cfea1f20eb7ae37e6e945c5f879ba4834d20918b74981f9e0eff4543a79ff4eb36d84a9c60000000000000000000000000000000007953cad14379ffd4309cef1ed6a2dbb73a93db0bd3a256753402e525bb62b10aaf22b662bb2c704865690af995e7d284b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000010e99f318111baeb1b4611847fdaea7cbd5e3ae532af667ad2498fb2e97b1eee0297e2811c7ae854b882f616da7733fd000000000000000000000000000000000e56cea75b4c4e4c669a492a6723fd60e351a66dc5c34c46469dc36cb04d2c23cfd4aeaa23d0e9e83d5b78a1b77696ed0000000000000000000000000000000018f838d6a582a52a508cbd6bbbb9cf515e091deb7a640e141dea4018af6593c001dc43a8fe4819a7877d9ecf53d5752000000000000000000000000000000000119aaa2ebcdb6379f7ae972cb709990a3e8254f1025cef308281bf7057295e3099d1f3127f76bd2f9ce0a03ae0de8e8d7e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000008f837c478e874b857f1c939a26a02e13061d50728c10939ffcf5e862cb177993e204590699a28cabc7593056617d433000000000000000000000000000000000432d9e66dc78bb58ab98771e7e8b5fe51835f286b488e2df6c1991fd36c3c537f2ce30abf24f9d4fb13941189972e39000000000000000000000000000000000b202de3708984f44f7d05ccd9e574a2a93a285d5ca262017346580be273c58f13165437dc90d1d4103d3b9eaac536ce000000000000000000000000000000001873e1251d9ae9448de8e7ccb7ca59a21bcc0d07a2819d140c06ec33cbba559ba90647494a7ecdec8b609b58cf7995cbfde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000000084e07b6576c73aaf43c0ef9c5666dc988ed93d1a106b71e4882fc0cfb5e710b91e5d5eff57327f5678f662f4a451d50000000000000000000000000000000008a29751f1653236a48adb5fbc59059c7137d36139574c6af97314bfbcc22f77a4c5162092762a26b5da7887b94f2da6000000000000000000000000000000000a4fd84c4d58cb9e18aeee180fb05f07c3e1d7ed8d09940182e9b4738744fa6faf600b6f720441e0ad6391a4d502ac040000000000000000000000000000000018b356be2aebca82c54988ab2a2ec58751ce7a815f3dd58a2218a638753d4734d38b74ca0e00bbc8681768f5d1a02b646f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000de0f22cf05620a5d4bdcf50ae179f23a9c089fd6eaeb14eca937d9e2480f1782a1c67df76e06191a9b87514daa8bbce000000000000000000000000000000001981cd1f260e7d96e55533b8e29867f37af507b4a58abd69e0ad6af2a55228ab1c82fc2de52deb7b7b7deae2fe621e10000000000000000000000000000000000d22a7a567ec8826391ee711768e612c403e3c16e20947ca5861185c24728b6c7e7756debb333e7acb53d86032d5748900000000000000000000000000000000016fad52e1e86b9e092955cefdf93a10f30db896fb519fd2ca12571d8dc8aa352cf4f8092e0e973d0b0c66df78433251e2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000000484bb2452158bca93dfeeedb40745bc5d9a9ad49afa20e6c29fc9ed1a8fde33ce508cc252ddd05fc486f8ef78738ac0000000000000000000000000000000003c2d6ff6f292b0f0e505fdfdd2940e72bf8c2837da4ec9c74fb593fe3318a9b9a8592524bb5d40f6c38ad871ab7b6150000000000000000000000000000000015f888ae2722713e1b5b02803a5b48d53116c1a4bb1191c9da77ded8c6ab49f1620b0f7c7867957d84503cfd3dca1be7000000000000000000000000000000000fd96baa382cceadc252eaf000d47d8c1e2085e9f274dd9dbb571bf85bba612836e1da2453fd914135842e2750796b54762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001820de289f62058920ac3d4bc60da023ac29c431ee429a10066f305d2b1a333ffaa906404af977cfd3212b53e66726b500000000000000000000000000000000094e448db84421e25cd03be3867125cedc7f77f286f404524757f3c1a9cfa28ab6771293da490a4d75852f515dfe1a6700000000000000000000000000000000097dec124970bc63d8f62f9133157d412f5ad3fd5eebb444568cf0fe2825d6ef6577ad302842f35570c9977638c6a827000000000000000000000000000000000490bdaabf4db27dce906cfacf3160c0fe25959df4af89301cbe6eeb29f72e4c55bb467841ba7d0750a59a32fc8b03d0ffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843",
+    "Expected": "00000000000000000000000000000000193358b283147ed5848559d4d1533734822b0248dd17b2effa80920a853b70e7fb683b08aad6ad4dbb91f964ad1b3bb6000000000000000000000000000000000649be60ba72734db4cc307a2fd8be57857f60660d0c496c0dad73794296552e17cb2eabb3537ce677edaac1c6997341000000000000000000000000000000000f91ce27345e86003c99d133eca50710c0722cb35af2ce442ebd74b46d659e0118be9bebf32111c258e4cb4ab795a2cf000000000000000000000000000000000d76ad65233522b1e079fcfef4dfa80f163682d7984d5062680a5dd4cbccd5044de4705013c6bce2140f7950032f90ec",
+    "Name": "matter_g2_multiexp_73",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013fe4afb94d08ae311b7442de7291a11e733d8e555f2da6f72bf99da780a8f8d357cbf3d8959f6aeaca7bf3f5b5bd10500000000000000000000000000000000025af713b18cbdb5a960371c2dd0317f4bfd0182f4bfd6b88d588b56fadc1a0398412e7e0a786c326aca8779ae384243000000000000000000000000000000000581c277053c15df8eec05c34267f62e63faeefa2d124c2b4b84d2a739ce5484641ce955fbecb901d1e8ca816690189b0000000000000000000000000000000005355dd304b9b60498a3fb1f08e1ba0c98db327365ca9a0365a7f1e5cb56aec43b7fd2b4aa104eac7b1c30b6f53cd422be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000016b5b3a6fdeffe5b9a0244a333ada4444a2e03771f94433832a4617be696e467b4e88ed80b174809dde4242bbb51248b0000000000000000000000000000000003dee846c5b84f89734016e547c63c02e4be07dbbecc86f811e2d8d3245f91205bfc055882565371db532240da1a845900000000000000000000000000000000194d53bbfa962def4da2a9bc7129fb6242a3922fe26cc4e603528ff31393a31d03dfc3463704250ea2ffa973ad175153000000000000000000000000000000000333768faee332d7468119b9e0469bbc7bc98a482562ff2fd9aeb6d9c67daac9c3da1db41c9e12224a2eff2feee51778bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c7260000000000000000000000000000000001910ded86d79f9b043bb79cc4049e0652c13d0fb8db2f070d695124d7a42cc3a2238282fc8a424fcd8d9ecdab4bb6fad000000000000000000000000000000000dc8d6caf97416928d2d58466219f054c6f28f49b2bc04d8a80cd46a308bc95aaca3a8df1914ab0c7da341862fdf47400000000000000000000000000000000004380ca7b1f7ef96295589f78a1683a51bce4b2afe50bd6076ccf5d07d35e6cb2ec7f74fa35097b2c0b9fff3f4797c1100000000000000000000000000000000054f492d7442b1c0d1293277d95efe822faa7d8881b9afde20db58d6267e049b90d0c8828a6c12540f4ba1e7c9ace6d84f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d7284000000000000000000000000000000000bba4761eda87a304a80180c2447a1d5a52f743015ea7c728e70d6a5defe3139c80696f842da3f06586be8d506ca4bc90000000000000000000000000000000019ea930d5733f4a1ace9fa0139d412d65b2886b659770e388894592de0694d38876fcd86d14580f9b92518d5496fd44c0000000000000000000000000000000002bf5d9a36d641d1259c1b30397aeb071b88844c4cf17e3de0984129d7b4d67865157ee2f682e7cf9d968fc07ce43618000000000000000000000000000000000f9a4f29868654abafc7ba935aa22d3d010023ef5112683a037a6c69b9e89374b256b8e1329eb5ad306d9f2063c22c335d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd00000000000000000000000000000000004f194f21373f09f8cb4984169890ad3855e814a4768c84e9fc97dfc181c60114aae534a27d3eb225b2125131c754ee000000000000000000000000000000000e6f88880e9645e35806d193f5d16799d63e2f9edd8ae28df54d19875c61857b0a34819a70ba3e9c31f00b5826b0cdc200000000000000000000000000000000193293c6cfae9ae4b24519fb23469e2f8dc4eda8524ee0b00c7141587b07c8a26a29841d41cafbd24bfbea2034a9c18e0000000000000000000000000000000017433efadfe9873dea9a68177af3d5dec4a13dcf4a710422d52020d4d145e2523ec0b48acc533a1ac7068c08ae6aa28bfeeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000011dc003f7542f6822cb872117fa658638dee2a15429aaa9dd576a7e895bc0a2160bc120558a32aab9e646354233a1afd000000000000000000000000000000000fe9ed8ba572ef7d1176176a31fa92a5ff3dc38b0183ea1e22618e3b3214ee78c53074d4c60b5056901c6f046f8210070000000000000000000000000000000006ef1c20c3bd88bd6787598dcfca52da4e5e0e7c7643af983c709b916e71fd15475da30d763ddba0899b182cbc070ca20000000000000000000000000000000001a38a2e54a44ade572ecde076038f5244f266cd99532024a377829a64c20fb2cfe1633367c74b5990febb08e776bc34edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a60000000000000000000000000000000011ce7b2cba037e5f3ff19b36371d34e287eec807178dad4118c6d43aba68623e182aedbf911a2ae5cf3d0e690ec3ba790000000000000000000000000000000017a617453f391e6e2437d56ee831ba895084f60d1a5f342e19a242b9661c703219d90a157e1b55f005f5059c15c179dd000000000000000000000000000000000746ab134c7f4bc19583a4ea4991c7cec3f651a60582b40c17b2d18cf6e252d93d2f3c2a1a3399be70512ec9eab251de000000000000000000000000000000000698daf214f2de44ebfaa36379862bd9ffb40987dfc8e632f14738c93c8e5c3fc7be9fa9100fb5f7440311cab34fe1897a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000000e4ac65ce62180ac602ad68098ee31cb747886e95a183e4f819d54af99850d70496e6952076084dc7bc2d3f7a273383100000000000000000000000000000000182c718fc9e5cc961426258e82594a5cafc36270af0eb50646d161fcc192c30d40d06647e14a282421638b31f378de940000000000000000000000000000000002bf448ebd27cb6270e1b87087796ca6534ff51ba0962f3290ee1d06dc18ed39fb736ec95632b483f44d3a9d0e45d1d50000000000000000000000000000000018b956acc1300e60b22bb936b2b52e2ae82e256f15f1415263157965179855137715c321d3765c5227dacb63ba2d6225f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000f5372651ffb40bf853f6f8396a7c7483c401b89b67e098ea888fde8d19e7552a006a127af1f3311203434126ffad85800000000000000000000000000000000050d7e89b21c7484cc5831885422fe7aa8e898df85cf7a3a275370623eb9660611610cdb829d3935f0d0955e0ac97506000000000000000000000000000000000f83a3f79f1dd110bdb8521e18a64490d567210801d77fa3c0c6e5cbc7285840da325cab7ab08494c8d516511eb189dd000000000000000000000000000000000f72904131be66380c5a18af4857ada7c15e88572197e100de1cfcc9fdb4306e446f2f330fefcccb41b676f24e3e0bf88031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000009ba6bbf102d390638ceb9259205a1856def2b3a4b5209eb3e4e54074347f71b6c06b70764fe85c8dfc9074067b8d00d000000000000000000000000000000000339c30631229eabc1230240942bdbcfa6e18f23bfbf88b7b8a8fa92f18e35d2f7336f0b819e875ac643b43e6d931e68000000000000000000000000000000000600cfeda6033ff51c3bf9182d22abbfbeb6db46c0fbe15ba82e72fee483744ba5a57ab2eab6f35927b4ba6d2b150063000000000000000000000000000000001530bba4db8a60bb6b7a05f72dbcd23044011d75221d114b839aaa9535400874472f94c849597174322291b5cfec4974cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000c636ac98557e22897fd101dc6c54d87060f460b4cf2c5a88ea14641e2a8a9395492fc5a946eebbba36dbe38f6f5c0c60000000000000000000000000000000007fe3a557aa93f2e9aef4ffc55d39a9172475e6595fd57409df3a7fe3d11558c4d3dea3396ee62f61190add83b85813d0000000000000000000000000000000015b04e0daf4a10541623e7523ac5fbe57dfff9ac17afaf4293c493c1982f3395980ec63046cb1d424c6dec91899202c10000000000000000000000000000000019617b191e9e493751b0a02511a18757330bde56722a72a29a399ace983db7114f84795e2b70bc9d670cc0095220454ed983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000017472b8c1cb3ec528400649fe7c39e3908b16ed69b42d967e4d225b694544e8bc7ce5bec87019db5539f1de39dc6807a0000000000000000000000000000000012b1c4884c37037a94f84c15061df5ca6c05c5a35ad9b37e3ab8e8297c9000e715fd2bdc3f2b485e86c415bf656392a10000000000000000000000000000000002c21af2933029f04b344be76e18ce499def4a0671a97dd9b6a108d0fb23852fcdc56f882be0319978952ef04a207a6a0000000000000000000000000000000015eb31e80fb162d5fa392fada8d43648ef54d4f9ebcb0e9652dd501f55a8875a16a148d42e283ea8bb2c5a38bfcc8843a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a70000000000000000000000000000000015e68e011ed063a9fd9cc8a806d8e3561e4f449526ccb6e5ce983ebc4fc49d61d26dad7db64f56ad5ab0b54fbdb76e61000000000000000000000000000000001617d7387fedcdd772a34b267a44315212d21b798c0fe1e7a9ed3caafb678910d9c9c3bd1fff4a3c8e339d0c90a865b8000000000000000000000000000000000e2b3c9b9cc10f41c4c0129d34c62d526aea47c77ded91a5ca3afa0da1801bba81def3ca66a978ebb2d1f3227ea82a9700000000000000000000000000000000096b6caf7b6f29e91bea370f91c2576c188b08b95f9df6c7df995fc9879c11cdbe2af86809468d472fcac8a89716d1d87ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b80000000000000000000000000000000016daa86ec04f57c72395d96b6ea5d6ba7cf2d9d4a50eb90f7121545f17c1ee16216f4086481d91e59fc5ed8542baeb7e0000000000000000000000000000000017a783d60be67206241e0bcad20e371d86d47d88ba1293b73f32999b0a1646967e5d031a5b28517f035168d7c7d7927800000000000000000000000000000000058f24fbe4e9befd8abe364c961f0ca4d9083260234a939bf6103a3e8f10a8381a9e3d74af7c13f159e5c7dcf456df00000000000000000000000000000000000485c9448fe3a069eb024ec43aaf563a98da09c02c294da2a94a98a95430e25b062e8ff886fb5fca240fba1abf7cee60297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000001230577527a0fde2e8e66b8c4d17594bdab8be1339866819c8890c600b35889d1e3a749fe15fd8182001e30e6420ca6d000000000000000000000000000000000ce03cccfa87229fa8d560884d8c7963276d79ae9873a23d550b4555cc4bda35a242dd2e70cc730b70cdf898609b3d8400000000000000000000000000000000174aab1f142fbb7a45bcdffd64c2d38b99c8919baf9651aa430bcd39613d7565196c18f0f4ee6fe05f5c40ddbcd4a67a0000000000000000000000000000000011dd23f59ca2a033ee5dfa50afb0c7ddeaec6d4f50e1866cca3f061fa03594216f005bc65b2c97ed1109c305e16222671b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b640000000000000000000000000000000014e1cdf4f10b11f47c15d0b6b7dfccb6081d05d116c8149989cce4f1c53dfcd2d0b7443677b03d037710eba813f6f597000000000000000000000000000000000c8415c7d5508010e0db1878ca663d359525b290b2f02c61436e945145a7a4e1b3ff4e27ea1b2c8d3adbe737d8291b14000000000000000000000000000000000e424ece68003cbfaf65a54dba51e7b0942cc53b2fa9794b4deb6aef1dc1ba1719cba285f9a1a59e71a881eebffe2eb9000000000000000000000000000000001404f9a3146b7201b09c5fd678fdbf2111c48130e82cc95012e5aec1df7e64a3b3c727afee4f603e620925686e126c0f5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6",
+    "Expected": "000000000000000000000000000000000e9f6bedba1f6e2a3ff33e0e4b18fbf8e77558bf42e89023df6338b03a648c591486c63c2ecc8ecbbce23b3ff9a7ae6e0000000000000000000000000000000013d2526d83b4495b5af645d5a1af7bd40bd0ebff125e0fa14f10d1c08511dc29643dcfbd25ca0bee5705a56b26c558730000000000000000000000000000000003fa442ab532094d47f1a9111c87deacb15d80ca6e76bfb5f9b9a209bfe196643351d778b0c6d6b274b4799f733abacf000000000000000000000000000000001278d51523d5d9aefc0d3783e745da54f74a88620f2161090a398defdebf82d13d5b5a21a5cd466352ab8685b034fa89",
+    "Name": "matter_g2_multiexp_74",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a497e74635fde8caaa5c9dfe666b1b40732e58b93a72d39c8a60c1f4b262e1f18f62229a30fb8257bf895352ac4d249000000000000000000000000000000000c1b2fcbd7f78d85c73ae55f67110b575750bec353e55761de0ff09a9f8a2d916c336655d8f6a78dfbae13fded5a9c36000000000000000000000000000000000173893333d998dd32cc3e82fd7ad8ce77003192ad2bfa1b1d2b43f9466898313276b922f9fbd8e83e86b67acfd9ad780000000000000000000000000000000004ed01b702bbafc73dc1e6846bc944be297ff08d1dfef397603294c7fe11668cd0670d386a8fa0f0f02c52d47f54a11b34aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a6000000000000000000000000000000000b5ec74a2150dcf5ebe09f39234c4dfec623318889d92b0bc1f197a69650bc48d28a1112306be763176b691c6915dc7c00000000000000000000000000000000028db19af73ffdd0111dabf9c7d6879cc7389320a249f108b41be8b1d4c259d5889dbcbb48b30a288e26cd9926682d1900000000000000000000000000000000172fe526c62f9cae49e6d3284170e6339d5af256441590cae9507c61f987eb495d340500cb761896163cb8ec631434690000000000000000000000000000000013bbfcf9cd3167b47b48af5f5ed7e6d45a5fa38192756c9e140eb89a85c75602814f767c57108cfa2f726e71f31548f808ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de0000000000000000000000000000000000052d7a0f93142b36489cfa21d76c0eb96904a3ddd946a53b8a6730036d88d30336fd8aae3ab29ebf62a48c6e849ca66200000000000000000000000000000000198350abe8cc91bd675f26516d771422c128d5dc0af844c6c1af07bf04a1d3ad9654cbddf2de5b7828d1446c45e7828b00000000000000000000000000000000198f35692d5face8dda4b464ff48d650145242852fe189748783b1a2e48806294368ae0a99481bfe739fb4962f3b86a4000000000000000000000000000000000e3cf2e018a7e0acfee25bc3a82cb282cb377bbd72ce3044dd20e109d948f68720c27aea3d4663ee45b2de6f178a00ac58c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb41500000000000000000000000000000000150e6b03a3052d043da6514bf4ee09baf1a35b2a909473db33ea0bd4c6af7d7aee9a8366c1d08d2adc5998635eb0dfb0000000000000000000000000000000001370c2976b0d36fcb955e797087e6ccffc851d2450cd63833d6cbf52e1fccbbbbf9dc695ee45c7df01c2828051bcd79700000000000000000000000000000000048b5fad2fe0af7ccdf675328d8ff5e63b564d8436d04c55b23b6ab7d2aedbd25d614d1780963fcd03d569bed2085bae00000000000000000000000000000000141f94b4e7ba542707d0c3cb69f8dd79e499602952be2374cead840dc669c5ac57089c5fd60c44291703b872098fa2daae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c000000000000000000000000000000000eec51e0ddb8cf9914304e7766a7418e2854ca71367c1d2b3875c12b7dc5c7cc2fbc136037bb7ff72458027104ed3f270000000000000000000000000000000009fe5e8d1918f9b5865a8b97c2c2cfc8bd750a0ccbe2942070827a09d8e41ca795a86b2262b10462795f833c73e788ec000000000000000000000000000000000b95c9146f3f560ad880ca905b5f297e48905680b4613e91f393f72ddb042f6a6201628fb5f75fc23f2298cde66a6df5000000000000000000000000000000000a29a8fba7644ac96d77ee73a93dae23b03d81a57f6cd8cb4594b23571cc1f658f163081ae50d72e09c6513d1cd2c8bf72022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c012871000000000000000000000000000000001807dd8d2bb40a642fef693739b1df12fc787db0f031306f31970d0f59f0c97c0894afc34b9a9913726a20dcb7d5191200000000000000000000000000000000096fe8bb5e911c1ed9985ac08d864c7020367f4259a0d074973a26cc421a44e8034a7007f6d1639285cf8acb8b2d64a60000000000000000000000000000000014026d43eceb26b9ab5bdd4139d4f94349b273e43f27737f9ad26d23454cdb1d35ea793d21f057359d28328a82d5290b0000000000000000000000000000000003dda2a84bd1f92524a8ede9f5e81f0f64b41b24510f4e0b8146496a776d5b509968f188c12c2d66cf755e5000cddb3b800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc00000000000000000000000000000000011246ad07713d1916c662679ab757c053e33def437d7a976533f0ce80ff6ffc259489c26524ea96898c3747c4127539000000000000000000000000000000000acf66265811a57e47a4c98b40b12a37c6f439550b18215fcf856c167b7218397d7d559f852fb45077945a5074f460be0000000000000000000000000000000009badf2799f1c43a2e3859123aca91e894f86d6298a06a9127249100ba270f2bdc79cf511691bf2d7faa45ffa17490eb00000000000000000000000000000000069438b1d53efcc4277ea7b41cbd28a19f80b5380136f62121e766bd2845e13d5cb40b2f15d508414876ddde491a3830a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c01300000000000000000000000000000000112c4cc34da9e83207b5ea8a9251ac5f004546596f2294b3fd51b77ad8d8e98239d53ec4f527c7280801233175500b1b0000000000000000000000000000000011dd8627748c9a2b08524f88e560cd3944bfd1fa17e1d6e2e9cd025b04f2e3ed35125197136afa2848d24fb5fd19508900000000000000000000000000000000093219f9ffbfdaa60c5965b45a5d5bd923eb5d3971542ac147de3f591a5fbe31b30704a0061a524e2ddd05a45dfcb6a10000000000000000000000000000000006407dffb5580790e250a72dfe68a488431f61f45ec9df279217b8800f0ac1ab585d84e486487d5688735fe5aae75bacebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec73040500000000000000000000000000000000107ede23f8e4f273ac2647fc251008905966dde32339c023f1da3c4d35d483a55b54f4157a303e68e1dd7fa3f3b14c8d000000000000000000000000000000001739327f282812fbcbeccb12e40df049284562d8986b8d4559787e1d5247eb6c83d6b838d099f36d8d0e32da2a7999a10000000000000000000000000000000005e5b6b2baede3ceae776da5adf075c1d774e83d6129ccfe7e835862686bb4064b187cc0be0cbfed37e5cc039f3a3fb6000000000000000000000000000000000249554dcfa53f73ef8f08daabf20c55301f75c8ce095cd794061c55e195221602a54ba54260980bcdb35685e41d0f4ffa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a700000000000000000000000000000000048b7fc5a71787231f1c7ed2134be528fc8d8f77102bda806ccbadf4f9bed79ee94b43c0fd3e5b1d776fe73d786872d1000000000000000000000000000000000152a1f005a64e16949d7249c3b391d5c1e0ded4893d0ce926cc666f0f88b64e8dd6ec4f92ddda18127ec24cad7e40b40000000000000000000000000000000013a2e1e7958a53307adf3beb32a88b7c493df0e37e074c9105da3c09bbaa01fed092fce2b1800790c6e8af3d30ec5a81000000000000000000000000000000000e2d405806764c75122c1b5e410673b28759f26af7489cfa6f35c6c0dd16c508af045009853f3329cda4a67948232bcef0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf2110000000000000000000000000000000000a71d61dbb3ae37230a2dceb54061d5f8c1ce645e20ec39785c229cf79aefe238959b2745e3b50e4b3c20c7a8e2ae27f0000000000000000000000000000000010e82b8dd5faed6bbd5755c4e5a88edbb3511d3f4442d1e44b82cf72a6414bf6558d29e8907b07f71c00f537637605bb000000000000000000000000000000000d8c93f1984b742b5a02777b706970215c7d8eeeb7377cc26c3af9005648c2eaea7f7a3177b6e049b132ef6bb4b188da0000000000000000000000000000000000ff082a252082499d70eaeba6d5514fc8d641404b48b2ecb256eeb40d9c6b68ad5af58556c9dcfc5667621c549b8ee760d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f9000000000000000000000000000000000f4cf354b8de6dd2231448bb235af3c84daac2db49abed345da6ded50eae93982a4f2c27b07ce725a062b07fdd9058fe00000000000000000000000000000000076cf19408f0f0379c7e65a6675b9856782990986f5c6d7002e9c9c74b95ab875924bd7ad5e4812844f6d1f530e58deb0000000000000000000000000000000007acffe32f96f5e56557965e3db8dce87eb7140d93608cc003bf4a43fb261bb7360c576da0b7c4dccdbdd9cc53b5c5f8000000000000000000000000000000000eba1c668fd9323d42d6a82d9f075cec2d278cc57122e25ccd72cf8b5a569552cc6b0e9f88d23b9b7af18f3bfa0cc820006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d60000000000000000000000000000000006157cb6e2dfa2733d4c489ec0334f0303ff1ad410f329cb59f99a5fa3ed2cf84eb7d2f231078ba5db0954badb58425f0000000000000000000000000000000003dfee394f4c140e2cad61e8675b26f91244880d9a0b6798d6111090dc9d080563db5c89b7293dcaadc74ea5849a08aa0000000000000000000000000000000001aa1e0683014d5b6f99f469a0b7beefaf05a7ac0298bd1a3e2da409f6cf856f70bc067610fd705a851cd70054df9562000000000000000000000000000000001571b129f69f3a6717272ff75351fa053f46294f68ba3f859208d6c91ba5eb9a0f2133a5e139d04e38c7f7aa303451768f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be4000000000000000000000000000000000c4e84b7c8e46daea67c8090b27dc28b7867b89b92f56232bfd8ecd9968b865a057957292e79c6dc08162f9e91e6a4b2000000000000000000000000000000000b8d1eadcf3f1de6ee608a4a0ebb7defeeaf4e251bf07717a6a8e50c07223ca32a2ef290f26d0de14b1942e02acba39a000000000000000000000000000000000e901b546a4d3c68e4432f376c97f42ecf0724777956c4ffb1e6ca4fda562e57be788ecfa45ba3afadb439c2ea546ff30000000000000000000000000000000007ffe01da4fbda9fe5d47c3bedb4b92fdd71ad73fa272b071a7a7d1cdce7743a535da7dfe05a43d03368eb97fff54b2d861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000008da0fe413e31ca68f84032f23bdd5399e01eb3b5ae47033c6834a39645d7b5cc2ec937067b91ac6d83035a86fa841f9000000000000000000000000000000000b950b982323f747782d9065dddca5332940058a604829e31560a6bf9b03ec72b09cfb87a1cd244ec694c7cf192c37ac000000000000000000000000000000000f4afddd25eac15d2248c71d76c9aa27323f75141820efeef1ab4f5003141053f138d9a7d1a901961d0f2c210ade27ed000000000000000000000000000000000217b1800c53d53459b00b8e463df1882b2cbafe85043f08093a5414e58ea7fd4dd933c601acfd7c154d0e4ce187468a2dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be55000000000000000000000000000000000046aed1acd19201553bb6a88fd6a6c0525ed44822d2a4ed3bca48a0a2b75e76cfcdced8f342b81ce03ffa72e667b3bf0000000000000000000000000000000009a5adbac43cca3402db016a2138342fae89285ab1fa16d7acaa9c3ee2b4e3df2641f7392355996bef7b1578ce1ef119000000000000000000000000000000000c8ebbcbdf2ac3fbb553a2e589f4b7c259a1621b83b14fd1927f92d9f6cb27e82507d7943ff5930f0c14b9fc38c9857900000000000000000000000000000000105b729f678db31d04ceae0aa37f9cb0b0319c4da9a1a4702a11bfe3a5f2f1f2af09b9cbd5ded5a930e2e65f4279a31699893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23",
+    "Expected": "000000000000000000000000000000000708e9b926f2536731b02b6b75305c549da58e312d9c53701a993624697af2f3469af34dd4634467f8c98a0f721cd9c00000000000000000000000000000000019185b84fc0511a048e3c39bc10334c91dc1052d323a31c8bf325479a2fa3e4228f8260c0e725c2b89d5a0319e6fbed70000000000000000000000000000000013c7c441d5cca81b48d43e908d6a3bf8b5057cf19e4884227cefa9b235103b46edbe01bada06bb9b620ebbd016d537630000000000000000000000000000000000431182c8a1eed66073956fe5798a894be396403c072e766cdc262b719d1779f960f4aebf61c1bcd4d005d3c7413e52",
+    "Name": "matter_g2_multiexp_75",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000199f555aa5c651183f52470e36cde438422f41c9b2d1947510665254b74ba0bb9cdc6e6a1283b0c8f58d8f009eec46900000000000000000000000000000000018f1d8f22f43b4649300aa23ac92a2e8f17e7e3853b912bbc8e90588125c371084cb224c2d54dcecb4946ff6db53cd02000000000000000000000000000000000efed0bcc83a52f0faf9e260815da8d4e5286396081268485aab052a96af8eea0112be6cce1486b10b60551ad6c810780000000000000000000000000000000013a3b1ca3b9b7d50083c10d36997f5f521d4426af8d2905aa5d074ff37e218a0c96c74387485c2dae24c0842b7a74cf0d8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000efc5a5c506e94ad2754e235e2da866d9c46342f14d518f12510c93f13a619f6bfefec50c146d6d6170f190497eff229000000000000000000000000000000000fb91f34356005f38c9804250549554cfe67ce195d5e218e4e1b1a4fb904257bdb68d6dfb013e8e85fb5a4cbdbf0f21a000000000000000000000000000000000f09903db4c41fe3f11c6f0cdb7c31a131033e30f52cb66ba10c2e7da1ed8a225ef280d313630121701f9a490e8a0f5c0000000000000000000000000000000003484f7e8f7d67ce40b4cccef110bc255d91f61a4e1968a9ad37e25058eeaf39e9f1ff89c9b2e515388a7c1b49a84a2c33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f000000000000000000000000000000000444215c3d4a7d62201ea1b69890e2ab90b5f5c6ff56fdc9908634c7489e785521b8dcd7ed409cf09c585cae8414a3250000000000000000000000000000000002d70674251a0c9ba76b8bf3b70547da77cde5592da9204954abd6d8aec82799cc0fa4fcd42139357043fc867b3d0e0d0000000000000000000000000000000018c57fafbad2351a3da695f8b523443e8c763dd7ab875caaa6a494a498cc40b1c0d44488e2dc80d1f0bce00a2c90c67000000000000000000000000000000000125d5a87ee3f558b5e1e7664b0cb95c195bcebd5e43b930fb47d15eee4fd50b3fdd0a401c9bb011c326acc77645440137894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde480000000000000000000000000000000018790123ce8b3b72d626493a16936c47770a9b06ca45b17c6fa5c7759f088cf98de8ce7b3b5d6082e9e42b39acf76f79000000000000000000000000000000000fea86cad8b40f315d8378550f6d3d831149339a8e8dafa77295859ddd2417e8f5c0ae2baad25fcfe00de14f45a537170000000000000000000000000000000014ad78bb2bce966d52b1fe1a273bc07f2f24b354465edef6dbb1e0123c7c3d7550983b3793ff1c7db846e88eddbf33c4000000000000000000000000000000000c0daa6fba40ec59f6b34d413130df5d9137297d1b7b71b83114a6570fef8e7f83d6f5689527164782f92da4b1ea12e8fb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000001186b7c78952e5c32a9393eab07ad4532471595bc2c5d8137c61dd7fe6b6ca3aaba82dc205a559bdc15421a001b7270d0000000000000000000000000000000012d56b6fcec3d6511d2d723601cb8c9faabdcdd12efdd0e2bfd7c9292f2c3bd7f39c6e9aa53e6955727f88ad69c5b4f10000000000000000000000000000000006a5e56e4a42b04c03619c78232104f1f1f39e755058a19354eb230f2f09bf486b2586817aa6b88f27b884957ea0226600000000000000000000000000000000118c8521dd4866df907ecb252d9ce7a489f17d0f240d054a5dbff6c35895ef20b205236aa6e5be6f0825f9df87878ab783c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000092809d18926c20456857826491f55cec17803e9e7d43f22faf4da18ede3bda15e3319539017ab20ed1de2bff490a33f0000000000000000000000000000000018d736b967eca64234f4e0018e5d6c902608e265037d9b8ba42dcc923b84ac62599e153e1c7d00e552ecc5aac57d1a5d000000000000000000000000000000001804aee99219354d4a5c46328f0658a417c85c6bc89af6db29a4911c4b0cad5638fac5ca61cc997fef3450cfb4a6c666000000000000000000000000000000000bf99dc4a400adda5bc89762e9011dae8ada23b284e52e2d49f75f1c75247f6282c95a36f7a72f896ea308131215404bd2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b1000000000000000000000000000000000604f8bde85c0b26894e0de155cf896c911bca47533362a0b59ccdad0dd64108d33af8262d3ca2ca399306723f2482a8000000000000000000000000000000000ec10d3777aa54cd0cfd84b4062092ca3ac840a24e8e8aaad5f4c275e4d45091f838ae522efb1b2a0fa42229157297d300000000000000000000000000000000132cc70638d02186116773b31ec0e571a55c1cd78ec055fc647ab09cf4d3c543e0552d559b3daa4e99cef031e583e61500000000000000000000000000000000194a6a32a269692906b64feef9e4e8cd204e560b98db8c66380758d2123babae871273b4c571a1570a317c13a51d0fe9120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f0000000000000000000000000000000016d05912dfff44912bf34f242ac85eb55bbb8a21625d45496c76d057f518352528c6632d6e8adbbccdd5983d13c26953000000000000000000000000000000000b10aa1402c15fd601ce605ade8f25531ea8f95cf592bf4ed86c4a3aa847dc8aa2369655ce5348da30a897fa8d71ffd800000000000000000000000000000000183f5a2f40da0a0f4598c6b9ea7b99f8cda1d85cec0e6da5365d7eaad1e9a3167bd647e5e654985f395ea72257f61e5d0000000000000000000000000000000014e615e2d5072c1b536ffa607f3a826ce297800b0da329fff397b6327800ecdc879e91f1e3ebc26c18e188e1ca66bfd66af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a000000000000000000000000000000000f3cd275d72a637bcce855e2e20727c6e5a1f15bc8d799231d3a7f61311d4cd2f58cf38448675aee9910c1a3d0b576210000000000000000000000000000000019efca445312f568727948c803d06b8d4e2c5289015740f2626fedbc0047d344aead06ef521ff7e139312fa41d1c107200000000000000000000000000000000141384e1c9f79e38bbb0bc1025c079741b93f56e150df58cf9a61ec27c2877c4188866fa197242965e3feb47a78c68380000000000000000000000000000000010638286faa6c45cf028e8e3d200edcb348560e2e35902927391401b3155240b62a40784db88e02b874e128e3a2132b5f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b000000000000000000000000000000000c7fc4216767ed298206bc142862c138d78726e2d39afa18fe5732616c73a965d95cd2032d4b2f5a4d562be48ba6885a000000000000000000000000000000000928bbbd76b87f58ecc850e1aa4a2be11b15a81786aa7ca8cf0f6cc342db87b66c435f009f88ad97b747400fbcc651e10000000000000000000000000000000019f5ae9f06f2bc27a39bafacc7f3745fcdf8c78c9ae8a3c066ffd704aa4117eba773691ae43387b93e86d2e2de3688700000000000000000000000000000000014360a7ed73c05ef5fe651321f7e839c920bbc1896636143b88357cbf76e15da839bc7e1f1e629768d447c9d313cec8e23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f50000000000000000000000000000000000b9d955f9d28f9485d0bc4a961f0acbf09ee5fef38ccd81a2c73cf87a461ff1bf28d4dd1e0db3ea522299af67bff93b000000000000000000000000000000000889061e71866001b0760f68e20c7c0c033d782e6e6752f11502a0e8b6b70277a985dd13dd83424d1e5cdb9eb96a01c0000000000000000000000000000000000e05a26686667f44de2bef53c36c82f1fdda13dd3f7f8fe1fb026273dc4dfad18241d732ccb757e2b46ed8317dc69fad00000000000000000000000000000000038b55685b02231905dd9a62a709c0f015cf5650b3fa469462b3e9d06e3af8092d998c8e08ee61db1fd5583b0809a38996c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f0000000000000000000000000000000006649a8eabb25fb7793344a0b29325a88294343f6c69612ee9d9002154a49791f6cd7b37b2bec69fa8ce11722e9f8a03000000000000000000000000000000000e10f2f3de16fce9b9817085f0130e1839d9aae949170ec16834732a9b12f589a2b00f17d2fd3416ddd020b7421ca20500000000000000000000000000000000016b51112b3c7c42a8c2a0fa7f286ec05cd07b6cea5675bf1132de99cf42b450b3c2a8f02ec821529a14a2a0fac3a751000000000000000000000000000000000f471ec8b65bde22e003500d1d422dd0d163abb424dd261fac588333755cc5124acde328085d8df852c61e024155564781c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c000000000000000000000000000000000ec162d22b6516c309efb6a4577c5631a5807bebddc5fd1be5446e4a64785d49eed80eba2e89cfefe484ecb8d50440a600000000000000000000000000000000070c252caf6c56018af6b281b829a4fb8dbab850ba0446d233dcd4d87bebac00e3e5070bd41898dd561526498b153199000000000000000000000000000000000a0d76d1205c1f520d82c85bac4473ea7cf5f68022d95b1f04d06062197973001234d86921e70a94e478eea85264f14a0000000000000000000000000000000014c6a07f0d568f2103ccf8f61278e916458820bcb61fd91479b0dee874fe36c063a34bcb14ee434b68681d297637b5bfe44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d3000000000000000000000000000000000aabac129385d145243c3a1f357ccc963ff14867ad039827488128ac639dc62fba82ace66f889b47d8eac39802bc1af900000000000000000000000000000000062bbbe8c72cd6f8626484bac159b7e28c6c8c3261edc6a05a30c308cc9e56db17eb58f62ab755f04a5c87e58c04c7550000000000000000000000000000000011a4a439d18501142350229778f67bbe0c9b948229dcecf70a8b09d1df6c54801a111c603301da2377d4198d09dd51e70000000000000000000000000000000017de3d9bc6fc5f415d04ecec013a635fa200699c496f4d0bdb5cea7d446274dddd0a7f6b06058fde43fc4f1457361558ae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d98000000000000000000000000000000000c86590a02fb5c9568af4e69611f09980cb5a7e040c94ecdbe64e40005783fd3305a5657a5c6bebca7d20ee123a872b4000000000000000000000000000000000bc873a9bc694171d2606f4efa409897e03198a61b1bb16ae90f0d12345d2650d93c46e0c22b717e2f0504b8983515990000000000000000000000000000000001df9160ac3bc54c0121a9c69e9065f4266202f755c961bcb8641d13720b82ebd73eb3804ba44769fb2d75144442f1c400000000000000000000000000000000045e9c8ed2fe1e5c9a2a5bda75dd60f6bb5dcd0a805f68c1f662a5960b025ff29c8e21857d2a61bcd65c747d2a2da8ef6a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e000000000000000000000000000000000f94df8d267339bb4f51b21014ca6d685f7657d0f0bca189e53cf19e0e5e05bfad773c0553daafd80c86f302b1907ba5000000000000000000000000000000000d92905addc028a1dfdad50e909c77662e10e4689e7c8a4a0174a3e1c746b361665b65e17fce02b6c067a5b8d7a6a6f500000000000000000000000000000000183444f0665790c48bd3c07545115a11f82463a092774234e7b33aac1094761f213235895e5e61ac1b0a15603bffe2140000000000000000000000000000000003cc2cbbf181fb023a5f6088d8a9793b17984b3dddc8c3ef1a9f82f8f436002610df60b2d35be212da9945bc8108c0bced3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5",
+    "Expected": "0000000000000000000000000000000011f85691799cb76213068ef4f997af66c349bf707295b969d85fe637d4eabf54f3f29e739152aba5027c1b55317a27210000000000000000000000000000000019627f9570f07f44f326b5b3ee19bc477e92d813be2865e00da93135645e02e6fe5507ac4d50085b02149667794609fd0000000000000000000000000000000018fdc97bf0f88b2348b436d70ac4e28b5ee5ba21e21e94808b8b9e401c0c7d688974fe203ebda0b23abe38018876f4930000000000000000000000000000000019e28c9c936ea5a0b3b41871c3afaaabd53a93902e44a96dcb7651bce7e6143d81cb695fea8b94aa32c09ec030dd9ac4",
+    "Name": "matter_g2_multiexp_76",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000703481cf48efe78fe8dad34184edd1765a1d01846de74a45b43d4721bf1af116c229f969868b0e6e851f22bdfb0451300000000000000000000000000000000063d316d495b1e82380c5b73bd61ce7f2159e7714c50e374e8a91dd56731dbe03a3378bf8afeccaba5fda73b4c2dd166000000000000000000000000000000001012cb2f6578065c93aeb673f447ce95fb42927ef9d12e07968ec04b6a604d785944620043dee5de4de33d59e67d64f20000000000000000000000000000000018cc7cfc360801ecc420d77ee171fb3eac3be0cf26b3f36a6cfb7c6adae7bb74c18071daed8fc56b8fa639ea138267928c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd10000000000000000000000000000000011e0dfc437a65c6fe37bb9e554b5138f68a3c52816807bdf7d98f13cfaf86b37e9669f4e0db1b7865d910a309f16cc200000000000000000000000000000000006f2323e01591a7db1d3c7fa1a2ce4540cbe0396cc55baa3a3e13650a6f6b926a7cde0eebb45d359edd52137152fe360000000000000000000000000000000000066bfec8df4ab5f5f5eb369b34e8e22fe32abfc00ac58b68f2d3841248fe5843d6d29ad012249fb9ee851e40b940dc2000000000000000000000000000000000f4ea977d9249bc05dafb682a863ed17f7fba0a06c4a13cdf5a836748664183272eed96bc4109bc5beff61c5469e221f8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000fa3e3e77112774fd6d6b560ff88cc92ef8d009675d0ed65705398ce727cfe786684da50bcdfaffae97d19bdaddd81c00000000000000000000000000000000019e98284b8b9f53faf3b73902cc322dd80fc330dcaff2a7fceb55db6a4b0f7f667297f5e4650c797ee337985dc6b54310000000000000000000000000000000004e30acf2ba66d842575c8679caec607fd090f0aa2350464f3b6eef22e2b9a1d9d5fabb0f3909f1c19f6b8f27c53b040000000000000000000000000000000000ad76b86e32f84ad74bac68909da0c271571606e071b13bd92e387a8a16a1c4002c5a5e94ecaa1e8d2d6e051e19a45c763016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc100000000000000000000000000000000019bd07479b234bba974ca2f39b317d5f4be33afef66c1d69e53c44cb5e44c679775ba141f82486424110d186561777f00000000000000000000000000000000130002de0d453abe9052a5f70a9d55de74939d1c8e6ad5871a669a867861b1359322eb98539f4a21597d806aeca62d18000000000000000000000000000000000b2f0c649fdb37216c10762f510c3bb4c789dbd29c4f9a8ff39f74ed1a96609c60473a50f5ce3f6535e4af0f2f0a150c000000000000000000000000000000000893b9af710787361a32fbd19c380161c9a214a1bcf3761563424b8546f6068ba650d9caff3e42be63ebf4b6afa2de516c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000019805c0de5e232632228e2772dc79712e3d863bd6fe56932b29ee99870d2ce5eaf90c73632d1dcddc093e9b6b5b0f1d000000000000000000000000000000000405d77f4b3c44f99a956ef375879e62df033aa408127e0fee013b74675a8c7d999c6abd30f459693086bfdb326d67af00000000000000000000000000000000110f2c231998aca3d76e40055a05feb37eba76cdd10106719f2300f57906424d7eb6d9f85115b78b7371ee60e26d02b5000000000000000000000000000000000593a4721a67caa7cbbe1566611a1d48532c68adcdbb67f362c9ec21e08aaddf6b5e09a9a96df9a89bc25f11665f3a36aaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d7100000000000000000000000000000000151758f1921743d116f1c4adfc09cb68b3ff911329e2f6d6bcd04beb9c109568c796f328e1f04381a995fe89aebbc49c000000000000000000000000000000001388c73b1db46bdbe70540c99db46b730e157a23afea97648d73f9d5f7e8b073ed665eed9e9e2500152c87715f1c4d4c000000000000000000000000000000000284ad228867ed14ade5a327ed951ca50c87f0a669e59b7a75d17feb54bc5d685245448a912590179db1e84f1eed1e5b0000000000000000000000000000000017d3da7c167733dd88f1c39315e47cc80c3310cc431989d4cc50ddb22e9fa481c5dc02d94dbf806c4c8da16ba5b24905f6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd490000000000000000000000000000000016064871cb68f748939a839800afbb018fd5836914a2b76c51818e764628a76817c7ea329e6b2f9de653c8162a2a2e0c00000000000000000000000000000000082fa03cda4c617a780caaecd7c859c5251b56b61f70fb3ea8c05b4c11c030adb8a96d715c1325ef3dce9b20e8065b6700000000000000000000000000000000174a245baedb7e1bf1368212620b850151be41ebb00c977d85da499223c207ab6f1a1d94a51aa9e90d07764ec3615b3a000000000000000000000000000000000df5b81cf4b008480775ff3d7644f546a60382e92a98b03deaa4a20f831e69e14a893ffa731c4ae9ee237d747149a9080cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c64700000000000000000000000000000000019d36b8dae5e1083e687743f7494b7f9dd0923024df81e2f83c78743e227ffce588a16630201b9909daa6c9207b5f430000000000000000000000000000000015659059cfee7850e1cf0e49abeef2fe5837cd128742e62de20dc734f1bba343aee1c9f1a59d920a0519995561891fdc00000000000000000000000000000000102b7221257c40d9adabd0db3ec9f6348487187ea1110773fcb2ac5ce210dfed167a4d15e605e9d9e666fd092147a1c7000000000000000000000000000000001402ff9770d27d2d82efa6abe4a181e3c1d944e97a06f670d9e46b24f9900fb4a838b32e17482f25be9b6f3240870c02ea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000001745ea52686f87a39fa42ddb5b0f69368db3757394fa7a1a93eb20c398c26415c8a7edeec7334df5b15345d6174126b0000000000000000000000000000000012b580e6fd228f087c7584cd95826e56d1c074cf16c35286c45d2067a362529d241c1e24fd22cc9727d423551de1a1f700000000000000000000000000000000104b46c42a706c61610f8c0434894c7cb9ef878cd0234f8aec0825cbb8297bed3de349e7f6037dd19a159103ca7753390000000000000000000000000000000010b781b3cbe6f415af15e37be7c60dc6703e6e79618cb3d8d9a5ea3b17c00822aef1eddacad66a646c009dac887bb070caf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea30000000000000000000000000000000004b172c360fca555e65860c7a294960f506b562e012ddebad5803bc3f4b93159c16cedb73f339def9cd1beaa0912c93c000000000000000000000000000000000242e37775a042ccf59e99da667c67fc49e80e54a1b438a74fe306d668059ab4dc7d9e457adb45e1f91b3e6bef0a130f00000000000000000000000000000000186eb83ce3abe66b8760dcc0d375eb783d175b0b2f36cc08793d8a86cf76b7618b826f50c6b02ed586394abe4efec2f1000000000000000000000000000000000bf780324df1cc5de325a796f1fde367eb52dac76c0632915dfcaf01f5acd6ae890dbfc2e505bafeba7fed8fd63018c2ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc000000000000000000000000000000001231b52c8a081add6e5c250caeb9467335933c2ed66826e4ab44561eda9259acf926f22ad0df8e8756aa51279d12bc9600000000000000000000000000000000051c46bb04d3e035d324de681c772e4561cecc6a5bc4ef0a0cea56618e09b3f39f5085e208229e50164bcdcd4abdefd2000000000000000000000000000000000ad7ee610398935a02c3a7139185409d7fd4681ebb74a239e15d1c092ea913016d3f585d8224cb1d109ac111660a94aa000000000000000000000000000000000903bb16efb052b99e9c46f3478b4acf800a173b35b0079d7728fc25c9415c8b05ad520f31e6a3c867245f64355cbc080d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001788efb21597aaac29b7bcb9ad6cecb89267c757cfcd8893c32fb13c0f3e1af7fcccb9573dcffe8d9220292b7861cac90000000000000000000000000000000015f85d3686148ad62d7fecb71920981117cb8759ab249d0ceb45f9e4687914536a1eb16ccd0e185d1352a8d2b4a8ee7a0000000000000000000000000000000015d8ed94c0415ee0f7c9854841bac5821253bb2ed4d86a61f494cbfbd61614983e4279fb17802ca68aba4a0302ec1d8a000000000000000000000000000000000f950a4c8aa18f4605e1252c367dba1e170ad00376a8560c2fccfa7d5487b0d1d5885cec16a0a17d81b5a584d473853f7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b82000000000000000000000000000000000da25ed9154121205ab6843f603a38a6892887d2725f16ff87a5218586c6139188f46da5a42b5e05982468e8115713ce0000000000000000000000000000000013c13ffbed4a60bcb8659013b022012ef3a4400f506d65aff7ffb1bd5a9a5e030a298e417cc1ec8ee7ebc06455dbe61b00000000000000000000000000000000132d83bd141c434326d4772de7f8772c30a6456de7adee7de66a04bece4c0d20bae5526c8eca5af5ef2eebd72c90d54d00000000000000000000000000000000131355c5e359081dc86e0b15c8aedb4f2016b41e8428051f5132258eaf4392fdb63a91452dc56aca20b7ad3263ebc8c92a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab7190000000000000000000000000000000012dbe1327162e4176b4988cec23df0c1b0075d0dc51ea8afbbf98f00891511d9023cf7538c5705d59b6d6ddcc90b101d00000000000000000000000000000000036c12c7f7627b6d6fcba9a303248c38d784a3d1d0ff02e550565efbab68c5116e9a88faaaf09bc72bcc3358e9dad0ee000000000000000000000000000000001578ffb68cf12dc9a5ae6fb5d822324cec9e3f576ce08d45e24fec9203d36a6461c5b8ea6ac50233e8893b07ea6e71e00000000000000000000000000000000015cdb43c82b20b8ab270b942b9e625ada9283962a7ce95eae156aa4355e1123ff87ddb1cc85b2a94bf36102ccbec33fb2bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da9600000000000000000000000000000000017041e16975850e6445c7b4896955eb5eab383ad3c3031aef04e8fdfb65a6d52c9e647330bfbb0f0eab630c9f9ef7a12000000000000000000000000000000000b62757ccfb913ac4264692053f766e142697f598a3fe26e998119b63a3abc7fee03db32a8af36aa21181fe9ea89d12c0000000000000000000000000000000006bbb842a889d7ff3c1eb5e0b16e3a921a11d28a251c488a8a17a29edd93672fd15974a7e972a34c47283c583cf2d29b000000000000000000000000000000000e94e685fb1751f8720b8af79aec7b245ae8daa195f11f485f2c0c5dd68cf39eef848a402ce2342a6b3398cc7879c6010f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed00000000000000000000000000000000100d3fee47ae6c8c7981c8cc615870924fbcb34c2ed817d6862e2e6d0b4612222a4c8332c7d51b58ec59df6832139e1d0000000000000000000000000000000017270fa71c34ec84043ef64c5dfd61614b5b3bd99204f9f70994d71498219818a5f16843c67c668b06aa5ad3a6ba8a0a00000000000000000000000000000000057948c0ebd14664bf33fb282e200fa0e641764a353e8347586465dab0c79ca2caffbdc2c6d60b2d7c8cb6b088bd16fc0000000000000000000000000000000012747eb070f2de18f517648395109bc08b4af3f04d98e23eb6b516199b4eefc5df7d57baec736987139c7b03b573941f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e",
+    "Expected": "00000000000000000000000000000000128c6c0283ea35c10330502d6aa849a909df6b8dd927a24f08072135b8e21e40c495c42e742160363772569189d73ef40000000000000000000000000000000016d78dba1e0feeab46f8cd38681a9c2f6490ecc3a6e79b31caead256611d133090a4eaed9691a87b66dd1c2ee50d5f470000000000000000000000000000000016de93e176c950975abcbc692384996315a98065db6d6a6214472e5a338e291b36abbcdea1b8be6162fe578acd669abf000000000000000000000000000000000d7155e239e9b15ab64a538b0a0bd53936df4ebdc3ec9b0b1d494e4df780bd014425759e9743c9b262cf48cda01e945a",
+    "Name": "matter_g2_multiexp_77",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a653e0c24eee1cdf8e3652809de0cd159f2c541981a4f43936e7d41c0f97ffe2f1e1e0d1032f0970023f1d27241a16a0000000000000000000000000000000012d1d8d2f96db0e5f97be096c961e3b90ef3d88492fb756894979d2e8104791a5b9a43888043ce9e543691f15d2fdb650000000000000000000000000000000006ffb94dc3c2d07830498260ebe4641b2cb64df61cebfffaf2d4ab5b6ba92cd75de209e8d7915ee744c4db5352ff239d0000000000000000000000000000000011f25722cf9db77ef8adb9caa250175e12412e6350b494395a86c31e1f5dee6c89cc6603f1dfd08a70344cdc44aa0c2df3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d80000000000000000000000000000000006177a74e3551770e7d906222590108bae7b97a5dd3bdd2344fc12e7005f2c1a188ab9dffe68f5ffb0cc36294106f15800000000000000000000000000000000041b140c46868767119a6ebb58562570732198854c92bcc070f2a8d9be91282a70c5ab99e75cc9e5064ed628aa5c59de000000000000000000000000000000000f318ee33fccf455e46add44922bb6e99afd4354bbc79d7550f8d12d3de4f75e5ddf4e62624b116f91aaa80a148adaf9000000000000000000000000000000000fe012bf88e152eb62c0c906dccba469abe591687573a59d3debe747b7d895e4b0755f16e67fa9193a2fd338c04d243a4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000b26c6e0106d4efbacf2dd0d15df17209b1306f388f493c096429c031bc4a6a535b64cb02b400433f948fd6004df2fa200000000000000000000000000000000061853cf1a32fdf4c370cd413754ea584d3722a08d58575075a7371e57a7bdef95386ed72f91c4893377f6b551dd6b1d000000000000000000000000000000000ebf17e60718c8563a1029ba035dbbba75e7191b4339d5d33f64bb35f34866081f26f4815e01b02e8330e7b7e9c428cd0000000000000000000000000000000008ce40f92efb5c5be48c814018fbbe45f1be45f5b607a6600cecd50d8f791de7d91939ab61204c2a1337c3f21b2c9d26518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af0700000000000000000000000000000000123ef52cc44f36326b33234ab3348893bc722bac3674e43385b201f372fe4ea3569d69d4d561e26f8ea903e017d7376a0000000000000000000000000000000005b1707ef61ff9acb9e8b4dd6922daaaa2d8a7558cb55b1b9b96eb6d57c23f50a7955763c9b5ef04f52b09be8d55f4b50000000000000000000000000000000015b6e35d14da61e7a7fcbcb0dddaf0071d8d2d89f7179f44851947a2b9b0535d6fa86b5cae9713a73bbed909a4c6deaa0000000000000000000000000000000013463e135b1fd460cf042dcd0226e229d60cc2beccd8a1832df241e65a644159722a14297c0033eb499e5890f0caff1e5561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c386551000000000000000000000000000000000fbdf4a533d355e232723fbc97352fc5d7d3d199934883a61a9ea116830bdf9e40d423256225d9a3458134332ef6e817000000000000000000000000000000001195f0ad227941c5e383c48f546be34762d158e6cee585650b6ee987f7b98e802f678abac6646832b30b6e12e90948cb000000000000000000000000000000001820d5fbb5a62140c6e8cd105a70fc2f1ed84e254c839deadae5eadbb75e1c33a07ad12ee92900f55478e91958a3147a0000000000000000000000000000000013849bdcae33fad27f16e91c6d46b9678a00491e3d70a8db905db4b1d2c6f02a29392b5b77c1472052d6f4d49f14a16737c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e2600000000000000000000000000000000188fe1e394b567d71099fa13b5c8a5891636d83b6b8a08f410b080658a0663deaae4dca1afe8b9023b5e8e573c752c92000000000000000000000000000000000f66c65dab8e1b2912fd5285a4c87821888532f5107075cdfedacc4d7f75c6a74b4828d0b4c3a2c0ed94576654a7047d0000000000000000000000000000000016af44a6df79c8c9b6f1d8aeca24e024c454d7b94c9ed386858dd35c4158cddcad1207f9fc3ac9e3b748c2314f875dac000000000000000000000000000000000315e5e4f78e9fcb93aac78025e95b8bf82ce4c840cf565e0a868b0aac22950d62f7becbf8039a16ca3ea66a7498327d981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de4000000000000000000000000000000000f9a61dd1b3034b8cd7408b0a44c8d02f4fe0e87778d5d34f5e884ccc9e2d51eca6b6060b46b66843e8247b3c794e19d0000000000000000000000000000000005c47fa7799a0fffcafbbe4694dfe8d0f47b60f712d6319e9a56ac459a636460e700e2af80f9c688208978aec7c413af000000000000000000000000000000000ab1c55fe2207865ecf12e372a341c776d24c08dba10702fce1cd2c01eda314852d81d0ccf1c3423c2a12e8960677f060000000000000000000000000000000014f8a1964aa3240d788ea40bb51abc50fae2736a34120ca9585fb2d5bba4e5cfa201c83be1e00ecd1c46fcb2ebb4eb809913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d30000000000000000000000000000000006441fcaf5e68b10e7e511a95e56b9613453ec6468bb126c5eb12f204c9681c69b5c296320f92a6fbb0b848f8ab5fcd1000000000000000000000000000000000141de16aeca0a2f991e9fca4b6ce8fbab3d66ee3ee4dffb0124384a7d4ba51864a53e005fd34516c92ecab33165944a0000000000000000000000000000000008543656b5495bdb726109cd98fa18e405648fa88cbe2e5fea5380b7d0ecb207f0343dc7888b9945e55156977336226b000000000000000000000000000000000b53d4e392f304225b1ef363a3528daca1d3a6ad64ee99d58491863ea432a29cde5edd4f390de45a567cf32112ca5929188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe0000000000000000000000000000000002e0c55a43078df575efb2c99b27c5632dd1c08bf28b6c0558081a78de58e4258d1b57d94ec6fa157add04aee06e7b6e0000000000000000000000000000000006d3f4f0791431a56fb386f4bb8e6744cd19b10bd0f2e65e927371ab488d3735e3b83400ddb25ef9d740a8620821b0ab0000000000000000000000000000000011e9cdfec8a8f8eba0de6809485911711149ca0ebd0cecc033e2e5ddfc195fa7de671a686edd2f56e5f7da7328dfbec000000000000000000000000000000000171f188afd5d9568cc5648aefb65cd715c0293344b9aceac1031f10b4a1e4b9fa2ab11114bd58f28aaa58c10ee0eeac65525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a000000000000000000000000000000001651d9bddf61e5e54f86609c2479513ae84b000ad7defd840d9619a8361922dde81c999d0e95d8a3044c46fe0360c2030000000000000000000000000000000014a68c248808e826a3bb50f3c1c1438483cbb9da8dd67a0c9633a47f733e6aa7deb4a13aaebcd50de6e8e8f00000424a0000000000000000000000000000000010c8a94b9e0ec9965f6c8bd0c4279102ab682a14fc3c22e9640d68f240ccecfead9a2c6e69f7c8ed369cce7e2da50d5000000000000000000000000000000000181493e8137fcfae203e1b45189fb828dc9eb56887c89aaf9aad0380fffada423f0ab48ed068ba4e67a2b01a16abbfe55ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc57000000000000000000000000000000000e3e33fa4d85a35e8707419ca6d4fb6a61ee6b07ce152adfbaf6b5f1d7ccc253b59f91e4545848b3570bfaa804ad9767000000000000000000000000000000000c923a4de074dce3ccc94698bf6445af5847c0e6f22f225c589f744ec83ed0810913af2a6d04bd55200ffc738b31b01200000000000000000000000000000000186961ed1c6039476eb6f13bf1b5f6627b3b017ece57a4a5f33db8ef12347fd507398a421932d3d2a1d009f65d06e42c0000000000000000000000000000000011e10ae0139f95a2f1144810894fb98f6e5e86ce67877b949a2a7134c446dfe53c23dfbfd12919b24975f26eafa249216ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb942800000000000000000000000000000000029265ecf3c81aab289c98d9cdb917749ceef56e2e4d59de2d6c83907f394ddd1cce9d093a20206c2c1c215493c41c49000000000000000000000000000000000986ad139381e4dbabd6beba179600e1c782f436f84a7bd58cdd96a22269f1d937f88f25059214fe2a781ac519aa621d0000000000000000000000000000000019e296d5b17f78b3ffbdaa2ef5228fa9dd65abdf6b2c5b0f99a708c4721797b3b156b8df98a5a879f17f095548555da7000000000000000000000000000000000349677d4719445d5525cd65e2338463d232eb75721ca51c48fe52d0fbd299ddbd6cbc12546f056bf212d5700c3c4100854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000016f5d5eb3fc3ff178843a7d21d3dd628bda120321ae44206d88f07ac001651428e0da95d3f0676e1bbb969a300406ce000000000000000000000000000000000029121c539ef1d7b9888497a362fda2f8402adf10a1bee11b53cf3dfcc6f99d5026bc386f86a2eecd0c276494878104f000000000000000000000000000000001320a402922f2a0bb287464854be6782046dd9dae4c0cd94efcb8ad8e0f37b7889bc97a3c8b4d3b3670a6924c8ee23ec00000000000000000000000000000000101fa8bb2c90b755bfba9cd7a98790b7bea2ede4c806fbd9f2006d10cf87c44172d4ba46ea40fb75afbbaa2abc3b6e9d7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd0000000000000000000000000000000003285cb099b04b6acd333c7ac76c839b6c09388792d5fa1f2af0821e49dfbf40a06803c4cca92512bb76d073129a48a00000000000000000000000000000000005b2fdbb25381b3b67814bf6cc0a4cc17271416d16ee369b723b1711d968c355b755183f0bce519709723250515ba32a0000000000000000000000000000000002c7062ba4f642b95e028a364b0698b801f48af3c336fa09d13d83ec6cff10d210b55b23cad1d999889c83df7d1ab7e10000000000000000000000000000000012cdfdc10bf46097083294259754453e084010f7ee928cf540d44c80aa4f601247223a318700bc24114e7603922d15ae923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f54760000000000000000000000000000000000669d760352e34a407aef8e141fcaa9468257b12ec08ec218f49f0769f3acd5068c6dc9d251a1b2af02a2d091f8ad0000000000000000000000000000000000064a7b4026ee3115cb730e56c4b9bf3e1527dd0f0ac6015f43d30a2f3d8d8c2659cf50247e70ca3c93d7e0a404d9faaa000000000000000000000000000000000979ca2e81663ed61486c1f841c19d83549388d798da72feda82283406d4964bc9991f876a6032382c35b605441ee7da0000000000000000000000000000000008d92cf77b44c516c243f3e6a8a8d3f9d3d7405820ab972338f700de1dd9a66d33b4a70540a30f630aa81fe1cb5bf057e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf900000000000000000000000000000000198831a40fec54a210a63f5e00b132bb1eca6408335b85a75e28be6a111beea3b99d9f2fe5091ab0eba0f082c201c14d000000000000000000000000000000000fe457f8d215f390000efbb7fe7193ba02a2ef78e9bff6539995f01604fdca9fa3c010276afb90215890f5a5df3ae21500000000000000000000000000000000076771823180422495d89c301443a9d1fa141716e5e27205b8cb6b461a3ded7e6f196c3976cd6ad56b2e6ebb6b3a70860000000000000000000000000000000007f666efc677f6f767828e1291bde0ba0ca445ddb2d69d5d2fa090ca49e697ce4e00f55d2b706454be6d68f012d76efbb6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa",
+    "Expected": "000000000000000000000000000000000e1268a5e2f654c4038647a910e6cb4bab1d6ca3562ad4a9ac18444c8b8a3fdfbd35acf37f9203041fd587a5175ce86d0000000000000000000000000000000005e701a8ddd15ecb0231b7625f7868c81467c140b2617e60440a9b348a192e5248b1b3c087545cfb6d173fafe48d32f600000000000000000000000000000000071327f52b1325bb664d32c513fb12afb96dd8da23dd91bc4c3e8ae5f97d6bf673a5d03bb8bdeb6da3144dedac200dbd000000000000000000000000000000001852b86d3ef45aaeb691f454a345ed61104cecf0f444e4d841f2bc0402ea1291ef056eddb3fc3929ef4623f31016c3b5",
+    "Name": "matter_g2_multiexp_78",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fcd3f253d018ef09a7c6e8f36662ab4190867a197e0c42a0b425dfb5fe61d57596ada28dde0b093676ce15d03406d20000000000000000000000000000000000df00598337060d603607f3b8dd16f277ce1882a2e9ced48e1944662323efc29b33c807653f31583a5d2198426019ba70000000000000000000000000000000009876c81a76986435d34c6d44d51cf1016c19ceed2432ef1e68decd64da2e31e42372c1a41a514b0eac0ac103ab6f43800000000000000000000000000000000121cf298ff8f610c64ca4a887c52cbe940333506ef2afecffe266b5b585ff737395e2c64adc23b5bd232250e67c7a62613ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff566000000000000000000000000000000001885d5cdc3e0e0c8cffa7519e6914e5d85962d07633970c4174ae4587853f13970a1f5d7ccba97458b9b5046847ad29800000000000000000000000000000000105b7c0ba96d5ce32d7447351ded3e3f491a0e741e921447b91f22a23b64c2d749055a0593e5b47f0ff7815e1a4c9943000000000000000000000000000000000cb88fc10c94642ae7e1d7275bbfd51a2d40e9b29f3d51a1ceda577beeb131eae4b17418f9f358d47b4b9c9ca4960a3b00000000000000000000000000000000131a3e080b1d4e936d97d255b07b09a6210b5fe6900da87b5cc595a72de2b6ddb01809e2dc63ad460a2926dd8d3b3b2ebcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a00000000000000000000000000000000066b31c0bc4b3b9fe420dc095d551903a2859556d86e210c96480f1d31d449d85ea292e2432babdb71c151c7b215cd6b0000000000000000000000000000000019d79a60793957745077f9233aee7a4f096515eefa7c49473f09bbc73fa0ee13a2a30a08bd7f3bc1d5c412d671fc37ff00000000000000000000000000000000006882160e4fa8ae2c2d48ae389d8f023e2775adb7a815edeba13728b8f6b343c45788c8e9116445e9989e01eb43e1500000000000000000000000000000000000ce53ab2d81ebaf4a85b3e12a6175ad7fb6cfbae207a69a0fe2195ab916fcb582b097f09d9fc565b837925f68855c4b59f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000a30d335c035afe459dc262fb1bd24dc0bafbc08fae0bed47e4e204280eb96595fada9c4332df1218748921bfb1274c7000000000000000000000000000000000e37eb189560211d6fe56faa3b6e710878a21907fdc1a9f8becabca290c24b8831e28ebb48d06bd822300fd09b4d103100000000000000000000000000000000104842b88b9df6a7b8243494eb11eb62c89d1ccbde9f55fe221c2366d6bc9149178f177628c6fe7c7661318640295e570000000000000000000000000000000011df8599d72b85ade11261076e02c036be5dfa3b6fab4ff72ed7413a879c0a0742be6c36a32d0829a4e3171b0341c6a3995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c0000000000000000000000000000000019f6634435be45b099cc739fe5c2dfa01f61fd2d466d5ea464053e2d5acf2e0e9448b1bb7770b5ad426f8a872c5764400000000000000000000000000000000002bbd52efecb10b3bb6f8bd04a5751042d8598cc34e2837184cea2b5953ec125dee871d1f2f57ebc84849e3a7ee5abe2000000000000000000000000000000001962b716342df9c13c21d89ab5b8c4c0ca191440fa709627e0f240a7ba518f4c95adfc5973b6ed0af591bb54bd00937f00000000000000000000000000000000089eec676276c52bfbb2593ef0362c12a5f3c1a0566d5aa862f5f5ba1580f4dadb36c15fdcf0c3910ee14487ff146c8997b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a5000000000000000000000000000000001556d081a489eba4fbb0c20e22b8cab432a9f6ff459ab9b0e7ceacbbd46c8e24a2ee70151b019a1b4bfe47d934afede30000000000000000000000000000000008fdd7391113e8d9865ef48b60acf921b17c50744e6ad62fa24abaae54836b3d59a7441371bdfdcdb251d252a43aed7b000000000000000000000000000000000cc66cdb1fe32beb91b05922f3920060e7a95467381d62f2f036e6268af4128c9516780ea53e873993744ce932b901f100000000000000000000000000000000151f94dec958859ecaeb810c4b1cc7a707d0e1671cd4a1e3c811910bc8b95c6c944167dd280c7fed22f92ce7650beef998115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd178100000000000000000000000000000000004f88568c7ede48d7476175f1d2e7ded4312c24934f0d47794705621f8aa8a5072b86cc41e187f4aeeb49bff17a4c9d000000000000000000000000000000000ca6c579e86a68b4041150fbbc36da744d359028993681c34e66c537eb8a0a0d55aeb9b8da7fecb844104dabeb507805000000000000000000000000000000000fec63c57d3d3ca98cd1735b2f59217e163ca53b07b4fabc4415b98377d87e75f0fcc9b51c99a57ff61ca8d0016a206d000000000000000000000000000000000940e9f93f3ccbe74c7be93236a2c440b213a014ed51cb57fa053495c3d6f6c8edc08ba8e10be26e5faa898162d67fe327370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf500000000000000000000000000000000055e4dd9da22201b5eb64e3b9eff2eab614c48450424491a85c18e05f50659b88e862490edd11ff980b06696b60c35b00000000000000000000000000000000018fab38f58d3d541666bc29b9e94cb3940f1794b2aa851d079b9aaa1cf742b07cd6dc7c985c7e4d7d3fe683bb15d618e000000000000000000000000000000000534de5e1c1181e951b437fd17993e995fd4aa2f6b28fc3612cd4db615de742e12d66c03b9ced538c1c7cde27752c190000000000000000000000000000000000aa8580f1da71f2ae9ec26f3b6466813a40ba5bd3f89ed0d42695d420032540194617fcc2f13e36219fc0cc3886a69c36bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f710000000000000000000000000000000001804ed7677fa3842bdc3eba708bf4fb7f7d4eaf2f1a46193c861595f64196398622df4358b9526f33663138b24fef1310000000000000000000000000000000011fdd7e1d0c5adfbbbaa69ce63c7c54525091289e4dfdfb3de772a8d5a958581cc23933deadcb8856540e2d0dc564dbc0000000000000000000000000000000013fcf17235506fb194e3adaab881c7aba4b87e5aef739e0547b858410e3cdbff0dab1980b1b30a7d03d617179ae545c900000000000000000000000000000000004eed0ca479cc458231ff969ebdd4e33732953e9f5610d78d4753b99c5f8cf73c742387b8e71b9be074fcc67acd71cf6b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d0000000000000000000000000000000001082b7796d35e387df689bcdda6e0316d343dc907822d1a873adea050374962b164ed27cea0e1b834997f8274e4c5438000000000000000000000000000000000b1905979a90c7a61f4ee2cf3a9f4d6ed4c724c9e216981b8ec34fb9b528018d237771ad620020efc2c3cb104df667cd000000000000000000000000000000000752663e72390108288ef4de3c3ea409c74e7051505b12083c41a2e8937eaadbd8cd61f96f7991722226fdd02dd8d252000000000000000000000000000000000f8e4eb7a3c78b8040a115c42b5d2fc69405f8334e948b8553f444dfef29bf3920892da431cd8394cf61f24e356e95694f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce11070000000000000000000000000000000010884516bb9916084709351ed8768c6105fa451e08d5acb233511254ddbf4e72baf9c43b56b4d7dd129a38f5b34ee5f0000000000000000000000000000000000228fc5fffef746419cc69abb17cdc63ded44892b8c5d02f0c72bc8506a61d15a74ec4ea0e1d78f555ddec07f418539500000000000000000000000000000000048a4192c204b7441e871076d91d4f610c347c2d71cf495ffcb2e2ab808a8c1a549eae96e657d756d9a3b94db2892a2f0000000000000000000000000000000017a94d2472df89104ed96e24d166f922bb852b5ad80f80188fce65b08d39cc3ecf94991c6bec5dc12f9337e7c087db2f8b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa3000000000000000000000000000000000f6293fb0e19ec85f43a1a02df9f59ad4fb0e49b16a216ce097b8ec59e781fdf176360d8492e8b77674ae2c0ddb1da70000000000000000000000000000000000e354d09aad68fce6cde40c787ba1e4488999d5b9f3fec25c9994b56bcccaaa746c958bd16ba271485f461b0d4e983200000000000000000000000000000000014fca0851b0bfdf2c69fb346f23b46135d2b7914bb49e297a0c1304d8c2851ff6bd0a0bb364938dd44680fe86cfe12e300000000000000000000000000000000164e23a53103dfa332e5ae09c7c898b95773c20f019d8b794a6b49594040e2e090db6a8047c943885dca95188e89a63b30e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b200000000000000000000000000000000146d37241ce4f71017e4423dd0bf907a12c1364ae9fc6dfe535c25e5e99e03ce157cbba2675829b396a69f92668107280000000000000000000000000000000000d5a992f5357615f436d95fa516212812f6811dd1f1921ba4129e84e3d487b6c97520995d8a65f6771dbba9d150c7ab0000000000000000000000000000000007b01f86574a9cb7eb3b9a19b6040055a5c11b13e7071078d16b9ad71f714ed28ad25db9511964b156ee34db22385cdf00000000000000000000000000000000154c29c6e2b21a75b14159b183e625c98a04be1850b22d314225e94b313619f641ead73130c1d6feb85abd8c9e172f6323a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe00000000000000000000000000000000075be2703b8416fa07a7cb6ae8841dcab1e36b0ea24231dba617a2fed3bebf8d952d31f68c149dd17eed136fe37b01880000000000000000000000000000000001156563f1401b731cc23c4be59e69b0e6a0827df4889cd9ef9e11310f679c1603a0d9c9679c29b8dab75ae51f49bfe3000000000000000000000000000000000663faacfaa92fbc095a5dd6b1f2dd141e248f84eff1716ee71bdffd4d28ef1f4c88828e3457e8ebf0daba1416d2d6070000000000000000000000000000000018f2871f5897aad9ff6ac45a9c0e78be8f312f07af5f1dab2bc4705558070abf367f1782af896288a7754da82bf1a5141ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000001618a284286899f501f46c4761c93b68bc8ab3157144e4013e242e1678cba20a2d978ab53b4b43145dd6062748df541000000000000000000000000000000000c25da737368775e41ddcd9c64cf99a824afacb1d404f1ef46ec7fe4ffd89673648c5207551914e6e0d12c57e7d7682c00000000000000000000000000000000097ff49c4872e2da1f6c24fd6dd4667f0bef4eb30fc197d13e8b66adc425e39841dea011d79e4d775106a19ea1978f4c00000000000000000000000000000000147426b7d9b0bdc2be051d8f6cc4249014e1bbc2369bc32eca94684483f50ced2c07be6a320effddcc1ed5cae455fc92212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000000cf68f7ab056c4689af95b361ee3e3b1c1c48f18b5aa655cce1a2be217010814b3f07dedf6f9a7b835cb13e2afd7136000000000000000000000000000000000dd6d0fb94048dab34410dba4e682f020ed54a655099fbb6f6e94a31511960f0447d7e94143eea88195291b225d11246000000000000000000000000000000001864c6ad3f2f794239a179647d68734e23b3520b79952bda20acf2f5afe1b76bc18e35b852d35a5cf3b02a3ce86f640700000000000000000000000000000000015ea24562d7bc59d813b77b2a4943f9e98842b5a41c0c7026077a02ddfd3d5fecf352d4399f507fb12ada4ac495ddece9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37",
+    "Expected": "00000000000000000000000000000000080f0e50f90e001a442965ba7900997fcc89246742590b99add6d90428d3b61516664654bc8fb423f701e85a342a668100000000000000000000000000000000003fa9e84ddd754047649b7cfcf5bd78852abb298b3bbe6575c4c7dbc2e7595499a9f42f379a2463aa74f29e5c73a9040000000000000000000000000000000009e72d3c418726f6400b8cd8b9b649005f3b25ade40cd6f77a0c3cbdbef461e917d4453c9e07ded45301d21df4ec44db0000000000000000000000000000000015a06cac223217602ccfba4f4586cb184994bf08b324bf977dbb3884c394aed0622da7dcf5712970554d73b18e2733c5",
+    "Name": "matter_g2_multiexp_79",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007340f432a5cd5aff1a1d98c6ea1c94be24de2d15a4e112925586c30979e23a5db93643308d3299e370b1f26bdd09eac00000000000000000000000000000000155027caae88381a60af71b2fa770e58efccfbb7642f5ef6b1591bf77e415eb117ab564aff8d9ebcd576f813b793ad2c000000000000000000000000000000000f604238d1b28f010ce8e45f2fe61d3ea20b902a4debbabcd54ce0ecd44a9540fe2bfe847178656fef0a5fd7e6d012b3000000000000000000000000000000000d7f503ede395dfa5682aadedc98bfe28d3fbfb52f42ecabc9eebc0e0a6616d3671604709f28255f50b62bee641d2711f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000176e1f9eac4dab0d253c0ff41b7600437b53a5ac5278d544a9620648e0bc4dc56aff0bda973fd1338f77fa174d8b13b90000000000000000000000000000000012919a18343cc166e2dfb92ff07bbf838779ef0479985bb85b3b82f9d0632b3f7a19d387f725a21729a77c58dd4d1d1d0000000000000000000000000000000017eb269ed75fe0403021ce70505bb60a711c91c551931605bb2a0773fafa07aeb47cdda382c0aa64f40f5e6e0e6bc77d000000000000000000000000000000000bed8ca999a4691646124a140fcc17dec02b74bd28b599c807abcaaff13bff65aff3892897652cd33b4ba5e4cc0198a9c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f000000000000000000000000000000001344d6902f5fdbb59a4c975847db0191beac284eb17cd92360e59f42cd7796cf2aa282bbd4cb074c4ee10b489ee3f2f60000000000000000000000000000000002158eb3429d0532792532fcceecc404e95a879be68b3685ae94016ca3762438b3320553ab6d5fbda3a0b615a04d996f00000000000000000000000000000000118f6fd8f60edf7088a0b4b49338bfcfc9c38be230460d7516f317b27c07600f504c8cc87acb0c95515c3acdc1b125ce0000000000000000000000000000000014eb422d44ec6931ac9860a6a017a907e8ed76de91bb7557e818dbacb19fb51457a1f45cca91f1d1d75a3567a3375b5cf2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a95853000000000000000000000000000000000f2c72c53fdb1b0cd13a1f20407c64c46e4a0e461778b0e2d48c4f20be7c655c639b38f758fa9199b8395f706df10e7a0000000000000000000000000000000016e6c75cdfbc20c5dbc2dbd1caa66be92911264d407ce3c689ef3ae1dca44dffacb4c0d8a78ac959e47ac5c454f607bc0000000000000000000000000000000011c5d80d52e864b0a46fb48488f497fb85f51ac040c77b1d01336860b972858c0a6e59914112f6cd6c1612c604d26f56000000000000000000000000000000001136aa7eb63d6f85d665d0539975a9a51a9a3f5bd8731910c32130b1ec8b07c39eb42e4f61e7d22bed933d9fce1810581022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b000000000000000000000000000000001306612f5119d33f177b8804443d14d04c8e059e28f63aa10ac6a1b25975327f378d5d24f0236e05849f07e99af93ae20000000000000000000000000000000017340f8887292264d498f84fce4af83573aa6cf1d57d99d364f2b84e1734fa4f9a1e07ddc81a2135ad5f5e0ed2989585000000000000000000000000000000000f65073250019ea69339379aacbeae7520c1ae10c8912ff827b702bdab2e15404cfc939389587364d811054b7d9f2b350000000000000000000000000000000019742f83ba0c9d36aa1d595fcedc3cdfa6c6f08579e66b8956fb32ac03530114ed4266738c57175e7a10313c8dd42deab80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000b2513f906db531d052e8e6f1cb8d7d3c41c7ec3158b370268d1de204ed8fe7618b64ae35029d1718153b5bdb8439dd90000000000000000000000000000000001664c367a2d4170f463c90351cb321608e2a49fca6f3258bf10d32c39747084cf9d2c38d5241888aaad97985cb09a450000000000000000000000000000000014de15b86461cda9f1be69f43a9ceadfe7b7d1548a206f3237d93c7c01ee554c4245fb73827ed0ab72b99a62215faeae000000000000000000000000000000000b25e458522be9fbdde4554b1a0d9af157aeb7d3ec1f89185b193c0429125dafa554d7a531ef9502d443a26112b940b8f397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000006862c0b0e3d7bc4507bea1df82080745aff21b7549b372085776be2f88aedd4cff00ab8258aa21e63340963bd0d937b0000000000000000000000000000000017199c5ec3a2dbc1f1e8d74648cf8da247e35cb07df22629b3845274d29e473819a31bc344f2a2bd6c790530cfcc0126000000000000000000000000000000000e7fd1ff41d86a02014229c5085c886988dfaddcb60f5c7c81063e8289aba846337d61bdde57e276fe6c65bdfb48751f0000000000000000000000000000000010efa6aaf7650edb0c74d30125e36cb67cffd1c7f57932d92ab4aaf36f8d9245d7c75dc2b3bc8f3f328589b16e26230e28e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c5000000000000000000000000000000000935de4b16f5f9c0accee77b5820cf36c24aad9953d40a2409b7e6040f09f85da7d2252843f9f8005316146caae539800000000000000000000000000000000008a8c542111951b32bb0b50f7631f8938d22e298193edffefa3e0f5c861ac8205ea9b865f9420ad74cd22b37c5cb56200000000000000000000000000000000012ddd660879a1f52ae6284e14f2ae6ea381ff3f321458cb76bfa566b04ae19f3793468d0aab652a82671be74332a3b7a0000000000000000000000000000000005eb148c35732f7ababc73861b71fe4ea5e25bcdd675e975fadd0a9e0fc54e175b2e39dcf0323f4a9802a68baecd25df3cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000000bc351eebfd3f3c332268055af1655c8729cea44eaae803607198cf747280adc0d3dedba137828834af3e7179ccff4c3000000000000000000000000000000000d8a6cca17e1c6ceace7c0ab1333ba76ed6c3b114bf99ff80127c6a17eb0585bf6fcce871deb7385e9a8896a21c065ba0000000000000000000000000000000013222db97e31e28946adecda10c9ccc9aa9fce33e0aca51d6483d2f0c5bc3f33994ad516215f8333e22167164ef5459500000000000000000000000000000000144d3707b1898d35c65ae2c89b1570971a9494e8bd23df835f565059554eb7b5cb66a6eec890058316aef43d6c6ff55c8e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c5000000000000000000000000000000000138da93a9a4948d41a6fc6d057a217faf5efad863b45ae8eab311360c033362213edb0ff90bad6c95f60b8e1131336e6000000000000000000000000000000000f41766d9b57b3210d315a2b8f90aabe591c1de6037ec79c0d72a283f0ac3094436bb97b82b7ad12ff4f471a41227bb50000000000000000000000000000000009aa4f5b674782b7adce6bf75ad676480f96a58d68dd7ef8d1fa488cfab794f06e7754e9315430189eed265913db8b300000000000000000000000000000000004e2a4a48f02079c0ed50c1daa91b1216af481a982c7aa64d8ba90449ed886cdeddd0cc08f1f8764f7f8c5988fe677f5674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000019c927bbffd96aeb9342666e1974d30f9dc215e8eca41c24244c63c106331ddad20d64c79faf8c5baa45cd30b561e167000000000000000000000000000000000523f063de96c9b77bfe5c5045a007e155b45dbe68c5f1162884f1d942bb385bd34c2a37e5e67e6dae4a23d600d75d1f000000000000000000000000000000000c221006f5bfc8baf43826258d0588d7c0fc345d68de1add1693bb897959c2cfdbb9c165e82c0c787529cd7be85afbc50000000000000000000000000000000004218e3d52b42a4504611929f94024326f38e78bba2aba105db3ffb4a51f8906b060ce2302e22ded60714d652a234c1f288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b3400000000000000000000000000000000189e5063a36b0edd736bcd9f997f4b08c62d33b27560e2e2b7b40039e7c63b75757f23746e70a330110d975ca683941300000000000000000000000000000000013393485ae494b1f1467cac9a8840c695d619aa1a78c40674038c053f264c1e20481f2005abc7f0545346f5a982d05e0000000000000000000000000000000003f2be501504f4d37e12acdc54b3280671ca0762a063fd3bc04473ed5a051cae3767044c002b7ed1abe88b2143af08750000000000000000000000000000000009d5952af88514996336e1ff19409e3e4eb3079f6dea22f9738f4a331ce842b151e0b842b68cddc10a711afa6d3242b256e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000a79444c673e630f46bbc5a9e06e8c023978a78e3c58d72910a04c3733ad873c0d0de61448076b2fd3764cc17d86d94f00000000000000000000000000000000110cfd215d67d4a091578203855fa0e85feb4dfd0076fbfad20bd092fb91b528a4117850955f5fb6568fc5844e17bbfc0000000000000000000000000000000012ece0577512182c50dbb4a485256e705410108d9ba9c8d57780d49e2e25a0f89ed1fe917797b902aafcb8f7d98fe931000000000000000000000000000000000217cf1dffac7ae162181d43ef12e3e88da4840f1573d7ffa271f64d8d54861099be37b644e96e650dc613975d8a00a4ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd0000000000000000000000000000000010bec428b2865aa7c077c168dc28dc549481c6f8367a5b84cbbad661b0225cf0fda3e840d96c4e4efc36c20d48f23d5d000000000000000000000000000000000ded3a1e9e2eded0a11211a217f9355070361f0a5887a7e19c74edc8768000311cb9dd8513977ecfb45416cda0908cca000000000000000000000000000000000b99ffddc79e825f0b73f2d0229d66e51624d854d00bdee5aa7a884dcafa1888963e2a2149db0f6e40ce3c67941a391000000000000000000000000000000000147618970c71965684bdf0d6cbe1de189bd23bddb2b861c9636efdcb7a96dff27bb1ac70485b562e78485a1e8e56531cb96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000001a402aba8fb28dd37f1be11fca037baa99a6b57188ccab66208a50bb6967dcacd1943cca73e34f6b2e2f72407103a73000000000000000000000000000000000c0bd64d043fa4e3ea566cb84f9139091891231ff500b67e5fd451805f79003f6303352a4f0c236063d60d9088fae88c0000000000000000000000000000000002861fa7d0222711ffcadac86e7b9e7b494f5561c22544bd0876fb6e1b2e680d0f7074c2800312cb233de2412ccbbc8600000000000000000000000000000000015945f0c83e738a17cb1283d08d63ecf12a7272bc62812006ed78254bfc45ca7c42306cb79bb16ed17bea600a4d62b5d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb0000000000000000000000000000000002268793f6872f7715d802c0d96f3b3d850249d8e70aaa97f19793d2c92e7cef384aaac603eb51525c7ceccdd0211fc40000000000000000000000000000000002507d680a2db16746810e966d1ba5547ac98d08c8402aed0859203e6dae0cbd87a9ddcc05119c1ca08fca2fd733882200000000000000000000000000000000192426b6438b2abc7386599afbe09081ed4908fbeb807a65bcb7c6676aa76e5e0c2c87612cd109cb124c73b9c8e0591a0000000000000000000000000000000017f125a2ef5246e7a19e1b2741b31b9224511ffefe63ccfffaef1b7949e88af573e267d6c7617ea97bbaee6d50eef67e1ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5",
+    "Expected": "0000000000000000000000000000000018c2f533f464f9768308a56209711cf9b6653e8d38591d782ae2374905f99f75c0d42c63af4b534056c28599a9da874400000000000000000000000000000000071d4d708f00875545f381e164f77183e14faab599e472b2857c15091254ddaf5c2e9df809875336f92ebcf5b7628da500000000000000000000000000000000099b207cf6ed022289c27393c32d0b83aed6c7b57323e746374c1a8e2ade071a5293168e72f7aab82f6c2e39b97b03830000000000000000000000000000000005dada01b4dfb6a52d998210a67ccedc11d6aca2405e0836280e2f7c8fd7c8dd271c815a2e9ea1dba6f1ab0d6e89d756",
+    "Name": "matter_g2_multiexp_80",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000195d3f440857011bf9b764ff270b8ba1d9d13daf48933e49c12ea20d335b58bcbef1353d9698a7e795b4370ee385f12b000000000000000000000000000000000716c151efc6e611b5b15c749eaf02816a86e267428750741b167404a21116f2025d0d07c447b9c7bee8edcc2c7b76d30000000000000000000000000000000012ba0bf62b35327111d09b402db2b75b2e835cbe581638af2fdd6d06034774533e6501be3de84e7075e4184e11fd81a8000000000000000000000000000000000329b14859d004c146047b03870371f53936e078ddc69294ff1fd6f42cf2a354a921e5f2e5c125c454e20af97dcf769e7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd00000000000000000000000000000000042286dd205ac86fdec3fee779059e2ad59adb62505f7b78606c128244b031c53dc40ebc2f5afdba348892d5ef4c10e7000000000000000000000000000000000f960010d4818846b3a0291c6fe1aa53bf0eafbc0e0968e3ee82324452a7c1a8041c06b4db9cd36a07c119c9fd2f9038000000000000000000000000000000001876da0dca72869708b8ff9ea0b74ad6be25ba82ccc76660246413a04344f2b72e5a7f6fddb58e9dc0bfaa6b33a5fadf000000000000000000000000000000001538ad1673f117493d998941d9356fb9907f70c279bde8ae8813b9c7b371344456f8e67cf02bf3401ee06d55604cadf9c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb1000000000000000000000000000000000d91319b4a5e047ffc8a68e10c34b2b90e7f3f08f9e3ec53ed12bba5f66c168c20c6583ba2016f0137caed834845f7470000000000000000000000000000000018d5542919674d2fc32430175405d806ae4abe3e1236df2188bf4c9ddf66c0974036e28414890212ff8dad244d11e3c700000000000000000000000000000000160b128f1ffeb97edf0e62dff85e3f90fa48567ab777a7937a2c0e4659df180fae4565107c2236a5f2808e42a03a4ab40000000000000000000000000000000003ee74d214ec491331fb9db8243e75570daba9feb587671496cea4b480d80ee162c6294c082203534bee450c384f645e69700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000001951afa33800a366944c43bb42b8c5c8beb9ea2e1cead8b84e0f94af51e4a156d9454c0f08d1b13c692c41cc480fdefb00000000000000000000000000000000077f4543fadad6f2f8ae8d5d98f64965bf9626971e7efef5221cb4d95d51b8764324cf4a11d0ff5330d58df70cb79d92000000000000000000000000000000000417251cd0c1b32505377e51bb30ac8a8a3c059644b9ddb5a058b3c6e1110e1c71ee19f549b15090144dcf4668d0d50100000000000000000000000000000000052133be345adc562238c4ecbaf76ca4159fc11ff563ab393317b03065ab668e7df401831baf7027f0577f5791b1ca3019e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b00000000000000000000000000000000067bd52b7a3193d31a4f1ffb76432c8d4108442616f17056d310fbfee2ffaade9437e2bdb8425cf83233f0c632efc1170000000000000000000000000000000011b045d6eebe1bc8218b696b5e81e78db78eadf1b5d987060c1bdd73aa65666f77e1d6bb6f3d939d64cb3e6bda08994c0000000000000000000000000000000016eb5ea5067413b72632f5300efbe0d01a284b2a59b68d0333c269da9302bf0f0cdc923acb27e51bbbbc1d4086e6b06a000000000000000000000000000000000ff37b8812963d9efaa1e6deb5cfd34eec70620fdb65808739295a819e03ebcc8f501b8194d0b3c72717fc922b785194199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e00000000000000000000000000000000149b5e0df255281c1b518427094cc0903fe89eac9a6dcdc379b8ca30f3696d89824c201601fc4b0795a3c859a82893170000000000000000000000000000000016ee9e7d957f439d078f3c5da98d114a1b5bc4da9c17e117e1f540dcbf83a349bba94def4b87b63247f190e3b5813cb00000000000000000000000000000000005d4f56bea105be4bf1fcaf4f25df30f85968d59e60b1c438c28ea0f480851f5ab9c05a7ca6677e6f12c7dd3ed67c2e0000000000000000000000000000000000dc0e87ca5a8b339b485ff3da2b9854a07e9663c43344dfb5ecf3ea055eadf67405c43013e15367fbaa55f1bd8e222f98159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b000000000000000000000000000000000b0575fe2adc9ad66209cb2191efc2946672e4e81b96d50493d2125d9c83165f0c4d3f714539eecef9de0706cc20da9b000000000000000000000000000000001511649f0cb6b86111d2830812231ad37df5500d7ce1086241591dc3cf40b30f1c53dda3133b2f7fff253c94d5eb98720000000000000000000000000000000005b15e4e32f4f4e46c1560792a9973f6ad63f5176694734f379375f16a08c162a4a820385d3ea6c191bd87fea4f5c8cb00000000000000000000000000000000089218403fef08dcc6e679b49a74557dafed3278d41ff36a9801db091b91de0d46d779a40574fa4a3f2baaa1a14be098ef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a0000000000000000000000000000000001c35a3fdea92b28c9ab4bd9ea592b998853a73be844b9dcb500ed6704bbf3ca4ed4216dc24b50254b6ca75c4ca3e7fc000000000000000000000000000000001815292d2a365dd7f41ecf3f9a89e040bab717241cefb3155a097eb9885d64fa55f5de7023f2ecfd33f483ff304666520000000000000000000000000000000013df522c72805b890aef97864ec6769f569504fca2d6a6beae97f80dc92643f8014daf3dafc0040dd7b985c0d9b2c462000000000000000000000000000000001155ad4373a8304fa6301cf48b4ace135d6a0c08cb06d624f42f88073e43612ced3cc37235422171b43af2b4ebbd5662d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c5000000000000000000000000000000000bb9afedf7417ca31beb96486b024af13c06007585d785efd1e78444daa9bc3c03e1d64b560e8d6a18ccf77a8c3c8d05000000000000000000000000000000001652d3adcf1612e487a9ca198801afb9ec30267148502684c2b91c05ebf6c48e2ce33f9c0a986daab81d5359ec1b503c000000000000000000000000000000000baf3d34bf4a78e3b9dfa637c6392c7f4d7ad0ec315d10748784b5b60221bd9da0f4b75c57c139ac2db329e270d559de0000000000000000000000000000000000c30e553fa2324d552bdbc7d2dc86531340c4894495ee9a38b64f5bb6f92314021a2a00c4bcd8837e55a0ae2676a9b761d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000159a1e4e87c35aaaeacdf21efbf8ed99fd6a2ddd7e990c12407b1417edaf185b8f1df9bafbddfaf3d581b5d97d7718300000000000000000000000000000000012239ef7b1e1009c81098aa4aaad8ee9e003530db5afd49867aec47f46d5e29d44b5e62d80d9e832937a299633e863c80000000000000000000000000000000016af6f74392461a9294d9f848508651ca5c0cb50494ee7c6a334bd770580b924a17beb7824b489e7e101ccd50aa0d5cf000000000000000000000000000000001912a0f54ba4fbecaa55c150ae93455e1db6b238c032fa7992bc8456f183c09b6005dd6398a77ab91cf547919ce7485b03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd000000000000000000000000000000000cf1cf7a09a12f51d10059425042ef8e140718ff11d2f17897a0156034f73ed29496d93b8695cdf609280d319c9bb742000000000000000000000000000000000b2c4d26fa1eb72eed1a24f27229d2675e0c6f91e3a4eba7d34b0fc1bf5a9b4eb49c3492d9586669abaf25a656e1f95d0000000000000000000000000000000012c5c83a03087b2449b71e9037591fa265d710ff6d869bfa18ac37cbdcc93024f673128db3dbad9e3517501af12f2540000000000000000000000000000000000ffe5824245e43953e3d0adcd5fdc1a97ffc87f8c5473fdb0fed57000fd126a9925ba7415c698248c51c1f3e12b270d5e4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b00000000000000000000000000000000036aa888e40882b2d6ac71d66c88543e32b4a0a7c959eec560e3d26114d8aeca63fd87dcbb3171622c989a6c7a204ac60000000000000000000000000000000006a5e552e6d2dc95ab8636a8be16bc79572b47860bb88934bf04c195ec01fd71eb91e45f24c58bc2812ed5fa10c8dd7d0000000000000000000000000000000015fa3ffcbd4e562a4bc29975cf8c1eedf442e37374fc87128e6f68bcdf6e996f6f054e0b8c608e651753de96655b2c100000000000000000000000000000000019bba7c0b170dfc1f8fdbf7a2e09ca0c4027a6aa6930d15dc2772a0f20e5e56f0d11644094dc866595f801ba5552e6c4c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000341f46ec06a8def4f044328bcdaa308798469c767d10e5db34b0ffb6f550421c67c6fab7b63cbc7504e55847cee419e0000000000000000000000000000000006952e5f791c37dfebcfe69cdef196dff66563b29e94927e3ab34365773b93e72251a63af4ff294af88d45fe0899a2c3000000000000000000000000000000000874dfe75b31450e99dea063c090e32d24fbff9b681b64a9dca5f967f82003005b003d17eb869bd3b37d4a412bcb28fb0000000000000000000000000000000014203b69e8af4e25232777f503d5e82d6121256fafdff1b037f65d5aaad0f09ce882151d6bb4705328400f00089dcc7a7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af4000000000000000000000000000000001403fa3f418107e0bf7f3f4bfcf621812d32b1b744ab5a4c37b5cf946a5e5dabd675c2b70bd355590a9883436c5e32dd00000000000000000000000000000000069e006f168bed4439fb46db9ba4f279f72ed608c12a05eed172608693f42cb1f04aaa54191f4b0b35f967bf03d0e63b0000000000000000000000000000000003f9ce029f6fe605802de64701ccdf52bf4aa299400a6e1c36f5a1f9173bc11a38e7628f123fdcae01d2b260f77c577c0000000000000000000000000000000009c9732809f60635115cb479c80457c6cd8dad092111d663c0cda0da1fa71c9bd6795ad013d2efaa4599c8ac5c88e5f26f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000115e08d8e4dff7adcfe46a416625be0ac26ea2d7900f5fed497809a6d46e7faa5b47c52ab3bbeb9fb16d82b549707ed6000000000000000000000000000000000dd1b31446e44f64ea5046dca5174ae854f6bb5d95886fb95aa136d432f1a8c03ef1a5f9320f89c82f764049a7f678a40000000000000000000000000000000014879783c07e6986cd393fa1e0ca8a7e23b2c9efa595229fc0b6a11b9c232ba33e92962a1087fe2ba0532d7b541827900000000000000000000000000000000013dc6e2bdb2801333e7f914b99f30b40125fa1ebd49b141d88a8c090b15ec3250a13812a19c3c0751a4e5ed100a6f0ba12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd6000000000000000000000000000000000a2cceef36ec78dc702b6731dbaf8cea1dc2b41fee1b235673c6941729bc5631e69ff37900479391a4d10b300fbf3eb40000000000000000000000000000000002f4881fd626f4ac434bc1e59716e5e5ee14dcb9adca4d639ebc9d86e323d274ad8ec0a4b1e6ff92e1fe7928d48924b000000000000000000000000000000000174cac80e7bc63989f58759e123513b611e9849b44d43a362f2eb84421ad008f3ae9e9f0f233e49fc8e10c1824ba948200000000000000000000000000000000143641099c8a6c8153dc8ce74debe795dd6c4487e8234f164f9f8dcdea6a53619c04a8fac215421f985557b5b956c20a5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256",
+    "Expected": "0000000000000000000000000000000009807ffe8fa881b235b1181d2d3f147dbe21042524fb0c0b4c90fb122d160c7b895034ab32e20324dfca564ca6e3183c0000000000000000000000000000000010f6da88525da3e86ee56cd5514a436e3ce4128e437a876be130a70c42444a05ac269326c84dca532ca2e546860027c00000000000000000000000000000000011396a7317918841ba171ea46bbddc9bb5a08db7c82b90008c6982b4b79a4dafc151081bbdb7b9fb79784e603e15eb9e00000000000000000000000000000000070b8580f303b83c643a484dd031b780ff4ca2ec805d8c538a0b0c791cc7f8163654f5e5a41776a8681500a6690e24a4",
+    "Name": "matter_g2_multiexp_81",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f38906bd058e4d32403fc3d39fa57bf49c0da65ef42fb129332b91c184185de4f9f0bfe8908a44833ff4ac4d65b88180000000000000000000000000000000014ea6fffa6dc462463c15feace841697698bc521f608ed0d16be5097bf42aefcd1f73182f37b6279f989e9668a8076d1000000000000000000000000000000000f56d296323177ce53c6977fb60e445278e59ed1cf92e3f68c570eb7a9e5f8afbec5e2ef64674bbb54d7016c829f72750000000000000000000000000000000001b29012ff3460cbe4a07bdc65885718f217cf177866823a7cbeae18bda67f65913ea20bb69e0ffb31bd82f19862113dacc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd000000000000000000000000000000000a4126bff91ada057ceb9a75d577120c7ac8c9ba62151602414364cf88a3e12dfac90b5590db3e40c16163177ad4e7520000000000000000000000000000000004a3768d326c4ebcd5ffed89341e8d04f89e674f3f2aded3205a7193e11c20115b3c4d595b959d6e39a03d76f6b5925b0000000000000000000000000000000006e0ae4a9c45bb69c3a1c65e26e4869f2eb18fefe584e4598ba99c0044e8d911145a5db3f57194ceb6201e7eab9a81b20000000000000000000000000000000005be2ba6b147f3f2052c4877c90ca364427c6721ab64dd35e89f14f3179564d8812b9013e3e3db22f69afd739229682b98c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf0000000000000000000000000000000004c36cf955fc81bdba4ea8d2ecf934adaa57fa4073199f77bd0428d3ef80a7d7102179d4a44ef0de887bcb3ae915408900000000000000000000000000000000138bd3ec7a1b6fb65d1df6bc1d2ada35aa52b06729c10b5d45b9bb7cbbdf41677942b99eb9c2d32e3e73da7d5f9cfed40000000000000000000000000000000000b0291ca10245e2f7a963fa07ec62b15f6bf9e7a5a7839840ebcbe538dfecaf2114c7864a16564a5b3c85c15d97fb7a000000000000000000000000000000000b436e912b8a71cf8050d10d59017eca6e494e5440f02d2816924ac9cc2034bedb1cce6eff5c42f3dc57a74cf1b51cc0a0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f0000000000000000000000000000000000c105ac7475ed9517a0b07f25a030a5616952d817f3893181e352907c7cf4ec9f5f3006e37b1da97e9cae4a1213584e20000000000000000000000000000000002c112c18268934823d5946d2322d0faec497d8e18736da91d2af744d90f74136c49370a4b43952152c62820d25e52ee000000000000000000000000000000000fe2818a397d70543e752e7022f12bab10f1b1289cee61a0230d545296ec872e34d8df6edf7ce9980f3c153e6e51d96d000000000000000000000000000000000f479e6a52bfaab3a31aa9a461adbec8a390daa8eb6273f9e425eeed764a6dbad44d12778bb888aa5808df272edde401e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe00000000000000000000000000000000016106cb42ffc41d5b23bc5b06001473bdfe556d375fac6a0cb0a12494e9c02ca2dd6133356846e1759a2c485faf5e890000000000000000000000000000000003cec25b0f5d1db0ead5319d6dd15517657d1fec442facda4335ae0bbeff606fa9caa6a4c00445001180aaeef895d7fd0000000000000000000000000000000016ce3573fbe27a8d23b3ebd22aec989d61fbd0e41a519c5e2f1d650f2ad73adcfc8c840fb12bce83b722a0cc69164e21000000000000000000000000000000001434d13d44fd8dcf776c2a045734dff7c09ded31c9e3a4b5e765cf26fbfea4cbb4ac15c06599012a7f2cd572bfafd78ba32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000019923ffba0d08ebf1bd43393142d61022430356081c18e37804172082c7ace987ece2594f4852e84604a77235c7795e000000000000000000000000000000000123acf9e1a86846ae27d5fc0358afa34fe9d6b68232c9ebf2d47cc169779c4bd24f225ad30886fdf68166adfd9898abf000000000000000000000000000000000a6061d4cef29d1e3535d54a2e36373e2c16f91543f53e1aca94c4abdabc663049673f2327ea8bb574244d7f5c99e981000000000000000000000000000000000b1f3e1d43575a74584ec7a3280f8b7196f9b99b5e911ed33ba6bde1188c82d906f0f8e6fc2b285fefa0ce59116e449524301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000000373d36dd0fac76a0fc46ba5da279ca3be5a1f8d799570004e429256787110d4fb746f65a8527d0ba681a81b9980bd5c00000000000000000000000000000000057933c2b3e482ae026159211c4742264f7e890efbaeb6e14f3bf66c80923289af095dc97b751a117e181ef917d049b000000000000000000000000000000000068816ad2369bb57b3430c657284858d3736c327284e7410b61ed444786bcb34a66db9c16aca583aa9722aa8d7975b440000000000000000000000000000000007fcd7dbc062d28f6ef906f6a455337e517e1d6e6c02c7c0b2b2685b79f56ca3436c1bfa0ab96e4a5eb0c2e2c321c0dc17a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff825695706740000000000000000000000000000000007bb0ab060cc12002e043724c0fd0c8bad30e08b65ba9f2fe5d09d18cac4bb2d50e29ee14590ca7bfc505f3ee3d4f93d000000000000000000000000000000000e680653d29eb5d90f21802f543eac3102a1de6d2a5bc943a53dd9b80bdcaa6951ced2eae5e2a25448b40468f1923ebc000000000000000000000000000000000b7494b494019e3ef36d5c620ac56483fc6b1c8fe5c6f67537b19f56ef01db327812095fdf805d3dfe678a3ed8bb6226000000000000000000000000000000000291e5b98ecaf7aef0374647d28fb9f8785a64d9165de407d062403047da14d4ecd19fad8575070b278608e16b71d387d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a31800000000000000000000000000000000186536e3ae3edd9cc6bc24fda6589ed26e72e06121e97e1ead65b200fa0578c6e53d1154dc7b14e7eccc3a53237685060000000000000000000000000000000012fefaf6c76ae7197b99571e41a19b14846fc4499e8e964ff750e7c3ffef6ab3dc19eeb42c5f6ba44a573bca7a15166b000000000000000000000000000000000a135db813a44a21174cea3a0b34fb49f273877203ccb66bce44b2b58794818d8bc1df27544ecbf780823467e2e4ee6b0000000000000000000000000000000009b08f70cdf4e349e1a73935de9fb2ad9f4feb8cf5f835be78383fda2af94d81af253ebce08cef825764151d5713ae60cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a0000000000000000000000000000000007976df2d47c14374e554401c4d3330bbf6f1e6b8fafcea1e1974af61e8ebf493dc0473d34b30b0b1cbee082550d85c200000000000000000000000000000000177cd64db8334dccb17fb207e467e5b09e891b05df7658d9b439e3cb72bf3e0a70e84f96fb5e448f33c003c279cb38d800000000000000000000000000000000094d739a02b8ea6ff8113019597f41df4728b270770edc5e68b1f5c32775f0c706e3f31c0a82059c1ee150b89097376a0000000000000000000000000000000006ed888aa4bdbee94ec67500e30d654071774fe22464dd5b900fdc17b445754293504b10d044aac8fa0c289f0b2d9dce6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000014a16b856b04ac4b687c79f2b4e1dd6d45db25b382e0ba6687afac648c9b6384cdcfa89812f1a726bb4d1c22ebaa6668000000000000000000000000000000000764088e337df6db30ce8aa23aefd91d9e35be911c9e89ac62a1e06c3d06e28efac256490400fac4490f595cd03c127e000000000000000000000000000000000894856fa1c8488fce182a9c7749f7953e6a73879b6e743fdb8c780275447122f512806fa83d5ad528f8f61598ed01d20000000000000000000000000000000002b33bfd09e0ff452c3336bde08df0102162488bc83c27052447a1e5d16c9c68bc529f96ee3787a26d2009f22a1246342e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b00000000000000000000000000000000133e2d092352d3ecef5b67a09c2be268fcd4fe1f7360a8ce3ef5f33bf689242961a140d9c8afcc1e2fab3ad4e3dba49d00000000000000000000000000000000101eb285f0c462a22406846d82ca6a278520b65132d2008b124f6647a642c221b0c3bbd4a0abe8af7417e7aefb81b5b20000000000000000000000000000000010958cbc317f1186aab69ac24be87647b8013b678b0eabc6270167bdc9c0cefbaf4d9a34dc41524b709f1b881e6bfa34000000000000000000000000000000000d92c47257fd0c4d6baa4c81efe65852840479b9bfda5cc06b253f167069ca7367924c0c67d6497a1e9abcce7d0ce9502eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f1840000000000000000000000000000000014ad0f935ba129b47ecaad63b9dda44e7ef7933f182a0f5226141c8f0ede026ca2f11db7f4924b5c582461688dad6359000000000000000000000000000000001453716381f13bf6ebf8fff2ed7bcb90f7beb44269008af5880a355dd03de5c84c14f5aaf69fda043b422aab0c694784000000000000000000000000000000000e983c9e9b799eccfdb56444d31948067d46adf275d7f39a70aaa8bfd0fe1b83632c23d87f4e993c8191901e9a607217000000000000000000000000000000000267c8b8c5e09b59277736caad12ec6986f206d1c1f48023356d8bc877a594c8bbd98981cec6382bf9bdb9a5fa38275ecaf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000002e28c245e71a7f6206427ee512f3250612785ce29b369682fbf767d06ac08f91de8ac9f82951574cce46cee1aa757720000000000000000000000000000000019b0dc35eacd961e0ca7d54a0e37c4ace37eb0200d5489316f3371412717c57c8f17c1379721f4dd67b3fde24f50d4cd0000000000000000000000000000000013b9741f7a32e5e5b1ae5400e32dd6fcc1fd43b68df54ade57c934720b1289a51deae77b1726e1955b6430f37928e2bf000000000000000000000000000000000693980b347ed7ee6cd93f565c87efb36fb304d7e9ae24e2b9f902bfc962b6c7fbab93287147f5ac892db2a709c9ab42106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000ad6b7cfc6cefa5783093b7d700360b354d0698d27ecefb7d5928ac5bd6c299e4001474d205cf3b85a32c600ddaf1a360000000000000000000000000000000017172c3d5acf59b70b340fc703e9b7801aeb4857ffbe7a9d5daa0f32ad80d1c0ef2f0b3b7d1fd83a757c076872425fc7000000000000000000000000000000001291f55fa7d14b14c578d57178cc707cabcdc4bfb444cecabda271cbfba2ab361947d045ed46d9edbd215fa4c8164e56000000000000000000000000000000000f64ed6c989eec5222239d888d08dfd638a0e35eff2266410dab0498941fcd1683654064107fb7e53b8c02fbe98a25622b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd30000000000000000000000000000000001d25b5bfcedc6d7ff7e9fcf729f858759936235d23ad45b14dfd0229bf3e50fc68799d19ef019b36728285bf7ecd0b4000000000000000000000000000000000326e300ba07935e0233a03ac891f18dc7b5a9ad9a28264136228e9e23e8f2aa31b7f5e5f3cb3354984f57a868a5d00c000000000000000000000000000000000dc92060e3403df3a92b15ba3e437ef0c403fcfc9c3545e544a78874e5d9b5e63b9ba6060c29022fe2594c2e6fbb6a840000000000000000000000000000000006a01e85f59dc45b1501309a350137d71147c30fb70da6b7637a9b1dd884aeb7e554215474784ecd3bef18d15d2c0524dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5",
+    "Expected": "000000000000000000000000000000000b7fc0b44723ff0d1cb7c43e470d4d432fc4bbc7f9d98ddb3d91434a5574956fdf15f898e579236426ea44677998665d00000000000000000000000000000000176586b6f157e408138391e3767d0c1c8457857f4cfae571267ed64ac86ff8a4b61a28b406e1caecffaae6a399f4ec9c000000000000000000000000000000000a420992f850db20d4f7d2ddff33e4dc79bc0c39caee533920c5d03d1c2619d8ced769ac09f664c0921829bd7edb446b0000000000000000000000000000000017e4000f4d03a6707174c3adb74966896bcc0eaabf4ff83cce92a666fbd13b59efa2c767442062b6c8b1a3abd604f0ac",
+    "Name": "matter_g2_multiexp_82",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016ac5146ffc26d1f0c33645931bddfb84756e1c7b03f4838467d1b6701ee9478ae03c910b6b4f5c14135487bd2c14bf8000000000000000000000000000000000e1d082f16e4d5c5f0b6fbe5178aef6f781a6fb165f775cf0cd4dd55f2b498a79edf373007113dcdf6b977a87e1fded2000000000000000000000000000000000bb94be280df1aed761651c0292f88037d172b675bae1933ec12323b947023b437d080aac9e196fe5e06e5c4137284d200000000000000000000000000000000064190f9725bfd5d56c31aef7fd1590c975e2ce549fa6d5c71b254851149e0a0dab0b9de3acfc2d64231c79bc0ebaa37ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000012199d02d3f1bd8c4005878c3302e6a731ea69d69accdd690b4667e847b079563d32e18eb7a440b8005ca936da5e73cf00000000000000000000000000000000125b0dbdb0058639513b007a84d3a3e6302f5d846f22f99a55181f097e200981d9013c00d688a11eb976120f1a5da64200000000000000000000000000000000081e723506635433528fe4a40fe4ecb8a9c3d8cd701c043c0418d149951651e21632cd85f03db33b89efadd69e009ebe000000000000000000000000000000000956af2e67f8ae676abc783c4ec9f85c50ea130410cee8216fe036cd0521b8ea38966288afe7d35c28b30f7ca5c6edc0c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a7730000000000000000000000000000000003e11a4e9dfe82cb495e9e698b16c257ea3f4ecb24749751e7334e0f31fbd6677545e4bf9ff78a82853560f7e7ba2ae2000000000000000000000000000000000caea2c527cb3aeae427e92fc364365b1f55e7128a544009be2ff7a5236d1cf8ffd5a5cefc87820bff5bdf1c6bfa165d00000000000000000000000000000000064a3186774da8bb5d013debf46ccb0d894592c414f32de6f77da47f4d42b0c8a13a2ba4f14b9883d564fd8ff6a4c90200000000000000000000000000000000072f6c48b6a05039e3a4dfc6b73501d6d4ca7e840b119da9c074bd4cd2adf4f2c6e9e6325ebf6f97c3f0b00e6b9bfac6dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b0000000000000000000000000000000019ddf708ab31f6f6f725f0e4f65d11248d3a79af30927a6f2673901fef9819b189502cb952bd4742d2b8e84acebb5196000000000000000000000000000000000d928535c47eafa5da4ce4f91467fc31aff8b86b850e4582a597b334491b14da71763f9aedb15ed32856382069c094ce0000000000000000000000000000000004d6b3545d067aa0768cda9dc3cca0f58eb546345b96f7d6b9355d47770e00286d962a6b3a64ca2ce22fdb4834a4bb6e000000000000000000000000000000000f4ef9366d342b309076299816c1ed9b424b68886a5c69e21e785f97cb0f99ae3a99ff6b5244dab817094449048a7552da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce478000000000000000000000000000000000236c5b4c57ee4facec5d4ff37a478c505217af66e029c3382613442c58875c75cb423789f6703ff3c1c0d80991c9e3a0000000000000000000000000000000016c052de3336002f362d9b0cb386b800860527e0fe81a1a6df0ccde31f3265e6246191b3febd1ea48e9391c44593ab0700000000000000000000000000000000078dcb04ca93c676a9a924e59f924d9d3af872849bc30ca633d4025aecd981ba12e626337635ea77886a45f4da84104f00000000000000000000000000000000027df6394b195222bb8357bd684088e3e2a398f0fb0cb812ca5dcdcd1fa1279cfd03db62e0f8b2800d4b8b48238931656aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab77000000000000000000000000000000000485ce58b387083172102145fdb3e26c6ffca8b35af0e1d84ce9cbd89055be083bddd3da56443924049a056fdb2ef092000000000000000000000000000000000d998b234a69d584c78ed054b1322ceb33f73cafb5b23c1703a9fd609edcabd44f1a642802b9c0b6fde6a6828b50c1200000000000000000000000000000000019235ff13567bd007d77e4dfab139cd57dbb309a3cf6a6198a548c4e6915778094ddf2b05a91f5478169757bf5a56cb300000000000000000000000000000000110f6ea19a7f62bc3e78f4c5c1c6d3efdf1a7f563576e758218b2c363fff8ad8fab0e72431619e4ebc93d2d739fc786c40273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a0000000000000000000000000000000001d45da76e3016c00fe65bb50f7067e4f06364ad8348184831c4932ea0e0f3a170ab5147e4670ee1b16924105b6fdb6f000000000000000000000000000000000b3468206db0613369b2b0750c98da65b660fc07c30cab4e459c311dab683b6b313b99ec0fbe92ba07f8aab43a12a2c9000000000000000000000000000000000f58a57c449a41105837d5e2419a34201cc921ec77408d6c0c7a2eb227be98ec1f6f6eb9fc088daa0d4c78928a1eacda000000000000000000000000000000000ba53b872dcb9fcabf35e673b467523ea77accfc1b38a5f92d7b9d269c28aa00d00b08d70eae6ed4d2e82bdb06008f9ab77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb540000000000000000000000000000000002b82e2b582b247271543117b939fd17ba8bdd617a223873296f7bd75de4790f0d5d8fe523792bc7fb4764d3739669d80000000000000000000000000000000006eb554347efc5f2ee79949bafc012e6d9964ce19459b3867865709d903fe3d11bc617f30f6279a9e62ea104565953600000000000000000000000000000000006a543fe5cfbae629fd3256575e3eb4e0b65864aad6c7f359e169038bf090ed9bd92fef32fe1ac20b2a8c90fbb6081690000000000000000000000000000000013ee42b0693b2f3b9b977fbae5c856e9e4c5e70120b5c29e0a9f898f6d04b7fe351e17b02716a44febcf0a00a9cdd9220be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000001ae7368f84e354e5758554aa9c72ab4b00a644cfb9a4ecba38dc72227d297749bbc98c8f5d6149143b31442359d8013000000000000000000000000000000000abf087f77c79cb8c69e4289fae87b2ed483442daec3851a5ba32c43e342be29433b2deac6dbfa7a787547a7361ed0a00000000000000000000000000000000000fa01cff7aea64b649951a8d85fef0bd475f31e47c706b96ee2753df9987508b5e5456cc49e88ec3aad720a2535f6940000000000000000000000000000000018874d020e2eec0e286dce324b91f15b2a4f293d32956b27524f478983f0e0c5b43df802b60f4f001753f12d449cd821c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000018f7d19362e2cba91023455e115cd90f02aeafcb026349393ca4105e270ab1cf589621b40965fdc9795f66ea0f6a053100000000000000000000000000000000170ce0eb304e0e1047617b709c834b67a8989212e5bf1cbd5a33242be94bb141d5366e636c01a229943bead9a7baf43900000000000000000000000000000000077a17356b3b31faf90f709042938b9e901817f7379b7bd486d18e47d22b0430ba70fb3006e9afa67d7dac71ffaf152400000000000000000000000000000000064aca92c41561e195fa8239800c97d5242ff0f8ce76b0d119063e2ffa09c26e01d23d5728765a59bb9587e885450ad1c6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b3000000000000000000000000000000000876a57dc24ad58416f910ee3ce220630a1297e6bc691c908e6cc16f975b146872d71661bbb869361623c61670627eb0000000000000000000000000000000000760fc65097d215ab9aeb3d5a5153977e1e399e2cc0b0cb9befb0266d98ac13512a0eadaba4e051bf56794621c551ec60000000000000000000000000000000003c8e205e53075a96c14ec26345c75881a0d67c7ce0d62d73c83dc353cd7b555cde52ffc5659ab0db2179a899f0fd694000000000000000000000000000000000d7e8a7fe6b751f7f478698f4f0d30cd0a435a2295a958cabedf4668769819b4cbd4e8b7721eeb5ced3f913156abcaaff3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a21000000000000000000000000000000000180546f697349adb2918129f4d0a979bb114d1b58e5baa6cc221a09d7083469bfaa61f80f1e3a6ccde0da54b24d59db70000000000000000000000000000000004074338380e3d7c0facbbc71d83e78b53191af9ba13ba0cba6015bf4f28e4b0b52ffb34c7867a335848f57b5ce5ef5200000000000000000000000000000000148a800ec38cfc2386497d9aacb4327d5953a6612cd4067ac13fb977046688e80032125d4b0e7cb49913e489796a50ea00000000000000000000000000000000132438d18d942e6dd3f69d117abf83c2fa18418e5145cc43b3cb8d18c873935e41279a9e13596f2863be7aeae9b73d172809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c0000000000000000000000000000000018b3102ce91af86cd10162d3a43e488a0d7b7807dfb9624c3cae76f342e86f8ef1200444a57e2ed7f819828357a6dfe80000000000000000000000000000000017137b470f3c8d1a03e7252e18f4466c9ff809408cbb2043d6b226ae2746d890b267ce3255114b2e073eb66e93c55eb200000000000000000000000000000000054dc1c981c9166d0bd3a54064c33f15ab856b240770ed44adaf9f32d4429babcd0baf2c5b8a1ff80728e9c63e806cd3000000000000000000000000000000001897595f836342ab54bc2e1b72f433bfe3b5bc989727de48575abe89386aaad9b1549af3ca55f39feec14355b29dc9e33521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac68000000000000000000000000000000000467f1a3093c72aba4c2d9e8171057cf88146eb32f38db0761a5ab2027f2213c89e12c67a338b4b342a73384109988d2000000000000000000000000000000000ab26c871d140c9c4e0512afe9fb576409ffdcb95417f8c6cdc0d964011dfb1e745045766bbbc08ff7dbd6935934bba300000000000000000000000000000000183488902b886200e63465098be87a905810b2e8ebe0364316da798e423dbb267743a0d2e3d93303623fb17df0e74ce30000000000000000000000000000000012c7e79f9ba36cc47762139d191e6625c850a03d5b6e0648032d1669575704c91e48a9ae432bb3553ec66e86e082de689c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000012ef4988956e026a79e5e904ad3d7ca56793321d62cad46de3cbde8570be5f0ac86d386216152b37053741fe342de7c60000000000000000000000000000000014ff7804312754d23b251a42aea65207695d4df65cac4f87fc96cb920843c022f24cd27731224db751cfb621886249540000000000000000000000000000000006ea693105a1b2afc79dbf75504c256c519f927ea0d79ddd1997a49638a67151dc81b84473208e8078cf71d456f2de0c00000000000000000000000000000000122d367c147c91517679432d3c7b56f2d529d70040109f803b89a04fd8540a6c565354ae420e1bd4ad4ff61427332629dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd989664000000000000000000000000000000000156e7f8f1412cec315eb76f10c92143157313b8eda0677a6c0236de5fd27e5660ec3eb7369f1604082c59e1aa5f94dd900000000000000000000000000000000018ca9f505a88ed2bf595fa9b55d2356748770af16b35bd5db448990b7d41c3aac53aa490791f7ac09d2f5a087f938f70000000000000000000000000000000017c76ca9ddfcc26b028928364ee35829c6e57fda40773a6bc0c259a1b3cdea715c664d7bd0340192aaf7dec7ad20a2ed00000000000000000000000000000000082a255966c4f9d0ad6bd3d88b136cb2cfca09ed6ae378c914c28ff3338a2cd466cafd839f3fff4a30b33ee56e684f4e00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0",
+    "Expected": "00000000000000000000000000000000075c71e21ce327a97024c8ab5fcbef4fff76260a4f8c8489167166c4a85d25096c617cceef73097a4bb956be3eae8b780000000000000000000000000000000016270f3ac86c0ec43b9472499c4d845eab488a34ad9e2148c72cbb1db13623c5dbbc8327c47ce596521bd1f54f119a660000000000000000000000000000000007ad4914ceda9fbc161121c818bd05953836a581dcdc78bebcd82ef548671c899581681c908a337618a445f77c6b7cf400000000000000000000000000000000173f401cb78024e844adcc88fcf0e52d32de134f6300216ea0da7747752ae3ddf4d181b8d266b53d1b699921f9871425",
+    "Name": "matter_g2_multiexp_83",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f40eea638a3d4fec417701c936d61c5b233c9d6b3e94ba9addd0fa0b20adf9f8e07c6b629977445c7750acfd18001c0000000000000000000000000000000005f1ca1ca9cf67c33e3ff174a65adfde2db62e74edf30b5b0156d6b9dc86dd619ad8863c055096685d611ac015ba884f0000000000000000000000000000000001683dc67710880b8af76b464291c17fb0ee4eff3f648ac0772f4a777025c8cda0342d8f5aae3123da7fac57b965685900000000000000000000000000000000143d919ce2cf00838b10fc65374e770bec3db8ecb17d2db08b6a10ac38657bb109f54c1b3040b661c3914ded6f7eb80fa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000000616299341f2921adf083d1190c212a7941bc0d9fee50b05b265f2e8c339fc3dd9f94607631f485e394f5a7d71ae73d00000000000000000000000000000000006b2f12e22369e8aff45b6c05a2bb72a706dc46a5d1393aaa9e5a7931ccff33a5df2967189114c3fc5dbf69d080e39dc000000000000000000000000000000000981e1b119d04343e075a80dfc189000b4cfb4e321575817aece6009e6b3a6233d1409e8e584f0ac9caaed1f43e40d7d0000000000000000000000000000000001ce4693e8c14032c35497e0f9a586a4541d8a1a68ad014b0850753a04215be2bb60cd7c2fa9be4f4f09a562d7b29f3892073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000012d634764207dd7a0201703f855365f7750291c810ff292b3e8dee682d7d8eebd6d6f3b3dc8b0c9e25bd2860e031311b0000000000000000000000000000000000eb0859d79fcdef546026fcd380f5c936e64a5665d73f56d92c03dfb50c534a00c857c86ec43275ce69cccc0b53137f000000000000000000000000000000000131bf000fd117ef722b33a1cebd28899fb012e1113f767d0ed46fdad82a32e4327b883fbe29abba1bb7ba3ecc1cbab2000000000000000000000000000000000e24ef1e44029366ae1daf06524d8beacb2b99f60f419cc2ec1a49013b79fb7a4781dbd37785f32ec67c0a28d61a3cea2117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f500000000000000000000000000000000067b6eda41cb8da47a424a02a142e2b98b9c69e7023cf616040993f41507798882194229cb6572806e82e9e5eb837b37000000000000000000000000000000000e38693cddf130d3645fd60ade780db84fa700e5bfb74ebea49cc95ab001bde442f363b4e4c61f683b3e67f1ec8c2af80000000000000000000000000000000006d593005cbccc55c5e336e19aded70da65a7fe42b6a85070e491a4ae54e18ac213556a91d5d62786b6d4d1305525a76000000000000000000000000000000000ff86216f5388114dc06deffa7b52a273b22fe0bc8d50804b491fac83e13915c2dd1b8c2779a46b5c313c4e1c05eb2979087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000262cf4727703fb227bd7fce6cd3f25c1897011ab892e79fa47446711d6867ca82b9b95f129f7ca24dcb60ac75173d4700000000000000000000000000000000136b5a304807e029d0a77b2ed505ee5c920248242f0f95aa07e9bc2e13d35f6f67451d028dc19d26095b55cdc2fae4fc000000000000000000000000000000000b511b2e19da7bfeb183f0aec91bc7db3e7c913f1c282e12d5d2f422a49e7fa78a5f35656dc9c980324717a5ad386dc30000000000000000000000000000000012eae443aae59fdf907bcfe3ee4366e252bb57e268fd569d742456f348429f009f67bf92f9dadd401104ccd2549cecc8255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000016a827938d8b98e3446445ce23f48a7ba0d634de826dd1ee3c71467eb57bd4c24e0d1b4190f47bd841183baceaa2593e0000000000000000000000000000000011d360e0c18b45ace82eaee902475127d8f18aa4a2ec2081a453f1c85ffe3c59c0f7016f966574a7c51bc14f1935568400000000000000000000000000000000186b5d452c6dcc1ddb4f47b07e01b6d64644f6d01cba8498c3059cc494a68bd25eef35cae05885b9f2689683e65161410000000000000000000000000000000000ff826e5a62affbfd6d2062bd329fcb561f287046870b8be461767759cb0d5f1ac904ecd1f136c5ccd784bc11088233e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c4000000000000000000000000000000001010e75c52ed0acebe30fc588961c849b7b6298bb8d859f9a9401737c467921c5e3cda101cd4e38e4318233d12b6c7b9000000000000000000000000000000001884db518fbe4d621403ce00521878c0d419d8cf476a1dfda59b7d3c7af2bd91058bbbf54ac0c5cf9a217beb78e3f98e0000000000000000000000000000000001272cf0ad917738bba052e88baf88347d60f63f5b875d604cf0531c1ba7d43e868bc70a682b7274067106f611f08ae60000000000000000000000000000000006e3236f6a66bd37af4be230d4edda6eaaed661f206ca4852d3004b5f358f184d80be6af81c62e5bc8c88e7a1072fe21fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000004fd1e2fd0d28db08224fa7e880abb8c48dfd0e488df4d2ae5f6649f448193acbe943baf22af4b12fd763e3e4ddaa08d0000000000000000000000000000000008df68f276f356ade28500eeae3b755c9af9b5acac5f5f60827b5b2044b2405129b00e5271baf9a80847d3b720026b3a0000000000000000000000000000000005e683d1556f513e6d093704405f312687c3b9e2de3b2840fff32e88186c89b18d1ac558d960b1196594730a9bc107480000000000000000000000000000000018161f8d23c394d10ba576fb0ceee530ebb95a670f2589d84c0646f693086ecb7ed80e556f3ed9434d7fa488430ccf430e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000017860708943449c2227c0f50cf1274652dd32e999d5f9b1a8d672feedde15e9f1af484a7b9462a62dd745bb6d3c7295a00000000000000000000000000000000064f8cb707494f82ffb6374641817a466af65f5c7d83cc2964e6cb8efd021e0c40934a3ffbb0d91bf8a7a616dbe8d220000000000000000000000000000000000eb37cc9d56fa0dbf050b557aaeec76f9f6d0a6c448ea298af78004e41ecd8a1df8fe8640e77cb76b593ee17658326ff00000000000000000000000000000000092ab597967544fda640b145edcb3ae6c3f027c2111dbc282ebdd48eb93287ae4729cb30e45c1c8999b3a45b099dbf0ca49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f00000000000000000000000000000000124fa4d48ffc5732fb21d465b559e995891fef98370a1eb73c9264988f75caa93fc134fde7f93c794582ba5cbf6bc685000000000000000000000000000000000b71d012abc1558e49831f053757518643ae04f79234fa92023db9c5483bbd872d24eb87a78960f12930094c4f8fb70e000000000000000000000000000000000651cf0016efea086d98e5bda8e1959e20e4947e302eeb021d196897cffde3e2c28f783521b2a28b8de1ad1a131f5e67000000000000000000000000000000000555ff8a930cc11d320afc3e0635a6f93da1487a5764d56636be4e5803d740a73d84666f6141ed5ee6b778a463823fbeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a7000000000000000000000000000000000974e769869719f0ee30895df837cff50d47382461c557abc4b8806b04776f401b76a5e630a6ccbd3484980d03ff58d300000000000000000000000000000000098157f0190e6bacbf34c20310f6471166750ea1b235e46a5fae313f90dddc799f21548088322910bb0fd7e41beb23450000000000000000000000000000000007f00d7d18719db9d91e2c32f51083b42c4fcb43c38087f86879ad6bc99600d4c395586187d26d041ff49dbbe517fca2000000000000000000000000000000000510cea4a7463bc5882d0cc25fa967a0b02072627bd57f9a5863fe5255953732846d4907fa301789bf02af9c1b25211c53961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000bf264c0b7bf68c595b89453ebbd7fe2e64f4ae2c7268ad51f4578c35d48040277f3dac9021997af02e492039348efaa00000000000000000000000000000000083a4fea41cb1e02e5002259f5f7b335c81e15cca93cbc884dc1b08ee981c55f2dd3c0db1a35ac9907435edd7f0ba625000000000000000000000000000000001468e508a02ed7b61f752ac38313345338d2b2d018f719f391c0f3fa1dd1602d9476f3d8829720d17021a459a2732e96000000000000000000000000000000000629edb2530c38ead8717b289c08036c12630cd8c9ae875111749ed893b8cbce40bcaeaf13df4044147bb665ecc2319ea04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001635830ebf227be126e13c634a84f3649d498e0999ad2dc73b9c7360db120dc2216addfe18c00676ed185efa1e789d8c000000000000000000000000000000000471e3cfca449bde0ba2b1e2a5b63d53badcb34da3251313190a35daf694d70ba385976d1f875242386fc74ae0173d18000000000000000000000000000000000986cf3f1eef587bcc70f66f25c60f353e6b15bd105fde9254487e9b522159658d0fc6b6a8a3ea38c27865f1ea4d76490000000000000000000000000000000015a2eccb9c10bc273cb712ee04bef01a11e486bc6a4d220a0f653582af6ba1bac0b5108250626ddf126f16f4015c9d2cb688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e22527000000000000000000000000000000001213cbd035615f09189171b3e22630d72df2df93fa8c14427bb00c34f5b55bc8d1b1a59404bed6549b582537a397eaab00000000000000000000000000000000161072d8ebec2841f0f34cb38a3e1b2094a597640a34178ee951e5c993646ecfc3a4c0dd753e7e76f3a6da5a091f9f7100000000000000000000000000000000077e9c95b6c6f726902392c3a16b5cc71cd9d4cec58c00eadca6091e45bc095e53006ce8ac8827565e867531013821950000000000000000000000000000000018cdf909bd9f38e57ee24c0f51a5f9f703eb3d190dfbf75be00969e9e8f8fee331cf32d93c3a956d12f374f8752c2c79cf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b6000000000000000000000000000000000cbc27995eaeef2bef14919d48a008a0b0467856f8a6659d6e68e47a2d9d41d217c5913aa1d67911325dbd4fc38e36eb0000000000000000000000000000000010639740654bad5c4ec93f2496f4dc54a7642bc92ed03372ad4edc5fedcdfcf37158d3f02279d4e15078e9d5a7f8b5df000000000000000000000000000000000155ff4d6dfa031b0cc2f57df41c1e1b1c81bf5a5cc1e3aa93920e93c2e2e7a71b56ac410a87855400025badf6dae8e60000000000000000000000000000000018e637da048e7e84b9d1654113978fb148a54d86e1d011d7f5a86cd4f1e5bc15abc5b67d00129f53c0c021cf933f399c528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f3650000000000000000000000000000000015d8f6e47b8f07b3e07ae0952a7c8f79519ce2828e3e26b284b8f2fae7432b337de17089b5c32f0081ec6c6916f2f53f0000000000000000000000000000000010ecfcdb02cff772db667266cb3f99f1dc28004ffcadca7a9c64b3b5853c09b7793ca0aadb155257bd64fa7bccb390450000000000000000000000000000000011096a52f3272955947304ba037e8b3fce6b2f07f2352c08d5932f4d2306ca511a74dc044d0f0e1e260ff40b0fac5e0e00000000000000000000000000000000130facbe0c1c6d077e9dcab647a44b049a1aba3df500bf27d1c268f71a59635e702c9ee1bdd68fbfcff7ae5b3e6bd83bc32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1",
+    "Expected": "000000000000000000000000000000000b47d58802579e662f34908a4060becd40434e4934ff58790df2a69a759223ca29f42e658ab475cb92bd9c46566811c7000000000000000000000000000000000091d3a4c58a669d3bf0377abfe28d1817168b2a86375928d95df3459c83334669a59aba95ab2b9957d5ded0bd8925910000000000000000000000000000000005aa9c3fe0067338675099ee32f93bc8a5e9ead94b120dfa391651da40cf1ef5ff79d193b0b14b5926f10660aca6c11500000000000000000000000000000000058200992b111461f4d737533301734a5c3731c9f2e7b55e18887ebff4d5b74dbbfd23773606f54cd6a930b85b89aabd",
+    "Name": "matter_g2_multiexp_84",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bc3eba5594666cec699a47fa13ee74722a80b4e5a1b28553de37c3163f2fdb8b801e35a19f6d99cd207d1642c0d4ad400000000000000000000000000000000104b334afeab36961824611f0fb58bcac55c9d36604ba73e9c8809085c019bd967cae7147c766df6565ddfcc0cd5fc9700000000000000000000000000000000026c3a6a4ace8638bf8ba7434b59c3aebd4bb274cbdcb898ec6a547b215f32d10395f3bb85a9eaecff5ef6d5ef2b50e000000000000000000000000000000000065bc419f9496b5e81ce72a13fc1bdce4738c2e3faacd80676be31db65ba3e7941ea75e370b6d6c0e7b2cdcce80a2fa14f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe000000000000000000000000000000000e7f1f5ead0f212439b4c47599581982712d2e6ba056f36cb04033ff5eebd81b5b41b874a78aeaa98562899418ab04c900000000000000000000000000000000095e45da9a4b2578cedd13af71e289d0067ecca1f09c014a294e0b250d1e8243ff98a9761030ac855a9d897cfe9fafdd00000000000000000000000000000000030b44b150d1337a3ed6a77f7b6332d7c8103da1aef0d445ff7467b4863e4c830fb782a81d01a6bf97e8d52bf333e78d0000000000000000000000000000000013bb76800375a45b847a96ef6edff3fc3c30e3d45bb4afe04230107f6a1802794e1dc23431797bc5e79e0d5ac6357eee4b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000602e0bd3d34415ddd517a73acaed5750dcfd68b633d51003edf79a169ad7a3ca2541d7a131c317c957a9597a753b5080000000000000000000000000000000007a964539081fff51e0ec24bb71257f6a1c513fb0047aad84b80180b22133246a1f62958ead75e4b2a68f973d17f1230000000000000000000000000000000000f48fe0f5b5a95e48bde4d8be1b2f352d748c1201b064bc88309a709b285f81260d4535d3e1dc7f1d6ee23ead35abd9f00000000000000000000000000000000135b480fc8a72248f7a4898fffc6c18b7f2f5b1af5cc3846610c299c8da252fb71190d9f761e037c6b47595bab6a03e56765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f0000000000000000000000000000000004773cd2884e52c2551c1ea0abb78fa17caacfffe13f18b75588484b8bfe781e623075bdf827fc82c6ed7e1d4d62081d0000000000000000000000000000000007e6023fc0e409bfc7d0b7ca65fa0e8d88bf1b4c202a8d1f0e1c3479b0963a646d16795fc5a128a54e624357050fed4000000000000000000000000000000000039f6eaaf99bcc9f4d8fb994a040af0d29c37960e9015d4e48466a9e554da30975c5534e76a1f08a55ed8ce7375b70100000000000000000000000000000000003d2b097d4afdde83a01cf2b4f9d12f77c8e92a8cadc225d40f974ccf385ae65bed1972a365d55e24231d58abed4395a2eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa00000000000000000000000000000000047b8c550310ae246e43b513d39e507f1dace7bcb543a49ae0854a397f62c408ae3632c94d172669ef3e013781796ecf000000000000000000000000000000001592914e260afaca80c0a240426c2828239ad5e256a707530f49cd65e9da2e4bb14a7d6d5978f52c04130a0d434cf4ca0000000000000000000000000000000006c0b8448ad87350db130373778d414deb738d3be97fba25c816826f59e3e926f44956c2e2056b7d769278cf56cf6fe0000000000000000000000000000000000a42d716fd83071bfa014a9b7af6c164d494f0347aed953bd2c1c97ade087a8bbea9f53c507fc0b22d520f28cc5d480cf8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000018026c9f6c86219d0be88955ce0afc3cb637b1c3a531aa2722c56816d368688181ef2fedf1525daec6d9b1651b71f27c000000000000000000000000000000000b40b15bb0621209bf9e33ebc27a7502d90fd3af62a1bb8f54a874a14c105df34ae34a43fc3805c1e4817ba30c048ac7000000000000000000000000000000000465262367e30ccc24632d39bf3af9cb160e97049d855176f665a185c138d5c529d11e53e56c65506e3e30be7b48c6730000000000000000000000000000000009485991319a311052d883b45911be12cf7648b5ca104ffe77594472f7047c803b8e9fb753b98645e630b9913bbc947e28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fcc0aca0d873cb8733ff7e2ea02b3736b737821af2db06ee6508e161f6159f9d944372c513a03cc4c9e30a707dca0930000000000000000000000000000000015c3774f4e0b30c9532beaa2f7f9b777f8d46bfd3888d6835f4a5a046153a98062efb17f78807fa17b3a995ce720c0b900000000000000000000000000000000083d48e01d2fb58244861a74a1261063f7d20b412c8a44f9945fbe373cb4b9a7ffd4c4ba4054ece0abddb6c14c013ceb00000000000000000000000000000000133c4976454b7be427c4c2ed437bc2e882854d2ddce42d2f97cd3fab1fcf60c3272aaa123a0cbecce1a774946bb7a8a0dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000f6ae7de1dba3b3030b208f61d182013231c4666f134b007b52d36bceb6f3cd77577be7b11abc097cf9618d351d61e270000000000000000000000000000000005803904e3e640e51900805f930638ddd8b86cc1bd50cbd32a142e10d85044cc52ff769bf1b40dcfb7269c913d00b01e000000000000000000000000000000000e6997b1f8bb649c56de5c4bf9968d19712abd22fb7dabee19e0aebd1b13adcd3e8b202975b4edc917d93adf087fb539000000000000000000000000000000000a32384fe03280962c5f575b47192e5ef3111fbbb0a01bda2db1e9733471f11eac0a37df8ae1a891de311770c482c06b0adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000531fad86551ac6dee15fbd62cb13f38d8d5c89d23a031b9977f110efcf16501534757bc5b93f0250ff02d6cfdf2009a000000000000000000000000000000000e6d78343049a68514271fc785de053ed7f50a7774b87f264c42e03e6f8f86285477f8cc57ae066ef0fde237c8d1ddb30000000000000000000000000000000013e313484da4d6b85634c5306444bdbe45d7db823616d72821eb64a2bb5f352a4f7e4273fb6557039fa563ce1b091bea0000000000000000000000000000000009a40a984be66c3442fc8946cc42eca722187dd819be9ab34a9c3b4b0de7de3d5f126c175fe84c51a6f09e18623214f9345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000008a76db551280cd43d4608e9fc629a021675bfdf9bc5a021546b92f3734acff1e97928850716b94d15b7dbcc4a1e0aee0000000000000000000000000000000000b2262872c268782e8f27ee8fefe0827d45131555e755c0a65a7c8b4185269bd621412b653348d7c1111d681f38d946000000000000000000000000000000000dabcf0f847045e01ef70ceaa32455f4c962e4657b840f97a1cff7cf5073cbf4ca8ea75a4887076f155e27e8d7406c95000000000000000000000000000000000a9c0ed94170eddfc485d9f1a770a8b493d4a59bd7156d6cd4b95b55bffa1b597ae9d6fbe529dc0833634d75906a4aba5ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c00000000000000000000000000000000170ac69c2bf9b48715f445524cab902b18ce6dea7b258481cc59986ae61c8fcb6708b1457be299a6e2f6f34dfd936fdb00000000000000000000000000000000107e855593b6f3bd2982a65167ecead47039065c9ae6e1bf963f81d441f0ebb411eec4b3ed1cff73044f68a4c114806a00000000000000000000000000000000063b470d158ebb4828e875c3dd0ca29a4fd2cd2af356233885a871cb5b77402090f29709c6d6a78f612c8ca4df2f4119000000000000000000000000000000000db75a60fa0b425b8cd2c955e21846ce3c407cb3f96c472cb412498143cc60212de0dfd0bf4de53ae3b345232180b4ad853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef38000000000000000000000000000000000389e79154f627463a7966252deab10b5e809b0c2a9e90989c56d4076b834e2081ddae1c02a9e01b71d96b772766fc680000000000000000000000000000000009109473c7aa614334fde410951a69ac45967f7550890e01b05279b6dff394775dac51d583ae0aa82edda18ecc5e66240000000000000000000000000000000019dd51ec6783c1618a7f12298e38cc75d4fa32fc31438f67eb15419a2f0e9d4b5f70ea59b69e531c868475cada519569000000000000000000000000000000001121c7a6cbbb54d5e30a11a73c158237dedac46385aa15d93592a30fb64fcd94a674cc77afd21a611f704734337905596dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d000000000000000000000000000000000178670fb06f5eb8a4f182913f46f66147deb3f9f634d620ed55da2ccc88895e75f76f55b979e1ba3c3db29710050c7bd0000000000000000000000000000000011adec68ef139716ee081db7122e911ec5a6e1fd7f681a96a713dddc2b742b6e7cf7485b8f45e7ebdec8b1174c02eaf100000000000000000000000000000000089dac9a47cbdfead8536d6cfe8b94d316123bd92ddf30091e16711ff4651c4e2d8dcaf6c72bc159d7de9fd832c6f5be000000000000000000000000000000000c40b871930f0c6826a943a229112f8bf9a3b7d7e07139e1a7d99f97601b6ca8cf3638e0265743dd732cee17fadf996721d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000001040c4cd3c28a752295b115fd80c8ef0e538e1a3906e0d326e46585d633140bd6b8231f50d50c8e7a9018a625c4bdc530000000000000000000000000000000008b966d9433bfc3bede4ddb005cd0c256a168437c31b8ecc83e6fefa6f4b1f2bfd057c78f82bb76279b74a2f7de493b5000000000000000000000000000000000c0f75db7a17e4b712666b16c31b10bb935e7127eb9a0e59e35ec54814a9de9012210ff1862aef5f765d4f7f673c4962000000000000000000000000000000001015e63589a8b56aa643a79c5a433dcd8f4933a10edc9921bcaa7098af435f7879a40868e25d1ca6f7852800df29c2eb3d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000000fab22ab380043b01d312004057488ffc958168f8fe4d9c86af622030121e14a46c4308d711d5fa9a414b9ef75d51ba300000000000000000000000000000000047c738fe5272e695f421ed463ce0d6308e05c23b6bd0973df9b55ca96d89c0771a45d53b4d17f30d8cf08edbf94490c0000000000000000000000000000000017bcb3ed735e5a302f76002ae82f4ac74889fa0e966f0fb611fa6a6a09440bc923f447eb6aebe47eef917753b7427efe000000000000000000000000000000000b189d5b64578eb53ad850c826082265e506ab620a9ab9684cc2a53718f26befc35e9431af012306a6190f144a9632bf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000049fff545ac239696c995eacc560580a0328af07376f5ec819902e30d5e7e40d5fe07295c4ccf54d5c06134370373c1b000000000000000000000000000000000bff448d5ab544a8cae0cacd216a6b6d48f0abe1b4bc946d95c1a8c4ae44bf049c3b572675a5e20c1b4188fa27a867a70000000000000000000000000000000011dbc52baa00712f66def2fa8fc77bcb07431d3285774e2517dcca65e611f07aac265856cdef0c1637def44c382230fe00000000000000000000000000000000090af0898dd578123c65d1f818c3f33866e4acea19aeafbb31bd8da029ed1daa2d7ab3b22147eb32a09021f7a78fdf2acc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3",
+    "Expected": "000000000000000000000000000000000d52fcbe9f1776477a9d2149ca55e0651fe9d098a67209ce2e7d772d4901ff2c70be432b53dc94886651865a81ba8c620000000000000000000000000000000006b54871379e2be969f86c72cda9acab9bc99f73de987f17ab8b25c63c55ffa2cff61b87e8c30d9f712afb62a2b9cfcb0000000000000000000000000000000005652612b19c38650d1babd4772722ae2c560e2914f2e246725cea86dbe1275a981a592eb55077ee4b7c6090e84d2ed3000000000000000000000000000000000ee37a6d42ce69aa67cdcacb19efc230c6c34969a2e081ac77e8f9d45128a6e8fff923c7647a0f168fee18342bc6d845",
+    "Name": "matter_g2_multiexp_85",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f114e56d10dba7945d125fe1ab7871d9510771548d8388a2aec8a481de92572645b73631f9a60285c3eebcacb3bc0f5000000000000000000000000000000000667d3f31955df11e4e7896a1856fbd4e573f1cfc906b3953b5806a5d01dcdb96009d9f148156a3828e822435f722c5e000000000000000000000000000000000d7740ae776eb4766999f5671315c8965ccc84ff71757e361fbbb55babeefb96265c97df8892acdd6a9166641f656e62000000000000000000000000000000000166529d1a76ad784557384cb971728dba298baacc2f2a39ee36516bc7a761e9a7c29e385cf5784efb9f6e60e998b01e864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000133e0b08430d9318d98bcf58b3d8f51c7b717fab56fe25f434bf521f830c7d4247d87d3df910490be2ad38adaa8eec26000000000000000000000000000000000e15afaee4f1ce6290ddfbc13cb887e540efc3fd8150dfbf3a5e7c759ccb8f334ba26953c7bbc43b5234b857159f6722000000000000000000000000000000000e4cc685524d42ea5e435afec7b3d7d025e93ea06407a28c246a39dee8ae77514a0bb2d5031f7367d658027299762bea0000000000000000000000000000000001b231237f7b0538d51adfa4ff92bf313507996cf5255f191875970ed4d946cffa8620b44045f4bcfd8f89baadd331fd93064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f53900000000000000000000000000000000084128f1848b2b244e4812eccba01287b9d07e85450459c8c42b01180bdd843058d9926f39e2fb5f610651a00233e31f000000000000000000000000000000000055ee70765f2cccac966dc08abd4bba0d004b379a2c6bf188f300f5d413f84e77ca1d462219bfb820d7f585b914a52f0000000000000000000000000000000002dd8f1d1cd85a5e6ac793f7e1e3cff887204aa4a5fed92f2088c06eae95842ab2c04d30d56f4b0fcfe61379e8e7c6940000000000000000000000000000000013e318f8b6f4165a8096c76ada440154901de42d69c38e66d9df4ffe5476666ecf7068e7163f29f04972682c43f3b0fd5e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000003415c8bab713aa18d3f0d54e0101ba36793e6e9dd3471f8eed9a15e00d8312732a9ce88b5f0c30207aed92eb173ac680000000000000000000000000000000008a7e145e9576be8ba2fd980fb1735a2b73d1bf5f3e108878b721b6ed8378b5e0f03ecac179a6d148541096ba483b40200000000000000000000000000000000029e5554752db8bb87d58275268f24ccfcf3e0923744d57473d54a72e2cccb847eaa8f3bf638833a934c43930fbf30990000000000000000000000000000000000e0f2ead2697110a132c4ce1643b97fc652dd0660deadaa4e0c45e7ebfa64cb6a6fbbaac7c4e2b725beeadf6881ae5893f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000014325fcc087aa108f152b42759cbc02cfa24e7e7cb995c78ccaa9a283ec2029c08cd747d599e0685d365ee99eeafca880000000000000000000000000000000011da603d3a1128329af19e596ebeaa4bad034c59581e9fa2e42a0260032f84654bf5ce22ee32c34eed7515d7fb0fade0000000000000000000000000000000000189cdb5b934cc1ec7ea0cf4b8158a1416712bb59c1650e6d244de33bebfffd3691b499b3ff8255b1b513deba709f7d3000000000000000000000000000000000e7ab2b279d0d5933df25d8fc4faeb8ca907e7bb8588e618b92737fcb6959380abc205118d2e3fc128b89a2ead5ca906145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000895b6777e677732c74cfa82d5348c4c8ddd63ce10347836f5140b9a64dfe631804ea3be8e20bd4438f5e7fa14a121d80000000000000000000000000000000002422cc4781f007f732239ff9eedc126777d6ca0f0365dd90bab6b68c9e3d02ce726726a6d30d7d51a1f0b45aec1854100000000000000000000000000000000048af8a79663aefaff77a934f0af3a09ba02077c13a794ddb88e5c679ce348b3ab0fa217954ce1422f4e212d1383ebdc000000000000000000000000000000001190fec6c510b0b16e1505f737b25dc2401e9fc2c95bca92aa5d6e93b284b766bfed93a80b137e5fcb339983a86acd41ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000014fbf4d005f43563fb7408d1f20f672c8983120c66462ba9156b64a287e66960fecb41ca129b6b14466a5a0de91b81c50000000000000000000000000000000004fb283724950174d60f64af7bc8a7d059431332c8f17769df33f6607d72633aae3a8d595cb8d5af3f8909297844b3a0000000000000000000000000000000000e187476a19280ad9f33a55c50f37f765e343f92938e247ec9fe099c7f3df65e24af14885539bfcf3efe3bde9f2700ce000000000000000000000000000000000f086e6b9e845fe3b0c5100f82bc8aeaed166bed9fa4d34bc03ed86342a997101c508a4c096c4f67cb5791cc1a1fdb8187bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000018dbe48c54347635d4b6bc17ff5ba390a73925f1b180d2c516eafc0936aa9bddaf7317cc0c211fb2a7f7bb096369a45d0000000000000000000000000000000015544c177a4b8018ed60c2639b43236957c2d995fb0f32523654584b0bf052e0930366a93406e1ec5c6d2edb955e811d000000000000000000000000000000000802d2cdbc5e15b25c77ded4bdba087f1d5760e6ebf9549a37f3314b1e88d3d6f58da9d8c6e9ef85028a271b83dd6242000000000000000000000000000000001577bfeaf213ca8b0983cb178e9634dd18f74baf02f6ca31b2e3b287d80a32d4cf11afc71df09ca5bb0bc8e60fc7ffa968cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000007c059044ce0c15bc527b19ce85cade8b1d5a9cc6dd304ce9a3c461e631e17c4feec52a0ab5cfab6a2270c75f73df86e00000000000000000000000000000000076344286cedc8c180e3bd762f12ac08f0ecc51293b9f9b8e7c0056ceba1bbb6fab4ee39cf559fdbd601db6c3d201199000000000000000000000000000000000bf6e708d0a4fd85c7566804e19f21f7a00bcc3bd7135f6639ad30aafef2ed1e72c84c8995b0e59738c2bf1e4040621b0000000000000000000000000000000018ff3d0ade15b690b6e306adaa5c10796b78ed7f8a984f637271cccfd39fd17c1e8288a11b051ca94de2a9bd04fa96d7a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c500000000000000000000000000000000025cb808922f6deb0bed979b80a675d9324cf25c53de373534d771afd919a182af9aa1dc26a2d0284887121bf4d6b6470000000000000000000000000000000018970aa4f456c1b203817322df2e222516bce67ff9ace069599061c6229596e506c0286171f3551302e45b7d3b69a39f000000000000000000000000000000000a57d0da60f03fd4a5664546f9809c771ab6188aca5102c31f26b09950cadc26b0275417ddd9c4f4cf29794b739733cf0000000000000000000000000000000004ebf2bd93d7921d8bd97ee71cadf91145e064a33651da2604ed6fc8e08b1b8305005f12fd4e6b68b7b6a3b5cf123b1324c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b000000000000000000000000000000000c8ff29d0333e3f38fd8af91ecdca49e54ea5dced71b60d693b1bbade99ae668e4f994f7a5417a08a8ddafa410d437f300000000000000000000000000000000078ac1d0898a9e6cae29fe6b50e435e5f543d0ee233346728c46d659c4338295f27b42fc4b2851ad5035feab2bea8871000000000000000000000000000000000b3a566d2ef4467f21c27e4a3dec99a26c304b32ba1fcce8276a8518383a7de44de5b4011ba738dbb8761e67e36115560000000000000000000000000000000015a0aab8c3d51fc3fc8aa35dcd07f8a08188976883f9d3ccc87ee148525f2115ca46726a2e3c550167c169977b216d6217ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a00000000000000000000000000000000115589e8e1440edcfe72c008f6e9cdf13fb7baaf70aee16166e7f32f4651db784f4c5cac15d91ee13001169fa777f0d00000000000000000000000000000000000f86710678b01c8f648bab2289e8f90648d9470cb13d5145ade526696d22508a4a59164290586c2c000dfc55b4a20350000000000000000000000000000000019b300961b40b0d9fe6e292e9357d04f0483ab3a8cc6f8f522153c51d22de8e96a812adf720d13ff7d05d1e68264638a000000000000000000000000000000000a80b61ab051ce413ec838167fce393f88c8a25f403bdf07cb60391fb15306a5271a7042d36f7c46b5978106a7b5293c4615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000567c33d22805319418cb1ea7eca6205a6c44f1f881c03e37bf3c66a1baa5153473cc73b8c25d497b0b0057ceb0395960000000000000000000000000000000014d7a2bfeea6a746e709f6108eb32581ba38a617e4450b3567c77a992988d91f4da31b209286f8e9fd0d7b8628aa6c4e000000000000000000000000000000000ae6c9fbf0e06f2e38e91699cd21596ba90f92f6022a4f3c7c8a6557b7e1331283bd4d7a7d31d77d9d7cf70a2945ea1600000000000000000000000000000000066b8132c73e1da8ae7fec9169770a188b686f223fd0306441356040bc9070f34a47fe1bb8c94de9fd7606c18b1d2b1dd38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f7000000000000000000000000000000001460040d0a19c37fb0736ebdac0324d8a38c94a73fc5f602b7ea5b7255be9d4b6ffc22fea5043d948420e9ae3476f56a000000000000000000000000000000000b37c0078ab8babcefa8874c6cd1c5184d713b976852d087ed84337073fab3054899859d0fac2f4351bb75ee0e534fa70000000000000000000000000000000004150f3b98e6166d9d6b0388342042dd8eff9b8e1239f479330b64c5b316f98fc7bb401b737efb87e1f6663ca4efa26700000000000000000000000000000000043e6131c1ff621fd6f8caf0939487a927550343e24425ada33cf622de757e6e75c9affff9f04373a954557181641617364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c74590000000000000000000000000000000009fa1754bbc957d2a8317a2eed859457073571379cc7c6d65bc6a0b5829f8142db77654eb98a2bb0cfa5223a27d756cd000000000000000000000000000000000cfe8b8fbbff7507d3d74f4f550b4c85e19b8929d3728a462e12b4008c79014103153c69ed8dc6b743e1b6fb4720bad00000000000000000000000000000000017ca0c08c320c12502a1dbc841425694bde68b7806eddbb40702e58ed26c7e112f9a821a6c67afed174f51896ec2287300000000000000000000000000000000014d08df9cf825b07a387642ac9959e8cd15ea8e752231a3047fa30816acb1ecb79f1755484af9a98b993f50128c2bf5031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f000000000000000000000000000000000273b64e867a9111e257c9b32484655e4d7e676ec50f174d9ebc9fc4262c037b176ada941dd8c1abf645e275dde04f4a0000000000000000000000000000000008a63b9604e96a5034d92e3790411f3112c2c7cdaa056f9f1bdfc0b164c37fc9f58dbb566337132cd1626f9ca2618f800000000000000000000000000000000006a661167c9fb6c26bfe0a3902f309fa683fd22729bfcb433756182e7e1a406bf44ae1d13ef0228534881daa339394e400000000000000000000000000000000193c6c5ec200d225c43c6e37cfd15e16e49b7d87e5515bb7b4c918903966f4f6ae0d42af6b98f6efdedc9b0301fa1c0f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f",
+    "Expected": "000000000000000000000000000000001403c7e3059135ebcf5e752011fdfaf66e348135314f3f4239b066e1c6192ffcaf89bad4228fcc2be19a64f4f5386f5e000000000000000000000000000000000aadbd8d0e53d5b409f7fa508089337bcf36212a3f613b37a95757793dd6b0ca99d1b3578ad8020d46e29c9c4197ea070000000000000000000000000000000019e43bb32f92ed187fc32d9dbe24a486e38316a3cec0fd7f7c19b313af43a10fd63738b78e609e04a083de6761d53a90000000000000000000000000000000001490da7d36ff16304b27f6e57412975497e9f3a6d35cb162464bcf69fe141d34ae27a33afc75a2802eb120e90d4897bb",
+    "Name": "matter_g2_multiexp_86",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000038ee0c2c409d8832437ea450ed705589c82791b8319fd0ba6fb4d302d3c5b73ea0521a0253716e5810f03fca2e9dc720000000000000000000000000000000018c9d748aa685bf6e11e6e4b6ad2290ceff59c8837a088b41a08983fb2c5ef077adb0730b298c5df9aa02a820a19a4bd00000000000000000000000000000000015d248426e362ad2489c0c6a567d80b22d54d6a79e198198a771fae4c4e97eb317da9feba8eaafc9460ef45b1a5e5690000000000000000000000000000000005a2342412801cb37911a04d7ee3b1e5d3dce2a06e0658d59f2ddcaa9ba32804a1ddbe8f4d00f4436aad1346ed1ea5344aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b90000000000000000000000000000000019d49748f05458cb9b316e433b0d341e23bb5aaa724b824bd147596761c11efe8f4940eae09e302e563e14e96b814f4a0000000000000000000000000000000018011e7ee4988da168adbcf81cd14a9232edacc06bbfef0fc78dc0f96b5ac86ea67be8661442b5ef60e3889f3137182200000000000000000000000000000000175a2ae3bdade6551b23656c16884ba0fd4247df4ba7471cf81022d7e224b23490db153c8289f95467ddf9671f8b6cf90000000000000000000000000000000013c58c0f55c46bced98faf3865e3b6a836252f252e97b6d2a799b574dc569f09ce33082880a4d0c3b8a2c7c0d4c30eae22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000ee45d5689a8ea6132d5ace000699a157c1cea3c0c98b38d504153d64fcaf1702ac7a1cb0889539d6b15489fef415aef000000000000000000000000000000000b320e0cdedbdc1fc5733488e6d2aece6386a030adc36b0a69dc3809827319947049f3861c2edc859797d30a3689322b00000000000000000000000000000000194096079b3a1d6ab1080dc71bf6d5734bc7b5e7f30bbb0f9b95c9495a6bc4adf76e198fc66accbbbaac215a8932d8c5000000000000000000000000000000000ec07be0cfa9b3d3a64c016471d9e6d25228b46dcaca6e197be00b9ca5087162c35f1d6326a3cf83f568cb06da8c5220895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f6832000000000000000000000000000000000a7f3a3fcf2e7b0ada6d4fce179bdf229454002f1271a39d5e99daae72da549c6ccfc7c574f35bb9784100675c30b1120000000000000000000000000000000000fad14ab095fa09bea919ada313727e7aa5aa06a1cc7746d006e3eaf70f79c5e4001a8a8de03540b45e0598b22710e00000000000000000000000000000000015345ade62c5691690c181da09d8f39c1ead42046987b8c7c975d40690a286a816f8cca519731d0ca23349c54b30d8570000000000000000000000000000000019f0a32361bb6ecd8b1d87c2e15d31c0e0cf995eac9facd5eca123c0799c465f156b0142d98e0f315e9b3595974a7b824c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000017af46e78904915e348734d2450fc6e1938bcf002989f855082e3b4ff3366d81ee8d28293609c3c3b11568668b1305f80000000000000000000000000000000018b0b3859763c2654fc00792a5193b7317fa5051bcfd15ea42be2fda0f43adf322219f34e54b2446ef73a4562151f9a70000000000000000000000000000000015c23509a1b324c649ff878d004ab5f253d041670ef172ec4dabec7a525d5ddb8f9f62f383e3f71b0e9c98532e247d560000000000000000000000000000000003a38564a55fdbe05b047281fa153f736edbf48c901749005473255333590f967171a6fc88751eaf57a5335bbfb6ebe86d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa00000000000000000000000000000000100322c4a92c136437714a6586c82a6842027ee218bf1fdfffaf95ce47c9c8b6c8f61115b092dff81ff2e645d0a7a4340000000000000000000000000000000013a91ed8629acb5e770683015c3c248255d673d4b2e6c96334d1c80326d1a8b4b655c81175e4a914a45fb37c1f178bd10000000000000000000000000000000019075c2eea3f64f42be82fdb8f83f2c68c08e858702a0225d869143c0b017b76a7a40d809116ffbdff6700b288f5ca3b000000000000000000000000000000000598ee9ba9d56400b59c7f5977aef1e179855a37179fbfe97b95f19137b6034568e5c7f616943b4aca804272955d42334cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b138400000000000000000000000000000000025a526b137aaab5ac1b5f8179a18b06feb7c905b4a843cd55e31b7464c2b6d432b569e9bfc3222511c18255102aba5b00000000000000000000000000000000090c20c9f78a242e52daa339d5cc1c3f35aff7ab802a3e4366597db8b6ca43d30fa0fe8d9484e49fa4fd0bf5509f19e6000000000000000000000000000000000e928b2173e32e5fc9c373a2a6f126e1a3a472c01a5e87677be0d29907022b9a7dbec3340cfc89e67377ce472c2d5d4c00000000000000000000000000000000147b4eaa2dcee39b918b7cdf24483b29466120677e5d42b51353a9b2fa207bd911d9b391142a13a212d0ab38adcbe10796111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c00000000000000000000000000000000007790cde9ff8af2d7597d33909f00963eafa228817de1ebf4233ef0831202700b99641318186aec80ac913a1b1143eb0000000000000000000000000000000009d42ea1386d8b019dcd26068ab156f399c35b7d492722a20da0c915f7abe44ba688d9486f4bbb44268542c5a49168930000000000000000000000000000000010611f233bc1c4af0a14e1d1b945c91c077ec3dda592e2f852e2de41e09331664e1a92f9a0b7416c50327bc943a17b9e00000000000000000000000000000000048614243262dd070a754f40652b96a03326fc51273dddabed85df0654890ff38e0da7abb8190e4ebefdd6f78a5fec509d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d0000000000000000000000000000000008451d24fdc873c61db44e57372d43c35a2a8098255f9aad3a6b244913b86bff6444042e391685b1244f009c5ccde935000000000000000000000000000000001177c2da9972a2b96afaf866f97dc149482fbaaa93e194803c09c8334c2c7025e08cad4f7898959a57b07a545ecf76ad0000000000000000000000000000000016f40426cbd1f0f4ca5ae1dfa4c3960a6fbd51a1b5b24ff5d03fb9911e908406a0ecf4f20a78a280d24dc9bdd1c0799b00000000000000000000000000000000194a8c55f549da1842cc3173f3eb7bfd70df26b43a3059a3590992e34fb19b2caac4149f64d442965e166225b9013e2b11ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d3000000000000000000000000000000000a97664c1d7624cae0e969c728a84130fe260581305435ff8ec701cdc51a73977f58c891ecee637eb6b7c972069ebbb80000000000000000000000000000000003f4ed6a9e9f4229f0fb35394bbc10da9adbf4985d4453da64eb312ec88cb15bdc189a3b5df1af3107a36fc001ec92ad000000000000000000000000000000000ac552c5f6170a70563fcdca8e0c6a7c6135af2f9d5ae6f60a2c459d1be4cf76ebcdf9bcd891db8a1e2fc905a23a97b4000000000000000000000000000000001734a46c99e776d1ed4b807f5b313562e0989ad5c67dbcb961c134f8b7b7601c23308839569dc224bdf7c370c4498303b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf440000000000000000000000000000000000a523182c886671435ccc75cbc78293274802c6142465acb31a1809e43b1d656ed9c808068de167b1ab126ed0f73a4490000000000000000000000000000000007c4616080b5a002fea3589d54c7510884a3ece705d27dee315851746b1ee748e8a08d3516d8c6afe1c0482b960a9c62000000000000000000000000000000000dd1bd9b4b9c140aeb97887a0266bfb5696813fea034b78bb7d0cf1cca15b5bb0ed92a97841c8d8cc614f7721b8b7e040000000000000000000000000000000012a41a8941b6f0e4c87f8188718f9bc75305d41d6f4441eb9682473340fce0bbb463e1b922d3af8daea32b8a8ac9c3b4a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf241169119000000000000000000000000000000001763ab2b361681955735ae00b69f26e06469391af993c8dc6f2e1dffb52ca01e49d58d6e2249e7433ccfb5ddaf8fead40000000000000000000000000000000003858f3bb01b2393aa4d4d7889bdeb0bb9bcde0dcb9b39c4ffe0fcd0b865baaff75b676c715be275929ff4303c416e0800000000000000000000000000000000086d64bd1302b0b3a620b87ac29cac3d9e606513ec8b47898cd852bf552c1364291aaa842616b92c8936e076e59451bd000000000000000000000000000000000967c9f59c15ed02c9b2da6e76fb0bf3d445ba849010afb7f9c994b1ef6a05ad577570d4adad043796eb90e51537ce5187fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c58945000000000000000000000000000000000e6b0da7b406bcac2dbb90fbf430fda6442cc2860ce633ab84404dfbb426949d55ecd72992da1a2e8e1ce229b599232c000000000000000000000000000000000fbe3a345ffc8fb85cedc4b8dedf9d952c41b4ff6f1c7ff4cf91b2276621969d905aa9aae5fc89bc516f96b9bd1bb3c10000000000000000000000000000000018c2a7fcc35099c41bb851ff66abb047e2af9cf4fa9fc45f030124ea2c7efd26e594abbfc7a7f258c8081a3a80d15105000000000000000000000000000000000a27cd33c2121c9c542e27b52a13275ef7e81dc0c6ece883b65e71d2bc3e7246f95aef7c6b41eace382a1400568cf298c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000158bd8e6198d22b52efb7f3b945668666e1190a4a8e70307ba5c1b737316a8f8568092f219f683c0f53f56f25745d4e600000000000000000000000000000000097e64e4553371c81a9bf553ddd9719f59b329284eca0d76f023d603c29a034d123ab777cf173c5f2bbc66412d69d4ce000000000000000000000000000000001298cd5501e136a06ad4fcf87a75c0c7b96c73e844863b74bf6aa581a0ea98c2b1f608c668743a3e37ad5ca2074af9340000000000000000000000000000000017ff9f1336d7f2152f17daddde9d3e1679cab8120ed2c0288b0908d4e2099a08c9bc6f79425f004ea3ac4d684abff6dcf29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b4989911700000000000000000000000000000000115baaab8f0331894da531ab557bb454e2003010ba1dc1d96e3d983d49b1312585c6d4c43d85dc074b23b2fb28c8a1d6000000000000000000000000000000000db1621b721c8a54ece26a355b190af5f3e1dc1b43e0827a1912ace651cbad4b980e77a4c3566aa809157229b234c808000000000000000000000000000000000c594e0ed3f7ee55886e251deef9732aea3de11f094ec53907a843b755add8fa5d00779a66621e615ba7772ee821c4030000000000000000000000000000000004e80aeff6c4b85188903b4d2dcac4f94f7cb4285a38f94b0becb556d83dce8735d1db5810b409d45a8dd1b9a6dde29c125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000371a74468ce2ad90e19b7fe3f57159dffb1b0422b32ad693b2fe6c45c5d371b97a90054095da887019d25c1ee8197800000000000000000000000000000000010575e1ec9a3e609ca086ef8bca679c4548482d9e0da2e51878158ac8e5b29d824c31ad7ff642041e748efc50c2514e000000000000000000000000000000000ef36130380f1e84b2f462b5f970abb8535431b79813015261015c6d7e74f038b47504de01794840d93fbbb4b386e17500000000000000000000000000000000018419e85fc2d75f007d1e0e02c1975332e03d42c3b41c50c3538c3625e702161cdcf8913babd2995aea7566ff15abf2bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6",
+    "Expected": "00000000000000000000000000000000125406a942ae0119575453beb4c093d2696d3bea7bc031d7a586439197f848e1d5a82b925b4e96138a3460eecf198ffa000000000000000000000000000000000befcee6bd1412c54674a3d519dd2813b87b18f2ab3375a731197e9f539f8f8fff634f15647e7fea3c65b93594343c2000000000000000000000000000000000011e4d432ee6babd502a9cbbb5cf4839dc6da6176b6bb0ba51d99a3587465f5f3f83f4d4cf2c7e6187de93b859ca61d800000000000000000000000000000000168509010b867aa198fc294a5879ce14a51503c1d0e8fbc02ec08cf62afbd357ceac24b633bd0fa99f83dda92e10724b",
+    "Name": "matter_g2_multiexp_87",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000070a0d060c6e9bad0d1bb15417578daaa8b7a5c984c4947dba10fd874d93fd1e3994337c87799d78a674087678d9168f00000000000000000000000000000000128985b69d5d6ea0ad0b19eba7c2b430f5242a7e89626c66fb83b58ca7cb65a479de4b2fca6886cf55b8cfb52394102a000000000000000000000000000000000bb0bced708571662af042d18956b5b7d797b61aba70823618682287deebe69bf1f9a94ca4059e0570e25a39e60b9a8b00000000000000000000000000000000193f0793324dc78c40f356dde030b632feeb1609a1bd75ce88f0d313a0864dbf1f5e92826870866ab9b3c98cd1c12aa508c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000000a61a310f90a5ffde617b78f784b2e699cd77e7c3e7c483a2ccb768f94d68e59a2a4521410c22ef6f21ba589ec3abdcc000000000000000000000000000000000e6568c83e0f7e459b27a28e5bf954983c5dee478a009c244da16041e710ddc67479cdb3da6f47e7203fedb8f765b2490000000000000000000000000000000001c5cf6b948b85a1c426fe932cd87605f1fbf6c932756eb1bfb43beaf012bec4612d8dd0840efd4cba3f5394beb65112000000000000000000000000000000000e02d5bc20c40d7cc2165a21ab37c6e4eb71322c01a43f2085f93b5b02bcabcd668dab90323db0f9288737d757997631a0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000165287d72eca1ecda5fe16a555245b0a34a04beaf9177466bfd88bbc675442d206e70f7a2063b6ed0e15e9406232f5ea0000000000000000000000000000000004c0608bd7e01e65a15716b0c505111a3abb0abac3efb846e05e8db59c063950dcee052f04d1c4e9e492bc6740fafe6d000000000000000000000000000000000de897f7ebaf9089f7e198ee41e1efd7d84fbec7327799b9293a489965cd36159442eb0dc1f79f6b1f122f592b013bb30000000000000000000000000000000009774586dc359e5d20486f00dcea6ff93948c5a8b74058645d1048fe46ae3330dd56d85204d328f43f15e674020f353ec252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000438ee51a560aa419ad6ae45e1014c38b7c43f1f6a512bccc2d4f10a35838369b71799fab4b6a754fd938c1a1b874fc0000000000000000000000000000000000c1491c85965c0b74d08f5866ca727fd30bf641a6ada0ab8363ff01916c37d10b1b7eccff79b396c587d9beca2c826c0000000000000000000000000000000001452f254ceae9626443265ba31a1a750a425f2a7789e69cde16b70eb319c744a6221e74a9e2881c6bafea161d29638df0000000000000000000000000000000011bd6a1bbded174e9cb95d74492f7b07a755339a6c40f2a1a76debccc0f3a32c7017ca4e6679fb2c038c751f19986f526d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b00000000000000000000000000000000139157c34aaf70cbfaa82be655281b085e37d6406df4cf8e291b221394e91d9e3cf04d431f15436064d0bfc8cbe13701000000000000000000000000000000000353fcf6e587e71e59d8f05d4085961d37b1f62694dd5c7f40efb5875b90459dd66c4d2d6c01a40834307ae9e82c2e08000000000000000000000000000000000a4975c9872fd167d0ff4cc80a6ce179b1e6e1eb21c8de80321451b1deffe68d8a13db26218f14935b64af25d63644c10000000000000000000000000000000001e8a2824f21cda745a24844ac0336994fb18e30608ac61201a932c0a5a58f1acd56cbd9353bfab4944efcf2859ad5915c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b1973000000000000000000000000000000000537d7a9d7d9dc451cba4d50630caed32e182cbbd95212577b8c2855c327530e447a4f3d73c7d63fa3ad5111254c9ed90000000000000000000000000000000006984b32955fac4ad3c0d181c81b98534ebaddc316d51a40baa1028bacd6a93a20d4bd6cad6a0f8cf7ade96bcd4d68dd000000000000000000000000000000000720c392a663884ad4d8daeb7279ac41717ea602108c76519da13a45a77d2acafee842828f5ccfcd786bf7ea88afd01600000000000000000000000000000000081f1d3e37ebaacc11671bfe1670ed65ece2aee0e3b5d746a8d618b44bd4b7dea905eb8e958bc026a092b2bd5a7b87cb11009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a00000000000000000000000000000000005af33731879a574f39dca99c5c1b9517eda13121221be77a0c1bac82fbf29b37889c15a9d32531a3f6bf9137ce82dc000000000000000000000000000000000c62939f00d70a07a85804cd97fd34b9764565bdba225cdd7549729ceb9735bf4d09a80ec3055c483e1e24b66c41e403000000000000000000000000000000000e415677988c9d4656e59f77c608926c83028f91bf4c0634120b5f774ba07180b98141ffdf727cf9d0fc7a4cb52f4393000000000000000000000000000000000c9c37eaca857151a0c4a49b079f2f061e6a8ebb77e11eb32b29227529562f8dc8e2646e25469491eec5a07b11943f203e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf00000000000000000000000000000000015113f8f9100cd18427ff48038e1070fd835fce6c0812b7bafa679ac733c80bef56492ec3ca08c1117bd0edf19cb26f000000000000000000000000000000000789cd90c0be1de5d0b359c030d4b9d8aef93951e26870e37c375b9e7879cf277971a05babd319a3a6ac53f00f3254e40000000000000000000000000000000019b1cb91c9a1b1ee49c3837339778806bf0c093f171c92c9931ad43e35fc61cc08dafaf55b7b9e0f49dac28a12bcf92d00000000000000000000000000000000066c7864631333226f191e313436453e59f48f91d42e68874fa4da45eeda1f6f7f6342204e64e124d5ecd861f02ef4f00444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000129d887d694be0ef2f84c343a9aebd0a2aaf19a4e78586470351ffaf0b1309593363bd9c6e7fe39a6e59445d935414ef000000000000000000000000000000000596d7061c2399b6a9be7d4d495e58c0377b18db1e45cf3eb431d10cb8b15ae42548a86a26086d57b1a71cb5857d7917000000000000000000000000000000000cce7181fc87dfe1bb493043279a5d93cb2d980eed38dab2ace8c9fb335c2890447434d80df6e7c95729933ada7b9d8f000000000000000000000000000000000f0e1274ff70bc6d3f1d0d5b251ae528ed94aa3a1b9bbdb260892bfaa6213892071b8a6407abe26105b2f81df90569492035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc7360000000000000000000000000000000001192050735b114c19eb2bb9aa01f04d1fd9bed4df877113a14f7fbc9c31acc10db3ed0e0d15d8433e7408bc237c985b9000000000000000000000000000000000a8a66cda780790311b56836fe69479c7b94dbc6c82ed5886887dbb539a40390ebb2683c04078ed105e639a2ed8732a1000000000000000000000000000000001678ddff677b99011c73e0c9875b5b2ba063170f4d565d261b4c6d3263ccce0334b5bbb7ee08692568037fa96782e48b000000000000000000000000000000000ae15f79ad7f790f8ceaf7709f4b5da71642da0c1f7c442eeaeb165c7dacd8a4892fdfc8447a03a7c56e12513499e43c4bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e8260000000000000000000000000000000007fcb5ea5358074d06b64c5f46454e682dd9ac2127374c83f3ac5ad46bc5fd2fff7c5a80ffc669a1c159ee8c9a01bd37000000000000000000000000000000001010ada1bd493d6282ac2d3582480f50074a02fdf412c63e93c5857974626ff464150c20bdf23a87692bfe69a075eeb300000000000000000000000000000000086bb5664a8738f02af5517aec4c6db47653a6d76bd4b5e37ba4d8b27a7819e82e6a4c7ba4f8377e06a5878e7c0bffbc000000000000000000000000000000000be1463ab76e468e47e1711c158dc9bb10d1278f5cc676cff937f60ba457061bacdad7b8d3286f40219963b147cce4bd369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c3329d1e1c76b0bcc7ca3766b2cc5ec8169690f45e0ea3e37b7173bfd6c884921c7523ff25391a85b47d5de395ca63b00000000000000000000000000000000081ff066c008d5a4c893a636d24e9752c6a06666dcbf80082167610e73a32d70aae3e58c88ffaa27f05260b86b11f72a000000000000000000000000000000001178e88c652d257888cda1c0b65ee2c0636184194fef9e6ae3791a85417c43a31fe75893773ff3e7b4d4cda9eafa8de40000000000000000000000000000000019657ec4604ab5e8812237a28e5ff320a0d728c60c541142ffd87fec2c703665638e5eebc33e308d5582cd043d08d788ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e120000000000000000000000000000000010789200f69d8acc70f108145804b62b521a30a04176c449f52bedff5975ad7b273aaf4a32f8461ced8e92b2229e2cef000000000000000000000000000000001178c36174cdb783b5b09d419ae4a154512bf9ce07368521d1576b2f1bf39f98be29bf533bad16ba9d96aae621612aa70000000000000000000000000000000002580f2115d1814667b6178b6bffca6a4d992eb66e9601c0d21e32a5f3b69e3f85e1205c877b2dc2696a0e872c5bbc6c0000000000000000000000000000000002c94d7ff016d57bd5f589971344c6499577bc2234e18e6c8dfd7d27a205442a4236ac54fe279d1bbca76467530140b42cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca36100000000000000000000000000000000108b6fef7396ef71b46339d421726f83b08320599d66da18234011720d2b524d24075a255d2771f1ae904958c50a9046000000000000000000000000000000000723d5045b65c0887da1bb01d874714ac86d21441119a93a1d5758957215f399f5ef1cbc00558db01b295bf0cc988cab000000000000000000000000000000000994914a3df9d3094dab0c0c41a45315dce5968a99e6171fc609ac9e50bee5ccac771efaa04067467e95709bd924973f000000000000000000000000000000000ac746602f804f52e9a485c30412adf92eb9af3f6daa8f23b974339a0ffa6f5aa1b70a80a9f19cde2a69a4b7251ecf5d60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff790000000000000000000000000000000000a2ffeaff148dc5f70fcf53e7e8d7b6100cd6e7df5b3fa4aa33bced243f15b4f77f48d25f74366a693404b6ed7d3075000000000000000000000000000000000f3e1b34ac8fde4caedf3d8c3e24db02de3f91487db300f09c779e7e4e96ae55229288abd946abcc3a8adaf18a0c89e000000000000000000000000000000000166a68c5191dd7f9d44eade2ef1a9b522dc062bba9c55e2ff03aef400e5d2765a12816b4ba51e10bc21e06113c8ddc5100000000000000000000000000000000109c00de20f7e827375c1841348e684fdb248fad116e9643dbda8be2bd06b71db264e9f2c40dec2092e7d518540a6d82652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000014ddb61173359514226c150a3343576b04fb1b06fabd8fe2f921fb3b90baf5513447c107f6d2f96c8b03274bfe451dca0000000000000000000000000000000001d1064860f6c4d62a282147308e80ceb0c5dd62f39b3232a231b1b287e497df31cbc5a3905a7687eb2f24447e50a395000000000000000000000000000000000859611bb3962955f92bff861e03d07bab7fe1f69e90c6bc7928be8d1758c9194ff7a52b16472d04564607b742543eaf0000000000000000000000000000000008a3e8396901a205a071aad06ba9812207171f33775eb358de4232826a5f0ff50ec3e137b1344b583849e8a5b424b46676a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20",
+    "Expected": "0000000000000000000000000000000008c9db83241e7f3ae6c2eac8fdcff5f2d35318e24c3b4130e9bb7048a3b84a52fa3f222a8190121d2a5b8835bf911bb200000000000000000000000000000000002db79cbcbabf41bd8c715e024f4687bc0d058d76b8dbe58ffdb80918212ab6e9b35256fde583c0fe903c34a4c41ba70000000000000000000000000000000019f37d05f5c9e65c6f004e1aef03ff0e1899f0739c9cc4e9038e18f9d45678388454d144495b2cd993eb3691bf3e96f5000000000000000000000000000000000d8e0d7715ed71291729bf480f5fee7ae04264015732677488472bedc0dbacf8b35eef7adcce196e3bba9cac0991be81",
+    "Name": "matter_g2_multiexp_88",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000064a134260b753af73df3764ab662e3b1bd624c8f3248e9bcf7676d8fb0825ab85ea33387d4641c81fb8ba3757e0870a000000000000000000000000000000000d67eff1936a395cd3f808ed7fc89f8b6a227c4849a6941d4bf762af6e41ae41c8114aeccc2565ba01fd902df530df1e000000000000000000000000000000000110ca2339832e7a9468844b94b3ced0c9216654bef1c8a5cf66385a99d5d452f978bbb7fe15fb477f56753488fc909b00000000000000000000000000000000173210b548d1b98b926539049996713f53108cd2911105235c1d5258360d5620d330951db67219ffaa304a67fd6219f39f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f000000000000000000000000000000000f7c3795ac3d511f93a3d85e65261e4c09cd316787f74ced6e472a3993b7b5b0ce5a7c91d99559a8e0791f712cb4e1700000000000000000000000000000000018eacb2c5fa9221881c6311256a69c7616748deb3235c61cc11412860450151a25e3d6a220bb23e0b3e3325044fba68300000000000000000000000000000000121827286873ad31f58cb3889fd01cb7d0f91ff1c241295f6ef2dd0e8aa8638b63a7e6061efc2e7ca1d3579b4868f0460000000000000000000000000000000003a57315175d70880b2b53c67d61831ab066b08d7ac68637364ab1c1f3efad96d42a3cf5189c45012c1f73a1b97bdb4c1deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f10800000000000000000000000000000000180648e5d0bf727101417f515cb9578bdde3e9f6c4176d516454ea7c32c1712610cc8bbed303bd1afd48f580ec11b77c000000000000000000000000000000000d6ffa9b85d69b67abb77f5c8bd776eae82d1cb055d2dcdea31ac66b1825014ec7f7a2aea320ef9f6897c9aac8c0706900000000000000000000000000000000073214fedbade28cc60ecfa4e1fe2fbc05f3d71528aca315312d50214f680956bb9e0fc12783843b00b3f4f0f52efe2700000000000000000000000000000000128f87e7da7b53f28944aeb26ef0f6c99d84038af51a1d242501ec84b5a6a8593ef1a0f6b523478d9fa12e36c2fdbe694b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000001443980d7450af1e19949fb328776cb7238a9b26240cddc565aa9d52c5592083b1533e8103dc07eac80e4bd830f209f000000000000000000000000000000000afdbea7f1cec534c03d3269d50017372f7ccbcba9f096fdb2754af4d6b4956decbab2b0afb69f97a03beeb20b4ccc31000000000000000000000000000000000a83dfa3197dc65097601457a97d0df7710e001e90657b150e289515609f13997b454167a7589ef218893309460139f300000000000000000000000000000000029c362244510c342358130f877de947acad5a379295f3149d5c713274316e06a169501f889e4b9cbf86f10b9521c1bb124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010a83f13a185c70ca3f724dd84efcfa3ec463d7c05360056f8b5304864b20025b0a82c9d542ba08b645e2334f176472d000000000000000000000000000000000848a6a18bcf64d083e118190805d68f7ffea8b5a66e0807b9cd3733d31ffa5cc25dbfa6ada604646dcd8dfa622e08a30000000000000000000000000000000009962205c0ba43e5101fc3d5353f429a57a97bcb84baa0942a7e7facdfb0d032b9307aed8bd2ac9094a2e5b1460db7140000000000000000000000000000000019b1012661a10d31a4a73d0cb31f7eec0e7be729a42baf560c1e90a9124fe8d5fe31ecbb6d4954dba7d943a7af773eaa5a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000e7f02c1d2ceae60f314f51374b338c329f2eaa82553c3fc1643c7f1910ca24e277f3d658f552a47f780d4d9e0ac5e030000000000000000000000000000000014b6b56afc4afed5199191ec13dbeedd797f14ed493c25658a9658f031ac8d43de12e6a8c4b1671c9e5ef78da1a55e2600000000000000000000000000000000194d8a50618ff55ba3fa5602d41cbbeadc01a348ad1484c5e9aee5fb7241fcd9018f436e3c6c6dc64beaa241513a6c8300000000000000000000000000000000052681eac4bd59e160b67ebb27582a6d3ad5286d652787a0e160026607acfbfc5b9f38b9b171375079d052cb242b87fe8c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab87000000000000000000000000000000000d96ce83d917204e674ad9f5e5728651f5f23df25236b0fe769be48adf482ed8c36ad9c9abb6efa3719bd35324bd700800000000000000000000000000000000107f55ab0e5b60dbcc0632c345a9e93818014d7657b264031709275744e1c6722ec63aa209e655878a57704ca6cb3bc10000000000000000000000000000000018d97fba324431fa28b8845d94f62fc9eacc0253134b923908f06889d375405b51610ac21a75bdfb27e3533dd4debc22000000000000000000000000000000001667856804a5471238ffd64bf3bf266ce3a2351ebc68265674bc86ce6faa8dd50a3dfa00c647fb4265951b3a9607ab99f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff000000000000000000000000000000000fde2fd0349e7a47a9b6858014d551aea569ef9802629bd9520e303ef0487c9d2d399682ac16ce6fa03adb6f4b478fa5000000000000000000000000000000001858ae58920dd0abd8ad94d2f9f946c53e050fe89c61f62fccad37e17f8723a4fbecb6b1be1e3cb853f045d0dca8e53e00000000000000000000000000000000093615a7f9d12e92c90706a47abe9620c4db41e95e42e478949745d6b73e021422e40b969e9e34263778c8a4d4907445000000000000000000000000000000001006ae7963b1e1c4d8c2c85175aca958758fb380019825b09ca3f728b5356254ae4fc670aa29812320b921b48a069df622d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000000ac407b75ea77789748e7607b5d6edb1d891875aeef2802715ddc393818fc8cbe82cde9f96377e3ac60107ddcda7e6610000000000000000000000000000000006e63e49356c38b816736d1d7c360ceaaba875c53c98ec68cb825962531855dc6410a125b914b0ad99f6f4327f5450890000000000000000000000000000000018ffb4ac95b8ffde112c8bdbf07a1c97b1d30a42dd4a97c82617698617ceb169e8702437ff6082a2ae387b462cd86256000000000000000000000000000000000497c4b3788c4d6c9b4cd8b3d3569ac4b4332b2f76c5f03f112e089bb79d33152b2469f7ad3eadb8b954775aab73f47de816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000003e16f2f5a2fe15fa02b6217aed7dc688dd2670c09c02791cafeccfceb7d99ce826bccf213f6a7c6064687519f9283de00000000000000000000000000000000095e6638ac74815dc451b3ec85a6a8cc18643b541e8be99052ff6dad39c971f2e8bee976ab2ed5e1cdacf92816249ded000000000000000000000000000000000f2703c08b1d707fb6de215de80b53ffbf2ac48f3dd059d2a952b1031189248fad27beec5c8591ac93625a08e3420f0200000000000000000000000000000000024ae36412ba6f2fdeb0777b892f1ed7bab0527879d93f7b71b62f437f5c1ad1f04a5a7380ae5990a455f11870c7208304f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea0000000000000000000000000000000000f7ab1908c6d4b152835f950b604b55fdda7eb55c6b90c05e98626ba7cd014683bd3e219fd0d5983e9dcfaaa5d389e560000000000000000000000000000000010b285c2884dbdd540d6dfeca704e00839337f12d2267f6a3fc731fa0f724cde19e268782b4b9c2e11ec3aef9a72a6ed0000000000000000000000000000000014a40cc55570e8f45369bd9dc622e05f03989bce6a98a0d87f4fa7add67eee3e2ad9a297615dde05e64203e86153ec230000000000000000000000000000000007f2b6a092adc595e4857e821579801301396321d4a20bccb3296a031d74a62bd79ea4ea094d2e545943138d2fc930fb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d200000000000000000000000000000000108834a685455dc0be10aaf54607a06100673140b012ef23a16d3df204a81dd8505d62ca3e0278a2581abc59e0fbc421000000000000000000000000000000000bca7130de9896e8d6858022f24308af7ca66fb4c91f38b30f717c5491996ef4cdb01f4d38a730f9ba9ca5af5ad1de7700000000000000000000000000000000007d60ded107a06114afaf741dc8826f9e14bac6014eba26089c4e31a73b0f30c4b6e22533ac0db7e73621cecf753590000000000000000000000000000000000b538213a703f7a0bbcffb4aa8ce25ba2a538bf599d3c0251f5e8acddfd596c9912d4cf9a1bd8d3ec070713328ca992205edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000cdf0b9bc829cd8537918d665e5bf344d309678d01ee80c71a6d6efb45ee8a7beca35bb5ee046e0a3fac76e1771520ff00000000000000000000000000000000014e5be9dca2f8ee4da18e5ec9c4caa891dd78acc47f553af584308c72988435b85ad21b14abf8421bdb9e25164d568f000000000000000000000000000000000accdde22a1c479e47a17b8da6f1d2b7f780ac278c68a68090e5402977d897bd734f5af8164118d613f480c1f65e5d8e00000000000000000000000000000000029614458afdf6b572bea02a0af987d178c43650ca1c80a297b1d31e259aabd3e2a2c8e4b2c044466924dd6e5e3483e6f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b0000000000000000000000000000000004e45cc43d4d10ed878e18df156062c799a687b8e6beedad9fa6f66ad855cd053af6918e234ff9a43561da7e67f3dee10000000000000000000000000000000009c9ae47a76c199c93c38e7213c8d6c030cfca709714c703839b9ae9b65207e83486f9c8c16373e2b37756f3fd4355fd0000000000000000000000000000000001594ce9c2e229491b22317452938115747515ce62a0d49f4dd12667f5b3e7b541b3775c9b1363cc185a539b9f7596330000000000000000000000000000000016bf68e05e32168c69ad67331d7bc88a6d130fe8aed3e42eddfeb1d92add266eb69487b246a3ca961ea6ac0a35f8da78059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b41900000000000000000000000000000000080d165c57354f87008eb97610d4a596f180e48ed3190779591a0f7e07278f8d2fa6cd21d1b10e6347f11bd9731fdfed0000000000000000000000000000000008d5a1e66ec76743ca366be80fd1cbd5efc9112dbcfa84ce6c44e8df03140ca5f07d4bafc6c6ce5f2f190ede55fe8718000000000000000000000000000000000d0e1d2e5ef384a4fb314fdce54ab7895f895b3bc669acffd48e92c6320024d4f371f42071fceea550c8cf68615b00960000000000000000000000000000000010beae4ffbb68cf6e5d0683dc0629411ee14563f84788d50b1c8755b0b06092cc0f0ef7b55a39d51945b5178e374f8e047b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface00000000000000000000000000000000161486d422462460923bd98834f0cc270982087697747fe40eb9153a7923d48eda191e4e7a75964f18f1df9365901a360000000000000000000000000000000017ab168a4ec81c8db4a74d529670fe6332b3870004f696f3a143cd1a62abd747d94afac9485e5dc19b0f4262dd379c990000000000000000000000000000000001e9cc85f03039ea53253f0fa2420012171fe39ed8696ddfbed57b80b73476171e59631388d75fe43aafde52aa14a64100000000000000000000000000000000109a5d5449002f4bdca44c0bd141175d5ca1cee449302f0314fcb5f282f022a7a3cef77f4e9fb515107e797726ff51d767d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c",
+    "Expected": "000000000000000000000000000000000aaa5de171664fcb45439b17a024806ff7e07d02294e0592ca74752a5b66f3365d1b49d6893b3bac3b8b0d10d026e48d000000000000000000000000000000000418354ce1820ecf848321a07ce22117303e5a15169a9cbfd141fb4797de8871d84d577e86270a9cbfe31c088ceed0250000000000000000000000000000000016884caa03ea641e0660a790975d77c5bb03568f873800d0559b69e3e0afcc10ddf031bb5c25c46f136f0791bbd3cc8f0000000000000000000000000000000002bdf659df76cbaaec030448e8f4bbd6b424037a8dfd7c4b8ccaa2224b0852c168f49c6f45c04f23abc85b8df21953ce",
+    "Name": "matter_g2_multiexp_89",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000062bad6816308f1c8c6941980caf71929a4006083dd29827902ffc92ebd9b14f1ef662f3a0125b1e74dabd039f9106400000000000000000000000000000000118e4ae76e2c321a5b89eb19b58f58f44e80dcbc7bd6d619579da40e1156aab32fe81df8eeb1bd047f96d65aed8b3b6a000000000000000000000000000000000c8c93e1beeb4efe52a96e5d5612338721e3e487c13c18b02475f9ccd8fafc2c95101aed291951f2031bee5216dba26f0000000000000000000000000000000016fba44e9aa39a12ae27e3c36de1f14e3f37ffb0ceaf5fed2a0d9815eab02c5aae91b254812a8f3a2e3654cec01a341caaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c0000000000000000000000000000000013716488daf8586719c52fcec80d35f17d4c595b66c7f2138244f3c8cea69b819778bfb50e49ca1d092e57c51674fca00000000000000000000000000000000019cee25c4731bf48602ceab23b5fc4f764993443e3622107b4c33b29c23d1b5916380431b7ecd94a0ce99811fe6dadba000000000000000000000000000000000562b28b245b7c1ee531a320fa0f4e12d7c171c7e3932ffda6cfebb123fa7f5993e5ed5e7b7d295405e5031b339994bf00000000000000000000000000000000180c4a8158a26d34123c870bc694382352a8e4de712b650d3e45e6baa16d6950ec15d3a4e032c1d1ae8fea18faa6f3d8db48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f20000000000000000000000000000000008472d40e0505d6b8b92500e8e9711112048611fcdcca2377481ae86a7f6da1571f179183301e2194a42dac3873a3ba5000000000000000000000000000000000e2c5b61c050a8a12298f76b5f15383e72b90b001fa26889b67a24bb374b63c1e00979b05450e44ed63e72042af6d46e000000000000000000000000000000000e8723eace9c7a72b3e6097afc9bcadde61462e2ee03fcd5ad1b1c0dcf39f437f80530c2a1c5e6ecdaac14e8715f02e30000000000000000000000000000000002e21e0f451d035a5257fb09e9ed17b27f0994e6d85ddaf8d33153628adb194c97db17656351c029be4d3125bd29dc22ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f40000000000000000000000000000000000dce49634595869d7858e95a301bcff8112eb73dca8a22042137456d6d4887998a541489ff09f8e006176e6beee4e300000000000000000000000000000000010835f7336dc49e62706da4ef21d8e3173629b16742c317c1b397d4f17ced40a56520ea63557d7ac7f251568f4eb3a220000000000000000000000000000000017446ebe659a4510a362ee3b406b636bea8f381503e51ac21031c7cc92acd23046d62c2f32cda01b680c0f107142ae7d0000000000000000000000000000000006ef82deabd8983ebe4255d8e06f4a1b3585c057b2a1ca3c3e1cf04b582b65792e9980e3a1735a8ad58b053b16ca03d036d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f46000000000000000000000000000000000cd8e7422ee179a0499178c3848cc4fbc87fc25c8c882f036a03cd9d3f273f7f2bf71bd3c9cf5e30c42b1ee6e90b36fb0000000000000000000000000000000005005a471d77a35e922b6d6a45b13a90947c2b31d8e7a2e4b6388265b039ce23ed958495dbf904186bef60fd547b941c0000000000000000000000000000000006c337380065eb8a5f63cb20fc61a9eec4ccf0e23c4e0f231a5bc4d765271b9c5697bbde692b4828ae22ea12423ad932000000000000000000000000000000000f7a0080cbe72a6e6473f66ed729f58683a80815a1748e52f7b67a6bf2846b7df8e7dd8599f87fe63706e9823bfe00d21a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000c94834474ac91547546d7d179b2091e33c8812c1b582ff186e69b63011177283a74b549aa342a7f3882ee82ad8ecc03000000000000000000000000000000000d72c4308e9ae695acedb9413445bf6a40d59ca78bd4f74ddbc1bcd8508cfb521bfcca99c98dad8022d3d1ccdd98bca9000000000000000000000000000000001487d006830d00d84a567c5d031019035443fae4791a05253f91249b32a4b3e7b3ce7eae885b8caeaea411a90b3445e0000000000000000000000000000000000d94f17aa100503f605732a48e4f55c394a8df1421a3d7c78bc85f4cb7a53744eadcf76e1620fc54204b123d6071cd3bad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e900000000000000000000000000000000101cfa8d9c7522277f2bb4bae6c09e8b93a876c749c91c61784feeb105be61c2479375abdaa81deafc2fe754ed6cd9da00000000000000000000000000000000089ebbdd489ff670a70218f5aaca78d4e7ade483c7f20de4a84d39217be8f560fbf7bbe36f3f8b8361ba16d17ce609d200000000000000000000000000000000094f094372b2315fabc219099200e7b9e2f3a2f6fef2ede6f83c82f44792da03aaad06b8cd06dc3f140746bee2a45706000000000000000000000000000000000cde6cf9a3a7018b2b1c0c26b5850820080c7e4b56e615d577a78565431c93de78348d2851d5ad9f120ddaa9ff3da31b8fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000059bb8e5dc5f0cd31cf674ea78b80b67b8a8a753e51284a2ab37d3f29459250d904e70ed00481b73556970a7f5424e5900000000000000000000000000000000043c6a53c413bfa2f4bb14ef296afd97ce801a37fe63d11a842f8d66160794c1a651d70f4c836af2c73cb1bc58c706460000000000000000000000000000000003e7b67da1513656f7b08fc5a77682477349ac57e53687c82b6d98772b5f929a2b06b0c7e14481d522aa94fa3a6e1cde00000000000000000000000000000000109e07928216eaea36fbb20a38711e73fdc26e18a6967b54f308b10116a5c8af0c8411406ef6ab1050b61c23bb746b0a3023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e000000000000000000000000000000000166f26d3d26cd48e498578900a8c830ce9b80f162c4b430749651b945d9f60ae6a26306ad7711a1f9d3428946074912d00000000000000000000000000000000165f1bc59c9c36d12754097ea83e9a63fb4ae5d1b93a1b9239a6f338cddf4a9b30415d58076852288c6a467ce9b6b9eb00000000000000000000000000000000198e73619cb93fa6a2bc700cd400519d11a7d3d6d945ffac9754a6faf37da8596b49b7a3a4f2cd899ec9c84f1e79b7ed000000000000000000000000000000000a4740820d60034d37bb85e3e622783852779d36d6e61f81a7eabcd094993dd7d81900277550bb4299d550d2805466aa8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000001714857b0ee07b94ea928ff57aae9fe003c0c85d8564456955d14fc8d4ae14a7c9bc303983af3e2999c6db2d000ea51d0000000000000000000000000000000016512cb60aa372cf5098ad514291d8168ed31bd755861dbd9ef020252c01379d343a9c058839cdec8d14f2fb9da0db80000000000000000000000000000000000af74d8ac711b6590e7041e80ca40dd4db659e42b950bdd68c56d676de654c1a47867bfe6483dfe1971eb7c1d1a70bd10000000000000000000000000000000019e56ca1ef3fffa9e131fc5bc93100577b062cf9b2acd234c79e5e54aa799a389f30002b4bd683edec5fb100f1800d66415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c00000000000000000000000000000000019d1d1e1e2dd4ab86df81a8246c902a573d1fd1598050663342e411a1d1b3c8849473c689afcc8e0ce5e51a9dc9c3b6200000000000000000000000000000000190d7c923bdd6336fe3e0509563b2eb6067354d8807f66e6052e97d5997464b9f07f29f3022f78779a5c4ac155a703ce00000000000000000000000000000000128591bb699c18a7b9e6e4e894654853f6a68233dfe8c744b42e057711b8d0efb3a98bab6aaa40ae7675d9200a8427d600000000000000000000000000000000045e0560e0936b16d1e055d3d3f4e0fb42d129546abddebeb78e871d1442f4796d939929d354b0326b95e50fd5208fa9ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a97400000000000000000000000000000000092373dfd7d4375d6bcffa415e5b36a31499e881a80be32400105a6d56b34d64f4fed09f12640a43289a710f034b71e6000000000000000000000000000000000fa75d6510b3b58a32635a7a6cb4b9255aa7af46905cafc893f29b7866e12565765bcde498dbe87df3d1dd53ab5628320000000000000000000000000000000010dfd3456cb6a8bc853b390380a13f045ab43abd289fd05e7f98839477dea1fb1fbe38ca4f5bdd6691446ac0219e453000000000000000000000000000000000112567397f3fda84db6042817a99aeccd0c46a11fd3ba44e2600deafaaab7014dba98cdcadf81b97272fb7f275ee8a4e039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff30000000000000000000000000000000007b397f093e69874d2bd3592489d93c80d0191b157e71d08a6ebe73063f77e7c5e084a24b34da2aa6354b1815a694185000000000000000000000000000000000fcede3a39dd5f905d072dafdb6f56d85726f6f362f91f079fcd47a8c1d3bdcf199d64edf17e3db1dfc96a3e59f69bfe0000000000000000000000000000000010cfa13c84e750d8af8bbb88bd6d16adf3bc7b532447c2e6accb359a5576be08c1b25f336047fb8e01a4d7f9080d0392000000000000000000000000000000000ca0e88b5c2035bcd3a65e8bf1aa219cf428b6f80617040ae02a0ed41559804844df373ac61a85899bec83e5a6243ed42c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd055500000000000000000000000000000000021f3b793680e0e3127fa53034e9fcf286f5279cd167ac1e8ba051c440aa265ec6d28fcc2f6d3bad126180efd4503fe900000000000000000000000000000000182b429f27996ee070ed27e7015bd70191b814bd02ca6558a9be81d6898161aa525197c1672ae75da92729f2fae9fa3c000000000000000000000000000000000a20b3922e07da4ef6696de85754eabf1f58f7f5d37accb6cde4f62066e789bc64bc8ad6ac827b8c955acc858b03d053000000000000000000000000000000000814faebd3b60fa1a8fb86b3cb57d36b9c85d4b28e97a2251e6bc1fed1ccb18f17664321f38f3723cf8b09a2161c6aeaab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000159f9d329f929a65e41c7a0d4c05e11db61ca7d6d82f8b92a780bac66568694656f4c845a730861fde9a313fa49bdf0e000000000000000000000000000000000d556bdc8dc959b00f74209dff27023c5521d387a40bf20ae2a98f3f55318eddd347bf1e9d856f43a4b5fcd26c3567ad0000000000000000000000000000000009b4b0cedf477ef1e0f99627bdd7a7afeb9e29afbac553a516fab479913b23a9be5e0b38994215a9e23849bb664201ee0000000000000000000000000000000010899f4dc55ac5d1f56a7b8d55ce7f6a5e0a8647bf1ef6e9050f00c5fcac9f679f138018b9aa611be73d3bdc0af2056e297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000000226c8a6b27437972ce29c2ed7e5cca4b6691e3a5dbbe713b5d309ff2f4cbb95e8f1571314444d65ff5fbc3281f9354f000000000000000000000000000000000282a49d0c560d873676967700c1062013a2d4beee96a09af7e14436fda4e3d2a32ab8ee4e591decec39a811ddff130400000000000000000000000000000000167bfe499f1f4609e67134e12ad91aadc37bdabd0055ecf7f96162c39a02a86e62a7b3d39f514f63edd82d04beb1958a00000000000000000000000000000000191673ea5470e4704e361f5ead1c56371d6aee3035d92d9e1b96fd119c4f877cde6451411e441fb45aa9fcb90fe4c66ba87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99",
+    "Expected": "000000000000000000000000000000001488532d83fddf0bfd69b32f965790b3fe4cd9f64e8d17e78189c346518c91e69db2f0b742cdd5804b3db3777dd931230000000000000000000000000000000016205c470c6371d73b012a14d519bf214ff10de458605097da1b798977bd938727c5be19a10f4f492f301d2ab6c38ed000000000000000000000000000000000142cc08f61d3c9bd4c7bfd0b7a0b8693af6120898fcaff49a7fb5abdaf1d15bf70eb033d6ff09a75995547e6856c595f00000000000000000000000000000000164b2807e19135ca3b66bac9aceb371165c930ae063f3cb5a06efb8985a1e0c39023d8f01df517713796083e8c2cceb7",
+    "Name": "matter_g2_multiexp_90",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000023bec14deefcc20a90e439bc16912e90191dc7142234b1870e4e8d70c56f695d5cd30a68930ff9b007bdcae8ca90d870000000000000000000000000000000000053a6e226f3bd82150e08ec3690f36616d5ab745b36a9990baac7ad3429a41bc60c7f7000ceda4cc9298b10043639e000000000000000000000000000000000b81b331589ac332093928faa60d6819d3b5559d32d37d2cc13c78aafa1cc34e32d317695c1c4b4979baa1865ced90150000000000000000000000000000000010dbac5e52f9a046ab88aa36b3c5f6952720e174bf8f1732e886e66e5803aab63642185aa24ea08c991edaf8375bcadd9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000001875ef3f90df03d49ce6cede2c791b4d8503b75acff2dcb1c7c88026394dfe11481da72de4ff58ee9a98e75577b6398c000000000000000000000000000000000c8ee603d1404e64ea3ff08c70b3dbffd318736ae95f9a96ca07ddaa449818e6c5a17b2970f572f53c90be893e5c323b000000000000000000000000000000000f31af63c68481f527092b261d29d5c2daa95873b68899c28ac7753d95a64f455ebabedfe6e72246e494cc5fa2a9bd040000000000000000000000000000000009fd06bc51d4dc51de9fad6d1eb763809cdb5ccdba8e0427859d878904bdf295983b318f311856728078e7cbbecb0c5b64be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a67690000000000000000000000000000000003ce75ecf6b605ce73f4e215b1aad4799f91e624daf0deae3a273968490bdbdbd0250686ee91a1c24c2e2f2b6024fa49000000000000000000000000000000000e4d9b65d71b7593310fb5145677d170663c0ca29636f7b7c50ec1988bd2d2f1c41d542d4cd8fa23fad94bd6a84aef5b000000000000000000000000000000000fa4accea53a6362651f6c6ad2a68d20b5f549f8eb961718e0c14cd05249a121e442a6a588eafc83d6a43d8baa66882400000000000000000000000000000000121e325406767852620ddc45677495fe3e0851fd3c70922896a3e92033347d2fe8d07f4db8f26b8127ec39d619d596030c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da0000000000000000000000000000000003a0ac3ac37932b71672b9c48bdbd368d64c11f57ccb952f633bcd10ec19134c65fb2cbad655d773a90cbec2d9232b3b0000000000000000000000000000000007553c470bd8f38a48490dadea29df81ad901ecaaf1eab35b1f497bb58acce77b883e03e78702930dda72e2277139a2b00000000000000000000000000000000044973913824b3326b72e62ccbabd8c9f1b5dc81b423d0dca37b6f33972d993a681c326730717036bc6f0286da9177430000000000000000000000000000000017b0407d2864cfb39dbb0a5fa8deb4ed4a690a4042153e829f51c56bd0f2953a440d8305a318e6d6f67970d473753021a2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a000000000000000000000000000000000b7478dda7053590ed013b7c23431a21626e748c3843e2332bde0bd3890ecea95b6104bac420a8be5f3dd9b075203616000000000000000000000000000000000e6dea641181cf796f62b196652f952ee2a26ba998cce1cfe9d65ae49198d10badffa561e2bd818eb2a7f350c122fa820000000000000000000000000000000003c79917ad5a9c7f046b34e5491ed015695aecb00760f3009dde4cfbf88ad1c03e44117fcb6cdbd5ecaa8df8760a3da100000000000000000000000000000000034e22ddbdeb9dea46c71ca2144ffcc8356c1a525c5ada69a6d5e5c1786aaaf0cf532e31a2f78371e04a72e8222ed4c7e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c5890000000000000000000000000000000017964112272360a38d3bddf89da922ab50be076bf71a094fc8afde109d3817cc2db633e6408f5716b76d70e30ae00c0d0000000000000000000000000000000009bed28bbf43846ab97b92aab9ce094b077bbc59db648dbb469f21842058ef20318a1a8c18045b3de555bd8c76132ff0000000000000000000000000000000001297110789c7aecb0fec577f6f4a4de14608d9aa26a8de68289adea7f6b53b766b840d315152ea346f8c10b2d2729e730000000000000000000000000000000002b551c6a7846b96c6895e55ec435397af70eb435dc1c562ac71a44c36936c2c6d3e6a1e3545513516513391aedaf9ca76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000003d463ee4d177d78849fdecba52b7e83ca90d54177ed39e82b4e80c17994a6a2bfd9c46edc0ddb256f8955428f30eca0000000000000000000000000000000011dd976dfeb8ecb7d7f5cd10c235131709fb16d8a827e83d7084266c2504cd1f5276ae3333bc7fbb4ebab48c0d97a9930000000000000000000000000000000005fd19477fffc246f5991603b48085d95256b273631bcfc16f19c6980a3ba01ac098061faa149b475bfce37d586464b800000000000000000000000000000000103ac3dd682aee109dd7fbf60b50c28cf7e37642f05b424773a06f6cfaf7e9fb01d5074ade97ef6cb0ace2e1fe07d54c7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d0000000000000000000000000000000003208ce7f51a96dee053cbaa66fbdb921c2c3b42ead78b39b4f1df7ab49f05cb88d0f4ac18de5839749416eba5535d4b0000000000000000000000000000000001ff7f9db52aaa0fddc8e96a67b99353b92d7032f59d200bf69da3b446d08435d2ddaeb93584d3b68a1934566187922b0000000000000000000000000000000005f05ccfa5704652cecfb42979c538823fb9d11a00222a963d00f1a4b9a040a0222dcf45baad40c6574d85e5617dbbea0000000000000000000000000000000018637b8c3ef111f6ad4538464c250d780e7f081802bdf720f4c925154f4667c5d50cdbc4dbb7d0b2747b97d2ba2280bfd35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f000000000000000000000000000000000eb400becfa5521b824a4288885fe46642c31576238e94f95e9b4bcbf62845ee9d9ee122f87d36fbe668f0e605fa2ce00000000000000000000000000000000003c8cbdeea0d09590e1719ddffa0a116723f0fe85585583f3f271ead66fbc2107873181915cc41eed3ec6e2c5669e9d3000000000000000000000000000000000e61c0768561517405952c6462f1c5df95be272251d8a7060624b62f9be310cef64436eb2c4c04e8352d7b75fea1756200000000000000000000000000000000036cd74a8efa8a1fce7587f07d5c2a6c4b7ef161b0faae037c9bbe63bd0c92b83e514c8c1bae4a5d9866c0889b1b914f3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e8680000000000000000000000000000000013019d0fc8b93da2c79e473d713d94af33eaffda65a7a49d0cbae9f5259b8323e6f29b83da9608ba7d6ec004fb0710eb000000000000000000000000000000001505d30bf8f7c51994d896d91e8e2259782e2b49bda834015477f18c29e64da4d31f8b96edd080267b77a9539afca06a000000000000000000000000000000000eba929531615d9c0f59c4b33c1fc34b81e9c77cd8c6887099d850b3e39326d7caee1feeb101222f22bea1e9853d06ea0000000000000000000000000000000019d88f62cae047ddf2cefe497495f890d9ab8499e56f72488af65095e992427bf821f63555a67b0afb00d6fb441080a010325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c11382000000000000000000000000000000000b7c8f3d0c56b3b7d96c0a24fea3394551a186f87acbbbbce41d1313b23762945bae2e911725da4211614b456b508c0500000000000000000000000000000000125316f64bdd0c5bcd26a0e5bcfc3139045b3a44c8a8dd1cebbfaeb83b963c5a5abd4a5961465cff261c0e49189278d800000000000000000000000000000000095a327f488b901fe7dcc9f9ce6f4f25876bb09b053b64e9f4de9506a0fb95fc0cd443473c2cc5436750581d39b8e51f0000000000000000000000000000000015d406b31c791ae2d25ce462304c0bcf341686d7967c9dbb6734bc28b02123b1730d0a673fa8071dd90950d9411a2b3909545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb3000000000000000000000000000000000c7029af9422246d0a30784431d6bf9eca09481589438fe9a6d2fe1d5e526ec3d176a3d550204aadb85353d99bfe3ce50000000000000000000000000000000014a0dcb26c40693ad19a1edccda05055a27ca24544e933d01dfb964571071f94c94233f81e1ead0925d24e6d3df2c21500000000000000000000000000000000147a55ebd83c746128ba9c7ac57be125ca5c95f80f891e2c5893caa779484bdc1f9c3b3ccc4223b2343ba939251f7fdc00000000000000000000000000000000125622a040d8b157432ad81b8a83a9b1f0920b92680bbb65050b4862b89017b3bfaf81a3402ccb383265ba7200ce677feef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c810000000000000000000000000000000013b394fd7a0f3d94e5fe4cf5cce3627d425ec848912395565b3e61ffe89e56be799c4779d3b9a0222ecc6538ca3346e40000000000000000000000000000000014ac1a87b333caed0f557fa5692d1138a8c1e92d1f9acdc9f357e2a46f27513dea42f367b046d389dc831610be4fbcf40000000000000000000000000000000011fa243a0aa8b0c01c7636387d60021afe6efc223b7deb69d030651c369643188b9dd5e08d6d031d71dd11eca1e825ac0000000000000000000000000000000015bf8fd7fe438407db7f1b0b586b2c285777c5b6dbef9e45b46cc0a50dc831f32a70e7d4316d4869bc769ff6de58ac30c8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e80000000000000000000000000000000005ea57c269c9d43d3f17a83df04c95ea7e7bd85aad1dc2dd285ccdbd52bfe707a1d2476417e848ab119e62fea30520af000000000000000000000000000000000b99768ffbe95e315b244bf996cf34f8ac356664adda5aa7f4ff8d513b2eb5934b8ffe0fd9af94bc9b934e0a8bbd51ba0000000000000000000000000000000003b02c259df189370dd2700c5cccfc8b212a4b332a083adf9771503f5bd0c9ef040590320fe4a86c555a4ea87531268100000000000000000000000000000000003ebb1e610bd055d037a410cce3ae06aa654950aee0210ed0ee79f7a332be7342e308347d7b17a146a8b4c623029e08a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000015c4da691b5e6242af870e06b29bcde467b4644f01080eca60a28c7f941590192be30e6a4270a36dc8959b80235600aa00000000000000000000000000000000080f3d3d5c35ee24179f51ad854a37ac4ff867a2736a0e3e8f3312ac98c7016beea6ffe2bad1dd4842d6ec77995ff97600000000000000000000000000000000130c29dc633aaefc831b0bccb13fde1212fdce8cdd17beaaf1d06e74ef5b1b69bcc219c8d63f054690af1b6dc7c0d647000000000000000000000000000000000767290aaa1ed4c1dfa5603d976df0715b417599445ca577ded7d99e685118bbec71443fe1d9a65e0f23436353df152cdcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a00000000000000000000000000000000133e997857f47f8d6278b8ad86f4692ba0dec9da336f2726704db593af368dda7aefc0b218ce1674f415e0d9e2dee5c60000000000000000000000000000000018db87da1272bd386f7d8b5245dc2de30e82739723b680dedd36f4ac4cf5042bcbada1e1bb307ba444431d73a4248f9c0000000000000000000000000000000006580be3e67c7a615408aaf9c95c0956678af0e2b1f536f1e69588193387f8a05b03d5e1060ca60c4fec9eaf3e72d39900000000000000000000000000000000050bd9879ef9eea147678f552cedacaee84562e6561b3b7338fa8f9d514099291c3f2a3723fdb22c88f1c9243d411ccba6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce",
+    "Expected": "000000000000000000000000000000000d8f69d90c871c08ae09e7b3e62e36514fd056c41fb596fec2fc9ce8509ab4f6675d7e85aa6b4b3197f5ab781f6f2e490000000000000000000000000000000011c4bd3cd156c34065e408efcaa5e13ad23d114458b71c2a6345f4aaf82af76cd4362db7ba9ee7e1e92ce72e242f570a000000000000000000000000000000000712dbbf20e9b24d20511d01717a3783608386408a258c2261fcdad5fbcab36c6bd21473c3d93ef8518975256c65a945000000000000000000000000000000000d13747be82153aea8076fd7813ecd7f60a214c31e88e25b14dee5cdb9336599e40b136d9ae6deb85606d35406b2675d",
+    "Name": "matter_g2_multiexp_91",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017da08f2faa32570d95b9efd2d2fe358faec1ffe304750dca1dc3a273be3427c70904d58864f76afa19b0fe33ab1535f0000000000000000000000000000000017de677b713202f23baecef2b0618da140af624e56b876f2d7a20cd437c3868ea00ff6cd9c8908c1ef323ad294edd9670000000000000000000000000000000011d50aad957c54868aed6d848b2e67094b129282cc2df56c41d6ffe976d02ee83a592c33370d3715588a074db503b3e8000000000000000000000000000000000b8aeb019d120959b21627c1dcfdfb67ade22a948fe433172994d4a34084ac9e1c11333a9c663c87acf50962e21c728e92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b70000000000000000000000000000000001312ebeee36fff8152324a3ed24c37eee50b3099619a33c7a6316470ae722548b4b9e0f0640453caf53f374dba504830000000000000000000000000000000005ea81d2e5d9edeb3ed6c200b75beb731c31ad666e6e37db72ffd0265378bffc2724047c7c0c6e3f1598345fd390e9270000000000000000000000000000000017617a836beb12e637c5bbadd4fbf1ca2f5cc3280814ff5cbb5890b31cf2d2faee9e3ea8134af97ad4feace50aa194140000000000000000000000000000000002606deb5d57dce5b3d2e5f7ccec3ad036992beae238673641ad6042479ec3cf83bcc0fd03b7dacb9b4bb6c181ea9cc8c193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000001203ef36896bfad2a2841689a964328fe4ce3d83798671630d0c8876e67ceda03d99555aac46d984f1d3bc38ffc134c50000000000000000000000000000000013e7461c256c8ff9144b17f8cc2e270aa94b64be62588280baca2ae6b6efc4d32b3800eb84da62561e0e96d5f0387a3f0000000000000000000000000000000009454b6a810647350cf0b364eb1c2b719670af45bdba9d7d1a534e23d4e810c3ef4d9318532e46fd104a83bb10159a30000000000000000000000000000000001034546c4288f642daeccf5b56beed2ca2d946bb4391d056df9c6fd6771048903fa330ec16d59d05540cd715333c4bc73dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae6000000000000000000000000000000000d7782404dc6721f52648fc6969db33a9aa209f8baf5faa9678437c76c9e1635fa6d22d94aedefc90112223bb81ce33f0000000000000000000000000000000001e442e548d3045d1589817d0b57dfcd66fc64ff978186f784bd576faf57607170d49364a72189328c9837c9a2d8b0a0000000000000000000000000000000000da2b207bb7720aeca2e6ea02b65076770b960d4b7a96ed941a7f409757b952031a472384298acc3948bdc485088501c00000000000000000000000000000000048f85bc05ed78c692138f27c3541ced11b6b0ec158b43d133c3450a905416682fbb8c83dea06a06d294c48289ddb829e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000017134f787c920bc15cf2228a186dfa1d10194087f28b6dd8f03e1c86226928f0eb1c27020a5cc74d94b50c4b4e36b8020000000000000000000000000000000012fa1fdcbaa81c4cc1e37447cae51beb29e55bb19b91e2b575afa3754589ee0151cd9e83573edaaefd341f381d34f4f8000000000000000000000000000000000ecafd00cc87a773a13909512466ed11288c842716e1ca5c37a4d9a4cd7585136c86f32140fdf02e2997a6e19e3d76a200000000000000000000000000000000104cf007ea863dbd473d7dbab6f55e74062b18986e9bc09bcfdc9c23e4bff8683f73aa998a5cce59ded10499d18a0ecc4dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d72000000000000000000000000000000000ffffc98e55f4ba9a642c40678d625690464bea39d085dbc9c99b4c36ea8bff5154eae3c315e1dec29aa669840accf290000000000000000000000000000000000a3df9595167048c52b8170596d4127968194aef7fbaea4594a27c6af05c54bb772928a7749d74311038d1c115e91b2000000000000000000000000000000000b317a3abd808e94a7197e0d3b2515a147774f78d0cd7d36e1156da28a26e33bfa76d75c6e3ae346f9ace050c9911cc6000000000000000000000000000000000fb5fbcc2f74fc30ae7e32143f219db7dfe5db6ecb09cedad8f087b6df56bf9693c8b7d78aace064e7c31785f6869541795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a95680000000000000000000000000000000011e4b907a72f34af899a6c4de211af5fbe0265e5bf24d406798de53ecea273d5df4f4953d13fd7c9dc3bb0f0c143e3e4000000000000000000000000000000001623de5e87b6e1ee920e1b7d979fb9c431c12abb47b93876f9ddfaf28a7b673c18be634f96b813f7e0574c55b628a8790000000000000000000000000000000018ba994b02dad759ee79301b42ea20d7545844c0ea4bff2f95dc9420194cc4196fff12cc09bc0cef03cb7ba868c273700000000000000000000000000000000004b3527c8d148bd9e6006bd298ff8d7fe320748dd3f6d23449e874fc0c2f58d933c1e038a74f60fb6032cce41a3dbf5725b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f083800000000000000000000000000000000129641af11fa92056236ef135843b2189d46d870381261d5781a5fd6f2c5cc1861ebb2e801f19f3adf2216609a9e196f0000000000000000000000000000000007b4007c55e47f6bf3aa420ad75fd191ffe0fe824fd30c3f1961a8168922476fdb3869822704999b044feead470e3b8f00000000000000000000000000000000174209113e2d8c363b04f49487176dc6d9eb4ecc0b22daa7ecaa5548d038b3b7c23ebda4f1b6845425cee13493385302000000000000000000000000000000000a58c80a02b7f93db01d2f8e0005839625e6c4f121f3d69115f435526a7f7cb53177caab4db86273bc2d2f0474235f31df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e000000000000000000000000000000001738a03b46a8ca3f3d1f4f4447497c59f114005400f06813b24ff462ebc6f27c1c3c788b5f83f65958cadb34fddd08f40000000000000000000000000000000004dcfff2bc9ca0282016f38df484655cce7b872b1ff047351ae6b903e05f457d7fefae93104f9dfb549980394dfad2760000000000000000000000000000000017cd89434225dba07be137a73892faf0258b3fb19e6c8cec412fcda912c0613f2a925ad50ae485187020a371ff2dbc59000000000000000000000000000000000f1f9f87d3401e7b3b59331a89d9535adc973f869b81bfd8892a37117d8597ebab2800c966e623469792f4ae2a8eb232959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000000bdcb1d2a782541ff7884dde4167ba060fbd4b117944ae69aa2ff685b9bd7d475f45adce0c9f92695b4f4ecdd48cb9b50000000000000000000000000000000012a55432678043888bb9e7e47efb17700b3e702e389d0f58dd454224a02da3f190b2fef4c9d3e2074c7bef813fb56fb0000000000000000000000000000000000efa51ba64f1e7a1a269dc083179a222afac916778a967098582f55a41394bff3747f8d024261959f6d399f44a40d0fe000000000000000000000000000000000845dd0974c5789a85c3cb09ea441f2c433f0606928ee1b177eb851530d6e6b620b4fdcaffb8f75623435dff99b3ad9526ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d700000000000000000000000000000000180beba92bdb95c7803fca0407e29929ee64e03d61cad96ea0e6c469c5a888cc5ca5eb20983b3418a8da6596a5f1b2ba000000000000000000000000000000001322f7356eb3069fe20063f4be22c44426162dc8fc117e4e382bc4e33bdf3d971ef662fffc1d58ce187c33a43a4c853e000000000000000000000000000000001601a0aadaba846f11ba5c9f48e13bda1007ffdc1b8bbc9e85e83e569e9ee17a1e9e780a50ce617e6c780b8155675f2100000000000000000000000000000000105b2c213aa43ead42d9cfdf1d6c0559c25b4b86af43d4493bd75b76986d0d4f1d9b3bf9e3922b5c08a37a1629cab7d8f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c20000000000000000000000000000000017bc70346765b7160a0a5e556805c7944304acbecde06cadba474c51f05f22445c3d943674cc8215f973cdf11b9ea2e9000000000000000000000000000000000bfdbe202619a1d95359941c249b25462d3ecf09fabb878943a8a37cb9eb94abd7e6399f8d82f90ffcf904f4466cc5b1000000000000000000000000000000000f048db8530a288fef10a5ef9bb3cdd9f3d3b0ef4824609efad96bdf52d7c3b10ef628fa04f8b6513485e55f653f4b990000000000000000000000000000000004ec35f59287eadb1738bb50b0e2ad9d280bedfdb0a201e72594bfc4322ade0b7ffd6b532ebc7796cfc71f88a194bef4b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb30000000000000000000000000000000014424c77af7ace8ebf66f556cf219919712d96d24438466ad620221ce1ae9b2cd75b9c526e25df7fbf3c9250583757f500000000000000000000000000000000198aa00723781714152b3494b76ea3ee043b363b3fa81806cdf7e440b4cea907f226a3c038fb95c932710dc9aad4c9dd000000000000000000000000000000001360e4c775f6fa5e987231dce25ec67f61429ca9fd8160c3074383c30a8c0d7ff068b1d1215b2c0cc87129d9c9aecbc9000000000000000000000000000000001280ee6160800c4b0f82d5c2775238b4b223d8a0ac9a8f8013f138d554ba31c9fedb30e0eb5c330da17f5785b2717422ad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c1000000000000000000000000000000000f1f84251204d9f9328f79a45d15b311984df0715579633a82b5a9f680f6645cbe748b0fa64b9ce1e696e20a5645d6d300000000000000000000000000000000156901506e502a09917f76d825614824dfbc34d019ed53c2ec5395b51512da512b27541bc53331444eac2f618ffd5357000000000000000000000000000000000ea8736a97a33112bea9d07b729e973e3a942422f1d2b24c30e96637b535ccfc10cb5930bb59ed90bef604453df8772100000000000000000000000000000000187378477f60e3eaa225e89d8532bd95babd4a5c51729cca800d364b61575704992639dc5035138664e8e074ed0820033c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e5281c1c9210269a7f5ccd02cd5a7d3648b56d9ca6a4ee50beadf151c2601e0291fe7f1b89b694500e6c636d4e445c4000000000000000000000000000000000d5d5399f49697e46013558dfff544383b25f3b60681ba5fa2c5e6edfd3924267d0992abe65cbd5109ba8a1c6eadc7e30000000000000000000000000000000012a2104aa92871dd8e41ae1ae6dc18ceb7d0f361a5a4fc67936454b8866b8aec1602dd596459cccf6d9e1319ec3299d4000000000000000000000000000000000268795f6f9892f5b476c3a534673538647300203a51a8ff60b530094608b5fdf16297f02ab7ba41d6fe556885f064a4ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff7000000000000000000000000000000000a5585961328c52e0fefff16e66e3367e34339dac1a20cbc5e89b78804b8bc265e6e3fec1da6a62cd8a46be2f08a6d960000000000000000000000000000000016fbbd698784beec5a636332c0b20fdcb68fd3015cc6d18b541346a5e6af76613e6fcb14c888a2b8133c0f4132fc079300000000000000000000000000000000041805e0adf2a32153b89d1131226cf0ebd77cde3116a168e792ae8b88ba2edcb1fe7275658a384251b805d282ee039c00000000000000000000000000000000024213e4a8504cbae4875617b9b78473e7842ff72415ceacfaaf2e8b415f9f7e411989bada8101be72f9295dfbddfa3f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af",
+    "Expected": "0000000000000000000000000000000003c4f051d528166f256d9356aa9cb885db5680c51990d9474a948848888fb82a9b86daa7a2273725ac8ec564ebbf15db00000000000000000000000000000000010a6c4c7067f511ca8f1b66bf9ffcbb275c7575540909262f7c4332c3d75b2f6d2f3ad2848c0d455410afb1cd60c835000000000000000000000000000000000ee5e582554b3930c5670d4e3542bf32e8b871849d7859eafc077bb2b533e936d462f614057f9fc09c4010afab501c1f0000000000000000000000000000000017fdbcaa065d301adb94a60dd20dbae71512d369fc82c556ea0dff66843be768be942e060752591c6eb0718985d8e313",
+    "Name": "matter_g2_multiexp_92",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001353960aff58d45691c5378a0676a8e837260f5819cbbac9cd75c8cc4c6f1e17b9dc9843eabc0b1dfb27ff7631e4e52d000000000000000000000000000000000d6279a43d3526c035e88b0b640b04d42ea573ed07323aaac1d9d5570a8be64782682892415ba2be5cbb13f56e3a44db000000000000000000000000000000001250fd14fd003f88eb6e0e80e9f2ebe204475fc6c06cd10fa45608a17b7039afe0326474ff80c357f86c2825cdf7a16d00000000000000000000000000000000186cd91cfc8ae625e302946f2b393ea67e1107c0bfe938f5f36d28879fa0c0780c847aac77d0310d43211152c1d5f5d314d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d98000000000000000000000000000000000e55a98e8b1e59600e86cabb5e8db8ee622009b1618ff0df3e93fb55b80985bf2a8ed060aeaba53773274d4186934f75000000000000000000000000000000000bb7215fc43f465f51fc8265477fc8c79493966f040e02f0eacc4ebcb3414b84fd94ded822bd24dd5ad5720f12bb8313000000000000000000000000000000000b23328e15cda8a576ea352b5dd7ce382ec781deca6c23f646e42f0cf63e28669539579ea51e3c0afebbb58e1e8e3243000000000000000000000000000000001716019236169bdb4af7bf7d7ce0aeee7900b74023acbb16f6965c2abcf28917bf88d0f9d5bc26a81710496f7821fd4682cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa8626000000000000000000000000000000001718a4d6f5e78524d8df23d2c589abf04e3567d2176539a30b9f73c6251de573caa60c2881f3da99c48d48e9aacd7402000000000000000000000000000000000ce0e35721379077e6eb3b572f7f7718bbf775b116521c14acbd3ff19549c75d50bf70ce84326cbc3f9e5e53605d8ecd0000000000000000000000000000000007cb3305ef0d2cd7de4dceaf25d2eff44d4f437e065f6b244bf1b0611c891626eafc4b759d55b45d76e94b85852df1de0000000000000000000000000000000011cb56d2ed32a46bd951836f8e0f92d3824a4cddf011eecf1e2d92d81bff407a04abdfcffd60ccecda6e9443b328d51eea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f982490000000000000000000000000000000011ba705da23100f853882dd166d81ee1d7621550d156b14f7c2123e2681887ec3724626061db68b2c63987325b27d6230000000000000000000000000000000014271414fe078a80587269398afd127ce34c8dc2a4851f76613b81dc99d766d75c703949c1093b04d66a301a79d89bc30000000000000000000000000000000011b7935ff284b0f812b5da5b28ed338dc4c21ebbc7fee04db834732b11fd76092db0e8d80368255b0f1205129081e9af00000000000000000000000000000000104ff0ad2e3db08d3b4890b2e54f29e456e627cefc3a4f07c1109b764dae4142480e3e5312ada43fec9ba96ce587e8a4e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d000000000000000000000000000000000c60749ef36d63960022f3127d0ab4e12acf05ba1e1a136dec89be388b9d7144c1d78c04df658727763dbaa9725bd8b90000000000000000000000000000000019932b1c205a765bc9de0cc136999deb153222a9dd9e9ec3660fb6daef56242d08791d440888e69ca0da2bbe0fcb7d79000000000000000000000000000000001764790d12f5ff79ee4f2c9fadd5dfb1cf47db70b9e86018bbdbffd1be18df193c7dfa71533afa381053a77e02719c6400000000000000000000000000000000044b2b0211cbb407281ab2abc4725c2cd791b313bab8779954a2461ce445cdae60d4a9efad9f90f80e66b1438514e0f0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc00000000000000000000000000000000175cf9e7eead650e7ae4fd657bc288b6b6392773bf1bbea48e17172a5019637fbb2bc0a3d0d1e3b8054564935c908db200000000000000000000000000000000136da2a625cf72403d0861b9cd947cdad12b1f1e6cdefc4aab6756536425285a7953a1b892df40ec12ac3430fec889cd000000000000000000000000000000000c2d10c6d71cff4e1deba1984bfd17166571e64659ac91b64c343cdf587c29d52a2266c00a57c01feddb1df6439d21d1000000000000000000000000000000000384a782fb31278f49c840bb8f0552ac2734ef36bb3d115be7df20333aa747c92db990f7e879399235d122fdba0eed76eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000a9a821cc63e7c9857b0f39f7444a1e00a422f7cd5d0575c26bc5c6b98313abfde51e3f6d5f4c817193bdf391344e5ba0000000000000000000000000000000010daa8c7194a75cea757b6ae4eee85006eda459ff2cf155b1b5f19c3ad341972f72e28b781c4878e8919c7e5abe9a1d5000000000000000000000000000000001154d5d5764aa2b8818a9dc5dce30ba2197a86d0bdc7dee3e600462e295cc3a69dfbf8db34acf138e7a1f16b62a45717000000000000000000000000000000000b4243a09b05a958d78ba8ae25fd3fa85d520b95e56f1dff44e556b221a075f8dd3370313886d9dbfc56a75697454d72acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a9000000000000000000000000000000001820f953fd22b71ce00bbe9e9b78fcf5fb28bcb925f6b5dbf5711e00470ed7fd2f38d7291d40514ab4258807f29150270000000000000000000000000000000007b737b56a2ba33f76bcf66c0b26fb44d5f79879273f6ab21ecbfe6a5744da289464ca2b46c55edaadfe3210b907f3f7000000000000000000000000000000001735d1b39c5369bbf886c5063a96dd12b85e56fd9d8ff9d84520918e1dfeccb62bbbe1c2ab440ccecd0fe66f6ec55853000000000000000000000000000000000e591b7709bf00bb2a87e9edb95720de19adc41a42378cf9ebb930c6d3f5993a1d7b6320040d5c69908685d978be8f980cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d2000000000000000000000000000000001732ac410b2a7d10110bbf7709dc6fdc91ce742f8cb9b2c3ba37ba5f0934f8622c675753a26d04a176e24a630d090d81000000000000000000000000000000001111a52da6aca10cf40127fa8ab7683505305e0d474eed28a5e1735ee6877aa00c1bd598420876f2154b814660f3fe7600000000000000000000000000000000098c6d19c2ff42c2c57a4924693325de1a91135e3474ec699b70439d034469e72e844a5511e23dff3948a66cc2a2165300000000000000000000000000000000175fb79e5e54963cdbb133f38dccea2d1abc3cdf005c17e8f2de6dba9b9dbdeff7719983aa9ddb602f0cf966fdd430e0b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a05440000000000000000000000000000000004e88468d35d72dba6b3e4b9ca216b75b5d20c447064a48bee6a6ddf994b1e22fd6ee8abd60c627622daffcda219645a0000000000000000000000000000000015eb2ae16e3310b4c4ff557f0615519c13f29109d9863418fdfbe6309b5bac4463456df8ebb0b6d9022e294cc16265ea000000000000000000000000000000001288ffe0ffdb96708558d914bc412758770d048c4d50523e2b134f8468d11a57da97e42bea303ab7137e2d26c0b3b8f30000000000000000000000000000000003ce563b63c50b09a80b71a1a82995238a9de31aaf189c6d29307924b6f0990854507b7dc1644f689c5abcf931dd5a3c825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a30000000000000000000000000000000009e39ce653485caf699ae1d1d9cf2b8c5ea85b80ea042279e57f0beb81056159e49f73d67e7b1f9ece9f9ece7dcd2cf50000000000000000000000000000000008d6492cc335660c54e4a34b29b337b5800f1ef992d124524c799c04c852ccd3cfc01bf39515cb8b96151753147e8c49000000000000000000000000000000000ca779d87aaa3a6552f9f1a10b0d2e635be90022326db04e6072f326b919ee55d4124b9268f55751dc0f18172bd327ae00000000000000000000000000000000112eea543d6609d0acfaeb7be98be609f03304f50c3814ee8a010283146e6b5dbf170c7314598cac06efb9ced1ac2930307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd0000000000000000000000000000000006624dd7f6eb043da41a36a15752f370eeb3cb2e6bd88b337b370fe0660c5ba8fe64f62e112f91d2524e9324f3a049fb000000000000000000000000000000000415b964484c9246385cf95461ab955ed0390e20209ed405d84fa8c8af9fa7ab39ce89049691a63c61b12bbf6aa2a4e80000000000000000000000000000000014411d7b2db7c9ee78ea14c6a315df3d90827b511db2e2423d660176384d8f8afd284879b22f5aeed73afb2eca4be52200000000000000000000000000000000105bfb471340e76f28901edbdbfe2ba246a8824b501ae2d4a73cffd2690181347c1e6530804614e88e2bb13a8edef8f4bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d0000000000000000000000000000000002b17f4b0b0231be229d87f075998435560ce9046a8b0e8f15e3a9f07cd52f3316f6d8c00d6a872362e7066715cf990e0000000000000000000000000000000003110eb232154f8a06834e2ddd33c0207ea552f439a6127b652bc261158209a00654e50341d333cd1b206a915fe0691d0000000000000000000000000000000007940e209c8934c185e4392f12fc0afe3d234dd1ef3f92df18d76be8fc42bdcdd6d1ea8d5bb6f07b3f3caecbeb5ef27f00000000000000000000000000000000012ec903a8442f68c03300ab02ddd08ec935d97bec9050d26a5e276584592df3ab87d596f90768d2c0918099b28963be58aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000010fdc16bff0fea02b325c672fe06297e0669094e2710d0baf3838f3e234c3f776bb3fd41b967c9ebbc72a6bc6eca70850000000000000000000000000000000009d64ce322e39d5b2d0872760a61a831877c450b1cfac6cacec52d4070b0f179dce90afbdefdaa8466f6a6e2e83ee8da000000000000000000000000000000000cddca46f3b24e05b76e61b4584bc716ca7036afdd914731a61347e453a26d07549e9808e553ee056bd47e53c75eac8f000000000000000000000000000000000451cccaebe1a188d3eaadd40090ca594f071c8b6d0e0d82f5b2d43fa784f8437e4226104c4cfdb24ece1ed75375aa616810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff0000000000000000000000000000000005832ad778dca8dfcfbe741dcf311024d76341d5920b6830cb75893a112c9d86719583d1dfa7287281fb73fe21650c3500000000000000000000000000000000044feb86b4816e45ffb98e9a670fcb039fd9d8844a2c7ff9b7752f20e619195fe6ab1148f30afa393936d3605fa4c8da0000000000000000000000000000000018db9365370a8c703364ba6d9c48b3512da46cc603a43c3fb91c0a8ee59777d7cf9ac646c3e4274bd950d7de92ebce840000000000000000000000000000000017bd82310e251701cafbf8c4dc5b9e6c88085b0df287b6dde7887e1f64f2d9487a25b31abe07aec7d99a75baa5983195c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d470000000000000000000000000000000002c1df194f01dcb503dcc8a283f059b82d141274c8f37cdb6441aa33f84f16dd288d566752a93ca23d26ef5834c0658c000000000000000000000000000000001700fa4459dd4e609453284f4f7dab479342675a87c1cb42b601908296557f39256f1597ed3b9ec38ad0a40a2c728f0d00000000000000000000000000000000135ed4f475eb99397cf204f971215a0303316a3ed8b62b303b4bf756ff753410b7fe263c4e97fd4c4b399c319ff3ad98000000000000000000000000000000000a487e179bf1b73627af9d7d2b43bc0e43127a8fbfeaea7ce958ddd53ecb27741eda187745e3917f1cbb60adf0286f5413a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc",
+    "Expected": "000000000000000000000000000000001327c57e16f03fbf652bbacd16cf574113860eb87b8f2f6e498dc5dcc4f2fa63859d922d88ccd6683d503d0962db5336000000000000000000000000000000000cb06948c539cbf686f6936b6a1ebef2e148d98c531da36272e0334afca5c2b16a52da542a0fdbc3bf764eb877f5778a0000000000000000000000000000000003acddfb5bc4fd5579d3f592977365840be4d3cff96434e5ff4f01ea798e4401930a1f5d91f8de3ff98504dce398c2ef000000000000000000000000000000000a5a332805f704613eb085d6639f99667d0d9247cae34eabcfa399eed551f24c5d5cb05d6458530ae270b1be682e71f4",
+    "Name": "matter_g2_multiexp_93",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b1b06a76e5bdcb6c1c2f1952b49e1820a9d8557229fbff8740269a0b819b91cfd0de20db0afd76a2cb0fbc5fac12ec5000000000000000000000000000000000347654df2084082efd32cba2b270f66b0ed30fa8713b27949fc9d270ee8eaa9b9a7896d7a52dfd8faa3e0cd112a24e0000000000000000000000000000000000bf5b7a2c0c8bc223ab334bd1df5d9fd4bc0c635379ed2b32da13f6178e07217bb88a4bc2eae0b975f2e566f657d23aa0000000000000000000000000000000017042f8585a07304995853270b1b03bb08484104f7498a12bc865f2a0e37e662fc4b0331b94ee5690efe74056567000bdedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d2000000000000000000000000000000000fcbc73d0628537eae417f8efc67af0a4c9c375d82406086bdff669911fe1307576333c389f189f49677cbbfe2ee98730000000000000000000000000000000019d552b85b1445660ca49518d202afdc67b0eb5be02c8d3482dc1b12e5d40a4ff95a49ce47809e4d6644d04aeb67b3c2000000000000000000000000000000000ed536c0f19f592180291bbce59a72ce5e516199dcbd4fbba736cae2edbe3cfb860ead0325dcc8f8d9be1ac126dc6cda000000000000000000000000000000000f5d4f0c0ae3e76b1c41edbbebcf1ff17c7cefd41e7ef8f75dfc10170834d05820149d5f721a8c6460cd0181571fca97db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d6207f6684f8d2f083c963551bbf0a674ba40e691a34ebe6164ff80ba9bab2cc23024a896d7b906fb74c95016a9adfb00000000000000000000000000000000145855e7d610b50cde39db8995b127145d68fc9bea3f075f65b7793acbb14bbb313a1a39bd96fbea6641baae02612b000000000000000000000000000000000005b533ee83cf72f0e4d9c9ddcc6b91f4364e50a106becf766987c490d559d0f733839ecc706bbc9c2c75b243814068a3000000000000000000000000000000000cd8fba13b9ba7557c7577da183bf50810fb14eec7380e3b3d4f2fed62bb36f2b5ff288736bed0578fb6f47fb6d22ac86e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca4000000000000000000000000000000000fd6a466f2eb12f6337ae9f9b847ac1481820013142af1a474229c5f5f5e1c0bb2d9678c19c7a3a1aa22cfc7b5052e0e0000000000000000000000000000000002a0340f5a0caf5c66719f7d546972bb4b89147989280542787d281901ff036b7c69d41418c21c43127c0158593aa5cb000000000000000000000000000000000deeee37ef96f26a4907e1a8a8f3f030dc09102799bd0c6dbeb1d208a0c86a423d0da6313e0be03c026da5614a6a576b0000000000000000000000000000000007220475449add59b3cc6570701528dcbdedacb9a3d39674ad4aef4d94114f24d2bff32f40b25af97ba883905ea6838a27a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000009d7023ebb73df81455f74cb2708c14ccecacd49521a0cf67ecb6edc8756e286ede59eed54d89eee5f77f178ea8fdee900000000000000000000000000000000002ad48fc3192634e7b01604678473e286afb0efe67a4377bb885d38b59ea00202241fb28c93232ce7c9a3dabb136a53000000000000000000000000000000001934664f2bfffb254f0415d6769f4e2ac710ee88cd822bf5da5df3a2541f887e4155dbb7e8056efb2a0370d6f9173e3b0000000000000000000000000000000019df518e1ebafe95adf683279729a3298fc8d7eb39c9a3dfe4b6665153f970e243e50dfb16fb87b3be54192f69766659446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000001870048d360f397877321904563d35bfd0817ce464e0078e9605a4744e2723f49f9cb21dd3d6f37f1f9aff5a6a99bc530000000000000000000000000000000000e29dd0da13ac451d013d4a38408827cb0e739772e1f250d31e4192ddc13d651ab576ed6b8f4ee44e928fa663244999000000000000000000000000000000001646183099579322e0115ab0b3bd6c814e216ae6b2b80206354925565b7bcd97bc12668b7f3530a95409456ac99bf01200000000000000000000000000000000092f6f594ad0d92c9c64f78c819c44320e6bb5dc1dc8fbe58acc7ce3c101e49a74ae6d50b1a668a3b7436dc445e3da345f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f0000000000000000000000000000000000428ff447de18dcc11b2c5c679bc2efd125464f589013c6964ea6cab33d9b7cbcce3a5d6177bf43114ee256f23fefa10000000000000000000000000000000000d1ded695e88dae6dfa702375959831f4bda688fc0faa289dcfb90a07f3a7963f2c9070958561909a2051a852cc15e1000000000000000000000000000000000c39bf1d11fc5693167890246c81133faee93a8639f459429757965e0b62e372153ce53c61f2c539247dbe7747b27d1c000000000000000000000000000000000e84ecb6dd9cbd4133c22350f07a976ae13dcbe4c6ae09ccb023f2118fa2dec68c20ba2266f9b571bbe30dde97480e0a94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000015e040fc8753f06ed1112cc06e2cb7142a4fc984834f01faae718c17cde782d5953547857ca9aeee1c4a7d91df060d330000000000000000000000000000000006789ac15d719a7159b650b757f7d3cf58fca02d3b8f3685478ad5e5b1dca0508dea7a8203ece97c7c6d32b2f194458d000000000000000000000000000000001824d75634043cac3fd17ff0bb141daf7010f70b5941d8f75f1ae076713afaa7e0a0a25fc71038baf1b1255d64c914c6000000000000000000000000000000000a2f71bf85af6392a8a070596e30225bec9e3dc12c70e8df7c545bd6bbcee56799db2c9a8d2504c4f90ecf6a5e18abc9b677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000008bd859ff1f22d682f86e1a0e3bdf3a332ae78d64814720687a3de44c9bdd7506d2696b4daf81a94d33f64983967fdc2000000000000000000000000000000000d7b4b958e0087f8edf18a4370ff98700764c126808d5c52afd3e71ee326c766c1e5712dfa351cf5b3c518e52133ce780000000000000000000000000000000013a145331bdd9c93e63edbabb9f6c541a7c4dccb1705f07eb353a0407074a76022a8e5f5f2535b41ecf6474649e257bf000000000000000000000000000000000a12e461b7439bff0dddb560dba21ec53ce88f71fd3dc10723f3d8742ed63a1ab725f7e9619ca1ccb729564dfbdb1be7f5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000001430a8184c5055008a06ea22ca9c997d1a24ddce7e374937c32ed1e487c80537b238a589b5e50b86fa194666bd3410e80000000000000000000000000000000005c78c94f457bdda242deab79524bd2beac82bb1cb427dcb2872b56d1f46d11fc9d69ba132004958fabc5da7d6d103fc000000000000000000000000000000000e985e8ca038b5dadc9fcaf22699e75cad9d2effa47fe7d4c579ee056b1e34ccc540372111a665041062fc6c39e05d170000000000000000000000000000000018c865243534fbde740de0ffbdeab0d38ee878c20f5d84c0226d1f2b14ed3359f5b5b909808b6b3789bfcab3be75c4cdfa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000c5b10541ec34dc0a8b8e42d9d6fd6f4f71e1fe56b5afa323f4ade35c0170b5e224a66771326d9edbddf2bd38c6c68ce0000000000000000000000000000000019cf33c19936f7489a1bbc095d0f5c6ddc1f43bccf7e8d1b30fb8e8cd1ef747b483b9a8e9faf21cba7cb17fbee887ad70000000000000000000000000000000010e83916faa7bc9de9feb8a7f34ac6f2aced06a771b662cbce846107245edb9c07632782300e838957788a8d88c8253c00000000000000000000000000000000066127bed5ac9f2871500fdd68a03ade57c35449d4b4186b9fac7c89e91b4ebf2f2a02e94d0b578aaf60b32017f147a493d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000000cb5a734a28b44f04d39ffae049fe8b63b138411661ca6dba00c72cadd47b50ad4b71e858e817561682d6ca378ebbe870000000000000000000000000000000000baf4d689baa09aaf763ae7e142b801223c8ff58f2b541ee4c44ab2460fb8f6dfc1e9f61a8d73aeb92d7d08c281cf410000000000000000000000000000000008a0c736f19bd0005c9d25f88565b1355e53fa3403021577de536712ec986567184f4dd626127ee80dd03cdf9044b2ba00000000000000000000000000000000063ffb7a3b4e057a9ffe233296c11fb462136fc4b187be6f9e36f9e6d335a3d673ef8b9ae6f60c146a075a1789f389cf3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d06000000000000000000000000000000000c33d89595d039722222b9b9ee7ff1a0dae896a8de97f202d3aca00bd81d0169f14676efc4b051bbd339dce862d8b60b000000000000000000000000000000001109a24dc6f70bea47e040b24df395bf561cf5f1ee79e90c9b0480fff0795677483a85e6f2e9ded4f36ca849ff39d6f60000000000000000000000000000000009c7878f3a4e4e3149b72149a7da91bf527c4d7c94b15ba80b02e0e50b02a2c482ecae9f458a881c87e669986514f6d70000000000000000000000000000000004284448e42187c128578b801f76d421fc508cfee9360a7203a91d6f9cc7ccb6ed3211fc5df9e15f14aea98bc298b2f95143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000271ec1a3f8e3364ba8e101b49c0bb17e2b7c7f27a4aa4d4db5c07203195050f30c1a05d33c524a84b1a2f0ce31a587200000000000000000000000000000000082ce9d1da5d7f192c537b2bd617b36b65f88b308fe1ff85e47c64b62dc62324458493d1cd1da9f5fe308d27545fb6510000000000000000000000000000000000b30356b59eb04258096d0c3f357fb04471583cfe6a060de5279bf2cff4413678c1716ba87d0b6de6b6e79a96ec26030000000000000000000000000000000003c02470a14211fef14d754f6f71efb33a06a76e099093a5b9512f907ff819e1e0e15f14995febe48852007bb5c380bd0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000172df3290c3c5044d590eea59980d02e02d4fc6fe7948168492362de8f0a85df0c3d09d8cd8b206cc4d1608311ef4c130000000000000000000000000000000010e4d14065315a0d9e48204e47955ee9652b08318251a7836f32e6fc015d4856444172de44b3b88efa1b54dad346e9b1000000000000000000000000000000001549b9c85cb2fc2c7495d7ef6aa1452e58937baf58717037069e6bc6d72ced3a163f800991cd26510e71aa64c44f66170000000000000000000000000000000007814c2f1734fcc8cbf9fcba06b936c86d0452a2370f8c9480b97105e42f9babfe0869cecda7e15500e9d8d868290201b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b0000000000000000000000000000000014f849e9749a5ff6b7b10daac7f5934be5f783d49c8593367c4243664e01b1d3552e878802d7dfee823e0122e9fd46f90000000000000000000000000000000000d0b32d7904dbf08269ca3c6ae3fe582501f55e32337ae361fe4a58dada560db54205e56a399aed33bce8758a05ebcb000000000000000000000000000000000cb21440baba44c3cc6943c8cfa2fe544a652f06423d3de06c2ff734ebbb544da07ba8982b3009b6c4857b73ceca570100000000000000000000000000000000174ef591975fdaa0e3cb05bbb4140abcb38f685ce4de77c95e2cec1911985557b77d9229940b8c9157ccf9fb553e8e0d98df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af",
+    "Expected": "0000000000000000000000000000000006da1222c7ae02843ff289931fcfcb315f621972f45e4fb4160b8bf48cd8102d50fb53d2c699afd36892d91f5e608784000000000000000000000000000000000523048c5de2d0139965c976d8c3328666e99c249104712719e992334442245e955cd6b14a1e3d666220617d78edcc630000000000000000000000000000000009f669d4e7d89fa8d999d8d5a6323da9445583344276bd6a29494a91174aeeb29132926a893d5a0eeee9c3048ebc0dd200000000000000000000000000000000099ee1c33d6f09a8d063393d2a8debeaba93027e31f7b23c5170b6747f56bd6e6494de966dc280dd67a38d39ae35a336",
+    "Name": "matter_g2_multiexp_94",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b46cd281a09b85d977e88cb2251cc61cf13181522cf1c344b114783c4fa22d08f2d008faea3dfee8ea7c56aa35ee39a0000000000000000000000000000000012b530bd34f5b31161355436c7dc444d41d634b0ac3292c0096558618005fe82d189e4b208a48240dfdb4d50ad44dc620000000000000000000000000000000014d334e7835d4bcee2698ca4f643f9692f28d8090ebb8ed8f67c60639c64eb116067e5e8a6d04a0c88c765c608d57ef1000000000000000000000000000000000578cb4e243b4b20c99afefcdc0e0a9e308ab4bec6d655a8c4981a711515c807067472a6fca30060d776213b8ed98d74e49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c8900000000000000000000000000000000046ad1212696acdbb686543d284d7cf2e1e8e8c10af76c6ba51d55f060c006dbab25d3a789c71c428f5bdde9aafbf6d5000000000000000000000000000000000a6a880d52fed6a45bdc61d9ee78d8fe472e76ccbe155bddd0e2a967f4d116bb9f2dd4c62cc6f7224b835c8060213ecd000000000000000000000000000000000786544589eda15edc433edcbaa46d4953da72473f04169ea64dc114b99f0a58181d41dce1fcaf7f3109f66aef02e53900000000000000000000000000000000030759c3bdeafc94fc8fc0b03ddcd96869459bf54ace74582aa06c179323ef076aef89c09ce8e7bf9109ab2e8c8fb0be79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f3953000000000000000000000000000000000e6addfe0db96a7377fcab1fb92183fd7d7f13ec003fdfe0740bcc8cf03d8cc602d5d808b4bc874f34944a65b249997a0000000000000000000000000000000014a4337107e716113d8ba0fc7f75e85edd1c132e2b3dadb3f9cdec1440f261513646525314b5c0de6fd372472aafe877000000000000000000000000000000000d472ee0484ed831f8ddf7ad86faef5443df8b943c6fd4c3f94c8d52d9eed6fbb53107170a60f25be52219ca4816788f00000000000000000000000000000000035d06ffc452c65a31f80c3f8a0c1e2d15e32d993ec06c50499bc0fb8f669acd3d2182ba23d942489ea922baf61dd49cd2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b000000000000000000000000000000000ef1f5f6b3041939557368d613279043d1aceaf5fee3ed90b3b756ad409d700fb41e62b3758c8c2d325db7a37f339c610000000000000000000000000000000004d66040a8e055399bacb6a1e762b698afbfabf789caeb957fb7a3dccb01d7dff5414e90f5a14961c4e980b298f834ec0000000000000000000000000000000006efe9e66078000c26d375e87ffaca643aae9cd3f8337f5718e0e268b74f4b7838f7661dc0ce60f557e162a21ff467160000000000000000000000000000000014ab782a3b2c06af7e9c2f28f1604cbfa8a676a874853bf38195780751d306936cefd1cc38c2192cb756e28793d2abb3e9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d300000000000000000000000000000000057270788a199a894b37a526a26bc4d293780d365a6b66247e7417884d543dd752ef7c89f2f4b38f4b51e6f9d86b45ad0000000000000000000000000000000000b59fedd6798487ec09d226a7406b27f04f7983075b4659ca6a78c6bb8aa83828fafdc6488518e2cba6fa4193de938c000000000000000000000000000000001105c18d92b4192833302814ee9b176831e57fb64b703ab3c2d3f440ab302c8fdf7ddc81933d3b1adaad16038dd6dc1f00000000000000000000000000000000020509b08e6ed980df29da649051c7095edcd4eed4ce95cd797da430cd09062a110bae21b6f73daff2053fc0289041fac818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000007be348ccf6a76827d3b9b33e7a89378c133c9b226e47dcb205ee061423ee6e1b838bc262a7befae7c15aa385ced00bf000000000000000000000000000000000689787c19192ad55b9c6c260a5ec3aa203ef71f0b746eebf10f82526c4fadaa8570936d7049c1a46e7f3cdc455a63a6000000000000000000000000000000000306965b09678d481aa4c754d56a0bb4565f16f7523cd0b404fbd39dfc3b6ed483f5239fa30f13aa3e87918ca039d5ee0000000000000000000000000000000000a2586143f9610a96eb0ef86593988770db5ed49663eab72f8c368b9388bdfbcd02fc6bee09f4fe055813d140ca0fa89ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c49645000000000000000000000000000000000018f46dfdde786a88e582ff6addbecb4f58e12c2625e3d6440f2e5b5781beaa95cad6f63b7d132e84700e7bd344fe3200000000000000000000000000000000185a4fc339a95a50551d53c18bb0dc3b74e9c164729c2b0d919392f7aad2be3ebae3b8f676ab81ea05233b3039918ab50000000000000000000000000000000015395b020a9d0bb336066c1347dd91c557b6ae7b8817cd8a2cba9e5bb149ca3401d661227c26d52a9be234faea894c8a00000000000000000000000000000000103d9d7e33a0767554e13b57dc756981488a3c7dfcc026ea84b35b0af21193e301226cb5a4760962707d19a95841be9296acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000018359aad8af59cdda484232b885d1b14956ec04b5584684b13a64d97b8310c283e5d66637dd75de405f5f4bc65a6879a000000000000000000000000000000000849fd55e4f3d4dfc643dfede6356826eef21290b84f7e8e226deabbc84273d95f7be5479e9656dc907ec367a7ebf8f60000000000000000000000000000000006ee01b54eb7834b4de53f821ad46f467cadffce6df09751b728d0952bfe615253d7ad173892a52c6181810a815bd90600000000000000000000000000000000161472d45b56dd9fd276fc607f2eef84c5c843ea05799e732d7eb6dce96c632335949e1b3a06815e410e919f4cdc3fb360bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf00000000000000000000000000000000107ef91cf3a3068c4e5644676f7bc7c5f9ecc361524bf3fe2ebfc606f22f8f83b38c0d4bae89f3cdff6119cc27fedf820000000000000000000000000000000006a7f7cad2fa9db8824e4e30da7158f7737d2536554b904ed835c37add0341c07c5220db0f9801da2587a456300c7b75000000000000000000000000000000000f6dc3adda42dbccb1d1e3fea8918f5572e8b26ba3011429e754edd28559b731853761d33777f4e767094f80e63d417700000000000000000000000000000000107d93537a79173ba9367732fa3a28113ec37e053cdf31ce6970dedfa8a9b4cd55238289be9a6f40319e3dfedd132f95537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa98302150000000000000000000000000000000019dc19a1663bb05ebfc0b7cc23ea9e07376de413f77e15a685a3f11fc19bf0ddf38d5671e2a5e6e31624cbcd47a19cf60000000000000000000000000000000019e78aae57f327fbe8ce794afc22bddde08ff9bc9ad3527601cb1fd5dc0b8ed8fdf3b210f86760954b48bf61d74162220000000000000000000000000000000013954a533bf871e99f4a7d81a8b9931c480ce7fc47260c3708c590ade42e6b7bb887d4d24aa18642d010a8170cf85d34000000000000000000000000000000000a561d3f64ba31a6d45ffcf1bcac95f8f665133a1e962e31351ec78e369042bd3afb0c43d12b3087168c1142107241f31a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a20000000000000000000000000000000007149094366e29537b0ad7239ce04bf49f253e4b746b9fe440dbf9b425bfff21064fce66e286e08c87dd83e22a3b499b00000000000000000000000000000000045ead132e0d03c842656cfc82a45c8b4a3b0cee7a5d071c5f235791ff7b5ead071b2c529b446a15aa8837aafc11222d00000000000000000000000000000000013159458f2123698ad4e7d41da47ad7d5083b928839e346a32f2307ee69f643ca11335d50e47d328b0079f1873cc7e800000000000000000000000000000000167edcf807ee723ba70e352367705448047c6b5223fe703381af6bb103cbb24da739ed005b14fab5699fbae6574505a7c7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000feb6f6f85903b3c8e4d6ec2ff234775f12727fdf7c35eade09c9773b004270f659b00248338f0b749d6715778f1f4d90000000000000000000000000000000003300794df19b9e472e8b869a2762c07a9251cdb96b508dfecdbd62fc3c3843b37118d216a64519bc3bdb71e40f9bd700000000000000000000000000000000005fa144135a5d6cf1c73055750ab6582b4c6d368566172b75902b1fc7a6f5de2a251ca7efc7ac6cc6c0bded14df02b700000000000000000000000000000000004239a7bfdefbe78116a588810328024b1bcebaf8f28f09387dcab66dcf2b02c94002df09d12db369fef9dd960783c0b84f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d00000000000000000000000000000000007c8a24005575a3098c12ffa65095bfe227ee59e5e978a7ccab7a9a72391fea61690648c102ce24af723945bbcafece0000000000000000000000000000000000323d57bec7dfbb4614c8c3b286860fbadbf71901fa006149053ea614dafd56b1f3d6a86fa55bf1cbdfe8af4ff08dec000000000000000000000000000000001180b2b0b9c4c12f6d06eec07bbf6f5a220722015fe5365d1c4ca9e58ac9c8f67964d8230152d7a2220575c756bdf8b0000000000000000000000000000000001969a364c447f07d0820586bade587ccc816e50696aa0c5ea4f1daf6cd577769a890b44caa013d93e7f21f5ea269aa85c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea0000000000000000000000000000000008a51c01c3bbed13d42a4da626a8b89e2811db1d83d7de3332b36881ad14a5c8668ece4f5ed2b71204810457aa3d75cb000000000000000000000000000000000658a56aaf627e3f776d3f03caa2c00425bf197c6fa20c92f563f48260109a8f935d0d1638f5039486ce0c0100834fcd00000000000000000000000000000000126d1964f2d964c290cd7364e175ca4a855149e5c4ba488829a436b09ee5e21f6c964e439739f15317873088726bd51f000000000000000000000000000000001803186f88833393bd853970ca4fe414a43b7a619ded1f9c830444b4d43a94e9146146e2284d690436b395bf1e3fad15a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000002dc3756c7f4bb47559cd720a3acf4159290d7413e0498877d1fe321cbcb7cdda90b6c8b4ef8e27b2642b82ab9b3174d000000000000000000000000000000000c7490f1ccfdd91aa37a3044d265cb0612bfd9c065c370adb813b2d96f02d44041e79921d1b8935dcdb8c83ea4460ef30000000000000000000000000000000007beb34bfb9ba9b6fb590c7e830400888095d1958b252d187c184de91f165e12599d66345341292fdcb662deadcded030000000000000000000000000000000001ce203d58bebe1eb5b7cbc6038f75b2f7534bce9f50e7e4c91d6cc5ac1bb68d9fd8ce99206c5ec92bcabb71672c6ac195a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000765acace3e238e51bdaa08c0f6d737c9de55b5ce9ac3523335f0d35bfab6f4e7e2944b8aa4ee031ae9d39d4db96e9ac000000000000000000000000000000000b0fd488a6f9e92c4bdb5e82b52a0035f9a0aed7f69ec65303632017669f34d11552f849326e4dd204d58f50f3ad124800000000000000000000000000000000033991f66588b5e39eb78c7cbf62a74bbde2fa1b7c96164cb58040f0887c485b372e0ef4def9d38da9c6f5c4df2d59a700000000000000000000000000000000187d41fa7905739078d2c2f8775394f830d20352a9d91e97568c6929412f356009239bc9e1da3a8c766e89d09893b5b5319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40",
+    "Expected": "000000000000000000000000000000000dedf92894c567ee656051a7f02384edc7206152af6d3c5f662ca02559a3cc349c6b034c6fadceeccf652a396dbec6c900000000000000000000000000000000089deb173bda620678247a7218408594efff7ab0cebbf627b93ed37e553cf944e09232b92afe2f5f31d29bb9ae442c26000000000000000000000000000000000178bc39b2ca8b032d3cde53d2da3f8797026d78c76c51381b377c79992f027cf55ba4e182773c99c99ea6293a948e5c00000000000000000000000000000000195d9cb91537e77e7a4be4370b982b6d36190342ef6ebc2250a9cc8ef6ef45211736ce1f41da899178c1adcc4927a9ba",
+    "Name": "matter_g2_multiexp_95",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004638ececd7f626a069b1bc3ad9d0f7cc71e5f0c1b11711fbfee1f81466b574f7a11de8161e55eb574ab367f56b9d6480000000000000000000000000000000013ef4f403f139771afe7e97815d3b3777818a3054d02125d3a25138e504c8c2c6696061572322aa19ace9ffd8e3ff308000000000000000000000000000000001910f776582f5acbaea626d2378e9da133b63afa087f25c2cfbcd1e7b34f6a237f2e9adccc303f9d5efe22496ee2ab75000000000000000000000000000000001963bd62098614c4ca2fc2a9e2d679c2b74bf9d322d34377cc63c3b8e7f8a4eb7d6d440d081e044606402fb3f51b0cee2a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000001935ecdf4f21cc6b510321b4ed2663e339954cd7399b9d67f1d9e2ea7fb9bfff8531f83ab59d3f0546393dc289ab2dd7000000000000000000000000000000000f390b86fb4cd4c1a072a83e1d1198a57a650fa6e94b69d983b693c592bc0c8fcd9a46c6883adacc4c7e2546dcd079fe00000000000000000000000000000000136beae11ea54ae26a8d69015ea7793675625b2013dbeb081a5ed877832849d67ac709b81fcc4fb322b262ec3776c0c00000000000000000000000000000000011f1df574f63f679b6464df463b58948fd337a4b3f159229ba0313cc040303345e75a3a2b0ed0dedfbefa89d8331d074f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b0000000000000000000000000000000001510378fdcde4027999edc99d49bfb46423ceb0d740829e310f8a381a7632fd0d6b6aa3533c2702a5ca76d386ac0145000000000000000000000000000000000bae237bfcc061552ca07eb14300cf557c974611885aa6894f7933f7dc7a0a1cc3b5587bbe1ea5fa604e3cee1db5f7c9000000000000000000000000000000001743207a1814990c798dc3de272a02b1b194a485bf09faea382dedd957861dc15cbf981f9906cda50cce2899785b9a6300000000000000000000000000000000106f902004c66c80437392e92cad73bed6b73010bdc7b6a75de4c01f0b6fbe5b8d1f47378279bfa42b3af05120854ced07f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e400000000000000000000000000000000123362e41268f7821fcd2294b032c6a51c6d80506eb052ab6132267fa248a1a60c3e4eb2e75a1674bee1c9d46d82b9180000000000000000000000000000000006296670461ca67081cd76528446867e1a4905f88742d0ed8d1f7baf86e0a5e5ee86c8b0eeef07c14dd821dee0143bea00000000000000000000000000000000058bff9544e4e02c063158a52a68c93c7544e8157d37159dbb99b51b09e3d8f5b307bfe63a10fa409e20a35219ac244a000000000000000000000000000000000b135edfbf53187004d0977db94eeabf426ad7bea84ad76c6ac771fa186a073d430af76d717070e3c4057a7a2da095984e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb605000000000000000000000000000000000133d3a223112dc5665e78fba8fc8d040d133858d984e66d2382d5e629f9369dd127e93c7a4da77fad98a0520ebedfeba000000000000000000000000000000000e88515db391bcdeeed2a9f64d27387af0391bf832164fba79100b560d8150debbd703c140dae3ba9b1ec35c1f45670600000000000000000000000000000000042583722c69a19f413392c6a2b75c8ca969be85eb951056d7e1d94e046dba49c346d5774009b8463a40b0576ccc1a6e000000000000000000000000000000000ee61a9eb6ad497c57405a44d798868e22b4fd5b8c480e9938cfdd3f1817eaaf331a9988368680158c59c2801add0a7a27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f228900000000000000000000000000000000020352de9b4e8ea1acc8589bb22e23dfd0ae3a80de9e21bdb3f6391dd05a012e635de9e1f5f450bf4aab05728c054f8a000000000000000000000000000000001733593b94ec800bb59ed97dacbefda5ad882a8023346bdff8f471c5613c67247e27d72cb4ed8cdaa0f236018dd2128a0000000000000000000000000000000011f272a3b25bc519fc3e229211b846042031e22fbed22ecd0d1a4ef1d05feacf105772d71157e3d7293575aca257cd5f00000000000000000000000000000000153b4b4d7d65f7bd13d20fee4812f04706c96cd1a0d27b7e139c47299805e0ac86e8941aa38d90331c78a61c2dd56aa3c2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc00000000000000000000000000000000143e1f6dd9397f0e89a46c6ec995bf6c87ec8a72b309f050dc5b3134e00e2a16327767cb0573ca5ea9776215a5815df500000000000000000000000000000000186cb3af2cdb4562bf2d0c180079547cfb345cc3943fd7f9203fabbdc1547079cb9ed854f9b1a47f513e318cd409df83000000000000000000000000000000000c8c9197fa5a1e66b371a653c5d18c01fed8d17a8aa92d89b2cbd954b9fd2931fa61abb6676e4851dc9481732c6195610000000000000000000000000000000009026b259e840cb5264f6aad6ebbb09661f5b6d980389817309aef99e4e0cb228d3a7a06e6c25bdb1aeafe5acdc44441911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000007bceb74ba86c07d0fca20e4febd3b12b1fe9f786c9a5da0531550244f40261d7ce728498fbfcfe16cc235db6ed42e11000000000000000000000000000000000883104ffcc0d040d70bda04dcf67c1197c39e200d4d9daf5f3c185638a13dffe3dcac94fce4175187dad867e8d2b78c000000000000000000000000000000001404e48e86f199486db7d40076cc8dc4e2aa2c1b6d4bed8f027512e2c71817905b26ff4f0551f9c08a2a7a27b2075b6c000000000000000000000000000000000b789a6addb98ea43c0f9e85831a75b8ee1977936c17929fb45d4c06b4f1ec33b9b41e32b52cde542c9e4b64d27c686cfd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b000000000000000000000000000000000654e7f3985bf90dd1e3169382690fdc0f804eb6384ce407a060f539804fe6e0451094abaa0dad611c15d3ee52f31a92000000000000000000000000000000000deeec957d58a2246ff8f7b7448f5198647576c16c1717369ad155ae36d5a6bdb42c8d6a1f0a095891fb0890b6203f950000000000000000000000000000000013a01a6ca4c296f59cfa4a5f5399d28af76ffcb8b218c861d5e6dc603e140f730f632028c8da46c823d87bff5ca703280000000000000000000000000000000003698f659e86b96613ca74a480c81e749bce4b74324976c1d241a0911d078926fb2adfcc3f901a7a015a02f525ddbb808118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c0000000000000000000000000000000016ce72e1798ffd84b52ac664a184c6cf5ce1ca2aa263c9d056355cf610517e9c7bf7f057c342f6e3ae801b84c2082c0f0000000000000000000000000000000010992af1438eec10881b5e2e3fa3b1e91b6b5313ea58dcc0cd2159f8ba6ce5912d81b38956929620e04b3596f6835a6f0000000000000000000000000000000014315dbebd532d0c835e8e85a02c0814574cf040a20c18d06573718223c8ea15b7ea69f0cf342dd09037258398ba4bef00000000000000000000000000000000136d13a83e72525b2d4af54d14d5e21d8bd9bed18543836b02ae0a7e51d433c93aa1943e85f978a8a9ff4454d8c5d120c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce7730000000000000000000000000000000002c1bf7dc75006c2941b89a2de52fdcdd1b4fbda5b14fe3fc165915b90fd9d93cdb8105898ef59d5b374707f0afaccd600000000000000000000000000000000049a16efcf81de84e443666bf990f6aad2145f9c9c2c61a752e256e8f447dfb27b462e4553544971807f909a666af12f0000000000000000000000000000000000aa4702fb69d791ef958826753d3f74f61c7a591ab94bb6c1bd5d82d94c5877121ecfc1e769d0d16ebe491b775ad96e0000000000000000000000000000000008cd7f2562eca6c53a37382fcdb04be53998f45c2241bfebac3d1fb08d8e1d4df3182f2bd63861d0de72d58072356ccc982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b000000000000000000000000000000000a6b30c4ddb692ae33c903693cdba00ba48efa48e90b9cec9dc747004e57a8d5a05b5522634fc0de306d38c28390dbf4000000000000000000000000000000000601341b3c4057767a910bf30dd16324ad7abcb55b7e98e73584f26d7f87d8a8d24ff2113c12ceb3077bf65e0912b2360000000000000000000000000000000019dc9c50f613470abdb5c763c0272e88e34ed38e617d6757f4e70d05b8ec9f67a023b4ec1363e7e60e38cff64e18f0510000000000000000000000000000000013fb1858f7efeec5fd03d9f7f4513e3e9103c340eee7bfe48ed3cd3dd073b96add9450a17f12e161f1d44669b1b2f813000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000d6c135bfe0fc7af93571a69b7c37ba691f051d69582cf159cbeb0bd59b48342172a82a3eec2e3d440805934e1574f2a0000000000000000000000000000000001b04e56cb3bb221caedd3582943f89a33b955f624f9e473941f1dd987f2898339142a654d11d87bf8bd2fd0fe0d4c1a000000000000000000000000000000000f185fd420b761a1e38d542558b0beffba916f369b37296fdd8878a7c3d2ac9d3ab1d8e45ad799f0d81bb439b5e5058100000000000000000000000000000000002d10ce460c414fa1094ef2b7de8f1ce024b6d086d10067be0fed4e45dc25c8e50bef361d39a2743be1e1ea4fb7e2ef773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb2000000000000000000000000000000001972ea36bae504d7047639ce6e0e6c3b16afe89fa3d6c6b33c910c8d4b70782d8165912e5bfbe8bd84f78f9f23f7f956000000000000000000000000000000000ae55c4fc1c01f1bbdb060191e8551a7ba5ebd3dbadd138202090d7dc6765fd1ef5fe8204ae76a8bcfa03ee5985a35280000000000000000000000000000000018ebed295805e0fc14f1c7b0e6ee12ca48cb7177c1d367a613e0d6cfaaac5128fefce0e8f38d4e2f11ae0d327be466a400000000000000000000000000000000157068d89fe48e77e0f62e3b5b0293423f35c5f4ffb9e0577f5aa49e91cea6bd312a0e65ec08af9c1f53de6499409c8d759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e00000000000000000000000000000000120e4a8935c08032dbfc19a43e2a770b12b05cf1dc229e12f683f0d7f604bc13666bf318fcc38038b618ef83c9448b870000000000000000000000000000000004e3f851be46bd85f37c8b1d84507f4ed63ea76bc305cc26a6f4cfa2135d5affcd3b319d9f57619e21c964c6246fc3f600000000000000000000000000000000138733f352029373b19e1c40d5958a04257e2b344770e1bbb8f377bcfb1c7225ae7a8b0b0e57795ec06a08e13c90d7de00000000000000000000000000000000093e85783c556a017829e28bc42b607b1035890fb9743bf0e279df4dd8a695c1dd07a76a213087c3a8a7e614b29b7a1ecce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f0525302938000000000000000000000000000000000be1ae00f9ba0a2e57f94728508e0029b1bafd52c91ce718ba41790a3541117d1a9f846d68440978cdef016c3b9ae422000000000000000000000000000000001947683154204c9fc93e3aeac17b417453a24d01804e8acbf6f67947f5962dce875f49d05e6ae65384602828784f852c000000000000000000000000000000000859dc1c00b49cd1292cdc65c6aa4b11e27637b949c7db508930c557ee3ce00f98f9cd3dd0f6d73a646d176a91d75c070000000000000000000000000000000015a7a6984b5f42aadebba1e1f4682aaf1a2d01c9ce2afab7fed2269373467787bb1361b493dcdd862180e9159ec2ad5785e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f",
+    "Expected": "00000000000000000000000000000000047cc33d9decfd059724bbb014fb9cd95de187e2dd29cf4a9bf9ad06d415e2cacb5a4e49266c13e0c38f2563d1a21d6a0000000000000000000000000000000011c79d93fa870d541e79ad4037c20b85d3cec053587f9df60dc11e7dc2727d79059f75bef76474c09fe61ed10b317cad0000000000000000000000000000000003df3f0db20c5ffea4dc9f4d9333d76141447bba50da18e521e89aae1e63750c71b392570d79e597967dfc9952e903c60000000000000000000000000000000014e83ea645b1019ac2dfafe370f616af0c5baeabe217ac1f9ecf78877717475b25911b339557422526a8163434439923",
+    "Name": "matter_g2_multiexp_96",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cd1d25f285c2073175ecad5bba4987cc52012eadc7b19dbaa20fa82d7a6cfb8a52f33469b6308d921eb4b3b23f7022d000000000000000000000000000000001707b67a23d9212d30c06f26f0040c38389b185057e80236d2c828a8d9ade4f72eee1d6eccd78e4f4d71e2c28ee9539e0000000000000000000000000000000008e5c04effd14d915b9afc2083afa2b6d4008cfa0e47144a41982d8b5a8e77922a2609384e2c5d18c871ae24a7d505b7000000000000000000000000000000000f414acb056fff2cd6d9b408adb6eb7f34c8f66a66ee93945a3381d46c2d181613047ca6d4067614c190da444223cab685431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c0000000000000000000000000000000008ea841aced2d0b8dd688947648a8ff18d0f6f03f63ee1c331f126dd4fc0da3d386535156b80902bdc1f65add7769cd70000000000000000000000000000000017a32ad2763d99c38c954f62466e78c0332e48875e15afbbe9c78376f1bab12346c73a573738353e2162d3928091dede0000000000000000000000000000000010ea738884dbfe5bc35d031bde9aa4109b1fca529502e236aebb5ad0bf71dd2f3db250d924415b0bfca56519f8ca5d290000000000000000000000000000000013699e29cc1871f51a469898be8b3c732b5cf7860286e655e65bd8176832804d17b48d0ff85eb023360db78162581297b6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa00000000000000000000000000000000004d504e62b2825651381ae862fd33407309851d5291591cd0f541fd092800d709ede00a9134e65ee752eaceb0a344b50000000000000000000000000000000016481efba290c37aa4ecbf940c76ee5df199b0c1f90fddebd2db28120bb5a14dd9f4a067b6d4889aeb683cca0f6ab337000000000000000000000000000000001400c89942cc63417ca4cb05c9d81dda3251e5611c27fc7727c3e803170672f103bff26f7216a0b646533aac3171488d0000000000000000000000000000000019889641be9db08880543ff476b9d4c72167092548ba49a3f3ace4518d3874f4f7993ae7b8cec90f092f144ec9d66c1a54dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000001927fe80adc6dbb581349c603103ad8831e645d9275af8669939b83829182cc6e2a30df2fdeda6d3aa2e2a6126e00ba3000000000000000000000000000000000b6d7934d5ca1098a85a0c60acca075220105e221b802b1be97c2967820bffc2937fc3278ed0f26905c60d44d5fd8dc000000000000000000000000000000000057acc1379f23c0d1d37427d400eb1b0a89f3736c83d3ffd797ae279e01e2acddd84082f13f3c8b8f1bf7c275702a9c700000000000000000000000000000000038dbcd7e08d34c553850a52336991a7d48968e98057e930790d78b5c6368eb2fe01571b60c4aefb653ec04122953d56eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a00000000000000000000000000000000014052031b88af979b7edb06c99c2e46bd9df2c862c7e1b71321754841fad67fc3242b51141e49ad86b61344aec913f40000000000000000000000000000000014806a86d078ee9bdde99257b67f50dc2ddf9bbf01dde931742ee6f739aa986cfdca06cd32d23d86f2c14c3b09033d29000000000000000000000000000000000e0561e795d35ceb8bd9e3b276406ec1f697a38ada25d1dbe08715a28bdd3d6ce6e0aac01f7dfc7c2b403850ab213b4700000000000000000000000000000000146a65209b09487e00e356e3b29952280ebc6a06343c4ce14efa0c6281bc2482698bd02295bc35125275ff5f5bb867dfb273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab8500000000000000000000000000000000021592cd7f4cf9cab3be53561c889c9ee865961aad51339f6393dd6a0b7dcc8a7c48b753c947b15cd3add01abd3d76d8000000000000000000000000000000000f9e1a80bad58055a8577700c177889c4d702de04343c1202eaed9485a76493158547b20bcb552b66c42a0c86df809ed0000000000000000000000000000000013908dcff1945cf06f038e3caac9a7fbb3a6466ca18627e93468a875759a2b5599a96834ff21fcd6bfbba82b79536b9a0000000000000000000000000000000001b6354665109c5a64613c3bd7d805b3a34098708f3d41c7b77db031ac6fa0b2d4e2f2f70c84ac78687b0c0f9bf334771342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f0000000000000000000000000000000019394063202186e141dcebce7b8f0f267ba6057a0f993bb1cbe22a5bc528323823bfd1597a87017d478186a18f09a47800000000000000000000000000000000148437bcc43d432d70b47dadac8e738616c97d38d0f84dc132599626612f7bba74988bb23ae47ac15e6f70c059d607ed00000000000000000000000000000000180851594710f4bb5be7ae0104a383081c50f59e4e048614660ab5a4e2661e171510f5b112d8cf97a6af27d56d137c860000000000000000000000000000000000599f3f82f29b493ffe9ee3a8363b9a599a5ef3c9c5c680d4e003f4ac5a7de0562cba8e2a4c6da7d07cbe86c3f7bfb85b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee82398125000000000000000000000000000000000cfdce7997601afbe484901893a1b5fc0b83e8d238d41d2f889a58fd4d884df1c667a000b53b587df2c42ad46aa2c3e0000000000000000000000000000000000c50bf3e06400cb10494cd09bd89f3c96ff49c9f74dd5325f9489ed6be13b59bd7b0b2351411ac854d430405b8a2a3de0000000000000000000000000000000001db313a34ca4073e4fa2287e234ac32bc579742de22e5218f7873b922f5804894826e6054925a394f125fce850f33ef000000000000000000000000000000000e0627a66d286e8d4d3654b32fc5f552a7ca12f0bd47eb6dee0dde22ee48165247c067a0f4c3d422bf3562d38a3c0cf1249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d170000000000000000000000000000000005285ba39f5bd981fce2fdf853706d70992acab2dc6d4c4198144fab397392a60d631056b580d0d98f3f350414ce554e0000000000000000000000000000000013bddbc1180f155872376fcdfaff2fb12d3d9645b81bd1475a5323ea855cea820ed7eb693791caa9bd3fa5c66036439700000000000000000000000000000000125644d32df397def58dff875d7e3f14166e765ed49a3991f45b38d74db3985fc7f5052058d85594c8b97afcf850e11b000000000000000000000000000000000fca4662eb1b39f576ee820385fba88ddd2fc01fcfb9d9f874453ad725cd5defb357be028fae97ce71bc5ac26d11c1bac014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e8950000000000000000000000000000000015b97d7c74c8ec102083b41d7ce5490466e1c0e261b5ea5c756d3f9ae79dd2d8ec6eb5075cfb76dfcf7bfdd80442f7d10000000000000000000000000000000016812f845faf96b8b69ac7a6af3c8947aa25877199e3c12552527706a17b768bbea259ea61ea82c4624a96cbcdf4040d00000000000000000000000000000000123ad55e5cb5ac5bdd3ca0a5afa7c3f8e4b98ad91a205f073fb546fe799ffc57b3c1c3a6209547ffc6ef05fd24be6f5d00000000000000000000000000000000017719f31946aedabe0e9d88ef3f90eb6ceda884f5e3d2ece368373785b2d8bf0f9677731803b25accfcb6cb716e0aa4ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000000497e3480d58027c780f47cc35a121ee0cd76c4e84d9a2f9002c04a1c286be990167a0138049ad70467132818f48ec9000000000000000000000000000000000ec0ddf938553105400f70989140ca322d996f48ffb1b35641ca36a6ba9ac1daac1603c100822f80cf62ec3bfb442158000000000000000000000000000000000a0b6ebee28a792df46d2f727af812c15fc91a471e0d8e34b25b26048f3b9606d8375b5b268c40fb04ef8f098e1d03340000000000000000000000000000000017843dd19bfabbd0cfa41fb58e70a8900397d17ccea783087ece90962560f5cf090e8d9eaa873a6a6ebac45219ea97a68b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000b27c82d71f7e4aab9a68596669596df3f62071e921e131ba4d9e59d8d81d370e077e93a4a6a43e059661227f40b38c800000000000000000000000000000000093004917ceb2fb4a1b33960ff74943d520f86e83aa02b9a6c85e4b9a489e9331863cd30cb6ad6f099d03289b4ada5520000000000000000000000000000000016f04e35186c7deaf730708e1678089bf3e73c1164bca24bf8f70c4f6cccd5bbb34bbb5dc313ee428aec4ac9c638a01a00000000000000000000000000000000011052348cec9dc3e85e01abcca5a652461f08a9f5d72b3fc27140a6a571137f0065ed7ccf9ec8cebe314ad9a214d5ed94ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000007083dfb0738d58ba8933a1f60283e5da8bf90af5aae4053ca573ee7223d3b80e4bdc30b4a831ce6af9f52f393e9742600000000000000000000000000000000130c627b7d3a527c94cfeba9f514e75eac047e1b6088c082229a8c95d0765a0898ce1e45694ca2c7935bb8e41e44e8b10000000000000000000000000000000009610645b074e652a08f2b89dbe594afa3009d795ef211f7c036a56274b1e1bd69a035c4f356b6b21f69b9cec2bf7c32000000000000000000000000000000001020f3cdef468af700269aa1e9d928e71b8c521f23586c9b0155314f0073da7de04ca41ececd5edcc052af72c05f0e4bb1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000123540047f0768b0af841aa4aeceaf3dac31ea832daed86c8cbd1d33ed0282c6f697d5881f9022af032e90ff82efb6bc000000000000000000000000000000000113daffbe413075f5f4f6fb42f37b6e9d5e5822aa24d6f865792f63e6078584246bcf8b17117385db1d6233974f6ed9000000000000000000000000000000001067b46fd221b6995d25d4bf0adb088e0554d858d4e5d9d6b59e1ae2a7d57188d559b0208918a8944aedd62b1ebd4f4700000000000000000000000000000000087dae77e483d5c0baa37b9b96dad5ca92b5869fa253bffad24dd8747446f7ce60858b52438e58233210d86f470f765fe670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af8500000000000000000000000000000000039d09a094d655c139cb9714aa258d9548473162548048b0f07c9317a41a7e5dbaa5aca156992c8a509d4071d9ae4394000000000000000000000000000000000f0273a38b1b9d006efa43c15a53f026587a676912d0275968608519e97994ea9c6a147e377f68b1738ebeaa178f9c1000000000000000000000000000000000132cd92417578d2e46884f1c1a1080b1916c8c8404d2533a4de02bf8575c80ce7e8097c2ddd1f95737355521c0ec21ce0000000000000000000000000000000019adbf09a268a3ed8eff936d25fbe8af2874e44d2580c7941dc14fb89c5da963b468a7088c4a763eac89f4d15deaaf5e54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000756dced467ea32c3c425590b7690a45e250e464ac6927ad3f5d2d8d2826961b8dd7572db609375c8d06cc3b9bc3a157000000000000000000000000000000000b79b4eecbaf1d0f8a89f9ef8fc144b3aff38148ae260da7c20e9dd3866d946585df7ed12c8b7005e7b0e1387c9db41d000000000000000000000000000000000afc403b008b70e19f17b1ef37c9c396577a585b6c34b23d09621b891efc00ef9460c3f4b5f3e851ef63620dc06c824300000000000000000000000000000000024e06f3f3b18c026a166c41f75d7bcf699480f5b6811463c27606c9ec1232fd249a46235b7f5b5a2ed3b53231b333150563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d",
+    "Expected": "0000000000000000000000000000000004f2480d0b7e84a996965b76055f6879aab5bab26656e4e5ca7220adc17f14b5946047484327bbc5952d9f2ffa5f92af0000000000000000000000000000000002f7260c55c500b54df4391a55eb4adefa7d19bcbec82a107fc0d222c898b97360a679a02ab3023ce2ebdcffd125ddf30000000000000000000000000000000002cddfa94c8f6b3f29c2fe018b1f8679d0e98c8c2656f86f327b9cbcba574cc52643ab423b458994a03347460deef6570000000000000000000000000000000014eb4c84f71ef5935e707a11a92ba34780677ac7eb198e160585ad92aa5c1ea21e3a77be940fe344e7e170680e2a8d53",
+    "Name": "matter_g2_multiexp_97",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c2c4c039047d297049afd0e8f0375bd4294d628d3a22078d93b684b737e8c4b6ad3ee544ecbeaad6b3c75d8d217f3a20000000000000000000000000000000004c77a2c0943c6f997ce2e785461f8ec253c47273ded4e1af43ae882766ef8c168e66d831abc2b3b3a0849bbc210cbd40000000000000000000000000000000004456a6c267a5cc6b7d9a9f573270855186a1b621cfdc465fe71ddb4d614565d9d36b13985b31396587919226843c6230000000000000000000000000000000009487cdd8a0cf7f40e9087fd3121cb480730f4302339d25fa12128033239662ed65579a59b837bf1bc5fa87db15b15335b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e990000000000000000000000000000000000ba43205e8392168824f77bac344d60c1a9a0b14ab55538c3bfea4a64984cf381a2f61c64f1ba1bcfd8a7973e43f6e80000000000000000000000000000000000e95e5ac415c3e3e7c9feb6e7a2af3e8189afca06ae1fe54bbeb31783810860921ab3c76a475fb227b3c8299e3f1caf00000000000000000000000000000000001e3cb2106a23e77a126013087751c4d2a419a51beedc3a33faa6c933bedb3d34ee9c6450c583642426edb352e04da98000000000000000000000000000000000ab5af4c98aca1fc3fa55355351d12f3bc639662bf8b5b772152988d676b00ef39f767237a2fa3be936e83d1dd77da86a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000012f3d927316ba638bd6294f7dd2f3f166d20285ee1662ae4dc145835704a17127078343d26042a5c397bfef31754186200000000000000000000000000000000162893d6252361c340057bcac31986458b8b55a8a4283f5a06ce1730098f9838dad1bca264374e7261bb9d08c177c1820000000000000000000000000000000017264aead0ec41a079827296f3d32c12adfca7cb6c674070d54087438d57b6ccca4822b2337263e60075d469b4ce0ccc000000000000000000000000000000000480cae035bd3bf1b4a4a766bcd5f188833e9587e1aff0e1f10e36ebbf2f3ae76bc0946e7c336efc3ee00bf42e7efbb9677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb607191000000000000000000000000000000000d13375356b1518e37a13b43b7d192eb74bd69636f91c570c41a741a8763c03caf8d13c7364f57c867a4a3983e88060f000000000000000000000000000000000f6f78dffb404faab88ac7373e0c765209c0af80514d438e18393bfcfeb60d9a5e13158d399f43162033571ee4a75dbc0000000000000000000000000000000010c379860638ccf3b6cb8479aa38881b0004197e3e367a1d5ef7c7fcf075689d283b87022e2825b5c789ac6a448467320000000000000000000000000000000002dc392872cf2fcd8e196f10c1ded175300070e4e38aa58c89c81e1aa5faa08d770a5ad90a8295a890551f9329a13cee53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac0000000000000000000000000000000010383a21acda7c8f3f3be980bff2d57fa0a5b2dc424164dd2ce53c0b20ca399d6227913b7b550462180b01c231e4813200000000000000000000000000000000078aec90354721f0a31e1377b3652bcb1f388ab36f1866c955f3ea8dfe6ac2c25bc4cea14f54aee71595c2c1bd2dc4910000000000000000000000000000000007dfeec77213d952c183452b98ad936e8854608d950c0c1451262cdc7d6de5aed0db07a8d74b3e8f674967cb4839c4d00000000000000000000000000000000015c09e4ed2ea76d10d196f7a733ccc272b94dc436d6bb5fafad2fae4a96372c2c6f1325d1554746814ae292d8e6b1e3634c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d500000000000000000000000000000000138656fa091cc6613b1fcff04a3efb4f9c393985b2c78fa838eecbbbb8b6dafd88d9c72441f9bc735649480b5187acac000000000000000000000000000000000a35cec4819ca3321917cea5aa589db8cf61882fd1135031dc41a8207a8e71d326312799291b160a646148c382ed086b0000000000000000000000000000000005b6e4c02c9c54630c96271073513cac3a42d47a7272f62a21c7ad4c85c19b60b70d04719626cf4273f6c5691719931700000000000000000000000000000000166a20da734a47d7e28cce8f0c2d679fa6c738a7a1ca9089dc67ba2b1c92a83b024b8991f131e7e8802a617153de4554bebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000a4432a544deda931b1f62759320ada2963062e722bc1b748c9bd0d026ffae10f228be36ea0ab076358924f4c06b6feb0000000000000000000000000000000000e955b1b1b28d2044b6be371c58bc85097c77145b239e913bb0729757518c465d9e69338066f7496aa6a2038ea604f900000000000000000000000000000000017ca2a7d52c3a82ab8abf9fc1bc187389b6e4904e161541008e5b3ba0981870e01060d1272a6d59bfcfb294c942403f000000000000000000000000000000001870649a50e0978185551f213eefd9305d33e92b3f8c39752b6ebe18ae86ad97f92acef05971dceee3b3729becea18168b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c95960000000000000000000000000000000013713effa20d5039ced751ebafe1516f062f11ee05ffad37281cfee9d7a49ab14c065709832f6674bfbf2c9f379bc9c9000000000000000000000000000000000295f7ef148430209b48c292b024474f05036edfdee082c56aea05a62f1fba3ee7a540955423f78614c8385da8ef60040000000000000000000000000000000007408c97321b6d7c27e5e442a9e35b054e743c34d845874aeb1ccf4e903ca7803ed7fb1288327865f9e0ff0a388e92b400000000000000000000000000000000081808d03722a2d48846a693059c2662dee614f181dc406825544d30a6adc0f9d84a712eff80bddd4a27a036e4bf7359d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca000000000000000000000000000000000880b646a674723c15b240ff56d2031e5db724251b1402a68df8b26261ffc9fb60a81abf165c6832137dc7a7293142d200000000000000000000000000000000172354b62bfb8d388b5a984411414738302725a508e8abeacdcb46454371d5e9cf762028fb65921d5c3dd8c67d42a981000000000000000000000000000000000a1af459bc3122dcef78359e468f4094d609ae3da09ca5aa6efb71a7494dafa2373a3906bac1f324d98b3eaa982a27d500000000000000000000000000000000092ac3b47253c7f090df076914cdc08a715faf153e8e365392b4859fca1db14d3f7fb998c97de9ad99b7d0b357252f086330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b80000000000000000000000000000000003b23eff722c078a781771d8b75d519e7a062ca3e4252ecca877845920158fb20d79a9ce449d9087426b113da0091826000000000000000000000000000000000c9026e8d3fee6282492393db504a2c41db19d8fbb83260624b05ba4107d6cb2c90d645a3c16862b27cc3fcce9bf89840000000000000000000000000000000018b8648d0a42285d474f809519696df9e1ad5c35d8e848ad74fbee37425aee8844a8be8cb4d3331670ee294ddb9a290200000000000000000000000000000000068cad37ee8578f4b502ac2ef4371a10e5432e57fe20d0cb074dc427831872113d3514a0b199d813b796b8357fa2a3dbc2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe3740000000000000000000000000000000013200f0aea4c60937be47213b6149b0ae76767f3559e0519f774af4a5d9431e2dd7ea74b42cc3ceb28ccf0d2f01116f30000000000000000000000000000000001c5bff08fd16ecb68f21289a3e7b9a2ec5da1357d604710a18e78ab780f8ef0343d5d9ee7f7988a009329b17e498beb00000000000000000000000000000000125453772eb9d1335ce4dbcc8f2ab8426fe89a0e49fec51d4e96718a38570aa82dbef452368141be2df260fb131c50b2000000000000000000000000000000000432cdd445519775b9914a986a0941cc829b4a15cd361df9ae7129547b24f7a6a15cd8fe9393fa1551db2d761a208b8ec9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000396cb6d7b44f92b716ed02985d351b4e8cd1bbb95f239e4f29d7379428470be395e2faeb8e3a910007aaa490d3c336d0000000000000000000000000000000000ad0c0623fdf50c2b504777554dbab3cde1b9705e976561873d7c22b81f49c7654a7c76e558fad1518ed73a0d3c3570000000000000000000000000000000001241d5bed68e02a2ddeb3ccbe109a161abe81edd7affb72182c5163851211c4763e6aecf766053b61ce575de893985f800000000000000000000000000000000183696d2a48feef6088f4e9f75a5055e8c54b3813658b593958490ddd4245ac495a8ff966861b20f26047f07fa8609a0c2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a000000000000000000000000000000000fe0ce41aa9e7cb2bcb4e01721b7b1d99fca4e9b7c4df09bec00bd346fc57c25118ba70d5333b7f3eef2659c64520a470000000000000000000000000000000005c932e09c62b7ddaf3f5c420c60740befa7cdff5bb812e0f089c45098d71b57004b7a207f0cdd34daaa3282cf6e9f7e000000000000000000000000000000001874200ead9776c1ecd6a54a57e5d0f9577910a4b3afb9b051622f658fe3ef6cc5070af60e7ef910562720e9716158d6000000000000000000000000000000000c2c657e58e400a67e59deee8c28234ff4688e781a2f6f2f0d0b186a5e4012695a522dfa0770cfd543f55939a05e20b09fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000f0b561e5860321249b9ff434c604d26c3275824fc4ab9c1ce5c5858605ddaafae83ae27e523bf6006932f6c7f33d0a7000000000000000000000000000000000b47aab85bbd909599aa85c5eda363b67790ac6729fd8b1f4f53f66dd796cf2fa3496407b1bfaea4dc8eae53519054e70000000000000000000000000000000000cab1ebd23bc05c53bc9e8481c469eac3ee1b140af545bebed10a8fe50698d2ed883219881929207c0addf2f687198d0000000000000000000000000000000007742de55b799950e6f786f4eef45d0fb67e0475272ad68a183135b70047abab6c2ed51ede16c39be7b986df334e9e75d4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000923861332988bc843a65ec5dd4637f9dca8a15e71b82c780fe60d768213d118d8948ab554e30bb9253e900a9b7d87f200000000000000000000000000000000132b1faef49e7966a05783ba526e71134bfb577b13116548352da37e91e617d7c72ed2645e672ebbc517e079247dfb0e00000000000000000000000000000000000a46a8893a194ebfe077afd05fb25d4680f1e4991a3ec29475fa5651d086d20b38136155a65a4c70af31de5a78af59000000000000000000000000000000001344eb957594028b4228cbdb8efb03cc7cf49ec43b2ca5481eae1df6f2df3d5be9a7c4e4e78f8c39be546e29a83c92f49644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000012d7111303563a6358e5ce9155d7a153b5781062c2f6b919efc67ddfb4c61ef03be8828ca6339397b84763a5f8a7e8330000000000000000000000000000000010a2a0ea9973728d3fb1b5906ee84b2635c687c11398ebf605cad30216df3b7b4e3ee1653d4b323a690e6ba614ebec30000000000000000000000000000000000b93d5de37b892d4de9407a820c73ecfd6cd9fa565db82e7e8c14c8406823f705ff0adf6bd6add5ddc5f72c91e52e840000000000000000000000000000000000dcb320ceba5436df8f099c5a77f34376c96d830f5e8ab80667d156d89f6bf8998c148ef9a53847ed395871ab86f6d280df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d",
+    "Expected": "000000000000000000000000000000000ff3e299e6b9fc488d6db991cf9d226d330846e87c4a5cd3e5d4ac955bc2c3c2d1ee5ff230098b48f594d256495f489800000000000000000000000000000000097fdb8fc95d64c7070d8c71e0fd2a933d1e1ad3fefa230f079bc5743828317cd1b0c6d4253ba9c3c6ec6b12d53afa700000000000000000000000000000000002448bbb179d82eaa453cd5452752b9a083b52694cc65c5d9b47f72eff118d7aa06b0fba18eafe93d2937d7399c001af0000000000000000000000000000000009dec4c0aff2a46b07855c87e08832811633709ddfbbc30dbb7e78b3de43bd95e383baa6ff4c58188f0c7643e0ea5a40",
+    "Name": "matter_g2_multiexp_98",
+    "Gas": 293920,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008afabec8a9985cbbc6246825785654c1d2eb7da5a01f76c4af4d0096b9baed3c33dbe492d14a6f9e762f06eb3d198f800000000000000000000000000000000027c592315dee4bcc892acc6f41a6eff5219c308253f7cd715d0e4a32c03c6d0d0e8568e146e9e799ac3025486c77fc30000000000000000000000000000000015b4ee27a3aa518a1ec1b447bb8f9128301c85b7176296d68dad3339b1dee78715b2f031a7fb6ba376145c97ceafeef60000000000000000000000000000000004b7e30ec7cc024ced863ce511cef3cabe954a4e5843dd636d776645a44225a36ed7e153ab5bf5d18f23c6444751875c8a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a83300000000000000000000000000000000119949d36d8d8e2bc1c26ded5f5fb01225a980a28b934ed3862480dc9297a3758e0f08ccaab3a09b5e5c0e4215e3246c0000000000000000000000000000000004a82dc22316ee6af39d937b662d1f1f2dc855c2ca8f33ec3274d833e87d594633fc7fab247911e0f46564397910d6ce00000000000000000000000000000000196900a09d8504ed960d41f4a8a2cde2e5dac61b008d3f6eb47e86d7b2ce6fcdc0f85157e3ab1571094d9fdaa75d0d500000000000000000000000000000000010c52ef9407eb4ec57844aebbcc3ea5000b1940d035dcc2a873327affaaabdd79e3560cbd29c63ce04f6279056d6eed1bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000000e43655ae05dc6cfa93113dc26cea895d1c5bc73f20454c7b441dbc5ac80035b290514b13b31b41931ea5336d8d9a6a7000000000000000000000000000000001199a873958c63147e6b82625dfea15ce90dd41ceb4e315f67221eb874ef32c6a2953412e7e981659c72239a7a72bfe6000000000000000000000000000000001845af5936b4d7487ffe59137ba2f86daea3770cf37fd560969ee48243389941a1072205e049ddaa06c0ac56b7edc8930000000000000000000000000000000003cc831177f24614f93a118b896434105f05a277051a852fb9973a775fc54f779c2a1f3d64c457e5231dc22d6aef606b58b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe400000000000000000000000000000000005e63584bc85ba58615985f6a466afe05268e545e0062cd7214e0b6fc8b87537c745b754cd9a1144948bc88b3c43acd000000000000000000000000000000000635b6a49090ccede3ed2ef203f0ed164783e3df4d9a7d93319515cb9230bd841b61a097f39e30175793b3e934d8e426000000000000000000000000000000001861e65f47a9da1584c45bc79a66045d86bc1709c2d1cf6cd2930a9fcc8c4efaa6536b5015be8d54789e8f574f93f9f70000000000000000000000000000000009290ce63d55eb436794acf11be9d896f03e7608a1bc8528f61ec9473f054bc9fbbda1072440e58e2f6ba080a01180173b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe0000000000000000000000000000000018feed9500bff884d2bb58554da2180c68267b6d3a45c2c7cee4c3f8524252d3faaa5eff971bf40123587e669fe66bbb000000000000000000000000000000001441bd3b58b4a4a87c2459f873c0692f5977b775af984bab46dd76cb9f775d2faebcb77b2854c9f1faa33f6c5de61c6a00000000000000000000000000000000123a890c3362c77e5b5cf9846d9c9e43fb3242d5a831e640ad080993fa0547854c8d11cc22f7f7b426528bf1154d2300000000000000000000000000000000000ff4a59ea98d13cfd353ae61e18d3c7018688f755561e6a1da5f09acc4277e8d49645087115acc64f992ea778a11f39bb049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc0000000000000000000000000000000006b019d005141e82393a2ca04469d1f6fd7b9456001ffef4c34eff6b2e91df58e99fd07944f52b108bd41ab6c4d6bbf200000000000000000000000000000000109ae87042029856befff0c916db5437e1e058a96f2970d8816b3becc93a1a50d6d336d5451303715f3e272147a36caa0000000000000000000000000000000000fc381b8dc9dc02d34db13e34732a10d0dfcf676c224a05a3bffd888b0af7c415b38af0b6afe6b464ffca42947c6ee5000000000000000000000000000000000087040d09c39ccd06c9ecc360fa02147a32e8036ad6e4b6bdf5b3883722a4e5a887dd022d53706d2585fe558696be6656197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d970330000000000000000000000000000000000e35c27b29df0fa9298bb9ab6a38b3450782223e2115d79152f9baa924d762d583b3ebe88e42f33028814ec78e5b319d00000000000000000000000000000000190c65667627a16f0af0ac7f23af0803bca810f3986b906b7b4f126d98473d52badf45e90e2e45bb390242fa8c40135100000000000000000000000000000000103f0283a5673c16bcc0f74f259c2eb077061947da04e467dfebf62aa005491e32b85cb73418b624a30dbaa01672921e000000000000000000000000000000000465466955c908607191faf15f0768dce42488c488eb4a065977f21ac7484766bc0abf23961ea2ba46dcc04956abf6c7721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000082346e352e845a54cd4267f93b85b2c8623d4650e00c1c56082b73ee31f63588d2c117d3cdecc0378fbbf8956b082040000000000000000000000000000000001a7f43c2bb19cb32345c43c950536f8e85815b86364f278f6ec8169eca80917c2b8fc08d59b20cf55f25dc468e7bd7f00000000000000000000000000000000085a5cb020df10f9b4c7afc01b1d11700579dec1e85e766507def2e6cf5b714174f7be9cce3b18533a5ebfeec2b4e481000000000000000000000000000000001836d7506d1cc984fb777b8ee935d6f5b110644f59e96ff44d8329336d59a3e1d2b53a05d35e97f634baa4fdc11a6cd8461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e000000000000000000000000000000000c1c59828ec6257a02679cff0bee0d665d449d2a158bc6d877e84cc0fe2161c297dde09b778d5e1249c515833e483004000000000000000000000000000000000f5e82589bfb7781e4110f1486752b00cbdf96cdf4191d75053c6d6d646e1c989add011361031a11559e156d64139fbf0000000000000000000000000000000015053afa7fb2b4e4b70f3c8a570fef8288fdc22dd951b6ba8a40b6087b9ab04ede21f0ddfa84d6d18914041bcf244c110000000000000000000000000000000003f399800cba51ab35624d866831ab6506392cb3acf549787153ffaf08cc451acea46c7a612821dd96c45f8b75133d88569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d000000000000000000000000000000000e26242c8f73116079369ef4265f624abd4377e4e3485c28197663de9de9f5618c3b6ee602ff6bebd1c242aef7295b2200000000000000000000000000000000066ceb3ea6067220bd28fa1164237782859d27c1d3087a42b4d09bcc343611e4ed2be014a27f5b394c67643dc00f57cf00000000000000000000000000000000157f9d30de52110ea7a2a35ddfe67d9fad7223c5e3307e797dd0df3621520a421958a2835205e3c4777923f47d47e5310000000000000000000000000000000016ebb41beb85b9489a6d5482f8a3330a5c5c5e5718e8efb8b67362f9d8e9c313e9e563275ba38c207c5bf3d89c406ea62061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000005d1c9109b5b7409f94ae3f7dd9e8ae4908a9b378fea4ea284cbd33d1e59b605577b63892aaa8ec14d415f34e22fec520000000000000000000000000000000005afed05e62599f20f7eca019f41d770c630cf6359cb5601464be821691fba5205c16e7b580e6881047214f938e5104b00000000000000000000000000000000105637a2aa4725d8e080dec3b731a111ea4c94b79f898dfd51f645501ef0c8d68ea8e80fde28ff96e927e44306ebbb1d00000000000000000000000000000000080cfeea754474ceb37973234d5dc3269f8ca99bd862d4d2d1a602321fc709945a3209e5ff2cc962cfa6d03017c9a1354129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000137762ea5c80033aaf17570451b15a062feedde810f11ebdbe9a79a3275dc12613e0505835c122bd5f9afea7dba84203000000000000000000000000000000000d89c04e45e60769a63fcd73df2a138c457bb549195f2c4eebb3be1ea46149f286756795be8328b5b886f497d8167b34000000000000000000000000000000000be43d515083c8c10f467618685a43d4d5f6457204bacd278445943a9f44f7189b561a0e1bc59d2757fcfab2e3f93a4a0000000000000000000000000000000011a52583227c6dcdc1784d3633fd584612a9f3bbc1922477396dcd5af84413e5e9382a34a71b3a72491ea09fab2fc6bf366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e8346729000000000000000000000000000000000073acefe33525dd2d5204cce72371ed82c7e4b58d1b4e7f4b4994f9c58b02d9d6206fefb3552446b6b355e860ace43c0000000000000000000000000000000007344eaeaae71e17930e769e02bcb4f44ddf3d040ffa0b081f25901cc125a37a58a6a5d13e7b0ba493802ccdaa054e29000000000000000000000000000000000a65fec6ad29ec3eee9ddc7ded2297f49d03ff18a255f1e6d29d2a67c20713f319d79d513af0c58ae3cddfd1f6240ff50000000000000000000000000000000019d5f00d9e2b271f4e9ac779a096386f08ae124f77fb8183405d48ea7f16e685805442dc67a392aefc643ea95b4f1fcfd997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f50000000000000000000000000000000018230bf1a873aa04855af1426da30f1b3ef4b64eec613b9f660222e3827b325c318baea031b463c7e9f775165d22ec8f00000000000000000000000000000000017faafa1294fac53e1de8cae9601acc62d76a5f01a39ce49d65f3f5d2cd5cca33eb90bb4116b3ea36f912ae2b81b6cf000000000000000000000000000000000fc3ef5ea59849a87fcd45500989f1744cb5570ee88e34a952cec32cea2eb5900b64d8d0d04ef5c51e8fdcccd46412490000000000000000000000000000000001c53aa8aaae8422fa4fddc86cacdefa89c37592c8e67e472a23627514623a90901a619af79e93561a0dc65215837274e881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a3371000000000000000000000000000000000e3db6885c2db9244548e11b8c49b73f85e4104b413f54308497262fdff1957495859830114528a22c45d39a554ba82700000000000000000000000000000000181b1bfe2d9a1c563e73356d73f4ed3e7061a79c610bc97c911ab1a0213d123c9f83ed6706e862087a796ce14c5cf53d0000000000000000000000000000000013f5fdceddce771588869b945bd6025e5ce485fe78a362356720b474b83998f27e535cfd8d33ee51cfc68e5d514f915c0000000000000000000000000000000007e8fd7ba457a3cefd50c641847425cf2262deb1d6945a0bd740eadf38dcaa616edc48c3912508d663349f089b8b56fadcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e650000000000000000000000000000000004b20b0408da7b704694b47607928a655077015f2174fe01bac9a0b3a61dae087b0b593f58d2947d8d84f75bbfb327c900000000000000000000000000000000106d623b2007c5d7128e03e540325ba763e992a651e2e5c78936f82ee2ff72d89a1a914345486cd0a04440c75beb190b000000000000000000000000000000001847348e5ef429cfdf1ba4d265d8c5ebcbec3d5dd4611ba36e2754fbd3d327273bf2eb7b7ba4b3888d059dc87f034739000000000000000000000000000000000bcb0a9dfe5189bc965e9721407b4cb3ed4171510aa4d4e5d5f0823a1c2827643e1278f9c0ee960c54ef8f6c208eee7b334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638",
+    "Expected": "000000000000000000000000000000001205b70b29ee04212589f8a70a71e004f517d3354e714c1b4fe42cf93faf1a8ed40dbc1b5089ddb53bb052c9cb74c0e8000000000000000000000000000000000f619082734dd9de653b61cf2fb927199f228637db70797bd2a21fdd48b6ecd4c4f712097037534024880a436fdd63680000000000000000000000000000000000592eca560be6ae256abe1796f7ec394a8085c127437f6590c8d41501a482c61456392cb320b9e801044dcba7802df9000000000000000000000000000000000a6d20b8009708ca01a274aed6dece732c5eed5aae5e4c2f3793b5fa1f8cb8c95037ce387bda2e7476e9c493507c7fbc",
+    "Name": "matter_g2_multiexp_99",
+    "Gas": 293920,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsMapG1.json b/core/vm/testdata/precompiles/blsMapG1.json
new file mode 100644
index 0000000000..07903a3125
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsMapG1.json
@@ -0,0 +1,702 @@
+[
+  {
+    "Input": "0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350",
+    "Expected": "000000000000000000000000000000000d7721bcdb7ce1047557776eb2659a444166dc6dd55c7ca6e240e21ae9aa18f529f04ac31d861b54faf3307692545db700000000000000000000000000000000108286acbdf4384f67659a8abe89e712a504cb3ce1cba07a716869025d60d499a00d1da8cdc92958918c222ea93d87f0",
+    "Name": "matter_fp_to_g1_0",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641",
+    "Expected": "00000000000000000000000000000000191ba6e4c4dafa22c03d41b050fe8782629337641be21e0397dc2553eb8588318a21d30647182782dee7f62a22fd020c000000000000000000000000000000000a721510a67277eabed3f153bd91df0074e1cbd37ef65b85226b1ce4fb5346d943cf21c388f0c5edbc753888254c760a",
+    "Name": "matter_fp_to_g1_1",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed",
+    "Expected": "000000000000000000000000000000001658c31c0db44b5f029dba56786776358f184341458577b94d3a53c877af84ffbb1a13cc47d228a76abb4b67912991850000000000000000000000000000000018cf1f27eab0a1a66f28a227bd624b7d1286af8f85562c3f03950879dd3b8b4b72e74b034223c6fd93de7cd1ade367cb",
+    "Name": "matter_fp_to_g1_2",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2",
+    "Expected": "000000000000000000000000000000001672a8831d3e8bf9441972969e56b338594c5c0ede7bdba5b4113ac31ccb848dc2a2c4e23c0b9ec88bfe7165f472b427000000000000000000000000000000000a86e65037cccb5281389512673068d6f91606923629905e895f630059cf87fb37e716494db288958316c6a50de65ca1",
+    "Name": "matter_fp_to_g1_3",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de2",
+    "Expected": "0000000000000000000000000000000019adfbc918cb74abc6fa0664dfe60697b233f0663665d2cc133478db4d6c9a41309ff09f9af9240037a7332bc42ffe3a000000000000000000000000000000000d31ffd63837cdf1cf2a7b3fe23a9d86c08f3a7c44ac4fa52d21b8c235f0d45f85c036d80bab332034635845deb31467",
+    "Name": "matter_fp_to_g1_4",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799",
+    "Expected": "00000000000000000000000000000000192a005eb944f391251402ac3d31c30f0b2d77987ed9928d244f492f96c1a0a06a7cd0be4bb3dfe3c484ab8ac5279a09000000000000000000000000000000000b99b9e7f0b51a2e0d12272fd0d9ae65294dfd34d45f30fe446a25b225316ef467b02acc3b6a578e054e612434096d7c",
+    "Name": "matter_fp_to_g1_5",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c",
+    "Expected": "0000000000000000000000000000000019dbf865a67157efe65fa7171279049864bf6c280d3c3462e93425bbf25f9cbad6c27885d7927b5cdca642df48ceccd2000000000000000000000000000000001606be1ef7aaf56349e5179b01b89e172e463bb3446792d5210452905fcde42522f9719b9e7ddeb8cc3f227eacd55947",
+    "Name": "matter_fp_to_g1_6",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60",
+    "Expected": "0000000000000000000000000000000017d476fdf0be6b09206dc83cce64c603a6b911f051e9191a2473a1bc6b1dd2c6e9bc4d262edc936f62911460f0b648a70000000000000000000000000000000016f824bb325ff7f485a8e9d116f4a56ea71ecd2c11b2a4d119c208cf323bc62bf1e9fc609230c571e7830a956e140e47",
+    "Name": "matter_fp_to_g1_7",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f157",
+    "Expected": "00000000000000000000000000000000145726f8479d7390e7a21cd31dfee0e6203115e72d04c5a735feb2cb688ff74944bff2b1af1b6368b4d095143662a1300000000000000000000000000000000002fd68d51753faa242bee10148c0e473f4110fc7b67848dfbab7d7105090648534854ba75890e099cb738d1dce604ea4",
+    "Name": "matter_fp_to_g1_8",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac",
+    "Expected": "000000000000000000000000000000000671b0f33b0f1ea3386e6876452989416c7171e283c4b0c375e840ea05e7fda22aa03899b50e59e9ca5a87039b2e732800000000000000000000000000000000031bf8caad5ce6a0d94f14693da0d551dd4bfd2c2163c8e8d5a448956153f63ce2ab72f03b97b560d67933887e83be1b",
+    "Name": "matter_fp_to_g1_9",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c8",
+    "Expected": "0000000000000000000000000000000007d67999ac2fe6ab93591646905f23aead0d37ca43573ab02dc16c2b199086f788a8a1de6b10aef4f4d772b2e12e72ad0000000000000000000000000000000003700b150ebf60cacbb2b7bcf969b70edb57b34b5c772cdf68d42dc9f1513631799b9b9041c5e94595ea848e195aa730",
+    "Name": "matter_fp_to_g1_10",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3",
+    "Expected": "0000000000000000000000000000000006cf4af50766ec08696c9bc0d9617c1f0fcb0ea1bcb576179cd4537d9d31b373bf8e3c5f5fde2c21e44917cf1f51ff0a00000000000000000000000000000000050a9f7d8490ba2b6e49762cf2bfce557e39edb51ef03128b64267fd3c6b996e95d73b26cf1965d427e3445b1ee4d133",
+    "Name": "matter_fp_to_g1_11",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e",
+    "Expected": "0000000000000000000000000000000009fbbc6ba7ec2315dc18aadda7b2e53180b904c5f1cbdca1b2f42ed9c6675da7beb4007ab6639520c4736bbf2ee3f04500000000000000000000000000000000113f0bc737b2f3a141121ef236cbaff2f34502aa13e121b857baf327f7be66be97867fc6f752555835fdd01610e30c77",
+    "Name": "matter_fp_to_g1_12",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be",
+    "Expected": "00000000000000000000000000000000047dd479fe99840150e73e4a8fa6be74a9b7d743e21cf33e9d7a9fd8700feeccd5111fb037eb3b15b79d5737ec4c7f0c00000000000000000000000000000000000ba7f57ce062eb9c67d84eee64d64d250a18454bd63dc5a136f5341214079eb9269eea7c4e0d836dd8be63a8a45c04",
+    "Name": "matter_fp_to_g1_13",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a",
+    "Expected": "000000000000000000000000000000000e8cf94e68b03d1f6a3d4eac7898f143324d08f7544aa9f952947e9008d2c14e46236667173266d82f5e41887c6f614200000000000000000000000000000000089a1ada37f30b1f6e3a6613705992e9708d0437611f1de72a9f696ea5efea6793f285bd5badbdc20af64df8ba95c79e",
+    "Name": "matter_fp_to_g1_14",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3",
+    "Expected": "0000000000000000000000000000000011e4919deb9eefd13dd0ba5184003ce34ff6c2bd8920dc49b936917a7b6aaf1c7541780b5d0e380e6c808f093a877eaa000000000000000000000000000000000152dbb758aa5f60b8d0703eb30680857abee717114b8cc5f6466e70856f19c76a88ec6c536e7a679c177986bf636e6a",
+    "Name": "matter_fp_to_g1_15",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e",
+    "Expected": "000000000000000000000000000000000750f69c43c56df2c8524b4ead9f6cb3ec16d3a6ec913254e585b0d8518e53c18e0e93dd4594adb926c51820de6001c10000000000000000000000000000000011f5c985ed12f72b6ec7e222dc8d93da520ac65476c716e231e7142cd3aca49b25dbd716a8f587006e4a2af31c37956e",
+    "Name": "matter_fp_to_g1_16",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad",
+    "Expected": "0000000000000000000000000000000012652effba341826ee7bc3108404f5fcac84776c6f5fef5d440454b59f04afc2cc87f243265248445c7c2bfc14493ece000000000000000000000000000000000c0fd215b7c012da4532c882d7d7f83ebf133d58acaf8b5123c1211aae5929c6726410631c7f9347456448df643c9ed8",
+    "Name": "matter_fp_to_g1_17",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6",
+    "Expected": "000000000000000000000000000000000c444b07e9ee5dc366c63ba30f1b17087bc4c548963caafacf223f4bf5b5bad1f9b51433bd1942978f3f5e5696d5056f000000000000000000000000000000000453941626954845d89821df34efc6f81660684b08f03fc42da54119d10f1f95357ba75a0962961f1487df45b0c534ac",
+    "Name": "matter_fp_to_g1_18",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20",
+    "Expected": "0000000000000000000000000000000013d5fcd7e4a0b1d7d8c7b242b46968519521ff8bc4b990a56ece26053d4bf884afd24a00670911f943522e06fe4f87d1000000000000000000000000000000000aab46534de37b5c6d206959a1023ad4f20ed5966bc3fd1750c1758ed806f077444ac70e9943b4e8debaecf208817a5d",
+    "Name": "matter_fp_to_g1_19",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab1",
+    "Expected": "000000000000000000000000000000001440f44e3964de59be03a6c69affbb3b44ffcf4ec4976361ac49c31a23f9f154f91750533ff2425d5e8fcde0974a91d50000000000000000000000000000000002031eb89620736dea022880e5188145f080537b1aec183db70bf307029be21a167fb6456bd1a47a75626280f78442a2",
+    "Name": "matter_fp_to_g1_20",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175",
+    "Expected": "000000000000000000000000000000000a2d7297376216582c3938c2aab0a26494da7d9df45e1af7b4f826f064467a939ad99134be4c9b804b5bf273e082c4c2000000000000000000000000000000000b0a4da7cc585be1be6c091006fe831edb6f6eadbe3ef611041efa3d14f442c9768feb2958efa161e0adf5c321d7d522",
+    "Name": "matter_fp_to_g1_21",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d23502",
+    "Expected": "000000000000000000000000000000001314d7faac7b4d5003baa10cc432108d6bb7f80bb13991f6ac45fd7a772f31cd43345ea100b05f2ad73e3bf583e7e7b2000000000000000000000000000000000eefa97eaf2143a991343a8823d8b362f77d8370421bd13a9a6cc4988544feb0cafd3a797a28d27f4f8d361cb7f49ed4",
+    "Name": "matter_fp_to_g1_22",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3",
+    "Expected": "0000000000000000000000000000000003ee4f3d29cd9f29a2e559a86d8204a1d65598e7788d585b145022de2c19022b122c1f10423d3bec769545d656726f5e000000000000000000000000000000001803f26af468740849a2737a42e53098b48c0709415247023aedb111c96043e3b13de300213e5196cc3b678f8be0696f",
+    "Name": "matter_fp_to_g1_23",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d9900",
+    "Expected": "000000000000000000000000000000001800b9766f3e621ad7a8d1870ce16c8cd054c87d7fb100120a38c3368cf1879859645874b23297957fef6cd8f9112bf800000000000000000000000000000000091a8b69a1f4eb883a25af2a3a0d1e384ef7a9ba4e8ff8811ad356781c79f631ea20fcd0590e94b9c1841e6add2b848b",
+    "Name": "matter_fp_to_g1_24",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844",
+    "Expected": "0000000000000000000000000000000005fe170feabac3805c3eaace41fdaab2c9ae7fe609ba609f4ebce2d24c0d704d847efd510acd8abe5aeff2eb24e781b80000000000000000000000000000000003262879ff5c9831ebdd0de9df478923fee72a8829378f40cfec310a41110ad22faa759276e3b9e015c86c94c3594e0a",
+    "Name": "matter_fp_to_g1_25",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08",
+    "Expected": "000000000000000000000000000000000e4110b2efc984c4d7affcbcf5cbbf919c55f948ac7412dc120d30774924d6020a2292f27b8e716c2b5045a561f2b14300000000000000000000000000000000194649f6906daa0394fbc1d45355e17d62f6c22a9e772bd7fa5149e29ab2ac6060d83dc5d70fad75bf3f2c7917b641e1",
+    "Name": "matter_fp_to_g1_26",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2",
+    "Expected": "0000000000000000000000000000000013af2a5f26d1f51da0d80fe7c62369ebbec235faf4565e62ba475e6f58418183efc8b9906196ffda72539506243e0482000000000000000000000000000000000774f3096c99bb826792cfd9243d8cbb1bab54fccc3a6347daea74ff1c8aebafdd971b7bfbea5b9a0bce243372caad6a",
+    "Name": "matter_fp_to_g1_27",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb8750",
+    "Expected": "00000000000000000000000000000000107c66e91d518789be416606058cfa8e9df478fa097241fc109d065005ae927d83563b72410e5b207d1556c2ee4dd67b00000000000000000000000000000000148c208e55e834c4e4fe20c02f517c21030f60c74b1a3bcf70bb2311cfb9b7548837b9187910bb7e8d1faa40ca8d6d92",
+    "Name": "matter_fp_to_g1_28",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75",
+    "Expected": "000000000000000000000000000000000fa72de55fc2229c0176120fac3e0a64c4498bcc7b67ca40b92d47a76a9db87ba498b72f06345c61d59a3d37c51153a300000000000000000000000000000000001f0e176d0987b8ceb7ca0e5ebb491bab0be17282cace8e03d52c986483026180082f86196fe512ac6bac58ec4cd024",
+    "Name": "matter_fp_to_g1_29",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be1222",
+    "Expected": "000000000000000000000000000000001655eedb905670d10d2f979962e864d68e9491aea41d073a6119e5bc0ae74216383501a48343d7099b93601f8b67c00c000000000000000000000000000000000842846147959f0f81efc6e8f515a9c59456637740bc15b2d335e0de45890cdd814ca7057c5d3e49e48e5a250c5dad25",
+    "Name": "matter_fp_to_g1_30",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760",
+    "Expected": "000000000000000000000000000000001098de70e8748daba7bbad52ce344619d3b5374821c1f932a18666ea0a591b24ece05004546cd519ba4d78c9747c57cb0000000000000000000000000000000005f537b6a394458ad51c2e677b2d52974a714bcf6a7474e748ad7f1b28738b6b874b6f49bdf19479bce4ff6c6a47de1a",
+    "Name": "matter_fp_to_g1_31",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2",
+    "Expected": "000000000000000000000000000000000a27f7fde0c79210f4b6cf59c97ac773c9766fdab289225c97f6cf42179385cf18f47f14b7e481df7c19418c79dfaaba000000000000000000000000000000000874f21294205152df3a4fab2ced482d325274886d8105b61668074dc8fc90240a715c62b2a2864901ca7a30f12e76a3",
+    "Name": "matter_fp_to_g1_32",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091",
+    "Expected": "000000000000000000000000000000000a6fd7355965c9514dc7237efd262fb9dfd8025ca2c56165e22675e615095887760ecfed4a2080cd5a2b8041ff26578e0000000000000000000000000000000019b1e02c9258fe62160d92eba8640ffd79b3bffb8ca4d602ca6c059239047c5563049758911d0e6034a25ec5094b1f33",
+    "Name": "matter_fp_to_g1_33",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18",
+    "Expected": "00000000000000000000000000000000013e4cd06b8ba7b5efb70feaa03550bfa45c7c2c79033c92b819257b2ddce28d501cc836a5ec81bf210bed671bfa66f100000000000000000000000000000000165d806d235d41f21e961795ec3da4f1b0334ba6e71ce384445bfda9e5d89e448d00253ec9f8d49825a230b25ffb2848",
+    "Name": "matter_fp_to_g1_34",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1",
+    "Expected": "00000000000000000000000000000000067c19b7c3dcf8b43d6e83dbda7406f5f88b06cfa0d7d145201164a1f06cb5549545ab28fd1ea8c1d5a662cced00822a00000000000000000000000000000000013aab7ac4ebce4686ad8a05e4eb2f60ebdf03c4f4ca0111bb1cd3dd5fa7558f1cf0dec394d0b616cf557f3811bc2104",
+    "Name": "matter_fp_to_g1_35",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f",
+    "Expected": "000000000000000000000000000000001673e66a7e558d533be5b855df7c3bdc58f1fb0a3b268b84b4fc25a3a8a211c4c9c8d884fc62f00eccbadbc96dadd7230000000000000000000000000000000016265b691fd43045567ab4fc7e7efa63c8430c8130761b128f0ba7bf381a7cb81bf05aea2526b50ff6e48a87c8ee9cf6",
+    "Name": "matter_fp_to_g1_36",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192",
+    "Expected": "000000000000000000000000000000000b36ad42aeacfa47d77f045de527d5bd4fa5fcf25ca3caca99e3e7980e283278e013611d1bc7694bb0b1b86d8589730700000000000000000000000000000000136290ed913b8669f522e16103ff42733a57c1026f966facf4a2d385b0bd52668925d748760975ca5a132d00deddf675",
+    "Name": "matter_fp_to_g1_37",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a77",
+    "Expected": "00000000000000000000000000000000076fef7b61f4c687246991d6f735d6f89c953476ffc193bacc1f3cf9573ed47bfbf6dcfbb3da1ec1bb764a9cc9b1c26b0000000000000000000000000000000012b6bb88e8acd6cd0ef1929a79bf4d8b10ec3fd575fe460686921fe94aa3a472cbc7aea543ee6284c368f5ef2c33ebc0",
+    "Name": "matter_fp_to_g1_38",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510",
+    "Expected": "000000000000000000000000000000000bbc25f7788b0031f1487ef154e877c5ae277a80d56b3a24a39c3ee94eb7df81a47bbff233c1baaf700829919e5254690000000000000000000000000000000019fd9d1237b508d06d7b2ff807c15c3ab36e6eab7e5b9f145bb2c0f2ce8ec96ca3a24932076abfb74eca85744eee4044",
+    "Name": "matter_fp_to_g1_39",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b80",
+    "Expected": "0000000000000000000000000000000015eca2e3d36d619601b0f40b01add7a708bbe59d04d5dfbf12d6e473e252505cec0cf7ea1c420000d49221d5e1ba6b91000000000000000000000000000000000cc6045184317aaf2bb8a904755bf48df9e6754e3a864037ebe0218eb3cd1c0a54e50b95f9e6d318799a72fac8d4e262",
+    "Name": "matter_fp_to_g1_40",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5",
+    "Expected": "000000000000000000000000000000000ef0a6307d4a3e92570cad673ca5212780902de416e81d15638ba654951f442e852b53255d7bc4d4e71098924d69f5a600000000000000000000000000000000156abf6f096326c75710300578f0cd946536e16bbf80034c6dbfe454565a501c268135118745989e5274ca2431ca5155",
+    "Name": "matter_fp_to_g1_41",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072",
+    "Expected": "000000000000000000000000000000000dc94ea6018ffc5838cb7cb00df9625c0c09701bbf19edddb735a3659b385bdd09e9a7d6e869720b727ec59ff3956d9b0000000000000000000000000000000000a20ea6360179bb6608bcbe4879df186916ee71b3ff7a1dd0fd137a0e9dfb135bfda2c66d1cf8d358d69934012a1a1e",
+    "Name": "matter_fp_to_g1_42",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03",
+    "Expected": "000000000000000000000000000000000b4e11f70679333c064d06180df6b54dd1df20ea216415ecb9b704bf4b206141fd841770ab77de4ab2400a076cf9dd04000000000000000000000000000000000ad8c02345e141396401221bb36a2ca21096e89aa76fca4121066da74f2f54b3e2c4049483d9855b7f3159ef448c120c",
+    "Name": "matter_fp_to_g1_43",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb",
+    "Expected": "00000000000000000000000000000000136cd8012cebf1639a396f331f73f0da6c114927559cc595f01bad1a18046ae8364858fa262ae04ae3f3b7d13db55a86000000000000000000000000000000000393a915629ccaa9ea06be749f3053dfd07061cfa24bc0aead12622c7d14c085e2994178bfec98b3f8867ac5b4b7a05e",
+    "Name": "matter_fp_to_g1_44",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e",
+    "Expected": "0000000000000000000000000000000019008e485a0a9c2f73a79bfe31782a17952edebca308bbc9f90e2ae15525bd501268a1c38c669de0b4e4fcaf1194591b0000000000000000000000000000000009c35254702eb7e3213fcbab62946ba79b7375cc320ee1733d8bf5729d378d1a98fb27d870e27c13626c35cb00a6bcbc",
+    "Name": "matter_fp_to_g1_45",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e9",
+    "Expected": "000000000000000000000000000000000059a3315f8b6e75c45e32843b4ff2401c41e1f6716a5909894cfdc71a49253d2cb04ec416d204bf0bdda051ace606260000000000000000000000000000000019cee852aa9fe28e1da49dfbfa7901220616f464ba447480c2421fd6d3a5a818c778510a04cb6557d27f7ef9a78f2fb8",
+    "Name": "matter_fp_to_g1_46",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe",
+    "Expected": "0000000000000000000000000000000001d157c963811725ad533539f17acd16ac3aa22917ecb2198d83a3ba396955f2c9654c02fd42e3d4ee6156cd148e9c270000000000000000000000000000000008fd299ddabfe525075f548a31ffc990a3626aba0369bd0accd0e1968204c8e1085c6b287b370808609178ec8ace2d0a",
+    "Name": "matter_fp_to_g1_47",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf48",
+    "Expected": "000000000000000000000000000000000ee6b51c5eb4dd9c27a61bc2f3480d799cc4fb88414630adb3961508c7067bb186682194af406f811296228c068e6415000000000000000000000000000000000b878c207bc4b61e827ee09a7825fb216a63ddbc4ef0522b8a944bcb673ca368996c31e6513504c5deb5325ef4df0459",
+    "Name": "matter_fp_to_g1_48",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909",
+    "Expected": "000000000000000000000000000000000658d61bbb2273e8969269dc16e16be93ef82be0668c3a164097a1c0816bb4aa94e5f70ed8d96bd15d9acb602d70f8ee0000000000000000000000000000000008f696d49a5c6f3dc971699a5837f7b3a20e222d9559d899eade367ce684b60153dfb75a9a8b81d7359a93069e2d7d7d",
+    "Name": "matter_fp_to_g1_49",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d5",
+    "Expected": "000000000000000000000000000000001313f4cc65865c367cb5c1c96cf30c7e993207e9ef4b2fce9db01181b1192520f01a0428668bb9d33eb857d9435939df0000000000000000000000000000000006b5e883fc24585de3b0a0b83cc1742050e578cb57e89b385e245da0dd2832852c3fa5f31ccf55e6744e9cae6c2f705f",
+    "Name": "matter_fp_to_g1_50",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5",
+    "Expected": "000000000000000000000000000000000f3fdb57966f9ffd0e20b9ad3bfb4fcade56468aa598cacfe388cd3b647d5966350586daa4493de23703a1debc82e48900000000000000000000000000000000044ff5ce3b9bed637709f9105bec0d86b4f0ea2dd86c9c3b1324637cd4c0fe5a4a965021c51279fc03592414e7968d23",
+    "Name": "matter_fp_to_g1_51",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0",
+    "Expected": "000000000000000000000000000000001288807e8f49323b39c5d592b97f19cf76f2f642dc4fa704004789d28452ce7a02a45f3f83a8d9875480d380e76df09400000000000000000000000000000000123b15dc7f166cb7c2c106cfd2f7c321a9bea9e3bdd118058c4745b6666a0df2a7c7fea16887a4c85faf860fe48a3787",
+    "Name": "matter_fp_to_g1_52",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a",
+    "Expected": "000000000000000000000000000000000adadcf2f074679ef3523c10674260b0e40106cca8d94b05f83e2b27d8da8c00dea4215a30275ea5e1a8fd0beb45dfb30000000000000000000000000000000003c2d436e545163abbb18ff7c8e6db1e55c733c75f9594c695c66656690e88995f9f266c2620e99075d3b78805e3ad41",
+    "Name": "matter_fp_to_g1_53",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f8",
+    "Expected": "0000000000000000000000000000000016cf855c1ea449c47236065ffe53a4c6afdadc08f1eaa26a8f79ea92a7a119b26dea1dfdab4db9b02b3dcad2c077338600000000000000000000000000000000071924c7d4e6aa5234dc921d288dcad3e49b44d2f455d207f3641f4b5b5c809b84c04945df08e785b3d99eda1807611c",
+    "Name": "matter_fp_to_g1_54",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa",
+    "Expected": "0000000000000000000000000000000016d80d4689e959233f05a3266628e233b747705bf6d6236771d5e697da03a0daa2dfa88aa5a3a5b97bc4517c467e94510000000000000000000000000000000003bc451286fec0e7a01d29ffae4986a2a3371d4aab875547cac05f759f5a52b8cbf84798b5b3d664a8692b212d4e974d",
+    "Name": "matter_fp_to_g1_55",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe",
+    "Expected": "0000000000000000000000000000000017297cdec2f6a54cb11c1fdac799f252c72dad52ead6c29de61d64e56ea0e0a1d3a60284029323e35d38a4a25f82fcd60000000000000000000000000000000009beaeaf3ce2c9bfbfe5e04ceaee87460d760c4c16caa7b37767e16b8e97cf08bdb6d30472b3027f66803dec1ce40eee",
+    "Name": "matter_fp_to_g1_56",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13",
+    "Expected": "0000000000000000000000000000000015a163067e8039be1c365804887dfbb78a7a699f0308c8e26519bf1c86fbe6acffaa26f0e5a2a380d1c704fe84d3bba60000000000000000000000000000000013f94e107625aca9c4346102dd5f09d51e445fd44ea67f171048e8f9965ce3496e759610c078404d41add90a358af482",
+    "Name": "matter_fp_to_g1_57",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce73",
+    "Expected": "000000000000000000000000000000000b0667e2b7c0fa318c5c0e66425f8cbb8217bec845bfe56997cdb9d0d915131b81e82419a4533eb573ffe103077f35c90000000000000000000000000000000018074b6e0cf144fff9da02a4b5785d21762952d4ed23b1430d6165974f49521b73eaf98973f7967ffb35cee92a2b5269",
+    "Name": "matter_fp_to_g1_58",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b",
+    "Expected": "000000000000000000000000000000000b5e953227f4f5e2070482cde7fded231bb0d4649a626d356cab2bfcba6c1588ef38c62cb2c550719091206727715dec00000000000000000000000000000000095f29eab98321d334f22b4db0c30a0604c5c385fd222a71399763f5c815e04226d9d06b460b9e3b44d1ec127d20315d",
+    "Name": "matter_fp_to_g1_59",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc",
+    "Expected": "0000000000000000000000000000000017946ce626cd11556f85d15b85044fdab0456e24b5e331886be860bf55411a03886738aed9b19d52e91a94ea5cc5f040000000000000000000000000000000000cbe613ecf3c8ca8a5f0617c64647a609ce6e8fd40ae42f69a928f4ba78f7038254689bac2dcde7a464a03d5e26e34ce",
+    "Name": "matter_fp_to_g1_60",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82",
+    "Expected": "0000000000000000000000000000000003b425300fc1885f2e932a469a8137bbf9df9560279a5ba87a13e7d4a461489bd8005054f14fad881e06aa46e4333d920000000000000000000000000000000011dcec636ef785d348fcbf9c59a82080b8f2c02d7ab954bc17af1c163a5383a36dd3948ac9110c6afb363ccfde2b6682",
+    "Name": "matter_fp_to_g1_61",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df",
+    "Expected": "000000000000000000000000000000000e545a87fb19f7943e18c75f7a173d18ef8129b200222bf6a2ba5a93a92c47ba7accecc4f089c42d6c6bb2425bd1786e0000000000000000000000000000000008c005ef6e5b25e84a8251add6112db49637c2b955af8cd65d029f8e17abfc660794b474689a00b5d2784163a9a0c241",
+    "Name": "matter_fp_to_g1_62",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb",
+    "Expected": "0000000000000000000000000000000004cedd2deb72d9168ab5704e21d9a5d85b65ae1510a628515753e85425286d9825dac99922be4a19870700956a65ece9000000000000000000000000000000000f5b0efbb2b327e294246fe862ac01dcedc7e728b938edb9c4a6128740b7d192cf8ad877b869207fb6d1453d85db895a",
+    "Name": "matter_fp_to_g1_63",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de74",
+    "Expected": "00000000000000000000000000000000031841f58b82f7e44aa03f474f18360128aa5699e748e4e2fda1c29d3cf165dc3542b90f09e415e92d73a162af38ad52000000000000000000000000000000000028cbb3ff58cf28f6dc876c2c1cb147bd6af85f3baabe253e9a1dd69687b3a46d4604d2d92d08310ecd7c90723bc7c2",
+    "Name": "matter_fp_to_g1_64",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5",
+    "Expected": "000000000000000000000000000000000e378bf9d1d65cf3a39dc2b3cd2dca8954270006abe048cc29183c5e7c1cf464b21a548679fdf5af8a31e198b69ded53000000000000000000000000000000000865c90b45eba1979e433f71c93c7b3b8e90d3d12a3c2153ab7c420f507bbf91edb593d3beb3899e76d41674b5ca33d6",
+    "Name": "matter_fp_to_g1_65",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276",
+    "Expected": "000000000000000000000000000000000844679db6a74e2a1f7c342771616c446c5e240e40e1f994fcba49f8ab22a7fe06b6909f50ea3c49a8fbebaf2b22b0a000000000000000000000000000000000090afa19255f7b71630c466d6b180b2100f8ea6b7ee2085973e409af8027859b61e0c46b639120ef6f3ee1555aed2f94",
+    "Name": "matter_fp_to_g1_66",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7",
+    "Expected": "0000000000000000000000000000000009ffb2b0054536d714944c6c96f8c1ea902e7109d4917a54ec551d811ab15042f843e158a9e4decab9761cb10e7c3e24000000000000000000000000000000000a6c7a862b951aa9f8c2d1e8ba30af8b7909e9721a06479d186e46ffae3ba09f5f52561c7c4c34d121be1304650cfc6a",
+    "Name": "matter_fp_to_g1_67",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031",
+    "Expected": "0000000000000000000000000000000001f9035574fac4ddc3f114a79938105d95ad4947588028b60e2926a8e0fd78710434edb2ab6b761fec43e458e19f0e200000000000000000000000000000000001e86d391172978aadc652b1c5d28dbb26a5357d1deb522bc280a270cc63cc18284e5b05033cd7ce1a6eb962a5b7e268",
+    "Name": "matter_fp_to_g1_68",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb",
+    "Expected": "0000000000000000000000000000000002583631492e3e0bf080a5f67334f7a2907c707a678bf63d53badb3ed90305a6eae895f7842a5d44a2110585d412ed860000000000000000000000000000000018719d22fc604567689870d5a5b043ee7234927b1e878dce88be212a8b0981e64f3cf9e03dea94439f504c846c6e42f9",
+    "Name": "matter_fp_to_g1_69",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5",
+    "Expected": "000000000000000000000000000000000fc1acd8490dee632c51e67356601295291b107087efc2483c1e1a41fedcff244114608c49f6911a4249a59a891264140000000000000000000000000000000019c402eaa9ddd6ff3c72a7d3bbc736cc867b437dbf56c9941ffdb2e0cd60bdb7ccbecef3d62aad22e97c1d96a328e8db",
+    "Name": "matter_fp_to_g1_70",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2",
+    "Expected": "000000000000000000000000000000001785abb82ace5d8024c97b3480fa69a65f5ed48fd3f5416f068690f8f79295d13929d01922c562277f65293abf5d739a000000000000000000000000000000001076dbc521375a1431b24f7d03902491b80b1856cbfd3e759b520927fc559e705801460afaba6991b032d59739c25059",
+    "Name": "matter_fp_to_g1_71",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b299",
+    "Expected": "000000000000000000000000000000000bf99b7aa1dd96f57974fd79d5823d1f379bc0e32ce416e6f89a499b82727081aa78529dcc76257d1d699b9979ee23f900000000000000000000000000000000067044e8b0cf455974850859bf76bca780f1908beb06a64a7ee8db2ed54703431c354cc3d7576fde0b45611a2f49f862",
+    "Name": "matter_fp_to_g1_72",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf",
+    "Expected": "000000000000000000000000000000000d102c354adf7380053c8b0c11a5c15b046516a87b3e98d1f909bdaff06eebfd9b0c457ec3741833da262f77d411cc500000000000000000000000000000000012cfcd6910ac046ab8c0b448edca5847d0f8cc2a4633fe42edd223ea1b73ec451de8d75cc3d37dfb741ee35259b34449",
+    "Name": "matter_fp_to_g1_73",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec433292",
+    "Expected": "0000000000000000000000000000000013c11f82052df6294da64b16551e689c439d2d27922bef2a067bc49eb4718a392693570f3b3e58158dc0f5bc3a5b8f73000000000000000000000000000000001517ee24f199913c184181561823d7c3506caa09d93d506c7773f9f615169df444c9f09b518e840735c259ec02488670",
+    "Name": "matter_fp_to_g1_74",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036",
+    "Expected": "000000000000000000000000000000000a546a1f4d65a37d7d60468c18f72152473feeed100119b4518f4c778a7a37a23e8c60ee04cc0b39d5a1eb8c908856870000000000000000000000000000000009c5766d9c88dca87768c0aff4160ff0fdc3aa67dde3eafcca030eb295a6736e95e415f3f5a443f2545c7fbd01f97964",
+    "Name": "matter_fp_to_g1_75",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d7",
+    "Expected": "000000000000000000000000000000000a197b81c0950b1b802128a01e3b620fb2134115a0d1aa2946a82fd22e91f172785d19017fca385863ee1643bcd332b80000000000000000000000000000000011fba5b82b0b2726bbe7a6157ec9103d0b5a480066ce5ab7120294930b81c04cf6d0fb8b979d17c3e262bd1268bdf1aa",
+    "Name": "matter_fp_to_g1_76",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e",
+    "Expected": "000000000000000000000000000000001644dd543ee92960effec90347ffe5f06d6b087f13c6bd73dca93c9e16818d25ffafe3610260cd43ce9909e2ac2e2884000000000000000000000000000000001893436c9dc44500be831076b375d0feccfad2a126110fbcfb77acfb95d6dd6c6615b4b795c007ece6ea0c31915b8e32",
+    "Name": "matter_fp_to_g1_77",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b6",
+    "Expected": "0000000000000000000000000000000001aded655b8ba2739b820b894eefd7e60d11889d7321fdae5ddff5dce11551af24acea3f501044562237fe5df53305df0000000000000000000000000000000010f4f3f415891ba4dfb21307798329aac5baea98cdb44354d4263e1ee6436f613a3accf06802ce2c2782e8a15738bc63",
+    "Name": "matter_fp_to_g1_78",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee",
+    "Expected": "000000000000000000000000000000000be0b580d0f12faa809d589ba59c5810c18f74b025e6dd4dc49c83b6a39423c5cf82b0dbb1d750e1801e37a5291692fa0000000000000000000000000000000010891c5bfece55dabcd223518167c5b0663f65c001ed051735635b417cbcf2484a057522e1c3417e43c82095b0cbb855",
+    "Name": "matter_fp_to_g1_79",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770",
+    "Expected": "00000000000000000000000000000000152454ae7fed9c971cfd72ed054f44124d71542f9ada5a90f1601114289c93fb490a1c5d99b3e8c70fc44fd10322173f0000000000000000000000000000000017bf9499bdc15ae5091daf41812c74535ca31b56520e420edf9e5aa90795ce5db5fa42a06dfcbc7438e954db83f09b75",
+    "Name": "matter_fp_to_g1_80",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38",
+    "Expected": "000000000000000000000000000000001633516081b91621b786a09389e89b274c2d9ec616db5028b009ed5c0a1ab47695a0b95c53a45112144613a4af08e6ea0000000000000000000000000000000014b09586f75c939fd62c3d667ab6263367f8961ad4597f1b92d792e8ef79a469137dfba5ec0a6354d5bfe3a84130bc65",
+    "Name": "matter_fp_to_g1_81",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e6",
+    "Expected": "0000000000000000000000000000000000f0474d596ed86a0d664885f9c981228fdc352755d52dd7e979a85fdb1b6dad106d8bc0a1eac04b510829b7da496686000000000000000000000000000000000a72f532897f912eeea707bfd6d183a73786c7b2e2c80a01f3abe7b959467d6ea63093c16d6465382a7808d5f0edd92f",
+    "Name": "matter_fp_to_g1_82",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d",
+    "Expected": "00000000000000000000000000000000139ae959f9b0cc2d900e748220c4bfa7dbe22926d8ecb9a10e7d713fa0a6e147fa3463e06b791a5e604c66110b77f7530000000000000000000000000000000013f8d09915f77f4a18854dc2451cf39d7ff502a8184d3b4c59ad3317d62940e903d68836751172ec0b4a796db003b373",
+    "Name": "matter_fp_to_g1_83",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b",
+    "Expected": "0000000000000000000000000000000015d065191ab63df2175f821cf62a4b948a6b2389512c7e94e1fa3c99506af624810ee17de2c183ebd69b4dc485ae264b000000000000000000000000000000000fa8cfd94bbfa6d504497866c1e0d9e84717fbf0468a164e3b8ca46348789e2b7f08ac5e8aa2e7205062f3d5083dc5fa",
+    "Name": "matter_fp_to_g1_84",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7",
+    "Expected": "0000000000000000000000000000000012978a0da7162aa1e8b32cb6ec0eebf2c2e62350cab4534358c6bf80299dda9281e16ee40313e7c52c804b2f4de7f1870000000000000000000000000000000009dfbafc8e40d71a789a52d5f8b80e7c8510c58bc0774cfa84211a9c1417d75d5c7b06d7aa9fe052ad9c1f30c922705e",
+    "Name": "matter_fp_to_g1_85",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d",
+    "Expected": "000000000000000000000000000000000de98869442b759a382d0f6ca45eb60424eb9aee2efdac83086cb6dd374120941343eb314756113e084f943cb60d91470000000000000000000000000000000019dacc8180e6dd09ac4bb97114d2ecadb04bd2aef6e5f0993742c5270267e42d052d436c99ba61f6c0fd1fd2cd51d172",
+    "Name": "matter_fp_to_g1_86",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1",
+    "Expected": "00000000000000000000000000000000123313e3cc006c4b95938f5eca903604ac9272c7a0c79cd932407b70635d7ca5de9297496c27406f180d5edebbb54c7e0000000000000000000000000000000002164460e59cc8788c96e235a6faa7fadb7e6ee9f6b0b95292992973ff54a92147dc7ae8e8f217515b6185875bd0bd7d",
+    "Name": "matter_fp_to_g1_87",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a",
+    "Expected": "000000000000000000000000000000000393a1b2395447b2e2838c2f49493c185424c4848f888616f16a95552671ff28b5ef223bf34299005f22a8df6efd68290000000000000000000000000000000012b1fe46279922e92d356355752ae0c2f28fc55de39ebfbd317a6c1c507d973f88c6282468571a1efc20c10314ac72f3",
+    "Name": "matter_fp_to_g1_88",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122",
+    "Expected": "000000000000000000000000000000000739f563b42648cde5befaf44317468982eb9d2fceee7d2efff1755be973cfc2beda829268246d09cd29fc3aa91f0b8a0000000000000000000000000000000014fe0b03ac5e0e03acd7811270d65742a3345bed7a4790d5f40097dd34050d0043104b65fd4691c251f03e67525d41b5",
+    "Name": "matter_fp_to_g1_89",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be",
+    "Expected": "00000000000000000000000000000000128e92c9c10fb9b065fe2c2dcfe365e98aa54eaeb3fae987306c7f0a227171ae0b3464d01a54a8d6b144ff60c45088a00000000000000000000000000000000001beaace4e23c9a31e1e9eb8596b3b05b9d72553f44c61627654757080171b05c900fe1b638193a69058e8d66cff1aa6",
+    "Name": "matter_fp_to_g1_90",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f",
+    "Expected": "000000000000000000000000000000000a6099ebb3a1101206bbd21149cf22af2371106bd34671c1cbd4f2e19311fd100bcb56a6d9d77bd834f972e55e0fb75e0000000000000000000000000000000001db77a2045e54b0ac4b3d61190684b4eec9c4ea415e5c820992b70d6ee2e086c02892228c4465c8494f939cc0b7b5ee",
+    "Name": "matter_fp_to_g1_91",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a",
+    "Expected": "000000000000000000000000000000001101cd37b61247a9859bb09ccf9eb416643f86b7109bb45d6827fbf424956c9a16b2a19c5e198551c43aa1934ad8ed0e000000000000000000000000000000000da562fcb2e3cba853de6d245a1ea0cfc3ac120b316a5f4f7072cc35a6634027409ad08c5d591a6688b24cdc4562cddb",
+    "Name": "matter_fp_to_g1_92",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb",
+    "Expected": "000000000000000000000000000000000323c3aa4b20691af32696c449668fb6da6a0c2e8eb176fb8fcd8aeebc9b5a3bffc57b28dd35e374811d420419fb0fd30000000000000000000000000000000019516a092385d8c917b46a742f086c51e2648c7e9a709ebeb5a0f8bc29c9aabf99972aa3a218582f37d91f9758a5ddb2",
+    "Name": "matter_fp_to_g1_93",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c9436",
+    "Expected": "000000000000000000000000000000000d0351d8557d21c2dd3b1be77bb01df804ebb9e2d7e80910264ff94861cdc0a4deedc1231c61b7503c5d653e31fe10850000000000000000000000000000000005858ee487860d1ba04cfdcedebda235616c2d271ed50f89d6cf2852ea7e10ac825dacd8b00071684858a12459d1705c",
+    "Name": "matter_fp_to_g1_94",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba",
+    "Expected": "00000000000000000000000000000000176c50c2fcf1bcbe03a1a1ed2eb120f94ad4fcea34a59607ea595bc2b37cb92f87641191b65d4b5d57f5491ce6576a670000000000000000000000000000000000e177361e09975c98849faf8e24086f75a48df0f257ea47b659cc2a142a57ad1f64416f6dee5cbc4e57f780dadd1cf2",
+    "Name": "matter_fp_to_g1_95",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c29",
+    "Expected": "000000000000000000000000000000000dcce000aae744f8b3b6754af57a36786d887d7f9857654f93edbcb6c4416ccfea5e859acc82860b5f706087e87cdc07000000000000000000000000000000001847c32c839668a38669fdbabb512df15cde2b28ca336b0e158d1fd57f74638d86ba40ff68f0a50cead7021e86c5271d",
+    "Name": "matter_fp_to_g1_96",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4",
+    "Expected": "00000000000000000000000000000000062a58846d39dd1fdbd34a7117797f2200d814b2a8eac9479885762565a979e93b5313575bff5ada3211eeed0a3f4ddc000000000000000000000000000000000548a24e7af2b38c4d16d8dfc8fb2d7e7669051e2643c44aee113f20d31f4853cef84e2dec20095c273680cca278331c",
+    "Name": "matter_fp_to_g1_97",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa1",
+    "Expected": "00000000000000000000000000000000107f01e4fb6430e34128e3335872cf40df2b498a63e048d46158190cb627e37833d2238dd72681037ce376384736b43e0000000000000000000000000000000000e1812299403efe0f8d111d97a4b7e7b8aa1f4ec58f9935b1367d81a847fb42cf756154448f9172118123679a41a280",
+    "Name": "matter_fp_to_g1_98",
+    "Gas": 5500,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79",
+    "Expected": "00000000000000000000000000000000188dc9e5ddf48977f33aeb6e505518269bf67fb624fa86b79741d842e75a6fa1be0911c2caa9e55571b6e55a3c0c0b9e00000000000000000000000000000000193e8b7c7e78daf104a59d7b39401a65355fa874bd34e91688580941e99a863367efc68fe871e38e07423090e93919c9",
+    "Name": "matter_fp_to_g1_99",
+    "Gas": 5500,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsMapG2.json b/core/vm/testdata/precompiles/blsMapG2.json
new file mode 100644
index 0000000000..f30eef6564
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsMapG2.json
@@ -0,0 +1,702 @@
+[
+  {
+    "Input": "0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641",
+    "Expected": "000000000000000000000000000000000d029393d3a13ff5b26fe52bd8953768946c5510f9441f1136f1e938957882db6adbd7504177ee49281ecccba596f2bf000000000000000000000000000000001993f668fb1ae603aefbb1323000033fcb3b65d8ed3bf09c84c61e27704b745f540299a1872cd697ae45a5afd780f1d600000000000000000000000000000000079cb41060ef7a128d286c9ef8638689a49ca19da8672ea5c47b6ba6dbde193ee835d3b87a76a689966037c07159c10d0000000000000000000000000000000017c688ae9a8b59a7069c27f2d58dd2196cb414f4fb89da8510518a1142ab19d158badd1c3bad03408fafb1669903cd6c",
+    "Name": "matter_fp2_to_g2_0",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2",
+    "Expected": "000000000000000000000000000000000a2bca68ca23f3f03c678140d87465b5b336dbd50926d1219fcc0def162280765fe1093c117d52483d3d8cdc7ab76529000000000000000000000000000000000fe83e3a958d6038569da6132bfa19f0e3dae3bee0d8a60e7cc33e4d7084a9e8c32fe31ec6e617277e2e450699eba1f80000000000000000000000000000000005602683f0ef231cc0b7c8c695765d7933f4efa7503ed9f2aa3c774284eabcdd32fd287b6a3539c9749f2e15b58f5cd50000000000000000000000000000000000b4f17de0db6e9d081723b613b23864c1eeae91b7cbda40ecd24823022aee7fc4068adc41947b97e17009fad9d0d4de",
+    "Name": "matter_fp2_to_g2_1",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de200000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799",
+    "Expected": "00000000000000000000000000000000184af3f8a359dd35dddd3dfcc6f5b55ed327907ed573378289209569244e3c9c02bdf278eb567186f8b64de380c115360000000000000000000000000000000012f5ba8e520c4730ac1fb75dabbfdc0181855e5ba2968a8c0ba36a47ab86ac45d19aa3d55f15a601e120be1f75eefe240000000000000000000000000000000004e313db704b103c2c1e3a58f8e95a470e7199081eb086e9524583131714c4a3db551fd51a3f2314a19a658e7b1765380000000000000000000000000000000004040eab7416a1703b0d103120506f1de2b26b0f48c7a0ea63dca4d9ad1c478ae03b5d7bfd51f4cd6f8cea26212c4edf",
+    "Name": "matter_fp2_to_g2_2",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60",
+    "Expected": "0000000000000000000000000000000017fc341e495bf4ef5da4c159a28320aca97ca28fe3a0441242cf506b0f89bb52f5b5d8c6e038d229ffe67d00151912f00000000000000000000000000000000007666300b7be3d904ae3d19019f7be5cf5ba6161b969c1a78aff639a24387d8fdcc4d0e3cd81ba6f063ebf2d859370f20000000000000000000000000000000007cc705dbfb5c0418beb1cfbd864fa0631bd60eccfdb16b5d55b6ef3558e2ec87dac3b45294dcf04a064d6d1eba5a6eb00000000000000000000000000000000052cb9c982e6b05c1d2ab4eed1d8082f96426b55615ebc6a53bdc320ccad0aad044395ed641b3176b554f19e62d46b73",
+    "Name": "matter_fp2_to_g2_3",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f1570000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac",
+    "Expected": "0000000000000000000000000000000015ad9155037e03898cb3b706f7105e39d413ff3a5abb65812b8d21d003cab8fbb607d3938ccd6a774bc8debfa30f42760000000000000000000000000000000019d6382bb2d78180a8998a0536d67412d00ec0ef65f4cbce01340b8d6e781c0ff790296f8cada28966b147c69e02f366000000000000000000000000000000001290c2c205b748069d0875a89ca74a3b05ad8218ed46a1570696932302983c090d96e17e0b828a666fdfc3b72cd348bc000000000000000000000000000000000114f2f7ffaa9f90b547e86c863a5d3585819a78b095848dfa39576a10874a905488687b73e613f3d426510f5d1d1ce1",
+    "Name": "matter_fp2_to_g2_4",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c80000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3",
+    "Expected": "0000000000000000000000000000000012084a53cde353a46af17cd2fb02c477e47b874d8ff58025b5015837759032ff98013dc5bf01253bb964f035183c9071000000000000000000000000000000001659272ab7e3a070a5c7b25a5d3402f7371ed67e58cac8438df41c39c1acd95ac5886b030384bf537d7c4bb8ddb2c538000000000000000000000000000000000852ddcc37a09a0a8f62dfbd1ba5064c1f6afacc9a279a4d998bed643eec5a0d96d6bad95701a04f52c83e8f87f48d5d00000000000000000000000000000000097a399370875398028d42bde8cf4e9641730af7a2971e2f59c95938120603a239c65030ded4323c955f7fd24bebf31b",
+    "Name": "matter_fp2_to_g2_5",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be",
+    "Expected": "0000000000000000000000000000000006f8191123f1e8f6a05e4e663fa763c8a0ade5de3c7cd38ec1c82e1c85f123ab51fffcebd677afec8e9adecd8d11263d0000000000000000000000000000000004fcd825bc55d044eb70e0bdd5ea2ac58ec1487e903b431c57a640c756265a382581b8450fb15dc649cf22a8539088220000000000000000000000000000000015259f83d76490bb868bb88c2a2c3e07a326bd3e97fc2f552adf85722a360a443d720c328076e35224328e09494746e0000000000000000000000000000000000f76b0b960a1343b4267f5aff44901fd6796a778b1a87666b95b773edd0e7ffb6656d4f0cc3b9b38bc6c0ed20cfce153",
+    "Name": "matter_fp2_to_g2_6",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3",
+    "Expected": "0000000000000000000000000000000018bf5f93dbc2c37479b819f8edccd687c4d3c4dd04f8c73762fd89d0c003674e3b2ed749d23e775f925279b3112689f80000000000000000000000000000000008a033b197aa8ea2213dbd7ed478d98c25dc6e9f91b9924f3c14124da26a67bb196926e02da89b746f2a67b14ad226070000000000000000000000000000000006f7824bdc9c53212609512858278f79d9b094165ff178e3da8776e24311bebbd9deb29f366d4c7693a15c34df118403000000000000000000000000000000000edde25fc24b9ec58b3c317aa3ae48dd5fecdf6397ed9636ea042722d264db0b1a89a15a1e16e892755730ef52796527",
+    "Name": "matter_fp2_to_g2_7",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad",
+    "Expected": "0000000000000000000000000000000002d28025f4b798083aec3ca9a91a051ce27a374b115c944932026b4fe0dcf68b335d5e47212f800c241c2d42fd219635000000000000000000000000000000001742fb6ef8e9a5a7572b0d3fa4ae8ae56c9c6f4daa20d0b88212c40511c6f6b5ee98314a2d1cbe4bbbec907495a1ade8000000000000000000000000000000000d700a511a58c1b8f11153669cb21d88512dfdacbabe38e402431b4f7ba374b5f9a88614da2d56799d39324e9d19e27a000000000000000000000000000000000c6068bc7a43d614b8f1132b13e04f66d2fb5ac0c5bc8501b754a0bcf4f382db92b0994c4999e104c9d1111ef91d5edc",
+    "Name": "matter_fp2_to_g2_8",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20",
+    "Expected": "000000000000000000000000000000001350c68434a9b02392e60540a3985bae8daf9a170b30336ac73afae6f892c7ae8f5f1cadfb2780d6e5961ebf91cd69ee0000000000000000000000000000000000c20bd286fc1886b9b28dfa40d1a27395cf76a8b73946849ea0a7b5e12530de13c16acef8fe2a2c247ea65ca023eed70000000000000000000000000000000002d8ffd0235fb60fa573662034d46260e0c96396537b2a9d486dd03bdd13c5a1efd2d3cb9849ed11c4376b665f378226000000000000000000000000000000000d90ca1b73a6a9566832f9f19d8530a3b12f22bef853fc44088559b923ca108cebf4291e0d7de8f25c7429d455f5ae46",
+    "Name": "matter_fp2_to_g2_9",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab10000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175",
+    "Expected": "0000000000000000000000000000000013f223602e8d12c3bb51cd393f6f59beb5c55fe80c3fc8fb0bc90eca533d9b7981563a30ebd727ab6cf0111fa2d3099d000000000000000000000000000000000962b0585c681894cb701f17ec06c0c240899db574c02d82d85ed4dabd4b8654c29b84c71d2921986fc2abc542a3ed9f0000000000000000000000000000000000f0e79245e645a6e3fb88b9103ede3e6ecdd7e45d61b5755d7a8d100d80719746af58bb23d3068cee7389b2acf17f8b0000000000000000000000000000000017fa0aac84c58283f34b9bf713cde98c175b38e92503c08205350822d778f3dd5bed8051e185c495831a628aa89335c7",
+    "Name": "matter_fp2_to_g2_10",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d235020000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3",
+    "Expected": "000000000000000000000000000000001062c97c214b86518660c5e1c33a4e48923ae89ab7d8bc5c798e631de16fc1f104aa957d3e7915aee8551e24aaafc8e6000000000000000000000000000000000e42b785f17f25b87a0dc558a8d57b19d8f41767c3b4fd70c147e95443aff2d9a743003da41d578a2b56d7dc748cf59500000000000000000000000000000000111fd38cd2f5f681bb37f6239a5eea820ce3f01023c685f8e7e244fe9aa9dcbd18f0e50705faa5d8d66b28af9f371c630000000000000000000000000000000004726d3e452f6fcb180ce1d50bbee3a23f7949b635a058f12de1cf5abda19c042168feea53211dbed0bfca489a020930",
+    "Name": "matter_fp2_to_g2_11",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d99000000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844",
+    "Expected": "000000000000000000000000000000001211bb8d3bf65b60efc7237ffecddb4e7e2f0dd36e2a704dfc9f4972897addff1a57182f8e0a0ac08c9af2c98eaa4c560000000000000000000000000000000007e9877280aad45a3b1453b6771ab509e4f53937cc6da73d3add50aff94869b27f49218fb479fe19a6176b9aadd36e35000000000000000000000000000000000ff915801695a281f6642751be77155a813847ae0237d77d2edf836aebac02b659b98d49842d4d10e82d9d146e63a3da000000000000000000000000000000000fae1c8c01a2dd94f17c660353d158ff6f3eed4e6375f1e414ade9d6fd040a48e3ff0d558c882e92e74bd6ef4ab06168",
+    "Name": "matter_fp2_to_g2_12",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2",
+    "Expected": "000000000000000000000000000000000dd00d9f31cb5148048125668286c1790cb7294e740df978ac0bdaa6e1c4ba139a04f5770b194c9bcfb123d9b40b6acb00000000000000000000000000000000085d5f4cb831720fa13cef25464a1ba7af33abcc4079d2c5736a219ad9649ebb5dbb8687a2d3952390866587d7088f72000000000000000000000000000000000de377d773e40e1c76e218b969297d15f7819c525ce39aee5114e8405bd7361116682cf9d673574d415a7016b23b567d0000000000000000000000000000000018db26c2097f72b8788ef5aad2d7aa400627e224924afea1ac7c7a6b5cff4a55255e218572614519a536eaaf0f65533c",
+    "Name": "matter_fp2_to_g2_13",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb87500000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75",
+    "Expected": "000000000000000000000000000000001853b4c4e6fcdbed29c5d3aa4a9f6d447adc512f66a32fdef06c6ad316c42eb3ca47ffe6f21318ad610d0a68673d7bc300000000000000000000000000000000123d15c37fa8b1a95229e28500c9a767e6286b780138dcff2714bf1f8242f39bebb7d86e2811551914719ca90fb5615f000000000000000000000000000000000537498c2ec64b2ba58aa0a858b69990cac544d5cac29abdf6a42ae9c04061f83580b79c2a6104ebc55939d9a2bc5ae2000000000000000000000000000000000b348c19aad3b67c690512f372d995555ee38bffcdaf33bb827160d6929d2ce598523880f6136f11e1d6482a654cb016",
+    "Name": "matter_fp2_to_g2_14",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be122200000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760",
+    "Expected": "000000000000000000000000000000000994e7b6ccafc996f672c42ab491105ffe1482e65aeb456de2213b531889773ad4d5e6ea1687d6a1f13e74878766f11e000000000000000000000000000000000b89030486a1d622c97970ee7da6189ac341b9cafbb4081463f579ab8b4b049c6e6c8b63157455770a79108424a14f24000000000000000000000000000000000ded43800a991f8c37282d803a39941d3bfbfbdc56dbf7500ef3d16750b27dcb1ad93f89714395fd3dffe318c1771375000000000000000000000000000000001994144b032e1f8c4d688754eef82cdba0018ac47030fcb77e8fd920e0b0336255d2cc8376c03e1074f91269cd2519d1",
+    "Name": "matter_fp2_to_g2_15",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091",
+    "Expected": "00000000000000000000000000000000195d99406baadc7d8740962cbbf4bc1f22b08eafb52f3cb3c588b6cb3cd89d16cb7b8d388563289f5b5ea466128525c80000000000000000000000000000000004809f70463633595dd763d658354df4f9b409911e1a0328fdaf486d76ffb410d7c6cfcc2d48fd6757d5c2a4834f81fd000000000000000000000000000000000654f8475562098a2cb27ce224674a383283cde35173e1c16b141998b641ac9ee663d766f045451a7f6d600973f0ec520000000000000000000000000000000013bac451a44982c7b1aaac7522dab598cb79b9a3dab77f4d5a4c1c97c154451499979af1f86ced8ce2099bccd400420d",
+    "Name": "matter_fp2_to_g2_16",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1",
+    "Expected": "0000000000000000000000000000000016ea88d0bce32981f489438df1bc14e7ade7a45d449ee1ac1a041c1204460cf53ae5c0e111914d8af9e6b3b7fa394484000000000000000000000000000000000db571ca6a55bc8285421553a373048f7877ecb9683d52acf07d48e1026795993e4e7177490921bc6fe1e63d69c2de3c0000000000000000000000000000000011602919de1df6cc0dd36a59c84ebb8e209056534e336f5074c9ae5323f8a03b123dc6354cf85301d838b16518ab64390000000000000000000000000000000004407d30fbd632fd493055bd4d8cbed337767a2ac534411a3eabec570ba41d2ad28ef37512a7da3611ad60b6536b3f07",
+    "Name": "matter_fp2_to_g2_17",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192",
+    "Expected": "000000000000000000000000000000000c1e7b188697aa9a053f14e2d907f2c61a59e0b0c72f9cce30faf81dc714a50113500ca9bc3af6657a5d214f52c90616000000000000000000000000000000001544c35d712eaf79d8dd5a22fbab72f8a6843728898412a7f305b205f8a50e03c6c462b87b3ac165e9e6428e0a44a74a00000000000000000000000000000000029ebafd90a1a887669fd0ace762a66bca2bf0a216333b0ac97dedb6bff3dda2bca1e3d0ed5fa9081c2887fe6a8e24cf000000000000000000000000000000000e1a01ca93ed268e0291a937483f7f8e252c91f9bd8bde55271b0c97fcbbb9219009514217dd8bd7e0267f44e9927a93",
+    "Name": "matter_fp2_to_g2_18",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a7700000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510",
+    "Expected": "0000000000000000000000000000000004e8ad9838e7e269cddf0ae5c8f0f57e7467e0b6f2b9e37e7c4bcae965e9582dc46c9c50aa01f5dc761bf2f1ad311eec0000000000000000000000000000000011b1438ccc668900914578c3ec6e1334d0823861c892608817498fe2e538deec73e0034a6e8ba9790f63fdd95af3714a0000000000000000000000000000000005b4c88196425d3ecd22bfc0cb1a95488493f85bb74f50315f0ffcdd57ad2de23c137cd6d2f6f6dca8af2e3f7bb0539c0000000000000000000000000000000017066344a0f345ecf6a2ba66c37ccbce26a3f551524f74636d4c4812bf5adfabffb0645b898b10c332e94e5f2ae2d1c2",
+    "Name": "matter_fp2_to_g2_19",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b8000000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5",
+    "Expected": "0000000000000000000000000000000010d393bf893d589c578df58f4d0098ad3cd10d3a1d0f112f51b132a369e68c0284a6b70a5673383ae24a27a9043b16cf0000000000000000000000000000000003402afb77b187b45906d9cce348976ed88c758d75b9962a53352a6c3ee37751a9928097c0d68c6f8a315def4ca875200000000000000000000000000000000019b98631e53a3ffda3fb9165ef7236dad5c0c8d57c3315617cbd3ce77430bd89b9e1d88a019042cae0075594514a5e67000000000000000000000000000000001783bf1c9b0ec44c9191dab01ef5bda0cb2f533dbcd3aeac2b7c6720dbc8e3f770a215ec8ea2035129711ce4b448ba87",
+    "Name": "matter_fp2_to_g2_20",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03",
+    "Expected": "00000000000000000000000000000000086ac901098212acd091d9c4d42a1318c3b343480f1130d6e52128d61df9e19fb61ef1ff35de0ef60062cd99202910ff0000000000000000000000000000000019109b7292f1a420f09a56dce9694cb4944808a2ce9f1964cbb6ffd14a710c35abe81300090ffcd9e95f33e0de9f879a0000000000000000000000000000000012660c4e114a215390c6f6eabc4bd6e3d062ee28d0c87e24351c7d43195253cb7b5bcfed2b4abb2fdeb3ac04ee228997000000000000000000000000000000000e56d35a7e40a86ffd2088c81488265ecc4468d6cf02d563c91611cdf8b4333cf66ef50b993fe651b1792d2b242cff94",
+    "Name": "matter_fp2_to_g2_21",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e",
+    "Expected": "00000000000000000000000000000000071d3e796fb15d63c2d5cf68f59f11792b0b580b85c8839a02fad96664f14735ede2edfd5ba5b64045b366904f54ab600000000000000000000000000000000013fd1ea38d32772458622731b9e2d9d749f2b747443f7e47ef5e041531b56f86d1775d42a548b2bb201228f49ec9f46800000000000000000000000000000000099c2bd996c8c5ee37de971e8b75a0bdd4f69299778ee3d216973c9dbba97c7a93e40b209d390024bc4b5e82560a1a83000000000000000000000000000000000c4922ed9af845467440b78efa3a53ba904f29adf66e8ac437c8bb6624b5e5ba0772a5639b45fe167b1fb9283747c50f",
+    "Name": "matter_fp2_to_g2_22",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e900000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe",
+    "Expected": "000000000000000000000000000000000e413d72fdc3db6fc79ef26ae8b37fe5c4356a80b3598513b5173b3406ffb54708b8794dae158060a1accbe956a39ff30000000000000000000000000000000019ba9dfa74fd241a55a3b47c9f37c6ebd1e8b51f46197881abb64b7f57c0e2d8f18edee35bb9da03702c0dc5cc8749f700000000000000000000000000000000183525156fbc80cc67d6cd15fd2ddf7fb0528656ec1d31b4c275ef101dbb635424abbff1154a3ee04346ac53148fb1f70000000000000000000000000000000011da0dcd666d01180902d8a7fd7d2fbb39f9c7587540451045956108a8579d7c116385a81627dad9d4cb8cfe68927b6d",
+    "Name": "matter_fp2_to_g2_23",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf480000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909",
+    "Expected": "000000000000000000000000000000001932acb1fd0708edf13c293007a035991bdfbfe0089b61c261258e8c5c10d82a5318b2af221b372f0f3f43c391421582000000000000000000000000000000000973650743f0ec8e2acca33f2ef230ee7a05635d14099cdce913ad8678458ec0dde5c5a941097af2ee0c8ffb937d09fd000000000000000000000000000000000bdaf319044101ee9aa27b3accd36a5ecaf8b80deda4548377ddeb97283537be3f7199ad3c190ed23cdb44abb8786a080000000000000000000000000000000006c448827e3fe4f274bfa55a66bc76c5b01e29ac6a8dbebd801855ba4e93bcbd03292ccf804f07f21481260c135b827b",
+    "Name": "matter_fp2_to_g2_24",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d50000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5",
+    "Expected": "0000000000000000000000000000000004aee050b0ea07118d76f835218b77b39854f5ababc4e2a29d7c8cc7c18a69c30bb22437049a051d049c8a84f7868ad40000000000000000000000000000000003b1b809d5046054924c3814d26fd5fbdc59e03e5505813bab73bc212b0f5bc0d3fc34478311c5e1ac70fd16a01c52800000000000000000000000000000000002249a026af0b49f4659eca2c23dc790fb36a7b2996188828a17d5852003f1420f11699062932835cfe6543d454521e30000000000000000000000000000000008217aea2221f8748cd81cd37777605a95a63aba36a6ddad72c1e1ac57b24d79ff9d9c4ed71a6e3ac8a378129d5475ad",
+    "Name": "matter_fp2_to_g2_25",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a",
+    "Expected": "000000000000000000000000000000001274f676bcc05e54fa4b0cce234870ba97a0b1626543d6a9f09afebd5a752769000df404e4d434ebfd561f8335f36d0d0000000000000000000000000000000002877c9438fa319dd1a00f381834e8f3d3cdebf4e1f7690cb82559a2e978bedfd2455be020d0353aa56d435c0174b5b10000000000000000000000000000000009487cc9c7a09be901673cb1bd9a51f45e5d2ed30c90cbdd3e2b294c8f866f68da55533b78152e9ef6de30c345fde5b7000000000000000000000000000000000a3a8d4aabdb260203898655745cb695e6dc90c6e7bf0248784f8aa2340390fd5d8f1c6a98eb1990eb97c2a7f103e3fe",
+    "Name": "matter_fp2_to_g2_26",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f80000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa",
+    "Expected": "000000000000000000000000000000000ef415dfc1e47f39e9632ed21c9c2bfcc1959299710dcd7935a757e3756a42c8f6c627c720fd62f9c486a8e88a64c76d00000000000000000000000000000000088079108fe7d9ac93590c045be0d41396f3204d83793c4e862c5360ddb3268a63f704a9d14323943fc85874cdadaff1000000000000000000000000000000000cce908e8dbb7ec35820f2db5ae1174e0f675b21ae416fc89a7f242df3ee98764022744842999f65132229156d2627370000000000000000000000000000000011e0e2f8513d0a71b48599139a9a29c8eca090c5b02292baba58e07b1d3898fe158cdeb3bbe8edb4a805e695e896984a",
+    "Name": "matter_fp2_to_g2_27",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13",
+    "Expected": "0000000000000000000000000000000007c6f133647745c312695439f1d8c251e941bad6e988cfe324ec7c959a9e0fb50618984429ff1841d4286922a26873170000000000000000000000000000000008edb220f77ed17fa1f4757a42ec66ad808c1acc25c4b9311be4c09703d547f648d9dd7c8109ffa89d01a35c69ec2685000000000000000000000000000000001595cc05b04f557ed569b19d64c09f4d82e6617437571fddd72a672d07ad94bfbaaed906b3a7e3db519159ec8d0a8c4400000000000000000000000000000000041157d4f40bfcef680af0143ccdd0c4bdd25e598a470dae844d887c398bc498edad715fd7383421fc78758cc9b00326",
+    "Name": "matter_fp2_to_g2_28",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce7300000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b",
+    "Expected": "000000000000000000000000000000000f983607a6d8a5c3b8a577cbd5d81ad2ae936e714199e3f4095cf280b8fd6d3699acf4d2ef251a571dd1ef4ba6d838bc00000000000000000000000000000000048c12f8b95f9537e56479b1bc43a121e4edfb6477fcb090a5ea60c5f4d01071776dd0264b0250902448f62800f4d2ea000000000000000000000000000000001644ba272d7003d0077991ccb4569638de0dcc48fd2e8e9a41cee1d2200aee1a849f2d620f60beeb06b08c31cd4eeacc0000000000000000000000000000000018892d773f7e48247215484ca0c8d996833c43a5291b0380c97607c86f4ab2784e692673a1da012ac4fec2713d156a49",
+    "Name": "matter_fp2_to_g2_29",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82",
+    "Expected": "000000000000000000000000000000000a06ea8e644d2d762520ad956d41ac2086a588450bc34f6d070b86fdfd73cd0734341a751d823935a009b7517770f86e00000000000000000000000000000000140ef0d6a0482537da7db8d775ac3c4a93b16c15fbe4602b5b1843ce757aada5f7776a74151d0bcf760f7284d4ffe56c000000000000000000000000000000000873c90f56a2b99da2f0a1528b8e376a5912f9cd81a159379ad70b7c10e6ebb7fea0a90d65543d968a34ebd539372e89000000000000000000000000000000000b05ff57079386e4e18e73cbff5f7b0efa329ef7355f083e8be258922203240dbb8926f7d11c22ab4c16d1df4bcbb600",
+    "Name": "matter_fp2_to_g2_30",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb",
+    "Expected": "000000000000000000000000000000000e9d6f9e83a2584f2cdacc4711085bd251e060f8c87ff7538ce474d663c6f23361c88971c9da589586e754ed69699c820000000000000000000000000000000003fa90cc1dd81b815704e15c0448bd0e8e8d0cd7ad51237a25d4b8a0f78f532b18ec30a108930b7407b7486aad9824de0000000000000000000000000000000000cb97bce1f75b1df5a4b52745014eb632d2d2230e52a9767e3dfd76754e98252ca81ce274b92a2947f6a65fedbaa3e400000000000000000000000000000000090edabb37f411fae1764792083c8c7412fb470833a9f7399fb312c58687d4afbdc622ecf9d74cdfa3ea87382adcdd5f",
+    "Name": "matter_fp2_to_g2_31",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de740000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5",
+    "Expected": "000000000000000000000000000000000cd234fcc729a4206233e46875a557027cb52c96322386b56d6e50d95dd9d23b6f8936ddc6f8475b1076a855c1ae23510000000000000000000000000000000010a774120f607bf9ad2d7bc498536cc9d35cefe384f88a2439a75f1a4f6a9e4b4253daff0d2c91b5915ee0e9a99b4582000000000000000000000000000000001496e7181495114abc0314f580c16038a04a8dab43b5564d518dba5f5e48112ce9daca4b16b6ad51c3af54ec9ce915d20000000000000000000000000000000002c61691a96a2120663c726d7fba3ed37524b58c92a024c15fccc659d1d2cdce077ba233a0d4419a6f237ee4e09abf52",
+    "Name": "matter_fp2_to_g2_32",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7",
+    "Expected": "00000000000000000000000000000000011cd454f16209b0b7040c744291f2df465ebc786946ce3cde77fe4d4bcc4b60a51573c45b8bb2d209da69107613764b0000000000000000000000000000000018a026f29fc2f81e82015ef8610b4396f2e3514ab1a213356953804d585c5cd6a3c5cffbf70d63d9dfca50129021f0e60000000000000000000000000000000015bdcc8c139e636b05ba7376c1ced4a183eb465df53b1996f4ddc8cbf42cdff4ae2bbc2d24831a8ec8b1134cff4444ee0000000000000000000000000000000017671fc3995babcd2c0a1d2a71c417fea84e29df67fa1096fe6d3ec77c45b64fb8da6ed08a57726ab314fb860899961d",
+    "Name": "matter_fp2_to_g2_33",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb",
+    "Expected": "00000000000000000000000000000000156d8823c37c81d8f03c0b2e61a2342aab6e6c9db36cadc9eb741e085de711e9fda08ca78f21753c4fdd8cec059b6c2800000000000000000000000000000000064d4fc2584c78f1e92f808d4457070b0470eb8de9d558885bba8b03efd8d8e195e4923d8e3382481a0ecee905371ae10000000000000000000000000000000008f1dc4d2ba12e7e3e1b0ef3855df4dbf29468bc99d5cb29fa3058a535af2ba038396bccaa238bba6d538498565c2809000000000000000000000000000000000fc9839b6ee876f7846b5086d487360b8faf133b6f5bd2dbc92a7fe2261b91b15aef8d90c227cd5f8ec05e32d807e022",
+    "Name": "matter_fp2_to_g2_34",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2",
+    "Expected": "000000000000000000000000000000000d40f1c25dd57e36ed305276d4505cb250d2d9da0d5b954fe5e396b2c17a5399613243216586cedb19340e80f898873800000000000000000000000000000000063367c4a622fc925319fc6d119d8592f40f126ae05eed86ee5e4f6707b1d234c747e698c40f292dcb82ac5fe74ea80c00000000000000000000000000000000199ddbb5d4b6cd0fb9225a72c53f4596cf2597de63da56f4a9a18be8321a982de17367b0f3d794fa799657dd8ca10c5f000000000000000000000000000000000f1ed84e4fd958547d40cd2dbf16e2da4cb6d0d02763441067221890ae27ea1f689c26c900b695464ededf083667146d",
+    "Name": "matter_fp2_to_g2_35",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b2990000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf",
+    "Expected": "000000000000000000000000000000000ee446310185ce76e31c13e4ca6c43166d971d9b9c539c7d0e8dd8ebbbdd9249922cb674bf6ad6840c203a5e208911fc00000000000000000000000000000000037344752896cff03bc39a9d09757a83c15fbd90f8bc1d8d58dca9b23bc00fa2b0f3f0bd7c9ed857d285825d40afde450000000000000000000000000000000003ef77f0220d1caa7538ecaef1ae2924ac1a180f11004034fc118aeac464fe1ce684b5fc90dae3370e3f79619889f3d7000000000000000000000000000000000fdfa434e7bedec071a1a333088d06299f55735f085a1e907a1c71c312bbb8d27ffa7de7ac69d421ebd675c4afd37594",
+    "Name": "matter_fp2_to_g2_36",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec43329200000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036",
+    "Expected": "0000000000000000000000000000000000ac465de3832452edcead434729be73be90785158617b5ec3ad53b12653e43721eda7de6742dc51d4d4bb58a291999f00000000000000000000000000000000147c39a5c162afa1f8eef400cfa1bdbe5436bc59d93973f50384022962f828ac934a4f88ab7c3d505b0bc3bb002f5efe00000000000000000000000000000000141bcdad53845a7eb2ec08189a55445059dad24ae5d39fedce869791aa28459f05a6cdf9575676cc6f3dd7d6faf077240000000000000000000000000000000010e9f539a9ced860661472f53147d0347927f065ec09bc32e00c5bc157b07f8b41b05aa4e0eedd1f73c7a287b2d0e5ab",
+    "Name": "matter_fp2_to_g2_37",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d700000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e",
+    "Expected": "0000000000000000000000000000000016479eca30f48bfdaba4c8afca63ddbf59fe3367b2d3c17d15a5869dd2956fc67ebde964530926598cdcb62cfc993d32000000000000000000000000000000000650b4fd24ffbb953ccdb1b112799149d29e2377ee233b9ac97f4db432da63c98b8aad751f6060d04fe1f9262b75fca50000000000000000000000000000000004568dc0b9b430596f2fa59291ea6f923d552683ab9ab93000788145cd7c468c5576efd981c9ecee2ee0c16eca1ecdbe00000000000000000000000000000000154af1490463930d6b8261aa1d066eeda6d65b742cb53c65348e5cd766d86982a1489ad191d1b126233f193d24823b9c",
+    "Name": "matter_fp2_to_g2_38",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b60000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee",
+    "Expected": "0000000000000000000000000000000000df692ca763a74877352af3609c8cdbc184eb71bd35fd86334cb88543637b40b3adbb5802dcd7b88f4d722b566aba7700000000000000000000000000000000181495e709d1617f2d912f43487ad3920ac5f8e47395ec4b58bcf0b2d986c674a0c7838830a039bfb5bb59cd2fee2f5c000000000000000000000000000000000d20b482dd8aad583bd5d08ba9c61b3e954f022d48f9f4f62ddc9f5015ac71dab7d206b1d8b885d5e605519bd33d93a20000000000000000000000000000000010d3deccb9364ee386eb35c7117bab373a76d024627b8a031f96465d5f75b029fa992e29ad4a170c4473cd1df585429b",
+    "Name": "matter_fp2_to_g2_39",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38",
+    "Expected": "000000000000000000000000000000000a007c914ed40c7f2719fc70def0d4752cbaa775cedae9365c5afb61a5e1a2854f9e1ce19af9fc85bfbfd2c33f5bf095000000000000000000000000000000000d85b0d173c25c2915fee429d2468a9eae01ba43c0f1a661f2ef83c1acd726865c00c40ccbc3aae306f93074e5e7858e000000000000000000000000000000000b3df302ec532c8100c121c9a3455392c713ec60de1f9572b040b0966f8ffb888e8cd768dcf6d63d4835a52d13a730c0000000000000000000000000000000001123c43dda8717d03fbc02fa53c4b1c9a931db6b274162cfb02ef5eec602bd8161dedc37c7f6217c8e82236f06e49e2e",
+    "Name": "matter_fp2_to_g2_40",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e60000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d",
+    "Expected": "0000000000000000000000000000000016d8d9b1b59a22fd830f88b9850576488f75672a87ccb766e52da77f187a8e66071130c7e71f86675f8379b2a8802c4b000000000000000000000000000000000aa4ca84aa23f01ec536ffa25c4b7a6c822f588bc75a4a72ed9237c0588ab892c8474a0f23afc7ff0dbc3b08f8e35b60000000000000000000000000000000001425e759e2537d9e5f0f356ff1d38128eff3a771fa661a839f7a8d0f548347438574ef7d592cd4273ef9b7269c9c5d7f0000000000000000000000000000000012cf1c67d1ce244ae22eec0bf4a400a0f356b9dd075d87a6e61941933872d7c0e42c1d238b2c1704d2cdb2df75169f39",
+    "Name": "matter_fp2_to_g2_41",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7",
+    "Expected": "0000000000000000000000000000000017f6e2743cb30fb93816d0dc802c24509315363c3652b0244e1395cb9200efb4d7b9fa7642e8d165d28a00740f1a83be000000000000000000000000000000001483644fffd3989ac98cea71843e87b8e446a3d497630419afe99b3f1729a831fa6a49bf763b0c410cfc5390ac4ac1db0000000000000000000000000000000018ad20ae5012266d771b2c86f891f498c2e90a7df19561be240319edc1fbfb316948fb3f8a6b0e3720676b076eb372e10000000000000000000000000000000012f404211899d8fc1221ab5b82db9042ad37e63348871e5ac6cdbddacda0a564888f89d22712069b6096b58c5935edd2",
+    "Name": "matter_fp2_to_g2_42",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1",
+    "Expected": "000000000000000000000000000000000d4d78f992f12aefb0e3a6b18fbe2411108327a9befe4a822618fecca4def3169972b4f1fb254cc4656a676529d554ad00000000000000000000000000000000145ef33250240a5c9434d4b2cf2404d9e7cc51b55e482ebc6a8aed85caa21ed00623b3cb2d76ce2d96b2f346d395dfc40000000000000000000000000000000011af2ee2514c58078da335c0273cd18b98d1ac6f0e67890677403f71b0e06863fc72611c0cfba39ac894ae500edbdbae00000000000000000000000000000000186863e7c24cbeb45f7a66b5dddc9b57c7e22c5139aa6bdb82e77cd8182bb8d2fb7bddd7d3516b5422f92e08d02606b5",
+    "Name": "matter_fp2_to_g2_43",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122",
+    "Expected": "0000000000000000000000000000000002af4a301e90c71eb375110e7fe23f8f05e2ede86b1a9b240e8d1d4d70e96f1dc3640fca7ebbcde9918deb91f3592de600000000000000000000000000000000058b5f36cfb6b0adb14b397dee4c3769c7446426eb5719aef4965cde2dcb70e6f2fa60101a5f03517c0040093453d092000000000000000000000000000000000f77b560469cd42c5cf3458ae13020c6678af3cddf9bc559372d12bc5d6b930795e1eb09f27cfdb8215f39fb2a11b30c0000000000000000000000000000000003308985946c742af7bd7d29abc2517ff1d225607b5f11fc66695cefabd8f25e294ebdb7339949d6bc4d98db19533966",
+    "Name": "matter_fp2_to_g2_44",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f",
+    "Expected": "00000000000000000000000000000000093c936d57135b25900bd5dd55cd579aa8b85b9c1b5e8dac6196c4450b624734d9bfc3fda499cedf2e877d79f2da650b000000000000000000000000000000001832306d3ac1c1c61bdaa73c9b6e9c2ccb484c3baa1de6a217a2884c72b72618e864f75fcc2dfaca358181ecbd3347980000000000000000000000000000000002b2e5ff1ee02657fa88c7d6f23cd4c0465152a9daad8479b4b68c97930acb22e4e2eb0011ec4062b8ec46991a7cc630000000000000000000000000000000000712543547e9d24cc78d1c2e3fbe0b51222185f4c6e513256d1ee066ba50beee20321bfd60462e2587c375a0e9395715",
+    "Name": "matter_fp2_to_g2_45",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb",
+    "Expected": "000000000000000000000000000000000f52e2f8dff9a93b2985d5c2b8b980e4869af53ce55aa48bc1c9295e557e3b5ff78896e5e6342c2d535d18b11950bf390000000000000000000000000000000013d36cf2805d350c5b748e639d20e592deb4c5bcde99a94fb539dc56d48a862151b925314f21dce4c9130b32e44f54060000000000000000000000000000000017728f485d881b861f626c9de8b3df7d807b266de6cf8dfcba262f40a6248fb5e6506d11e88f460f0b5f1a1907ae5f3e000000000000000000000000000000000c0ab998f63f861c82106dc3ed5ea11a16e98139e8686f8442047a1cf9ac48c3d34b5129263767830144e9a13d4a1f44",
+    "Name": "matter_fp2_to_g2_46",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c94360000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba",
+    "Expected": "0000000000000000000000000000000003463d887c4d0aaa21acaa308d77f2c7e13d10157efa9ec3fb1586a8db5ff1a9e807c91c86afc4df34c9fcf06e8561d700000000000000000000000000000000128a81efb9f30ed811ea3163c71b6a46ba2cbdbd3a9f93cb8d0f518747cc860431c6e93bdcdf36d00f83838965da4b50000000000000000000000000000000001777802b7c41111b38da3fd8092c280b4925827b2c1592f779a4ddca71f8268858855c413fd5c0057a652155261d75ba000000000000000000000000000000000c88b522d6dc2000cfbb7052e141ddfe15c6cd7fddc970edc4afc36fc59e7f8e31415706a8121e8e84348be0b50d0d88",
+    "Name": "matter_fp2_to_g2_47",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c2900000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4",
+    "Expected": "0000000000000000000000000000000014be96cfc0dbe09155ac8d8233b71ed584153e279b2b2be88471eb653aa4913fd2c33947547c61f7fd8bedbb552a8b1b00000000000000000000000000000000146b9a0011260e2646920894cf405bdebb101db12da7849b30868655fb5f972113cdf2fc322cc246d3dbd9f20b98fe2f00000000000000000000000000000000104bc20e104da5173dcff3e195f80960819a0d64e922bb484c2739c4b7c22535f7faeb1c85188aa853277740b389eac90000000000000000000000000000000019f5aec599f9ec286aefe48eedca3f929ac6c758c231182b92dc965d6ac1f3db53d93f57d733ca8425a5dde070b0dfa8",
+    "Name": "matter_fp2_to_g2_48",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa10000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79",
+    "Expected": "0000000000000000000000000000000004ca0149527817b4df0f08acabd4e8c6329c0d1bd9f2e8211cbea25d69b84009ef158c770f948fd67e4609ccadc938680000000000000000000000000000000004101b351e2a9d34042291f38a289d8575872104bcf76f60bf888c60cca5101c34c247da30f7a8db4f0cf2f32abd302c00000000000000000000000000000000167e668de3207ddc60b8a5d5d246bf2f63ceae3bcbc4309e73eebf4d4234c2785bb13e4d5d8fff9c5f205e4fb942a2f6000000000000000000000000000000000491b965ed005065abdac53e3065781f2fd23f6159debc64f01c9f62073c651da33c05ed84617efcb5ffe08ce05e3b2c",
+    "Name": "matter_fp2_to_g2_49",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f2dd27e3f0ab503a8752c0802ee14c655271e8cfbc734905b4331fb4e70cdfe291ff71053fbaf91680b1dd108f458f000000000000000000000000000000000c62014b7694a3e81370761e0adcc32430547a1bbe33746637e7762dc24f8d04b4bb955f17ca901659482c622d777642",
+    "Expected": "000000000000000000000000000000001541320fb6f8a8c3c67278a7ad05ae7927d3555ad562bc8addb54c6693c51fb1c7355d2e74ff10f6bc3eb182d8f5b88b00000000000000000000000000000000172b65b110935b116ee683c8680ef0a660afdee43b9b8fce08ef3a70b352f8710c06b820348c338fb903a165cc5376da000000000000000000000000000000000df529b0e274e2e8993dd89ffef487aff23d31f502a19dd7d383de08fc77f1308a59ac5bf7cc899e81d377b2422187850000000000000000000000000000000010b40c9063d174b358637ab710d15c80d9230a1b3a056cfac4d583ad8c5b79c3d9bf22a1b0a4e0f629cd09ff7586f886",
+    "Name": "matter_fp2_to_g2_50",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014d1491a45b4b0914a6cb2e4dc7de9d0962f5c175cd571057cae1e17d2c943954d119690ea14f5815f858d277a9ad828000000000000000000000000000000001650771e0f7b33d235f229b7d49a7a5a0f00f78e5f4abaa70f39ec452370198a8532b5873e41f17c449f9c565e6adea5",
+    "Expected": "000000000000000000000000000000000978ff68d94d33703488298658cf2c1b6034d3d8d21c175d71a0545bc2f99eaaf131f061f3e4f55622668e686e691f53000000000000000000000000000000001124804b252f8187178435761897d00c43cf67b588ca69f97c20b0ffad3ed94acc2c0f85f900713dd6ee9f38e5ca94490000000000000000000000000000000010ca2a8ce71b9a096c132c4a060a17365475b6556d4fc6284266ae787e217b3ceaa3a32bdf751375eaf6ab49800132fd000000000000000000000000000000000a43b435b116d9480497f6b2e1bb377550cb1a7ad59e4214bffacd517afc6b7bf91112fe57b17a02a86876ea07361bca",
+    "Name": "matter_fp2_to_g2_51",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aeb244909654b3e1df7cbeccf297223be57c2f514474edf0740dff48dcd5898b6e49eb65c787aa56ef79778249f4e07000000000000000000000000000000001007c89a66dab07f54313db8682f9e829baea229b030b4514d9c93686747207939c50a198e83ac2cf50315e02642a24f",
+    "Expected": "000000000000000000000000000000000c3d87b1b78fab65cfc853304c682b39b6ec2b4ed005e9108f69daee5aecbd586c9818c37cdee865ba53eab9302320ce00000000000000000000000000000000062a7203cd2fd04a957cac8b6b6bb51e635ed7165c547ace10f93a32b7f37747a2e63d5767d966684409a6c748d4ee6c000000000000000000000000000000000526b44af8157dd68725aa8743684e020c1e385af7413c9dcebb320568663d18b6f29edea26f2628358852b794ffcc8e00000000000000000000000000000000098126f486ff55c21f64421e85b09a1b54f42d3499dc0e198db6f3bf7dd8476cad97c02b5b366e5ea20d8f83cc223f7c",
+    "Name": "matter_fp2_to_g2_52",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000398d86b5206bae4ceef0bcc6335b1f6bf5d17863ef3a5e8463aaa69d9f73f8227263964659d4b770d6d9813f9399b9d00000000000000000000000000000000096bd18be1176e16a0d80e60f7d7ec9d3b6162f683440e3cde70082a73605da3783c8a058bf76d7e25056f5cd95c31ed",
+    "Expected": "000000000000000000000000000000000f3e76e7d1cadfaad08d16457b02d89c40c157225eec7916d306faca8dbda008f41792888c647dff1acb4d4ba3b43c4900000000000000000000000000000000132bf730456e2afe745a58cdee689e37223292bf682d5b7dafa7df99e40d385559d0b3161bdda0bf5173c43ee46412dd00000000000000000000000000000000141b36ff6890e35db0054358bc0731b3aa0efac1a247a51daeff3515746456216975f44769174a4be41c109d35e4be33000000000000000000000000000000000ca401ee1addff8fe87b600e057ae34ba297886f92c5be8a8c00b360ada71831e31bc4ea1c309c7da31cb28d1011ecad",
+    "Name": "matter_fp2_to_g2_53",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004ca5cb60c32edfa385baa911ccb7fd1f383824c22b945944b0f3f7011db8c123efd8fa70e4fe699d40c6716021f0151000000000000000000000000000000001339adb0dd8d83574c2008f0a7ed001b0808d2fb639b5e57e1d293884247d5c66c948ecc60caeea7bf440a3a44ed296d",
+    "Expected": "0000000000000000000000000000000009d0af77517b654ad97de3ee1dbf69ec1eee901facd0f8c39b4af393d0e63957292a7529b461f7fa58909acad32ba3a2000000000000000000000000000000000fda17cd878ec0f8c294daec1bd1d56c63e875b002a81c9c41146dbb564bab6e4eae2717c9fd718af1ba816a1526e8fa0000000000000000000000000000000017563b7ff22b50b6d9e24b1e0d89ca5c72e68d4d3cc24cce36856191111d087c3dfb392070462dc7850ef5a1422931c600000000000000000000000000000000020001fcff638504055ba35230b360e6d3cb5777b959c194d6f9b038b58d3ead0b82b28bb215378abd85d357b85ea260",
+    "Name": "matter_fp2_to_g2_54",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000089211892a61202b1ad3a85aab9f08f8d028f3e3deb16c1de4d62c1a403fa63c6dbbdf8cec37f0a9d6f346b1c7ee179d0000000000000000000000000000000012a9fc2070b326f4d7e64804b3a2e977f4bb36b6a4afcf27252af757d8535e8172a99dc909fad5a3ff8df23d6d6c5948",
+    "Expected": "0000000000000000000000000000000000d51c77c2443f00d965c0d7ec9b5a8a8003c2a77b0ffce3e47bcb55420e8690a9c2ba9235b62a4b351d79d216a3aad40000000000000000000000000000000013cd46e3ee6cbb3bfb771ee30b5f5faf0a64a9efa1f8fc57024c83ad07a9b25e513f211ea604cfdf319dc42bf4c067d300000000000000000000000000000000009fbe1fffc67220067c948e0c80de23795e045fbe8031c9010eaa69356ffd8e5741cfe12731ec13aa236630f1b1dab4000000000000000000000000000000000e5ecdf808d10d47f041e4b078e79b32520ce9623b50059a3bd8b59daebf9103c31425659ecbaebfb2384d1c2f1b400d",
+    "Name": "matter_fp2_to_g2_55",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b37365748fdb21fcb46f94edf86c586f17d0e042c4683e68c6cb83e7c0ed2c30ed260c15af2c9dce77bb705debfa7590000000000000000000000000000000010d7c02c6c1ba3cf6ac09a05dfe043905e1a7eb32b67f2e8a5dfe82eaca66ef46cce43aaadeff58ca85345dd0d3bf3cb",
+    "Expected": "000000000000000000000000000000000f3e4d2559261829c0f4816f8b571170de1f74d75d74997cba56fdad42932db73504691f9e001f5b4604705a8c1a38e40000000000000000000000000000000018c72136bc7d3050ee693270668e706ebf70f990e447ecc6153a10625cccc9deaf5ae82d2a656b1376bf33b1c1fdc2c9000000000000000000000000000000001754f2725bfa76e92a74ad5b520ec2aa82a1f86e8623a054ebba489adfc9e71d1f14d4692ff9fdd8acc3d768b67e1b7000000000000000000000000000000000096f1373434a8822569cba0679dbd2abf619bd9a8c73e54e078688d4e2615d45431ac8cf3da5e15a83fe77d14b339e49",
+    "Name": "matter_fp2_to_g2_56",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000aeee59421c8ee65f8070b9d036e6bacb39dd2537d02960a3a57da4f0985cc7b27784d60fc1613f5a83c34d2250395c1000000000000000000000000000000001715ddcbaed0a05b38b1c820724405a713cc0215a4c497892f00746c0f9af28b440a3686178d9bfcd41944a224311306",
+    "Expected": "0000000000000000000000000000000018d515b8c99f541c7dd448c3564c1909b84517b662d6a2d1176d3bf5e70abc0a2995c73ae3f1614bfed2f64229e173e80000000000000000000000000000000012126ab671420933cc4fa9206311200cc5241ca3eec54f5d97a426a72642bdde32a65c79735446779cd1744d112d544100000000000000000000000000000000190d836312ffb0d6bf493f4c942263922659abec46ac4de639efc311753148b445509f808c2fd813729b1bd96e0e663f0000000000000000000000000000000006494f9a451460ac658ec17710bef79d59b6e0fca049804c0954c5fc472bbef520f75d34408ccc62cf2da3deeb79acc2",
+    "Name": "matter_fp2_to_g2_57",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ca4b3e1a8351057ba4a2ffaf0cdf1c3c0717ccfe26433f6c40e2cc29e32ed884f63d25979580fb555a5a86c9147bcb00000000000000000000000000000000010c1db593af38aa14ca9dd588f54b219ff1fc9edd25b3d16c595662ffa7939879244326b14d978e0dfdd25e37776964c",
+    "Expected": "00000000000000000000000000000000173fa567aa952bfaa9a60b8232a185475cbb36761ebef49ea5fce900a06043d0e2c1b6024e40eadc9f4bf04b077201450000000000000000000000000000000010fdc32ff84f79fe39351cee1ed6b67dbcf2956020e2518d5bb5b367b61f86f1bce36f75516d9551d74cc3a567e6c2be0000000000000000000000000000000007abdff8a8967eccc4de6b4ce142173841c0e8399f5a67dcf0f7b5e5b4133391b44bf4d41d3ae3426839b19aa4c5d40c000000000000000000000000000000000c99f160062566418c09f10eb80f005f2c8c12825435f354f1d65bec0322e9b8ee968c009a84ba792a7ee7334b32bb3d",
+    "Name": "matter_fp2_to_g2_58",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017cd94e7e672f0dba9a3c1db742d87cb18b9401e8311c4badc24f811a8f40c27942da6485807701c1a12da58076c756b0000000000000000000000000000000012f6de4ac9883e78f9d658cede4c70b44bac6b4c9734cbf24298ddf0df0cf54164aca245d8e313be4aca66ba3cab5d70",
+    "Expected": "0000000000000000000000000000000019dc92f1da66d0855ebc8e7a2ddec623a2f843a97c7385364a631671be7ee3387a0f98940b5a51c8d9e23eb27e3133b00000000000000000000000000000000008493903c5c68b2847869b8c3b0fa9b8ba15bf1f11a40a29e6e82942e2910901044254cc8e8c3c3bf56e1f1b6dab7e86000000000000000000000000000000000bd3c1e302a191094059a6493e59a11ab05a49faf333f36f7680ec9b1043e59dfd7f0fabe9f334b97cd638dbb8bb664b00000000000000000000000000000000141c9b07ff33b6ab55b320dda6be54320082f0057c446236cf3d3a51e674c26a5241f2c702d9989adbae9045942eeab6",
+    "Name": "matter_fp2_to_g2_59",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001b2843d9852feae3145b242cd0999877c07785bc72cc2626f388dca32edfb112bb90f9aefd6953eb15a0babe748573d000000000000000000000000000000000a69bfe809a67ee853cb96b5a7a72798748cda56936e5664d509001544539730f57a7541ecd2396c9225818b9dbfa3c6",
+    "Expected": "000000000000000000000000000000000d0922466c358cfd756727e134b5e64d211244587e4eea036f0959e78570dce3ee264c703cc356cde20637c7560369340000000000000000000000000000000011a66d618f79fb662ac2b2d3b50750a5567e36d7092dfcc72d8f340c04df75ecc0ce4a01b410ea775dc548b8dc66c3d8000000000000000000000000000000000cc49cf4be5e2df6b43054092afa2d6acd66f5a43ef0667f6a2d660beb7fec70558ce02d7acbcd090df91fe833326718000000000000000000000000000000001270b0519db083f903a3dbe0b1b1bd5ce0b0059ea2c2c50335dd80b4bf154fc23a3de1ea753b0e279145254d8e5bd045",
+    "Name": "matter_fp2_to_g2_60",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc7",
+    "Expected": "0000000000000000000000000000000007826398b4ec35ab58ba9fda5c15ada2a41d3854677172ef6a4a54087b64d0f73fc875ad62236eb7fdcbd94f14c8895b0000000000000000000000000000000016b14fa92de5f6e43988829ea2f851746efd6680b0ea1283264f803c8ffbe85a343bdd42225caefd1b94b8b311d2f4950000000000000000000000000000000018797093ff82bc10e6db60b1da50b9a60da01d67673e9bee8c7af2bfa2d57f409f7b06f53944938e5c73b049c2d3c6500000000000000000000000000000000000c66dcc3d30f35c21b8a9369c8f6de28af404e8b30d3c9a7f09c461b0272ba6d5a29e716012536dbeac1d9672af8427",
+    "Name": "matter_fp2_to_g2_61",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e6fcc48312831b910e52aebbf19869b3b32f05db8310b68905bb244ab784df5732db2e72183de5d231d803d92aacff9000000000000000000000000000000000f61f9e52fe3afc2a6bf12e420cebf83bc55a449d6a167779e5b6ba3281c51d790a045643aa75f2516eaf6ae2a816ac4",
+    "Expected": "00000000000000000000000000000000191aacce60a1a83f2c453fe196bbe5839a3a1178b147580435f7de8a2b0b4f65b3e280ac7a67570aba0fdbce6c11ad9700000000000000000000000000000000075ddd6b256f53a6ae6758a5158508540aa99b78ca069378f0ae3f5621ec24b9acff1f9b61d378334a63682a33fb0561000000000000000000000000000000000b06e11c9f858446fcc90c69d05cc26c33bafed0feda19adbd838c9c24bbf567b673110a1b248d0ee97fc682e561298e0000000000000000000000000000000018c75dc203493e12e1523af50f85ed648130ce5d3e9757f713850c867cc95c7acbb66c9733dc4f53d6a0e64bfaad5832",
+    "Name": "matter_fp2_to_g2_62",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018efc6d366d79a09b7d56c81c17c2eec2ef7395fdb5991f41273329cdcf4537d342bddd83c3994a40d5c18f6afa054c600000000000000000000000000000000127021ce28627a9d6a492720f728acef3b38c12b951f70a932c7fc0ce3f5b6c80783351cec55d7d1bc4ab964bb4913b2",
+    "Expected": "0000000000000000000000000000000012931f51430bea6e96f8ec456ce6b3c9e058b0bd3bbfbfe8b6e84fd6110c3bbbe0001018064e8981797f9c93713a0e4400000000000000000000000000000000196b6093dd2276098853ef2bfac84f0cad06b67a12484e98915dcc756310b818d8136954de1b602eb825ab29a143cf4b0000000000000000000000000000000008284beaa877b25374571dccb218c401cd905b351dd96700853f01920e409d11c4e440e90dc175cdf0fa807cb9d1e93a00000000000000000000000000000000063c6c238485c291fbb60bd2824154a9e23dea374292966d271ae94875391b7ceeee813e3fb9504223bb86f0ea3b6cb4",
+    "Name": "matter_fp2_to_g2_63",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a0277228ab4e880c12f957a6fcdfe49e2155083f3f93d3f00c68622415cd1f5bae183b7df9e08328a8139392772cdc6000000000000000000000000000000000de0ab426e56029790a5ff72f34da97e11c028dc5d31e448c49ede004102804d2bcc36d509640247a4c8bfdf5104a781",
+    "Expected": "0000000000000000000000000000000000f7bd0705cc4ea96ca38314cb85963044164b83a506ffeaea6e5eb8f7c4967cab1f1658f33b5435191427aaf9605bbb0000000000000000000000000000000007a93e2a5c118aff6ceaf2370ddad52a82854946ae595d384ee0b2b4935a574ba758736d84b0ae792f998ec6a707dfbe00000000000000000000000000000000090936add00fe5c7556610b28ecb4466ffc37b95b5cab43e072a585920b3cbe70faad01ef75d1dcb4f7d00d900bd99600000000000000000000000000000000006ae82539c68b7af3143e23229fe320924472c2b3e15a2e27e94cba674d30f083dce94706da094435c53285a43f89e56",
+    "Name": "matter_fp2_to_g2_64",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000170b243c5aa49a0134bf3d6494cc1e55a1c6ebefc6117eca3b343a48ef0a2b077c443ec5b9201b198df015a38e66b7910000000000000000000000000000000019a8ac8a3be1d45318801bb0a654963b312540d24aafec46bb7661cebeec27b0b345275fd53c041d02b1ebfa27fc3854",
+    "Expected": "00000000000000000000000000000000024c1b869fc13191b71d7159a07e869f1b13c11c73231b82e9bd0a7b4c32d7b376fb73d54f7231dd4974713179f235140000000000000000000000000000000012b9f95af661e8452aa5026302a7c28695307f75e9e4e32365caf378ed394fcecc831a3c47b443172188f4d18338fa75000000000000000000000000000000000f52675fb4d112d1d39ff953a253b22dfa0b73d972e756ea7fb673bf87aa992883c5baf32be6f50f880b03dcb740f06c0000000000000000000000000000000008b57726e17c873e12834dc291cff6bd95307f50e7b1d0caebd8c1eeb6eff4acc0520b135bc8e35a257133b7dc640db2",
+    "Name": "matter_fp2_to_g2_65",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000fbbd5a10eeb2f358f2b167f1985d4084c4b12accb1520d780ef1c52f6fa80e97aaf190e7a7b241ef96fe8289fc0a9600000000000000000000000000000000155687114e7aa786ba27aeada830fc705aed069c4e3a07e88d7f33923319f416ff3caf6533cbb36e5bbb1b93a191bfd0",
+    "Expected": "00000000000000000000000000000000061938df3365bf910884ccbd74d3cea7c30416bddc1a9b65e7723c15d89aa657da36a45fe10ed50bfa0c2769bb98aa2b0000000000000000000000000000000007b3981054255715826cf8f247210521ac681305aad3928b69804117fc143c5101383eab7017127c8452a79003a857d60000000000000000000000000000000004c745113480fd87212ed3ff30ba43c8716b32e62c1f0091bde53bd4a8fa8fe6bbcf0904144f4791ed1bf12dffa1f17a000000000000000000000000000000001237ba297c7f69e5e240846a12d86c8276a9a6ceb4af977edadc7ebfba3ad3f4ecc0b875da0ea578c83fc3b91f9f31a5",
+    "Name": "matter_fp2_to_g2_66",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000115edef357ccc3432226d9bad796a42b1a278d9c8adfdddc5a0f8a36d32ea1787183877f8b7dfab71424cdd10b63441a0000000000000000000000000000000014b369ce61abe60d942346e049644b95a0fda96316446b2fe7ee427641af52fdd2a654bf125ff6c8c7f3dec98f6cbfb9",
+    "Expected": "000000000000000000000000000000000a0cc3e328b4cfd01afe53dbf971ad78fc74d951050d76210e4c84438109622f0531747e762e185e3d7ecb9faa7c3255000000000000000000000000000000000622ad6092caa727d069b8921f4124d5996f3019705a908ef95d23092c5bb148873a22c227aa25ebee361d4184cc38a10000000000000000000000000000000002938d2ff50cffaab8c056c2844c50013f5bcdbb4f91b3f823836edabb39ba17ed1b8b5862301efad04bd2f5d5bf599b00000000000000000000000000000000072e96136afebbf8c06a37cf9b20c85ef8cb3f7f99d5c71b05a187c193711e5b76f52863c7ef080a1b64b2120ab2ed84",
+    "Name": "matter_fp2_to_g2_67",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d22b7b36ac66b10adb4570f8e7521ed76de2df2a7b94b2d0b9ee4514cdff6fa7c74854d16e7e70f054a91df93c7ebaf0000000000000000000000000000000016867c9cba66dd9f1d0332d31c4e46f8e393eeeeb19af7e6e01effb29ad999b3086b599ee4b371de557d4fafd5da3794",
+    "Expected": "00000000000000000000000000000000142ceeefa9fceb903b25d4dc68f6755833d7529752db0f125f7f65f2b7aeea8c90e599ac409576e82f7b9d6f83c43aa0000000000000000000000000000000001664acd89b482aed04ef40bd4d1ff9f39c80d7738771e2b3ca731af01aa230d865869cb05d83992e94ad99549fd0b8550000000000000000000000000000000013d6ace9b492c014d9a7504b5abe442e3bba13b1ada454aa53177990ec44f616e091f1382d36db87b7e794c11570a9bf00000000000000000000000000000000081b7a8a2906435f8a9242f573225ea62c5429e903bebda9fe9973a18ed2682185d72aaa6584b9848d1cc45ac907dd27",
+    "Name": "matter_fp2_to_g2_68",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000db9258e1257e659e60bf8569ea90c8247a53a1d1eb958481623447a38d0f1f1686c3e40c8f15bd06cf5be9c02485452000000000000000000000000000000000517c87f3df032ff08d960f524939e66f7fa69b4168b0f2507baf7d7231a70dc5690a02d317b26f219365ac3255bee78",
+    "Expected": "000000000000000000000000000000001182e4230f0c360c07913349f89f8436c01841c9615348a0d7057336c7483342024b0369ae52f39d4582f9885f552b5d000000000000000000000000000000000d15433ed130163a85f8ba87468c906aba88ef8610fcc1a8d6b3308cda29907acca351fd7fb19799184f1ad91c751b5e00000000000000000000000000000000111089005c4c5370863b0ea6b629197a865f978f71becb741f50f9b4e49b13162ca63c29aa26287faa9c923f57f4ad4c000000000000000000000000000000000dce405ed2a79ad433123105ad01a26ee85d1ba4e5f3b4e0339fea787058c06e9a6b10f5ec8f6eeb85b211e18b6ea076",
+    "Name": "matter_fp2_to_g2_69",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000b6573c743989fc8613d4ea09c2e500ce965b50cf0c8975ff703116464082efff4b42828c8337809f6938d7cdd3f66e000000000000000000000000000000000896d316629c80ce6e5f240535863b9e064728665c0815f39b21675c236f6995e7dfff1e4aec9ad05861e2122469ea56",
+    "Expected": "000000000000000000000000000000001694cb615d2994a903a13645ad44a63395320f286503902b6009e7c795dc8f024260e0c45bedd864edc9fcb9d1ca6bc1000000000000000000000000000000000f20538af015bd6d213f90fb1a1ebde4d9e2ab2defaf80d791a1f70af2ca7ea1598d43e9eef1cc982f468cf15d223c9d00000000000000000000000000000000046c62bec4c6876a67f5fe68107d677db8fa4d59ac0cb7afe6e706864c6e94744bedac6b34a68e8ebf89c231307b86d3000000000000000000000000000000001839f3b8a6dd8fe8028247670fe5b491bb43ea8fda53116dca87f97da96573a5e701a703fb5fa7bca457ef88a827e061",
+    "Name": "matter_fp2_to_g2_70",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011fd2ccf6883b78fe19cfe7beded503cdbe1cd5dc9ee452aa6b329d2237c2529df6774334b132cfeaa616f20335f88680000000000000000000000000000000009eacceef036ec500e7676f54af703016fac93d69ed19c8943b64ffed2db498b19cd255a0a3437b568eade0f497c7b17",
+    "Expected": "0000000000000000000000000000000009d8725eb8757828a94969ebf40545a62835686897d4504a66484a3078b2f15e39fe918d8dc01bc7560dcb005a7a0dbb000000000000000000000000000000000954a6cc9b2dedca1cf280f72fd0625184b8f83b78ee1ffcaf0f9178ce97900d759e4a74b914c3ddc32f84c3f4c3a8d60000000000000000000000000000000014121b83d2a06390ce7359e570e1593d5ff097cb0e44c38bc74171fbd8a8da0dfffcc2bcb95fb2d80a55933f696a86cb0000000000000000000000000000000016f71d24256de70618a02b0f016c6f31a21d2cc42855886ba30176584a028c2e12367be19b834bf41356cdab21223314",
+    "Name": "matter_fp2_to_g2_71",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004a851380536054f6b69ef7581b57dfd753d1e6201069bd1218ae5766aada087b4b08f65d43b3ce0215640e8d34633310000000000000000000000000000000013579671b64f2d9a2c3ac2737cf95c2148acce3dcecb3db6d019730010c50d1c0504ba4ed42d93771ba296b0b07487d7",
+    "Expected": "000000000000000000000000000000000cd47f0982904ccaf4f3cdaa37091a08e67a5f04af09033b864631300bb6c2aacbad105eca6ddf68a643976fb555d3d80000000000000000000000000000000012332ddb0e91f0ef9e085f21634c6d69576e60d3d24732a0c91a560906791f60f79d09ac0ebf448bd39f047b1dd428450000000000000000000000000000000000a756a869b3cbc5624f0e08019170beda35fd2642a79108b284a503942f8267b75868636302e5a12b4f1505331b15f9000000000000000000000000000000000f60724f6c8200edff41f3299ca003e9ea03b97b01a3e8c63763bdf67b9f7677331a7144915312458c40d041be97b3c8",
+    "Name": "matter_fp2_to_g2_72",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000021dc1dedded9b0dd90afa9ab7fa8f9c33930fe4ae68185ea4cce9ed97ce4cc9ff93f96377b11f8d42b02e759a10b06200000000000000000000000000000000034c963fda3bb80043d6d7887661ad59b3c31c88c958b451a8e11684770c132205db6655ad7cbd604ecc3225b0c128b0",
+    "Expected": "00000000000000000000000000000000095cd509e53f10b1ee18b2120e2d18f0905a202a992a9c62480beb6588275fc8b5b151e6abf17a12b6d9cd03a8b37a59000000000000000000000000000000001723bf1a3d79935eb4b39f7feaa1e05cd8f3e7a32e2c406625053d8d8fde33eefec231ee00adb00b0acac16a83dc77fb0000000000000000000000000000000004af528e886dad3f9fa7232605936bc22a6a22622828367791920ec9d31cdb2f290e37f5fc79efaeaf96c86b3f6e39220000000000000000000000000000000015bada14a84fdb09b77397cd2e27836f9f88854924af0cafc6f9125d32be848c8325a3eee1a26de8be8eb80b601f1ad5",
+    "Name": "matter_fp2_to_g2_73",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003e8d1be04f8dbe5c7e1c7553cde8355ae16d26c819dea92fb543cbd9fe9e726359e1e4be0483a7720343f34c6a3fb9200000000000000000000000000000000062bc5fdae812802bdea09e4130c3d9bf80c7518138b116a4b6a302c155b97226a6ccc8a3ace18744e7adece08781f72",
+    "Expected": "000000000000000000000000000000000d8f14042f36bb377655b63dbc37c50e0eb5775d4e4399972a6758cdfa9751cb4b733745ed1a47fe5f2cc434efc5af81000000000000000000000000000000001384016829d028f823e6d062898c042a461bca13ae4627c983d9b5c9e8b4ffff7eb25daa1c52b39e309b9c1e7e4f2e920000000000000000000000000000000004f7904d491a0c2018b1361a9cfec4fc829e607402859fd9b9ded60adcee51e9b522d302f9064130a4eed1327f49bb4f000000000000000000000000000000000ef4fe949fca569b31fc57ae7d0166ea53318c5712311076e052c2967144116f5490fdf56f26adf64aa01beb4f6cd214",
+    "Name": "matter_fp2_to_g2_74",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000014b922157b19ed9debd9ae95cd9435f7980b8d4ea33fd29f99d5e7fb1a96f6d99ae13f7f86239c4bc286c3927d3522a000000000000000000000000000000000f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1",
+    "Expected": "000000000000000000000000000000000f20b3a6505784681331208b573d3a241706692db71b5daf4e9c80adb1fa9bb87023d7ba7f9c65158653c735dee9dfdd000000000000000000000000000000000f7f357407ca6cc5c5fae4b84509d71b2f4de9af226cb4038b4820c0541d4999b7396608efd2f322a00a768129f9800400000000000000000000000000000000138dcc1b9d978adb5eee6356980cec5d18cfbfbf18cf6fd14f4119a563f473f5027af06342e84ea858223ed63d1a16af00000000000000000000000000000000012b63f0d2e8ea361d55aa617a99e066b5feef3af1930b83d2a48b527e0ef304ceadf7cba1415db80c54fdcbbcf66d14",
+    "Name": "matter_fp2_to_g2_75",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a4d00000000000000000000000000000000051c6f0ce621e8e27e5017628690fb68f0fea27d67726a0a77b0caf9f524936e123ff096168ff2079b9990c07fa80354",
+    "Expected": "0000000000000000000000000000000015ff2aa94f802d8f9c60ddcb43aee598239cf3ab7f90f8289a487b673f6065f8d9bc92bd4cd28df4a7b0d3bb78fad243000000000000000000000000000000000884b5d4ca3c8abea737cfca05878528890b6cee9bbac0bf027df5d4e0add431829caddf4c1e001818581ce08686eeed0000000000000000000000000000000019b91a7738fde9760240b335457955e963030848e85717858f22dc33ba5a4721156cfdd7341aa86d10d268e2fc9a1d26000000000000000000000000000000000af85e60161795906f3cf705f5e8cb8c15083a90836eac78445c6bc27ffbfc8c2df3009b436989b46b271dd8d1dbc282",
+    "Name": "matter_fp2_to_g2_76",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000094e958d9b7dac39fa4f0143a333b2ccee09046cd23e6a1c0712470a3c2e61d2f8b85aeca37350f71d7ec75aea2b3b6b00000000000000000000000000000000080743cdb5e359e8b8ad3485d53ea286669ad66d841945296edf80dde77b20a158e2c1529dfc33a1fbecf177d75a0c69",
+    "Expected": "0000000000000000000000000000000001bd1fe6a6c373cfdc2bfd488b0c942492b77d55b2560824edef3a91c711ee336bc1366690be40949d04edd39ad48a7500000000000000000000000000000000161476946a5687113c74a34284f49b0658e323fae57aba88b039eae584d6ef28adca669fb083a2fe8f0ef664eb5b957d0000000000000000000000000000000007aead870ae09a04cf9c9fa49d0888f7010782cdc5a0ade4c1340ff15d99cb39b7412d66d4147b95601fcf5a39c39bca00000000000000000000000000000000095cce83dbfec12973e27627bfb2d93fa9a027a2c2af4259a0879d6bda055d74559fc93fb3b4f6b0088f702af29a7643",
+    "Name": "matter_fp2_to_g2_77",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000dec04526dbf7666d2c29db5de1ef0b3da85380a171d871a57ae3df364d2754fceabf9d4d2a3da1ecd94e377abc78430000000000000000000000000000000000d19875fe988ffbd0cf1e9bfefc7019579962ffa3a585ee232615e4a5fce0a07bce0537b203ea00010a90ec05d5b8de7",
+    "Expected": "00000000000000000000000000000000133cdf684c3ff1cdaf07ff787b57a66c215eef06acc2aec4d726a086480e7b2a5dead2cb357d99e298df32d4c6f5029b0000000000000000000000000000000019cd65b830fb17880f40e104ed63a7d49b0fbad8eead7502f18f1b9f85f3f6ba6c275b8a242effc61a7a5d770a4fdaa700000000000000000000000000000000039aeacd163862e476b17a22c76042d7896a04f158489ae71afdd35d27106a3ec276baf5c08e3eed4b3f0a79c3c458d200000000000000000000000000000000125a9bd770c1fea2155a581211bd71d55eb1966645cc892a05d32cf1e4e5b23278ea2fb1336bba7f2c887debe4a93b52",
+    "Name": "matter_fp2_to_g2_78",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000016dd03f0df71b183e42cc29c665f18d57637b65f05df85aed9a0f7b8aa37f7913f6606c10f24a7a8c570f485905583a00000000000000000000000000000000161e62d8be678a114fd4a99a6caeb9481f5eaef145571152fe8a6ed77a34c06a1b6ff66044102d19a94abcaaeb254e73",
+    "Expected": "0000000000000000000000000000000007843268081f61ad2b3f6653336a99086381bb4da4c23b7d59b9c7827f2d4c196d136508c8a1f3d2f939e8c9799b95e10000000000000000000000000000000000e2c57ad95f762115d8230320810a4ea9978e26ca17decd6af4c112789608967a92fafe3fb3e79539d75d1c0bae97740000000000000000000000000000000010951c9839db9dd6ca5ef95bd1b1b9cf60bfd97cf88129fca23b24f19c9d5c71486dffb762e92f23d2a9e9d462556f620000000000000000000000000000000013d35c17b3763fc5db46ac8c44aef996f3f876c49f5278b7c97e844f23ac49f2d50b3af080322d30ead873af7b4257e1",
+    "Name": "matter_fp2_to_g2_79",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000036efffcb0c6f42109bf9b8b7421e32fa3f855373345341e6000eccaca135ef3b2e1c0151bddbd46ae92185acb847d74000000000000000000000000000000000edbd7a40f3e688eaff5e29800159b8d799df07e81f65d59011e84329b4689a28a15ce11537fb560df705be26bf14b1e",
+    "Expected": "0000000000000000000000000000000001aa1919a50b5bad62b839d672d5a11ad345fcc61f75eccc42990e113deb8a486423d1b27e7c81536d8a5799986b9408000000000000000000000000000000001879295d2f7bb3923ec61c063ee4f96d7d7cf7786259e2f4cbc3ccffe7e114af264b3527a5e06dcfad50ec1e2a9c1ae0000000000000000000000000000000001042632662e406c95f3fd44a6d956e526907147e7e6d4219c1c4b28a31e479974d00d4ad6e683f6a834d3d4a20830f4b000000000000000000000000000000000a29ea98ec25e7827bcb349ccdb2a57926809f3cce44d5ff6cd636460278c8103b0db78fa580e9edd4ecd0bdb21018ff",
+    "Name": "matter_fp2_to_g2_80",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000974c7d17cbf91947ad435b30ad2b639671a43d67da6a4edc7f8bdc11fe817d4d42f687dd642a2be89c81bc36e8df592000000000000000000000000000000000efeeb85860877abdabae35672a77ca9d2cf0ed18ed209fb905b591a841c900ed06d2c32c56bed5f7efd469d369b05b8",
+    "Expected": "000000000000000000000000000000000c67498c6751cc27d871b8711c4739398c501a5bfb688d7e1a73dc7db5c47c3e28b633078cb83745bf5b0d5d2dde3ce2000000000000000000000000000000000c205c03305422bd44082715b90e0a0ec178003d6f5e14a0d13bb0f2c38f2270816b884b4870b75db44ab080f88a35e2000000000000000000000000000000000257f378935772d326710ec6efeb22f8c9b6b549c8a4c0205b75740047d750d73da4e71aaa8ff33b9bd8ab7621b08e62000000000000000000000000000000000c386a15f09c849be9f449a59e1332a1e7f16a9394c8de198c01399a05b0f963921c4c57d49916407ae0d202af8da32a",
+    "Name": "matter_fp2_to_g2_81",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015333364f4d0d173ef35e447fc189b9d65ef71b9fc4ecba25fb6c8c1bfe8467f26bb9c55ef10bb34125d714b94aa1df1000000000000000000000000000000000cbba9d8ac191032f03c0746f13108962130c9e2c01d47f01174a4c4d3daa7631268f7dcc08dfda317bd249fb6e73e8a",
+    "Expected": "000000000000000000000000000000000864da537fd94a9ff1bdae733f01e145dc97a894733d0811cd67c2648ba61d0b187241f9ec69d8c011f514894a05a608000000000000000000000000000000000a53ea4ff9c0ff71541ee21127a33daff2b39e74301946a86e51dc7834717e7d8784cf92fa5845bc0613b6b869003f58000000000000000000000000000000000582f5a1fcef3067dfcdfabc6af33871114538abcb02fcad761cb496020c7b423fc52f0075916f160fbe03574df97ea4000000000000000000000000000000001244ede8ba0dc09aacdc5d9f886e59bf963a25885dbbe2c3d1f611bfae82debc556ec4c94f0606492c7b8c7bf976ec34",
+    "Name": "matter_fp2_to_g2_82",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000781e980c167c982c2fc8d0baa3907bc5499eafca675ae20a10b25063c9088fd06f6769df505e5900bcaf99e266c052c00000000000000000000000000000000183c12798438ea92db75d5bf76cf29d320fab3653e4131989205f2817aebcb1b13f161864c084fd13a11459d7d5ccd92",
+    "Expected": "0000000000000000000000000000000016c334aec0e19934665596f0ae37eb398f1d6f0d0c9f08189f1ccc219230395124a9da03858bdba13ec5366da54228af000000000000000000000000000000000b156ea34ae7b5c252dd90997f1c693773a463c26935a69bcc0599b95bde9e6aa31649c48b6ee4ec1f0a56b19273a5170000000000000000000000000000000014b2d69e02418844effcbc0d564b2721deae2872cd1f27f61d544fc0ebd5cadc77c6777ec944ef0500db181a5443618e0000000000000000000000000000000004f0d48a25c1eb81233f385af17ab6abf554e1285b669eeb5e884c64d5815fd5fa1350bb361997cf2e317f7c5e9cd19a",
+    "Name": "matter_fp2_to_g2_83",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000879133a3c0e50c90abf1a6ac75bbeca1121c865ef39e9224ddb160eb725e0850a34aaf9014e42174966773e40e4c99a0000000000000000000000000000000004c66f8f5bd462cb27e9f5e1d93e322bd97582b9e81d07b2877103420262e4cfe6d0e3bc07f9f160701fd754793eae33",
+    "Expected": "0000000000000000000000000000000003c0d6b721cee4e5fdc6a02095674a58075f81b1d28163f81d5b258c82634297009e6bfc8193969e23e196cf7a99ad6c0000000000000000000000000000000013229818411c8e55e50a63df6983150c1d5ead828711131d9c81841850ed76e4712954d3225eb6d7fffd3cb9924f7497000000000000000000000000000000000f42d6e4d5a28dbfda87c806cb0b1bbabb745e63e655c3c6be50411da4dcdc745ae50f71d56e88db8454d40375e325810000000000000000000000000000000000f663ab791b48f76d358e66e8cd8fa40848dff2bbec758ce1d7b3fe02d1f6b3f123cef644d4fd86d6a77b8155feae58",
+    "Name": "matter_fp2_to_g2_84",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a7e855324ef471b8fefb31967cec84d57953407ba555b672fa59364b303030cb02b6c77933cc63fcd1b8c107b263554000000000000000000000000000000000b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e7524",
+    "Expected": "00000000000000000000000000000000197865f685e78a8842fa79ddc728d507e9f31b31666d1952a46f6422c97c83fba3087be70e3bb588260556014523f74000000000000000000000000000000000131f5d85ad3beaabd129d5a5675d90ea911ebd02cddb5ddc7a8be28c33061430d684d123d5c516785d21ebf756c99195000000000000000000000000000000000c7a14948f3aa29f845e5ca9877db9f0477af376eaeb45324c21e6f99e738aeec96b89af4df942bffbabbf50172d8e5b000000000000000000000000000000000ed4aea3cb585b0d36972f9ad6943172ca7375b44d1d6e80e0bf97a0b25d74deca4d35ce865c8747f1c7a2771a37c667",
+    "Name": "matter_fp2_to_g2_85",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001706830efca18d3e75ea0f1ca8af23a816017ceeb045694cdbad6d3d9aa5a9ddb123f5097a226a217166de3a82038393000000000000000000000000000000000402132ac383a2fcb17fe73398273ef0c2f2d0d6edabc78f31080d3ecbf7c249ffeef28bb8b37a6ef2a7d726c070dc41",
+    "Expected": "000000000000000000000000000000000a795c2affaaecab6cd2cfd6c8fab6e35cdd646e9cfa7b5e02400ef4abf839a69924ea80152eca7810a5041d1bf58ee800000000000000000000000000000000121426bb945d6f6b385c98a5247b7dadaebd3375dd8b2bff7aa77fddfbe603de89e77baf0e8f36a924c707c53d29a1450000000000000000000000000000000007a6fcb486634186f001c8b99874f0a07a37f1ff4b30599d2f570f1bb4ff290b816547f6ce8b3c1ed33e57630a1d57ab000000000000000000000000000000000fa65924a8f17414eb7dcc54f2a4134568484e91533dd21fd33cbcc37a920f2804516a64f1986e9d887ca189179d07c8",
+    "Name": "matter_fp2_to_g2_86",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024beda2b950efcee233435f0c748e33aa928f54ff29d3db217d7e32b1aac5f4ed11705da4fb8fd38481382486e4aef7000000000000000000000000000000000c85283ad6e35a72d07b74775df1a4660113d50b51426451f454a575adf9cbf9d7be3f521649f6c367c4f4c74b67ff6b",
+    "Expected": "00000000000000000000000000000000049d9ac43e31faa3d02f8255d207b82e4b27e8a9a61ba45fc4f9ad8048e5f89b58d25d98253aabe29334e0dc09d1cd6b000000000000000000000000000000001544f90a0baea38b48d89bcb337cf5a80faaa79334733b7e6126f55358a7e498aeb61419065b9434cab9d10fe8e7fd9f00000000000000000000000000000000139bdd668462a1b5d3ef1299d47aa91ed141ccbeba5b08a8ee31b023aa78c16514a97ba08abf5c8bb1abbd85b3fe87350000000000000000000000000000000005c7dbb8a22403a96aee634cfc67ee6f1069cd61a1e1831e8faa9d7e1aa5e4f7623f51f2e5b739f7fcf3b4ba77c82ff1",
+    "Name": "matter_fp2_to_g2_87",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000cb18f477abe58af92116101c3f52ad4f6074ed92a08c3adcc6660b555df9cff09dd8b34e032ed81e868a62bda50378d0000000000000000000000000000000013c4ab1558dc250c3b5d0f0fae3db62b8df969bb41e9ecc24c10e1e51cb399f1368bed7375a9b9ad9c7653c868eecfe3",
+    "Expected": "000000000000000000000000000000000b8b8bf2b25c2386e5f3be4bdb387d8005cf055e68ab9a5606f17dbedc4fbd7a11314fd646d08bbd6e394485d4f56f5f00000000000000000000000000000000173a45d766682f82ec2d69aed1d80ede2477c276ddaa8fb97f5f4d0515b2c2e370c615cd81c1e361f95db855c9b1b6e200000000000000000000000000000000115868a9187a0465a9309054e865ef224ec3c88a5eafbcc25f9a912ee3b19084757a90b72a4038ba71b10f59fe2f93100000000000000000000000000000000006c5476eb8aa1a471d289af52c7d1df55f6bb1ad53d7eaba6bdc2a97fcb24ec480f9d8e12079d366f2213194c861f016",
+    "Name": "matter_fp2_to_g2_88",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000188f650fdc51b970d16c0637ad5e97aade93c7f1398751439484ec6cc56613814908e51cfa7f68da9d980bb9dac47a400000000000000000000000000000000081834f86f1310135a2cb03265f37d9b7c9459bb149bc54b5a61319a7cde36c6a2a0fb49f8f1fb9d80f07b84f799119f",
+    "Expected": "0000000000000000000000000000000016e8fea4d09831146fc35bcad28e441f2c02e4d17838e04dc7cf909b2133297a13f07ee927722f3d78e36721d6848e3400000000000000000000000000000000114dee8b3a47269e9ada05ee015a874d1cbdfff4acdf5310642f829efd08f78dd6110e1c7a514e7d76aff52046f4ed140000000000000000000000000000000017b9d23f7a865a3ca61197d841fd9195805a9e883d79dc7d36e82f504e6689ade0e84c70a5c5c516fac3e3c643942e160000000000000000000000000000000001ab82b2a0986dec3211507b8adca351829b0a13f25e281f98f54d9e0e32280ea4c638dcb74280eb747a0d9af43b6b74",
+    "Name": "matter_fp2_to_g2_89",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006f66eb49f95f51ec90df86254580f0ae57862bdd8b5f2759ace63c5f14f8c5a762207f744bb82a8962f8c4fa410dfdb0000000000000000000000000000000004e02a80628c30ce336eab890fa37a227f77357a60be72cb87cc2b7442d2163d395fdc59350624ca1322bfe8619a2efd",
+    "Expected": "0000000000000000000000000000000006bc2ae646a603a1f4524b445cdeb99914e4ed19cd0676d511764b828bfe126e81cad2cb566655f04de1a302c14d70bc00000000000000000000000000000000023bd509aabfa41385e90cd4b1cbbfa45d066c4defab56993aaa386dc5b7707b1a3a7d444b8bd295a30d0b8f4bdc572e0000000000000000000000000000000006f82e60e18cc958375cce6f465db461ff46ed9d15cfcc01a3aff455d54c77ebba5a654c2ec788b6ed8ac53c39defdd3000000000000000000000000000000000896fbe6492c4c297f8b6d60295a7f2565734d69eea67b2675211a203fec043f0d181b1348bea425a068b7bc12676ed0",
+    "Name": "matter_fp2_to_g2_90",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001451bcd19495cea3a19393b77760f688fbf17b107dc131c88cbb503eee2a804e2978d6e8a4720d144083d28be73371d70000000000000000000000000000000017db715e8680a0e82e18b513f2c9c7ea136cefe8add71aac6baba146e3e33a498d025c7e0808ced306c915eb02900c61",
+    "Expected": "0000000000000000000000000000000008604a06a198c3e11458de920176842221667d024f9c155892485a37ff56252be1dc629a6fd580fa41f5e598a23f3651000000000000000000000000000000000e008eed25eafeaa67f27e89e1f81b469724a4b00f08dc4ae672aa1587b19dc615330e3fce0fbd98d7526bc2c4afe69e0000000000000000000000000000000015bc1e4ea5ae2a7fde6d5e5c3e58f6ff5df5bcb125ab402f10edd09087bde39fa27dfcdce7d04fd18ce399729e155fae0000000000000000000000000000000006684e9be8bf9fa4badda842a1d8840f0820d9a797e482c64f4004a18cd63986f19abfc93f6bf068d38eb1e491cabbe6",
+    "Name": "matter_fp2_to_g2_91",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf77",
+    "Expected": "000000000000000000000000000000000f15c3d0b40735babb2e38a2471773faa16b2fa307c3a573ef4cfa5a5559574b2d26cf88b19dee204b77f6e11a1b927c000000000000000000000000000000000d224445f3d31d381bb29c4fdc8130174f5bcb957f451c92f4a652cc3d2b5df985017133a944849b5228a88f99bec771000000000000000000000000000000001338b48bc1fa229f251bcd4828654baec9d149f090b19596ad3b444eacc7bc583f97d9cfc40d5611fdcf89cc9a88e33b000000000000000000000000000000000c30dd2aa51f6577d57175edb3ccc1b324717bc195eb0073c1dff4e5b0d77cf5e41ec233527b3936994e86303f91b172",
+    "Name": "matter_fp2_to_g2_92",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003379bc10acda5ed1014e2bba1e30cf83b72fe69259eb35476a031b8a898e0183bc32ee853a85fb3d738424208fc880900000000000000000000000000000000175a2e5a44ed62744fbbab9581ea7283470bff12436dfc414ad80b4070f895de3786022cbaed55bdbbc4f68db7460548",
+    "Expected": "000000000000000000000000000000001735e1f2fe905839fd6534c95b95322f8cc86a4c482f1ad7691b9b9bb8f55015b4faaa1f243786aa33b5874817cd09c80000000000000000000000000000000013f1a27931ac513145f2601e009cf637ba4bdb18a7604f89534fa3ec8488f5b6eab9963c5d753fdd34cbe7d2f8eb8a5900000000000000000000000000000000092d8f800e7a4bf6f9a25ddd7f64fc403db53b1695ae59c15f229458f347a8e7c2ebc415af2d3849282b670c5cf6f8600000000000000000000000000000000019d22d694e559c55db63521e7b60a1a2342c3cce868d70951e5ed32ec0f5efaeab0e78b21359110f6e769776b745938a",
+    "Name": "matter_fp2_to_g2_93",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b384a9db472c38a5d246da56059b7630f002b5f4663abce7c5f6e896222a1ca1ac02883a1ec95a4ef09bcfab7d0652a000000000000000000000000000000000de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc",
+    "Expected": "000000000000000000000000000000000d7dc499e5213120b3ccc173c83d3c15dde9e13ef57238cad84889243b35c8e69eea2ac7ef7560051dcd7402b46b733e00000000000000000000000000000000063ad31c17eb17d39cb4b33e45a0b0e951becc11b685b10cb45cff268b6dca40b780f7e1532be91903372c413a11b5be00000000000000000000000000000000140da959456cbd34e041409350d6106ff65ce6dd2ac3149f04959b16eb83dd0456ca11e5990daf4a1e5c23d3f30a6c4b00000000000000000000000000000000195d07ab127d49baf89fcf5eea1f5e4cffea1a577a5c864c0e637fbdfa10182adc1d5d4ebb871949300193e45ae0fbdd",
+    "Name": "matter_fp2_to_g2_94",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014df33e7d3ef2c339b958fee667097ccf146556261f7db4b0b0a3c29897b73a0ca249866cff1461488012bc16df43b0d00000000000000000000000000000000099dda253a43b8cfac580306267d9dfeb2c129ac1818fee43c6df5e582f5fa726ba73e1a2ef6a9e011a387c393529678",
+    "Expected": "0000000000000000000000000000000013ec1ef25b303fe2f10a0bbe9bd77a4b2a055e176c2870c99e63b4baf2b313a835459263351dfbc25c22ea32946d8956000000000000000000000000000000000cb1c3292a2e0c9b1c1ff43cbf7595f39c00fd413b54782681fe75a6f5f231d13912f8d598dd8aaae8159de083dccd8e0000000000000000000000000000000005385f2d4bb6d94d67b2a3bacd3aae31da282707672252c0ab1a12fc85d8e9b9eb75454eb145937542099b860f9d6dce000000000000000000000000000000000e59506f7733a38a7e1da4ea5958de4755b52a9307ba2e5813131b33b86f0e401f97594d9674ff1667068a1ec3c9b145",
+    "Name": "matter_fp2_to_g2_95",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000011c89c8d7e83155a308b2e517a23f05a4a55353332292b44b0a891b6f730fd126bd0b97eb87f0fbdb6c82779791d022f000000000000000000000000000000000da6f02450955bf26e236ec63aaf80a018ac34fd8784bb24a22a1fc5e8bd686244a923009a10cb38b1422534d0997afd",
+    "Expected": "000000000000000000000000000000000f4392a41fb3e58dea97b97fd22e2fe6436c3f9bbcd944585a76a5f1a8f98ea4ee21639208d765b6c3a7d08f8cd3f3f00000000000000000000000000000000002c3d62794996dbb881b665eece98926f41a42c21539125fda6070d9f69e29e0557c886b42e4bcd97b14134d6e9d1d710000000000000000000000000000000004b93f315822aa1be8250c2e736727d390ae3a862c4c7dda452817f70f01c73e6f344df1b0f05f03bd574edecc70902e000000000000000000000000000000000731403981fd6243d00c23d0a42a759016f7907548847743f18421f51b1e72cea92f0c5580328babd4ae3e15bc9c56de",
+    "Name": "matter_fp2_to_g2_96",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015bb227b5c9ccfb8390edcd158b04a69a88a3b99a10ae90e548182751a16448df25493061afde2c9790a0e75e6f409a20000000000000000000000000000000001d7b609155bf3939192eee9642032e6fb10f57d53916674c60211a37b4a7662759899a9569e2dc730febd23f747a7a3",
+    "Expected": "000000000000000000000000000000000b35c6294b70336217eb9334ff1f1bde9d892d109e947de7f4f5681b3830ed00ad1b89ccd7cbad88ce1586449140697d00000000000000000000000000000000032691e5f4597c06496e9e37907041ddcadd18ca8ce64a8b400b1e2e8d63acce5533231edb66b69807fa2dc026c1d2be000000000000000000000000000000000773ccd132cb215cd98aa17d7fc432e0577b08d8faaa35199000d46fdeeb954e8652566384fa0cc5bcd1724942f7075b00000000000000000000000000000000112e951db3694944fc82fb980547cd8b7f2e5ec6fd2051b6aff2573797bd6a28437848ea0627054af1960ad1de0981e5",
+    "Name": "matter_fp2_to_g2_97",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000017599d71686e817cf58b78dd7586d5b359999b32b0dec2d67e33fb6388411418ecfaa2670a2cc9dce3dadaed0fb3364000000000000000000000000000000001773995b540be9ffbfd276a92c0494e4eae296d094f9f7eca975cf4f73ae05e92bd64ea71ac47bba534044f4072a6591",
+    "Expected": "0000000000000000000000000000000018f2eace212eacabd44ff01d886543410ef72b4d27f8d25cb080dbe4b1d4b2b4e57e4dd40723d15789d9b5104b088d9b00000000000000000000000000000000098e9e9b302876ce85ba486609fd028f357314149ce8b530778e6de586ab057fe59648d8c8ae80fe619c4c605b90784a0000000000000000000000000000000016d20a8ca43d37518c8a0f47566ba61a7aade9ea2cdd4a0907ff0ed862c6b7c64815d50397eebec262a05c6010cfaa790000000000000000000000000000000005a70c2fce25acdc4a95fc2bdedb007d71f24b0b5714fa14910ef590215d25442e91a66b6bfea5f7777f0c6d202eff32",
+    "Name": "matter_fp2_to_g2_98",
+    "Gas": 110000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9",
+    "Expected": "0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa274526850000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e245782100000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7",
+    "Name": "matter_fp2_to_g2_99",
+    "Gas": 110000,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/blsPairing.json b/core/vm/testdata/precompiles/blsPairing.json
new file mode 100644
index 0000000000..138b13944a
--- /dev/null
+++ b/core/vm/testdata/precompiles/blsPairing.json
@@ -0,0 +1,702 @@
+[
+  {
+    "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e8490000000000000000000000000000000006e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb9090000000000000000000000000000000017d81038f7d60bee9110d9c0d6d1102fe2d998c957f28e31ec284cc04134df8e47e8f82ff3af2e60a6d9688a4563477c00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "bls_pairing_e(2*G1,3*G2)=e(6*G1,G2)",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e00000000000000000000000000000000166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d2800000000000000000000000000000000122915c824a0857e2ee414a3dccb23ae691ae54329781315a0c75df1c04d6d7a50a030fc866f09d516020ef82324afae0000000000000000000000000000000009380275bbc8e5dcea7dc4dd7e0550ff2ac480905396eda55062650f8d251c96eb480673937cc6d9d6a44aaa56ca66dc000000000000000000000000000000000b21da7955969e61010c7a1abc1a6f0136961d1e3b20b1a7326ac738fef5c721479dfd948b52fdf2455e44813ecfd8920000000000000000000000000000000008f239ba329b3967fe48d718a36cfe5f62a7e42e0bf1c1ed714150a166bfbd6bcf6b3b58b975b9edea56d53f23a0e8490000000000000000000000000000000010e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc0000000000000000000000000000000016ba437edcc6551e30c10512367494bfb6b01cc6681e8a4c3cd2501832ab5c4abc40b4578b85cbaffbf0bcd70d67c6e200000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_pairing_e(2*G1,3*G2)=e(5*G1,G2)",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a9000000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf100000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab160000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb541170000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a70000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb3000000000000000000000000000000000029ea93c2f1eb48b195815571ea0148198ff1b19462618cab08d037646b592ecab5a66b4bc660ffd02d1b996ca377da000000000000000000000000000000000bb319a4550c981ee89e3c7e6dcc434283454847792807940f72fd2dbf3625b092e0a0c03e581fd9bd9cf74f95ccef15000000000000000000000000000000000abb072b8d9011e81c9f5b23ba86fdb6399c878aa4eadee45fb2486afe594dffc53be643598a23e5428894a36f5ac3ce0000000000000000000000000000000005d04aa0b644faae17d4c76a14aa680c69fdfc6b59fee3ef45641f566165fced60cbbda4ca096e132bb6f58ab4516686000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436000000000000000000000000000000001065f2a2d29a997343765f239c99a018490eced40ac42fc93217dfe20d8b43ee2215f65166aff483b3dc042c5a43b196000000000000000000000000000000000766e4c66f4a442ff1f61a7a4d197d2b47dd226d0e7822a9b065108cfc643cd3f3d5ae59ed2ce4cde13fd9260bb5b7cc0000000000000000000000000000000012251cc6abbabeb7bbe1fdd63eaee10832a748fff24f7e3fdccaea87facb6e99f2e0407a38f27f90450a471b873104620000000000000000000000000000000011181e08c8fba91271adfee9d31681f8412ab7a3f754f7ba4709024c0ad2287e32dd455d71a296b4838072a8ab9d96f2000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f53580000000000000000000000000000000005a7445f55add1ed5c143424ceef3d594280e316c9441a8e68c3ad97377141d015bf878bdfcf0df9fbcd0529f4e8100800000000000000000000000000000000192b52ba08ed509fc84d5775a7182498fd1ff80941d673c53470c9c9f1192f9c0057d68a1dfee0c68fe5df3625cc43bf000000000000000000000000000000000d3fcaf2f727e0eb32c65da9b910dc681b948dda874d0db6f6ed3f063430fbf073385a9a14c2dd78568726124e2b3ea8000000000000000000000000000000001943ce22cdb2387bd5796950dc95d1ace4012ab9bb4afb46223760230c1709e075f1ae76d6b3f2e947ba6b16d458ccd1000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000a29e38da2d42fd4712052800c7c8dd6e94fd9f506e946068aaac799d60b94c2d7515769ffdd32ea95d3910330ec47de000000000000000000000000000000000c60dae92451206390e30b5daa7151d63624dee496753c87dd54eadc92dc9602081fae02a1a53bac97e984a571923a5d00000000000000000000000000000000085f4fda4c72328895f20c683cb49603a37ff2c43d62f66602506dad5b8d1daebfbac7a7db3f50ccf4dfff277deb105c0000000000000000000000000000000005674d005457e0fe1f0fd978d63996c5f3d29f9149ee4eb04c464742dd329ccaef5e5f6b896d986ddfc9f1b2a3aec13100000000000000000000000000000000071bc66d6e2d244afc4a5ce4da1dce3d0c22c303ba61310fdf57843bbd97763ef496833dfa99d14be084bb1a039bb2da0000000000000000000000000000000012c22e047b0af8e2f4bf3bd3633ef0f8264004ca8ea5677a468857a1762f815235a479e53f4ad4741ffda3fb855021c900000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "bls_pairing_10paircheckstrue",
+    "Gas": 345000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a550000000000000000000000000000000004523f5a3915fc57ee889cdb057e3e76109112d125217546ccfe26810c99b130d1b27820595ad61c7527dc5bbb132a9000000000000000000000000000000000186a1da343cacf1815b9c8b6c807f536249dbfdb59d77bf4920ad2198a0d83ada21f7c39de6f06a5599f22571cab288d000000000000000000000000000000000ba1ec44f95121bd622932b84bbb4b3d279f69c494ee44db68e3165c86b627ba5e397ee197313fb5b775972798997332000000000000000000000000000000000783e7493e9fb106fa0d085e7c03eb816468d12c65d9b77643ed07c02583d491f4db5db44e565d50d8ccaa9ad8f7f8e80000000000000000000000000000000010a6a5fd90cd5f4fb6545814f5df065b001074bb3f29f649dd2612815df3a19a320f7754dd3d458e48e7fb1b4953978f000000000000000000000000000000000345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c00000000000000000000000000000000083d3baf25e42f2845d8fa594dda2e0f40a4d670dda40f30da0aff0d81c87ac3d687fe84eca72f34c7c755a045668cf100000000000000000000000000000000129c4945fe62538d2806fff056adac24f3bba8e17e42d82122affe6ad2123d68784348a79755f194fde3b3d448924032000000000000000000000000000000000528590e82f409ea8ce953f0c59d15080185dc6e3219b69fcaa3a2c8fc9d0b9e0bc1e75ec6c52638e6eaa4584005b5380000000000000000000000000000000018dc3e893f74729d27dd44f45a5a4f433dcd09a3b485e9d1c2bd0eb5e0e4c9024d928ddc426fdecae931e89885ee4db4000000000000000000000000000000000d6ee02e1fc7e52a8e1ef17e753065882c6fcc14da61da7ffe955fe84a9d2af9ba57562c69db3088652931bf124b0d5300000000000000000000000000000000051f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e000000000000000000000000000000000b6a63ac48b7d7666ccfcf1e7de0097c5e6e1aacd03507d23fb975d8daec42857b3a471bf3fc471425b63864e045f4df00000000000000000000000000000000131747485cce9a5c32837a964b8c0689ff70cb4702c6520f2220ab95192d73ae9508c5b998ffb0be40520926846ce3f100000000000000000000000000000000101e147f8bd7682b47b3a6cc0c552c26ce90b9ce0daef21f7f634b3360483afa14a11e6745e7de01a35c65b396a1a12700000000000000000000000000000000090ca61ed16c4c1e80acfef736eea2db0d7425d9110cb53e6c4a2aa3f8a59ee6c60bdce8df5825011066d44bef84d29600000000000000000000000000000000028207394adcbf30250ac21a8f1db6283580bc5e39159930552e5edb25e6215c66b6450296edc80dbc3a2acd125dab160000000000000000000000000000000019bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3000000000000000000000000000000000adb3250ba142db6a748a85e4e401fa0490dd10f27068d161bd47cb562cc189b3194ab53a998e48a48c65e071bb541170000000000000000000000000000000016cfabbe60d1e55723a0ff72cf802f2d1cf13ed131e17729adc88522a657f320a336078a9399c8e61a3bbde3d52fd3640000000000000000000000000000000009aa9a3c2a6d49d286aa593c6ff644f1786fa9ae471bdb3fe70b150a9ed7584eaa886ac057c30005c3642f65ad5581cc0000000000000000000000000000000001d417894c0cce924955a795b188b27951f8438a5485404b921a42fa79dea03c10e29d0390df2f34d7be13f360a7fada00000000000000000000000000000000189b0b3a04e6c613899d51231dbf0cba6a8a8f507ebed99d24fba7ebac6c97a8859ffde88e6d95c1a9d6b4f0a8f3c417000000000000000000000000000000000d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a414950835820000000000000000000000000000000009d0d1f706f1a85a98f3efaf5c35a41c9182afc129285cf2db3212f6ea0da586ca539bc66181f2ccb228485dd8aff0a70000000000000000000000000000000016cad7807d761f2c0c6ff11e786a9ed296442de8acc50f72a87139b9f1eb7c168e1c2f0b2a1ad7f9579e1e922d0eb309000000000000000000000000000000000d3577c713fcbc0648ca8fbdda0a0bf83c726a6205ee04d2d34cacff92b58725ca3c9766206e22d0791cb232fa8a9bc3000000000000000000000000000000000f5ea1957be1b9ca8956ba5f6b1c37ea72e2529f80d7a1c61df01afcc2df6f99ced81ac0052bd0e1e83f09d76ad8d33b000000000000000000000000000000000aabced4e2b9e4a473e72bf2b1cc0ce7ab13de533107df2205ed9e2bb50fa0217e6a13abcd12fce1bda1ccf84dac237a00000000000000000000000000000000073eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086000000000000000000000000000000001825bacd18f695351f843521ebeada20352c3c3965626f98bc4c68e6ff7c4eed38b48f328204bbb9cd461511d24ebfb3000000000000000000000000000000000029ea93c2f1eb48b195815571ea0148198ff1b19462618cab08d037646b592ecab5a66b4bc660ffd02d1b996ca377da000000000000000000000000000000000bb319a4550c981ee89e3c7e6dcc434283454847792807940f72fd2dbf3625b092e0a0c03e581fd9bd9cf74f95ccef15000000000000000000000000000000000abb072b8d9011e81c9f5b23ba86fdb6399c878aa4eadee45fb2486afe594dffc53be643598a23e5428894a36f5ac3ce0000000000000000000000000000000005d04aa0b644faae17d4c76a14aa680c69fdfc6b59fee3ef45641f566165fced60cbbda4ca096e132bb6f58ab4516686000000000000000000000000000000001098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a000000000000000000000000000000000ea923110b733b531006075f796cc9368f2477fe26020f465468efbb380ce1f8eebaf5c770f31d320f9bd378dc758436000000000000000000000000000000001065f2a2d29a997343765f239c99a018490eced40ac42fc93217dfe20d8b43ee2215f65166aff483b3dc042c5a43b196000000000000000000000000000000000766e4c66f4a442ff1f61a7a4d197d2b47dd226d0e7822a9b065108cfc643cd3f3d5ae59ed2ce4cde13fd9260bb5b7cc0000000000000000000000000000000012251cc6abbabeb7bbe1fdd63eaee10832a748fff24f7e3fdccaea87facb6e99f2e0407a38f27f90450a471b873104620000000000000000000000000000000011181e08c8fba91271adfee9d31681f8412ab7a3f754f7ba4709024c0ad2287e32dd455d71a296b4838072a8ab9d96f2000000000000000000000000000000001252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada0000000000000000000000000000000002a1bc189e36902d1a49b9965eca3cb818ab5c26dffca63ca9af032870f7bbc615ac65f21bed27bd77dd65f2e90f53580000000000000000000000000000000005a7445f55add1ed5c143424ceef3d594280e316c9441a8e68c3ad97377141d015bf878bdfcf0df9fbcd0529f4e8100800000000000000000000000000000000192b52ba08ed509fc84d5775a7182498fd1ff80941d673c53470c9c9f1192f9c0057d68a1dfee0c68fe5df3625cc43bf000000000000000000000000000000000d3fcaf2f727e0eb32c65da9b910dc681b948dda874d0db6f6ed3f063430fbf073385a9a14c2dd78568726124e2b3ea8000000000000000000000000000000001943ce22cdb2387bd5796950dc95d1ace4012ab9bb4afb46223760230c1709e075f1ae76d6b3f2e947ba6b16d458ccd1000000000000000000000000000000001271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4000000000000000000000000000000001407ffc2c1a2fe3b00d1f91e1f4febcda31004f7c301075c9031c55dd3dfa8104b156a6a3b7017fccd27f81c2af222ef000000000000000000000000000000000a29e38da2d42fd4712052800c7c8dd6e94fd9f506e946068aaac799d60b94c2d7515769ffdd32ea95d3910330ec47de000000000000000000000000000000000c60dae92451206390e30b5daa7151d63624dee496753c87dd54eadc92dc9602081fae02a1a53bac97e984a571923a5d00000000000000000000000000000000085f4fda4c72328895f20c683cb49603a37ff2c43d62f66602506dad5b8d1daebfbac7a7db3f50ccf4dfff277deb105c0000000000000000000000000000000005674d005457e0fe1f0fd978d63996c5f3d29f9149ee4eb04c464742dd329ccaef5e5f6b896d986ddfc9f1b2a3aec13100000000000000000000000000000000071bc66d6e2d244afc4a5ce4da1dce3d0c22c303ba61310fdf57843bbd97763ef496833dfa99d14be084bb1a039bb2da0000000000000000000000000000000012c22e047b0af8e2f4bf3bd3633ef0f8264004ca8ea5677a468857a1762f815235a479e53f4ad4741ffda3fb855021c900000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "bls_pairing_10pairchecksfalse",
+    "Gas": 345000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_0",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed200000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_1",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_2",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_3",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_4",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f9000000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_5",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_6",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_7",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_8",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_9",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac94300000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_10",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e0000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_11",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_12",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_13",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_14",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_15",
+    "Gas": 138000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_16",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f900000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a6700000000000000000000000000000000018c690fc5571f69793ec3c82915ac9513726ec891312f4f11dd3ba0ee95cc98b50d925099b0642e58a106e8456bbcbed0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_17",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae70000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e88000000000000000000000000000000001129c94e0c06f51f1f808a62e42a5449dd159289c14a09c4cc382c91bcfe878b6f3555767c310de1b1c275eb3d17bcf5000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_18",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a79157800000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000f46a7dee0cb471ddef3a50670a5bfc443b8455877f1ff602b21faff1eff07fc6bf27930ca2159f01479359548339560000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_19",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b090000000000000000000000000000000011dbce34af6cb14d3e4d49f2482e1b058609f25dca79e2ca48e289ace9d1c8db177b7bc35daad79aa5fdac77728bd5fc0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_20",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd6700000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000108248cdc6f503c7bad30eac875d92a75feccbdbe7b5394f8557a92bb12a796430a2c60ca23c6ecd259e5b01e53891820000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_21",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed30250000000000000000000000000000000006f1fe4d98675ffc62d4d5dd0af9968faca4d0ef425d56fa31b80aea892820e270f6da17aec9eb83c6cc9f84289ecbff000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_22",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff32000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e705800000000000000000000000000000000091ce9e6c4bab3ad3d275cf5888387646c3d9bb53ace7bd0c118fce3e44fcd96241b58e5af53978272f56d04b7d7ab79000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_23",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000b4322c2fb5d5dd2c65b45f74ca75002c97444d8d4e5680d0369d32d180c93b294e30ef42f21ac274f77ad89633ab1b9000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_24",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c200000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b100000000000000000000000000000000081162fe2e65a642993ba283df9bc8d60bfe495b2dc5623f0467c87bc7570980be2654c8cc8838fb362c677f3a3cb1e900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_25",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e272839000000000000000000000000000000001324e51f295ea95adedc9730dac2e1ab6d85838840e3955103d76677ce9a7359215f8d954286950bed1be3bbfebabeda0000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_26",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b00000000000000000000000000000000132d85236d655190323279a01acc8cbc6fb736d951e3999589f0559cb61ea93e2e1c526ed08ef08046c3d7a40d7cfdaf00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_27",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da0000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a60000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000a4ec4acf91be994fe45f08dbeb2bbd053275861d11a486693fd6e5bfa3736134396f6b1c3ad146642f2e972ba609d0b000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_28",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b592947030000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe9760000000000000000000000000000000011dc30871a7a9b33e2882f6b24ccd192aad215edfc6c6bd9acd064dff4a43f41b4c212068875e896daf127547bbeca58000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_29",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000dd1137e592119c134103b9e29e4f14b48387a767d4effae44f807e5b579e7f9c2f60105495f070d0a6ab2410f62844d00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_30",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad62420000000000000000000000000000000016ad3b981f689f51f46e3e5e166986e4e71655dcc1e928327751ffdcfff8934caec5cc37327b3320202c4efbcfda6ae3000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_31",
+    "Gas": 161000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000cbcd06a1c576af16d0d77ff8bcc3669a486d044cc7b85db03661a92f4c5c44a28d028521dfcfc292d8ecd05aed6ab940000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f00000000000000000000000000000000153ba4ab4fecc724c843b8f78db2db1943e91051b8cb9be2eb7e610a570f1f5925b7981334951b505cce1a3992ff05c9000000000000000000000000000000000c1e79925e9ebfd99e5d11489c56a994e0f855a759f0652cc9bb5151877cfea5c37896f56b949167b9cd2226f14333dd",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_32",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c81292925400000000000000000000000000000000065d3d4be1589a6f00c85c208c44916a35349a096b09219704b4c31861ef58e6b4ea5be57a175b429e482b1038718d6c000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000b8bf51e6509e81b70ff44d6e0df8f9f7bc8e33126e9f1b5a8419414878a2209c05df56cf590c8e33f484587f4a1f6950000000000000000000000000000000001c07a85ece4a1c5072fe722fb264bd1d3aaabf9db9809d5a6cb3fe17157d8fd1bfa730a26e34c87279ea81abe141fe6",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_33",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda00000000000000000000000000000000055caff20f9f3f2ea27c64caeb6bb1880f326c64eb6ed6ee7d15da7bfeb16518f76a75f061cd347f7322e0cec634f0fc000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000000197b17c5b695db49c7c8b6fd6f314da7cfdfc4dbe61c76475839c89064870fad5104234c09aee7bafc1660263b6959e0000000000000000000000000000000019e514b65a358640ea90cd3cf547d591f5ff1a13ad99c568ef70c558cbec26dd1e582cc92d849fcfce9749068d361372",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_34",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000003c0c619be1382199bee84862a0ac6bf4c891d22f722b6af5bfef0edd1ed8c7e9af5efb5d3fc546801f3e019329ae4e80000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa5200000000000000000000000000000000173bdb0d0a79f1f2607867ddbf2581b4c1cc20fc0bced60ed8756aa4e79cb87c47fa722b1c8fdbe99a8208fc532327b600000000000000000000000000000000172f37eac49dd7f09adf602ebe562e5d0bd52ee2419fc08dc7fda241c0319b3f43b3ec79ab5aac246a783f13e268d4bb",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_35",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a030000000000000000000000000000000014c22dcacf2e21ff447c94d81067c626b1217e58b7dc98aacab2ea3fc00b1c5e66f660d19f1c69b16571e49d13c8e1d0000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec35000000000000000000000000000000000125ffac343f97afc413ae80d40a309dfe461fe6b20eb84f8eec3a2718ec2c9f1273bcba2fa1ca59fdc924e471173d68000000000000000000000000000000000ebfaf78e267fd93f0e35e0d19feae9db125660a3dde99b028be77c6fac0116a4808aab02a29063c3c0bc4476924d04f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_36",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000010338047b7c67c122ffb13466935623ef2338b32bbf5452f78f7abe9a13a16824c11f5520c9dac256b9d257da88d92a30000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000a96371995c5333949b5e9869599fcb67222c2e44447d133e9b3e18a9e9e14a92ee03dcba86832cde7d35b35a88bcd240000000000000000000000000000000003a912710b425cc477c43ff93684249649732b1e99270bba725e990559169b505e8411fba174b6a15f90d5b3962f5399",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_37",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000008418a39124b40643dddcd6afe1dbdf930303bca65226c2fee1b95de6e080e25451f8b4f2b2b7c4633e1de6a5a7d47cb000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c000000000000000000000000000000001535bce9acb48b6ebc4d3dbb7c236d7cc57d656210e42e9379d4f528fc0ba59bf868503d3bb8e5cd3dafdd881ec56d5c00000000000000000000000000000000037033062d13644c88c317f1c58c18e0d9b4facbbe0701ac8bbdf3c7f0c37b14034c7882d5112a94bea39dfdbfc518e7",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_38",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000ada13f88a645bc6082c6321e0cf2b7ac45c633fe2f0cb36aeb187fe2e50e7510df2a86b98979e8551636e94488c8ce000000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000002f98cb2305518737e92ee72e2c48d0dbdbbb1f2dc63b9d02d1a8c27dd1ba5a071dc72a8dcc7813b5757bc244357f6630000000000000000000000000000000017775ae72405e39e2db32d5b9db6637b7bbb9799e614bd783f0b785c3f2ee815367e67b15cc037fec8252735f36c28cf",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_39",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a3700000000000000000000000000000000162e49ebd1b50c7837336509e48ace0e7856f00ec45a76b96d1dd88eea300a8118357cafabf32ee2d06b601def523e4800000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b6000000000000000000000000000000001575d39e959d14b96f261265417ca949a248c3d46e2abb093541c103dadf58cd5b21e28c79f17b376070799492457358000000000000000000000000000000001240585d5f4c28467bccb5193e4aad78ea3b1d8dfb4716a3310fb5215a478aac3f05a8ed478486c9e703a59b9c32bfe5",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_40",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000efe47bf2b11dd10608a309c8aaefdbcbc2bb5e6adceef375371cface8f79668e2b7c2ce9990063a3b53e419e80e2a79000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba00000000000000000000000000000000088ea99f17bb06ba20c5c67aeb8fbbd19b2270986ee7c6517209679e6f84f5d43fa22daf6264c993f1d048d016e3b39e0000000000000000000000000000000008af6330f5638c0a9f339f4e8d841b955e322766457112039b2a852b37d3bc45efb67aeb216a09a8940f5e4e1a771da8",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_41",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c1872290000000000000000000000000000000001099fe889d8f5ddcad09328997c7c3098ef4b4d74ab1d9f6fcbc33a03cafb59c7b28931da67950d1389fbcedca3fb5bb00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e00000000000000000000000000000000006b5813a1c1f934e8e564a7cad93dc7f57de7a9592aedeb116fa4ed6bc6452bbc0da23be10adfea4ad4fa82969e7a150000000000000000000000000000000008e760ad89fd250a9d5041ec81e51b8b66f5265037e7237f7c4a08bb83e7799f352c54c37cf70a6c61bb95bfbf8a616e",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_42",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a9000000000000000000000000000000001750d2e78525453f113b76f18abf2334de9755c03786fbc9233cda2364d57ed493f4fe6c2b565f4d82ff8113e9b63c4d0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000018446c6ba126e030ed071f87189cbd618627419c82065d26044759f6e4c7257f45021dfad1dcb34dd06b4e391329a61f00000000000000000000000000000000136b60cd7658a5d135d4bc38edff042570c7824245ed9f7a6414e9e7ab3840a99700fb620e809891b66003340db29e64",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_43",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb0000000000000000000000000000000001a28b7856a22db6e79ac4165e60addbb7dfe1f19d815032bc68fea905bd0d7709c2dafc65fe51493c964de678a30d32000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb877660000000000000000000000000000000006a5d436046e0ac1975e4d24bb3e3f35c1ba3801f37705505cdeb6a86c58bf8068b43462a55155799fe2d2cc60c398b900000000000000000000000000000000191fde77c7c2b397a950f0542d2edd183a5e9404e516146697a755561ab2a9705f970b491e4c0003657d864258f391ec",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_44",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e270000000000000000000000000000000007c17aaf82c2aa6bf01695157bcd0c2b34734dfbd572b0abe79c8dd3eef7ce6eb9c5e7de55b36ddf87f05e55ba9ac28b00000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df700000000000000000000000000000000073b61e6c2de0969138ce3671582c9b58305c5abefb54cfe13eb3284c2be04d26c906c717fd29aaf60b485e18de8e4fb0000000000000000000000000000000006297267dd3b6f3de2329e837302427ab6235b3ad4a9f8c6bb45795852d3c3c61fe75747bbc78043102fc3f646f5d1f9",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_45",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000017147eda83f35d0b6c8894317da5b2e991818479674d7dd1aef6bfaebacbb61ad4b2a17ce7e799939f8c2004af4799530000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c74390000000000000000000000000000000009e2fdaeb5f35c5aeb9aaca231439c45ac022875d55575cbf25c15cb6177c6b67416ad22fa7e7cb1924d4c2501f98bd80000000000000000000000000000000012dcaeaa2e415f46b579d9e325d7d7c3cd94083d25fe38c872f1907bbb741aff660d28bb663edd502444e11d2d60d8f0",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_46",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f10000000000000000000000000000000016c1c9ca735535f801c58a9e35a80ce122d20abb327b44db4dea31b899982c4e136a2430c51cf3a31adc5611621f9dde000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000b49e02d9fb238a258f3a4307b6a2f64912b7fa91712b5639de24e90c09f9797654e0f7e2d31e968c040b867de03cd370000000000000000000000000000000005c56a16431ba175ad81260faeac87d8238f86b2828b0e74dbb0b296b34745ac17e6b684a25a16240183619c96b986bd",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_47",
+    "Gas": 184000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf20000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf2",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_48",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e500000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e5",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_49",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_50",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae850000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae85",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_51",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_52",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_53",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_54",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c50000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c5",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_55",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e790000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e79",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_56",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_57",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_58",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_59",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_60",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa4820000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa482",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_61",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_62",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_63",
+    "Gas": 207000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_64",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_65",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_66",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_67",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_68",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_69",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_70",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_71",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_72",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_73",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_74",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_75",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_76",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_77",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_78",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "matter_pairing_79",
+    "Gas": 230000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb963",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_80",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_81",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_82",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_83",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_84",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_85",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_86",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_87",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_88",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_89",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e6",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_90",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_91",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_92",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_93",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a7",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_94",
+    "Gas": 299000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "matter_pairing_95",
+    "Gas": 299000,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/bn256Add.json b/core/vm/testdata/precompiles/bn256Add.json
new file mode 100644
index 0000000000..b6fcd550e3
--- /dev/null
+++ b/core/vm/testdata/precompiles/bn256Add.json
@@ -0,0 +1,114 @@
+[
+  {
+    "Input": "18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f3726607c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7",
+    "Expected": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915",
+    "Name": "chfast1",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c91518b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266",
+    "Expected": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204",
+    "Name": "chfast2",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "cdetrio1",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "cdetrio2",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "cdetrio3",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "cdetrio4",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "cdetrio5",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Name": "cdetrio6",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Name": "cdetrio7",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Name": "cdetrio8",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Gas": 150,
+    "Name": "cdetrio9",
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Gas": 150,
+    "Name": "cdetrio10",
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
+    "Expected": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4",
+    "Name": "cdetrio11",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd315ed738c0e0a7c92e7845f96b2ae9c0a68a6a449e3538fc7ff3ebf7a5a18a2c4",
+    "Name": "cdetrio12",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98",
+    "Expected": "15bf2bb17880144b5d1cd2b1f46eff9d617bffd1ca57c37fb5a49bd84e53cf66049c797f9ce0d17083deb32b5e36f2ea2a212ee036598dd7624c168993d1355f",
+    "Name": "cdetrio13",
+    "Gas": 150,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Expected": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "cdetrio14",
+    "Gas": 150,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/bn256Pairing.json b/core/vm/testdata/precompiles/bn256Pairing.json
new file mode 100644
index 0000000000..3fbed6b87c
--- /dev/null
+++ b/core/vm/testdata/precompiles/bn256Pairing.json
@@ -0,0 +1,100 @@
+[
+  {
+    "Input": "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "jeff1",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc0203d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db841213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f06967a1237ebfeca9aaae0d6d0bab8e28c198c5a339ef8a2407e31cdac516db922160fa257a5fd5b280642ff47b65eca77e626cb685c84fa6d3b6882a283ddd1198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "jeff2",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd216da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb314a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee245901b9e027bd5cfc2cb5db82d4dc9677ac795ec500ecd47deee3b5da006d6d049b811d7511c78158de484232fc68daf8a45cf217d1c2fae693ff5871e8752d73b21198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "jeff3",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "2f2ea0b3da1e8ef11914acf8b2e1b32d99df51f5f4f206fc6b947eae860eddb6068134ddb33dc888ef446b648d72338684d678d2eb2371c61a50734d78da4b7225f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb122acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf6806d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb186bac5188a98c45e6016873d107f5cd131f3a3e339d0375e58bd6219347b008122ae2b09e539e152ec5364e7e2204b03d11d3caa038bfc7cd499f8176aacbee1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd415794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f211b88da1679b0b64a63b7e0e7bfe52aae524f73a55be7fe70c7e9bfc94b4cf0da1213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "jeff4",
+    "Gas": 147000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "20a754d2071d4d53903e3b31a7e98ad6882d58aec240ef981fdf0a9d22c5926a29c853fcea789887315916bbeb89ca37edb355b4f980c9a12a94f30deeed30211213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f1abb4a25eb9379ae96c84fff9f0540abcfc0a0d11aeda02d4f37e4baf74cb0c11073b3ff2cdbb38755f8691ea59e9606696b3ff278acfc098fa8226470d03869217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac290a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a98552fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d70f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2198a1f162a73261f112401aa2db79c7dab1533c9935c77290a6ce3b191f2318d198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "jeff5",
+    "Gas": 147000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c103188585e2364128fe25c70558f1560f4f9350baf3959e603cc91486e110936198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "jeff6",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "empty_data",
+    "Gas": 45000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "one_point",
+    "Gas": 79000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "two_point_match_2",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "two_point_match_3",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "two_point_match_4",
+    "Gas": 113000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "ten_point_match_1",
+    "Gas": 385000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "ten_point_match_2",
+    "Gas": 385000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "ten_point_match_3",
+    "Gas": 113000,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/bn256ScalarMul.json b/core/vm/testdata/precompiles/bn256ScalarMul.json
new file mode 100644
index 0000000000..2a28f6304b
--- /dev/null
+++ b/core/vm/testdata/precompiles/bn256ScalarMul.json
@@ -0,0 +1,128 @@
+[
+  {
+    "Input": "2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb721611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb20400000000000000000000000000000000000000000000000011138ce750fa15c2",
+    "Expected": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc",
+    "Name": "chfast1",
+    "Gas": 6000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46",
+    "Expected": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e",
+    "Name": "chfast2",
+    "Gas": 6000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "025a6f4181d2b4ea8b724290ffb40156eb0adb514c688556eb79cdea0752c2bb2eff3f31dea215f1eb86023a133a996eb6300b44da664d64251d05381bb8a02e183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3",
+    "Expected": "14789d0d4a730b354403b5fac948113739e276c23e0258d8596ee72f9cd9d3230af18a63153e0ec25ff9f2951dd3fa90ed0197bfef6e2a1a62b5095b9d2b4a27",
+    "Name": "chfast3",
+    "Gas": 6000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    "Expected": "2cde5879ba6f13c0b5aa4ef627f159a3347df9722efce88a9afbb20b763b4c411aa7e43076f6aee272755a7f9b84832e71559ba0d2e0b17d5f9f01755e5b0d11",
+    "Name": "cdetrio1",
+    "Gas": 6000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f630644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",
+    "Expected": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe3163511ddc1c3f25d396745388200081287b3fd1472d8339d5fecb2eae0830451",
+    "Name": "cdetrio2",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000100000000000000000000000000000000",
+    "Expected": "1051acb0700ec6d42a88215852d582efbaef31529b6fcbc3277b5c1b300f5cf0135b2394bb45ab04b8bd7611bd2dfe1de6a4e6e2ccea1ea1955f577cd66af85b",
+    "Name": "cdetrio3",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000009",
+    "Expected": "1dbad7d39dbc56379f78fac1bca147dc8e66de1b9d183c7b167351bfe0aeab742cd757d51289cd8dbd0acf9e673ad67d0f0a89f912af47ed1be53664f5692575",
+    "Name": "cdetrio4",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f60000000000000000000000000000000000000000000000000000000000000001",
+    "Expected": "1a87b0584ce92f4593d161480614f2989035225609f08058ccfa3d0f940febe31a2f3c951f6dadcc7ee9007dff81504b0fcd6d7cf59996efdc33d92bf7f9f8f6",
+    "Name": "cdetrio5",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    "Expected": "29e587aadd7c06722aabba753017c093f70ba7eb1f1c0104ec0564e7e3e21f6022b1143f6a41008e7755c71c3d00b6b915d386de21783ef590486d8afa8453b1",
+    "Name": "cdetrio6",
+    "Gas": 6000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",
+    "Expected": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa92e83f8d734803fc370eba25ed1f6b8768bd6d83887b87165fc2434fe11a830cb",
+    "Name": "cdetrio7",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000100000000000000000000000000000000",
+    "Expected": "221a3577763877920d0d14a91cd59b9479f83b87a653bb41f82a3f6f120cea7c2752c7f64cdd7f0e494bff7b60419f242210f2026ed2ec70f89f78a4c56a1f15",
+    "Name": "cdetrio8",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000009",
+    "Expected": "228e687a379ba154554040f8821f4e41ee2be287c201aa9c3bc02c9dd12f1e691e0fd6ee672d04cfd924ed8fdc7ba5f2d06c53c1edc30f65f2af5a5b97f0a76a",
+    "Name": "cdetrio9",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c0000000000000000000000000000000000000000000000000000000000000001",
+    "Expected": "17c139df0efee0f766bc0204762b774362e4ded88953a39ce849a8a7fa163fa901e0559bacb160664764a357af8a9fe70baa9258e0b959273ffc5718c6d4cc7c",
+    "Name": "cdetrio10",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+    "Expected": "00a1a234d08efaa2616607e31eca1980128b00b415c845ff25bba3afcb81dc00242077290ed33906aeb8e42fd98c41bcb9057ba03421af3f2d08cfc441186024",
+    "Name": "cdetrio11",
+    "Gas": 6000,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d9830644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000",
+    "Expected": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b8692929ee761a352600f54921df9bf472e66217e7bb0cee9032e00acc86b3c8bfaf",
+    "Name": "cdetrio12",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000100000000000000000000000000000000",
+    "Expected": "1071b63011e8c222c5a771dfa03c2e11aac9666dd097f2c620852c3951a4376a2f46fe2f73e1cf310a168d56baa5575a8319389d7bfa6b29ee2d908305791434",
+    "Name": "cdetrio13",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000009",
+    "Expected": "19f75b9dd68c080a688774a6213f131e3052bd353a304a189d7a2ee367e3c2582612f545fb9fc89fde80fd81c68fc7dcb27fea5fc124eeda69433cf5c46d2d7f",
+    "Name": "cdetrio14",
+    "Gas": 6000,
+    "NoBenchmark": true
+  },
+  {
+    "Input": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d980000000000000000000000000000000000000000000000000000000000000001",
+    "Expected": "039730ea8dff1254c0fee9c0ea777d29a9c710b7e616683f194f18c43b43b869073a5ffcc6fc7a28c30723d6e58ce577356982d65b833a5a5c15bf9024b43d98",
+    "Name": "cdetrio15",
+    "Gas": 6000,
+    "NoBenchmark": true
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/ecRecover.json b/core/vm/testdata/precompiles/ecRecover.json
new file mode 100644
index 0000000000..4911d6157e
--- /dev/null
+++ b/core/vm/testdata/precompiles/ecRecover.json
@@ -0,0 +1,37 @@
+[
+  {
+    "Input": "a8b53bdf3306a35a7103ab5504a0c9b492295564b6202b1942a84ef300107281000000000000000000000000000000000000000000000000000000000000001b307835653165303366353363653138623737326363623030393366663731663366353366356337356237346463623331613835616138623838393262346538621122334455667788991011121314151617181920212223242526272829303132",
+    "Expected": "",
+    "Gas": 3000,
+    "Name": "CallEcrecoverUnrecoverableKey",
+    "NoBenchmark": false
+  },
+  {
+    "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
+    "Expected": "000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
+    "Gas": 3000,
+    "Name": "ValidKey",
+    "NoBenchmark": false
+  },
+  {
+    "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c100000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
+    "Expected": "",
+    "Gas": 3000,
+    "Name": "InvalidHighV-bits-1",
+    "NoBenchmark": false
+  },
+  {
+    "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000001000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
+    "Expected": "",
+    "Gas": 3000,
+    "Name": "InvalidHighV-bits-2",
+    "NoBenchmark": false
+  },
+  {
+    "Input": "18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000001000000000000000000000011c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
+    "Expected": "",
+    "Gas": 3000,
+    "Name": "InvalidHighV-bits-3",
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blake2f.json b/core/vm/testdata/precompiles/fail-blake2f.json
new file mode 100644
index 0000000000..70835aa5b0
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blake2f.json
@@ -0,0 +1,22 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "vector 0: empty input"
+  },
+  {
+    "Input": "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+    "ExpectedError": "invalid input length",
+    "Name": "vector 1: less than 213 bytes input"
+  },
+  {
+    "Input": "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001",
+    "ExpectedError": "invalid input length",
+    "Name": "vector 2: more than 213 bytes input"
+  },
+  {
+    "Input": "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002",
+    "ExpectedError": "invalid final flag",
+    "Name": "vector 3: malformed final block indicator flag"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsG1Add.json b/core/vm/testdata/precompiles/fail-blsG1Add.json
new file mode 100644
index 0000000000..e58ec0e90e
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsG1Add.json
@@ -0,0 +1,32 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1add_empty_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1add_short_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1add_large_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000108b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_g1add_violate_top_bytes"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_g1add_invalid_field_element"
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_g1add_point_not_on_curve"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsG1Mul.json b/core/vm/testdata/precompiles/fail-blsG1Mul.json
new file mode 100644
index 0000000000..acb8228aaa
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsG1Mul.json
@@ -0,0 +1,32 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1mul_empty_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1mul_short_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1mul_large_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000108b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_g1mul_violate_top_bytes"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_g1mul_invalid_field_element"
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_g1mul_point_not_on_curve"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsG1MultiExp.json b/core/vm/testdata/precompiles/fail-blsG1MultiExp.json
new file mode 100644
index 0000000000..2cd28bd3b5
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsG1MultiExp.json
@@ -0,0 +1,32 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1multiexp_empty_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1multiexp_short_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g1multiexp_large_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_g1multiexp_invalid_field_element"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000108b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e10000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_g1multiexp_violate_top_bytes"
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_g1multiexp_point_not_on_curve"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsG2Add.json b/core/vm/testdata/precompiles/fail-blsG2Add.json
new file mode 100644
index 0000000000..b1fe9d5b8d
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsG2Add.json
@@ -0,0 +1,32 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2add_empty_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b828010000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2add_short_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2add_large_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_g2add_violate_top_bytes"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_g2add_invalid_field_element"
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_g2add_point_not_on_curve"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsG2Mul.json b/core/vm/testdata/precompiles/fail-blsG2Mul.json
new file mode 100644
index 0000000000..c2f0b89c8a
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsG2Mul.json
@@ -0,0 +1,32 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2mul_empty_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b828010000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2mul_short_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2mul_large_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_g2mul_violate_top_bytes"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_g2mul_invalid_field_element"
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_g2mul_point_not_on_curve"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsG2MultiExp.json b/core/vm/testdata/precompiles/fail-blsG2MultiExp.json
new file mode 100644
index 0000000000..437f8dfca5
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsG2MultiExp.json
@@ -0,0 +1,32 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2multiexp_empty_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b828010000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2multiexp_short_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_g2multiexp_large_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_g2multiexp_violate_top_bytes"
+  },
+  {
+    "Input": "00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac0000000000000000000000000000000000000000000000000000000000000007",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_g2multiexp_invalid_field_element"
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_g2multiexp_point_not_on_curve"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsMapG1.json b/core/vm/testdata/precompiles/fail-blsMapG1.json
new file mode 100644
index 0000000000..8550269f12
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsMapG1.json
@@ -0,0 +1,22 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_mapg1_empty_input"
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_mapg1_short_input"
+  },
+  {
+    "Input": "00000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_mapg1_top_bytes"
+  },
+  {
+    "Input": "000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_mapg1_invalid_fq_element"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsMapG2.json b/core/vm/testdata/precompiles/fail-blsMapG2.json
new file mode 100644
index 0000000000..397a608b0a
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsMapG2.json
@@ -0,0 +1,22 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_mapg2_empty_input"
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_mapg2_short_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_mapg2_top_bytes"
+  },
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_mapg2_invalid_fq_element"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/fail-blsPairing.json b/core/vm/testdata/precompiles/fail-blsPairing.json
new file mode 100644
index 0000000000..084e55635c
--- /dev/null
+++ b/core/vm/testdata/precompiles/fail-blsPairing.json
@@ -0,0 +1,42 @@
+[
+  {
+    "Input": "",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_pairing_empty_input"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b8280100000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "invalid input length",
+    "Name": "bls_pairing_extra_data"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000001a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac",
+    "ExpectedError": "must be less than modulus",
+    "Name": "bls_pairing_invalid_field_element"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000010606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "invalid field element top bytes",
+    "Name": "bls_pairing_top_bytes"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_pairing_g1_not_on_curve"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
+    "ExpectedError": "point is not on curve",
+    "Name": "bls_pairing_g2_not_on_curve"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000a989badd40d6212b33cffc3f3763e9bc760f988c9926b26da9dd85e928483446346b8ed00e1de5d5ea93e354abe706c00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be",
+    "ExpectedError": "g1 point is not on correct subgroup",
+    "Name": "bls_pairing_g1_not_in_correct_subgroup"
+  },
+  {
+    "Input": "0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013a59858b6809fca4d9a3b6539246a70051a3c88899964a42bc9a69cf9acdd9dd387cfa9086b894185b9a46a402be730000000000000000000000000000000002d27e0ec3356299a346a09ad7dc4ef68a483c3aed53f9139d2f929a3eecebf72082e5e58c6da24ee32e03040c406d4f",
+    "ExpectedError": "g2 point is not on correct subgroup",
+    "Name": "bls_pairing_g2_not_in_correct_subgroup"
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/modexp.json b/core/vm/testdata/precompiles/modexp.json
new file mode 100644
index 0000000000..4550eb9138
--- /dev/null
+++ b/core/vm/testdata/precompiles/modexp.json
@@ -0,0 +1,121 @@
+[
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "eip_example1",
+    "Gas": 13056,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "eip_example2",
+    "Gas": 13056,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
+    "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc",
+    "Name": "nagydani-1-square",
+    "Gas": 204,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
+    "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec",
+    "Name": "nagydani-1-qube",
+    "Gas": 204,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
+    "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2",
+    "Name": "nagydani-1-pow0x10001",
+    "Gas": 3276,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
+    "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70",
+    "Name": "nagydani-2-square",
+    "Gas": 665,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
+    "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f",
+    "Name": "nagydani-2-qube",
+    "Gas": 665,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
+    "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2",
+    "Name": "nagydani-2-pow0x10001",
+    "Gas": 10649,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
+    "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8",
+    "Name": "nagydani-3-square",
+    "Gas": 1894,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
+    "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e",
+    "Name": "nagydani-3-qube",
+    "Gas": 1894,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
+    "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76",
+    "Name": "nagydani-3-pow0x10001",
+    "Gas": 30310,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
+    "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10",
+    "Name": "nagydani-4-square",
+    "Gas": 5580,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
+    "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc",
+    "Name": "nagydani-4-qube",
+    "Gas": 5580,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
+    "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963",
+    "Name": "nagydani-4-pow0x10001",
+    "Gas": 89292,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
+    "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb",
+    "Name": "nagydani-5-square",
+    "Gas": 17868,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
+    "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f",
+    "Name": "nagydani-5-qube",
+    "Gas": 17868,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
+    "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500",
+    "Name": "nagydani-5-pow0x10001",
+    "Gas": 285900,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/core/vm/testdata/precompiles/modexp_eip2565.json b/core/vm/testdata/precompiles/modexp_eip2565.json
new file mode 100644
index 0000000000..c55441439e
--- /dev/null
+++ b/core/vm/testdata/precompiles/modexp_eip2565.json
@@ -0,0 +1,121 @@
+[
+  {
+    "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000001",
+    "Name": "eip_example1",
+    "Gas": 1360,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
+    "Expected": "0000000000000000000000000000000000000000000000000000000000000000",
+    "Name": "eip_example2",
+    "Gas": 1360,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
+    "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc",
+    "Name": "nagydani-1-square",
+    "Gas": 200,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
+    "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec",
+    "Name": "nagydani-1-qube",
+    "Gas": 200,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b",
+    "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2",
+    "Name": "nagydani-1-pow0x10001",
+    "Gas": 341,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
+    "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70",
+    "Name": "nagydani-2-square",
+    "Gas": 200,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
+    "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f",
+    "Name": "nagydani-2-qube",
+    "Gas": 200,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087",
+    "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2",
+    "Name": "nagydani-2-pow0x10001",
+    "Gas": 1365,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
+    "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8",
+    "Name": "nagydani-3-square",
+    "Gas": 341,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
+    "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e",
+    "Name": "nagydani-3-qube",
+    "Gas": 341,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d",
+    "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76",
+    "Name": "nagydani-3-pow0x10001",
+    "Gas": 5461,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
+    "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10",
+    "Name": "nagydani-4-square",
+    "Gas": 1365,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
+    "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc",
+    "Name": "nagydani-4-qube",
+    "Gas": 1365,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f",
+    "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963",
+    "Name": "nagydani-4-pow0x10001",
+    "Gas": 21845,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
+    "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb",
+    "Name": "nagydani-5-square",
+    "Gas": 5461,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
+    "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f",
+    "Name": "nagydani-5-qube",
+    "Gas": 5461,
+    "NoBenchmark": false
+  },
+  {
+    "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad",
+    "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500",
+    "Name": "nagydani-5-pow0x10001",
+    "Gas": 87381,
+    "NoBenchmark": false
+  }
+]
\ No newline at end of file
diff --git a/crypto/bls12381/arithmetic_decl.go b/crypto/bls12381/arithmetic_decl.go
new file mode 100644
index 0000000000..ec0b21e805
--- /dev/null
+++ b/crypto/bls12381/arithmetic_decl.go
@@ -0,0 +1,83 @@
+// 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 .
+
+// +build amd64,blsasm amd64,blsadx
+
+package bls12381
+
+import (
+	"golang.org/x/sys/cpu"
+)
+
+func init() {
+	if !enableADX || !cpu.X86.HasADX || !cpu.X86.HasBMI2 {
+		mul = mulNoADX
+	}
+}
+
+// Use ADX backend for default
+var mul func(c, a, b *fe) = mulADX
+
+func square(c, a *fe) {
+	mul(c, a, a)
+}
+
+func neg(c, a *fe) {
+	if a.isZero() {
+		c.set(a)
+	} else {
+		_neg(c, a)
+	}
+}
+
+//go:noescape
+func add(c, a, b *fe)
+
+//go:noescape
+func addAssign(a, b *fe)
+
+//go:noescape
+func ladd(c, a, b *fe)
+
+//go:noescape
+func laddAssign(a, b *fe)
+
+//go:noescape
+func double(c, a *fe)
+
+//go:noescape
+func doubleAssign(a *fe)
+
+//go:noescape
+func ldouble(c, a *fe)
+
+//go:noescape
+func sub(c, a, b *fe)
+
+//go:noescape
+func subAssign(a, b *fe)
+
+//go:noescape
+func lsubAssign(a, b *fe)
+
+//go:noescape
+func _neg(c, a *fe)
+
+//go:noescape
+func mulNoADX(c, a, b *fe)
+
+//go:noescape
+func mulADX(c, a, b *fe)
diff --git a/crypto/bls12381/arithmetic_fallback.go b/crypto/bls12381/arithmetic_fallback.go
new file mode 100644
index 0000000000..91cabf4f3d
--- /dev/null
+++ b/crypto/bls12381/arithmetic_fallback.go
@@ -0,0 +1,566 @@
+// Native go field arithmetic code is generated with 'goff'
+// https://github.com/ConsenSys/goff
+// Many function signature of field operations are renamed.
+
+// Copyright 2020 ConsenSys AG
+//
+// 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.
+
+// field modulus q =
+//
+// 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
+// Code generated by goff DO NOT EDIT
+// goff version: v0.1.0 - build: 790f1f56eac432441e043abff8819eacddd1d668
+// fe are assumed to be in Montgomery form in all methods
+
+// /!\ WARNING /!\
+// this code has not been audited and is provided as-is. In particular,
+// there is no security guarantees such as constant time implementation
+// or side-channel attack resistance
+// /!\ WARNING /!\
+
+// Package bls (generated by goff) contains field arithmetics operations
+
+// +build !amd64 !blsasm,!blsadx
+
+package bls12381
+
+import (
+	"math/bits"
+)
+
+func add(z, x, y *fe) {
+	var carry uint64
+
+	z[0], carry = bits.Add64(x[0], y[0], 0)
+	z[1], carry = bits.Add64(x[1], y[1], carry)
+	z[2], carry = bits.Add64(x[2], y[2], carry)
+	z[3], carry = bits.Add64(x[3], y[3], carry)
+	z[4], carry = bits.Add64(x[4], y[4], carry)
+	z[5], _ = bits.Add64(x[5], y[5], carry)
+
+	// if z > q --> z -= q
+	// note: this is NOT constant time
+	if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) {
+		var b uint64
+		z[0], b = bits.Sub64(z[0], 13402431016077863595, 0)
+		z[1], b = bits.Sub64(z[1], 2210141511517208575, b)
+		z[2], b = bits.Sub64(z[2], 7435674573564081700, b)
+		z[3], b = bits.Sub64(z[3], 7239337960414712511, b)
+		z[4], b = bits.Sub64(z[4], 5412103778470702295, b)
+		z[5], _ = bits.Sub64(z[5], 1873798617647539866, b)
+	}
+}
+
+func addAssign(x, y *fe) {
+	var carry uint64
+
+	x[0], carry = bits.Add64(x[0], y[0], 0)
+	x[1], carry = bits.Add64(x[1], y[1], carry)
+	x[2], carry = bits.Add64(x[2], y[2], carry)
+	x[3], carry = bits.Add64(x[3], y[3], carry)
+	x[4], carry = bits.Add64(x[4], y[4], carry)
+	x[5], _ = bits.Add64(x[5], y[5], carry)
+
+	// if z > q --> z -= q
+	// note: this is NOT constant time
+	if !(x[5] < 1873798617647539866 || (x[5] == 1873798617647539866 && (x[4] < 5412103778470702295 || (x[4] == 5412103778470702295 && (x[3] < 7239337960414712511 || (x[3] == 7239337960414712511 && (x[2] < 7435674573564081700 || (x[2] == 7435674573564081700 && (x[1] < 2210141511517208575 || (x[1] == 2210141511517208575 && (x[0] < 13402431016077863595))))))))))) {
+		var b uint64
+		x[0], b = bits.Sub64(x[0], 13402431016077863595, 0)
+		x[1], b = bits.Sub64(x[1], 2210141511517208575, b)
+		x[2], b = bits.Sub64(x[2], 7435674573564081700, b)
+		x[3], b = bits.Sub64(x[3], 7239337960414712511, b)
+		x[4], b = bits.Sub64(x[4], 5412103778470702295, b)
+		x[5], _ = bits.Sub64(x[5], 1873798617647539866, b)
+	}
+}
+
+func ladd(z, x, y *fe) {
+	var carry uint64
+	z[0], carry = bits.Add64(x[0], y[0], 0)
+	z[1], carry = bits.Add64(x[1], y[1], carry)
+	z[2], carry = bits.Add64(x[2], y[2], carry)
+	z[3], carry = bits.Add64(x[3], y[3], carry)
+	z[4], carry = bits.Add64(x[4], y[4], carry)
+	z[5], _ = bits.Add64(x[5], y[5], carry)
+}
+
+func laddAssign(x, y *fe) {
+	var carry uint64
+	x[0], carry = bits.Add64(x[0], y[0], 0)
+	x[1], carry = bits.Add64(x[1], y[1], carry)
+	x[2], carry = bits.Add64(x[2], y[2], carry)
+	x[3], carry = bits.Add64(x[3], y[3], carry)
+	x[4], carry = bits.Add64(x[4], y[4], carry)
+	x[5], _ = bits.Add64(x[5], y[5], carry)
+}
+
+func double(z, x *fe) {
+	var carry uint64
+
+	z[0], carry = bits.Add64(x[0], x[0], 0)
+	z[1], carry = bits.Add64(x[1], x[1], carry)
+	z[2], carry = bits.Add64(x[2], x[2], carry)
+	z[3], carry = bits.Add64(x[3], x[3], carry)
+	z[4], carry = bits.Add64(x[4], x[4], carry)
+	z[5], _ = bits.Add64(x[5], x[5], carry)
+
+	// if z > q --> z -= q
+	// note: this is NOT constant time
+	if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) {
+		var b uint64
+		z[0], b = bits.Sub64(z[0], 13402431016077863595, 0)
+		z[1], b = bits.Sub64(z[1], 2210141511517208575, b)
+		z[2], b = bits.Sub64(z[2], 7435674573564081700, b)
+		z[3], b = bits.Sub64(z[3], 7239337960414712511, b)
+		z[4], b = bits.Sub64(z[4], 5412103778470702295, b)
+		z[5], _ = bits.Sub64(z[5], 1873798617647539866, b)
+	}
+}
+
+func doubleAssign(z *fe) {
+	var carry uint64
+
+	z[0], carry = bits.Add64(z[0], z[0], 0)
+	z[1], carry = bits.Add64(z[1], z[1], carry)
+	z[2], carry = bits.Add64(z[2], z[2], carry)
+	z[3], carry = bits.Add64(z[3], z[3], carry)
+	z[4], carry = bits.Add64(z[4], z[4], carry)
+	z[5], _ = bits.Add64(z[5], z[5], carry)
+
+	// if z > q --> z -= q
+	// note: this is NOT constant time
+	if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) {
+		var b uint64
+		z[0], b = bits.Sub64(z[0], 13402431016077863595, 0)
+		z[1], b = bits.Sub64(z[1], 2210141511517208575, b)
+		z[2], b = bits.Sub64(z[2], 7435674573564081700, b)
+		z[3], b = bits.Sub64(z[3], 7239337960414712511, b)
+		z[4], b = bits.Sub64(z[4], 5412103778470702295, b)
+		z[5], _ = bits.Sub64(z[5], 1873798617647539866, b)
+	}
+}
+
+func ldouble(z, x *fe) {
+	var carry uint64
+
+	z[0], carry = bits.Add64(x[0], x[0], 0)
+	z[1], carry = bits.Add64(x[1], x[1], carry)
+	z[2], carry = bits.Add64(x[2], x[2], carry)
+	z[3], carry = bits.Add64(x[3], x[3], carry)
+	z[4], carry = bits.Add64(x[4], x[4], carry)
+	z[5], _ = bits.Add64(x[5], x[5], carry)
+}
+
+func sub(z, x, y *fe) {
+	var b uint64
+	z[0], b = bits.Sub64(x[0], y[0], 0)
+	z[1], b = bits.Sub64(x[1], y[1], b)
+	z[2], b = bits.Sub64(x[2], y[2], b)
+	z[3], b = bits.Sub64(x[3], y[3], b)
+	z[4], b = bits.Sub64(x[4], y[4], b)
+	z[5], b = bits.Sub64(x[5], y[5], b)
+	if b != 0 {
+		var c uint64
+		z[0], c = bits.Add64(z[0], 13402431016077863595, 0)
+		z[1], c = bits.Add64(z[1], 2210141511517208575, c)
+		z[2], c = bits.Add64(z[2], 7435674573564081700, c)
+		z[3], c = bits.Add64(z[3], 7239337960414712511, c)
+		z[4], c = bits.Add64(z[4], 5412103778470702295, c)
+		z[5], _ = bits.Add64(z[5], 1873798617647539866, c)
+	}
+}
+
+func subAssign(z, x *fe) {
+	var b uint64
+	z[0], b = bits.Sub64(z[0], x[0], 0)
+	z[1], b = bits.Sub64(z[1], x[1], b)
+	z[2], b = bits.Sub64(z[2], x[2], b)
+	z[3], b = bits.Sub64(z[3], x[3], b)
+	z[4], b = bits.Sub64(z[4], x[4], b)
+	z[5], b = bits.Sub64(z[5], x[5], b)
+	if b != 0 {
+		var c uint64
+		z[0], c = bits.Add64(z[0], 13402431016077863595, 0)
+		z[1], c = bits.Add64(z[1], 2210141511517208575, c)
+		z[2], c = bits.Add64(z[2], 7435674573564081700, c)
+		z[3], c = bits.Add64(z[3], 7239337960414712511, c)
+		z[4], c = bits.Add64(z[4], 5412103778470702295, c)
+		z[5], _ = bits.Add64(z[5], 1873798617647539866, c)
+	}
+}
+
+func lsubAssign(z, x *fe) {
+	var b uint64
+	z[0], b = bits.Sub64(z[0], x[0], 0)
+	z[1], b = bits.Sub64(z[1], x[1], b)
+	z[2], b = bits.Sub64(z[2], x[2], b)
+	z[3], b = bits.Sub64(z[3], x[3], b)
+	z[4], b = bits.Sub64(z[4], x[4], b)
+	z[5], _ = bits.Sub64(z[5], x[5], b)
+}
+
+func neg(z *fe, x *fe) {
+	if x.isZero() {
+		z.zero()
+		return
+	}
+	var borrow uint64
+	z[0], borrow = bits.Sub64(13402431016077863595, x[0], 0)
+	z[1], borrow = bits.Sub64(2210141511517208575, x[1], borrow)
+	z[2], borrow = bits.Sub64(7435674573564081700, x[2], borrow)
+	z[3], borrow = bits.Sub64(7239337960414712511, x[3], borrow)
+	z[4], borrow = bits.Sub64(5412103778470702295, x[4], borrow)
+	z[5], _ = bits.Sub64(1873798617647539866, x[5], borrow)
+}
+
+func mul(z, x, y *fe) {
+	var t [6]uint64
+	var c [3]uint64
+	{
+		// round 0
+		v := x[0]
+		c[1], c[0] = bits.Mul64(v, y[0])
+		m := c[0] * 9940570264628428797
+		c[2] = madd0(m, 13402431016077863595, c[0])
+		c[1], c[0] = madd1(v, y[1], c[1])
+		c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0])
+		c[1], c[0] = madd1(v, y[2], c[1])
+		c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0])
+		c[1], c[0] = madd1(v, y[3], c[1])
+		c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0])
+		c[1], c[0] = madd1(v, y[4], c[1])
+		c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0])
+		c[1], c[0] = madd1(v, y[5], c[1])
+		t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1])
+	}
+	{
+		// round 1
+		v := x[1]
+		c[1], c[0] = madd1(v, y[0], t[0])
+		m := c[0] * 9940570264628428797
+		c[2] = madd0(m, 13402431016077863595, c[0])
+		c[1], c[0] = madd2(v, y[1], c[1], t[1])
+		c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0])
+		c[1], c[0] = madd2(v, y[2], c[1], t[2])
+		c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0])
+		c[1], c[0] = madd2(v, y[3], c[1], t[3])
+		c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0])
+		c[1], c[0] = madd2(v, y[4], c[1], t[4])
+		c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0])
+		c[1], c[0] = madd2(v, y[5], c[1], t[5])
+		t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1])
+	}
+	{
+		// round 2
+		v := x[2]
+		c[1], c[0] = madd1(v, y[0], t[0])
+		m := c[0] * 9940570264628428797
+		c[2] = madd0(m, 13402431016077863595, c[0])
+		c[1], c[0] = madd2(v, y[1], c[1], t[1])
+		c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0])
+		c[1], c[0] = madd2(v, y[2], c[1], t[2])
+		c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0])
+		c[1], c[0] = madd2(v, y[3], c[1], t[3])
+		c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0])
+		c[1], c[0] = madd2(v, y[4], c[1], t[4])
+		c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0])
+		c[1], c[0] = madd2(v, y[5], c[1], t[5])
+		t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1])
+	}
+	{
+		// round 3
+		v := x[3]
+		c[1], c[0] = madd1(v, y[0], t[0])
+		m := c[0] * 9940570264628428797
+		c[2] = madd0(m, 13402431016077863595, c[0])
+		c[1], c[0] = madd2(v, y[1], c[1], t[1])
+		c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0])
+		c[1], c[0] = madd2(v, y[2], c[1], t[2])
+		c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0])
+		c[1], c[0] = madd2(v, y[3], c[1], t[3])
+		c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0])
+		c[1], c[0] = madd2(v, y[4], c[1], t[4])
+		c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0])
+		c[1], c[0] = madd2(v, y[5], c[1], t[5])
+		t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1])
+	}
+	{
+		// round 4
+		v := x[4]
+		c[1], c[0] = madd1(v, y[0], t[0])
+		m := c[0] * 9940570264628428797
+		c[2] = madd0(m, 13402431016077863595, c[0])
+		c[1], c[0] = madd2(v, y[1], c[1], t[1])
+		c[2], t[0] = madd2(m, 2210141511517208575, c[2], c[0])
+		c[1], c[0] = madd2(v, y[2], c[1], t[2])
+		c[2], t[1] = madd2(m, 7435674573564081700, c[2], c[0])
+		c[1], c[0] = madd2(v, y[3], c[1], t[3])
+		c[2], t[2] = madd2(m, 7239337960414712511, c[2], c[0])
+		c[1], c[0] = madd2(v, y[4], c[1], t[4])
+		c[2], t[3] = madd2(m, 5412103778470702295, c[2], c[0])
+		c[1], c[0] = madd2(v, y[5], c[1], t[5])
+		t[5], t[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1])
+	}
+	{
+		// round 5
+		v := x[5]
+		c[1], c[0] = madd1(v, y[0], t[0])
+		m := c[0] * 9940570264628428797
+		c[2] = madd0(m, 13402431016077863595, c[0])
+		c[1], c[0] = madd2(v, y[1], c[1], t[1])
+		c[2], z[0] = madd2(m, 2210141511517208575, c[2], c[0])
+		c[1], c[0] = madd2(v, y[2], c[1], t[2])
+		c[2], z[1] = madd2(m, 7435674573564081700, c[2], c[0])
+		c[1], c[0] = madd2(v, y[3], c[1], t[3])
+		c[2], z[2] = madd2(m, 7239337960414712511, c[2], c[0])
+		c[1], c[0] = madd2(v, y[4], c[1], t[4])
+		c[2], z[3] = madd2(m, 5412103778470702295, c[2], c[0])
+		c[1], c[0] = madd2(v, y[5], c[1], t[5])
+		z[5], z[4] = madd3(m, 1873798617647539866, c[0], c[2], c[1])
+	}
+
+	// if z > q --> z -= q
+	// note: this is NOT constant time
+	if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) {
+		var b uint64
+		z[0], b = bits.Sub64(z[0], 13402431016077863595, 0)
+		z[1], b = bits.Sub64(z[1], 2210141511517208575, b)
+		z[2], b = bits.Sub64(z[2], 7435674573564081700, b)
+		z[3], b = bits.Sub64(z[3], 7239337960414712511, b)
+		z[4], b = bits.Sub64(z[4], 5412103778470702295, b)
+		z[5], _ = bits.Sub64(z[5], 1873798617647539866, b)
+	}
+}
+
+func square(z, x *fe) {
+
+	var p [6]uint64
+
+	var u, v uint64
+	{
+		// round 0
+		u, p[0] = bits.Mul64(x[0], x[0])
+		m := p[0] * 9940570264628428797
+		C := madd0(m, 13402431016077863595, p[0])
+		var t uint64
+		t, u, v = madd1sb(x[0], x[1], u)
+		C, p[0] = madd2(m, 2210141511517208575, v, C)
+		t, u, v = madd1s(x[0], x[2], t, u)
+		C, p[1] = madd2(m, 7435674573564081700, v, C)
+		t, u, v = madd1s(x[0], x[3], t, u)
+		C, p[2] = madd2(m, 7239337960414712511, v, C)
+		t, u, v = madd1s(x[0], x[4], t, u)
+		C, p[3] = madd2(m, 5412103778470702295, v, C)
+		_, u, v = madd1s(x[0], x[5], t, u)
+		p[5], p[4] = madd3(m, 1873798617647539866, v, C, u)
+	}
+	{
+		// round 1
+		m := p[0] * 9940570264628428797
+		C := madd0(m, 13402431016077863595, p[0])
+		u, v = madd1(x[1], x[1], p[1])
+		C, p[0] = madd2(m, 2210141511517208575, v, C)
+		var t uint64
+		t, u, v = madd2sb(x[1], x[2], p[2], u)
+		C, p[1] = madd2(m, 7435674573564081700, v, C)
+		t, u, v = madd2s(x[1], x[3], p[3], t, u)
+		C, p[2] = madd2(m, 7239337960414712511, v, C)
+		t, u, v = madd2s(x[1], x[4], p[4], t, u)
+		C, p[3] = madd2(m, 5412103778470702295, v, C)
+		_, u, v = madd2s(x[1], x[5], p[5], t, u)
+		p[5], p[4] = madd3(m, 1873798617647539866, v, C, u)
+	}
+	{
+		// round 2
+		m := p[0] * 9940570264628428797
+		C := madd0(m, 13402431016077863595, p[0])
+		C, p[0] = madd2(m, 2210141511517208575, p[1], C)
+		u, v = madd1(x[2], x[2], p[2])
+		C, p[1] = madd2(m, 7435674573564081700, v, C)
+		var t uint64
+		t, u, v = madd2sb(x[2], x[3], p[3], u)
+		C, p[2] = madd2(m, 7239337960414712511, v, C)
+		t, u, v = madd2s(x[2], x[4], p[4], t, u)
+		C, p[3] = madd2(m, 5412103778470702295, v, C)
+		_, u, v = madd2s(x[2], x[5], p[5], t, u)
+		p[5], p[4] = madd3(m, 1873798617647539866, v, C, u)
+	}
+	{
+		// round 3
+		m := p[0] * 9940570264628428797
+		C := madd0(m, 13402431016077863595, p[0])
+		C, p[0] = madd2(m, 2210141511517208575, p[1], C)
+		C, p[1] = madd2(m, 7435674573564081700, p[2], C)
+		u, v = madd1(x[3], x[3], p[3])
+		C, p[2] = madd2(m, 7239337960414712511, v, C)
+		var t uint64
+		t, u, v = madd2sb(x[3], x[4], p[4], u)
+		C, p[3] = madd2(m, 5412103778470702295, v, C)
+		_, u, v = madd2s(x[3], x[5], p[5], t, u)
+		p[5], p[4] = madd3(m, 1873798617647539866, v, C, u)
+	}
+	{
+		// round 4
+		m := p[0] * 9940570264628428797
+		C := madd0(m, 13402431016077863595, p[0])
+		C, p[0] = madd2(m, 2210141511517208575, p[1], C)
+		C, p[1] = madd2(m, 7435674573564081700, p[2], C)
+		C, p[2] = madd2(m, 7239337960414712511, p[3], C)
+		u, v = madd1(x[4], x[4], p[4])
+		C, p[3] = madd2(m, 5412103778470702295, v, C)
+		_, u, v = madd2sb(x[4], x[5], p[5], u)
+		p[5], p[4] = madd3(m, 1873798617647539866, v, C, u)
+	}
+	{
+		// round 5
+		m := p[0] * 9940570264628428797
+		C := madd0(m, 13402431016077863595, p[0])
+		C, z[0] = madd2(m, 2210141511517208575, p[1], C)
+		C, z[1] = madd2(m, 7435674573564081700, p[2], C)
+		C, z[2] = madd2(m, 7239337960414712511, p[3], C)
+		C, z[3] = madd2(m, 5412103778470702295, p[4], C)
+		u, v = madd1(x[5], x[5], p[5])
+		z[5], z[4] = madd3(m, 1873798617647539866, v, C, u)
+	}
+
+	// if z > q --> z -= q
+	// note: this is NOT constant time
+	if !(z[5] < 1873798617647539866 || (z[5] == 1873798617647539866 && (z[4] < 5412103778470702295 || (z[4] == 5412103778470702295 && (z[3] < 7239337960414712511 || (z[3] == 7239337960414712511 && (z[2] < 7435674573564081700 || (z[2] == 7435674573564081700 && (z[1] < 2210141511517208575 || (z[1] == 2210141511517208575 && (z[0] < 13402431016077863595))))))))))) {
+		var b uint64
+		z[0], b = bits.Sub64(z[0], 13402431016077863595, 0)
+		z[1], b = bits.Sub64(z[1], 2210141511517208575, b)
+		z[2], b = bits.Sub64(z[2], 7435674573564081700, b)
+		z[3], b = bits.Sub64(z[3], 7239337960414712511, b)
+		z[4], b = bits.Sub64(z[4], 5412103778470702295, b)
+		z[5], _ = bits.Sub64(z[5], 1873798617647539866, b)
+	}
+}
+
+// arith.go
+// Copyright 2020 ConsenSys AG
+//
+// 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.
+
+// Code generated by goff DO NOT EDIT
+
+func madd(a, b, t, u, v uint64) (uint64, uint64, uint64) {
+	var carry uint64
+	hi, lo := bits.Mul64(a, b)
+	v, carry = bits.Add64(lo, v, 0)
+	u, carry = bits.Add64(hi, u, carry)
+	t, _ = bits.Add64(t, 0, carry)
+	return t, u, v
+}
+
+// madd0 hi = a*b + c (discards lo bits)
+func madd0(a, b, c uint64) (hi uint64) {
+	var carry, lo uint64
+	hi, lo = bits.Mul64(a, b)
+	_, carry = bits.Add64(lo, c, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	return
+}
+
+// madd1 hi, lo = a*b + c
+func madd1(a, b, c uint64) (hi uint64, lo uint64) {
+	var carry uint64
+	hi, lo = bits.Mul64(a, b)
+	lo, carry = bits.Add64(lo, c, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	return
+}
+
+// madd2 hi, lo = a*b + c + d
+func madd2(a, b, c, d uint64) (hi uint64, lo uint64) {
+	var carry uint64
+	hi, lo = bits.Mul64(a, b)
+	c, carry = bits.Add64(c, d, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	lo, carry = bits.Add64(lo, c, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	return
+}
+
+// madd2s superhi, hi, lo = 2*a*b + c + d + e
+func madd2s(a, b, c, d, e uint64) (superhi, hi, lo uint64) {
+	var carry, sum uint64
+
+	hi, lo = bits.Mul64(a, b)
+	lo, carry = bits.Add64(lo, lo, 0)
+	hi, superhi = bits.Add64(hi, hi, carry)
+
+	sum, carry = bits.Add64(c, e, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	lo, carry = bits.Add64(lo, sum, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	hi, _ = bits.Add64(hi, 0, d)
+	return
+}
+
+func madd1s(a, b, d, e uint64) (superhi, hi, lo uint64) {
+	var carry uint64
+
+	hi, lo = bits.Mul64(a, b)
+	lo, carry = bits.Add64(lo, lo, 0)
+	hi, superhi = bits.Add64(hi, hi, carry)
+	lo, carry = bits.Add64(lo, e, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	hi, _ = bits.Add64(hi, 0, d)
+	return
+}
+
+func madd2sb(a, b, c, e uint64) (superhi, hi, lo uint64) {
+	var carry, sum uint64
+
+	hi, lo = bits.Mul64(a, b)
+	lo, carry = bits.Add64(lo, lo, 0)
+	hi, superhi = bits.Add64(hi, hi, carry)
+
+	sum, carry = bits.Add64(c, e, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	lo, carry = bits.Add64(lo, sum, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	return
+}
+
+func madd1sb(a, b, e uint64) (superhi, hi, lo uint64) {
+	var carry uint64
+
+	hi, lo = bits.Mul64(a, b)
+	lo, carry = bits.Add64(lo, lo, 0)
+	hi, superhi = bits.Add64(hi, hi, carry)
+	lo, carry = bits.Add64(lo, e, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	return
+}
+
+func madd3(a, b, c, d, e uint64) (hi uint64, lo uint64) {
+	var carry uint64
+	hi, lo = bits.Mul64(a, b)
+	c, carry = bits.Add64(c, d, 0)
+	hi, _ = bits.Add64(hi, 0, carry)
+	lo, carry = bits.Add64(lo, c, 0)
+	hi, _ = bits.Add64(hi, e, carry)
+	return
+}
diff --git a/crypto/bls12381/arithmetic_x86.s b/crypto/bls12381/arithmetic_x86.s
new file mode 100644
index 0000000000..2cebbc46f7
--- /dev/null
+++ b/crypto/bls12381/arithmetic_x86.s
@@ -0,0 +1,2150 @@
+// +build amd64,blsasm amd64,blsadx
+
+#include "textflag.h"
+
+// addition w/ modular reduction
+// a = (a + b) % p
+TEXT ·addAssign(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ b+8(FP), SI
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+
+	// |
+	ADDQ (SI), R8
+	ADCQ 8(SI), R9
+	ADCQ 16(SI), R10
+	ADCQ 24(SI), R11
+	ADCQ 32(SI), R12
+	ADCQ 40(SI), R13
+
+	// |
+	MOVQ R8, R14
+	MOVQ R9, R15
+	MOVQ R10, CX
+	MOVQ R11, DX
+	MOVQ R12, SI
+	MOVQ R13, BX
+	MOVQ $0xb9feffffffffaaab, AX
+	SUBQ AX, R14
+	MOVQ $0x1eabfffeb153ffff, AX
+	SBBQ AX, R15
+	MOVQ $0x6730d2a0f6b0f624, AX
+	SBBQ AX, CX
+	MOVQ $0x64774b84f38512bf, AX
+	SBBQ AX, DX
+	MOVQ $0x4b1ba7b6434bacd7, AX
+	SBBQ AX, SI
+	MOVQ $0x1a0111ea397fe69a, AX
+	SBBQ AX, BX
+	CMOVQCC R14, R8
+	CMOVQCC R15, R9
+	CMOVQCC CX, R10
+	CMOVQCC DX, R11
+	CMOVQCC SI, R12
+	CMOVQCC BX, R13
+
+	// |
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+
+/*	 | end											*/
+
+
+// addition w/ modular reduction
+// c = (a + b) % p
+TEXT ·add(SB), NOSPLIT, $0-24
+	// |
+	MOVQ a+8(FP), DI
+	MOVQ b+16(FP), SI
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+
+	// |
+	ADDQ (SI), R8
+	ADCQ 8(SI), R9
+	ADCQ 16(SI), R10
+	ADCQ 24(SI), R11
+	ADCQ 32(SI), R12
+	ADCQ 40(SI), R13
+
+	// |
+	MOVQ R8, R14
+	MOVQ R9, R15
+	MOVQ R10, CX
+	MOVQ R11, DX
+	MOVQ R12, SI
+	MOVQ R13, BX
+	MOVQ $0xb9feffffffffaaab, DI
+	SUBQ DI, R14
+	MOVQ $0x1eabfffeb153ffff, DI
+	SBBQ DI, R15
+	MOVQ $0x6730d2a0f6b0f624, DI
+	SBBQ DI, CX
+	MOVQ $0x64774b84f38512bf, DI
+	SBBQ DI, DX
+	MOVQ $0x4b1ba7b6434bacd7, DI
+	SBBQ DI, SI
+	MOVQ $0x1a0111ea397fe69a, DI
+	SBBQ DI, BX
+	CMOVQCC R14, R8
+	CMOVQCC R15, R9
+	CMOVQCC CX, R10
+	CMOVQCC DX, R11
+	CMOVQCC SI, R12
+	CMOVQCC BX, R13
+
+	// |
+	MOVQ c+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// addition w/o reduction check
+// c = (a + b)
+TEXT ·ladd(SB), NOSPLIT, $0-24
+	// |
+	MOVQ a+8(FP), DI
+	MOVQ b+16(FP), SI
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+
+	// |
+	ADDQ (SI), R8
+	ADCQ 8(SI), R9
+	ADCQ 16(SI), R10
+	ADCQ 24(SI), R11
+	ADCQ 32(SI), R12
+	ADCQ 40(SI), R13
+
+	// |
+	MOVQ c+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// addition w/o reduction check
+// a = a + b
+TEXT ·laddAssign(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ b+8(FP), SI
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+
+	// |
+	ADDQ (SI), R8
+	ADCQ 8(SI), R9
+	ADCQ 16(SI), R10
+	ADCQ 24(SI), R11
+	ADCQ 32(SI), R12
+	ADCQ 40(SI), R13
+
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// subtraction w/ modular reduction
+// c = (a - b) % p
+TEXT ·sub(SB), NOSPLIT, $0-24
+	// |
+	MOVQ a+8(FP), DI
+	MOVQ b+16(FP), SI
+	XORQ AX, AX
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+	SUBQ (SI), R8
+	SBBQ 8(SI), R9
+	SBBQ 16(SI), R10
+	SBBQ 24(SI), R11
+	SBBQ 32(SI), R12
+	SBBQ 40(SI), R13
+
+	// |
+	MOVQ $0xb9feffffffffaaab, R14
+	MOVQ $0x1eabfffeb153ffff, R15
+	MOVQ $0x6730d2a0f6b0f624, CX
+	MOVQ $0x64774b84f38512bf, DX
+	MOVQ $0x4b1ba7b6434bacd7, SI
+	MOVQ $0x1a0111ea397fe69a, BX
+	CMOVQCC AX, R14
+	CMOVQCC AX, R15
+	CMOVQCC AX, CX
+	CMOVQCC AX, DX
+	CMOVQCC AX, SI
+	CMOVQCC AX, BX
+	ADDQ R14, R8
+	ADCQ R15, R9
+	ADCQ CX, R10
+	ADCQ DX, R11
+	ADCQ SI, R12
+	ADCQ BX, R13
+
+	// |
+	MOVQ c+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// subtraction w/ modular reduction
+// a = (a - b) % p
+TEXT ·subAssign(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ b+8(FP), SI
+	XORQ AX, AX
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+	SUBQ (SI), R8
+	SBBQ 8(SI), R9
+	SBBQ 16(SI), R10
+	SBBQ 24(SI), R11
+	SBBQ 32(SI), R12
+	SBBQ 40(SI), R13
+
+	// |
+	MOVQ $0xb9feffffffffaaab, R14
+	MOVQ $0x1eabfffeb153ffff, R15
+	MOVQ $0x6730d2a0f6b0f624, CX
+	MOVQ $0x64774b84f38512bf, DX
+	MOVQ $0x4b1ba7b6434bacd7, SI
+	MOVQ $0x1a0111ea397fe69a, BX
+	CMOVQCC AX, R14
+	CMOVQCC AX, R15
+	CMOVQCC AX, CX
+	CMOVQCC AX, DX
+	CMOVQCC AX, SI
+	CMOVQCC AX, BX
+	ADDQ R14, R8
+	ADCQ R15, R9
+	ADCQ CX, R10
+	ADCQ DX, R11
+	ADCQ SI, R12
+	ADCQ BX, R13
+
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// subtraction w/o reduction check
+// a = (a - b)
+TEXT ·lsubAssign(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ b+8(FP), SI
+
+	// |
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+	SUBQ (SI), R8
+	SBBQ 8(SI), R9
+	SBBQ 16(SI), R10
+	SBBQ 24(SI), R11
+	SBBQ 32(SI), R12
+	SBBQ 40(SI), R13
+	
+	// |
+	MOVQ a+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+// doubling w/ reduction
+// c = (2 * a) % p
+TEXT ·double(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+8(FP), DI
+
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+	ADDQ R8, R8
+	ADCQ R9, R9
+	ADCQ R10, R10
+	ADCQ R11, R11
+	ADCQ R12, R12
+	ADCQ R13, R13
+
+	// |
+	MOVQ R8, R14
+	MOVQ R9, R15
+	MOVQ R10, CX
+	MOVQ R11, DX
+	MOVQ R12, SI
+	MOVQ R13, BX
+	MOVQ $0xb9feffffffffaaab, DI
+	SUBQ DI, R14
+	MOVQ $0x1eabfffeb153ffff, DI
+	SBBQ DI, R15
+	MOVQ $0x6730d2a0f6b0f624, DI
+	SBBQ DI, CX
+	MOVQ $0x64774b84f38512bf, DI
+	SBBQ DI, DX
+	MOVQ $0x4b1ba7b6434bacd7, DI
+	SBBQ DI, SI
+	MOVQ $0x1a0111ea397fe69a, DI
+	SBBQ DI, BX
+	CMOVQCC R14, R8
+	CMOVQCC R15, R9
+	CMOVQCC CX, R10
+	CMOVQCC DX, R11
+	CMOVQCC SI, R12
+	CMOVQCC BX, R13
+
+	// |
+	MOVQ c+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// doubling w/ reduction
+// a = (2 * a) % p
+TEXT ·doubleAssign(SB), NOSPLIT, $0-8
+	// |
+	MOVQ a+0(FP), DI
+
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+	ADDQ R8, R8
+	ADCQ R9, R9
+	ADCQ R10, R10
+	ADCQ R11, R11
+	ADCQ R12, R12
+	ADCQ R13, R13
+
+	// |
+	MOVQ R8, R14
+	MOVQ R9, R15
+	MOVQ R10, CX
+	MOVQ R11, DX
+	MOVQ R12, SI
+	MOVQ R13, BX
+	MOVQ $0xb9feffffffffaaab, AX
+	SUBQ AX, R14
+	MOVQ $0x1eabfffeb153ffff, AX
+	SBBQ AX, R15
+	MOVQ $0x6730d2a0f6b0f624, AX
+	SBBQ AX, CX
+	MOVQ $0x64774b84f38512bf, AX
+	SBBQ AX, DX
+	MOVQ $0x4b1ba7b6434bacd7, AX
+	SBBQ AX, SI
+	MOVQ $0x1a0111ea397fe69a, AX
+	SBBQ AX, BX
+	CMOVQCC R14, R8
+	CMOVQCC R15, R9
+	CMOVQCC CX, R10
+	CMOVQCC DX, R11
+	CMOVQCC SI, R12
+	CMOVQCC BX, R13
+
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// doubling w/o reduction
+// c = 2 * a
+TEXT ·ldouble(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+8(FP), DI
+
+	MOVQ (DI), R8
+	MOVQ 8(DI), R9
+	MOVQ 16(DI), R10
+	MOVQ 24(DI), R11
+	MOVQ 32(DI), R12
+	MOVQ 40(DI), R13
+
+	// |
+	ADDQ R8, R8
+	ADCQ R9, R9
+	ADCQ R10, R10
+	ADCQ R11, R11
+	ADCQ R12, R12
+	ADCQ R13, R13
+
+	// |
+	MOVQ c+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+
+	RET
+/*	 | end													*/
+
+
+TEXT ·_neg(SB), NOSPLIT, $0-16
+	// |
+	MOVQ a+8(FP), DI
+
+	// |
+	MOVQ $0xb9feffffffffaaab, R8
+	MOVQ $0x1eabfffeb153ffff, R9
+	MOVQ $0x6730d2a0f6b0f624, R10
+	MOVQ $0x64774b84f38512bf, R11
+	MOVQ $0x4b1ba7b6434bacd7, R12
+	MOVQ $0x1a0111ea397fe69a, R13
+	SUBQ (DI), R8
+	SBBQ 8(DI), R9
+	SBBQ 16(DI), R10
+	SBBQ 24(DI), R11
+	SBBQ 32(DI), R12
+	SBBQ 40(DI), R13
+
+	// |
+	MOVQ c+0(FP), DI
+	MOVQ R8, (DI)
+	MOVQ R9, 8(DI)
+	MOVQ R10, 16(DI)
+	MOVQ R11, 24(DI)
+	MOVQ R12, 32(DI)
+	MOVQ R13, 40(DI)
+	RET
+/*	 | end													*/
+
+
+// multiplication without using MULX/ADX
+// c = a * b % p
+TEXT ·mulNoADX(SB), NOSPLIT, $24-24
+	// |
+
+/* inputs                                  */
+
+	MOVQ a+8(FP), DI
+	MOVQ b+16(FP), SI
+	MOVQ $0x00, R9
+	MOVQ $0x00, R10
+	MOVQ $0x00, R11
+	MOVQ $0x00, R12
+	MOVQ $0x00, R13
+	MOVQ $0x00, R14
+	MOVQ $0x00, R15
+
+	// |
+
+/* i0                                   */
+
+	// | a0 @ CX
+	MOVQ (DI), CX
+
+	// | a0 * b0
+	MOVQ (SI), AX
+	MULQ CX
+	MOVQ AX, (SP)
+	MOVQ DX, R8
+
+	// | a0 * b1
+	MOVQ 8(SI), AX
+	MULQ CX
+	ADDQ AX, R8
+	ADCQ DX, R9
+
+	// | a0 * b2
+	MOVQ 16(SI), AX
+	MULQ CX
+	ADDQ AX, R9
+	ADCQ DX, R10
+
+	// | a0 * b3
+	MOVQ 24(SI), AX
+	MULQ CX
+	ADDQ AX, R10
+	ADCQ DX, R11
+
+	// | a0 * b4
+	MOVQ 32(SI), AX
+	MULQ CX
+	ADDQ AX, R11
+	ADCQ DX, R12
+
+	// | a0 * b5
+	MOVQ 40(SI), AX
+	MULQ CX
+	ADDQ AX, R12
+	ADCQ DX, R13
+
+	// |
+
+/* i1                                   */
+
+	// | a1 @ CX
+	MOVQ 8(DI), CX
+	MOVQ $0x00, BX
+
+	// | a1 * b0
+	MOVQ (SI), AX
+	MULQ CX
+	ADDQ AX, R8
+	ADCQ DX, R9
+	ADCQ $0x00, R10
+	ADCQ $0x00, BX
+	MOVQ R8, 8(SP)
+	MOVQ $0x00, R8
+
+	// | a1 * b1
+	MOVQ 8(SI), AX
+	MULQ CX
+	ADDQ AX, R9
+	ADCQ DX, R10
+	ADCQ BX, R11
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a1 * b2
+	MOVQ 16(SI), AX
+	MULQ CX
+	ADDQ AX, R10
+	ADCQ DX, R11
+	ADCQ BX, R12
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a1 * b3
+	MOVQ 24(SI), AX
+	MULQ CX
+	ADDQ AX, R11
+	ADCQ DX, R12
+	ADCQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a1 * b4
+	MOVQ 32(SI), AX
+	MULQ CX
+	ADDQ AX, R12
+	ADCQ DX, R13
+	ADCQ BX, R14
+
+	// | a1 * b5
+	MOVQ 40(SI), AX
+	MULQ CX
+	ADDQ AX, R13
+	ADCQ DX, R14
+
+	// |
+
+/* i2                                   */
+
+	// | a2 @ CX
+	MOVQ 16(DI), CX
+	MOVQ $0x00, BX
+
+	// | a2 * b0
+	MOVQ (SI), AX
+	MULQ CX
+	ADDQ AX, R9
+	ADCQ DX, R10
+	ADCQ $0x00, R11
+	ADCQ $0x00, BX
+	MOVQ R9, 16(SP)
+	MOVQ $0x00, R9
+
+	// | a2 * b1
+	MOVQ 8(SI), AX
+	MULQ CX
+	ADDQ AX, R10
+	ADCQ DX, R11
+	ADCQ BX, R12
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a2 * b2
+	MOVQ 16(SI), AX
+	MULQ CX
+	ADDQ AX, R11
+	ADCQ DX, R12
+	ADCQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a2 * b3
+	MOVQ 24(SI), AX
+	MULQ CX
+	ADDQ AX, R12
+	ADCQ DX, R13
+	ADCQ BX, R14
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a2 * b4
+	MOVQ 32(SI), AX
+	MULQ CX
+	ADDQ AX, R13
+	ADCQ DX, R14
+	ADCQ BX, R15
+
+	// | a2 * b5
+	MOVQ 40(SI), AX
+	MULQ CX
+	ADDQ AX, R14
+	ADCQ DX, R15
+
+	// |
+
+/* i3                                   */
+
+	// | a3 @ CX
+	MOVQ 24(DI), CX
+	MOVQ $0x00, BX
+
+	// | a3 * b0
+	MOVQ (SI), AX
+	MULQ CX
+	ADDQ AX, R10
+	ADCQ DX, R11
+	ADCQ $0x00, R12
+	ADCQ $0x00, BX
+
+	// | a3 * b1
+	MOVQ 8(SI), AX
+	MULQ CX
+	ADDQ AX, R11
+	ADCQ DX, R12
+	ADCQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a3 * b2
+	MOVQ 16(SI), AX
+	MULQ CX
+	ADDQ AX, R12
+	ADCQ DX, R13
+	ADCQ BX, R14
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a3 * b3
+	MOVQ 24(SI), AX
+	MULQ CX
+	ADDQ AX, R13
+	ADCQ DX, R14
+	ADCQ BX, R15
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a3 * b4
+	MOVQ 32(SI), AX
+	MULQ CX
+	ADDQ AX, R14
+	ADCQ DX, R15
+	ADCQ BX, R8
+
+	// | a3 * b5
+	MOVQ 40(SI), AX
+	MULQ CX
+	ADDQ AX, R15
+	ADCQ DX, R8
+
+	// |
+
+/* i4                                   */
+
+	// | a4 @ CX
+	MOVQ 32(DI), CX
+	MOVQ $0x00, BX
+
+	// | a4 * b0
+	MOVQ (SI), AX
+	MULQ CX
+	ADDQ AX, R11
+	ADCQ DX, R12
+	ADCQ $0x00, R13
+	ADCQ $0x00, BX
+
+	// | a4 * b1
+	MOVQ 8(SI), AX
+	MULQ CX
+	ADDQ AX, R12
+	ADCQ DX, R13
+	ADCQ BX, R14
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a4 * b2
+	MOVQ 16(SI), AX
+	MULQ CX
+	ADDQ AX, R13
+	ADCQ DX, R14
+	ADCQ BX, R15
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a4 * b3
+	MOVQ 24(SI), AX
+	MULQ CX
+	ADDQ AX, R14
+	ADCQ DX, R15
+	ADCQ BX, R8
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a4 * b4
+	MOVQ 32(SI), AX
+	MULQ CX
+	ADDQ AX, R15
+	ADCQ DX, R8
+	ADCQ BX, R9
+
+	// | a4 * b5
+	MOVQ 40(SI), AX
+	MULQ CX
+	ADDQ AX, R8
+	ADCQ DX, R9
+
+	// |
+
+/* i5                                   */
+
+	// | a5 @ CX
+	MOVQ 40(DI), CX
+	MOVQ $0x00, BX
+
+	// | a5 * b0
+	MOVQ (SI), AX
+	MULQ CX
+	ADDQ AX, R12
+	ADCQ DX, R13
+	ADCQ $0x00, R14
+	ADCQ $0x00, BX
+
+	// | a5 * b1
+	MOVQ 8(SI), AX
+	MULQ CX
+	ADDQ AX, R13
+	ADCQ DX, R14
+	ADCQ BX, R15
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a5 * b2
+	MOVQ 16(SI), AX
+	MULQ CX
+	ADDQ AX, R14
+	ADCQ DX, R15
+	ADCQ BX, R8
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a5 * b3
+	MOVQ 24(SI), AX
+	MULQ CX
+	ADDQ AX, R15
+	ADCQ DX, R8
+	ADCQ BX, R9
+	MOVQ $0x00, BX
+	ADCQ $0x00, BX
+
+	// | a5 * b4
+	MOVQ 32(SI), AX
+	MULQ CX
+	ADDQ AX, R8
+	ADCQ DX, R9
+	ADCQ $0x00, BX
+
+	// | a5 * b5
+	MOVQ 40(SI), AX
+	MULQ CX
+	ADDQ AX, R9
+	ADCQ DX, BX
+
+	// |
+
+/* 			                                     */
+
+	// |
+	// | W
+	// | 0   (SP)      | 1   8(SP)     | 2   16(SP)    | 3   R10       | 4   R11       | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  R9        | 11  BX
+
+
+	MOVQ (SP), CX
+	MOVQ 8(SP), DI
+	MOVQ 16(SP), SI
+	MOVQ BX, (SP)
+	MOVQ R9, 8(SP)
+
+	// |
+
+/* montgomery reduction                    */
+
+	// |
+
+/* i0                                   */
+
+	// |
+	// | W
+	// | 0   CX        | 1   DI        | 2   SI        | 3   R10       | 4   R11       | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  8(SP)     | 11  (SP)
+
+
+	// | | u0 = w0 * inp
+	MOVQ CX, AX
+	MULQ ·inp+0(SB)
+	MOVQ AX, R9
+	MOVQ $0x00, BX
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w0 @ CX
+	MOVQ ·modulus+0(SB), AX
+	MULQ R9
+	ADDQ AX, CX
+	ADCQ DX, BX
+
+	// | j1
+
+	// | w1 @ DI
+	MOVQ ·modulus+8(SB), AX
+	MULQ R9
+	ADDQ AX, DI
+	ADCQ $0x00, DX
+	ADDQ BX, DI
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j2
+
+	// | w2 @ SI
+	MOVQ ·modulus+16(SB), AX
+	MULQ R9
+	ADDQ AX, SI
+	ADCQ $0x00, DX
+	ADDQ BX, SI
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j3
+
+	// | w3 @ R10
+	MOVQ ·modulus+24(SB), AX
+	MULQ R9
+	ADDQ AX, R10
+	ADCQ $0x00, DX
+	ADDQ BX, R10
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j4
+
+	// | w4 @ R11
+	MOVQ ·modulus+32(SB), AX
+	MULQ R9
+	ADDQ AX, R11
+	ADCQ $0x00, DX
+	ADDQ BX, R11
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j5
+
+	// | w5 @ R12
+	MOVQ ·modulus+40(SB), AX
+	MULQ R9
+	ADDQ AX, R12
+	ADCQ $0x00, DX
+	ADDQ BX, R12
+
+	// | w6 @ R13
+	ADCQ DX, R13
+	ADCQ $0x00, CX
+
+	// |
+
+/* i1                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   DI        | 2   SI        | 3   R10       | 4   R11       | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  8(SP)     | 11  (SP)
+
+
+	// | | u1 = w1 * inp
+	MOVQ DI, AX
+	MULQ ·inp+0(SB)
+	MOVQ AX, R9
+	MOVQ $0x00, BX
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w1 @ DI
+	MOVQ ·modulus+0(SB), AX
+	MULQ R9
+	ADDQ AX, DI
+	ADCQ DX, BX
+
+	// | j1
+
+	// | w2 @ SI
+	MOVQ ·modulus+8(SB), AX
+	MULQ R9
+	ADDQ AX, SI
+	ADCQ $0x00, DX
+	ADDQ BX, SI
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j2
+
+	// | w3 @ R10
+	MOVQ ·modulus+16(SB), AX
+	MULQ R9
+	ADDQ AX, R10
+	ADCQ $0x00, DX
+	ADDQ BX, R10
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j3
+
+	// | w4 @ R11
+	MOVQ ·modulus+24(SB), AX
+	MULQ R9
+	ADDQ AX, R11
+	ADCQ $0x00, DX
+	ADDQ BX, R11
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j4
+
+	// | w5 @ R12
+	MOVQ ·modulus+32(SB), AX
+	MULQ R9
+	ADDQ AX, R12
+	ADCQ $0x00, DX
+	ADDQ BX, R12
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j5
+
+	// | w6 @ R13
+	MOVQ ·modulus+40(SB), AX
+	MULQ R9
+	ADDQ AX, R13
+	ADCQ DX, CX
+	ADDQ BX, R13
+
+	// | w7 @ R14
+	ADCQ CX, R14
+	MOVQ $0x00, CX
+	ADCQ $0x00, CX
+
+	// |
+
+/* i2                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   SI        | 3   R10       | 4   R11       | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  8(SP)     | 11  (SP)
+
+
+	// | | u2 = w2 * inp
+	MOVQ SI, AX
+	MULQ ·inp+0(SB)
+	MOVQ AX, R9
+	MOVQ $0x00, BX
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w2 @ SI
+	MOVQ ·modulus+0(SB), AX
+	MULQ R9
+	ADDQ AX, SI
+	ADCQ DX, BX
+
+	// | j1
+
+	// | w3 @ R10
+	MOVQ ·modulus+8(SB), AX
+	MULQ R9
+	ADDQ AX, R10
+	ADCQ $0x00, DX
+	ADDQ BX, R10
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j2
+
+	// | w4 @ R11
+	MOVQ ·modulus+16(SB), AX
+	MULQ R9
+	ADDQ AX, R11
+	ADCQ $0x00, DX
+	ADDQ BX, R11
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j3
+
+	// | w5 @ R12
+	MOVQ ·modulus+24(SB), AX
+	MULQ R9
+	ADDQ AX, R12
+	ADCQ $0x00, DX
+	ADDQ BX, R12
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j4
+
+	// | w6 @ R13
+	MOVQ ·modulus+32(SB), AX
+	MULQ R9
+	ADDQ AX, R13
+	ADCQ $0x00, DX
+	ADDQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j5
+
+	// | w7 @ R14
+	MOVQ ·modulus+40(SB), AX
+	MULQ R9
+	ADDQ AX, R14
+	ADCQ DX, CX
+	ADDQ BX, R14
+
+	// | w8 @ R15
+	ADCQ CX, R15
+	MOVQ $0x00, CX
+	ADCQ $0x00, CX
+
+	// |
+
+/* i3                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   -         | 3   R10       | 4   R11       | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  8(SP)     | 11  (SP)
+
+
+	// | | u3 = w3 * inp
+	MOVQ R10, AX
+	MULQ ·inp+0(SB)
+	MOVQ AX, R9
+	MOVQ $0x00, BX
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w3 @ R10
+	MOVQ ·modulus+0(SB), AX
+	MULQ R9
+	ADDQ AX, R10
+	ADCQ DX, BX
+
+	// | j1
+
+	// | w4 @ R11
+	MOVQ ·modulus+8(SB), AX
+	MULQ R9
+	ADDQ AX, R11
+	ADCQ $0x00, DX
+	ADDQ BX, R11
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j2
+
+	// | w5 @ R12
+	MOVQ ·modulus+16(SB), AX
+	MULQ R9
+	ADDQ AX, R12
+	ADCQ $0x00, DX
+	ADDQ BX, R12
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j3
+
+	// | w6 @ R13
+	MOVQ ·modulus+24(SB), AX
+	MULQ R9
+	ADDQ AX, R13
+	ADCQ $0x00, DX
+	ADDQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j4
+
+	// | w7 @ R14
+	MOVQ ·modulus+32(SB), AX
+	MULQ R9
+	ADDQ AX, R14
+	ADCQ $0x00, DX
+	ADDQ BX, R14
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j5
+
+	// | w8 @ R15
+	MOVQ ·modulus+40(SB), AX
+	MULQ R9
+	ADDQ AX, R15
+	ADCQ DX, CX
+	ADDQ BX, R15
+
+	// | w9 @ R8
+	ADCQ CX, R8
+	MOVQ $0x00, CX
+	ADCQ $0x00, CX
+
+	// |
+
+/* i4                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   -         | 3   -         | 4   R11       | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  8(SP)     | 11  (SP)
+
+
+	// | | u4 = w4 * inp
+	MOVQ R11, AX
+	MULQ ·inp+0(SB)
+	MOVQ AX, R9
+	MOVQ $0x00, BX
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w4 @ R11
+	MOVQ ·modulus+0(SB), AX
+	MULQ R9
+	ADDQ AX, R11
+	ADCQ DX, BX
+
+	// | j1
+
+	// | w5 @ R12
+	MOVQ ·modulus+8(SB), AX
+	MULQ R9
+	ADDQ AX, R12
+	ADCQ $0x00, DX
+	ADDQ BX, R12
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j2
+
+	// | w6 @ R13
+	MOVQ ·modulus+16(SB), AX
+	MULQ R9
+	ADDQ AX, R13
+	ADCQ $0x00, DX
+	ADDQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j3
+
+	// | w7 @ R14
+	MOVQ ·modulus+24(SB), AX
+	MULQ R9
+	ADDQ AX, R14
+	ADCQ $0x00, DX
+	ADDQ BX, R14
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j4
+
+	// | w8 @ R15
+	MOVQ ·modulus+32(SB), AX
+	MULQ R9
+	ADDQ AX, R15
+	ADCQ $0x00, DX
+	ADDQ BX, R15
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j5
+
+	// | w9 @ R8
+	MOVQ ·modulus+40(SB), AX
+	MULQ R9
+	ADDQ AX, R8
+	ADCQ DX, CX
+	ADDQ BX, R8
+
+	// | move to idle register
+	MOVQ 8(SP), DI
+
+	// | w10 @ DI
+	ADCQ CX, DI
+	MOVQ $0x00, CX
+	ADCQ $0x00, CX
+
+	// |
+
+/* i5                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   -         | 3   -         | 4   -         | 5   R12
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  DI        | 11  (SP)
+
+
+	// | | u5 = w5 * inp
+	MOVQ R12, AX
+	MULQ ·inp+0(SB)
+	MOVQ AX, R9
+	MOVQ $0x00, BX
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w5 @ R12
+	MOVQ ·modulus+0(SB), AX
+	MULQ R9
+	ADDQ AX, R12
+	ADCQ DX, BX
+
+	// | j1
+
+	// | w6 @ R13
+	MOVQ ·modulus+8(SB), AX
+	MULQ R9
+	ADDQ AX, R13
+	ADCQ $0x00, DX
+	ADDQ BX, R13
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j2
+
+	// | w7 @ R14
+	MOVQ ·modulus+16(SB), AX
+	MULQ R9
+	ADDQ AX, R14
+	ADCQ $0x00, DX
+	ADDQ BX, R14
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j3
+
+	// | w8 @ R15
+	MOVQ ·modulus+24(SB), AX
+	MULQ R9
+	ADDQ AX, R15
+	ADCQ $0x00, DX
+	ADDQ BX, R15
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j4
+
+	// | w9 @ R8
+	MOVQ ·modulus+32(SB), AX
+	MULQ R9
+	ADDQ AX, R8
+	ADCQ $0x00, DX
+	ADDQ BX, R8
+	MOVQ $0x00, BX
+	ADCQ DX, BX
+
+	// | j5
+
+	// | w10 @ DI
+	MOVQ ·modulus+40(SB), AX
+	MULQ R9
+	ADDQ AX, DI
+	ADCQ DX, CX
+	ADDQ BX, DI
+
+	// | w11 @ CX
+	ADCQ (SP), CX
+
+	// |
+	// | W montgomerry reduction ends
+	// | 0   -         | 1   -         | 2   -         | 3   -         | 4   -         | 5   -
+	// | 6   R13       | 7   R14       | 8   R15       | 9   R8        | 10  DI        | 11  CX
+
+
+	// |
+
+
+/* modular reduction                       */
+
+	MOVQ R13, R10
+	SUBQ ·modulus+0(SB), R10
+	MOVQ R14, R11
+	SBBQ ·modulus+8(SB), R11
+	MOVQ R15, R12
+	SBBQ ·modulus+16(SB), R12
+	MOVQ R8, AX
+	SBBQ ·modulus+24(SB), AX
+	MOVQ DI, BX
+	SBBQ ·modulus+32(SB), BX
+	MOVQ CX, R9
+	SBBQ ·modulus+40(SB), R9
+	// |
+
+/* out                                     */
+
+	MOVQ    c+0(FP), SI
+	CMOVQCC R10, R13
+	MOVQ    R13, (SI)
+	CMOVQCC R11, R14
+	MOVQ    R14, 8(SI)
+	CMOVQCC R12, R15
+	MOVQ    R15, 16(SI)
+	CMOVQCC AX, R8
+	MOVQ    R8, 24(SI)
+	CMOVQCC BX, DI
+	MOVQ    DI, 32(SI)
+	CMOVQCC R9, CX
+	MOVQ    CX, 40(SI)
+	RET
+
+	// |
+
+/* end                                     */
+
+
+// multiplication
+// c = a * b % p
+TEXT ·mulADX(SB), NOSPLIT, $16-24
+	// |
+
+/* inputs                                  */
+
+	MOVQ a+8(FP), DI
+	MOVQ b+16(FP), SI
+	XORQ AX, AX
+
+	// |
+
+/* i0                                   */
+
+	// | a0 @ DX
+	MOVQ (DI), DX
+
+	// | a0 * b0
+	MULXQ (SI), AX, CX
+	MOVQ  AX, (SP)
+
+	// | a0 * b1
+	MULXQ 8(SI), AX, R8
+	ADCXQ AX, CX
+
+	// | a0 * b2
+	MULXQ 16(SI), AX, R9
+	ADCXQ AX, R8
+
+	// | a0 * b3
+	MULXQ 24(SI), AX, R10
+	ADCXQ AX, R9
+
+	// | a0 * b4
+	MULXQ 32(SI), AX, R11
+	ADCXQ AX, R10
+
+	// | a0 * b5
+	MULXQ 40(SI), AX, R12
+	ADCXQ AX, R11
+	ADCQ  $0x00, R12
+
+	// |
+
+/* i1                                   */
+
+	// | a1 @ DX
+	MOVQ 8(DI), DX
+	XORQ R13, R13
+
+	// | a1 * b0
+	MULXQ (SI), AX, BX
+	ADOXQ AX, CX
+	ADCXQ BX, R8
+	MOVQ  CX, 8(SP)
+
+	// | a1 * b1
+	MULXQ 8(SI), AX, BX
+	ADOXQ AX, R8
+	ADCXQ BX, R9
+
+	// | a1 * b2
+	MULXQ 16(SI), AX, BX
+	ADOXQ AX, R9
+	ADCXQ BX, R10
+
+	// | a1 * b3
+	MULXQ 24(SI), AX, BX
+	ADOXQ AX, R10
+	ADCXQ BX, R11
+
+	// | a1 * b4
+	MULXQ 32(SI), AX, BX
+	ADOXQ AX, R11
+	ADCXQ BX, R12
+
+	// | a1 * b5
+	MULXQ 40(SI), AX, BX
+	ADOXQ AX, R12
+	ADOXQ R13, R13
+	ADCXQ BX, R13
+
+	// |
+
+/* i2                                   */
+
+	// | a2 @ DX
+	MOVQ 16(DI), DX
+	XORQ R14, R14
+
+	// | a2 * b0
+	MULXQ (SI), AX, BX
+	ADOXQ AX, R8
+	ADCXQ BX, R9
+
+	// | a2 * b1
+	MULXQ 8(SI), AX, BX
+	ADOXQ AX, R9
+	ADCXQ BX, R10
+
+	// | a2 * b2
+	MULXQ 16(SI), AX, BX
+	ADOXQ AX, R10
+	ADCXQ BX, R11
+
+	// | a2 * b3
+	MULXQ 24(SI), AX, BX
+	ADOXQ AX, R11
+	ADCXQ BX, R12
+
+	// | a2 * b4
+	MULXQ 32(SI), AX, BX
+	ADOXQ AX, R12
+	ADCXQ BX, R13
+
+	// | a2 * b5
+	MULXQ 40(SI), AX, BX
+	ADOXQ AX, R13
+	ADOXQ R14, R14
+	ADCXQ BX, R14
+
+	// |
+
+/* i3                                   */
+
+	// | a3 @ DX
+	MOVQ 24(DI), DX
+	XORQ R15, R15
+
+	// | a3 * b0
+	MULXQ (SI), AX, BX
+	ADOXQ AX, R9
+	ADCXQ BX, R10
+
+	// | a3 * b1
+	MULXQ 8(SI), AX, BX
+	ADOXQ AX, R10
+	ADCXQ BX, R11
+
+	// | a3 * b2
+	MULXQ 16(SI), AX, BX
+	ADOXQ AX, R11
+	ADCXQ BX, R12
+
+	// | a3 * b3
+	MULXQ 24(SI), AX, BX
+	ADOXQ AX, R12
+	ADCXQ BX, R13
+
+	// | a3 * b4
+	MULXQ 32(SI), AX, BX
+	ADOXQ AX, R13
+	ADCXQ BX, R14
+
+	// | a3 * b5
+	MULXQ 40(SI), AX, BX
+	ADOXQ AX, R14
+	ADOXQ R15, R15
+	ADCXQ BX, R15
+
+	// |
+
+/* i4                                   */
+
+	// | a4 @ DX
+	MOVQ 32(DI), DX
+	XORQ CX, CX
+
+	// | a4 * b0
+	MULXQ (SI), AX, BX
+	ADOXQ AX, R10
+	ADCXQ BX, R11
+
+	// | a4 * b1
+	MULXQ 8(SI), AX, BX
+	ADOXQ AX, R11
+	ADCXQ BX, R12
+
+	// | a4 * b2
+	MULXQ 16(SI), AX, BX
+	ADOXQ AX, R12
+	ADCXQ BX, R13
+
+	// | a4 * b3
+	MULXQ 24(SI), AX, BX
+	ADOXQ AX, R13
+	ADCXQ BX, R14
+
+	// | a4 * b4
+	MULXQ 32(SI), AX, BX
+	ADOXQ AX, R14
+	ADCXQ BX, R15
+
+	// | a4 * b5
+	MULXQ 40(SI), AX, BX
+	ADOXQ AX, R15
+	ADOXQ CX, CX
+	ADCXQ BX, CX
+
+	// |
+
+/* i5                                   */
+
+	// | a5 @ DX
+	MOVQ 40(DI), DX
+	XORQ DI, DI
+
+	// | a5 * b0
+	MULXQ (SI), AX, BX
+	ADOXQ AX, R11
+	ADCXQ BX, R12
+
+	// | a5 * b1
+	MULXQ 8(SI), AX, BX
+	ADOXQ AX, R12
+	ADCXQ BX, R13
+
+	// | a5 * b2
+	MULXQ 16(SI), AX, BX
+	ADOXQ AX, R13
+	ADCXQ BX, R14
+
+	// | a5 * b3
+	MULXQ 24(SI), AX, BX
+	ADOXQ AX, R14
+	ADCXQ BX, R15
+
+	// | a5 * b4
+	MULXQ 32(SI), AX, BX
+	ADOXQ AX, R15
+	ADCXQ BX, CX
+
+	// | a5 * b5
+	MULXQ 40(SI), AX, BX
+	ADOXQ AX, CX
+	ADOXQ BX, DI
+	ADCQ  $0x00, DI
+
+	// |
+
+/* 			                                     */
+
+	// |
+	// | W
+	// | 0   (SP)      | 1   8(SP)     | 2   R8        | 3   R9        | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  DI
+
+
+	MOVQ (SP), BX
+	MOVQ 8(SP), SI
+	MOVQ DI, (SP)
+
+	// |
+	// | W ready to mont
+	// | 0   BX        | 1   SI        | 2   R8        | 3   R9        | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// |
+
+/* montgomery reduction                    */
+
+	// | clear flags
+	XORQ AX, AX
+
+	// |
+
+/* i0                                   */
+
+	// |
+	// | W
+	// | 0   BX        | 1   SI        | 2   R8        | 3   R9        | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// | | u0 = w0 * inp
+	MOVQ  BX, DX
+	MULXQ ·inp+0(SB), DX, DI
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w0 @ BX
+	MULXQ ·modulus+0(SB), AX, DI
+	ADOXQ AX, BX
+	ADCXQ DI, SI
+
+	// | j1
+
+	// | w1 @ SI
+	MULXQ ·modulus+8(SB), AX, DI
+	ADOXQ AX, SI
+	ADCXQ DI, R8
+
+	// | j2
+
+	// | w2 @ R8
+	MULXQ ·modulus+16(SB), AX, DI
+	ADOXQ AX, R8
+	ADCXQ DI, R9
+
+	// | j3
+
+	// | w3 @ R9
+	MULXQ ·modulus+24(SB), AX, DI
+	ADOXQ AX, R9
+	ADCXQ DI, R10
+
+	// | j4
+
+	// | w4 @ R10
+	MULXQ ·modulus+32(SB), AX, DI
+	ADOXQ AX, R10
+	ADCXQ DI, R11
+
+	// | j5
+
+	// | w5 @ R11
+	MULXQ ·modulus+40(SB), AX, DI
+	ADOXQ AX, R11
+	ADCXQ DI, R12
+	ADOXQ BX, R12
+	ADCXQ BX, BX
+	MOVQ  $0x00, AX
+	ADOXQ AX, BX
+
+	// | clear flags
+	XORQ AX, AX
+
+	// |
+
+/* i1                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   SI        | 2   R8        | 3   R9        | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// | | u1 = w1 * inp
+	MOVQ  SI, DX
+	MULXQ ·inp+0(SB), DX, DI
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w1 @ SI
+	MULXQ ·modulus+0(SB), AX, DI
+	ADOXQ AX, SI
+	ADCXQ DI, R8
+
+	// | j1
+
+	// | w2 @ R8
+	MULXQ ·modulus+8(SB), AX, DI
+	ADOXQ AX, R8
+	ADCXQ DI, R9
+
+	// | j2
+
+	// | w3 @ R9
+	MULXQ ·modulus+16(SB), AX, DI
+	ADOXQ AX, R9
+	ADCXQ DI, R10
+
+	// | j3
+
+	// | w4 @ R10
+	MULXQ ·modulus+24(SB), AX, DI
+	ADOXQ AX, R10
+	ADCXQ DI, R11
+
+	// | j4
+
+	// | w5 @ R11
+	MULXQ ·modulus+32(SB), AX, DI
+	ADOXQ AX, R11
+	ADCXQ DI, R12
+
+	// | j5
+
+	// | w6 @ R12
+	MULXQ ·modulus+40(SB), AX, DI
+	ADOXQ AX, R12
+	ADCXQ DI, R13
+	ADOXQ BX, R13
+	ADCXQ SI, SI
+	MOVQ  $0x00, AX
+	ADOXQ AX, SI
+
+	// | clear flags
+	XORQ AX, AX
+
+	// |
+
+/* i2                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   R8        | 3   R9        | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// | | u2 = w2 * inp
+	MOVQ  R8, DX
+	MULXQ ·inp+0(SB), DX, DI
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w2 @ R8
+	MULXQ ·modulus+0(SB), AX, DI
+	ADOXQ AX, R8
+	ADCXQ DI, R9
+
+	// | j1
+
+	// | w3 @ R9
+	MULXQ ·modulus+8(SB), AX, DI
+	ADOXQ AX, R9
+	ADCXQ DI, R10
+
+	// | j2
+
+	// | w4 @ R10
+	MULXQ ·modulus+16(SB), AX, DI
+	ADOXQ AX, R10
+	ADCXQ DI, R11
+
+	// | j3
+
+	// | w5 @ R11
+	MULXQ ·modulus+24(SB), AX, DI
+	ADOXQ AX, R11
+	ADCXQ DI, R12
+
+	// | j4
+
+	// | w6 @ R12
+	MULXQ ·modulus+32(SB), AX, DI
+	ADOXQ AX, R12
+	ADCXQ DI, R13
+
+	// | j5
+
+	// | w7 @ R13
+	MULXQ ·modulus+40(SB), AX, DI
+	ADOXQ AX, R13
+	ADCXQ DI, R14
+	ADOXQ SI, R14
+	ADCXQ R8, R8
+	MOVQ  $0x00, AX
+	ADOXQ AX, R8
+
+	// | clear flags
+	XORQ AX, AX
+
+	// |
+
+/* i3                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   -         | 3   R9        | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// | | u3 = w3 * inp
+	MOVQ  R9, DX
+	MULXQ ·inp+0(SB), DX, DI
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w3 @ R9
+	MULXQ ·modulus+0(SB), AX, DI
+	ADOXQ AX, R9
+	ADCXQ DI, R10
+
+	// | j1
+
+	// | w4 @ R10
+	MULXQ ·modulus+8(SB), AX, DI
+	ADOXQ AX, R10
+	ADCXQ DI, R11
+
+	// | j2
+
+	// | w5 @ R11
+	MULXQ ·modulus+16(SB), AX, DI
+	ADOXQ AX, R11
+	ADCXQ DI, R12
+
+	// | j3
+
+	// | w6 @ R12
+	MULXQ ·modulus+24(SB), AX, DI
+	ADOXQ AX, R12
+	ADCXQ DI, R13
+
+	// | j4
+
+	// | w7 @ R13
+	MULXQ ·modulus+32(SB), AX, DI
+	ADOXQ AX, R13
+	ADCXQ DI, R14
+
+	// | j5
+
+	// | w8 @ R14
+	MULXQ ·modulus+40(SB), AX, DI
+	ADOXQ AX, R14
+	ADCXQ DI, R15
+	ADOXQ R8, R15
+	ADCXQ R9, R9
+	MOVQ  $0x00, AX
+	ADOXQ AX, R9
+
+	// | clear flags
+	XORQ AX, AX
+
+	// |
+
+/* i4                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   -         | 3   -         | 4   R10       | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// | | u4 = w4 * inp
+	MOVQ  R10, DX
+	MULXQ ·inp+0(SB), DX, DI
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w4 @ R10
+	MULXQ ·modulus+0(SB), AX, DI
+	ADOXQ AX, R10
+	ADCXQ DI, R11
+
+	// | j1
+
+	// | w5 @ R11
+	MULXQ ·modulus+8(SB), AX, DI
+	ADOXQ AX, R11
+	ADCXQ DI, R12
+
+	// | j2
+
+	// | w6 @ R12
+	MULXQ ·modulus+16(SB), AX, DI
+	ADOXQ AX, R12
+	ADCXQ DI, R13
+
+	// | j3
+
+	// | w7 @ R13
+	MULXQ ·modulus+24(SB), AX, DI
+	ADOXQ AX, R13
+	ADCXQ DI, R14
+
+	// | j4
+
+	// | w8 @ R14
+	MULXQ ·modulus+32(SB), AX, DI
+	ADOXQ AX, R14
+	ADCXQ DI, R15
+
+	// | j5
+
+	// | w9 @ R15
+	MULXQ ·modulus+40(SB), AX, DI
+	ADOXQ AX, R15
+	ADCXQ DI, CX
+	ADOXQ R9, CX
+	ADCXQ R10, R10
+	MOVQ  $0x00, AX
+	ADOXQ AX, R10
+
+	// | clear flags
+	XORQ AX, AX
+
+	// |
+
+/* i5                                   */
+
+	// |
+	// | W
+	// | 0   -         | 1   -         | 2   -         | 3   -         | 4   -         | 5   R11
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  (SP)
+
+
+	// | | u5 = w5 * inp
+	MOVQ  R11, DX
+	MULXQ ·inp+0(SB), DX, DI
+
+	// |
+
+/*                                         */
+
+	// | j0
+
+	// | w5 @ R11
+	MULXQ ·modulus+0(SB), AX, DI
+	ADOXQ AX, R11
+	ADCXQ DI, R12
+
+	// | j1
+
+	// | w6 @ R12
+	MULXQ ·modulus+8(SB), AX, DI
+	ADOXQ AX, R12
+	ADCXQ DI, R13
+
+	// | j2
+
+	// | w7 @ R13
+	MULXQ ·modulus+16(SB), AX, DI
+	ADOXQ AX, R13
+	ADCXQ DI, R14
+
+	// | j3
+
+	// | w8 @ R14
+	MULXQ ·modulus+24(SB), AX, DI
+	ADOXQ AX, R14
+	ADCXQ DI, R15
+
+	// | j4
+
+	// | w9 @ R15
+	MULXQ ·modulus+32(SB), AX, DI
+	ADOXQ AX, R15
+	ADCXQ DI, CX
+
+	// | j5
+
+	// | w10 @ CX
+	MULXQ ·modulus+40(SB), AX, DI
+	ADOXQ AX, CX
+
+	// | w11 @ (SP)
+	// | move to an idle register
+	MOVQ  (SP), BX
+	ADCXQ DI, BX
+	ADOXQ R10, BX
+
+	// |
+	// | W montgomery reduction ends
+	// | 0   -         | 1   -         | 2   -         | 3   -         | 4   -         | 5   -
+	// | 6   R12       | 7   R13       | 8   R14       | 9   R15       | 10  CX        | 11  BX
+
+
+	// |
+
+/* modular reduction                       */
+
+	MOVQ R12, AX
+	SUBQ ·modulus+0(SB), AX
+	MOVQ R13, DI
+	SBBQ ·modulus+8(SB), DI
+	MOVQ R14, SI
+	SBBQ ·modulus+16(SB), SI
+	MOVQ R15, R8
+	SBBQ ·modulus+24(SB), R8
+	MOVQ CX, R9
+	SBBQ ·modulus+32(SB), R9
+	MOVQ BX, R10
+	SBBQ ·modulus+40(SB), R10
+
+	// |
+
+/* out                                     */
+
+	MOVQ    c+0(FP), R11
+	CMOVQCC AX, R12
+	MOVQ    R12, (R11)
+	CMOVQCC DI, R13
+	MOVQ    R13, 8(R11)
+	CMOVQCC SI, R14
+	MOVQ    R14, 16(R11)
+	CMOVQCC R8, R15
+	MOVQ    R15, 24(R11)
+	CMOVQCC R9, CX
+	MOVQ    CX, 32(R11)
+	CMOVQCC R10, BX
+	MOVQ    BX, 40(R11)
+	RET
+
+	// |
+
+/* end 																			*/
diff --git a/core/vm/int_pool_verifier.go b/crypto/bls12381/arithmetic_x86_adx.go
similarity index 69%
rename from core/vm/int_pool_verifier.go
rename to crypto/bls12381/arithmetic_x86_adx.go
index 82fbfed699..9c30741e6a 100644
--- a/core/vm/int_pool_verifier.go
+++ b/crypto/bls12381/arithmetic_x86_adx.go
@@ -1,4 +1,4 @@
-// Copyright 2017 The go-ethereum Authors
+// 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
@@ -14,18 +14,11 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// +build VERIFY_EVM_INTEGER_POOL
+// +build amd64,blsadx
 
-package vm
+package bls12381
 
-import "fmt"
-
-const verifyPool = true
-
-func verifyIntegerPool(ip *intPool) {
-	for i, item := range ip.pool.data {
-		if item.Cmp(checkVal) != 0 {
-			panic(fmt.Sprintf("%d'th item failed aggressive pool check. Value was modified", i))
-		}
-	}
-}
+// enableADX is true if the ADX/BMI2 instruction set was requested for the BLS
+// implementation. The system may still fall back to plain ASM if the necessary
+// instructions are unavailable on the CPU.
+const enableADX = true
diff --git a/crypto/bls12381/arithmetic_x86_noadx.go b/crypto/bls12381/arithmetic_x86_noadx.go
new file mode 100644
index 0000000000..eaac4b45d7
--- /dev/null
+++ b/crypto/bls12381/arithmetic_x86_noadx.go
@@ -0,0 +1,24 @@
+// 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 .
+
+// +build amd64,blsasm
+
+package bls12381
+
+// enableADX is true if the ADX/BMI2 instruction set was requested for the BLS
+// implementation. The system may still fall back to plain ASM if the necessary
+// instructions are unavailable on the CPU.
+const enableADX = false
diff --git a/crypto/bls12381/bls12_381.go b/crypto/bls12381/bls12_381.go
new file mode 100644
index 0000000000..e204a927d1
--- /dev/null
+++ b/crypto/bls12381/bls12_381.go
@@ -0,0 +1,230 @@
+// 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 .
+
+package bls12381
+
+/*
+	Field Constants
+*/
+
+// Base field modulus
+// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
+
+// Size of six words
+// r = 2 ^ 384
+
+// modulus = p
+var modulus = fe{0xb9feffffffffaaab, 0x1eabfffeb153ffff, 0x6730d2a0f6b0f624, 0x64774b84f38512bf, 0x4b1ba7b6434bacd7, 0x1a0111ea397fe69a}
+
+var (
+	// -p^(-1) mod 2^64
+	inp uint64 = 0x89f3fffcfffcfffd
+	// This value is used in assembly code
+	_ = inp
+)
+
+// r mod p
+var r1 = &fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}
+
+// r^2 mod p
+var r2 = &fe{
+	0xf4df1f341c341746, 0x0a76e6a609d104f1, 0x8de5476c4c95b6d5, 0x67eb88a9939d83c0, 0x9a793e85b519952d, 0x11988fe592cae3aa,
+}
+
+// -1 + 0 * u
+var negativeOne2 = &fe2{
+	fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206},
+	fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+}
+
+// 2 ^ (-1)
+var twoInv = &fe{0x1804000000015554, 0x855000053ab00001, 0x633cb57c253c276f, 0x6e22d1ec31ebb502, 0xd3916126f2d14ca2, 0x17fbb8571a006596}
+
+// (p - 3) / 4
+var pMinus3Over4 = bigFromHex("0x680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaaa")
+
+// (p + 1) / 4
+var pPlus1Over4 = bigFromHex("0x680447a8e5ff9a692c6e9ed90d2eb35d91dd2e13ce144afd9cc34a83dac3d8907aaffffac54ffffee7fbfffffffeaab")
+
+// (p - 1) / 2
+var pMinus1Over2 = bigFromHex("0xd0088f51cbff34d258dd3db21a5d66bb23ba5c279c2895fb39869507b587b120f55ffff58a9ffffdcff7fffffffd555")
+
+// -1
+var nonResidue1 = &fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206}
+
+// (1 + 1 * u)
+var nonResidue2 = &fe2{
+	fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+	fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+}
+
+/*
+	Curve Constants
+*/
+
+// b coefficient for G1
+var b = &fe{0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x09d645513d83de7e}
+
+// b coefficient for G2
+var b2 = &fe2{
+	fe{0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x09d645513d83de7e},
+	fe{0xaa270000000cfff3, 0x53cc0032fc34000a, 0x478fe97a6b0a807f, 0xb1d37ebee6ba24d7, 0x8ec9733bbf78ab2f, 0x09d645513d83de7e},
+}
+
+// Curve order
+var q = bigFromHex("0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001")
+
+// Efficient cofactor of G1
+var cofactorEFFG1 = bigFromHex("0xd201000000010001")
+
+// Efficient cofactor of G2
+var cofactorEFFG2 = bigFromHex("0x0bc69f08f2ee75b3584c6a0ea91b352888e2a8e9145ad7689986ff031508ffe1329c2f178731db956d82bf015d1212b02ec0ec69d7477c1ae954cbc06689f6a359894c0adebbf6b4e8020005aaa95551")
+
+var g1One = PointG1{
+	fe{0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747, 0xedce6ecc21dbf440, 0x120177419e0bfb75},
+	fe{0xbaac93d50ce72271, 0x8c22631a7918fd8e, 0xdd595f13570725ce, 0x51ac582950405194, 0x0e1c8c3fad0059c0, 0x0bbc3efc5008a26a},
+	fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+}
+
+var g2One = PointG2{
+	fe2{
+		fe{0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9, 0x6f67b7631863366b, 0x058191924350bcd7},
+		fe{0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367, 0xc2b6ed0ef2158547, 0x11922a097360edf3},
+	},
+	fe2{
+		fe{0x4c730af860494c4a, 0x597cfa1f5e369c5a, 0xe7e6856caa0a635a, 0xbbefb5e96e0d495f, 0x07d3a975f0ef25a2, 0x083fd8e7e80dae5},
+		fe{0xadc0fc92df64b05d, 0x18aa270a2b1461dc, 0x86adac6a3be4eba0, 0x79495c4ec93da33a, 0xe7175850a43ccaed, 0xb2bc2a163de1bf2},
+	},
+	fe2{
+		fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+}
+
+/*
+	Frobenious Coeffs
+*/
+
+var frobeniusCoeffs61 = [6]fe2{
+	fe2{
+		fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+		fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741},
+	},
+	fe2{
+		fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+		fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+	},
+	fe2{
+		fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+		fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160},
+	},
+}
+
+var frobeniusCoeffs62 = [6]fe2{
+	fe2{
+		fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0xecfb361b798dba3a, 0xc100ddb891865a2c, 0x0ec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x0110f184e51c5f59},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+}
+
+var frobeniusCoeffs12 = [12]fe2{
+	fe2{
+		fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x07089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x08f2220fb0fb66eb},
+		fe{0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf},
+	},
+	fe2{
+		fe{0xecfb361b798dba3a, 0xc100ddb891865a2c, 0x0ec08ff1232bda8e, 0xd5c13cc6f1ca4721, 0x47222a47bf7b5c04, 0x0110f184e51c5f59},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0x0bd592fc7d825ec8},
+		fe{0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0x0e2b7eedbbfd87d2},
+	},
+	fe2{
+		fe{0x30f1361b798a64e8, 0xf3b8ddab7ece5a2a, 0x16a8ca3ac61577f7, 0xc26a2ff874fd029b, 0x3636b76660701c6e, 0x051ba4ab241b6160},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x0095ba654ed2226b, 0x02e370eccc86f7dd},
+		fe{0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd},
+	},
+	fe2{
+		fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0xb2f66aad4ce5d646, 0x5842a06bfc497cec, 0xcf4895d42599d394, 0xc11b9cba40a8e8d0, 0x2e3813cbe5a0de89, 0x110eefda88847faf},
+		fe{0x07089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, 0xa35baecab2dc29ee, 0x1ce393ea5daace4d, 0x08f2220fb0fb66eb},
+	},
+	fe2{
+		fe{0xcd03c9e48671f071, 0x5dab22461fcda5d2, 0x587042afd3851b95, 0x8eb60ebe01bacb9e, 0x03f97d6e83d050d2, 0x18f0206554638741},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x7bcfa7a25aa30fda, 0xdc17dec12a927e7c, 0x2f088dd86b4ebef1, 0xd1ca2087da74d4a7, 0x2da2596696cebc1d, 0x0e2b7eedbbfd87d2},
+		fe{0x3e2f585da55c9ad1, 0x4294213d86c18183, 0x382844c88b623732, 0x92ad2afd19103e18, 0x1d794e4fac7cf0b9, 0x0bd592fc7d825ec8},
+	},
+	fe2{
+		fe{0x890dc9e4867545c3, 0x2af322533285a5d5, 0x50880866309b7e2c, 0xa20d1b8c7e881024, 0x14e4f04fe2db9068, 0x14e56d3f1564853a},
+		fe{0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000},
+	},
+	fe2{
+		fe{0x82d83cf50dbce43f, 0xa2813e53df9d018f, 0xc6f0caa53c65e181, 0x7525cf528d50fe95, 0x4a85ed50f4798a6b, 0x171da0fd6cf8eebd},
+		fe{0x3726c30af242c66c, 0x7c2ac1aad1b6fe70, 0xa04007fbba4b14a2, 0xef517c3266341429, 0x0095ba654ed2226b, 0x02e370eccc86f7dd},
+	},
+}
+
+/*
+	x
+*/
+
+var x = bigFromHex("0xd201000000010000")
diff --git a/crypto/bls12381/bls12_381_test.go b/crypto/bls12381/bls12_381_test.go
new file mode 100644
index 0000000000..6bf5834105
--- /dev/null
+++ b/crypto/bls12381/bls12_381_test.go
@@ -0,0 +1,13 @@
+package bls12381
+
+import (
+	"crypto/rand"
+	"math/big"
+)
+
+var fuz = 10
+
+func randScalar(max *big.Int) *big.Int {
+	a, _ := rand.Int(rand.Reader, max)
+	return a
+}
diff --git a/crypto/bls12381/field_element.go b/crypto/bls12381/field_element.go
new file mode 100644
index 0000000000..9fdddc6184
--- /dev/null
+++ b/crypto/bls12381/field_element.go
@@ -0,0 +1,340 @@
+// 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 .
+
+package bls12381
+
+import (
+	"crypto/rand"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"math/big"
+)
+
+// fe is base field element representation
+type fe [6]uint64
+
+// fe2 is element representation of 'fp2' which is quadratic extension of base field 'fp'
+// Representation follows c[0] + c[1] * u encoding order.
+type fe2 [2]fe
+
+// fe6 is element representation of 'fp6' field which is cubic extension of 'fp2'
+// Representation follows c[0] + c[1] * v + c[2] * v^2 encoding order.
+type fe6 [3]fe2
+
+// fe12 is element representation of 'fp12' field which is quadratic extension of 'fp6'
+// Representation follows c[0] + c[1] * w encoding order.
+type fe12 [2]fe6
+
+func (fe *fe) setBytes(in []byte) *fe {
+	size := 48
+	l := len(in)
+	if l >= size {
+		l = size
+	}
+	padded := make([]byte, size)
+	copy(padded[size-l:], in[:])
+	var a int
+	for i := 0; i < 6; i++ {
+		a = size - i*8
+		fe[i] = uint64(padded[a-1]) | uint64(padded[a-2])<<8 |
+			uint64(padded[a-3])<<16 | uint64(padded[a-4])<<24 |
+			uint64(padded[a-5])<<32 | uint64(padded[a-6])<<40 |
+			uint64(padded[a-7])<<48 | uint64(padded[a-8])<<56
+	}
+	return fe
+}
+
+func (fe *fe) setBig(a *big.Int) *fe {
+	return fe.setBytes(a.Bytes())
+}
+
+func (fe *fe) setString(s string) (*fe, error) {
+	if s[:2] == "0x" {
+		s = s[2:]
+	}
+	bytes, err := hex.DecodeString(s)
+	if err != nil {
+		return nil, err
+	}
+	return fe.setBytes(bytes), nil
+}
+
+func (fe *fe) set(fe2 *fe) *fe {
+	fe[0] = fe2[0]
+	fe[1] = fe2[1]
+	fe[2] = fe2[2]
+	fe[3] = fe2[3]
+	fe[4] = fe2[4]
+	fe[5] = fe2[5]
+	return fe
+}
+
+func (fe *fe) bytes() []byte {
+	out := make([]byte, 48)
+	var a int
+	for i := 0; i < 6; i++ {
+		a = 48 - i*8
+		out[a-1] = byte(fe[i])
+		out[a-2] = byte(fe[i] >> 8)
+		out[a-3] = byte(fe[i] >> 16)
+		out[a-4] = byte(fe[i] >> 24)
+		out[a-5] = byte(fe[i] >> 32)
+		out[a-6] = byte(fe[i] >> 40)
+		out[a-7] = byte(fe[i] >> 48)
+		out[a-8] = byte(fe[i] >> 56)
+	}
+	return out
+}
+
+func (fe *fe) big() *big.Int {
+	return new(big.Int).SetBytes(fe.bytes())
+}
+
+func (fe *fe) string() (s string) {
+	for i := 5; i >= 0; i-- {
+		s = fmt.Sprintf("%s%16.16x", s, fe[i])
+	}
+	return "0x" + s
+}
+
+func (fe *fe) zero() *fe {
+	fe[0] = 0
+	fe[1] = 0
+	fe[2] = 0
+	fe[3] = 0
+	fe[4] = 0
+	fe[5] = 0
+	return fe
+}
+
+func (fe *fe) one() *fe {
+	return fe.set(r1)
+}
+
+func (fe *fe) rand(r io.Reader) (*fe, error) {
+	bi, err := rand.Int(r, modulus.big())
+	if err != nil {
+		return nil, err
+	}
+	return fe.setBig(bi), nil
+}
+
+func (fe *fe) isValid() bool {
+	return fe.cmp(&modulus) < 0
+}
+
+func (fe *fe) isOdd() bool {
+	var mask uint64 = 1
+	return fe[0]&mask != 0
+}
+
+func (fe *fe) isEven() bool {
+	var mask uint64 = 1
+	return fe[0]&mask == 0
+}
+
+func (fe *fe) isZero() bool {
+	return (fe[5] | fe[4] | fe[3] | fe[2] | fe[1] | fe[0]) == 0
+}
+
+func (fe *fe) isOne() bool {
+	return fe.equal(r1)
+}
+
+func (fe *fe) cmp(fe2 *fe) int {
+	for i := 5; i >= 0; i-- {
+		if fe[i] > fe2[i] {
+			return 1
+		} else if fe[i] < fe2[i] {
+			return -1
+		}
+	}
+	return 0
+}
+
+func (fe *fe) equal(fe2 *fe) bool {
+	return fe2[0] == fe[0] && fe2[1] == fe[1] && fe2[2] == fe[2] && fe2[3] == fe[3] && fe2[4] == fe[4] && fe2[5] == fe[5]
+}
+
+func (e *fe) sign() bool {
+	r := new(fe)
+	fromMont(r, e)
+	return r[0]&1 == 0
+}
+
+func (fe *fe) div2(e uint64) {
+	fe[0] = fe[0]>>1 | fe[1]<<63
+	fe[1] = fe[1]>>1 | fe[2]<<63
+	fe[2] = fe[2]>>1 | fe[3]<<63
+	fe[3] = fe[3]>>1 | fe[4]<<63
+	fe[4] = fe[4]>>1 | fe[5]<<63
+	fe[5] = fe[5]>>1 | e<<63
+}
+
+func (fe *fe) mul2() uint64 {
+	e := fe[5] >> 63
+	fe[5] = fe[5]<<1 | fe[4]>>63
+	fe[4] = fe[4]<<1 | fe[3]>>63
+	fe[3] = fe[3]<<1 | fe[2]>>63
+	fe[2] = fe[2]<<1 | fe[1]>>63
+	fe[1] = fe[1]<<1 | fe[0]>>63
+	fe[0] = fe[0] << 1
+	return e
+}
+
+func (e *fe2) zero() *fe2 {
+	e[0].zero()
+	e[1].zero()
+	return e
+}
+
+func (e *fe2) one() *fe2 {
+	e[0].one()
+	e[1].zero()
+	return e
+}
+
+func (e *fe2) set(e2 *fe2) *fe2 {
+	e[0].set(&e2[0])
+	e[1].set(&e2[1])
+	return e
+}
+
+func (e *fe2) rand(r io.Reader) (*fe2, error) {
+	a0, err := new(fe).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	a1, err := new(fe).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	return &fe2{*a0, *a1}, nil
+}
+
+func (e *fe2) isOne() bool {
+	return e[0].isOne() && e[1].isZero()
+}
+
+func (e *fe2) isZero() bool {
+	return e[0].isZero() && e[1].isZero()
+}
+
+func (e *fe2) equal(e2 *fe2) bool {
+	return e[0].equal(&e2[0]) && e[1].equal(&e2[1])
+}
+
+func (e *fe2) sign() bool {
+	r := new(fe)
+	if !e[0].isZero() {
+		fromMont(r, &e[0])
+		return r[0]&1 == 0
+	}
+	fromMont(r, &e[1])
+	return r[0]&1 == 0
+}
+
+func (e *fe6) zero() *fe6 {
+	e[0].zero()
+	e[1].zero()
+	e[2].zero()
+	return e
+}
+
+func (e *fe6) one() *fe6 {
+	e[0].one()
+	e[1].zero()
+	e[2].zero()
+	return e
+}
+
+func (e *fe6) set(e2 *fe6) *fe6 {
+	e[0].set(&e2[0])
+	e[1].set(&e2[1])
+	e[2].set(&e2[2])
+	return e
+}
+
+func (e *fe6) rand(r io.Reader) (*fe6, error) {
+	a0, err := new(fe2).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	a1, err := new(fe2).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	a2, err := new(fe2).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	return &fe6{*a0, *a1, *a2}, nil
+}
+
+func (e *fe6) isOne() bool {
+	return e[0].isOne() && e[1].isZero() && e[2].isZero()
+}
+
+func (e *fe6) isZero() bool {
+	return e[0].isZero() && e[1].isZero() && e[2].isZero()
+}
+
+func (e *fe6) equal(e2 *fe6) bool {
+	return e[0].equal(&e2[0]) && e[1].equal(&e2[1]) && e[2].equal(&e2[2])
+}
+
+func (e *fe12) zero() *fe12 {
+	e[0].zero()
+	e[1].zero()
+	return e
+}
+
+func (e *fe12) one() *fe12 {
+	e[0].one()
+	e[1].zero()
+	return e
+}
+
+func (e *fe12) set(e2 *fe12) *fe12 {
+	e[0].set(&e2[0])
+	e[1].set(&e2[1])
+	return e
+}
+
+func (e *fe12) rand(r io.Reader) (*fe12, error) {
+	a0, err := new(fe6).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	a1, err := new(fe6).rand(r)
+	if err != nil {
+		return nil, err
+	}
+	return &fe12{*a0, *a1}, nil
+}
+
+func (e *fe12) isOne() bool {
+	return e[0].isOne() && e[1].isZero()
+}
+
+func (e *fe12) isZero() bool {
+	return e[0].isZero() && e[1].isZero()
+}
+
+func (e *fe12) equal(e2 *fe12) bool {
+	return e[0].equal(&e2[0]) && e[1].equal(&e2[1])
+}
diff --git a/crypto/bls12381/field_element_test.go b/crypto/bls12381/field_element_test.go
new file mode 100644
index 0000000000..0f6abd280c
--- /dev/null
+++ b/crypto/bls12381/field_element_test.go
@@ -0,0 +1,251 @@
+package bls12381
+
+import (
+	"bytes"
+	"crypto/rand"
+	"math/big"
+	"testing"
+)
+
+func TestFieldElementValidation(t *testing.T) {
+	zero := new(fe).zero()
+	if !zero.isValid() {
+		t.Fatal("zero must be valid")
+	}
+	one := new(fe).one()
+	if !one.isValid() {
+		t.Fatal("one must be valid")
+	}
+	if modulus.isValid() {
+		t.Fatal("modulus must be invalid")
+	}
+	n := modulus.big()
+	n.Add(n, big.NewInt(1))
+	if new(fe).setBig(n).isValid() {
+		t.Fatal("number greater than modulus must be invalid")
+	}
+}
+
+func TestFieldElementEquality(t *testing.T) {
+	// fe
+	zero := new(fe).zero()
+	if !zero.equal(zero) {
+		t.Fatal("0 == 0")
+	}
+	one := new(fe).one()
+	if !one.equal(one) {
+		t.Fatal("1 == 1")
+	}
+	a, _ := new(fe).rand(rand.Reader)
+	if !a.equal(a) {
+		t.Fatal("a == a")
+	}
+	b := new(fe)
+	add(b, a, one)
+	if a.equal(b) {
+		t.Fatal("a != a + 1")
+	}
+	// fe2
+	zero2 := new(fe2).zero()
+	if !zero2.equal(zero2) {
+		t.Fatal("0 == 0")
+	}
+	one2 := new(fe2).one()
+	if !one2.equal(one2) {
+		t.Fatal("1 == 1")
+	}
+	a2, _ := new(fe2).rand(rand.Reader)
+	if !a2.equal(a2) {
+		t.Fatal("a == a")
+	}
+	b2 := new(fe2)
+	fp2 := newFp2()
+	fp2.add(b2, a2, one2)
+	if a2.equal(b2) {
+		t.Fatal("a != a + 1")
+	}
+	// fe6
+	zero6 := new(fe6).zero()
+	if !zero6.equal(zero6) {
+		t.Fatal("0 == 0")
+	}
+	one6 := new(fe6).one()
+	if !one6.equal(one6) {
+		t.Fatal("1 == 1")
+	}
+	a6, _ := new(fe6).rand(rand.Reader)
+	if !a6.equal(a6) {
+		t.Fatal("a == a")
+	}
+	b6 := new(fe6)
+	fp6 := newFp6(fp2)
+	fp6.add(b6, a6, one6)
+	if a6.equal(b6) {
+		t.Fatal("a != a + 1")
+	}
+	// fe12
+	zero12 := new(fe12).zero()
+	if !zero12.equal(zero12) {
+		t.Fatal("0 == 0")
+	}
+	one12 := new(fe12).one()
+	if !one12.equal(one12) {
+		t.Fatal("1 == 1")
+	}
+	a12, _ := new(fe12).rand(rand.Reader)
+	if !a12.equal(a12) {
+		t.Fatal("a == a")
+	}
+	b12 := new(fe12)
+	fp12 := newFp12(fp6)
+	fp12.add(b12, a12, one12)
+	if a12.equal(b12) {
+		t.Fatal("a != a + 1")
+	}
+
+}
+
+func TestFieldElementHelpers(t *testing.T) {
+	// fe
+	zero := new(fe).zero()
+	if !zero.isZero() {
+		t.Fatal("'zero' is not zero")
+	}
+	one := new(fe).one()
+	if !one.isOne() {
+		t.Fatal("'one' is not one")
+	}
+	odd := new(fe).setBig(big.NewInt(1))
+	if !odd.isOdd() {
+		t.Fatal("1 must be odd")
+	}
+	if odd.isEven() {
+		t.Fatal("1 must not be even")
+	}
+	even := new(fe).setBig(big.NewInt(2))
+	if !even.isEven() {
+		t.Fatal("2 must be even")
+	}
+	if even.isOdd() {
+		t.Fatal("2 must not be odd")
+	}
+	// fe2
+	zero2 := new(fe2).zero()
+	if !zero2.isZero() {
+		t.Fatal("'zero' is not zero, 2")
+	}
+	one2 := new(fe2).one()
+	if !one2.isOne() {
+		t.Fatal("'one' is not one, 2")
+	}
+	// fe6
+	zero6 := new(fe6).zero()
+	if !zero6.isZero() {
+		t.Fatal("'zero' is not zero, 6")
+	}
+	one6 := new(fe6).one()
+	if !one6.isOne() {
+		t.Fatal("'one' is not one, 6")
+	}
+	// fe12
+	zero12 := new(fe12).zero()
+	if !zero12.isZero() {
+		t.Fatal("'zero' is not zero, 12")
+	}
+	one12 := new(fe12).one()
+	if !one12.isOne() {
+		t.Fatal("'one' is not one, 12")
+	}
+}
+
+func TestFieldElementSerialization(t *testing.T) {
+	t.Run("zero", func(t *testing.T) {
+		in := make([]byte, 48)
+		fe := new(fe).setBytes(in)
+		if !fe.isZero() {
+			t.Fatal("bad serialization")
+		}
+		if !bytes.Equal(in, fe.bytes()) {
+			t.Fatal("bad serialization")
+		}
+	})
+	t.Run("bytes", func(t *testing.T) {
+		for i := 0; i < fuz; i++ {
+			a, _ := new(fe).rand(rand.Reader)
+			b := new(fe).setBytes(a.bytes())
+			if !a.equal(b) {
+				t.Fatal("bad serialization")
+			}
+		}
+	})
+	t.Run("big", func(t *testing.T) {
+		for i := 0; i < fuz; i++ {
+			a, _ := new(fe).rand(rand.Reader)
+			b := new(fe).setBig(a.big())
+			if !a.equal(b) {
+				t.Fatal("bad encoding or decoding")
+			}
+		}
+	})
+	t.Run("string", func(t *testing.T) {
+		for i := 0; i < fuz; i++ {
+			a, _ := new(fe).rand(rand.Reader)
+			b, err := new(fe).setString(a.string())
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !a.equal(b) {
+				t.Fatal("bad encoding or decoding")
+			}
+		}
+	})
+}
+
+func TestFieldElementByteInputs(t *testing.T) {
+	zero := new(fe).zero()
+	in := make([]byte, 0)
+	a := new(fe).setBytes(in)
+	if !a.equal(zero) {
+		t.Fatal("bad serialization")
+	}
+	in = make([]byte, 48)
+	a = new(fe).setBytes(in)
+	if !a.equal(zero) {
+		t.Fatal("bad serialization")
+	}
+	in = make([]byte, 64)
+	a = new(fe).setBytes(in)
+	if !a.equal(zero) {
+		t.Fatal("bad serialization")
+	}
+	in = make([]byte, 49)
+	in[47] = 1
+	normalOne := &fe{1, 0, 0, 0, 0, 0}
+	a = new(fe).setBytes(in)
+	if !a.equal(normalOne) {
+		t.Fatal("bad serialization")
+	}
+}
+
+func TestFieldElementCopy(t *testing.T) {
+	a, _ := new(fe).rand(rand.Reader)
+	b := new(fe).set(a)
+	if !a.equal(b) {
+		t.Fatal("bad copy, 1")
+	}
+	a2, _ := new(fe2).rand(rand.Reader)
+	b2 := new(fe2).set(a2)
+	if !a2.equal(b2) {
+		t.Fatal("bad copy, 2")
+	}
+	a6, _ := new(fe6).rand(rand.Reader)
+	b6 := new(fe6).set(a6)
+	if !a6.equal(b6) {
+		t.Fatal("bad copy, 6")
+	}
+	a12, _ := new(fe12).rand(rand.Reader)
+	b12 := new(fe12).set(a12)
+	if !a12.equal(b12) {
+		t.Fatal("bad copy, 12")
+	}
+}
diff --git a/crypto/bls12381/fp.go b/crypto/bls12381/fp.go
new file mode 100644
index 0000000000..09f6f49bc0
--- /dev/null
+++ b/crypto/bls12381/fp.go
@@ -0,0 +1,167 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math/big"
+)
+
+func fromBytes(in []byte) (*fe, error) {
+	fe := &fe{}
+	if len(in) != 48 {
+		return nil, errors.New("input string should be equal 48 bytes")
+	}
+	fe.setBytes(in)
+	if !fe.isValid() {
+		return nil, errors.New("must be less than modulus")
+	}
+	toMont(fe, fe)
+	return fe, nil
+}
+
+func fromBig(in *big.Int) (*fe, error) {
+	fe := new(fe).setBig(in)
+	if !fe.isValid() {
+		return nil, errors.New("invalid input string")
+	}
+	toMont(fe, fe)
+	return fe, nil
+}
+
+func fromString(in string) (*fe, error) {
+	fe, err := new(fe).setString(in)
+	if err != nil {
+		return nil, err
+	}
+	if !fe.isValid() {
+		return nil, errors.New("invalid input string")
+	}
+	toMont(fe, fe)
+	return fe, nil
+}
+
+func toBytes(e *fe) []byte {
+	e2 := new(fe)
+	fromMont(e2, e)
+	return e2.bytes()
+}
+
+func toBig(e *fe) *big.Int {
+	e2 := new(fe)
+	fromMont(e2, e)
+	return e2.big()
+}
+
+func toString(e *fe) (s string) {
+	e2 := new(fe)
+	fromMont(e2, e)
+	return e2.string()
+}
+
+func toMont(c, a *fe) {
+	mul(c, a, r2)
+}
+
+func fromMont(c, a *fe) {
+	mul(c, a, &fe{1})
+}
+
+func exp(c, a *fe, e *big.Int) {
+	z := new(fe).set(r1)
+	for i := e.BitLen(); i >= 0; i-- {
+		mul(z, z, z)
+		if e.Bit(i) == 1 {
+			mul(z, z, a)
+		}
+	}
+	c.set(z)
+}
+
+func inverse(inv, e *fe) {
+	if e.isZero() {
+		inv.zero()
+		return
+	}
+	u := new(fe).set(&modulus)
+	v := new(fe).set(e)
+	s := &fe{1}
+	r := &fe{0}
+	var k int
+	var z uint64
+	var found = false
+	// Phase 1
+	for i := 0; i < 768; i++ {
+		if v.isZero() {
+			found = true
+			break
+		}
+		if u.isEven() {
+			u.div2(0)
+			s.mul2()
+		} else if v.isEven() {
+			v.div2(0)
+			z += r.mul2()
+		} else if u.cmp(v) == 1 {
+			lsubAssign(u, v)
+			u.div2(0)
+			laddAssign(r, s)
+			s.mul2()
+		} else {
+			lsubAssign(v, u)
+			v.div2(0)
+			laddAssign(s, r)
+			z += r.mul2()
+		}
+		k += 1
+	}
+
+	if !found {
+		inv.zero()
+		return
+	}
+
+	if k < 381 || k > 381+384 {
+		inv.zero()
+		return
+	}
+
+	if r.cmp(&modulus) != -1 || z > 0 {
+		lsubAssign(r, &modulus)
+	}
+	u.set(&modulus)
+	lsubAssign(u, r)
+
+	// Phase 2
+	for i := k; i < 384*2; i++ {
+		double(u, u)
+	}
+	inv.set(u)
+}
+
+func sqrt(c, a *fe) bool {
+	u, v := new(fe).set(a), new(fe)
+	exp(c, a, pPlus1Over4)
+	square(v, c)
+	return u.equal(v)
+}
+
+func isQuadraticNonResidue(elem *fe) bool {
+	result := new(fe)
+	exp(result, elem, pMinus1Over2)
+	return !result.isOne()
+}
diff --git a/crypto/bls12381/fp12.go b/crypto/bls12381/fp12.go
new file mode 100644
index 0000000000..3141c76c39
--- /dev/null
+++ b/crypto/bls12381/fp12.go
@@ -0,0 +1,279 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math/big"
+)
+
+type fp12 struct {
+	fp12temp
+	fp6 *fp6
+}
+
+type fp12temp struct {
+	t2  [9]*fe2
+	t6  [5]*fe6
+	t12 *fe12
+}
+
+func newFp12Temp() fp12temp {
+	t2 := [9]*fe2{}
+	t6 := [5]*fe6{}
+	for i := 0; i < len(t2); i++ {
+		t2[i] = &fe2{}
+	}
+	for i := 0; i < len(t6); i++ {
+		t6[i] = &fe6{}
+	}
+	return fp12temp{t2, t6, &fe12{}}
+}
+
+func newFp12(fp6 *fp6) *fp12 {
+	t := newFp12Temp()
+	if fp6 == nil {
+		return &fp12{t, newFp6(nil)}
+	}
+	return &fp12{t, fp6}
+}
+
+func (e *fp12) fp2() *fp2 {
+	return e.fp6.fp2
+}
+
+func (e *fp12) fromBytes(in []byte) (*fe12, error) {
+	if len(in) != 576 {
+		return nil, errors.New("input string should be larger than 96 bytes")
+	}
+	fp6 := e.fp6
+	c1, err := fp6.fromBytes(in[:288])
+	if err != nil {
+		return nil, err
+	}
+	c0, err := fp6.fromBytes(in[288:])
+	if err != nil {
+		return nil, err
+	}
+	return &fe12{*c0, *c1}, nil
+}
+
+func (e *fp12) toBytes(a *fe12) []byte {
+	fp6 := e.fp6
+	out := make([]byte, 576)
+	copy(out[:288], fp6.toBytes(&a[1]))
+	copy(out[288:], fp6.toBytes(&a[0]))
+	return out
+}
+
+func (e *fp12) new() *fe12 {
+	return new(fe12)
+}
+
+func (e *fp12) zero() *fe12 {
+	return new(fe12)
+}
+
+func (e *fp12) one() *fe12 {
+	return new(fe12).one()
+}
+
+func (e *fp12) add(c, a, b *fe12) {
+	fp6 := e.fp6
+	fp6.add(&c[0], &a[0], &b[0])
+	fp6.add(&c[1], &a[1], &b[1])
+
+}
+
+func (e *fp12) double(c, a *fe12) {
+	fp6 := e.fp6
+	fp6.double(&c[0], &a[0])
+	fp6.double(&c[1], &a[1])
+}
+
+func (e *fp12) sub(c, a, b *fe12) {
+	fp6 := e.fp6
+	fp6.sub(&c[0], &a[0], &b[0])
+	fp6.sub(&c[1], &a[1], &b[1])
+
+}
+
+func (e *fp12) neg(c, a *fe12) {
+	fp6 := e.fp6
+	fp6.neg(&c[0], &a[0])
+	fp6.neg(&c[1], &a[1])
+}
+
+func (e *fp12) conjugate(c, a *fe12) {
+	fp6 := e.fp6
+	c[0].set(&a[0])
+	fp6.neg(&c[1], &a[1])
+}
+
+func (e *fp12) square(c, a *fe12) {
+	fp6, t := e.fp6, e.t6
+	fp6.add(t[0], &a[0], &a[1])
+	fp6.mul(t[2], &a[0], &a[1])
+	fp6.mulByNonResidue(t[1], &a[1])
+	fp6.addAssign(t[1], &a[0])
+	fp6.mulByNonResidue(t[3], t[2])
+	fp6.mulAssign(t[0], t[1])
+	fp6.subAssign(t[0], t[2])
+	fp6.sub(&c[0], t[0], t[3])
+	fp6.double(&c[1], t[2])
+}
+
+func (e *fp12) cyclotomicSquare(c, a *fe12) {
+	t, fp2 := e.t2, e.fp2()
+	e.fp4Square(t[3], t[4], &a[0][0], &a[1][1])
+	fp2.sub(t[2], t[3], &a[0][0])
+	fp2.doubleAssign(t[2])
+	fp2.add(&c[0][0], t[2], t[3])
+	fp2.add(t[2], t[4], &a[1][1])
+	fp2.doubleAssign(t[2])
+	fp2.add(&c[1][1], t[2], t[4])
+	e.fp4Square(t[3], t[4], &a[1][0], &a[0][2])
+	e.fp4Square(t[5], t[6], &a[0][1], &a[1][2])
+	fp2.sub(t[2], t[3], &a[0][1])
+	fp2.doubleAssign(t[2])
+	fp2.add(&c[0][1], t[2], t[3])
+	fp2.add(t[2], t[4], &a[1][2])
+	fp2.doubleAssign(t[2])
+	fp2.add(&c[1][2], t[2], t[4])
+	fp2.mulByNonResidue(t[3], t[6])
+	fp2.add(t[2], t[3], &a[1][0])
+	fp2.doubleAssign(t[2])
+	fp2.add(&c[1][0], t[2], t[3])
+	fp2.sub(t[2], t[5], &a[0][2])
+	fp2.doubleAssign(t[2])
+	fp2.add(&c[0][2], t[2], t[5])
+}
+
+func (e *fp12) mul(c, a, b *fe12) {
+	t, fp6 := e.t6, e.fp6
+	fp6.mul(t[1], &a[0], &b[0])
+	fp6.mul(t[2], &a[1], &b[1])
+	fp6.add(t[0], t[1], t[2])
+	fp6.mulByNonResidue(t[2], t[2])
+	fp6.add(t[3], t[1], t[2])
+	fp6.add(t[1], &a[0], &a[1])
+	fp6.add(t[2], &b[0], &b[1])
+	fp6.mulAssign(t[1], t[2])
+	c[0].set(t[3])
+	fp6.sub(&c[1], t[1], t[0])
+}
+
+func (e *fp12) mulAssign(a, b *fe12) {
+	t, fp6 := e.t6, e.fp6
+	fp6.mul(t[1], &a[0], &b[0])
+	fp6.mul(t[2], &a[1], &b[1])
+	fp6.add(t[0], t[1], t[2])
+	fp6.mulByNonResidue(t[2], t[2])
+	fp6.add(t[3], t[1], t[2])
+	fp6.add(t[1], &a[0], &a[1])
+	fp6.add(t[2], &b[0], &b[1])
+	fp6.mulAssign(t[1], t[2])
+	a[0].set(t[3])
+	fp6.sub(&a[1], t[1], t[0])
+}
+
+func (e *fp12) fp4Square(c0, c1, a0, a1 *fe2) {
+	t, fp2 := e.t2, e.fp2()
+	fp2.square(t[0], a0)
+	fp2.square(t[1], a1)
+	fp2.mulByNonResidue(t[2], t[1])
+	fp2.add(c0, t[2], t[0])
+	fp2.add(t[2], a0, a1)
+	fp2.squareAssign(t[2])
+	fp2.subAssign(t[2], t[0])
+	fp2.sub(c1, t[2], t[1])
+}
+
+func (e *fp12) inverse(c, a *fe12) {
+	fp6, t := e.fp6, e.t6
+	fp6.square(t[0], &a[0])
+	fp6.square(t[1], &a[1])
+	fp6.mulByNonResidue(t[1], t[1])
+	fp6.sub(t[1], t[0], t[1])
+	fp6.inverse(t[0], t[1])
+	fp6.mul(&c[0], &a[0], t[0])
+	fp6.mulAssign(t[0], &a[1])
+	fp6.neg(&c[1], t[0])
+}
+
+func (e *fp12) mulBy014Assign(a *fe12, c0, c1, c4 *fe2) {
+	fp2, fp6, t, t2 := e.fp2(), e.fp6, e.t6, e.t2[0]
+	fp6.mulBy01(t[0], &a[0], c0, c1)
+	fp6.mulBy1(t[1], &a[1], c4)
+	fp2.add(t2, c1, c4)
+	fp6.add(t[2], &a[1], &a[0])
+	fp6.mulBy01Assign(t[2], c0, t2)
+	fp6.subAssign(t[2], t[0])
+	fp6.sub(&a[1], t[2], t[1])
+	fp6.mulByNonResidue(t[1], t[1])
+	fp6.add(&a[0], t[1], t[0])
+}
+
+func (e *fp12) exp(c, a *fe12, s *big.Int) {
+	z := e.one()
+	for i := s.BitLen() - 1; i >= 0; i-- {
+		e.square(z, z)
+		if s.Bit(i) == 1 {
+			e.mul(z, z, a)
+		}
+	}
+	c.set(z)
+}
+
+func (e *fp12) cyclotomicExp(c, a *fe12, s *big.Int) {
+	z := e.one()
+	for i := s.BitLen() - 1; i >= 0; i-- {
+		e.cyclotomicSquare(z, z)
+		if s.Bit(i) == 1 {
+			e.mul(z, z, a)
+		}
+	}
+	c.set(z)
+}
+
+func (e *fp12) frobeniusMap(c, a *fe12, power uint) {
+	fp6 := e.fp6
+	fp6.frobeniusMap(&c[0], &a[0], power)
+	fp6.frobeniusMap(&c[1], &a[1], power)
+	switch power {
+	case 0:
+		return
+	case 6:
+		fp6.neg(&c[1], &c[1])
+	default:
+		fp6.mulByBaseField(&c[1], &c[1], &frobeniusCoeffs12[power])
+	}
+}
+
+func (e *fp12) frobeniusMapAssign(a *fe12, power uint) {
+	fp6 := e.fp6
+	fp6.frobeniusMapAssign(&a[0], power)
+	fp6.frobeniusMapAssign(&a[1], power)
+	switch power {
+	case 0:
+		return
+	case 6:
+		fp6.neg(&a[1], &a[1])
+	default:
+		fp6.mulByBaseField(&a[1], &a[1], &frobeniusCoeffs12[power])
+	}
+}
diff --git a/crypto/bls12381/fp2.go b/crypto/bls12381/fp2.go
new file mode 100644
index 0000000000..0f1c5a23ac
--- /dev/null
+++ b/crypto/bls12381/fp2.go
@@ -0,0 +1,252 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math/big"
+)
+
+type fp2Temp struct {
+	t [4]*fe
+}
+
+type fp2 struct {
+	fp2Temp
+}
+
+func newFp2Temp() fp2Temp {
+	t := [4]*fe{}
+	for i := 0; i < len(t); i++ {
+		t[i] = &fe{}
+	}
+	return fp2Temp{t}
+}
+
+func newFp2() *fp2 {
+	t := newFp2Temp()
+	return &fp2{t}
+}
+
+func (e *fp2) fromBytes(in []byte) (*fe2, error) {
+	if len(in) != 96 {
+		return nil, errors.New("length of input string should be 96 bytes")
+	}
+	c1, err := fromBytes(in[:48])
+	if err != nil {
+		return nil, err
+	}
+	c0, err := fromBytes(in[48:])
+	if err != nil {
+		return nil, err
+	}
+	return &fe2{*c0, *c1}, nil
+}
+
+func (e *fp2) toBytes(a *fe2) []byte {
+	out := make([]byte, 96)
+	copy(out[:48], toBytes(&a[1]))
+	copy(out[48:], toBytes(&a[0]))
+	return out
+}
+
+func (e *fp2) new() *fe2 {
+	return new(fe2).zero()
+}
+
+func (e *fp2) zero() *fe2 {
+	return new(fe2).zero()
+}
+
+func (e *fp2) one() *fe2 {
+	return new(fe2).one()
+}
+
+func (e *fp2) add(c, a, b *fe2) {
+	add(&c[0], &a[0], &b[0])
+	add(&c[1], &a[1], &b[1])
+}
+
+func (e *fp2) addAssign(a, b *fe2) {
+	addAssign(&a[0], &b[0])
+	addAssign(&a[1], &b[1])
+}
+
+func (e *fp2) ladd(c, a, b *fe2) {
+	ladd(&c[0], &a[0], &b[0])
+	ladd(&c[1], &a[1], &b[1])
+}
+
+func (e *fp2) double(c, a *fe2) {
+	double(&c[0], &a[0])
+	double(&c[1], &a[1])
+}
+
+func (e *fp2) doubleAssign(a *fe2) {
+	doubleAssign(&a[0])
+	doubleAssign(&a[1])
+}
+
+func (e *fp2) ldouble(c, a *fe2) {
+	ldouble(&c[0], &a[0])
+	ldouble(&c[1], &a[1])
+}
+
+func (e *fp2) sub(c, a, b *fe2) {
+	sub(&c[0], &a[0], &b[0])
+	sub(&c[1], &a[1], &b[1])
+}
+
+func (e *fp2) subAssign(c, a *fe2) {
+	subAssign(&c[0], &a[0])
+	subAssign(&c[1], &a[1])
+}
+
+func (e *fp2) neg(c, a *fe2) {
+	neg(&c[0], &a[0])
+	neg(&c[1], &a[1])
+}
+
+func (e *fp2) mul(c, a, b *fe2) {
+	t := e.t
+	mul(t[1], &a[0], &b[0])
+	mul(t[2], &a[1], &b[1])
+	add(t[0], &a[0], &a[1])
+	add(t[3], &b[0], &b[1])
+	sub(&c[0], t[1], t[2])
+	addAssign(t[1], t[2])
+	mul(t[0], t[0], t[3])
+	sub(&c[1], t[0], t[1])
+}
+
+func (e *fp2) mulAssign(a, b *fe2) {
+	t := e.t
+	mul(t[1], &a[0], &b[0])
+	mul(t[2], &a[1], &b[1])
+	add(t[0], &a[0], &a[1])
+	add(t[3], &b[0], &b[1])
+	sub(&a[0], t[1], t[2])
+	addAssign(t[1], t[2])
+	mul(t[0], t[0], t[3])
+	sub(&a[1], t[0], t[1])
+}
+
+func (e *fp2) square(c, a *fe2) {
+	t := e.t
+	ladd(t[0], &a[0], &a[1])
+	sub(t[1], &a[0], &a[1])
+	ldouble(t[2], &a[0])
+	mul(&c[0], t[0], t[1])
+	mul(&c[1], t[2], &a[1])
+}
+
+func (e *fp2) squareAssign(a *fe2) {
+	t := e.t
+	ladd(t[0], &a[0], &a[1])
+	sub(t[1], &a[0], &a[1])
+	ldouble(t[2], &a[0])
+	mul(&a[0], t[0], t[1])
+	mul(&a[1], t[2], &a[1])
+}
+
+func (e *fp2) mulByNonResidue(c, a *fe2) {
+	t := e.t
+	sub(t[0], &a[0], &a[1])
+	add(&c[1], &a[0], &a[1])
+	c[0].set(t[0])
+}
+
+func (e *fp2) mulByB(c, a *fe2) {
+	t := e.t
+	double(t[0], &a[0])
+	double(t[1], &a[1])
+	doubleAssign(t[0])
+	doubleAssign(t[1])
+	sub(&c[0], t[0], t[1])
+	add(&c[1], t[0], t[1])
+}
+
+func (e *fp2) inverse(c, a *fe2) {
+	t := e.t
+	square(t[0], &a[0])
+	square(t[1], &a[1])
+	addAssign(t[0], t[1])
+	inverse(t[0], t[0])
+	mul(&c[0], &a[0], t[0])
+	mul(t[0], t[0], &a[1])
+	neg(&c[1], t[0])
+}
+
+func (e *fp2) mulByFq(c, a *fe2, b *fe) {
+	mul(&c[0], &a[0], b)
+	mul(&c[1], &a[1], b)
+}
+
+func (e *fp2) exp(c, a *fe2, s *big.Int) {
+	z := e.one()
+	for i := s.BitLen() - 1; i >= 0; i-- {
+		e.square(z, z)
+		if s.Bit(i) == 1 {
+			e.mul(z, z, a)
+		}
+	}
+	c.set(z)
+}
+
+func (e *fp2) frobeniusMap(c, a *fe2, power uint) {
+	c[0].set(&a[0])
+	if power%2 == 1 {
+		neg(&c[1], &a[1])
+		return
+	}
+	c[1].set(&a[1])
+}
+
+func (e *fp2) frobeniusMapAssign(a *fe2, power uint) {
+	if power%2 == 1 {
+		neg(&a[1], &a[1])
+		return
+	}
+}
+
+func (e *fp2) sqrt(c, a *fe2) bool {
+	u, x0, a1, alpha := &fe2{}, &fe2{}, &fe2{}, &fe2{}
+	u.set(a)
+	e.exp(a1, a, pMinus3Over4)
+	e.square(alpha, a1)
+	e.mul(alpha, alpha, a)
+	e.mul(x0, a1, a)
+	if alpha.equal(negativeOne2) {
+		neg(&c[0], &x0[1])
+		c[1].set(&x0[0])
+		return true
+	}
+	e.add(alpha, alpha, e.one())
+	e.exp(alpha, alpha, pMinus1Over2)
+	e.mul(c, alpha, x0)
+	e.square(alpha, c)
+	return alpha.equal(u)
+}
+
+func (e *fp2) isQuadraticNonResidue(a *fe2) bool {
+	// https://github.com/leovt/constructible/wiki/Taking-Square-Roots-in-quadratic-extension-Fields
+	c0, c1 := new(fe), new(fe)
+	square(c0, &a[0])
+	square(c1, &a[1])
+	add(c1, c1, c0)
+	return isQuadraticNonResidue(c1)
+}
diff --git a/crypto/bls12381/fp6.go b/crypto/bls12381/fp6.go
new file mode 100644
index 0000000000..304173baa3
--- /dev/null
+++ b/crypto/bls12381/fp6.go
@@ -0,0 +1,351 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math/big"
+)
+
+type fp6Temp struct {
+	t [6]*fe2
+}
+
+type fp6 struct {
+	fp2 *fp2
+	fp6Temp
+}
+
+func newFp6Temp() fp6Temp {
+	t := [6]*fe2{}
+	for i := 0; i < len(t); i++ {
+		t[i] = &fe2{}
+	}
+	return fp6Temp{t}
+}
+
+func newFp6(f *fp2) *fp6 {
+	t := newFp6Temp()
+	if f == nil {
+		return &fp6{newFp2(), t}
+	}
+	return &fp6{f, t}
+}
+
+func (e *fp6) fromBytes(b []byte) (*fe6, error) {
+	if len(b) < 288 {
+		return nil, errors.New("input string should be larger than 288 bytes")
+	}
+	fp2 := e.fp2
+	u2, err := fp2.fromBytes(b[:96])
+	if err != nil {
+		return nil, err
+	}
+	u1, err := fp2.fromBytes(b[96:192])
+	if err != nil {
+		return nil, err
+	}
+	u0, err := fp2.fromBytes(b[192:])
+	if err != nil {
+		return nil, err
+	}
+	return &fe6{*u0, *u1, *u2}, nil
+}
+
+func (e *fp6) toBytes(a *fe6) []byte {
+	fp2 := e.fp2
+	out := make([]byte, 288)
+	copy(out[:96], fp2.toBytes(&a[2]))
+	copy(out[96:192], fp2.toBytes(&a[1]))
+	copy(out[192:], fp2.toBytes(&a[0]))
+	return out
+}
+
+func (e *fp6) new() *fe6 {
+	return new(fe6)
+}
+
+func (e *fp6) zero() *fe6 {
+	return new(fe6)
+}
+
+func (e *fp6) one() *fe6 {
+	return new(fe6).one()
+}
+
+func (e *fp6) add(c, a, b *fe6) {
+	fp2 := e.fp2
+	fp2.add(&c[0], &a[0], &b[0])
+	fp2.add(&c[1], &a[1], &b[1])
+	fp2.add(&c[2], &a[2], &b[2])
+}
+
+func (e *fp6) addAssign(a, b *fe6) {
+	fp2 := e.fp2
+	fp2.addAssign(&a[0], &b[0])
+	fp2.addAssign(&a[1], &b[1])
+	fp2.addAssign(&a[2], &b[2])
+}
+
+func (e *fp6) double(c, a *fe6) {
+	fp2 := e.fp2
+	fp2.double(&c[0], &a[0])
+	fp2.double(&c[1], &a[1])
+	fp2.double(&c[2], &a[2])
+}
+
+func (e *fp6) doubleAssign(a *fe6) {
+	fp2 := e.fp2
+	fp2.doubleAssign(&a[0])
+	fp2.doubleAssign(&a[1])
+	fp2.doubleAssign(&a[2])
+}
+
+func (e *fp6) sub(c, a, b *fe6) {
+	fp2 := e.fp2
+	fp2.sub(&c[0], &a[0], &b[0])
+	fp2.sub(&c[1], &a[1], &b[1])
+	fp2.sub(&c[2], &a[2], &b[2])
+}
+
+func (e *fp6) subAssign(a, b *fe6) {
+	fp2 := e.fp2
+	fp2.subAssign(&a[0], &b[0])
+	fp2.subAssign(&a[1], &b[1])
+	fp2.subAssign(&a[2], &b[2])
+}
+
+func (e *fp6) neg(c, a *fe6) {
+	fp2 := e.fp2
+	fp2.neg(&c[0], &a[0])
+	fp2.neg(&c[1], &a[1])
+	fp2.neg(&c[2], &a[2])
+}
+
+func (e *fp6) mul(c, a, b *fe6) {
+	fp2, t := e.fp2, e.t
+	fp2.mul(t[0], &a[0], &b[0])
+	fp2.mul(t[1], &a[1], &b[1])
+	fp2.mul(t[2], &a[2], &b[2])
+	fp2.add(t[3], &a[1], &a[2])
+	fp2.add(t[4], &b[1], &b[2])
+	fp2.mulAssign(t[3], t[4])
+	fp2.add(t[4], t[1], t[2])
+	fp2.subAssign(t[3], t[4])
+	fp2.mulByNonResidue(t[3], t[3])
+	fp2.add(t[5], t[0], t[3])
+	fp2.add(t[3], &a[0], &a[1])
+	fp2.add(t[4], &b[0], &b[1])
+	fp2.mulAssign(t[3], t[4])
+	fp2.add(t[4], t[0], t[1])
+	fp2.subAssign(t[3], t[4])
+	fp2.mulByNonResidue(t[4], t[2])
+	fp2.add(&c[1], t[3], t[4])
+	fp2.add(t[3], &a[0], &a[2])
+	fp2.add(t[4], &b[0], &b[2])
+	fp2.mulAssign(t[3], t[4])
+	fp2.add(t[4], t[0], t[2])
+	fp2.subAssign(t[3], t[4])
+	fp2.add(&c[2], t[1], t[3])
+	c[0].set(t[5])
+}
+
+func (e *fp6) mulAssign(a, b *fe6) {
+	fp2, t := e.fp2, e.t
+	fp2.mul(t[0], &a[0], &b[0])
+	fp2.mul(t[1], &a[1], &b[1])
+	fp2.mul(t[2], &a[2], &b[2])
+	fp2.add(t[3], &a[1], &a[2])
+	fp2.add(t[4], &b[1], &b[2])
+	fp2.mulAssign(t[3], t[4])
+	fp2.add(t[4], t[1], t[2])
+	fp2.subAssign(t[3], t[4])
+	fp2.mulByNonResidue(t[3], t[3])
+	fp2.add(t[5], t[0], t[3])
+	fp2.add(t[3], &a[0], &a[1])
+	fp2.add(t[4], &b[0], &b[1])
+	fp2.mulAssign(t[3], t[4])
+	fp2.add(t[4], t[0], t[1])
+	fp2.subAssign(t[3], t[4])
+	fp2.mulByNonResidue(t[4], t[2])
+	fp2.add(&a[1], t[3], t[4])
+	fp2.add(t[3], &a[0], &a[2])
+	fp2.add(t[4], &b[0], &b[2])
+	fp2.mulAssign(t[3], t[4])
+	fp2.add(t[4], t[0], t[2])
+	fp2.subAssign(t[3], t[4])
+	fp2.add(&a[2], t[1], t[3])
+	a[0].set(t[5])
+}
+
+func (e *fp6) square(c, a *fe6) {
+	fp2, t := e.fp2, e.t
+	fp2.square(t[0], &a[0])
+	fp2.mul(t[1], &a[0], &a[1])
+	fp2.doubleAssign(t[1])
+	fp2.sub(t[2], &a[0], &a[1])
+	fp2.addAssign(t[2], &a[2])
+	fp2.squareAssign(t[2])
+	fp2.mul(t[3], &a[1], &a[2])
+	fp2.doubleAssign(t[3])
+	fp2.square(t[4], &a[2])
+	fp2.mulByNonResidue(t[5], t[3])
+	fp2.add(&c[0], t[0], t[5])
+	fp2.mulByNonResidue(t[5], t[4])
+	fp2.add(&c[1], t[1], t[5])
+	fp2.addAssign(t[1], t[2])
+	fp2.addAssign(t[1], t[3])
+	fp2.addAssign(t[0], t[4])
+	fp2.sub(&c[2], t[1], t[0])
+}
+
+func (e *fp6) mulBy01Assign(a *fe6, b0, b1 *fe2) {
+	fp2, t := e.fp2, e.t
+	fp2.mul(t[0], &a[0], b0)
+	fp2.mul(t[1], &a[1], b1)
+	fp2.add(t[5], &a[1], &a[2])
+	fp2.mul(t[2], b1, t[5])
+	fp2.subAssign(t[2], t[1])
+	fp2.mulByNonResidue(t[2], t[2])
+	fp2.add(t[5], &a[0], &a[2])
+	fp2.mul(t[3], b0, t[5])
+	fp2.subAssign(t[3], t[0])
+	fp2.add(&a[2], t[3], t[1])
+	fp2.add(t[4], b0, b1)
+	fp2.add(t[5], &a[0], &a[1])
+	fp2.mulAssign(t[4], t[5])
+	fp2.subAssign(t[4], t[0])
+	fp2.sub(&a[1], t[4], t[1])
+	fp2.add(&a[0], t[2], t[0])
+}
+
+func (e *fp6) mulBy01(c, a *fe6, b0, b1 *fe2) {
+	fp2, t := e.fp2, e.t
+	fp2.mul(t[0], &a[0], b0)
+	fp2.mul(t[1], &a[1], b1)
+	fp2.add(t[2], &a[1], &a[2])
+	fp2.mulAssign(t[2], b1)
+	fp2.subAssign(t[2], t[1])
+	fp2.mulByNonResidue(t[2], t[2])
+	fp2.add(t[3], &a[0], &a[2])
+	fp2.mulAssign(t[3], b0)
+	fp2.subAssign(t[3], t[0])
+	fp2.add(&c[2], t[3], t[1])
+	fp2.add(t[4], b0, b1)
+	fp2.add(t[3], &a[0], &a[1])
+	fp2.mulAssign(t[4], t[3])
+	fp2.subAssign(t[4], t[0])
+	fp2.sub(&c[1], t[4], t[1])
+	fp2.add(&c[0], t[2], t[0])
+}
+
+func (e *fp6) mulBy1(c, a *fe6, b1 *fe2) {
+	fp2, t := e.fp2, e.t
+	fp2.mul(t[0], &a[2], b1)
+	fp2.mul(&c[2], &a[1], b1)
+	fp2.mul(&c[1], &a[0], b1)
+	fp2.mulByNonResidue(&c[0], t[0])
+}
+
+func (e *fp6) mulByNonResidue(c, a *fe6) {
+	fp2, t := e.fp2, e.t
+	t[0].set(&a[0])
+	fp2.mulByNonResidue(&c[0], &a[2])
+	c[2].set(&a[1])
+	c[1].set(t[0])
+}
+
+func (e *fp6) mulByBaseField(c, a *fe6, b *fe2) {
+	fp2 := e.fp2
+	fp2.mul(&c[0], &a[0], b)
+	fp2.mul(&c[1], &a[1], b)
+	fp2.mul(&c[2], &a[2], b)
+}
+
+func (e *fp6) exp(c, a *fe6, s *big.Int) {
+	z := e.one()
+	for i := s.BitLen() - 1; i >= 0; i-- {
+		e.square(z, z)
+		if s.Bit(i) == 1 {
+			e.mul(z, z, a)
+		}
+	}
+	c.set(z)
+}
+
+func (e *fp6) inverse(c, a *fe6) {
+	fp2, t := e.fp2, e.t
+	fp2.square(t[0], &a[0])
+	fp2.mul(t[1], &a[1], &a[2])
+	fp2.mulByNonResidue(t[1], t[1])
+	fp2.subAssign(t[0], t[1])
+	fp2.square(t[1], &a[1])
+	fp2.mul(t[2], &a[0], &a[2])
+	fp2.subAssign(t[1], t[2])
+	fp2.square(t[2], &a[2])
+	fp2.mulByNonResidue(t[2], t[2])
+	fp2.mul(t[3], &a[0], &a[1])
+	fp2.subAssign(t[2], t[3])
+	fp2.mul(t[3], &a[2], t[2])
+	fp2.mul(t[4], &a[1], t[1])
+	fp2.addAssign(t[3], t[4])
+	fp2.mulByNonResidue(t[3], t[3])
+	fp2.mul(t[4], &a[0], t[0])
+	fp2.addAssign(t[3], t[4])
+	fp2.inverse(t[3], t[3])
+	fp2.mul(&c[0], t[0], t[3])
+	fp2.mul(&c[1], t[2], t[3])
+	fp2.mul(&c[2], t[1], t[3])
+}
+
+func (e *fp6) frobeniusMap(c, a *fe6, power uint) {
+	fp2 := e.fp2
+	fp2.frobeniusMap(&c[0], &a[0], power)
+	fp2.frobeniusMap(&c[1], &a[1], power)
+	fp2.frobeniusMap(&c[2], &a[2], power)
+	switch power % 6 {
+	case 0:
+		return
+	case 3:
+		neg(&c[0][0], &a[1][1])
+		c[1][1].set(&a[1][0])
+		fp2.neg(&a[2], &a[2])
+	default:
+		fp2.mul(&c[1], &c[1], &frobeniusCoeffs61[power%6])
+		fp2.mul(&c[2], &c[2], &frobeniusCoeffs62[power%6])
+	}
+}
+
+func (e *fp6) frobeniusMapAssign(a *fe6, power uint) {
+	fp2 := e.fp2
+	fp2.frobeniusMapAssign(&a[0], power)
+	fp2.frobeniusMapAssign(&a[1], power)
+	fp2.frobeniusMapAssign(&a[2], power)
+	t := e.t
+	switch power % 6 {
+	case 0:
+		return
+	case 3:
+		neg(&t[0][0], &a[1][1])
+		a[1][1].set(&a[1][0])
+		a[1][0].set(&t[0][0])
+		fp2.neg(&a[2], &a[2])
+	default:
+		fp2.mulAssign(&a[1], &frobeniusCoeffs61[power%6])
+		fp2.mulAssign(&a[2], &frobeniusCoeffs62[power%6])
+	}
+}
diff --git a/crypto/bls12381/fp_test.go b/crypto/bls12381/fp_test.go
new file mode 100644
index 0000000000..97528d9db3
--- /dev/null
+++ b/crypto/bls12381/fp_test.go
@@ -0,0 +1,1412 @@
+package bls12381
+
+import (
+	"bytes"
+	"crypto/rand"
+	"math/big"
+	"testing"
+)
+
+func TestFpSerialization(t *testing.T) {
+	t.Run("zero", func(t *testing.T) {
+		in := make([]byte, 48)
+		fe, err := fromBytes(in)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !fe.isZero() {
+			t.Fatal("bad serialization")
+		}
+		if !bytes.Equal(in, toBytes(fe)) {
+			t.Fatal("bad serialization")
+		}
+	})
+	t.Run("bytes", func(t *testing.T) {
+		for i := 0; i < fuz; i++ {
+			a, _ := new(fe).rand(rand.Reader)
+			b, err := fromBytes(toBytes(a))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !a.equal(b) {
+				t.Fatal("bad serialization")
+			}
+		}
+	})
+	t.Run("string", func(t *testing.T) {
+		for i := 0; i < fuz; i++ {
+			a, _ := new(fe).rand(rand.Reader)
+			b, err := fromString(toString(a))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !a.equal(b) {
+				t.Fatal("bad encoding or decoding")
+			}
+		}
+	})
+	t.Run("big", func(t *testing.T) {
+		for i := 0; i < fuz; i++ {
+			a, _ := new(fe).rand(rand.Reader)
+			b, err := fromBig(toBig(a))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if !a.equal(b) {
+				t.Fatal("bad encoding or decoding")
+			}
+		}
+	})
+}
+
+func TestFpAdditionCrossAgainstBigInt(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		b, _ := new(fe).rand(rand.Reader)
+		c := new(fe)
+		big_a := toBig(a)
+		big_b := toBig(b)
+		big_c := new(big.Int)
+		add(c, a, b)
+		out_1 := toBytes(c)
+		out_2 := padBytes(big_c.Add(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied A")
+		}
+		double(c, a)
+		out_1 = toBytes(c)
+		out_2 = padBytes(big_c.Add(big_a, big_a).Mod(big_c, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied B")
+		}
+		sub(c, a, b)
+		out_1 = toBytes(c)
+		out_2 = padBytes(big_c.Sub(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied C")
+		}
+		neg(c, a)
+		out_1 = toBytes(c)
+		out_2 = padBytes(big_c.Neg(big_a).Mod(big_c, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied D")
+		}
+	}
+}
+
+func TestFpAdditionCrossAgainstBigIntAssigned(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		b, _ := new(fe).rand(rand.Reader)
+		big_a, big_b := toBig(a), toBig(b)
+		addAssign(a, b)
+		out_1 := toBytes(a)
+		out_2 := padBytes(big_a.Add(big_a, big_b).Mod(big_a, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied A")
+		}
+		a, _ = new(fe).rand(rand.Reader)
+		big_a = toBig(a)
+		doubleAssign(a)
+		out_1 = toBytes(a)
+		out_2 = padBytes(big_a.Add(big_a, big_a).Mod(big_a, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied B")
+		}
+		a, _ = new(fe).rand(rand.Reader)
+		b, _ = new(fe).rand(rand.Reader)
+		big_a, big_b = toBig(a), toBig(b)
+		subAssign(a, b)
+		out_1 = toBytes(a)
+		out_2 = padBytes(big_a.Sub(big_a, big_b).Mod(big_a, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied A")
+		}
+	}
+}
+
+func TestFpAdditionProperties(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		zero := new(fe).zero()
+		a, _ := new(fe).rand(rand.Reader)
+		b, _ := new(fe).rand(rand.Reader)
+		c_1, c_2 := new(fe), new(fe)
+		add(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a + 0 == a")
+		}
+		sub(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a - 0 == a")
+		}
+		double(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		neg(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("-0 == 0")
+		}
+		sub(c_1, zero, a)
+		neg(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("0-a == -a")
+		}
+		double(c_1, a)
+		add(c_2, a, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("2 * a == a + a")
+		}
+		add(c_1, a, b)
+		add(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		sub(c_1, a, b)
+		sub(c_2, b, a)
+		neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		c_x, _ := new(fe).rand(rand.Reader)
+		add(c_1, a, b)
+		add(c_1, c_1, c_x)
+		add(c_2, a, c_x)
+		add(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a + b) + c == (a + c ) + b")
+		}
+		sub(c_1, a, b)
+		sub(c_1, c_1, c_x)
+		sub(c_2, a, c_x)
+		sub(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a - b) - c == (a - c ) -b")
+		}
+	}
+}
+
+func TestFpAdditionPropertiesAssigned(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		zero := new(fe).zero()
+		a, b := new(fe), new(fe)
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		addAssign(a, zero)
+		if !a.equal(b) {
+			t.Fatal("a + 0 == a")
+		}
+		subAssign(a, zero)
+		if !a.equal(b) {
+			t.Fatal("a - 0 == a")
+		}
+		a.set(zero)
+		doubleAssign(a)
+		if !a.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		a.set(zero)
+		subAssign(a, b)
+		neg(b, b)
+		if !a.equal(b) {
+			t.Fatal("0-a == -a")
+		}
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		doubleAssign(a)
+		addAssign(b, b)
+		if !a.equal(b) {
+			t.Fatal("2 * a == a + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c_1, c_2 := new(fe).set(a), new(fe).set(b)
+		addAssign(c_1, b)
+		addAssign(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c_1.set(a)
+		c_2.set(b)
+		subAssign(c_1, b)
+		subAssign(c_2, a)
+		neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c, _ := new(fe).rand(rand.Reader)
+		a0 := new(fe).set(a)
+		addAssign(a, b)
+		addAssign(a, c)
+		addAssign(b, c)
+		addAssign(b, a0)
+		if !a.equal(b) {
+			t.Fatal("(a + b) + c == (b + c) + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		_, _ = c.rand(rand.Reader)
+		a0.set(a)
+		subAssign(a, b)
+		subAssign(a, c)
+		subAssign(a0, c)
+		subAssign(a0, b)
+		if !a.equal(a0) {
+			t.Fatal("(a - b) - c == (a - c) -b")
+		}
+	}
+}
+
+func TestFpLazyOperations(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		b, _ := new(fe).rand(rand.Reader)
+		c, _ := new(fe).rand(rand.Reader)
+		c0 := new(fe)
+		c1 := new(fe)
+		ladd(c0, a, b)
+		add(c1, a, b)
+		mul(c0, c0, c)
+		mul(c1, c1, c)
+		if !c0.equal(c1) {
+			// l+ operator stands for lazy addition
+			t.Fatal("(a + b) * c == (a l+ b) * c")
+		}
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		ldouble(a, a)
+		ladd(b, b, b)
+		if !a.equal(b) {
+			t.Fatal("2 l* a = a l+ a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		_, _ = c.rand(rand.Reader)
+		a0 := new(fe).set(a)
+		lsubAssign(a, b)
+		laddAssign(a, &modulus)
+		mul(a, a, c)
+		subAssign(a0, b)
+		mul(a0, a0, c)
+		if !a.equal(a0) {
+			t.Fatal("((a l- b) + p) * c = (a-b) * c")
+		}
+	}
+}
+
+func TestFpMultiplicationCrossAgainstBigInt(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		b, _ := new(fe).rand(rand.Reader)
+		c := new(fe)
+		big_a := toBig(a)
+		big_b := toBig(b)
+		big_c := new(big.Int)
+		mul(c, a, b)
+		out_1 := toBytes(c)
+		out_2 := padBytes(big_c.Mul(big_a, big_b).Mod(big_c, modulus.big()).Bytes(), 48)
+		if !bytes.Equal(out_1, out_2) {
+			t.Fatal("cross test against big.Int is not satisfied")
+		}
+	}
+}
+
+func TestFpMultiplicationProperties(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		b, _ := new(fe).rand(rand.Reader)
+		zero, one := new(fe).zero(), new(fe).one()
+		c_1, c_2 := new(fe), new(fe)
+		mul(c_1, a, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		mul(c_1, a, one)
+		if !c_1.equal(a) {
+			t.Fatal("a * 1 == a")
+		}
+		mul(c_1, a, b)
+		mul(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a * b == b * a")
+		}
+		c_x, _ := new(fe).rand(rand.Reader)
+		mul(c_1, a, b)
+		mul(c_1, c_1, c_x)
+		mul(c_2, c_x, b)
+		mul(c_2, c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+		square(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("0^2 == 0")
+		}
+		square(a, one)
+		if !a.equal(one) {
+			t.Fatal("1^2 == 1")
+		}
+		_, _ = a.rand(rand.Reader)
+		square(c_1, a)
+		mul(c_2, a, a)
+		if !c_1.equal(c_1) {
+			t.Fatal("a^2 == a*a")
+		}
+	}
+}
+
+func TestFpExponentiation(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		u := new(fe)
+		exp(u, a, big.NewInt(0))
+		if !u.isOne() {
+			t.Fatal("a^0 == 1")
+		}
+		exp(u, a, big.NewInt(1))
+		if !u.equal(a) {
+			t.Fatal("a^1 == a")
+		}
+		v := new(fe)
+		mul(u, a, a)
+		mul(u, u, u)
+		mul(u, u, u)
+		exp(v, a, big.NewInt(8))
+		if !u.equal(v) {
+			t.Fatal("((a^2)^2)^2 == a^8")
+		}
+		p := modulus.big()
+		exp(u, a, p)
+		if !u.equal(a) {
+			t.Fatal("a^p == a")
+		}
+		exp(u, a, p.Sub(p, big.NewInt(1)))
+		if !u.isOne() {
+			t.Fatal("a^(p-1) == 1")
+		}
+	}
+}
+
+func TestFpInversion(t *testing.T) {
+	for i := 0; i < fuz; i++ {
+		u := new(fe)
+		zero, one := new(fe).zero(), new(fe).one()
+		inverse(u, zero)
+		if !u.equal(zero) {
+			t.Fatal("(0^-1) == 0)")
+		}
+		inverse(u, one)
+		if !u.equal(one) {
+			t.Fatal("(1^-1) == 1)")
+		}
+		a, _ := new(fe).rand(rand.Reader)
+		inverse(u, a)
+		mul(u, u, a)
+		if !u.equal(one) {
+			t.Fatal("(r*a) * r*(a^-1) == r)")
+		}
+		v := new(fe)
+		p := modulus.big()
+		exp(u, a, p.Sub(p, big.NewInt(2)))
+		inverse(v, a)
+		if !v.equal(u) {
+			t.Fatal("a^(p-2) == a^-1")
+		}
+	}
+}
+
+func TestFpSquareRoot(t *testing.T) {
+	r := new(fe)
+	if sqrt(r, nonResidue1) {
+		t.Fatal("non residue cannot have a sqrt")
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		aa, rr, r := &fe{}, &fe{}, &fe{}
+		square(aa, a)
+		if !sqrt(r, aa) {
+			t.Fatal("bad sqrt 1")
+		}
+		square(rr, r)
+		if !rr.equal(aa) {
+			t.Fatal("bad sqrt 2")
+		}
+	}
+}
+
+func TestFpNonResidue(t *testing.T) {
+	if !isQuadraticNonResidue(nonResidue1) {
+		t.Fatal("element is quadratic non residue, 1")
+	}
+	if isQuadraticNonResidue(new(fe).one()) {
+		t.Fatal("one is not quadratic non residue")
+	}
+	if !isQuadraticNonResidue(new(fe).zero()) {
+		t.Fatal("should accept zero as quadratic non residue")
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		square(a, a)
+		if isQuadraticNonResidue(new(fe).one()) {
+			t.Fatal("element is not quadratic non residue")
+		}
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe).rand(rand.Reader)
+		if !sqrt(new(fe), a) {
+			if !isQuadraticNonResidue(a) {
+				t.Fatal("element is quadratic non residue, 2", i)
+			}
+		} else {
+			i -= 1
+		}
+	}
+
+}
+
+func TestFp2Serialization(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		b, err := field.fromBytes(field.toBytes(a))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !a.equal(b) {
+			t.Fatal("bad serialization")
+		}
+	}
+}
+
+func TestFp2AdditionProperties(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		zero := field.zero()
+		a, _ := new(fe2).rand(rand.Reader)
+		b, _ := new(fe2).rand(rand.Reader)
+		c_1 := field.new()
+		c_2 := field.new()
+		field.add(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a + 0 == a")
+		}
+		field.sub(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a - 0 == a")
+		}
+		field.double(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		field.neg(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("-0 == 0")
+		}
+		field.sub(c_1, zero, a)
+		field.neg(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("0-a == -a")
+		}
+		field.double(c_1, a)
+		field.add(c_2, a, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("2 * a == a + a")
+		}
+		field.add(c_1, a, b)
+		field.add(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		field.sub(c_1, a, b)
+		field.sub(c_2, b, a)
+		field.neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		c_x, _ := new(fe2).rand(rand.Reader)
+		field.add(c_1, a, b)
+		field.add(c_1, c_1, c_x)
+		field.add(c_2, a, c_x)
+		field.add(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a + b) + c == (a + c ) + b")
+		}
+		field.sub(c_1, a, b)
+		field.sub(c_1, c_1, c_x)
+		field.sub(c_2, a, c_x)
+		field.sub(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a - b) - c == (a - c ) -b")
+		}
+	}
+}
+
+func TestFp2AdditionPropertiesAssigned(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		zero := new(fe2).zero()
+		a, b := new(fe2), new(fe2)
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		field.addAssign(a, zero)
+		if !a.equal(b) {
+			t.Fatal("a + 0 == a")
+		}
+		field.subAssign(a, zero)
+		if !a.equal(b) {
+			t.Fatal("a - 0 == a")
+		}
+		a.set(zero)
+		field.doubleAssign(a)
+		if !a.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		a.set(zero)
+		field.subAssign(a, b)
+		field.neg(b, b)
+		if !a.equal(b) {
+			t.Fatal("0-a == -a")
+		}
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		field.doubleAssign(a)
+		field.addAssign(b, b)
+		if !a.equal(b) {
+			t.Fatal("2 * a == a + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c_1, c_2 := new(fe2).set(a), new(fe2).set(b)
+		field.addAssign(c_1, b)
+		field.addAssign(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c_1.set(a)
+		c_2.set(b)
+		field.subAssign(c_1, b)
+		field.subAssign(c_2, a)
+		field.neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c, _ := new(fe2).rand(rand.Reader)
+		a0 := new(fe2).set(a)
+		field.addAssign(a, b)
+		field.addAssign(a, c)
+		field.addAssign(b, c)
+		field.addAssign(b, a0)
+		if !a.equal(b) {
+			t.Fatal("(a + b) + c == (b + c) + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		_, _ = c.rand(rand.Reader)
+		a0.set(a)
+		field.subAssign(a, b)
+		field.subAssign(a, c)
+		field.subAssign(a0, c)
+		field.subAssign(a0, b)
+		if !a.equal(a0) {
+			t.Fatal("(a - b) - c == (a - c) -b")
+		}
+	}
+}
+
+func TestFp2LazyOperations(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		b, _ := new(fe2).rand(rand.Reader)
+		c, _ := new(fe2).rand(rand.Reader)
+		c0 := new(fe2)
+		c1 := new(fe2)
+		field.ladd(c0, a, b)
+		field.add(c1, a, b)
+		field.mulAssign(c0, c)
+		field.mulAssign(c1, c)
+		if !c0.equal(c1) {
+			// l+ operator stands for lazy addition
+			t.Fatal("(a + b) * c == (a l+ b) * c")
+		}
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		field.ldouble(a, a)
+		field.ladd(b, b, b)
+		if !a.equal(b) {
+			t.Fatal("2 l* a = a l+ a")
+		}
+	}
+}
+
+func TestFp2MultiplicationProperties(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		b, _ := new(fe2).rand(rand.Reader)
+		zero := field.zero()
+		one := field.one()
+		c_1, c_2 := field.new(), field.new()
+		field.mul(c_1, a, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		field.mul(c_1, a, one)
+		if !c_1.equal(a) {
+			t.Fatal("a * 1 == a")
+		}
+		field.mul(c_1, a, b)
+		field.mul(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a * b == b * a")
+		}
+		c_x, _ := new(fe2).rand(rand.Reader)
+		field.mul(c_1, a, b)
+		field.mul(c_1, c_1, c_x)
+		field.mul(c_2, c_x, b)
+		field.mul(c_2, c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+		field.square(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("0^2 == 0")
+		}
+		field.square(a, one)
+		if !a.equal(one) {
+			t.Fatal("1^2 == 1")
+		}
+		_, _ = a.rand(rand.Reader)
+		field.square(c_1, a)
+		field.mul(c_2, a, a)
+		if !c_2.equal(c_1) {
+			t.Fatal("a^2 == a*a")
+		}
+	}
+}
+
+func TestFp2MultiplicationPropertiesAssigned(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		zero, one := new(fe2).zero(), new(fe2).one()
+		field.mulAssign(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		_, _ = a.rand(rand.Reader)
+		a0 := new(fe2).set(a)
+		field.mulAssign(a, one)
+		if !a.equal(a0) {
+			t.Fatal("a * 1 == a")
+		}
+		_, _ = a.rand(rand.Reader)
+		b, _ := new(fe2).rand(rand.Reader)
+		a0.set(a)
+		field.mulAssign(a, b)
+		field.mulAssign(b, a0)
+		if !a.equal(b) {
+			t.Fatal("a * b == b * a")
+		}
+		c, _ := new(fe2).rand(rand.Reader)
+		a0.set(a)
+		field.mulAssign(a, b)
+		field.mulAssign(a, c)
+		field.mulAssign(a0, c)
+		field.mulAssign(a0, b)
+		if !a.equal(a0) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+		a0.set(a)
+		field.squareAssign(a)
+		field.mulAssign(a0, a0)
+		if !a.equal(a0) {
+			t.Fatal("a^2 == a*a")
+		}
+	}
+}
+
+func TestFp2Exponentiation(t *testing.T) {
+	field := newFp2()
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		u := field.new()
+		field.exp(u, a, big.NewInt(0))
+		if !u.equal(field.one()) {
+			t.Fatal("a^0 == 1")
+		}
+		field.exp(u, a, big.NewInt(1))
+		if !u.equal(a) {
+			t.Fatal("a^1 == a")
+		}
+		v := field.new()
+		field.mul(u, a, a)
+		field.mul(u, u, u)
+		field.mul(u, u, u)
+		field.exp(v, a, big.NewInt(8))
+		if !u.equal(v) {
+			t.Fatal("((a^2)^2)^2 == a^8")
+		}
+	}
+}
+
+func TestFp2Inversion(t *testing.T) {
+	field := newFp2()
+	u := field.new()
+	zero := field.zero()
+	one := field.one()
+	field.inverse(u, zero)
+	if !u.equal(zero) {
+		t.Fatal("(0 ^ -1) == 0)")
+	}
+	field.inverse(u, one)
+	if !u.equal(one) {
+		t.Fatal("(1 ^ -1) == 1)")
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		field.inverse(u, a)
+		field.mul(u, u, a)
+		if !u.equal(one) {
+			t.Fatal("(r * a) * r * (a ^ -1) == r)")
+		}
+	}
+}
+
+func TestFp2SquareRoot(t *testing.T) {
+	field := newFp2()
+	for z := 0; z < 1000; z++ {
+		zi := new(fe)
+		sub(zi, &modulus, &fe{uint64(z * z)})
+		// r = (-z*z, 0)
+		r := &fe2{*zi, fe{0}}
+		toMont(&r[0], &r[0])
+		toMont(&r[1], &r[1])
+		c := field.new()
+		// sqrt((-z*z, 0)) = (0, z)
+		if !field.sqrt(c, r) {
+			t.Fatal("z*z does have a square root")
+		}
+		e := &fe2{fe{uint64(0)}, fe{uint64(z)}}
+		toMont(&e[0], &e[0])
+		toMont(&e[1], &e[1])
+		field.square(e, e)
+		field.square(c, c)
+		if !e.equal(c) {
+			t.Fatal("square root failed")
+		}
+	}
+	if field.sqrt(field.new(), nonResidue2) {
+		t.Fatal("non residue cannot have a sqrt")
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		aa, rr, r := field.new(), field.new(), field.new()
+		field.square(aa, a)
+		if !field.sqrt(r, aa) {
+			t.Fatal("bad sqrt 1")
+		}
+		field.square(rr, r)
+		if !rr.equal(aa) {
+			t.Fatal("bad sqrt 2")
+		}
+	}
+}
+
+func TestFp2NonResidue(t *testing.T) {
+	field := newFp2()
+	if !field.isQuadraticNonResidue(nonResidue2) {
+		t.Fatal("element is quadratic non residue, 1")
+	}
+	if field.isQuadraticNonResidue(new(fe2).one()) {
+		t.Fatal("one is not quadratic non residue")
+	}
+	if !field.isQuadraticNonResidue(new(fe2).zero()) {
+		t.Fatal("should accept zero as quadratic non residue")
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		field.squareAssign(a)
+		if field.isQuadraticNonResidue(new(fe2).one()) {
+			t.Fatal("element is not quadratic non residue")
+		}
+	}
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe2).rand(rand.Reader)
+		if !field.sqrt(new(fe2), a) {
+			if !field.isQuadraticNonResidue(a) {
+				t.Fatal("element is quadratic non residue, 2", i)
+			}
+		} else {
+			i -= 1
+		}
+	}
+}
+
+func TestFp6Serialization(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe6).rand(rand.Reader)
+		b, err := field.fromBytes(field.toBytes(a))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !a.equal(b) {
+			t.Fatal("bad serialization")
+		}
+	}
+}
+
+func TestFp6AdditionProperties(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		zero := field.zero()
+		a, _ := new(fe6).rand(rand.Reader)
+		b, _ := new(fe6).rand(rand.Reader)
+		c_1 := field.new()
+		c_2 := field.new()
+		field.add(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a + 0 == a")
+		}
+		field.sub(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a - 0 == a")
+		}
+		field.double(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		field.neg(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("-0 == 0")
+		}
+		field.sub(c_1, zero, a)
+		field.neg(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("0-a == -a")
+		}
+		field.double(c_1, a)
+		field.add(c_2, a, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("2 * a == a + a")
+		}
+		field.add(c_1, a, b)
+		field.add(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		field.sub(c_1, a, b)
+		field.sub(c_2, b, a)
+		field.neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		c_x, _ := new(fe6).rand(rand.Reader)
+		field.add(c_1, a, b)
+		field.add(c_1, c_1, c_x)
+		field.add(c_2, a, c_x)
+		field.add(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a + b) + c == (a + c ) + b")
+		}
+		field.sub(c_1, a, b)
+		field.sub(c_1, c_1, c_x)
+		field.sub(c_2, a, c_x)
+		field.sub(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a - b) - c == (a - c ) -b")
+		}
+	}
+}
+
+func TestFp6AdditionPropertiesAssigned(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		zero := new(fe6).zero()
+		a, b := new(fe6), new(fe6)
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		field.addAssign(a, zero)
+		if !a.equal(b) {
+			t.Fatal("a + 0 == a")
+		}
+		field.subAssign(a, zero)
+		if !a.equal(b) {
+			t.Fatal("a - 0 == a")
+		}
+		a.set(zero)
+		field.doubleAssign(a)
+		if !a.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		a.set(zero)
+		field.subAssign(a, b)
+		field.neg(b, b)
+		if !a.equal(b) {
+			t.Fatal("0-a == -a")
+		}
+		_, _ = a.rand(rand.Reader)
+		b.set(a)
+		field.doubleAssign(a)
+		field.addAssign(b, b)
+		if !a.equal(b) {
+			t.Fatal("2 * a == a + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c_1, c_2 := new(fe6).set(a), new(fe6).set(b)
+		field.addAssign(c_1, b)
+		field.addAssign(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c_1.set(a)
+		c_2.set(b)
+		field.subAssign(c_1, b)
+		field.subAssign(c_2, a)
+		field.neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		c, _ := new(fe6).rand(rand.Reader)
+		a0 := new(fe6).set(a)
+		field.addAssign(a, b)
+		field.addAssign(a, c)
+		field.addAssign(b, c)
+		field.addAssign(b, a0)
+		if !a.equal(b) {
+			t.Fatal("(a + b) + c == (b + c) + a")
+		}
+		_, _ = a.rand(rand.Reader)
+		_, _ = b.rand(rand.Reader)
+		_, _ = c.rand(rand.Reader)
+		a0.set(a)
+		field.subAssign(a, b)
+		field.subAssign(a, c)
+		field.subAssign(a0, c)
+		field.subAssign(a0, b)
+		if !a.equal(a0) {
+			t.Fatal("(a - b) - c == (a - c) -b")
+		}
+	}
+}
+
+func TestFp6SparseMultiplication(t *testing.T) {
+	fp6 := newFp6(nil)
+	var a, b, u *fe6
+	for j := 0; j < fuz; j++ {
+		a, _ = new(fe6).rand(rand.Reader)
+		b, _ = new(fe6).rand(rand.Reader)
+		u, _ = new(fe6).rand(rand.Reader)
+		b[2].zero()
+		fp6.mul(u, a, b)
+		fp6.mulBy01(a, a, &b[0], &b[1])
+		if !a.equal(u) {
+			t.Fatal("bad mul by 01")
+		}
+	}
+	for j := 0; j < fuz; j++ {
+		a, _ = new(fe6).rand(rand.Reader)
+		b, _ = new(fe6).rand(rand.Reader)
+		u, _ = new(fe6).rand(rand.Reader)
+		b[2].zero()
+		b[0].zero()
+		fp6.mul(u, a, b)
+		fp6.mulBy1(a, a, &b[1])
+		if !a.equal(u) {
+			t.Fatal("bad mul by 1")
+		}
+	}
+}
+
+func TestFp6MultiplicationProperties(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe6).rand(rand.Reader)
+		b, _ := new(fe6).rand(rand.Reader)
+		zero := field.zero()
+		one := field.one()
+		c_1, c_2 := field.new(), field.new()
+		field.mul(c_1, a, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		field.mul(c_1, a, one)
+		if !c_1.equal(a) {
+			t.Fatal("a * 1 == a")
+		}
+		field.mul(c_1, a, b)
+		field.mul(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a * b == b * a")
+		}
+		c_x, _ := new(fe6).rand(rand.Reader)
+		field.mul(c_1, a, b)
+		field.mul(c_1, c_1, c_x)
+		field.mul(c_2, c_x, b)
+		field.mul(c_2, c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+		field.square(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("0^2 == 0")
+		}
+		field.square(a, one)
+		if !a.equal(one) {
+			t.Fatal("1^2 == 1")
+		}
+		_, _ = a.rand(rand.Reader)
+		field.square(c_1, a)
+		field.mul(c_2, a, a)
+		if !c_2.equal(c_1) {
+			t.Fatal("a^2 == a*a")
+		}
+	}
+}
+
+func TestFp6MultiplicationPropertiesAssigned(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe6).rand(rand.Reader)
+		zero, one := new(fe6).zero(), new(fe6).one()
+		field.mulAssign(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		_, _ = a.rand(rand.Reader)
+		a0 := new(fe6).set(a)
+		field.mulAssign(a, one)
+		if !a.equal(a0) {
+			t.Fatal("a * 1 == a")
+		}
+		_, _ = a.rand(rand.Reader)
+		b, _ := new(fe6).rand(rand.Reader)
+		a0.set(a)
+		field.mulAssign(a, b)
+		field.mulAssign(b, a0)
+		if !a.equal(b) {
+			t.Fatal("a * b == b * a")
+		}
+		c, _ := new(fe6).rand(rand.Reader)
+		a0.set(a)
+		field.mulAssign(a, b)
+		field.mulAssign(a, c)
+		field.mulAssign(a0, c)
+		field.mulAssign(a0, b)
+		if !a.equal(a0) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+	}
+}
+
+func TestFp6Exponentiation(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe6).rand(rand.Reader)
+		u := field.new()
+		field.exp(u, a, big.NewInt(0))
+		if !u.equal(field.one()) {
+			t.Fatal("a^0 == 1")
+		}
+		field.exp(u, a, big.NewInt(1))
+		if !u.equal(a) {
+			t.Fatal("a^1 == a")
+		}
+		v := field.new()
+		field.mul(u, a, a)
+		field.mul(u, u, u)
+		field.mul(u, u, u)
+		field.exp(v, a, big.NewInt(8))
+		if !u.equal(v) {
+			t.Fatal("((a^2)^2)^2 == a^8")
+		}
+	}
+}
+
+func TestFp6Inversion(t *testing.T) {
+	field := newFp6(nil)
+	for i := 0; i < fuz; i++ {
+		u := field.new()
+		zero := field.zero()
+		one := field.one()
+		field.inverse(u, zero)
+		if !u.equal(zero) {
+			t.Fatal("(0^-1) == 0)")
+		}
+		field.inverse(u, one)
+		if !u.equal(one) {
+			t.Fatal("(1^-1) == 1)")
+		}
+		a, _ := new(fe6).rand(rand.Reader)
+		field.inverse(u, a)
+		field.mul(u, u, a)
+		if !u.equal(one) {
+			t.Fatal("(r*a) * r*(a^-1) == r)")
+		}
+	}
+}
+
+func TestFp12Serialization(t *testing.T) {
+	field := newFp12(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe12).rand(rand.Reader)
+		b, err := field.fromBytes(field.toBytes(a))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !a.equal(b) {
+			t.Fatal("bad serialization")
+		}
+	}
+}
+
+func TestFp12AdditionProperties(t *testing.T) {
+	field := newFp12(nil)
+	for i := 0; i < fuz; i++ {
+		zero := field.zero()
+		a, _ := new(fe12).rand(rand.Reader)
+		b, _ := new(fe12).rand(rand.Reader)
+		c_1 := field.new()
+		c_2 := field.new()
+		field.add(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a + 0 == a")
+		}
+		field.sub(c_1, a, zero)
+		if !c_1.equal(a) {
+			t.Fatal("a - 0 == a")
+		}
+		field.double(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		field.neg(c_1, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("-0 == 0")
+		}
+		field.sub(c_1, zero, a)
+		field.neg(c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("0-a == -a")
+		}
+		field.double(c_1, a)
+		field.add(c_2, a, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("2 * a == a + a")
+		}
+		field.add(c_1, a, b)
+		field.add(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a + b = b + a")
+		}
+		field.sub(c_1, a, b)
+		field.sub(c_2, b, a)
+		field.neg(c_2, c_2)
+		if !c_1.equal(c_2) {
+			t.Fatal("a - b = - ( b - a )")
+		}
+		c_x, _ := new(fe12).rand(rand.Reader)
+		field.add(c_1, a, b)
+		field.add(c_1, c_1, c_x)
+		field.add(c_2, a, c_x)
+		field.add(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a + b) + c == (a + c ) + b")
+		}
+		field.sub(c_1, a, b)
+		field.sub(c_1, c_1, c_x)
+		field.sub(c_2, a, c_x)
+		field.sub(c_2, c_2, b)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a - b) - c == (a - c ) -b")
+		}
+	}
+}
+
+func TestFp12MultiplicationProperties(t *testing.T) {
+	field := newFp12(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe12).rand(rand.Reader)
+		b, _ := new(fe12).rand(rand.Reader)
+		zero := field.zero()
+		one := field.one()
+		c_1, c_2 := field.new(), field.new()
+		field.mul(c_1, a, zero)
+		if !c_1.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		field.mul(c_1, a, one)
+		if !c_1.equal(a) {
+			t.Fatal("a * 1 == a")
+		}
+		field.mul(c_1, a, b)
+		field.mul(c_2, b, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("a * b == b * a")
+		}
+		c_x, _ := new(fe12).rand(rand.Reader)
+		field.mul(c_1, a, b)
+		field.mul(c_1, c_1, c_x)
+		field.mul(c_2, c_x, b)
+		field.mul(c_2, c_2, a)
+		if !c_1.equal(c_2) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+		field.square(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("0^2 == 0")
+		}
+		field.square(a, one)
+		if !a.equal(one) {
+			t.Fatal("1^2 == 1")
+		}
+		_, _ = a.rand(rand.Reader)
+		field.square(c_1, a)
+		field.mul(c_2, a, a)
+		if !c_2.equal(c_1) {
+			t.Fatal("a^2 == a*a")
+		}
+	}
+}
+
+func TestFp12MultiplicationPropertiesAssigned(t *testing.T) {
+	field := newFp12(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe12).rand(rand.Reader)
+		zero, one := new(fe12).zero(), new(fe12).one()
+		field.mulAssign(a, zero)
+		if !a.equal(zero) {
+			t.Fatal("a * 0 == 0")
+		}
+		_, _ = a.rand(rand.Reader)
+		a0 := new(fe12).set(a)
+		field.mulAssign(a, one)
+		if !a.equal(a0) {
+			t.Fatal("a * 1 == a")
+		}
+		_, _ = a.rand(rand.Reader)
+		b, _ := new(fe12).rand(rand.Reader)
+		a0.set(a)
+		field.mulAssign(a, b)
+		field.mulAssign(b, a0)
+		if !a.equal(b) {
+			t.Fatal("a * b == b * a")
+		}
+		c, _ := new(fe12).rand(rand.Reader)
+		a0.set(a)
+		field.mulAssign(a, b)
+		field.mulAssign(a, c)
+		field.mulAssign(a0, c)
+		field.mulAssign(a0, b)
+		if !a.equal(a0) {
+			t.Fatal("(a * b) * c == (a * c) * b")
+		}
+	}
+}
+
+func TestFp12SparseMultiplication(t *testing.T) {
+	fp12 := newFp12(nil)
+	var a, b, u *fe12
+	for j := 0; j < fuz; j++ {
+		a, _ = new(fe12).rand(rand.Reader)
+		b, _ = new(fe12).rand(rand.Reader)
+		u, _ = new(fe12).rand(rand.Reader)
+		b[0][2].zero()
+		b[1][0].zero()
+		b[1][2].zero()
+		fp12.mul(u, a, b)
+		fp12.mulBy014Assign(a, &b[0][0], &b[0][1], &b[1][1])
+		if !a.equal(u) {
+			t.Fatal("bad mul by 01")
+		}
+	}
+}
+
+func TestFp12Exponentiation(t *testing.T) {
+	field := newFp12(nil)
+	for i := 0; i < fuz; i++ {
+		a, _ := new(fe12).rand(rand.Reader)
+		u := field.new()
+		field.exp(u, a, big.NewInt(0))
+		if !u.equal(field.one()) {
+			t.Fatal("a^0 == 1")
+		}
+		field.exp(u, a, big.NewInt(1))
+		if !u.equal(a) {
+			t.Fatal("a^1 == a")
+		}
+		v := field.new()
+		field.mul(u, a, a)
+		field.mul(u, u, u)
+		field.mul(u, u, u)
+		field.exp(v, a, big.NewInt(8))
+		if !u.equal(v) {
+			t.Fatal("((a^2)^2)^2 == a^8")
+		}
+	}
+}
+
+func TestFp12Inversion(t *testing.T) {
+	field := newFp12(nil)
+	for i := 0; i < fuz; i++ {
+		u := field.new()
+		zero := field.zero()
+		one := field.one()
+		field.inverse(u, zero)
+		if !u.equal(zero) {
+			t.Fatal("(0^-1) == 0)")
+		}
+		field.inverse(u, one)
+		if !u.equal(one) {
+			t.Fatal("(1^-1) == 1)")
+		}
+		a, _ := new(fe12).rand(rand.Reader)
+		field.inverse(u, a)
+		field.mul(u, u, a)
+		if !u.equal(one) {
+			t.Fatal("(r*a) * r*(a^-1) == r)")
+		}
+	}
+}
+
+func BenchmarkMultiplication(t *testing.B) {
+	a, _ := new(fe).rand(rand.Reader)
+	b, _ := new(fe).rand(rand.Reader)
+	c, _ := new(fe).rand(rand.Reader)
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		mul(c, a, b)
+	}
+}
+
+func BenchmarkInverse(t *testing.B) {
+	a, _ := new(fe).rand(rand.Reader)
+	b, _ := new(fe).rand(rand.Reader)
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		inverse(a, b)
+	}
+}
+
+func padBytes(in []byte, size int) []byte {
+	out := make([]byte, size)
+	if len(in) > size {
+		panic("bad input for padding")
+	}
+	copy(out[size-len(in):], in)
+	return out
+}
diff --git a/crypto/bls12381/g1.go b/crypto/bls12381/g1.go
new file mode 100644
index 0000000000..d853823cd2
--- /dev/null
+++ b/crypto/bls12381/g1.go
@@ -0,0 +1,434 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math"
+	"math/big"
+)
+
+// PointG1 is type for point in G1.
+// PointG1 is both used for Affine and Jacobian point representation.
+// If z is equal to one the point is considered as in affine form.
+type PointG1 [3]fe
+
+func (p *PointG1) Set(p2 *PointG1) *PointG1 {
+	p[0].set(&p2[0])
+	p[1].set(&p2[1])
+	p[2].set(&p2[2])
+	return p
+}
+
+// Zero returns G1 point in point at infinity representation
+func (p *PointG1) Zero() *PointG1 {
+	p[0].zero()
+	p[1].one()
+	p[2].zero()
+	return p
+}
+
+type tempG1 struct {
+	t [9]*fe
+}
+
+// G1 is struct for G1 group.
+type G1 struct {
+	tempG1
+}
+
+// NewG1 constructs a new G1 instance.
+func NewG1() *G1 {
+	t := newTempG1()
+	return &G1{t}
+}
+
+func newTempG1() tempG1 {
+	t := [9]*fe{}
+	for i := 0; i < 9; i++ {
+		t[i] = &fe{}
+	}
+	return tempG1{t}
+}
+
+// Q returns group order in big.Int.
+func (g *G1) Q() *big.Int {
+	return new(big.Int).Set(q)
+}
+
+func (g *G1) fromBytesUnchecked(in []byte) (*PointG1, error) {
+	p0, err := fromBytes(in[:48])
+	if err != nil {
+		return nil, err
+	}
+	p1, err := fromBytes(in[48:])
+	if err != nil {
+		return nil, err
+	}
+	p2 := new(fe).one()
+	return &PointG1{*p0, *p1, *p2}, nil
+}
+
+// FromBytes constructs a new point given uncompressed byte input.
+// FromBytes does not take zcash flags into account.
+// Byte input expected to be larger than 96 bytes.
+// First 96 bytes should be concatenation of x and y values.
+// Point (0, 0) is considered as infinity.
+func (g *G1) FromBytes(in []byte) (*PointG1, error) {
+	if len(in) != 96 {
+		return nil, errors.New("input string should be equal or larger than 96")
+	}
+	p0, err := fromBytes(in[:48])
+	if err != nil {
+		return nil, err
+	}
+	p1, err := fromBytes(in[48:])
+	if err != nil {
+		return nil, err
+	}
+	// check if given input points to infinity
+	if p0.isZero() && p1.isZero() {
+		return g.Zero(), nil
+	}
+	p2 := new(fe).one()
+	p := &PointG1{*p0, *p1, *p2}
+	if !g.IsOnCurve(p) {
+		return nil, errors.New("point is not on curve")
+	}
+	return p, nil
+}
+
+// DecodePoint given encoded (x, y) coordinates in 128 bytes returns a valid G1 Point.
+func (g *G1) DecodePoint(in []byte) (*PointG1, error) {
+	if len(in) != 128 {
+		return nil, errors.New("invalid g1 point length")
+	}
+	pointBytes := make([]byte, 96)
+	// decode x
+	xBytes, err := decodeFieldElement(in[:64])
+	if err != nil {
+		return nil, err
+	}
+	// decode y
+	yBytes, err := decodeFieldElement(in[64:])
+	if err != nil {
+		return nil, err
+	}
+	copy(pointBytes[:48], xBytes)
+	copy(pointBytes[48:], yBytes)
+	return g.FromBytes(pointBytes)
+}
+
+// ToBytes serializes a point into bytes in uncompressed form.
+// ToBytes does not take zcash flags into account.
+// ToBytes returns (0, 0) if point is infinity.
+func (g *G1) ToBytes(p *PointG1) []byte {
+	out := make([]byte, 96)
+	if g.IsZero(p) {
+		return out
+	}
+	g.Affine(p)
+	copy(out[:48], toBytes(&p[0]))
+	copy(out[48:], toBytes(&p[1]))
+	return out
+}
+
+// EncodePoint encodes a point into 128 bytes.
+func (g *G1) EncodePoint(p *PointG1) []byte {
+	outRaw := g.ToBytes(p)
+	out := make([]byte, 128)
+	// encode x
+	copy(out[16:], outRaw[:48])
+	// encode y
+	copy(out[64+16:], outRaw[48:])
+	return out
+}
+
+// New creates a new G1 Point which is equal to zero in other words point at infinity.
+func (g *G1) New() *PointG1 {
+	return g.Zero()
+}
+
+// Zero returns a new G1 Point which is equal to point at infinity.
+func (g *G1) Zero() *PointG1 {
+	return new(PointG1).Zero()
+}
+
+// One returns a new G1 Point which is equal to generator point.
+func (g *G1) One() *PointG1 {
+	p := &PointG1{}
+	return p.Set(&g1One)
+}
+
+// IsZero returns true if given point is equal to zero.
+func (g *G1) IsZero(p *PointG1) bool {
+	return p[2].isZero()
+}
+
+// Equal checks if given two G1 point is equal in their affine form.
+func (g *G1) Equal(p1, p2 *PointG1) bool {
+	if g.IsZero(p1) {
+		return g.IsZero(p2)
+	}
+	if g.IsZero(p2) {
+		return g.IsZero(p1)
+	}
+	t := g.t
+	square(t[0], &p1[2])
+	square(t[1], &p2[2])
+	mul(t[2], t[0], &p2[0])
+	mul(t[3], t[1], &p1[0])
+	mul(t[0], t[0], &p1[2])
+	mul(t[1], t[1], &p2[2])
+	mul(t[1], t[1], &p1[1])
+	mul(t[0], t[0], &p2[1])
+	return t[0].equal(t[1]) && t[2].equal(t[3])
+}
+
+// InCorrectSubgroup checks whether given point is in correct subgroup.
+func (g *G1) InCorrectSubgroup(p *PointG1) bool {
+	tmp := &PointG1{}
+	g.MulScalar(tmp, p, q)
+	return g.IsZero(tmp)
+}
+
+// IsOnCurve checks a G1 point is on curve.
+func (g *G1) IsOnCurve(p *PointG1) bool {
+	if g.IsZero(p) {
+		return true
+	}
+	t := g.t
+	square(t[0], &p[1])
+	square(t[1], &p[0])
+	mul(t[1], t[1], &p[0])
+	square(t[2], &p[2])
+	square(t[3], t[2])
+	mul(t[2], t[2], t[3])
+	mul(t[2], b, t[2])
+	add(t[1], t[1], t[2])
+	return t[0].equal(t[1])
+}
+
+// IsAffine checks a G1 point whether it is in affine form.
+func (g *G1) IsAffine(p *PointG1) bool {
+	return p[2].isOne()
+}
+
+// Add adds two G1 points p1, p2 and assigns the result to point at first argument.
+func (g *G1) Affine(p *PointG1) *PointG1 {
+	if g.IsZero(p) {
+		return p
+	}
+	if !g.IsAffine(p) {
+		t := g.t
+		inverse(t[0], &p[2])
+		square(t[1], t[0])
+		mul(&p[0], &p[0], t[1])
+		mul(t[0], t[0], t[1])
+		mul(&p[1], &p[1], t[0])
+		p[2].one()
+	}
+	return p
+}
+
+// Add adds two G1 points p1, p2 and assigns the result to point at first argument.
+func (g *G1) Add(r, p1, p2 *PointG1) *PointG1 {
+	// http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#addition-add-2007-bl
+	if g.IsZero(p1) {
+		return r.Set(p2)
+	}
+	if g.IsZero(p2) {
+		return r.Set(p1)
+	}
+	t := g.t
+	square(t[7], &p1[2])
+	mul(t[1], &p2[0], t[7])
+	mul(t[2], &p1[2], t[7])
+	mul(t[0], &p2[1], t[2])
+	square(t[8], &p2[2])
+	mul(t[3], &p1[0], t[8])
+	mul(t[4], &p2[2], t[8])
+	mul(t[2], &p1[1], t[4])
+	if t[1].equal(t[3]) {
+		if t[0].equal(t[2]) {
+			return g.Double(r, p1)
+		}
+		return r.Zero()
+	}
+	sub(t[1], t[1], t[3])
+	double(t[4], t[1])
+	square(t[4], t[4])
+	mul(t[5], t[1], t[4])
+	sub(t[0], t[0], t[2])
+	double(t[0], t[0])
+	square(t[6], t[0])
+	sub(t[6], t[6], t[5])
+	mul(t[3], t[3], t[4])
+	double(t[4], t[3])
+	sub(&r[0], t[6], t[4])
+	sub(t[4], t[3], &r[0])
+	mul(t[6], t[2], t[5])
+	double(t[6], t[6])
+	mul(t[0], t[0], t[4])
+	sub(&r[1], t[0], t[6])
+	add(t[0], &p1[2], &p2[2])
+	square(t[0], t[0])
+	sub(t[0], t[0], t[7])
+	sub(t[0], t[0], t[8])
+	mul(&r[2], t[0], t[1])
+	return r
+}
+
+// Double doubles a G1 point p and assigns the result to the point at first argument.
+func (g *G1) Double(r, p *PointG1) *PointG1 {
+	// http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
+	if g.IsZero(p) {
+		return r.Set(p)
+	}
+	t := g.t
+	square(t[0], &p[0])
+	square(t[1], &p[1])
+	square(t[2], t[1])
+	add(t[1], &p[0], t[1])
+	square(t[1], t[1])
+	sub(t[1], t[1], t[0])
+	sub(t[1], t[1], t[2])
+	double(t[1], t[1])
+	double(t[3], t[0])
+	add(t[0], t[3], t[0])
+	square(t[4], t[0])
+	double(t[3], t[1])
+	sub(&r[0], t[4], t[3])
+	sub(t[1], t[1], &r[0])
+	double(t[2], t[2])
+	double(t[2], t[2])
+	double(t[2], t[2])
+	mul(t[0], t[0], t[1])
+	sub(t[1], t[0], t[2])
+	mul(t[0], &p[1], &p[2])
+	r[1].set(t[1])
+	double(&r[2], t[0])
+	return r
+}
+
+// Neg negates a G1 point p and assigns the result to the point at first argument.
+func (g *G1) Neg(r, p *PointG1) *PointG1 {
+	r[0].set(&p[0])
+	r[2].set(&p[2])
+	neg(&r[1], &p[1])
+	return r
+}
+
+// Sub subtracts two G1 points p1, p2 and assigns the result to point at first argument.
+func (g *G1) Sub(c, a, b *PointG1) *PointG1 {
+	d := &PointG1{}
+	g.Neg(d, b)
+	g.Add(c, a, d)
+	return c
+}
+
+// MulScalar multiplies a point by given scalar value in big.Int and assigns the result to point at first argument.
+func (g *G1) MulScalar(c, p *PointG1, e *big.Int) *PointG1 {
+	q, n := &PointG1{}, &PointG1{}
+	n.Set(p)
+	l := e.BitLen()
+	for i := 0; i < l; i++ {
+		if e.Bit(i) == 1 {
+			g.Add(q, q, n)
+		}
+		g.Double(n, n)
+	}
+	return c.Set(q)
+}
+
+// ClearCofactor maps given a G1 point to correct subgroup
+func (g *G1) ClearCofactor(p *PointG1) {
+	g.MulScalar(p, p, cofactorEFFG1)
+}
+
+// MultiExp calculates multi exponentiation. Given pairs of G1 point and scalar values
+// (P_0, e_0), (P_1, e_1), ... (P_n, e_n) calculates r = e_0 * P_0 + e_1 * P_1 + ... + e_n * P_n
+// Length of points and scalars are expected to be equal, otherwise an error is returned.
+// Result is assigned to point at first argument.
+func (g *G1) MultiExp(r *PointG1, points []*PointG1, powers []*big.Int) (*PointG1, error) {
+	if len(points) != len(powers) {
+		return nil, errors.New("point and scalar vectors should be in same length")
+	}
+	var c uint32 = 3
+	if len(powers) >= 32 {
+		c = uint32(math.Ceil(math.Log10(float64(len(powers)))))
+	}
+	bucketSize, numBits := (1<= 0; i-- {
+			g.Add(sum, sum, bucket[i])
+			g.Add(acc, acc, sum)
+		}
+		windows[j] = g.New()
+		windows[j].Set(acc)
+		j++
+		cur += c
+	}
+	acc.Zero()
+	for i := len(windows) - 1; i >= 0; i-- {
+		for j := uint32(0); j < c; j++ {
+			g.Double(acc, acc)
+		}
+		g.Add(acc, acc, windows[i])
+	}
+	return r.Set(acc), nil
+}
+
+// MapToCurve given a byte slice returns a valid G1 point.
+// This mapping function implements the Simplified Shallue-van de Woestijne-Ulas method.
+// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06
+// Input byte slice should be a valid field element, otherwise an error is returned.
+func (g *G1) MapToCurve(in []byte) (*PointG1, error) {
+	u, err := fromBytes(in)
+	if err != nil {
+		return nil, err
+	}
+	x, y := swuMapG1(u)
+	isogenyMapG1(x, y)
+	one := new(fe).one()
+	p := &PointG1{*x, *y, *one}
+	g.ClearCofactor(p)
+	return g.Affine(p), nil
+}
diff --git a/crypto/bls12381/g1_test.go b/crypto/bls12381/g1_test.go
new file mode 100644
index 0000000000..eef9f45055
--- /dev/null
+++ b/crypto/bls12381/g1_test.go
@@ -0,0 +1,283 @@
+package bls12381
+
+import (
+	"bytes"
+	"crypto/rand"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+func (g *G1) one() *PointG1 {
+	one, _ := g.fromBytesUnchecked(
+		common.FromHex("" +
+			"17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" +
+			"08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1",
+		),
+	)
+	return one
+}
+
+func (g *G1) rand() *PointG1 {
+	k, err := rand.Int(rand.Reader, q)
+	if err != nil {
+		panic(err)
+	}
+	return g.MulScalar(&PointG1{}, g.one(), k)
+}
+
+func TestG1Serialization(t *testing.T) {
+	g1 := NewG1()
+	for i := 0; i < fuz; i++ {
+		a := g1.rand()
+		buf := g1.ToBytes(a)
+		b, err := g1.FromBytes(buf)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !g1.Equal(a, b) {
+			t.Fatal("bad serialization from/to")
+		}
+	}
+	for i := 0; i < fuz; i++ {
+		a := g1.rand()
+		encoded := g1.EncodePoint(a)
+		b, err := g1.DecodePoint(encoded)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !g1.Equal(a, b) {
+			t.Fatal("bad serialization encode/decode")
+		}
+	}
+}
+
+func TestG1IsOnCurve(t *testing.T) {
+	g := NewG1()
+	zero := g.Zero()
+	if !g.IsOnCurve(zero) {
+		t.Fatal("zero must be on curve")
+	}
+	one := new(fe).one()
+	p := &PointG1{*one, *one, *one}
+	if g.IsOnCurve(p) {
+		t.Fatal("(1, 1) is not on curve")
+	}
+}
+
+func TestG1AdditiveProperties(t *testing.T) {
+	g := NewG1()
+	t0, t1 := g.New(), g.New()
+	zero := g.Zero()
+	for i := 0; i < fuz; i++ {
+		a, b := g.rand(), g.rand()
+		g.Add(t0, a, zero)
+		if !g.Equal(t0, a) {
+			t.Fatal("a + 0 == a")
+		}
+		g.Add(t0, zero, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("0 + 0 == 0")
+		}
+		g.Sub(t0, a, zero)
+		if !g.Equal(t0, a) {
+			t.Fatal("a - 0 == a")
+		}
+		g.Sub(t0, zero, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("0 - 0 == 0")
+		}
+		g.Neg(t0, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("- 0 == 0")
+		}
+		g.Sub(t0, zero, a)
+		g.Neg(t0, t0)
+		if !g.Equal(t0, a) {
+			t.Fatal(" - (0 - a) == a")
+		}
+		g.Double(t0, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		g.Double(t0, a)
+		g.Sub(t0, t0, a)
+		if !g.Equal(t0, a) || !g.IsOnCurve(t0) {
+			t.Fatal(" (2 * a) - a == a")
+		}
+		g.Add(t0, a, b)
+		g.Add(t1, b, a)
+		if !g.Equal(t0, t1) {
+			t.Fatal("a + b == b + a")
+		}
+		g.Sub(t0, a, b)
+		g.Sub(t1, b, a)
+		g.Neg(t1, t1)
+		if !g.Equal(t0, t1) {
+			t.Fatal("a - b == - ( b - a )")
+		}
+		c := g.rand()
+		g.Add(t0, a, b)
+		g.Add(t0, t0, c)
+		g.Add(t1, a, c)
+		g.Add(t1, t1, b)
+		if !g.Equal(t0, t1) {
+			t.Fatal("(a + b) + c == (a + c ) + b")
+		}
+		g.Sub(t0, a, b)
+		g.Sub(t0, t0, c)
+		g.Sub(t1, a, c)
+		g.Sub(t1, t1, b)
+		if !g.Equal(t0, t1) {
+			t.Fatal("(a - b) - c == (a - c) -b")
+		}
+	}
+}
+
+func TestG1MultiplicativeProperties(t *testing.T) {
+	g := NewG1()
+	t0, t1 := g.New(), g.New()
+	zero := g.Zero()
+	for i := 0; i < fuz; i++ {
+		a := g.rand()
+		s1, s2, s3 := randScalar(q), randScalar(q), randScalar(q)
+		sone := big.NewInt(1)
+		g.MulScalar(t0, zero, s1)
+		if !g.Equal(t0, zero) {
+			t.Fatal(" 0 ^ s == 0")
+		}
+		g.MulScalar(t0, a, sone)
+		if !g.Equal(t0, a) {
+			t.Fatal(" a ^ 1 == a")
+		}
+		g.MulScalar(t0, zero, s1)
+		if !g.Equal(t0, zero) {
+			t.Fatal(" 0 ^ s == a")
+		}
+		g.MulScalar(t0, a, s1)
+		g.MulScalar(t0, t0, s2)
+		s3.Mul(s1, s2)
+		g.MulScalar(t1, a, s3)
+		if !g.Equal(t0, t1) {
+			t.Errorf(" (a ^ s1) ^ s2 == a ^ (s1 * s2)")
+		}
+		g.MulScalar(t0, a, s1)
+		g.MulScalar(t1, a, s2)
+		g.Add(t0, t0, t1)
+		s3.Add(s1, s2)
+		g.MulScalar(t1, a, s3)
+		if !g.Equal(t0, t1) {
+			t.Errorf(" (a ^ s1) + (a ^ s2) == a ^ (s1 + s2)")
+		}
+	}
+}
+
+func TestG1MultiExpExpected(t *testing.T) {
+	g := NewG1()
+	one := g.one()
+	var scalars [2]*big.Int
+	var bases [2]*PointG1
+	scalars[0] = big.NewInt(2)
+	scalars[1] = big.NewInt(3)
+	bases[0], bases[1] = new(PointG1).Set(one), new(PointG1).Set(one)
+	expected, result := g.New(), g.New()
+	g.MulScalar(expected, one, big.NewInt(5))
+	_, _ = g.MultiExp(result, bases[:], scalars[:])
+	if !g.Equal(expected, result) {
+		t.Fatal("bad multi-exponentiation")
+	}
+}
+
+func TestG1MultiExpBatch(t *testing.T) {
+	g := NewG1()
+	one := g.one()
+	n := 1000
+	bases := make([]*PointG1, n)
+	scalars := make([]*big.Int, n)
+	// scalars: [s0,s1 ... s(n-1)]
+	// bases: [P0,P1,..P(n-1)] = [s(n-1)*G, s(n-2)*G ... s0*G]
+	for i, j := 0, n-1; i < n; i, j = i+1, j-1 {
+		scalars[j], _ = rand.Int(rand.Reader, big.NewInt(100000))
+		bases[i] = g.New()
+		g.MulScalar(bases[i], one, scalars[j])
+	}
+	// expected: s(n-1)*P0 + s(n-2)*P1 + s0*P(n-1)
+	expected, tmp := g.New(), g.New()
+	for i := 0; i < n; i++ {
+		g.MulScalar(tmp, bases[i], scalars[i])
+		g.Add(expected, expected, tmp)
+	}
+	result := g.New()
+	_, _ = g.MultiExp(result, bases, scalars)
+	if !g.Equal(expected, result) {
+		t.Fatal("bad multi-exponentiation")
+	}
+}
+
+func TestG1MapToCurve(t *testing.T) {
+	for i, v := range []struct {
+		u        []byte
+		expected []byte
+	}{
+		{
+			u:        make([]byte, 48),
+			expected: common.FromHex("11a9a0372b8f332d5c30de9ad14e50372a73fa4c45d5f2fa5097f2d6fb93bcac592f2e1711ac43db0519870c7d0ea415" + "092c0f994164a0719f51c24ba3788de240ff926b55f58c445116e8bc6a47cd63392fd4e8e22bdf9feaa96ee773222133"),
+		},
+		{
+			u:        common.FromHex("07fdf49ea58e96015d61f6b5c9d1c8f277146a533ae7fbca2a8ef4c41055cd961fbc6e26979b5554e4b4f22330c0e16d"),
+			expected: common.FromHex("1223effdbb2d38152495a864d78eee14cb0992d89a241707abb03819a91a6d2fd65854ab9a69e9aacb0cbebfd490732c" + "0f925d61e0b235ecd945cbf0309291878df0d06e5d80d6b84aa4ff3e00633b26f9a7cb3523ef737d90e6d71e8b98b2d5"),
+		},
+		{
+			u:        common.FromHex("1275ab3adbf824a169ed4b1fd669b49cf406d822f7fe90d6b2f8c601b5348436f89761bb1ad89a6fb1137cd91810e5d2"),
+			expected: common.FromHex("179d3fd0b4fb1da43aad06cea1fb3f828806ddb1b1fa9424b1e3944dfdbab6e763c42636404017da03099af0dcca0fd6" + "0d037cb1c6d495c0f5f22b061d23f1be3d7fe64d3c6820cfcd99b6b36fa69f7b4c1f4addba2ae7aa46fb25901ab483e4"),
+		},
+		{
+			u:        common.FromHex("0e93d11d30de6d84b8578827856f5c05feef36083eef0b7b263e35ecb9b56e86299614a042e57d467fa20948e8564909"),
+			expected: common.FromHex("15aa66c77eded1209db694e8b1ba49daf8b686733afaa7b68c683d0b01788dfb0617a2e2d04c0856db4981921d3004af" + "0952bb2f61739dd1d201dd0a79d74cda3285403d47655ee886afe860593a8a4e51c5b77a22d2133e3a4280eaaaa8b788"),
+		},
+		{
+			u:        common.FromHex("015a41481155d17074d20be6d8ec4d46632a51521cd9c916e265bd9b47343b3689979b50708c8546cbc2916b86cb1a3a"),
+			expected: common.FromHex("06328ce5106e837935e8da84bd9af473422e62492930aa5f460369baad9545defa468d9399854c23a75495d2a80487ee" + "094bfdfe3e552447433b5a00967498a3f1314b86ce7a7164c8a8f4131f99333b30a574607e301d5f774172c627fd0bca"),
+		},
+	} {
+		g := NewG1()
+		p0, err := g.MapToCurve(v.u)
+		if err != nil {
+			t.Fatal("map to curve fails", i, err)
+		}
+		if !bytes.Equal(g.ToBytes(p0), v.expected) {
+			t.Fatal("map to curve fails", i)
+		}
+	}
+}
+
+func BenchmarkG1Add(t *testing.B) {
+	g1 := NewG1()
+	a, b, c := g1.rand(), g1.rand(), PointG1{}
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		g1.Add(&c, a, b)
+	}
+}
+
+func BenchmarkG1Mul(t *testing.B) {
+	g1 := NewG1()
+	a, e, c := g1.rand(), q, PointG1{}
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		g1.MulScalar(&c, a, e)
+	}
+}
+
+func BenchmarkG1MapToCurve(t *testing.B) {
+	a := make([]byte, 48)
+	g1 := NewG1()
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		_, err := g1.MapToCurve(a)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
diff --git a/crypto/bls12381/g2.go b/crypto/bls12381/g2.go
new file mode 100644
index 0000000000..fa110e3edf
--- /dev/null
+++ b/crypto/bls12381/g2.go
@@ -0,0 +1,456 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math"
+	"math/big"
+)
+
+// PointG2 is type for point in G2.
+// PointG2 is both used for Affine and Jacobian point representation.
+// If z is equal to one the point is considered as in affine form.
+type PointG2 [3]fe2
+
+// Set copies valeus of one point to another.
+func (p *PointG2) Set(p2 *PointG2) *PointG2 {
+	p[0].set(&p2[0])
+	p[1].set(&p2[1])
+	p[2].set(&p2[2])
+	return p
+}
+
+// Zero returns G2 point in point at infinity representation
+func (p *PointG2) Zero() *PointG2 {
+	p[0].zero()
+	p[1].one()
+	p[2].zero()
+	return p
+
+}
+
+type tempG2 struct {
+	t [9]*fe2
+}
+
+// G2 is struct for G2 group.
+type G2 struct {
+	f *fp2
+	tempG2
+}
+
+// NewG2 constructs a new G2 instance.
+func NewG2() *G2 {
+	return newG2(nil)
+}
+
+func newG2(f *fp2) *G2 {
+	if f == nil {
+		f = newFp2()
+	}
+	t := newTempG2()
+	return &G2{f, t}
+}
+
+func newTempG2() tempG2 {
+	t := [9]*fe2{}
+	for i := 0; i < 9; i++ {
+		t[i] = &fe2{}
+	}
+	return tempG2{t}
+}
+
+// Q returns group order in big.Int.
+func (g *G2) Q() *big.Int {
+	return new(big.Int).Set(q)
+}
+
+func (g *G2) fromBytesUnchecked(in []byte) (*PointG2, error) {
+	p0, err := g.f.fromBytes(in[:96])
+	if err != nil {
+		return nil, err
+	}
+	p1, err := g.f.fromBytes(in[96:])
+	if err != nil {
+		return nil, err
+	}
+	p2 := new(fe2).one()
+	return &PointG2{*p0, *p1, *p2}, nil
+}
+
+// FromBytes constructs a new point given uncompressed byte input.
+// FromBytes does not take zcash flags into account.
+// Byte input expected to be larger than 96 bytes.
+// First 192 bytes should be concatenation of x and y values
+// Point (0, 0) is considered as infinity.
+func (g *G2) FromBytes(in []byte) (*PointG2, error) {
+	if len(in) != 192 {
+		return nil, errors.New("input string should be equal or larger than 192")
+	}
+	p0, err := g.f.fromBytes(in[:96])
+	if err != nil {
+		return nil, err
+	}
+	p1, err := g.f.fromBytes(in[96:])
+	if err != nil {
+		return nil, err
+	}
+	// check if given input points to infinity
+	if p0.isZero() && p1.isZero() {
+		return g.Zero(), nil
+	}
+	p2 := new(fe2).one()
+	p := &PointG2{*p0, *p1, *p2}
+	if !g.IsOnCurve(p) {
+		return nil, errors.New("point is not on curve")
+	}
+	return p, nil
+}
+
+// DecodePoint given encoded (x, y) coordinates in 256 bytes returns a valid G1 Point.
+func (g *G2) DecodePoint(in []byte) (*PointG2, error) {
+	if len(in) != 256 {
+		return nil, errors.New("invalid g2 point length")
+	}
+	pointBytes := make([]byte, 192)
+	x0Bytes, err := decodeFieldElement(in[:64])
+	if err != nil {
+		return nil, err
+	}
+	x1Bytes, err := decodeFieldElement(in[64:128])
+	if err != nil {
+		return nil, err
+	}
+	y0Bytes, err := decodeFieldElement(in[128:192])
+	if err != nil {
+		return nil, err
+	}
+	y1Bytes, err := decodeFieldElement(in[192:])
+	if err != nil {
+		return nil, err
+	}
+	copy(pointBytes[:48], x1Bytes)
+	copy(pointBytes[48:96], x0Bytes)
+	copy(pointBytes[96:144], y1Bytes)
+	copy(pointBytes[144:192], y0Bytes)
+	return g.FromBytes(pointBytes)
+}
+
+// ToBytes serializes a point into bytes in uncompressed form,
+// does not take zcash flags into account,
+// returns (0, 0) if point is infinity.
+func (g *G2) ToBytes(p *PointG2) []byte {
+	out := make([]byte, 192)
+	if g.IsZero(p) {
+		return out
+	}
+	g.Affine(p)
+	copy(out[:96], g.f.toBytes(&p[0]))
+	copy(out[96:], g.f.toBytes(&p[1]))
+	return out
+}
+
+// EncodePoint encodes a point into 256 bytes.
+func (g *G2) EncodePoint(p *PointG2) []byte {
+	// outRaw is 96 bytes
+	outRaw := g.ToBytes(p)
+	out := make([]byte, 256)
+	// encode x
+	copy(out[16:16+48], outRaw[48:96])
+	copy(out[80:80+48], outRaw[:48])
+	// encode y
+	copy(out[144:144+48], outRaw[144:])
+	copy(out[208:208+48], outRaw[96:144])
+	return out
+}
+
+// New creates a new G2 Point which is equal to zero in other words point at infinity.
+func (g *G2) New() *PointG2 {
+	return new(PointG2).Zero()
+}
+
+// Zero returns a new G2 Point which is equal to point at infinity.
+func (g *G2) Zero() *PointG2 {
+	return new(PointG2).Zero()
+}
+
+// One returns a new G2 Point which is equal to generator point.
+func (g *G2) One() *PointG2 {
+	p := &PointG2{}
+	return p.Set(&g2One)
+}
+
+// IsZero returns true if given point is equal to zero.
+func (g *G2) IsZero(p *PointG2) bool {
+	return p[2].isZero()
+}
+
+// Equal checks if given two G2 point is equal in their affine form.
+func (g *G2) Equal(p1, p2 *PointG2) bool {
+	if g.IsZero(p1) {
+		return g.IsZero(p2)
+	}
+	if g.IsZero(p2) {
+		return g.IsZero(p1)
+	}
+	t := g.t
+	g.f.square(t[0], &p1[2])
+	g.f.square(t[1], &p2[2])
+	g.f.mul(t[2], t[0], &p2[0])
+	g.f.mul(t[3], t[1], &p1[0])
+	g.f.mul(t[0], t[0], &p1[2])
+	g.f.mul(t[1], t[1], &p2[2])
+	g.f.mul(t[1], t[1], &p1[1])
+	g.f.mul(t[0], t[0], &p2[1])
+	return t[0].equal(t[1]) && t[2].equal(t[3])
+}
+
+// InCorrectSubgroup checks whether given point is in correct subgroup.
+func (g *G2) InCorrectSubgroup(p *PointG2) bool {
+	tmp := &PointG2{}
+	g.MulScalar(tmp, p, q)
+	return g.IsZero(tmp)
+}
+
+// IsOnCurve checks a G2 point is on curve.
+func (g *G2) IsOnCurve(p *PointG2) bool {
+	if g.IsZero(p) {
+		return true
+	}
+	t := g.t
+	g.f.square(t[0], &p[1])
+	g.f.square(t[1], &p[0])
+	g.f.mul(t[1], t[1], &p[0])
+	g.f.square(t[2], &p[2])
+	g.f.square(t[3], t[2])
+	g.f.mul(t[2], t[2], t[3])
+	g.f.mul(t[2], b2, t[2])
+	g.f.add(t[1], t[1], t[2])
+	return t[0].equal(t[1])
+}
+
+// IsAffine checks a G2 point whether it is in affine form.
+func (g *G2) IsAffine(p *PointG2) bool {
+	return p[2].isOne()
+}
+
+// Affine calculates affine form of given G2 point.
+func (g *G2) Affine(p *PointG2) *PointG2 {
+	if g.IsZero(p) {
+		return p
+	}
+	if !g.IsAffine(p) {
+		t := g.t
+		g.f.inverse(t[0], &p[2])
+		g.f.square(t[1], t[0])
+		g.f.mul(&p[0], &p[0], t[1])
+		g.f.mul(t[0], t[0], t[1])
+		g.f.mul(&p[1], &p[1], t[0])
+		p[2].one()
+	}
+	return p
+}
+
+// Add adds two G2 points p1, p2 and assigns the result to point at first argument.
+func (g *G2) Add(r, p1, p2 *PointG2) *PointG2 {
+	// http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#addition-add-2007-bl
+	if g.IsZero(p1) {
+		return r.Set(p2)
+	}
+	if g.IsZero(p2) {
+		return r.Set(p1)
+	}
+	t := g.t
+	g.f.square(t[7], &p1[2])
+	g.f.mul(t[1], &p2[0], t[7])
+	g.f.mul(t[2], &p1[2], t[7])
+	g.f.mul(t[0], &p2[1], t[2])
+	g.f.square(t[8], &p2[2])
+	g.f.mul(t[3], &p1[0], t[8])
+	g.f.mul(t[4], &p2[2], t[8])
+	g.f.mul(t[2], &p1[1], t[4])
+	if t[1].equal(t[3]) {
+		if t[0].equal(t[2]) {
+			return g.Double(r, p1)
+		}
+		return r.Zero()
+	}
+	g.f.sub(t[1], t[1], t[3])
+	g.f.double(t[4], t[1])
+	g.f.square(t[4], t[4])
+	g.f.mul(t[5], t[1], t[4])
+	g.f.sub(t[0], t[0], t[2])
+	g.f.double(t[0], t[0])
+	g.f.square(t[6], t[0])
+	g.f.sub(t[6], t[6], t[5])
+	g.f.mul(t[3], t[3], t[4])
+	g.f.double(t[4], t[3])
+	g.f.sub(&r[0], t[6], t[4])
+	g.f.sub(t[4], t[3], &r[0])
+	g.f.mul(t[6], t[2], t[5])
+	g.f.double(t[6], t[6])
+	g.f.mul(t[0], t[0], t[4])
+	g.f.sub(&r[1], t[0], t[6])
+	g.f.add(t[0], &p1[2], &p2[2])
+	g.f.square(t[0], t[0])
+	g.f.sub(t[0], t[0], t[7])
+	g.f.sub(t[0], t[0], t[8])
+	g.f.mul(&r[2], t[0], t[1])
+	return r
+}
+
+// Double doubles a G2 point p and assigns the result to the point at first argument.
+func (g *G2) Double(r, p *PointG2) *PointG2 {
+	// http://www.hyperelliptic.org/EFD/gp/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
+	if g.IsZero(p) {
+		return r.Set(p)
+	}
+	t := g.t
+	g.f.square(t[0], &p[0])
+	g.f.square(t[1], &p[1])
+	g.f.square(t[2], t[1])
+	g.f.add(t[1], &p[0], t[1])
+	g.f.square(t[1], t[1])
+	g.f.sub(t[1], t[1], t[0])
+	g.f.sub(t[1], t[1], t[2])
+	g.f.double(t[1], t[1])
+	g.f.double(t[3], t[0])
+	g.f.add(t[0], t[3], t[0])
+	g.f.square(t[4], t[0])
+	g.f.double(t[3], t[1])
+	g.f.sub(&r[0], t[4], t[3])
+	g.f.sub(t[1], t[1], &r[0])
+	g.f.double(t[2], t[2])
+	g.f.double(t[2], t[2])
+	g.f.double(t[2], t[2])
+	g.f.mul(t[0], t[0], t[1])
+	g.f.sub(t[1], t[0], t[2])
+	g.f.mul(t[0], &p[1], &p[2])
+	r[1].set(t[1])
+	g.f.double(&r[2], t[0])
+	return r
+}
+
+// Neg negates a G2 point p and assigns the result to the point at first argument.
+func (g *G2) Neg(r, p *PointG2) *PointG2 {
+	r[0].set(&p[0])
+	g.f.neg(&r[1], &p[1])
+	r[2].set(&p[2])
+	return r
+}
+
+// Sub subtracts two G2 points p1, p2 and assigns the result to point at first argument.
+func (g *G2) Sub(c, a, b *PointG2) *PointG2 {
+	d := &PointG2{}
+	g.Neg(d, b)
+	g.Add(c, a, d)
+	return c
+}
+
+// MulScalar multiplies a point by given scalar value in big.Int and assigns the result to point at first argument.
+func (g *G2) MulScalar(c, p *PointG2, e *big.Int) *PointG2 {
+	q, n := &PointG2{}, &PointG2{}
+	n.Set(p)
+	l := e.BitLen()
+	for i := 0; i < l; i++ {
+		if e.Bit(i) == 1 {
+			g.Add(q, q, n)
+		}
+		g.Double(n, n)
+	}
+	return c.Set(q)
+}
+
+// ClearCofactor maps given a G2 point to correct subgroup
+func (g *G2) ClearCofactor(p *PointG2) {
+	g.MulScalar(p, p, cofactorEFFG2)
+}
+
+// MultiExp calculates multi exponentiation. Given pairs of G2 point and scalar values
+// (P_0, e_0), (P_1, e_1), ... (P_n, e_n) calculates r = e_0 * P_0 + e_1 * P_1 + ... + e_n * P_n
+// Length of points and scalars are expected to be equal, otherwise an error is returned.
+// Result is assigned to point at first argument.
+func (g *G2) MultiExp(r *PointG2, points []*PointG2, powers []*big.Int) (*PointG2, error) {
+	if len(points) != len(powers) {
+		return nil, errors.New("point and scalar vectors should be in same length")
+	}
+	var c uint32 = 3
+	if len(powers) >= 32 {
+		c = uint32(math.Ceil(math.Log10(float64(len(powers)))))
+	}
+	bucketSize, numBits := (1<= 0; i-- {
+			g.Add(sum, sum, bucket[i])
+			g.Add(acc, acc, sum)
+		}
+		windows[j] = g.New()
+		windows[j].Set(acc)
+		j++
+		cur += c
+	}
+	acc.Zero()
+	for i := len(windows) - 1; i >= 0; i-- {
+		for j := uint32(0); j < c; j++ {
+			g.Double(acc, acc)
+		}
+		g.Add(acc, acc, windows[i])
+	}
+	return r.Set(acc), nil
+}
+
+// MapToCurve given a byte slice returns a valid G2 point.
+// This mapping function implements the Simplified Shallue-van de Woestijne-Ulas method.
+// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-6.6.2
+// Input byte slice should be a valid field element, otherwise an error is returned.
+func (g *G2) MapToCurve(in []byte) (*PointG2, error) {
+	fp2 := g.f
+	u, err := fp2.fromBytes(in)
+	if err != nil {
+		return nil, err
+	}
+	x, y := swuMapG2(fp2, u)
+	isogenyMapG2(fp2, x, y)
+	z := new(fe2).one()
+	q := &PointG2{*x, *y, *z}
+	g.ClearCofactor(q)
+	return g.Affine(q), nil
+}
diff --git a/crypto/bls12381/g2_test.go b/crypto/bls12381/g2_test.go
new file mode 100644
index 0000000000..f16f0e5eea
--- /dev/null
+++ b/crypto/bls12381/g2_test.go
@@ -0,0 +1,286 @@
+package bls12381
+
+import (
+	"bytes"
+	"crypto/rand"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+func (g *G2) one() *PointG2 {
+	one, _ := g.fromBytesUnchecked(
+		common.FromHex("" +
+			"13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e" +
+			"024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8" +
+			"0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be" +
+			"0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801",
+		),
+	)
+	return one
+}
+
+func (g *G2) rand() *PointG2 {
+	k, err := rand.Int(rand.Reader, q)
+	if err != nil {
+		panic(err)
+	}
+	return g.MulScalar(&PointG2{}, g.one(), k)
+}
+
+func TestG2Serialization(t *testing.T) {
+	g2 := NewG2()
+	for i := 0; i < fuz; i++ {
+		a := g2.rand()
+		buf := g2.ToBytes(a)
+		b, err := g2.FromBytes(buf)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !g2.Equal(a, b) {
+			t.Fatal("bad serialization from/to")
+		}
+	}
+	for i := 0; i < fuz; i++ {
+		a := g2.rand()
+		encoded := g2.EncodePoint(a)
+		b, err := g2.DecodePoint(encoded)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !g2.Equal(a, b) {
+			t.Fatal("bad serialization encode/decode")
+		}
+	}
+}
+
+func TestG2IsOnCurve(t *testing.T) {
+	g := NewG2()
+	zero := g.Zero()
+	if !g.IsOnCurve(zero) {
+		t.Fatal("zero must be on curve")
+	}
+	one := new(fe2).one()
+	p := &PointG2{*one, *one, *one}
+	if g.IsOnCurve(p) {
+		t.Fatal("(1, 1) is not on curve")
+	}
+}
+
+func TestG2AdditiveProperties(t *testing.T) {
+	g := NewG2()
+	t0, t1 := g.New(), g.New()
+	zero := g.Zero()
+	for i := 0; i < fuz; i++ {
+		a, b := g.rand(), g.rand()
+		_, _, _ = b, t1, zero
+		g.Add(t0, a, zero)
+		if !g.Equal(t0, a) {
+			t.Fatal("a + 0 == a")
+		}
+		g.Add(t0, zero, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("0 + 0 == 0")
+		}
+		g.Sub(t0, a, zero)
+		if !g.Equal(t0, a) {
+			t.Fatal("a - 0 == a")
+		}
+		g.Sub(t0, zero, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("0 - 0 == 0")
+		}
+		g.Neg(t0, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("- 0 == 0")
+		}
+		g.Sub(t0, zero, a)
+		g.Neg(t0, t0)
+		if !g.Equal(t0, a) {
+			t.Fatal(" - (0 - a) == a")
+		}
+		g.Double(t0, zero)
+		if !g.Equal(t0, zero) {
+			t.Fatal("2 * 0 == 0")
+		}
+		g.Double(t0, a)
+		g.Sub(t0, t0, a)
+		if !g.Equal(t0, a) || !g.IsOnCurve(t0) {
+			t.Fatal(" (2 * a) - a == a")
+		}
+		g.Add(t0, a, b)
+		g.Add(t1, b, a)
+		if !g.Equal(t0, t1) {
+			t.Fatal("a + b == b + a")
+		}
+		g.Sub(t0, a, b)
+		g.Sub(t1, b, a)
+		g.Neg(t1, t1)
+		if !g.Equal(t0, t1) {
+			t.Fatal("a - b == - ( b - a )")
+		}
+		c := g.rand()
+		g.Add(t0, a, b)
+		g.Add(t0, t0, c)
+		g.Add(t1, a, c)
+		g.Add(t1, t1, b)
+		if !g.Equal(t0, t1) {
+			t.Fatal("(a + b) + c == (a + c ) + b")
+		}
+		g.Sub(t0, a, b)
+		g.Sub(t0, t0, c)
+		g.Sub(t1, a, c)
+		g.Sub(t1, t1, b)
+		if !g.Equal(t0, t1) {
+			t.Fatal("(a - b) - c == (a - c) -b")
+		}
+	}
+}
+
+func TestG2MultiplicativeProperties(t *testing.T) {
+	g := NewG2()
+	t0, t1 := g.New(), g.New()
+	zero := g.Zero()
+	for i := 0; i < fuz; i++ {
+		a := g.rand()
+		s1, s2, s3 := randScalar(q), randScalar(q), randScalar(q)
+		sone := big.NewInt(1)
+		g.MulScalar(t0, zero, s1)
+		if !g.Equal(t0, zero) {
+			t.Fatal(" 0 ^ s == 0")
+		}
+		g.MulScalar(t0, a, sone)
+		if !g.Equal(t0, a) {
+			t.Fatal(" a ^ 1 == a")
+		}
+		g.MulScalar(t0, zero, s1)
+		if !g.Equal(t0, zero) {
+			t.Fatal(" 0 ^ s == a")
+		}
+		g.MulScalar(t0, a, s1)
+		g.MulScalar(t0, t0, s2)
+		s3.Mul(s1, s2)
+		g.MulScalar(t1, a, s3)
+		if !g.Equal(t0, t1) {
+			t.Errorf(" (a ^ s1) ^ s2 == a ^ (s1 * s2)")
+		}
+		g.MulScalar(t0, a, s1)
+		g.MulScalar(t1, a, s2)
+		g.Add(t0, t0, t1)
+		s3.Add(s1, s2)
+		g.MulScalar(t1, a, s3)
+		if !g.Equal(t0, t1) {
+			t.Errorf(" (a ^ s1) + (a ^ s2) == a ^ (s1 + s2)")
+		}
+	}
+}
+
+func TestG2MultiExpExpected(t *testing.T) {
+	g := NewG2()
+	one := g.one()
+	var scalars [2]*big.Int
+	var bases [2]*PointG2
+	scalars[0] = big.NewInt(2)
+	scalars[1] = big.NewInt(3)
+	bases[0], bases[1] = new(PointG2).Set(one), new(PointG2).Set(one)
+	expected, result := g.New(), g.New()
+	g.MulScalar(expected, one, big.NewInt(5))
+	_, _ = g.MultiExp(result, bases[:], scalars[:])
+	if !g.Equal(expected, result) {
+		t.Fatal("bad multi-exponentiation")
+	}
+}
+
+func TestG2MultiExpBatch(t *testing.T) {
+	g := NewG2()
+	one := g.one()
+	n := 1000
+	bases := make([]*PointG2, n)
+	scalars := make([]*big.Int, n)
+	// scalars: [s0,s1 ... s(n-1)]
+	// bases: [P0,P1,..P(n-1)] = [s(n-1)*G, s(n-2)*G ... s0*G]
+	for i, j := 0, n-1; i < n; i, j = i+1, j-1 {
+		scalars[j], _ = rand.Int(rand.Reader, big.NewInt(100000))
+		bases[i] = g.New()
+		g.MulScalar(bases[i], one, scalars[j])
+	}
+	// expected: s(n-1)*P0 + s(n-2)*P1 + s0*P(n-1)
+	expected, tmp := g.New(), g.New()
+	for i := 0; i < n; i++ {
+		g.MulScalar(tmp, bases[i], scalars[i])
+		g.Add(expected, expected, tmp)
+	}
+	result := g.New()
+	_, _ = g.MultiExp(result, bases, scalars)
+	if !g.Equal(expected, result) {
+		t.Fatal("bad multi-exponentiation")
+	}
+}
+
+func TestG2MapToCurve(t *testing.T) {
+	for i, v := range []struct {
+		u        []byte
+		expected []byte
+	}{
+		{
+			u:        make([]byte, 96),
+			expected: common.FromHex("0a67d12118b5a35bb02d2e86b3ebfa7e23410db93de39fb06d7025fa95e96ffa428a7a27c3ae4dd4b40bd251ac658892" + "018320896ec9eef9d5e619848dc29ce266f413d02dd31d9b9d44ec0c79cd61f18b075ddba6d7bd20b7ff27a4b324bfce" + "04c69777a43f0bda07679d5805e63f18cf4e0e7c6112ac7f70266d199b4f76ae27c6269a3ceebdae30806e9a76aadf5c" + "0260e03644d1a2c321256b3246bad2b895cad13890cbe6f85df55106a0d334604fb143c7a042d878006271865bc35941"),
+		},
+		{
+			u:        common.FromHex("025fbc07711ba267b7e70c82caa70a16fbb1d470ae24ceef307f5e2000751677820b7013ad4e25492dcf30052d3e5eca" + "0e775d7827adf385b83e20e4445bd3fab21d7b4498426daf3c1d608b9d41e9edb5eda0df022e753b8bb4bc3bb7db4914"),
+			expected: common.FromHex("0d4333b77becbf9f9dfa3ca928002233d1ecc854b1447e5a71f751c9042d000f42db91c1d6649a5e0ad22bd7bf7398b8" + "027e4bfada0b47f9f07e04aec463c7371e68f2fd0c738cd517932ea3801a35acf09db018deda57387b0f270f7a219e4d" + "0cc76dc777ea0d447e02a41004f37a0a7b1fafb6746884e8d9fc276716ccf47e4e0899548a2ec71c2bdf1a2a50e876db" + "053674cba9ef516ddc218fedb37324e6c47de27f88ab7ef123b006127d738293c0277187f7e2f80a299a24d84ed03da7"),
+		},
+		{
+			u:        common.FromHex("1870a7dbfd2a1deb74015a3546b20f598041bf5d5202997956a94a368d30d3f70f18cdaa1d33ce970a4e16af961cbdcb" + "045ab31ce4b5a8ba7c4b2851b64f063a66cd1223d3c85005b78e1beee65e33c90ceef0244e45fc45a5e1d6eab6644fdb"),
+			expected: common.FromHex("18f0f87b40af67c056915dbaf48534c592524e82c1c2b50c3734d02c0172c80df780a60b5683759298a3303c5d942778" + "09349f1cb5b2e55489dcd45a38545343451cc30a1681c57acd4fb0a6db125f8352c09f4a67eb7d1d8242cb7d3405f97b" + "10a2ba341bc689ab947b7941ce6ef39be17acaab067bd32bd652b471ab0792c53a2bd03bdac47f96aaafe96e441f63c0" + "02f2d9deb2c7742512f5b8230bf0fd83ea42279d7d39779543c1a43b61c885982b611f6a7a24b514995e8a098496b811"),
+		},
+		{
+			u:        common.FromHex("088fe329b054db8a6474f21a7fbfdf17b4c18044db299d9007af582c3d5f17d00e56d99921d4b5640fce44b05219b5de" + "0b6e6135a4cd31ba980ddbd115ac48abef7ec60e226f264d7befe002c165f3a496f36f76dd524efd75d17422558d10b4"),
+			expected: common.FromHex("19808ec5930a53c7cf5912ccce1cc33f1b3dcff24a53ce1cc4cba41fd6996dbed4843ccdd2eaf6a0cd801e562718d163" + "149fe43777d34f0d25430dea463889bd9393bdfb4932946db23671727081c629ebb98a89604f3433fba1c67d356a4af7" + "04783e391c30c83f805ca271e353582fdf19d159f6a4c39b73acbb637a9b8ac820cfbe2738d683368a7c07ad020e3e33" + "04c0d6793a766233b2982087b5f4a254f261003ccb3262ea7c50903eecef3e871d1502c293f9e063d7d293f6384f4551"),
+		},
+		{
+			u:        common.FromHex("03df16a66a05e4c1188c234788f43896e0565bfb64ac49b9639e6b284cc47dad73c47bb4ea7e677db8d496beb907fbb6" + "0f45b50647d67485295aa9eb2d91a877b44813677c67c8d35b2173ff3ba95f7bd0806f9ca8a1436b8b9d14ee81da4d7e"),
+			expected: common.FromHex("0b8e0094c886487870372eb6264613a6a087c7eb9804fab789be4e47a57b29eb19b1983a51165a1b5eb025865e9fc63a" + "0804152cbf8474669ad7d1796ab92d7ca21f32d8bed70898a748ed4e4e0ec557069003732fc86866d938538a2ae95552" + "14c80f068ece15a3936bb00c3c883966f75b4e8d9ddde809c11f781ab92d23a2d1d103ad48f6f3bb158bf3e3a4063449" + "09e5c8242dd7281ad32c03fe4af3f19167770016255fb25ad9b67ec51d62fade31a1af101e8f6172ec2ee8857662be3a"),
+		},
+	} {
+		g := NewG2()
+		p0, err := g.MapToCurve(v.u)
+		if err != nil {
+			t.Fatal("map to curve fails", i, err)
+		}
+		if !bytes.Equal(g.ToBytes(p0), v.expected) {
+			t.Fatal("map to curve fails", i)
+		}
+	}
+}
+
+func BenchmarkG2Add(t *testing.B) {
+	g2 := NewG2()
+	a, b, c := g2.rand(), g2.rand(), PointG2{}
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		g2.Add(&c, a, b)
+	}
+}
+
+func BenchmarkG2Mul(t *testing.B) {
+	g2 := NewG2()
+	a, e, c := g2.rand(), q, PointG2{}
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		g2.MulScalar(&c, a, e)
+	}
+}
+
+func BenchmarkG2SWUMap(t *testing.B) {
+	a := make([]byte, 96)
+	g2 := NewG2()
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		_, err := g2.MapToCurve(a)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
diff --git a/crypto/bls12381/gt.go b/crypto/bls12381/gt.go
new file mode 100644
index 0000000000..2ac265e956
--- /dev/null
+++ b/crypto/bls12381/gt.go
@@ -0,0 +1,121 @@
+// 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 .
+
+package bls12381
+
+import (
+	"errors"
+	"math/big"
+)
+
+// E is type for target group element
+type E = fe12
+
+// GT is type for target multiplicative group GT.
+type GT struct {
+	fp12 *fp12
+}
+
+func (e *E) Set(e2 *E) *E {
+	return e.set(e2)
+}
+
+// One sets a new target group element to one
+func (e *E) One() *E {
+	e = new(fe12).one()
+	return e
+}
+
+// IsOne returns true if given element equals to one
+func (e *E) IsOne() bool {
+	return e.isOne()
+}
+
+// Equal returns true if given two element is equal, otherwise returns false
+func (g *E) Equal(g2 *E) bool {
+	return g.equal(g2)
+}
+
+// NewGT constructs new target group instance.
+func NewGT() *GT {
+	fp12 := newFp12(nil)
+	return >{fp12}
+}
+
+// Q returns group order in big.Int.
+func (g *GT) Q() *big.Int {
+	return new(big.Int).Set(q)
+}
+
+// FromBytes expects 576 byte input and returns target group element
+// FromBytes returns error if given element is not on correct subgroup.
+func (g *GT) FromBytes(in []byte) (*E, error) {
+	e, err := g.fp12.fromBytes(in)
+	if err != nil {
+		return nil, err
+	}
+	if !g.IsValid(e) {
+		return e, errors.New("invalid element")
+	}
+	return e, nil
+}
+
+// ToBytes serializes target group element.
+func (g *GT) ToBytes(e *E) []byte {
+	return g.fp12.toBytes(e)
+}
+
+// IsValid checks whether given target group element is in correct subgroup.
+func (g *GT) IsValid(e *E) bool {
+	r := g.New()
+	g.fp12.exp(r, e, q)
+	return r.isOne()
+}
+
+// New initializes a new target group element which is equal to one
+func (g *GT) New() *E {
+	return new(E).One()
+}
+
+// Add adds two field element `a` and `b` and assigns the result to the element in first argument.
+func (g *GT) Add(c, a, b *E) {
+	g.fp12.add(c, a, b)
+}
+
+// Sub subtracts two field element `a` and `b`, and assigns the result to the element in first argument.
+func (g *GT) Sub(c, a, b *E) {
+	g.fp12.sub(c, a, b)
+}
+
+// Mul multiplies two field element `a` and `b` and assigns the result to the element in first argument.
+func (g *GT) Mul(c, a, b *E) {
+	g.fp12.mul(c, a, b)
+}
+
+// Square squares an element `a` and assigns the result to the element in first argument.
+func (g *GT) Square(c, a *E) {
+	g.fp12.cyclotomicSquare(c, a)
+}
+
+// Exp exponents an element `a` by a scalar `s` and assigns the result to the element in first argument.
+func (g *GT) Exp(c, a *E, s *big.Int) {
+	g.fp12.cyclotomicExp(c, a, s)
+}
+
+// Inverse inverses an element `a` and assigns the result to the element in first argument.
+func (g *GT) Inverse(c, a *E) {
+	g.fp12.inverse(c, a)
+}
diff --git a/crypto/bls12381/isogeny.go b/crypto/bls12381/isogeny.go
new file mode 100644
index 0000000000..91e03936d5
--- /dev/null
+++ b/crypto/bls12381/isogeny.go
@@ -0,0 +1,227 @@
+// 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 .
+
+package bls12381
+
+// isogenyMapG1 applies 11-isogeny map for BLS12-381 G1 defined at draft-irtf-cfrg-hash-to-curve-06.
+func isogenyMapG1(x, y *fe) {
+	// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#appendix-C.2
+	params := isogenyConstansG1
+	degree := 15
+	xNum, xDen, yNum, yDen := new(fe), new(fe), new(fe), new(fe)
+	xNum.set(params[0][degree])
+	xDen.set(params[1][degree])
+	yNum.set(params[2][degree])
+	yDen.set(params[3][degree])
+	for i := degree - 1; i >= 0; i-- {
+		mul(xNum, xNum, x)
+		mul(xDen, xDen, x)
+		mul(yNum, yNum, x)
+		mul(yDen, yDen, x)
+		add(xNum, xNum, params[0][i])
+		add(xDen, xDen, params[1][i])
+		add(yNum, yNum, params[2][i])
+		add(yDen, yDen, params[3][i])
+	}
+	inverse(xDen, xDen)
+	inverse(yDen, yDen)
+	mul(xNum, xNum, xDen)
+	mul(yNum, yNum, yDen)
+	mul(yNum, yNum, y)
+	x.set(xNum)
+	y.set(yNum)
+}
+
+// isogenyMapG2 applies 11-isogeny map for BLS12-381 G1 defined at draft-irtf-cfrg-hash-to-curve-06.
+func isogenyMapG2(e *fp2, x, y *fe2) {
+	if e == nil {
+		e = newFp2()
+	}
+	// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#appendix-C.2
+	params := isogenyConstantsG2
+	degree := 3
+	xNum := new(fe2).set(params[0][degree])
+	xDen := new(fe2).set(params[1][degree])
+	yNum := new(fe2).set(params[2][degree])
+	yDen := new(fe2).set(params[3][degree])
+	for i := degree - 1; i >= 0; i-- {
+		e.mul(xNum, xNum, x)
+		e.mul(xDen, xDen, x)
+		e.mul(yNum, yNum, x)
+		e.mul(yDen, yDen, x)
+		e.add(xNum, xNum, params[0][i])
+		e.add(xDen, xDen, params[1][i])
+		e.add(yNum, yNum, params[2][i])
+		e.add(yDen, yDen, params[3][i])
+	}
+	e.inverse(xDen, xDen)
+	e.inverse(yDen, yDen)
+	e.mul(xNum, xNum, xDen)
+	e.mul(yNum, yNum, yDen)
+	e.mul(yNum, yNum, y)
+	x.set(xNum)
+	y.set(yNum)
+}
+
+var isogenyConstansG1 = [4][16]*fe{
+	[16]*fe{
+		&fe{0x4d18b6f3af00131c, 0x19fa219793fee28c, 0x3f2885f1467f19ae, 0x23dcea34f2ffb304, 0xd15b58d2ffc00054, 0x0913be200a20bef4},
+		&fe{0x898985385cdbbd8b, 0x3c79e43cc7d966aa, 0x1597e193f4cd233a, 0x8637ef1e4d6623ad, 0x11b22deed20d827b, 0x07097bc5998784ad},
+		&fe{0xa542583a480b664b, 0xfc7169c026e568c6, 0x5ba2ef314ed8b5a6, 0x5b5491c05102f0e7, 0xdf6e99707d2a0079, 0x0784151ed7605524},
+		&fe{0x494e212870f72741, 0xab9be52fbda43021, 0x26f5577994e34c3d, 0x049dfee82aefbd60, 0x65dadd7828505289, 0x0e93d431ea011aeb},
+		&fe{0x90ee774bd6a74d45, 0x7ada1c8a41bfb185, 0x0f1a8953b325f464, 0x104c24211be4805c, 0x169139d319ea7a8f, 0x09f20ead8e532bf6},
+		&fe{0x6ddd93e2f43626b7, 0xa5482c9aa1ccd7bd, 0x143245631883f4bd, 0x2e0a94ccf77ec0db, 0xb0282d480e56489f, 0x18f4bfcbb4368929},
+		&fe{0x23c5f0c953402dfd, 0x7a43ff6958ce4fe9, 0x2c390d3d2da5df63, 0xd0df5c98e1f9d70f, 0xffd89869a572b297, 0x1277ffc72f25e8fe},
+		&fe{0x79f4f0490f06a8a6, 0x85f894a88030fd81, 0x12da3054b18b6410, 0xe2a57f6505880d65, 0xbba074f260e400f1, 0x08b76279f621d028},
+		&fe{0xe67245ba78d5b00b, 0x8456ba9a1f186475, 0x7888bff6e6b33bb4, 0xe21585b9a30f86cb, 0x05a69cdcef55feee, 0x09e699dd9adfa5ac},
+		&fe{0x0de5c357bff57107, 0x0a0db4ae6b1a10b2, 0xe256bb67b3b3cd8d, 0x8ad456574e9db24f, 0x0443915f50fd4179, 0x098c4bf7de8b6375},
+		&fe{0xe6b0617e7dd929c7, 0xfe6e37d442537375, 0x1dafdeda137a489e, 0xe4efd1ad3f767ceb, 0x4a51d8667f0fe1cf, 0x054fdf4bbf1d821c},
+		&fe{0x72db2a50658d767b, 0x8abf91faa257b3d5, 0xe969d6833764ab47, 0x464170142a1009eb, 0xb14f01aadb30be2f, 0x18ae6a856f40715d},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+	},
+	[16]*fe{
+		&fe{0xb962a077fdb0f945, 0xa6a9740fefda13a0, 0xc14d568c3ed6c544, 0xb43fc37b908b133e, 0x9c0b3ac929599016, 0x0165aa6c93ad115f},
+		&fe{0x23279a3ba506c1d9, 0x92cfca0a9465176a, 0x3b294ab13755f0ff, 0x116dda1c5070ae93, 0xed4530924cec2045, 0x083383d6ed81f1ce},
+		&fe{0x9885c2a6449fecfc, 0x4a2b54ccd37733f0, 0x17da9ffd8738c142, 0xa0fba72732b3fafd, 0xff364f36e54b6812, 0x0f29c13c660523e2},
+		&fe{0xe349cc118278f041, 0xd487228f2f3204fb, 0xc9d325849ade5150, 0x43a92bd69c15c2df, 0x1c2c7844bc417be4, 0x12025184f407440c},
+		&fe{0x587f65ae6acb057b, 0x1444ef325140201f, 0xfbf995e71270da49, 0xccda066072436a42, 0x7408904f0f186bb2, 0x13b93c63edf6c015},
+		&fe{0xfb918622cd141920, 0x4a4c64423ecaddb4, 0x0beb232927f7fb26, 0x30f94df6f83a3dc2, 0xaeedd424d780f388, 0x06cc402dd594bbeb},
+		&fe{0xd41f761151b23f8f, 0x32a92465435719b3, 0x64f436e888c62cb9, 0xdf70a9a1f757c6e4, 0x6933a38d5b594c81, 0x0c6f7f7237b46606},
+		&fe{0x693c08747876c8f7, 0x22c9850bf9cf80f0, 0x8e9071dab950c124, 0x89bc62d61c7baf23, 0xbc6be2d8dad57c23, 0x17916987aa14a122},
+		&fe{0x1be3ff439c1316fd, 0x9965243a7571dfa7, 0xc7f7f62962f5cd81, 0x32c6aa9af394361c, 0xbbc2ee18e1c227f4, 0x0c102cbac531bb34},
+		&fe{0x997614c97bacbf07, 0x61f86372b99192c0, 0x5b8c95fc14353fc3, 0xca2b066c2a87492f, 0x16178f5bbf698711, 0x12a6dcd7f0f4e0e8},
+		&fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+		&fe{0, 0, 0, 0, 0, 0},
+	},
+	[16]*fe{
+		&fe{0x2b567ff3e2837267, 0x1d4d9e57b958a767, 0xce028fea04bd7373, 0xcc31a30a0b6cd3df, 0x7d7b18a682692693, 0x0d300744d42a0310},
+		&fe{0x99c2555fa542493f, 0xfe7f53cc4874f878, 0x5df0608b8f97608a, 0x14e03832052b49c8, 0x706326a6957dd5a4, 0x0a8dadd9c2414555},
+		&fe{0x13d942922a5cf63a, 0x357e33e36e261e7d, 0xcf05a27c8456088d, 0x0000bd1de7ba50f0, 0x83d0c7532f8c1fde, 0x13f70bf38bbf2905},
+		&fe{0x5c57fd95bfafbdbb, 0x28a359a65e541707, 0x3983ceb4f6360b6d, 0xafe19ff6f97e6d53, 0xb3468f4550192bf7, 0x0bb6cde49d8ba257},
+		&fe{0x590b62c7ff8a513f, 0x314b4ce372cacefd, 0x6bef32ce94b8a800, 0x6ddf84a095713d5f, 0x64eace4cb0982191, 0x0386213c651b888d},
+		&fe{0xa5310a31111bbcdd, 0xa14ac0f5da148982, 0xf9ad9cc95423d2e9, 0xaa6ec095283ee4a7, 0xcf5b1f022e1c9107, 0x01fddf5aed881793},
+		&fe{0x65a572b0d7a7d950, 0xe25c2d8183473a19, 0xc2fcebe7cb877dbd, 0x05b2d36c769a89b0, 0xba12961be86e9efb, 0x07eb1b29c1dfde1f},
+		&fe{0x93e09572f7c4cd24, 0x364e929076795091, 0x8569467e68af51b5, 0xa47da89439f5340f, 0xf4fa918082e44d64, 0x0ad52ba3e6695a79},
+		&fe{0x911429844e0d5f54, 0xd03f51a3516bb233, 0x3d587e5640536e66, 0xfa86d2a3a9a73482, 0xa90ed5adf1ed5537, 0x149c9c326a5e7393},
+		&fe{0x462bbeb03c12921a, 0xdc9af5fa0a274a17, 0x9a558ebde836ebed, 0x649ef8f11a4fae46, 0x8100e1652b3cdc62, 0x1862bd62c291dacb},
+		&fe{0x05c9b8ca89f12c26, 0x0194160fa9b9ac4f, 0x6a643d5a6879fa2c, 0x14665bdd8846e19d, 0xbb1d0d53af3ff6bf, 0x12c7e1c3b28962e5},
+		&fe{0xb55ebf900b8a3e17, 0xfedc77ec1a9201c4, 0x1f07db10ea1a4df4, 0x0dfbd15dc41a594d, 0x389547f2334a5391, 0x02419f98165871a4},
+		&fe{0xb416af000745fc20, 0x8e563e9d1ea6d0f5, 0x7c763e17763a0652, 0x01458ef0159ebbef, 0x8346fe421f96bb13, 0x0d2d7b829ce324d2},
+		&fe{0x93096bb538d64615, 0x6f2a2619951d823a, 0x8f66b3ea59514fa4, 0xf563e63704f7092f, 0x724b136c4cf2d9fa, 0x046959cfcfd0bf49},
+		&fe{0xea748d4b6e405346, 0x91e9079c2c02d58f, 0x41064965946d9b59, 0xa06731f1d2bbe1ee, 0x07f897e267a33f1b, 0x1017290919210e5f},
+		&fe{0x872aa6c17d985097, 0xeecc53161264562a, 0x07afe37afff55002, 0x54759078e5be6838, 0xc4b92d15db8acca8, 0x106d87d1b51d13b9},
+	},
+	[16]*fe{
+		&fe{0xeb6c359d47e52b1c, 0x18ef5f8a10634d60, 0xddfa71a0889d5b7e, 0x723e71dcc5fc1323, 0x52f45700b70d5c69, 0x0a8b981ee47691f1},
+		&fe{0x616a3c4f5535b9fb, 0x6f5f037395dbd911, 0xf25f4cc5e35c65da, 0x3e50dffea3c62658, 0x6a33dca523560776, 0x0fadeff77b6bfe3e},
+		&fe{0x2be9b66df470059c, 0x24a2c159a3d36742, 0x115dbe7ad10c2a37, 0xb6634a652ee5884d, 0x04fe8bb2b8d81af4, 0x01c2a7a256fe9c41},
+		&fe{0xf27bf8ef3b75a386, 0x898b367476c9073f, 0x24482e6b8c2f4e5f, 0xc8e0bbd6fe110806, 0x59b0c17f7631448a, 0x11037cd58b3dbfbd},
+		&fe{0x31c7912ea267eec6, 0x1dbf6f1c5fcdb700, 0xd30d4fe3ba86fdb1, 0x3cae528fbee9a2a4, 0xb1cce69b6aa9ad9a, 0x044393bb632d94fb},
+		&fe{0xc66ef6efeeb5c7e8, 0x9824c289dd72bb55, 0x71b1a4d2f119981d, 0x104fc1aafb0919cc, 0x0e49df01d942a628, 0x096c3a09773272d4},
+		&fe{0x9abc11eb5fadeff4, 0x32dca50a885728f0, 0xfb1fa3721569734c, 0xc4b76271ea6506b3, 0xd466a75599ce728e, 0x0c81d4645f4cb6ed},
+		&fe{0x4199f10e5b8be45b, 0xda64e495b1e87930, 0xcb353efe9b33e4ff, 0x9e9efb24aa6424c6, 0xf08d33680a237465, 0x0d3378023e4c7406},
+		&fe{0x7eb4ae92ec74d3a5, 0xc341b4aa9fac3497, 0x5be603899e907687, 0x03bfd9cca75cbdeb, 0x564c2935a96bfa93, 0x0ef3c33371e2fdb5},
+		&fe{0x7ee91fd449f6ac2e, 0xe5d5bd5cb9357a30, 0x773a8ca5196b1380, 0xd0fda172174ed023, 0x6cb95e0fa776aead, 0x0d22d5a40cec7cff},
+		&fe{0xf727e09285fd8519, 0xdc9d55a83017897b, 0x7549d8bd057894ae, 0x178419613d90d8f8, 0xfce95ebdeb5b490a, 0x0467ffaef23fc49e},
+		&fe{0xc1769e6a7c385f1b, 0x79bc930deac01c03, 0x5461c75a23ede3b5, 0x6e20829e5c230c45, 0x828e0f1e772a53cd, 0x116aefa749127bff},
+		&fe{0x101c10bf2744c10a, 0xbbf18d053a6a3154, 0xa0ecf39ef026f602, 0xfc009d4996dc5153, 0xb9000209d5bd08d3, 0x189e5fe4470cd73c},
+		&fe{0x7ebd546ca1575ed2, 0xe47d5a981d081b55, 0x57b2b625b6d4ca21, 0xb0a1ba04228520cc, 0x98738983c2107ff3, 0x13dddbc4799d81d6},
+		&fe{0x09319f2e39834935, 0x039e952cbdb05c21, 0x55ba77a9a2f76493, 0xfd04e3dfc6086467, 0xfb95832e7d78742e, 0x0ef9c24eccaf5e0e},
+		&fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+	},
+}
+
+var isogenyConstantsG2 = [4][4]*fe2{
+	[4]*fe2{
+		&fe2{
+			fe{0x47f671c71ce05e62, 0x06dd57071206393e, 0x7c80cd2af3fd71a2, 0x048103ea9e6cd062, 0xc54516acc8d037f6, 0x13808f550920ea41},
+			fe{0x47f671c71ce05e62, 0x06dd57071206393e, 0x7c80cd2af3fd71a2, 0x048103ea9e6cd062, 0xc54516acc8d037f6, 0x13808f550920ea41},
+		},
+		&fe2{
+			fe{0, 0, 0, 0, 0, 0},
+			fe{0x5fe55555554c71d0, 0x873fffdd236aaaa3, 0x6a6b4619b26ef918, 0x21c2888408874945, 0x2836cda7028cabc5, 0x0ac73310a7fd5abd},
+		},
+		&fe2{
+			fe{0x0a0c5555555971c3, 0xdb0c00101f9eaaae, 0xb1fb2f941d797997, 0xd3960742ef416e1c, 0xb70040e2c20556f4, 0x149d7861e581393b},
+			fe{0xaff2aaaaaaa638e8, 0x439fffee91b55551, 0xb535a30cd9377c8c, 0x90e144420443a4a2, 0x941b66d3814655e2, 0x0563998853fead5e},
+		},
+		&fe2{
+			fe{0x40aac71c71c725ed, 0x190955557a84e38e, 0xd817050a8f41abc3, 0xd86485d4c87f6fb1, 0x696eb479f885d059, 0x198e1a74328002d2},
+			fe{0, 0, 0, 0, 0, 0},
+		},
+	},
+	[4]*fe2{
+		&fe2{
+			fe{0, 0, 0, 0, 0, 0},
+			fe{0x1f3affffff13ab97, 0xf25bfc611da3ff3e, 0xca3757cb3819b208, 0x3e6427366f8cec18, 0x03977bc86095b089, 0x04f69db13f39a952},
+		},
+		&fe2{
+			fe{0x447600000027552e, 0xdcb8009a43480020, 0x6f7ee9ce4a6e8b59, 0xb10330b7c0a95bc6, 0x6140b1fcfb1e54b7, 0x0381be097f0bb4e1},
+			fe{0x7588ffffffd8557d, 0x41f3ff646e0bffdf, 0xf7b1e8d2ac426aca, 0xb3741acd32dbb6f8, 0xe9daf5b9482d581f, 0x167f53e0ba7431b8},
+		},
+		&fe2{
+			fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+			fe{0, 0, 0, 0, 0, 0},
+		},
+		&fe2{
+			fe{0, 0, 0, 0, 0, 0},
+			fe{0, 0, 0, 0, 0, 0},
+		},
+	},
+	[4]*fe2{
+		&fe2{
+			fe{0x96d8f684bdfc77be, 0xb530e4f43b66d0e2, 0x184a88ff379652fd, 0x57cb23ecfae804e1, 0x0fd2e39eada3eba9, 0x08c8055e31c5d5c3},
+			fe{0x96d8f684bdfc77be, 0xb530e4f43b66d0e2, 0x184a88ff379652fd, 0x57cb23ecfae804e1, 0x0fd2e39eada3eba9, 0x08c8055e31c5d5c3},
+		},
+		&fe2{
+			fe{0, 0, 0, 0, 0, 0},
+			fe{0xbf0a71c71c91b406, 0x4d6d55d28b7638fd, 0x9d82f98e5f205aee, 0xa27aa27b1d1a18d5, 0x02c3b2b2d2938e86, 0x0c7d13420b09807f},
+		},
+		&fe2{
+			fe{0xd7f9555555531c74, 0x21cffff748daaaa8, 0x5a9ad1866c9bbe46, 0x4870a2210221d251, 0x4a0db369c0a32af1, 0x02b1ccc429ff56af},
+			fe{0xe205aaaaaaac8e37, 0xfcdc000768795556, 0x0c96011a8a1537dd, 0x1c06a963f163406e, 0x010df44c82a881e6, 0x174f45260f808feb},
+		},
+		&fe2{
+			fe{0xa470bda12f67f35c, 0xc0fe38e23327b425, 0xc9d3d0f2c6f0678d, 0x1c55c9935b5a982e, 0x27f6c0e2f0746764, 0x117c5e6e28aa9054},
+			fe{0, 0, 0, 0, 0, 0},
+		},
+	},
+	[4]*fe2{
+		&fe2{
+			fe{0x0162fffffa765adf, 0x8f7bea480083fb75, 0x561b3c2259e93611, 0x11e19fc1a9c875d5, 0xca713efc00367660, 0x03c6a03d41da1151},
+			fe{0x0162fffffa765adf, 0x8f7bea480083fb75, 0x561b3c2259e93611, 0x11e19fc1a9c875d5, 0xca713efc00367660, 0x03c6a03d41da1151},
+		},
+		&fe2{
+			fe{0, 0, 0, 0, 0, 0},
+			fe{0x5db0fffffd3b02c5, 0xd713f52358ebfdba, 0x5ea60761a84d161a, 0xbb2c75a34ea6c44a, 0x0ac6735921c1119b, 0x0ee3d913bdacfbf6},
+		},
+		&fe2{
+			fe{0x66b10000003affc5, 0xcb1400e764ec0030, 0xa73e5eb56fa5d106, 0x8984c913a0fe09a9, 0x11e10afb78ad7f13, 0x05429d0e3e918f52},
+			fe{0x534dffffffc4aae6, 0x5397ff174c67ffcf, 0xbff273eb870b251d, 0xdaf2827152870915, 0x393a9cbaca9e2dc3, 0x14be74dbfaee5748},
+		},
+		&fe2{
+			fe{0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493},
+			fe{0, 0, 0, 0, 0, 0},
+		},
+	},
+}
diff --git a/crypto/bls12381/pairing.go b/crypto/bls12381/pairing.go
new file mode 100644
index 0000000000..d292d7c3a5
--- /dev/null
+++ b/crypto/bls12381/pairing.go
@@ -0,0 +1,282 @@
+// 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 .
+
+package bls12381
+
+type pair struct {
+	g1 *PointG1
+	g2 *PointG2
+}
+
+func newPair(g1 *PointG1, g2 *PointG2) pair {
+	return pair{g1, g2}
+}
+
+// Engine is BLS12-381 elliptic curve pairing engine
+type Engine struct {
+	G1   *G1
+	G2   *G2
+	fp12 *fp12
+	fp2  *fp2
+	pairingEngineTemp
+	pairs []pair
+}
+
+// NewPairingEngine creates new pairing engine instance.
+func NewPairingEngine() *Engine {
+	fp2 := newFp2()
+	fp6 := newFp6(fp2)
+	fp12 := newFp12(fp6)
+	g1 := NewG1()
+	g2 := newG2(fp2)
+	return &Engine{
+		fp2:               fp2,
+		fp12:              fp12,
+		G1:                g1,
+		G2:                g2,
+		pairingEngineTemp: newEngineTemp(),
+	}
+}
+
+type pairingEngineTemp struct {
+	t2  [10]*fe2
+	t12 [9]fe12
+}
+
+func newEngineTemp() pairingEngineTemp {
+	t2 := [10]*fe2{}
+	for i := 0; i < 10; i++ {
+		t2[i] = &fe2{}
+	}
+	t12 := [9]fe12{}
+	return pairingEngineTemp{t2, t12}
+}
+
+// AddPair adds a g1, g2 point pair to pairing engine
+func (e *Engine) AddPair(g1 *PointG1, g2 *PointG2) *Engine {
+	p := newPair(g1, g2)
+	if !e.isZero(p) {
+		e.affine(p)
+		e.pairs = append(e.pairs, p)
+	}
+	return e
+}
+
+// AddPairInv adds a G1, G2 point pair to pairing engine. G1 point is negated.
+func (e *Engine) AddPairInv(g1 *PointG1, g2 *PointG2) *Engine {
+	e.G1.Neg(g1, g1)
+	e.AddPair(g1, g2)
+	return e
+}
+
+// Reset deletes added pairs.
+func (e *Engine) Reset() *Engine {
+	e.pairs = []pair{}
+	return e
+}
+
+func (e *Engine) isZero(p pair) bool {
+	return e.G1.IsZero(p.g1) || e.G2.IsZero(p.g2)
+}
+
+func (e *Engine) affine(p pair) {
+	e.G1.Affine(p.g1)
+	e.G2.Affine(p.g2)
+}
+
+func (e *Engine) doublingStep(coeff *[3]fe2, r *PointG2) {
+	// Adaptation of Formula 3 in https://eprint.iacr.org/2010/526.pdf
+	fp2 := e.fp2
+	t := e.t2
+	fp2.mul(t[0], &r[0], &r[1])
+	fp2.mulByFq(t[0], t[0], twoInv)
+	fp2.square(t[1], &r[1])
+	fp2.square(t[2], &r[2])
+	fp2.double(t[7], t[2])
+	fp2.add(t[7], t[7], t[2])
+	fp2.mulByB(t[3], t[7])
+	fp2.double(t[4], t[3])
+	fp2.add(t[4], t[4], t[3])
+	fp2.add(t[5], t[1], t[4])
+	fp2.mulByFq(t[5], t[5], twoInv)
+	fp2.add(t[6], &r[1], &r[2])
+	fp2.square(t[6], t[6])
+	fp2.add(t[7], t[2], t[1])
+	fp2.sub(t[6], t[6], t[7])
+	fp2.sub(&coeff[0], t[3], t[1])
+	fp2.square(t[7], &r[0])
+	fp2.sub(t[4], t[1], t[4])
+	fp2.mul(&r[0], t[4], t[0])
+	fp2.square(t[2], t[3])
+	fp2.double(t[3], t[2])
+	fp2.add(t[3], t[3], t[2])
+	fp2.square(t[5], t[5])
+	fp2.sub(&r[1], t[5], t[3])
+	fp2.mul(&r[2], t[1], t[6])
+	fp2.double(t[0], t[7])
+	fp2.add(&coeff[1], t[0], t[7])
+	fp2.neg(&coeff[2], t[6])
+}
+
+func (e *Engine) additionStep(coeff *[3]fe2, r, q *PointG2) {
+	// Algorithm 12 in https://eprint.iacr.org/2010/526.pdf
+	fp2 := e.fp2
+	t := e.t2
+	fp2.mul(t[0], &q[1], &r[2])
+	fp2.neg(t[0], t[0])
+	fp2.add(t[0], t[0], &r[1])
+	fp2.mul(t[1], &q[0], &r[2])
+	fp2.neg(t[1], t[1])
+	fp2.add(t[1], t[1], &r[0])
+	fp2.square(t[2], t[0])
+	fp2.square(t[3], t[1])
+	fp2.mul(t[4], t[1], t[3])
+	fp2.mul(t[2], &r[2], t[2])
+	fp2.mul(t[3], &r[0], t[3])
+	fp2.double(t[5], t[3])
+	fp2.sub(t[5], t[4], t[5])
+	fp2.add(t[5], t[5], t[2])
+	fp2.mul(&r[0], t[1], t[5])
+	fp2.sub(t[2], t[3], t[5])
+	fp2.mul(t[2], t[2], t[0])
+	fp2.mul(t[3], &r[1], t[4])
+	fp2.sub(&r[1], t[2], t[3])
+	fp2.mul(&r[2], &r[2], t[4])
+	fp2.mul(t[2], t[1], &q[1])
+	fp2.mul(t[3], t[0], &q[0])
+	fp2.sub(&coeff[0], t[3], t[2])
+	fp2.neg(&coeff[1], t[0])
+	coeff[2].set(t[1])
+}
+
+func (e *Engine) preCompute(ellCoeffs *[68][3]fe2, twistPoint *PointG2) {
+	// Algorithm 5 in  https://eprint.iacr.org/2019/077.pdf
+	if e.G2.IsZero(twistPoint) {
+		return
+	}
+	r := new(PointG2).Set(twistPoint)
+	j := 0
+	for i := x.BitLen() - 2; i >= 0; i-- {
+		e.doublingStep(&ellCoeffs[j], r)
+		if x.Bit(i) != 0 {
+			j++
+			ellCoeffs[j] = fe6{}
+			e.additionStep(&ellCoeffs[j], r, twistPoint)
+		}
+		j++
+	}
+}
+
+func (e *Engine) millerLoop(f *fe12) {
+	pairs := e.pairs
+	ellCoeffs := make([][68][3]fe2, len(pairs))
+	for i := 0; i < len(pairs); i++ {
+		e.preCompute(&ellCoeffs[i], pairs[i].g2)
+	}
+	fp12, fp2 := e.fp12, e.fp2
+	t := e.t2
+	f.one()
+	j := 0
+	for i := 62; /* x.BitLen() - 2 */ i >= 0; i-- {
+		if i != 62 {
+			fp12.square(f, f)
+		}
+		for i := 0; i <= len(pairs)-1; i++ {
+			fp2.mulByFq(t[0], &ellCoeffs[i][j][2], &pairs[i].g1[1])
+			fp2.mulByFq(t[1], &ellCoeffs[i][j][1], &pairs[i].g1[0])
+			fp12.mulBy014Assign(f, &ellCoeffs[i][j][0], t[1], t[0])
+		}
+		if x.Bit(i) != 0 {
+			j++
+			for i := 0; i <= len(pairs)-1; i++ {
+				fp2.mulByFq(t[0], &ellCoeffs[i][j][2], &pairs[i].g1[1])
+				fp2.mulByFq(t[1], &ellCoeffs[i][j][1], &pairs[i].g1[0])
+				fp12.mulBy014Assign(f, &ellCoeffs[i][j][0], t[1], t[0])
+			}
+		}
+		j++
+	}
+	fp12.conjugate(f, f)
+}
+
+func (e *Engine) exp(c, a *fe12) {
+	fp12 := e.fp12
+	fp12.cyclotomicExp(c, a, x)
+	fp12.conjugate(c, c)
+}
+
+func (e *Engine) finalExp(f *fe12) {
+	fp12 := e.fp12
+	t := e.t12
+	// easy part
+	fp12.frobeniusMap(&t[0], f, 6)
+	fp12.inverse(&t[1], f)
+	fp12.mul(&t[2], &t[0], &t[1])
+	t[1].set(&t[2])
+	fp12.frobeniusMapAssign(&t[2], 2)
+	fp12.mulAssign(&t[2], &t[1])
+	fp12.cyclotomicSquare(&t[1], &t[2])
+	fp12.conjugate(&t[1], &t[1])
+	// hard part
+	e.exp(&t[3], &t[2])
+	fp12.cyclotomicSquare(&t[4], &t[3])
+	fp12.mul(&t[5], &t[1], &t[3])
+	e.exp(&t[1], &t[5])
+	e.exp(&t[0], &t[1])
+	e.exp(&t[6], &t[0])
+	fp12.mulAssign(&t[6], &t[4])
+	e.exp(&t[4], &t[6])
+	fp12.conjugate(&t[5], &t[5])
+	fp12.mulAssign(&t[4], &t[5])
+	fp12.mulAssign(&t[4], &t[2])
+	fp12.conjugate(&t[5], &t[2])
+	fp12.mulAssign(&t[1], &t[2])
+	fp12.frobeniusMapAssign(&t[1], 3)
+	fp12.mulAssign(&t[6], &t[5])
+	fp12.frobeniusMapAssign(&t[6], 1)
+	fp12.mulAssign(&t[3], &t[0])
+	fp12.frobeniusMapAssign(&t[3], 2)
+	fp12.mulAssign(&t[3], &t[1])
+	fp12.mulAssign(&t[3], &t[6])
+	fp12.mul(f, &t[3], &t[4])
+}
+
+func (e *Engine) calculate() *fe12 {
+	f := e.fp12.one()
+	if len(e.pairs) == 0 {
+		return f
+	}
+	e.millerLoop(f)
+	e.finalExp(f)
+	return f
+}
+
+// Check computes pairing and checks if result is equal to one
+func (e *Engine) Check() bool {
+	return e.calculate().isOne()
+}
+
+// Result computes pairing and returns target group element as result.
+func (e *Engine) Result() *E {
+	r := e.calculate()
+	e.Reset()
+	return r
+}
+
+// GT returns target group instance.
+func (e *Engine) GT() *GT {
+	return NewGT()
+}
diff --git a/crypto/bls12381/pairing_test.go b/crypto/bls12381/pairing_test.go
new file mode 100644
index 0000000000..77676fe9b1
--- /dev/null
+++ b/crypto/bls12381/pairing_test.go
@@ -0,0 +1,230 @@
+package bls12381
+
+import (
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+func TestPairingExpected(t *testing.T) {
+	bls := NewPairingEngine()
+	G1, G2 := bls.G1, bls.G2
+	GT := bls.GT()
+	expected, err := GT.FromBytes(
+		common.FromHex("" +
+			"0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631" +
+			"04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef" +
+			"03350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a2" +
+			"11b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba57" +
+			"06fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1a" +
+			"19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89d" +
+			"018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b6" +
+			"01b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5" +
+			"193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934f" +
+			"1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87" +
+			"089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170f" +
+			"1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6",
+		),
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	r := bls.AddPair(G1.One(), G2.One()).Result()
+	if !r.Equal(expected) {
+		t.Fatal("bad pairing")
+	}
+	if !GT.IsValid(r) {
+		t.Fatal("element is not in correct subgroup")
+	}
+}
+
+func TestPairingNonDegeneracy(t *testing.T) {
+	bls := NewPairingEngine()
+	G1, G2 := bls.G1, bls.G2
+	g1Zero, g2Zero, g1One, g2One := G1.Zero(), G2.Zero(), G1.One(), G2.One()
+	GT := bls.GT()
+	// e(g1^a, g2^b) != 1
+	bls.Reset()
+	{
+		bls.AddPair(g1One, g2One)
+		e := bls.Result()
+		if e.IsOne() {
+			t.Fatal("pairing result is not expected to be one")
+		}
+		if !GT.IsValid(e) {
+			t.Fatal("pairing result is not valid")
+		}
+	}
+	// e(g1^a, 0) == 1
+	bls.Reset()
+	{
+		bls.AddPair(g1One, g2Zero)
+		e := bls.Result()
+		if !e.IsOne() {
+			t.Fatal("pairing result is expected to be one")
+		}
+	}
+	// e(0, g2^b) == 1
+	bls.Reset()
+	{
+		bls.AddPair(g1Zero, g2One)
+		e := bls.Result()
+		if !e.IsOne() {
+			t.Fatal("pairing result is expected to be one")
+		}
+	}
+	//
+	bls.Reset()
+	{
+		bls.AddPair(g1Zero, g2One)
+		bls.AddPair(g1One, g2Zero)
+		bls.AddPair(g1Zero, g2Zero)
+		e := bls.Result()
+		if !e.IsOne() {
+			t.Fatal("pairing result is expected to be one")
+		}
+	}
+	//
+	bls.Reset()
+	{
+		expected, err := GT.FromBytes(
+			common.FromHex("" +
+				"0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631" +
+				"04c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef" +
+				"03350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a2" +
+				"11b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba57" +
+				"06fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1a" +
+				"19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89d" +
+				"018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b6" +
+				"01b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5" +
+				"193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934f" +
+				"1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87" +
+				"089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170f" +
+				"1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6",
+			),
+		)
+		if err != nil {
+			t.Fatal(err)
+		}
+		bls.AddPair(g1Zero, g2One)
+		bls.AddPair(g1One, g2Zero)
+		bls.AddPair(g1Zero, g2Zero)
+		bls.AddPair(g1One, g2One)
+		e := bls.Result()
+		if !e.Equal(expected) {
+			t.Fatal("bad pairing")
+		}
+	}
+}
+
+func TestPairingBilinearity(t *testing.T) {
+	bls := NewPairingEngine()
+	g1, g2 := bls.G1, bls.G2
+	gt := bls.GT()
+	// e(a*G1, b*G2) = e(G1, G2)^c
+	{
+		a, b := big.NewInt(17), big.NewInt(117)
+		c := new(big.Int).Mul(a, b)
+		G1, G2 := g1.One(), g2.One()
+		e0 := bls.AddPair(G1, G2).Result()
+		P1, P2 := g1.New(), g2.New()
+		g1.MulScalar(P1, G1, a)
+		g2.MulScalar(P2, G2, b)
+		e1 := bls.AddPair(P1, P2).Result()
+		gt.Exp(e0, e0, c)
+		if !e0.Equal(e1) {
+			t.Fatal("bad pairing, 1")
+		}
+	}
+	// e(a * G1, b * G2) = e((a + b) * G1, G2)
+	{
+		// scalars
+		a, b := big.NewInt(17), big.NewInt(117)
+		c := new(big.Int).Mul(a, b)
+		// LHS
+		G1, G2 := g1.One(), g2.One()
+		g1.MulScalar(G1, G1, c)
+		bls.AddPair(G1, G2)
+		// RHS
+		P1, P2 := g1.One(), g2.One()
+		g1.MulScalar(P1, P1, a)
+		g2.MulScalar(P2, P2, b)
+		bls.AddPairInv(P1, P2)
+		// should be one
+		if !bls.Check() {
+			t.Fatal("bad pairing, 2")
+		}
+	}
+	// e(a * G1, b * G2) = e((a + b) * G1, G2)
+	{
+		// scalars
+		a, b := big.NewInt(17), big.NewInt(117)
+		c := new(big.Int).Mul(a, b)
+		// LHS
+		G1, G2 := g1.One(), g2.One()
+		g2.MulScalar(G2, G2, c)
+		bls.AddPair(G1, G2)
+		// RHS
+		H1, H2 := g1.One(), g2.One()
+		g1.MulScalar(H1, H1, a)
+		g2.MulScalar(H2, H2, b)
+		bls.AddPairInv(H1, H2)
+		// should be one
+		if !bls.Check() {
+			t.Fatal("bad pairing, 3")
+		}
+	}
+}
+
+func TestPairingMulti(t *testing.T) {
+	// e(G1, G2) ^ t == e(a01 * G1, a02 * G2) * e(a11 * G1, a12 * G2) * ... * e(an1 * G1, an2 * G2)
+	// where t = sum(ai1 * ai2)
+	bls := NewPairingEngine()
+	g1, g2 := bls.G1, bls.G2
+	numOfPair := 100
+	targetExp := new(big.Int)
+	// RHS
+	for i := 0; i < numOfPair; i++ {
+		// (ai1 * G1, ai2 * G2)
+		a1, a2 := randScalar(q), randScalar(q)
+		P1, P2 := g1.One(), g2.One()
+		g1.MulScalar(P1, P1, a1)
+		g2.MulScalar(P2, P2, a2)
+		bls.AddPair(P1, P2)
+		// accumulate targetExp
+		// t += (ai1 * ai2)
+		a1.Mul(a1, a2)
+		targetExp.Add(targetExp, a1)
+	}
+	// LHS
+	// e(t * G1, G2)
+	T1, T2 := g1.One(), g2.One()
+	g1.MulScalar(T1, T1, targetExp)
+	bls.AddPairInv(T1, T2)
+	if !bls.Check() {
+		t.Fatal("fail multi pairing")
+	}
+}
+
+func TestPairingEmpty(t *testing.T) {
+	bls := NewPairingEngine()
+	if !bls.Check() {
+		t.Fatal("empty check should be accepted")
+	}
+	if !bls.Result().IsOne() {
+		t.Fatal("empty pairing result should be one")
+	}
+}
+
+func BenchmarkPairing(t *testing.B) {
+	bls := NewPairingEngine()
+	g1, g2, gt := bls.G1, bls.G2, bls.GT()
+	bls.AddPair(g1.One(), g2.One())
+	e := gt.New()
+	t.ResetTimer()
+	for i := 0; i < t.N; i++ {
+		e = bls.calculate()
+	}
+	_ = e
+}
diff --git a/crypto/bls12381/swu.go b/crypto/bls12381/swu.go
new file mode 100644
index 0000000000..40d8c9154d
--- /dev/null
+++ b/crypto/bls12381/swu.go
@@ -0,0 +1,158 @@
+// 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 .
+
+package bls12381
+
+// swuMapG1 is implementation of Simplified Shallue-van de Woestijne-Ulas Method
+// follows the implmentation at draft-irtf-cfrg-hash-to-curve-06.
+func swuMapG1(u *fe) (*fe, *fe) {
+	var params = swuParamsForG1
+	var tv [4]*fe
+	for i := 0; i < 4; i++ {
+		tv[i] = new(fe)
+	}
+	square(tv[0], u)
+	mul(tv[0], tv[0], params.z)
+	square(tv[1], tv[0])
+	x1 := new(fe)
+	add(x1, tv[0], tv[1])
+	inverse(x1, x1)
+	e1 := x1.isZero()
+	one := new(fe).one()
+	add(x1, x1, one)
+	if e1 {
+		x1.set(params.zInv)
+	}
+	mul(x1, x1, params.minusBOverA)
+	gx1 := new(fe)
+	square(gx1, x1)
+	add(gx1, gx1, params.a)
+	mul(gx1, gx1, x1)
+	add(gx1, gx1, params.b)
+	x2 := new(fe)
+	mul(x2, tv[0], x1)
+	mul(tv[1], tv[0], tv[1])
+	gx2 := new(fe)
+	mul(gx2, gx1, tv[1])
+	e2 := !isQuadraticNonResidue(gx1)
+	x, y2 := new(fe), new(fe)
+	if e2 {
+		x.set(x1)
+		y2.set(gx1)
+	} else {
+		x.set(x2)
+		y2.set(gx2)
+	}
+	y := new(fe)
+	sqrt(y, y2)
+	if y.sign() != u.sign() {
+		neg(y, y)
+	}
+	return x, y
+}
+
+// swuMapG2 is implementation of Simplified Shallue-van de Woestijne-Ulas Method
+// defined at draft-irtf-cfrg-hash-to-curve-06.
+func swuMapG2(e *fp2, u *fe2) (*fe2, *fe2) {
+	if e == nil {
+		e = newFp2()
+	}
+	params := swuParamsForG2
+	var tv [4]*fe2
+	for i := 0; i < 4; i++ {
+		tv[i] = e.new()
+	}
+	e.square(tv[0], u)
+	e.mul(tv[0], tv[0], params.z)
+	e.square(tv[1], tv[0])
+	x1 := e.new()
+	e.add(x1, tv[0], tv[1])
+	e.inverse(x1, x1)
+	e1 := x1.isZero()
+	e.add(x1, x1, e.one())
+	if e1 {
+		x1.set(params.zInv)
+	}
+	e.mul(x1, x1, params.minusBOverA)
+	gx1 := e.new()
+	e.square(gx1, x1)
+	e.add(gx1, gx1, params.a)
+	e.mul(gx1, gx1, x1)
+	e.add(gx1, gx1, params.b)
+	x2 := e.new()
+	e.mul(x2, tv[0], x1)
+	e.mul(tv[1], tv[0], tv[1])
+	gx2 := e.new()
+	e.mul(gx2, gx1, tv[1])
+	e2 := !e.isQuadraticNonResidue(gx1)
+	x, y2 := e.new(), e.new()
+	if e2 {
+		x.set(x1)
+		y2.set(gx1)
+	} else {
+		x.set(x2)
+		y2.set(gx2)
+	}
+	y := e.new()
+	e.sqrt(y, y2)
+	if y.sign() != u.sign() {
+		e.neg(y, y)
+	}
+	return x, y
+}
+
+var swuParamsForG1 = struct {
+	z           *fe
+	zInv        *fe
+	a           *fe
+	b           *fe
+	minusBOverA *fe
+}{
+	a:           &fe{0x2f65aa0e9af5aa51, 0x86464c2d1e8416c3, 0xb85ce591b7bd31e2, 0x27e11c91b5f24e7c, 0x28376eda6bfc1835, 0x155455c3e5071d85},
+	b:           &fe{0xfb996971fe22a1e0, 0x9aa93eb35b742d6f, 0x8c476013de99c5c4, 0x873e27c3a221e571, 0xca72b5e45a52d888, 0x06824061418a386b},
+	z:           &fe{0x886c00000023ffdc, 0x0f70008d3090001d, 0x77672417ed5828c3, 0x9dac23e943dc1740, 0x50553f1b9c131521, 0x078c712fbe0ab6e8},
+	zInv:        &fe{0x0e8a2e8ba2e83e10, 0x5b28ba2ca4d745d1, 0x678cd5473847377a, 0x4c506dd8a8076116, 0x9bcb227d79284139, 0x0e8d3154b0ba099a},
+	minusBOverA: &fe{0x052583c93555a7fe, 0x3b40d72430f93c82, 0x1b75faa0105ec983, 0x2527e7dc63851767, 0x99fffd1f34fc181d, 0x097cab54770ca0d3},
+}
+
+var swuParamsForG2 = struct {
+	z           *fe2
+	zInv        *fe2
+	a           *fe2
+	b           *fe2
+	minusBOverA *fe2
+}{
+	a: &fe2{
+		fe{0, 0, 0, 0, 0, 0},
+		fe{0xe53a000003135242, 0x01080c0fdef80285, 0xe7889edbe340f6bd, 0x0b51375126310601, 0x02d6985717c744ab, 0x1220b4e979ea5467},
+	},
+	b: &fe2{
+		fe{0x22ea00000cf89db2, 0x6ec832df71380aa4, 0x6e1b94403db5a66e, 0x75bf3c53a79473ba, 0x3dd3a569412c0a34, 0x125cdb5e74dc4fd1},
+		fe{0x22ea00000cf89db2, 0x6ec832df71380aa4, 0x6e1b94403db5a66e, 0x75bf3c53a79473ba, 0x3dd3a569412c0a34, 0x125cdb5e74dc4fd1},
+	},
+	z: &fe2{
+		fe{0x87ebfffffff9555c, 0x656fffe5da8ffffa, 0x0fd0749345d33ad2, 0xd951e663066576f4, 0xde291a3d41e980d3, 0x0815664c7dfe040d},
+		fe{0x43f5fffffffcaaae, 0x32b7fff2ed47fffd, 0x07e83a49a2e99d69, 0xeca8f3318332bb7a, 0xef148d1ea0f4c069, 0x040ab3263eff0206},
+	},
+	zInv: &fe2{
+		fe{0xacd0000000011110, 0x9dd9999dc88ccccd, 0xb5ca2ac9b76352bf, 0xf1b574bcf4bc90ce, 0x42dab41f28a77081, 0x132fc6ac14cd1e12},
+		fe{0xe396ffffffff2223, 0x4fbf332fcd0d9998, 0x0c4bbd3c1aff4cc4, 0x6b9c91267926ca58, 0x29ae4da6aef7f496, 0x10692e942f195791},
+	},
+	minusBOverA: &fe2{
+		fe{0x903c555555474fb3, 0x5f98cc95ce451105, 0x9f8e582eefe0fade, 0xc68946b6aebbd062, 0x467a4ad10ee6de53, 0x0e7146f483e23a05},
+		fe{0x29c2aaaaaab85af8, 0xbf133368e30eeefa, 0xc7a27a7206cffb45, 0x9dee04ce44c9425c, 0x04a15ce53464ce83, 0x0b8fcaf5b59dac95},
+	},
+}
diff --git a/accounts/abi/numbers.go b/crypto/bls12381/utils.go
similarity index 54%
rename from accounts/abi/numbers.go
rename to crypto/bls12381/utils.go
index 4d706846da..de8bf495fe 100644
--- a/accounts/abi/numbers.go
+++ b/crypto/bls12381/utils.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The go-ethereum Authors
+// 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
@@ -14,31 +14,32 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package abi
+package bls12381
 
 import (
+	"errors"
 	"math/big"
-	"reflect"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/math"
 )
 
-var (
-	bigT      = reflect.TypeOf(&big.Int{})
-	derefbigT = reflect.TypeOf(big.Int{})
-	uint8T    = reflect.TypeOf(uint8(0))
-	uint16T   = reflect.TypeOf(uint16(0))
-	uint32T   = reflect.TypeOf(uint32(0))
-	uint64T   = reflect.TypeOf(uint64(0))
-	int8T     = reflect.TypeOf(int8(0))
-	int16T    = reflect.TypeOf(int16(0))
-	int32T    = reflect.TypeOf(int32(0))
-	int64T    = reflect.TypeOf(int64(0))
-	addressT  = reflect.TypeOf(common.Address{})
-)
+func bigFromHex(hex string) *big.Int {
+	return new(big.Int).SetBytes(common.FromHex(hex))
+}
 
-// U256 converts a big Int into a 256bit EVM number.
-func U256(n *big.Int) []byte {
-	return math.PaddedBigBytes(math.U256(n), 32)
+// decodeFieldElement expects 64 byte input with zero top 16 bytes,
+// returns lower 48 bytes.
+func decodeFieldElement(in []byte) ([]byte, error) {
+	if len(in) != 64 {
+		return nil, errors.New("invalid field element length")
+	}
+	// check top bytes
+	for i := 0; i < 16; i++ {
+		if in[i] != byte(0x00) {
+			return nil, errors.New("invalid field element top bytes")
+		}
+	}
+	out := make([]byte, 48)
+	copy(out[:], in[16:])
+	return out, nil
 }
diff --git a/crypto/bn256/bn256_fuzz.go b/crypto/bn256/bn256_fuzz.go
deleted file mode 100644
index 6aa1421170..0000000000
--- a/crypto/bn256/bn256_fuzz.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2018 Péter Szilágyi. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
-
-// +build gofuzz
-
-package bn256
-
-import (
-	"bytes"
-	"math/big"
-
-	cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare"
-	google "github.com/ethereum/go-ethereum/crypto/bn256/google"
-)
-
-// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries.
-func FuzzAdd(data []byte) int {
-	// Ensure we have enough data in the first place
-	if len(data) != 128 {
-		return 0
-	}
-	// Ensure both libs can parse the first curve point
-	xc := new(cloudflare.G1)
-	_, errc := xc.Unmarshal(data[:64])
-
-	xg := new(google.G1)
-	_, errg := xg.Unmarshal(data[:64])
-
-	if (errc == nil) != (errg == nil) {
-		panic("parse mismatch")
-	} else if errc != nil {
-		return 0
-	}
-	// Ensure both libs can parse the second curve point
-	yc := new(cloudflare.G1)
-	_, errc = yc.Unmarshal(data[64:])
-
-	yg := new(google.G1)
-	_, errg = yg.Unmarshal(data[64:])
-
-	if (errc == nil) != (errg == nil) {
-		panic("parse mismatch")
-	} else if errc != nil {
-		return 0
-	}
-	// Add the two points and ensure they result in the same output
-	rc := new(cloudflare.G1)
-	rc.Add(xc, yc)
-
-	rg := new(google.G1)
-	rg.Add(xg, yg)
-
-	if !bytes.Equal(rc.Marshal(), rg.Marshal()) {
-		panic("add mismatch")
-	}
-	return 0
-}
-
-// FuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare
-// libraries.
-func FuzzMul(data []byte) int {
-	// Ensure we have enough data in the first place
-	if len(data) != 96 {
-		return 0
-	}
-	// Ensure both libs can parse the curve point
-	pc := new(cloudflare.G1)
-	_, errc := pc.Unmarshal(data[:64])
-
-	pg := new(google.G1)
-	_, errg := pg.Unmarshal(data[:64])
-
-	if (errc == nil) != (errg == nil) {
-		panic("parse mismatch")
-	} else if errc != nil {
-		return 0
-	}
-	// Add the two points and ensure they result in the same output
-	rc := new(cloudflare.G1)
-	rc.ScalarMult(pc, new(big.Int).SetBytes(data[64:]))
-
-	rg := new(google.G1)
-	rg.ScalarMult(pg, new(big.Int).SetBytes(data[64:]))
-
-	if !bytes.Equal(rc.Marshal(), rg.Marshal()) {
-		panic("scalar mul mismatch")
-	}
-	return 0
-}
-
-func FuzzPair(data []byte) int {
-	// Ensure we have enough data in the first place
-	if len(data) != 192 {
-		return 0
-	}
-	// Ensure both libs can parse the curve point
-	pc := new(cloudflare.G1)
-	_, errc := pc.Unmarshal(data[:64])
-
-	pg := new(google.G1)
-	_, errg := pg.Unmarshal(data[:64])
-
-	if (errc == nil) != (errg == nil) {
-		panic("parse mismatch")
-	} else if errc != nil {
-		return 0
-	}
-	// Ensure both libs can parse the twist point
-	tc := new(cloudflare.G2)
-	_, errc = tc.Unmarshal(data[64:])
-
-	tg := new(google.G2)
-	_, errg = tg.Unmarshal(data[64:])
-
-	if (errc == nil) != (errg == nil) {
-		panic("parse mismatch")
-	} else if errc != nil {
-		return 0
-	}
-	// Pair the two points and ensure thet result in the same output
-	if cloudflare.PairingCheck([]*cloudflare.G1{pc}, []*cloudflare.G2{tc}) != google.PairingCheck([]*google.G1{pg}, []*google.G2{tg}) {
-		panic("pair mismatch")
-	}
-	return 0
-}
diff --git a/crypto/bn256/cloudflare/bn256.go b/crypto/bn256/cloudflare/bn256.go
index 38822a76bf..4f607af2ad 100644
--- a/crypto/bn256/cloudflare/bn256.go
+++ b/crypto/bn256/cloudflare/bn256.go
@@ -9,8 +9,13 @@
 //
 // This package specifically implements the Optimal Ate pairing over a 256-bit
 // Barreto-Naehrig curve as described in
-// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
-// with the implementation described in that paper.
+// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not
+// compatible with the implementation described in that paper, as different
+// parameters are chosen.
+//
+// (This package previously claimed to operate at a 128-bit security level.
+// However, recent improvements in attacks mean that is no longer true. See
+// https://moderncrypto.org/mail-archive/curves/2016/000740.html.)
 package bn256
 
 import (
@@ -23,7 +28,7 @@ import (
 func randomK(r io.Reader) (k *big.Int, err error) {
 	for {
 		k, err = rand.Int(r, Order)
-		if k.Sign() > 0 || err != nil {
+		if err != nil || k.Sign() > 0 {
 			return
 		}
 	}
diff --git a/crypto/bn256/google/bn256.go b/crypto/bn256/google/bn256.go
index e0402e51f0..0a9d5cd35d 100644
--- a/crypto/bn256/google/bn256.go
+++ b/crypto/bn256/google/bn256.go
@@ -12,8 +12,9 @@
 //
 // This package specifically implements the Optimal Ate pairing over a 256-bit
 // Barreto-Naehrig curve as described in
-// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
-// with the implementation described in that paper.
+// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not
+// compatible with the implementation described in that paper, as different
+// parameters are chosen.
 //
 // (This package previously claimed to operate at a 128-bit security level.
 // However, recent improvements in attacks mean that is no longer true. See
diff --git a/crypto/bn256/google/constants.go b/crypto/bn256/google/constants.go
index ab649d7f3f..2990bd9512 100644
--- a/crypto/bn256/google/constants.go
+++ b/crypto/bn256/google/constants.go
@@ -13,13 +13,16 @@ func bigFromBase10(s string) *big.Int {
 	return n
 }
 
-// u is the BN parameter that determines the prime: 1868033³.
+// u is the BN parameter that determines the prime.
 var u = bigFromBase10("4965661367192848881")
 
-// p is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1.
+// P is a prime over which we form a basic field: 36u⁴+36u³+24u²+6u+1.
 var P = bigFromBase10("21888242871839275222246405745257275088696311157297823662689037894645226208583")
 
 // Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1.
+// Needs to be highly 2-adic for efficient SNARK key and proof generation.
+// Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081.
+// Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters.
 var Order = bigFromBase10("21888242871839275222246405745257275088548364400416034343698204186575808495617")
 
 // xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9.
diff --git a/crypto/crypto.go b/crypto/crypto.go
index 1f43ad15e8..40969a2895 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -24,6 +24,7 @@ import (
 	"encoding/hex"
 	"errors"
 	"fmt"
+	"hash"
 	"io"
 	"io/ioutil"
 	"math/big"
@@ -51,23 +52,46 @@ var (
 
 var errInvalidPubkey = errors.New("invalid secp256k1 public key")
 
+// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports
+// Read to get a variable amount of data from the hash state. Read is faster than Sum
+// because it doesn't copy the internal state, but also modifies the internal state.
+type KeccakState interface {
+	hash.Hash
+	Read([]byte) (int, error)
+}
+
+// NewKeccakState creates a new KeccakState
+func NewKeccakState() KeccakState {
+	return sha3.NewLegacyKeccak256().(KeccakState)
+}
+
+// HashData hashes the provided data using the KeccakState and returns a 32 byte hash
+func HashData(kh KeccakState, data []byte) (h common.Hash) {
+	kh.Reset()
+	kh.Write(data)
+	kh.Read(h[:])
+	return h
+}
+
 // Keccak256 calculates and returns the Keccak256 hash of the input data.
 func Keccak256(data ...[]byte) []byte {
-	d := sha3.NewLegacyKeccak256()
+	b := make([]byte, 32)
+	d := NewKeccakState()
 	for _, b := range data {
 		d.Write(b)
 	}
-	return d.Sum(nil)
+	d.Read(b)
+	return b
 }
 
 // Keccak256Hash calculates and returns the Keccak256 hash of the input data,
 // converting it to an internal Hash data structure.
 func Keccak256Hash(data ...[]byte) (h common.Hash) {
-	d := sha3.NewLegacyKeccak256()
+	d := NewKeccakState()
 	for _, b := range data {
 		d.Write(b)
 	}
-	d.Sum(h[:0])
+	d.Read(h[:])
 	return h
 }
 
diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go
index f71ae8232a..f9b0d3e834 100644
--- a/crypto/crypto_test.go
+++ b/crypto/crypto_test.go
@@ -42,6 +42,13 @@ func TestKeccak256Hash(t *testing.T) {
 	checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp)
 }
 
+func TestKeccak256Hasher(t *testing.T) {
+	msg := []byte("abc")
+	exp, _ := hex.DecodeString("4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45")
+	hasher := NewKeccakState()
+	checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := HashData(hasher, in); return h[:] }, msg, exp)
+}
+
 func TestToECDSAErrors(t *testing.T) {
 	if _, err := HexToECDSA("0000000000000000000000000000000000000000000000000000000000000000"); err == nil {
 		t.Fatal("HexToECDSA should've returned error")
diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go
index 5409ee1d2c..8f83cccad9 100644
--- a/crypto/secp256k1/curve.go
+++ b/crypto/secp256k1/curve.go
@@ -116,6 +116,10 @@ func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool {
 // affineFromJacobian reverses the Jacobian transform. See the comment at the
 // top of the file.
 func (BitCurve *BitCurve) affineFromJacobian(x, y, z *big.Int) (xOut, yOut *big.Int) {
+	if z.Sign() == 0 {
+		return new(big.Int), new(big.Int)
+	}
+
 	zinv := new(big.Int).ModInverse(z, BitCurve.P)
 	zinvsq := new(big.Int).Mul(zinv, zinv)
 
diff --git a/crypto/secp256k1/dummy.go b/crypto/secp256k1/dummy.go
new file mode 100644
index 0000000000..c0f2ee52c0
--- /dev/null
+++ b/crypto/secp256k1/dummy.go
@@ -0,0 +1,20 @@
+// +build dummy
+
+// This file is part of a workaround for `go mod vendor` which won't vendor
+// C files if there's no Go file in the same directory.
+// This would prevent the crypto/secp256k1/libsecp256k1/include/secp256k1.h file to be vendored.
+//
+// This Go file imports the c directory where there is another dummy.go file which
+// is the second part of this workaround.
+//
+// These two files combined make it so `go mod vendor` behaves correctly.
+//
+// See this issue for reference: https://github.com/golang/go/issues/26366
+
+package secp256k1
+
+import (
+	_ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/include"
+	_ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src"
+	_ "github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1/src/modules/recovery"
+)
diff --git a/crypto/secp256k1/libsecp256k1/contrib/dummy.go b/crypto/secp256k1/libsecp256k1/contrib/dummy.go
new file mode 100644
index 0000000000..fda594be99
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/contrib/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package contrib
diff --git a/crypto/secp256k1/libsecp256k1/dummy.go b/crypto/secp256k1/libsecp256k1/dummy.go
new file mode 100644
index 0000000000..379b16992f
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package libsecp256k1
diff --git a/crypto/secp256k1/libsecp256k1/include/dummy.go b/crypto/secp256k1/libsecp256k1/include/dummy.go
new file mode 100644
index 0000000000..5af540c73c
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/include/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package include
diff --git a/crypto/secp256k1/libsecp256k1/src/dummy.go b/crypto/secp256k1/libsecp256k1/src/dummy.go
new file mode 100644
index 0000000000..65868f38a8
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/src/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package src
diff --git a/crypto/secp256k1/libsecp256k1/src/modules/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go
new file mode 100644
index 0000000000..3c7a696439
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/src/modules/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package module
diff --git a/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go
new file mode 100644
index 0000000000..b6fc38327e
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/src/modules/ecdh/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package ecdh
diff --git a/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go
new file mode 100644
index 0000000000..b9491f0cb9
--- /dev/null
+++ b/crypto/secp256k1/libsecp256k1/src/modules/recovery/dummy.go
@@ -0,0 +1,7 @@
+// +build dummy
+
+// Package c contains only a C file.
+//
+// This Go file is part of a workaround for `go mod vendor`.
+// Please see the file crypto/secp256k1/dummy.go for more information.
+package recovery
diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go
index 35d0eef34a..9a7c06d7ce 100644
--- a/crypto/secp256k1/secp256.go
+++ b/crypto/secp256k1/secp256.go
@@ -8,10 +8,19 @@ package secp256k1
 /*
 #cgo CFLAGS: -I./libsecp256k1
 #cgo CFLAGS: -I./libsecp256k1/src/
+
+#ifdef __SIZEOF_INT128__
+#  define HAVE___INT128
+#  define USE_FIELD_5X52
+#  define USE_SCALAR_4X64
+#else
+#  define USE_FIELD_10X26
+#  define USE_SCALAR_8X32
+#endif
+
+#define USE_ENDOMORPHISM
 #define USE_NUM_NONE
-#define USE_FIELD_10X26
 #define USE_FIELD_INV_BUILTIN
-#define USE_SCALAR_8X32
 #define USE_SCALAR_INV_BUILTIN
 #define NDEBUG
 #include "./libsecp256k1/src/secp256k1.c"
diff --git a/crypto/signify/signify.go b/crypto/signify/signify.go
new file mode 100644
index 0000000000..e280f87268
--- /dev/null
+++ b/crypto/signify/signify.go
@@ -0,0 +1,100 @@
+// 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 .
+
+// signFile reads the contents of an input file and signs it (in armored format)
+// with the key provided, placing the signature into the output file.
+
+package signify
+
+import (
+	"bytes"
+	"crypto/ed25519"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"strings"
+	"time"
+)
+
+var (
+	errInvalidKeyHeader = errors.New("incorrect key header")
+	errInvalidKeyLength = errors.New("invalid, key length != 104")
+)
+
+func parsePrivateKey(key string) (k ed25519.PrivateKey, header []byte, keyNum []byte, err error) {
+	keydata, err := base64.StdEncoding.DecodeString(key)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	if len(keydata) != 104 {
+		return nil, nil, nil, errInvalidKeyLength
+	}
+	if string(keydata[:2]) != "Ed" {
+		return nil, nil, nil, errInvalidKeyHeader
+	}
+	return keydata[40:], keydata[:2], keydata[32:40], nil
+}
+
+// SignFile creates a signature of the input file.
+//
+// This accepts base64 keys in the format created by the 'signify' tool.
+// The signature is written to the 'output' file.
+func SignFile(input string, output string, key string, untrustedComment string, trustedComment string) error {
+	// Pre-check comments and ensure they're set to something.
+	if strings.IndexByte(untrustedComment, '\n') >= 0 {
+		return errors.New("untrusted comment must not contain newline")
+	}
+	if strings.IndexByte(trustedComment, '\n') >= 0 {
+		return errors.New("trusted comment must not contain newline")
+	}
+	if untrustedComment == "" {
+		untrustedComment = "verify with " + input + ".pub"
+	}
+	if trustedComment == "" {
+		trustedComment = fmt.Sprintf("timestamp:%d", time.Now().Unix())
+	}
+
+	filedata, err := ioutil.ReadFile(input)
+	if err != nil {
+		return err
+	}
+	skey, header, keyNum, err := parsePrivateKey(key)
+	if err != nil {
+		return err
+	}
+
+	// Create the main data signature.
+	rawSig := ed25519.Sign(skey, filedata)
+	var dataSig []byte
+	dataSig = append(dataSig, header...)
+	dataSig = append(dataSig, keyNum...)
+	dataSig = append(dataSig, rawSig...)
+
+	// Create the comment signature.
+	var commentSigInput []byte
+	commentSigInput = append(commentSigInput, rawSig...)
+	commentSigInput = append(commentSigInput, []byte(trustedComment)...)
+	commentSig := ed25519.Sign(skey, commentSigInput)
+
+	// Create the output file.
+	var out = new(bytes.Buffer)
+	fmt.Fprintln(out, "untrusted comment:", untrustedComment)
+	fmt.Fprintln(out, base64.StdEncoding.EncodeToString(dataSig))
+	fmt.Fprintln(out, "trusted comment:", trustedComment)
+	fmt.Fprintln(out, base64.StdEncoding.EncodeToString(commentSig))
+	return ioutil.WriteFile(output, out.Bytes(), 0644)
+}
diff --git a/crypto/signify/signify_fuzz.go b/crypto/signify/signify_fuzz.go
new file mode 100644
index 0000000000..f9167900ad
--- /dev/null
+++ b/crypto/signify/signify_fuzz.go
@@ -0,0 +1,150 @@
+// 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 .
+
+// +build gofuzz
+
+package signify
+
+import (
+	"bufio"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+
+	fuzz "github.com/google/gofuzz"
+	"github.com/jedisct1/go-minisign"
+)
+
+func Fuzz(data []byte) int {
+	if len(data) < 32 {
+		return -1
+	}
+	tmpFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.Remove(tmpFile.Name())
+	defer tmpFile.Close()
+
+	testSecKey, testPubKey := createKeyPair()
+	// Create message
+	tmpFile.Write(data)
+	if err = tmpFile.Close(); err != nil {
+		panic(err)
+	}
+	// Fuzz comments
+	var untrustedComment string
+	var trustedComment string
+	f := fuzz.NewFromGoFuzz(data)
+	f.Fuzz(&untrustedComment)
+	f.Fuzz(&trustedComment)
+	fmt.Printf("untrusted: %v\n", untrustedComment)
+	fmt.Printf("trusted: %v\n", trustedComment)
+
+	err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment)
+	if err != nil {
+		panic(err)
+	}
+	defer os.Remove(tmpFile.Name() + ".sig")
+
+	signify := "signify"
+	path := os.Getenv("SIGNIFY")
+	if path != "" {
+		signify = path
+	}
+
+	_, err := exec.LookPath(signify)
+	if err != nil {
+		panic(err)
+	}
+
+	// Write the public key into the file to pass it as
+	// an argument to signify-openbsd
+	pubKeyFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.Remove(pubKeyFile.Name())
+	defer pubKeyFile.Close()
+	pubKeyFile.WriteString("untrusted comment: signify public key\n")
+	pubKeyFile.WriteString(testPubKey)
+	pubKeyFile.WriteString("\n")
+
+	cmd := exec.Command(signify, "-V", "-p", pubKeyFile.Name(), "-x", tmpFile.Name()+".sig", "-m", tmpFile.Name())
+	if output, err := cmd.CombinedOutput(); err != nil {
+		panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output))
+	}
+
+	// Verify the signature using a golang library
+	sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig")
+	if err != nil {
+		panic(err)
+	}
+
+	pKey, err := minisign.NewPublicKey(testPubKey)
+	if err != nil {
+		panic(err)
+	}
+
+	valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig)
+	if err != nil {
+		panic(err)
+	}
+	if !valid {
+		panic("invalid signature")
+	}
+	return 1
+}
+
+func getKey(fileS string) (string, error) {
+	file, err := os.Open(fileS)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	// Discard the first line
+	scanner.Scan()
+	scanner.Scan()
+	return scanner.Text(), scanner.Err()
+}
+
+func createKeyPair() (string, string) {
+	// Create key and put it in correct format
+	tmpKey, err := ioutil.TempFile("", "")
+	if err != nil {
+		panic(err)
+	}
+	defer os.Remove(tmpKey.Name())
+	defer os.Remove(tmpKey.Name() + ".pub")
+	defer os.Remove(tmpKey.Name() + ".sec")
+	cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output))
+	}
+	secKey, err := getKey(tmpKey.Name() + ".sec")
+	if err != nil {
+		panic(err)
+	}
+	pubKey, err := getKey(tmpKey.Name() + ".pub")
+	if err != nil {
+		panic(err)
+	}
+	return secKey, pubKey
+}
diff --git a/crypto/signify/signify_test.go b/crypto/signify/signify_test.go
new file mode 100644
index 0000000000..615d4e6527
--- /dev/null
+++ b/crypto/signify/signify_test.go
@@ -0,0 +1,154 @@
+// 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 .
+
+// signFile reads the contents of an input file and signs it (in armored format)
+// with the key provided, placing the signature into the output file.
+
+package signify
+
+import (
+	"io/ioutil"
+	"math/rand"
+	"os"
+	"testing"
+	"time"
+
+	"github.com/jedisct1/go-minisign"
+)
+
+var (
+	testSecKey = "RWRCSwAAAABVN5lr2JViGBN8DhX3/Qb/0g0wBdsNAR/APRW2qy9Fjsfr12sK2cd3URUFis1jgzQzaoayK8x4syT4G3Gvlt9RwGIwUYIQW/0mTeI+ECHu1lv5U4Wa2YHEPIesVPyRm5M="
+	testPubKey = "RWTAPRW2qy9FjsBiMFGCEFv9Jk3iPhAh7tZb+VOFmtmBxDyHrFT8kZuT"
+)
+
+func TestSignify(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name())
+	defer tmpFile.Close()
+
+	rand.Seed(time.Now().UnixNano())
+
+	data := make([]byte, 1024)
+	rand.Read(data)
+	tmpFile.Write(data)
+
+	if err = tmpFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "clé", "croissants")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name() + ".sig")
+
+	// Verify the signature using a golang library
+	sig, err := minisign.NewSignatureFromFile(tmpFile.Name() + ".sig")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pKey, err := minisign.NewPublicKey(testPubKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	valid, err := pKey.VerifyFromFile(tmpFile.Name(), sig)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !valid {
+		t.Fatal("invalid signature")
+	}
+}
+
+func TestSignifyTrustedCommentTooManyLines(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name())
+	defer tmpFile.Close()
+
+	rand.Seed(time.Now().UnixNano())
+
+	data := make([]byte, 1024)
+	rand.Read(data)
+	tmpFile.Write(data)
+
+	if err = tmpFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "crois\nsants")
+	if err == nil || err.Error() == "" {
+		t.Fatalf("should have errored on a multi-line trusted comment, got %v", err)
+	}
+	defer os.Remove(tmpFile.Name() + ".sig")
+}
+
+func TestSignifyTrustedCommentTooManyLinesLF(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name())
+	defer tmpFile.Close()
+
+	rand.Seed(time.Now().UnixNano())
+
+	data := make([]byte, 1024)
+	rand.Read(data)
+	tmpFile.Write(data)
+
+	if err = tmpFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "crois\rsants", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name() + ".sig")
+}
+
+func TestSignifyTrustedCommentEmpty(t *testing.T) {
+	tmpFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name())
+	defer tmpFile.Close()
+
+	rand.Seed(time.Now().UnixNano())
+
+	data := make([]byte, 1024)
+	rand.Read(data)
+	tmpFile.Write(data)
+
+	if err = tmpFile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, "", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmpFile.Name() + ".sig")
+}
diff --git a/eth/api.go b/eth/api.go
index b3415f923c..7387459c94 100644
--- a/eth/api.go
+++ b/eth/api.go
@@ -63,16 +63,7 @@ func (api *PublicEthereumAPI) Coinbase() (common.Address, error) {
 
 // Hashrate returns the POW hashrate
 func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
-	return hexutil.Uint64(api.e.Miner().HashRate())
-}
-
-// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
-func (api *PublicEthereumAPI) ChainId() hexutil.Uint64 {
-	chainID := new(big.Int)
-	if config := api.e.blockchain.Config(); config.IsEIP155(api.e.blockchain.CurrentBlock().Number()) {
-		chainID = config.ChainID
-	}
-	return (hexutil.Uint64)(chainID.Uint64())
+	return hexutil.Uint64(api.e.Miner().Hashrate())
 }
 
 // PublicMinerAPI provides an API to control the miner.
@@ -149,11 +140,6 @@ func (api *PrivateMinerAPI) SetRecommitInterval(interval int) {
 	api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
 }
 
-// GetHashrate returns the current hashrate of the miner.
-func (api *PrivateMinerAPI) GetHashrate() uint64 {
-	return api.e.miner.HashRate()
-}
-
 // PrivateAdminAPI is the collection of Ethereum full node-related APIs
 // exposed over the private admin endpoint.
 type PrivateAdminAPI struct {
@@ -331,22 +317,29 @@ type BadBlockArgs struct {
 // GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network
 // and returns them as a JSON list of block-hashes
 func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) {
-	blocks := api.eth.BlockChain().BadBlocks()
-	results := make([]*BadBlockArgs, len(blocks))
-
-	var err error
-	for i, block := range blocks {
-		results[i] = &BadBlockArgs{
-			Hash: block.Hash(),
-		}
+	var (
+		err     error
+		blocks  = rawdb.ReadAllBadBlocks(api.eth.chainDb)
+		results = make([]*BadBlockArgs, 0, len(blocks))
+	)
+	for _, block := range blocks {
+		var (
+			blockRlp  string
+			blockJSON map[string]interface{}
+		)
 		if rlpBytes, err := rlp.EncodeToBytes(block); err != nil {
-			results[i].RLP = err.Error() // Hacky, but hey, it works
+			blockRlp = err.Error() // Hacky, but hey, it works
 		} else {
-			results[i].RLP = fmt.Sprintf("0x%x", rlpBytes)
+			blockRlp = fmt.Sprintf("0x%x", rlpBytes)
 		}
-		if results[i].Block, err = ethapi.RPCMarshalBlock(block, true, true); err != nil {
-			results[i].Block = map[string]interface{}{"error": err.Error()}
+		if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true); err != nil {
+			blockJSON = map[string]interface{}{"error": err.Error()}
 		}
+		results = append(results, &BadBlockArgs{
+			Hash:  block.Hash(),
+			RLP:   blockRlp,
+			Block: blockJSON,
+		})
 	}
 	return results, nil
 }
@@ -354,7 +347,7 @@ func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs,
 // AccountRangeMaxResults is the maximum number of results to be returned per call
 const AccountRangeMaxResults = 256
 
-// AccountRangeAt enumerates all accounts in the given block and start point in paging request
+// AccountRange enumerates all accounts in the given block and start point in paging request
 func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) {
 	var stateDb *state.StateDB
 	var err error
@@ -389,6 +382,8 @@ func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, sta
 		if err != nil {
 			return state.IteratorDump{}, err
 		}
+	} else {
+		return state.IteratorDump{}, errors.New("either block number or block hash must be specified")
 	}
 
 	if maxResults > AccountRangeMaxResults || maxResults <= 0 {
@@ -412,7 +407,12 @@ type storageEntry struct {
 
 // StorageRangeAt returns the storage at the given block height and transaction index.
 func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
-	_, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0)
+	// Retrieve the block
+	block := api.eth.blockchain.GetBlockByHash(blockHash)
+	if block == nil {
+		return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash)
+	}
+	_, _, statedb, err := api.eth.stateAtTransaction(block, txIndex, 0)
 	if err != nil {
 		return StorageRangeResult{}, err
 	}
diff --git a/eth/api_backend.go b/eth/api_backend.go
index a82aee4eb7..7ac1f82a86 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/accounts"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/bloombits"
 	"github.com/ethereum/go-ethereum/core/rawdb"
@@ -33,15 +34,17 @@ import (
 	"github.com/ethereum/go-ethereum/eth/gasprice"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
+	"github.com/ethereum/go-ethereum/miner"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
 // EthAPIBackend implements ethapi.Backend for full nodes
 type EthAPIBackend struct {
-	extRPCEnabled bool
-	eth           *Ethereum
-	gpo           *gasprice.Oracle
+	extRPCEnabled       bool
+	allowUnprotectedTxs bool
+	eth                 *Ethereum
+	gpo                 *gasprice.Oracle
 }
 
 // ChainConfig returns the active chain configuration.
@@ -54,7 +57,7 @@ func (b *EthAPIBackend) CurrentBlock() *types.Block {
 }
 
 func (b *EthAPIBackend) SetHead(number uint64) {
-	b.eth.protocolManager.downloader.Cancel()
+	b.eth.handler.downloader.Cancel()
 	b.eth.blockchain.SetHead(number)
 }
 
@@ -185,15 +188,18 @@ func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*typ
 	return logs, nil
 }
 
-func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int {
-	return b.eth.blockchain.GetTdByHash(blockHash)
+func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
+	return b.eth.blockchain.GetTdByHash(hash)
 }
 
-func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) {
+func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
 	vmError := func() error { return nil }
-
-	context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil)
-	return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil
+	if vmConfig == nil {
+		vmConfig = b.eth.blockchain.GetVMConfig()
+	}
+	txContext := core.NewEVMTxContext(msg)
+	context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
+	return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), vmError, nil
 }
 
 func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
@@ -257,6 +263,10 @@ func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions,
 	return b.eth.TxPool().Content()
 }
 
+func (b *EthAPIBackend) TxPool() *core.TxPool {
+	return b.eth.TxPool()
+}
+
 func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
 	return b.eth.TxPool().SubscribeNewTxsEvent(ch)
 }
@@ -265,10 +275,6 @@ func (b *EthAPIBackend) Downloader() *downloader.Downloader {
 	return b.eth.Downloader()
 }
 
-func (b *EthAPIBackend) ProtocolVersion() int {
-	return b.eth.EthVersion()
-}
-
 func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
 	return b.gpo.SuggestPrice(ctx)
 }
@@ -289,10 +295,18 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool {
 	return b.extRPCEnabled
 }
 
-func (b *EthAPIBackend) RPCGasCap() *big.Int {
+func (b *EthAPIBackend) UnprotectedAllowed() bool {
+	return b.allowUnprotectedTxs
+}
+
+func (b *EthAPIBackend) RPCGasCap() uint64 {
 	return b.eth.config.RPCGasCap
 }
 
+func (b *EthAPIBackend) RPCTxFeeCap() float64 {
+	return b.eth.config.RPCTxFeeCap
+}
+
 func (b *EthAPIBackend) BloomStatus() (uint64, uint64) {
 	sections, _, _ := b.eth.bloomIndexer.Sections()
 	return params.BloomBitsBlocks, sections
@@ -303,3 +317,27 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
 		go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
 	}
 }
+
+func (b *EthAPIBackend) Engine() consensus.Engine {
+	return b.eth.engine
+}
+
+func (b *EthAPIBackend) CurrentHeader() *types.Header {
+	return b.eth.blockchain.CurrentHeader()
+}
+
+func (b *EthAPIBackend) Miner() *miner.Miner {
+	return b.eth.Miner()
+}
+
+func (b *EthAPIBackend) StartMining(threads int) error {
+	return b.eth.StartMining(threads)
+}
+
+func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
+	return b.eth.stateAtBlock(block, reexec, base, checkLive)
+}
+
+func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
+	return b.eth.stateAtTransaction(block, txIndex, reexec)
+}
diff --git a/eth/api_test.go b/eth/api_test.go
index 42f71e261e..b44eed40bc 100644
--- a/eth/api_test.go
+++ b/eth/api_test.go
@@ -57,8 +57,10 @@ func (h resultHash) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
 func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 }
 
 func TestAccountRange(t *testing.T) {
+	t.Parallel()
+
 	var (
-		statedb  = state.NewDatabase(rawdb.NewMemoryDatabase())
+		statedb  = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil)
 		state, _ = state.New(common.Hash{}, statedb, nil)
 		addrs    = [AccountRangeMaxResults * 2]common.Address{}
 		m        = map[common.Address]bool{}
@@ -126,6 +128,8 @@ func TestAccountRange(t *testing.T) {
 }
 
 func TestEmptyAccountRange(t *testing.T) {
+	t.Parallel()
+
 	var (
 		statedb  = state.NewDatabase(rawdb.NewMemoryDatabase())
 		state, _ = state.New(common.Hash{}, statedb, nil)
@@ -142,6 +146,8 @@ func TestEmptyAccountRange(t *testing.T) {
 }
 
 func TestStorageRangeAt(t *testing.T) {
+	t.Parallel()
+
 	// Create a state where account 0x010000... has a few storage entries.
 	var (
 		state, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
diff --git a/eth/backend.go b/eth/backend.go
index 8293cd3f12..224253d4e1 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -24,23 +24,26 @@ import (
 	"runtime"
 	"sync"
 	"sync/atomic"
+	"time"
 
 	"github.com/ethereum/go-ethereum/accounts"
-	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/consensus/clique"
-	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/consensus/parlia"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/bloombits"
 	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state/pruner"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/eth/filters"
 	"github.com/ethereum/go-ethereum/eth/gasprice"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/eth/protocols/snap"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/internal/ethapi"
@@ -48,32 +51,27 @@ import (
 	"github.com/ethereum/go-ethereum/miner"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/dnsdisc"
 	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
-type LesServer interface {
-	Start(srvr *p2p.Server)
-	Stop()
-	APIs() []rpc.API
-	Protocols() []p2p.Protocol
-	SetBloomBitsIndexer(bbIndexer *core.ChainIndexer)
-	SetContractBackend(bind.ContractBackend)
-}
+// Config contains the configuration options of the ETH protocol.
+// Deprecated: use ethconfig.Config instead.
+type Config = ethconfig.Config
 
 // Ethereum implements the Ethereum full node service.
 type Ethereum struct {
-	config *Config
+	config *ethconfig.Config
 
 	// Handlers
-	txPool          *core.TxPool
-	blockchain      *core.BlockChain
-	protocolManager *ProtocolManager
-	lesServer       LesServer
-	dialCandiates   enode.Iterator
+	txPool             *core.TxPool
+	blockchain         *core.BlockChain
+	handler            *handler
+	ethDialCandidates  enode.Iterator
+	snapDialCandidates enode.Iterator
 
 	// DB interfaces
 	chainDb ethdb.Database // Block chain database
@@ -95,25 +93,14 @@ type Ethereum struct {
 	networkID     uint64
 	netRPCService *ethapi.PublicNetAPI
 
-	lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
-}
-
-func (s *Ethereum) AddLesServer(ls LesServer) {
-	s.lesServer = ls
-	ls.SetBloomBitsIndexer(s.bloomIndexer)
-}
+	p2pServer *p2p.Server
 
-// SetClient sets a rpc client which connecting to our local node.
-func (s *Ethereum) SetContractBackend(backend bind.ContractBackend) {
-	// Pass the rpc client to les server if it is enabled.
-	if s.lesServer != nil {
-		s.lesServer.SetContractBackend(backend)
-	}
+	lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
 }
 
 // New creates a new Ethereum object (including the
 // initialisation of the common Ethereum object)
-func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
+func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
 	// Ensure configuration values are compatible and sane
 	if config.SyncMode == downloader.LightSync {
 		return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
@@ -122,56 +109,73 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
 		return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
 	}
 	if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 {
-		log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", DefaultConfig.Miner.GasPrice)
-		config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice)
+		log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice)
+		config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice)
 	}
 	if config.NoPruning && config.TrieDirtyCache > 0 {
-		config.TrieCleanCache += config.TrieDirtyCache * 3 / 5
-		config.SnapshotCache += config.TrieDirtyCache * 3 / 5
+		if config.SnapshotCache > 0 {
+			config.TrieCleanCache += config.TrieDirtyCache * 3 / 5
+			config.SnapshotCache += config.TrieDirtyCache * 2 / 5
+		} else {
+			config.TrieCleanCache += config.TrieDirtyCache
+		}
 		config.TrieDirtyCache = 0
 	}
 	log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
 
+	// Transfer mining-related config to the ethash config.
+	ethashConfig := config.Ethash
+	ethashConfig.NotifyFull = config.Miner.NotifyFull
+
 	// Assemble the Ethereum object
-	chainDb, err := ctx.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/")
+	chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false)
 	if err != nil {
 		return nil, err
 	}
-	chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideIstanbul, config.OverrideMuirGlacier)
+	chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin)
 	if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
 		return nil, genesisErr
 	}
 	log.Info("Initialised chain configuration", "config", chainConfig)
 
+	if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
+		log.Error("Failed to recover state", "error", err)
+	}
 	eth := &Ethereum{
 		config:            config,
 		chainDb:           chainDb,
-		eventMux:          ctx.EventMux,
-		accountManager:    ctx.AccountManager,
+		eventMux:          stack.EventMux(),
+		accountManager:    stack.AccountManager(),
 		closeBloomHandler: make(chan struct{}),
 		networkID:         config.NetworkId,
 		gasPrice:          config.Miner.GasPrice,
 		etherbase:         config.Miner.Etherbase,
 		bloomRequests:     make(chan chan *bloombits.Retrieval),
-		bloomIndexer:      NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
+		bloomIndexer:      core.NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms),
+		p2pServer:         stack.Server(),
 	}
 
-	eth.APIBackend = &EthAPIBackend{ctx.ExtRPCEnabled(), eth, nil}
+	eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
+	if eth.APIBackend.allowUnprotectedTxs {
+		log.Info("Unprotected transactions allowed")
+	}
 	ethAPI := ethapi.NewPublicBlockChainAPI(eth.APIBackend)
-	eth.engine = CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb, ethAPI, genesisHash)
+	eth.engine = ethconfig.CreateConsensusEngine(stack, chainConfig, ðashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb, ethAPI, genesisHash)
 
 	bcVersion := rawdb.ReadDatabaseVersion(chainDb)
 	var dbVer = ""
 	if bcVersion != nil {
 		dbVer = fmt.Sprintf("%d", *bcVersion)
 	}
-	log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId, "dbversion", dbVer)
+	log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer)
 
 	if !config.SkipBcVersionCheck {
 		if bcVersion != nil && *bcVersion > core.BlockChainVersion {
 			return nil, fmt.Errorf("database version is v%d, Geth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion)
 		} else if bcVersion == nil || *bcVersion < core.BlockChainVersion {
-			log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
+			if bcVersion != nil { // only print warning on upgrade, not on init
+				log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion)
+			}
 			rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
 		}
 	}
@@ -183,14 +187,17 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
 		}
 		cacheConfig = &core.CacheConfig{
 			TrieCleanLimit:      config.TrieCleanCache,
+			TrieCleanJournal:    stack.ResolvePath(config.TrieCleanCacheJournal),
+			TrieCleanRejournal:  config.TrieCleanCacheRejournal,
 			TrieCleanNoPrefetch: config.NoPrefetch,
 			TrieDirtyLimit:      config.TrieDirtyCache,
 			TrieDirtyDisabled:   config.NoPruning,
 			TrieTimeLimit:       config.TrieTimeout,
 			SnapshotLimit:       config.SnapshotCache,
+			Preimages:           config.Preimages,
 		}
 	)
-	eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve)
+	eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit)
 	if err != nil {
 		return nil, err
 	}
@@ -203,7 +210,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
 	eth.bloomIndexer.Start(eth.blockchain)
 
 	if config.TxPool.Journal != "" {
-		config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal)
+		config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
 	}
 	eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain)
 
@@ -213,23 +220,62 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
 	if checkpoint == nil {
 		checkpoint = params.TrustedCheckpoints[genesisHash]
 	}
-	if eth.protocolManager, err = NewProtocolManager(chainConfig, checkpoint, config.SyncMode, config.NetworkId, eth.eventMux, eth.txPool, eth.engine, eth.blockchain, chainDb, cacheLimit, config.Whitelist, config.DirectBroadcast); err != nil {
+
+	if eth.handler, err = newHandler(&handlerConfig{
+		Database:        chainDb,
+		Chain:           eth.blockchain,
+		TxPool:          eth.txPool,
+		Network:         config.NetworkId,
+		Sync:            config.SyncMode,
+		BloomCache:      uint64(cacheLimit),
+		EventMux:        eth.eventMux,
+		Checkpoint:      checkpoint,
+		Whitelist:       config.Whitelist,
+		DirectBroadcast: config.DirectBroadcast,
+	}); err != nil {
 		return nil, err
 	}
 
 	eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
 	eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
+
 	gpoParams := config.GPO
 	if gpoParams.Default == nil {
 		gpoParams.Default = config.Miner.GasPrice
 	}
 	eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
 
-	eth.dialCandiates, err = eth.setupDiscovery(&ctx.Config.P2P)
+	// Setup DNS discovery iterators.
+	dnsclient := dnsdisc.NewClient(dnsdisc.Config{})
+	eth.ethDialCandidates, err = dnsclient.NewIterator(eth.config.EthDiscoveryURLs...)
+	if err != nil {
+		return nil, err
+	}
+	eth.snapDialCandidates, err = dnsclient.NewIterator(eth.config.SnapDiscoveryURLs...)
 	if err != nil {
 		return nil, err
 	}
 
+	// Start the RPC service
+	eth.netRPCService = ethapi.NewPublicNetAPI(eth.p2pServer, config.NetworkId)
+
+	// Register the backend on the node
+	stack.RegisterAPIs(eth.APIs())
+	stack.RegisterProtocols(eth.Protocols())
+	stack.RegisterLifecycle(eth)
+	// Check for unclean shutdown
+	if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
+		log.Error("Could not update unclean-shutdown-marker list", "error", err)
+	} else {
+		if discards > 0 {
+			log.Warn("Old unclean shutdowns found", "count", discards)
+		}
+		for _, tstamp := range uncleanShutdowns {
+			t := time.Unix(int64(tstamp), 0)
+			log.Warn("Unclean shutdown detected", "booted", t,
+				"age", common.PrettyAge(t))
+		}
+	}
 	return eth, nil
 }
 
@@ -250,59 +296,14 @@ func makeExtraData(extra []byte) []byte {
 	return extra
 }
 
-// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
-func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, ee *ethapi.PublicBlockChainAPI, genesisHash common.Hash) consensus.Engine {
-	// If proof-of-authority is requested, set it up
-	if chainConfig.Clique != nil {
-		return clique.New(chainConfig.Clique, db)
-	}
-	if chainConfig.Parlia != nil {
-		return parlia.New(chainConfig, db, ee, genesisHash)
-	}
-	// Otherwise assume proof-of-work
-	switch config.PowMode {
-	case ethash.ModeFake:
-		log.Warn("Ethash used in fake mode")
-		return ethash.NewFaker()
-	case ethash.ModeTest:
-		log.Warn("Ethash used in test mode")
-		return ethash.NewTester(nil, noverify)
-	case ethash.ModeShared:
-		log.Warn("Ethash used in shared mode")
-		return ethash.NewShared()
-	default:
-		engine := ethash.New(ethash.Config{
-			CacheDir:         ctx.ResolvePath(config.CacheDir),
-			CachesInMem:      config.CachesInMem,
-			CachesOnDisk:     config.CachesOnDisk,
-			CachesLockMmap:   config.CachesLockMmap,
-			DatasetDir:       config.DatasetDir,
-			DatasetsInMem:    config.DatasetsInMem,
-			DatasetsOnDisk:   config.DatasetsOnDisk,
-			DatasetsLockMmap: config.DatasetsLockMmap,
-		}, notify, noverify)
-		engine.SetThreads(-1) // Disable CPU mining
-		return engine
-	}
-}
-
 // APIs return the collection of RPC services the ethereum package offers.
 // NOTE, some of these services probably need to be moved to somewhere else.
 func (s *Ethereum) APIs() []rpc.API {
 	apis := ethapi.GetAPIs(s.APIBackend)
 
-	// Append any APIs exposed explicitly by the les server
-	if s.lesServer != nil {
-		apis = append(apis, s.lesServer.APIs()...)
-	}
 	// Append any APIs exposed explicitly by the consensus engine
 	apis = append(apis, s.engine.APIs(s.BlockChain())...)
 
-	// Append any APIs exposed explicitly by the les server
-	if s.lesServer != nil {
-		apis = append(apis, s.lesServer.APIs()...)
-	}
-
 	// Append all the local APIs and return
 	return append(apis, []rpc.API{
 		{
@@ -318,7 +319,7 @@ func (s *Ethereum) APIs() []rpc.API {
 		}, {
 			Namespace: "eth",
 			Version:   "1.0",
-			Service:   downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux),
+			Service:   downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
 			Public:    true,
 		}, {
 			Namespace: "miner",
@@ -328,7 +329,7 @@ func (s *Ethereum) APIs() []rpc.API {
 		}, {
 			Namespace: "eth",
 			Version:   "1.0",
-			Service:   filters.NewPublicFilterAPI(s.APIBackend, false, s.config.RangeLimit),
+			Service:   filters.NewPublicFilterAPI(s.APIBackend, false, 5*time.Minute, s.config.RangeLimit),
 			Public:    true,
 		}, {
 			Namespace: "admin",
@@ -493,7 +494,7 @@ func (s *Ethereum) StartMining(threads int) error {
 		}
 		// If mining is started, we can disable the transaction rejection mechanism
 		// introduced to speed sync times.
-		atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
+		atomic.StoreUint32(&s.handler.acceptTxs, 1)
 
 		go s.miner.Start(eb)
 	}
@@ -524,62 +525,49 @@ func (s *Ethereum) EventMux() *event.TypeMux           { return s.eventMux }
 func (s *Ethereum) Engine() consensus.Engine           { return s.engine }
 func (s *Ethereum) ChainDb() ethdb.Database            { return s.chainDb }
 func (s *Ethereum) IsListening() bool                  { return true } // Always listening
-func (s *Ethereum) EthVersion() int                    { return int(ProtocolVersions[0]) }
-func (s *Ethereum) NetVersion() uint64                 { return s.networkID }
-func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
-func (s *Ethereum) Synced() bool                       { return atomic.LoadUint32(&s.protocolManager.acceptTxs) == 1 }
+func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader }
+func (s *Ethereum) Synced() bool                       { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 }
 func (s *Ethereum) ArchiveMode() bool                  { return s.config.NoPruning }
+func (s *Ethereum) BloomIndexer() *core.ChainIndexer   { return s.bloomIndexer }
 
-// Protocols implements node.Service, returning all the currently configured
+// Protocols returns all the currently configured
 // network protocols to start.
 func (s *Ethereum) Protocols() []p2p.Protocol {
-	protos := make([]p2p.Protocol, len(ProtocolVersions))
-	for i, vsn := range ProtocolVersions {
-		protos[i] = s.protocolManager.makeProtocol(vsn)
-		protos[i].Attributes = []enr.Entry{s.currentEthEntry()}
-		protos[i].DialCandidates = s.dialCandiates
-	}
-	if s.lesServer != nil {
-		protos = append(protos, s.lesServer.Protocols()...)
+	protos := eth.MakeProtocols((*ethHandler)(s.handler), s.networkID, s.ethDialCandidates)
+	if s.config.SnapshotCache > 0 {
+		protos = append(protos, snap.MakeProtocols((*snapHandler)(s.handler), s.snapDialCandidates)...)
 	}
 	return protos
 }
 
-// Start implements node.Service, starting all internal goroutines needed by the
+// Start implements node.Lifecycle, starting all internal goroutines needed by the
 // Ethereum protocol implementation.
-func (s *Ethereum) Start(srvr *p2p.Server) error {
-	s.startEthEntryUpdate(srvr.LocalNode())
+func (s *Ethereum) Start() error {
+	eth.StartENRUpdater(s.blockchain, s.p2pServer.LocalNode())
 
 	// Start the bloom bits servicing goroutines
 	s.startBloomHandlers(params.BloomBitsBlocks)
 
-	// Start the RPC service
-	s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.NetVersion())
-
 	// Figure out a max peers count based on the server limits
-	maxPeers := srvr.MaxPeers
+	maxPeers := s.p2pServer.MaxPeers
 	if s.config.LightServ > 0 {
-		if s.config.LightPeers >= srvr.MaxPeers {
-			return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, srvr.MaxPeers)
+		if s.config.LightPeers >= s.p2pServer.MaxPeers {
+			return fmt.Errorf("invalid peer config: light peer count (%d) >= total peer count (%d)", s.config.LightPeers, s.p2pServer.MaxPeers)
 		}
 		maxPeers -= s.config.LightPeers
 	}
 	// Start the networking layer and the light server if requested
-	s.protocolManager.Start(maxPeers)
-	if s.lesServer != nil {
-		s.lesServer.Start(srvr)
-	}
+	s.handler.Start(maxPeers)
 	return nil
 }
 
-// Stop implements node.Service, terminating all internal goroutines used by the
+// Stop implements node.Lifecycle, terminating all internal goroutines used by the
 // Ethereum protocol.
 func (s *Ethereum) Stop() error {
 	// Stop all the peer-related stuff first.
-	s.protocolManager.Stop()
-	if s.lesServer != nil {
-		s.lesServer.Stop()
-	}
+	s.ethDialCandidates.Close()
+	s.snapDialCandidates.Close()
+	s.handler.Stop()
 
 	// Then stop everything else.
 	s.bloomIndexer.Close()
@@ -588,7 +576,9 @@ func (s *Ethereum) Stop() error {
 	s.miner.Stop()
 	s.blockchain.Stop()
 	s.engine.Close()
+	rawdb.PopUncleanShutdownMarker(s.chainDb)
 	s.chainDb.Close()
 	s.eventMux.Stop()
+
 	return nil
 }
diff --git a/eth/bloombits.go b/eth/bloombits.go
index 35522b9bfa..0cb7050d23 100644
--- a/eth/bloombits.go
+++ b/eth/bloombits.go
@@ -17,16 +17,10 @@
 package eth
 
 import (
-	"context"
 	"time"
 
-	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/bitutil"
-	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/core/bloombits"
 	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/ethdb"
 )
 
 const (
@@ -78,61 +72,3 @@ func (eth *Ethereum) startBloomHandlers(sectionSize uint64) {
 		}()
 	}
 }
-
-const (
-	// bloomThrottling is the time to wait between processing two consecutive index
-	// sections. It's useful during chain upgrades to prevent disk overload.
-	bloomThrottling = 100 * time.Millisecond
-)
-
-// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index
-// for the Ethereum header bloom filters, permitting blazing fast filtering.
-type BloomIndexer struct {
-	size    uint64               // section size to generate bloombits for
-	db      ethdb.Database       // database instance to write index data and metadata into
-	gen     *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
-	section uint64               // Section is the section number being processed currently
-	head    common.Hash          // Head is the hash of the last header processed
-}
-
-// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
-// canonical chain for fast logs filtering.
-func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *core.ChainIndexer {
-	backend := &BloomIndexer{
-		db:   db,
-		size: size,
-	}
-	table := rawdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
-
-	return core.NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits")
-}
-
-// Reset implements core.ChainIndexerBackend, starting a new bloombits index
-// section.
-func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
-	gen, err := bloombits.NewGenerator(uint(b.size))
-	b.gen, b.section, b.head = gen, section, common.Hash{}
-	return err
-}
-
-// Process implements core.ChainIndexerBackend, adding a new header's bloom into
-// the index.
-func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error {
-	b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom)
-	b.head = header.Hash()
-	return nil
-}
-
-// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
-// writing it out into the database.
-func (b *BloomIndexer) Commit() error {
-	batch := b.db.NewBatch()
-	for i := 0; i < types.BloomBitLength; i++ {
-		bits, err := b.gen.Bitset(uint(i))
-		if err != nil {
-			return err
-		}
-		rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits))
-	}
-	return batch.Write()
-}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
new file mode 100644
index 0000000000..ccb0e6bb45
--- /dev/null
+++ b/eth/catalyst/api.go
@@ -0,0 +1,307 @@
+// 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 .
+
+// Package catalyst implements the temporary eth1/eth2 RPC integration.
+package catalyst
+
+import (
+	"errors"
+	"fmt"
+	"math/big"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/node"
+	chainParams "github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+// Register adds catalyst APIs to the node.
+func Register(stack *node.Node, backend *eth.Ethereum) error {
+	chainconfig := backend.BlockChain().Config()
+	if chainconfig.CatalystBlock == nil {
+		return errors.New("catalystBlock is not set in genesis config")
+	} else if chainconfig.CatalystBlock.Sign() != 0 {
+		return errors.New("catalystBlock of genesis config must be zero")
+	}
+
+	log.Warn("Catalyst mode enabled")
+	stack.RegisterAPIs([]rpc.API{
+		{
+			Namespace: "consensus",
+			Version:   "1.0",
+			Service:   newConsensusAPI(backend),
+			Public:    true,
+		},
+	})
+	return nil
+}
+
+type consensusAPI struct {
+	eth *eth.Ethereum
+}
+
+func newConsensusAPI(eth *eth.Ethereum) *consensusAPI {
+	return &consensusAPI{eth: eth}
+}
+
+// blockExecutionEnv gathers all the data required to execute
+// a block, either when assembling it or when inserting it.
+type blockExecutionEnv struct {
+	chain   *core.BlockChain
+	state   *state.StateDB
+	tcount  int
+	gasPool *core.GasPool
+
+	header   *types.Header
+	txs      []*types.Transaction
+	receipts []*types.Receipt
+}
+
+func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
+	vmconfig := *env.chain.GetVMConfig()
+	receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig)
+	if err != nil {
+		return err
+	}
+	env.txs = append(env.txs, tx)
+	env.receipts = append(env.receipts, receipt)
+	return nil
+}
+
+func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) {
+	state, err := api.eth.BlockChain().StateAt(parent.Root())
+	if err != nil {
+		return nil, err
+	}
+	env := &blockExecutionEnv{
+		chain:   api.eth.BlockChain(),
+		state:   state,
+		header:  header,
+		gasPool: new(core.GasPool).AddGas(header.GasLimit),
+	}
+	return env, nil
+}
+
+// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution
+// data" required for eth2 clients to process the new block.
+func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableData, error) {
+	log.Info("Producing block", "parentHash", params.ParentHash)
+
+	bc := api.eth.BlockChain()
+	parent := bc.GetBlockByHash(params.ParentHash)
+	if parent == nil {
+		log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", params.ParentHash)
+		return nil, fmt.Errorf("cannot assemble block with unknown parent %s", params.ParentHash)
+	}
+
+	pool := api.eth.TxPool()
+
+	if parent.Time() >= params.Timestamp {
+		return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp)
+	}
+	if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
+		wait := time.Duration(params.Timestamp-now) * time.Second
+		log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait))
+		time.Sleep(wait)
+	}
+
+	pending, err := pool.Pending()
+	if err != nil {
+		return nil, err
+	}
+
+	coinbase, err := api.eth.Etherbase()
+	if err != nil {
+		return nil, err
+	}
+	num := parent.Number()
+	header := &types.Header{
+		ParentHash: parent.Hash(),
+		Number:     num.Add(num, common.Big1),
+		Coinbase:   coinbase,
+		GasLimit:   parent.GasLimit(), // Keep the gas limit constant in this prototype
+		Extra:      []byte{},
+		Time:       params.Timestamp,
+	}
+	err = api.eth.Engine().Prepare(bc, header)
+	if err != nil {
+		return nil, err
+	}
+
+	env, err := api.makeEnv(parent, header)
+	if err != nil {
+		return nil, err
+	}
+
+	var (
+		signer       = types.MakeSigner(bc.Config(), header.Number)
+		txHeap       = types.NewTransactionsByPriceAndNonce(signer, pending)
+		transactions []*types.Transaction
+	)
+	for {
+		if env.gasPool.Gas() < chainParams.TxGas {
+			log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas)
+			break
+		}
+		tx := txHeap.Peek()
+		if tx == nil {
+			break
+		}
+
+		// The sender is only for logging purposes, and it doesn't really matter if it's correct.
+		from, _ := types.Sender(signer, tx)
+
+		// Execute the transaction
+		env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount)
+		err = env.commitTransaction(tx, coinbase)
+		switch err {
+		case core.ErrGasLimitReached:
+			// Pop the current out-of-gas transaction without shifting in the next from the account
+			log.Trace("Gas limit exceeded for current block", "sender", from)
+			txHeap.Pop()
+
+		case core.ErrNonceTooLow:
+			// New head notification data race between the transaction pool and miner, shift
+			log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
+			txHeap.Shift()
+
+		case core.ErrNonceTooHigh:
+			// Reorg notification data race between the transaction pool and miner, skip account =
+			log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce())
+			txHeap.Pop()
+
+		case nil:
+			// Everything ok, collect the logs and shift in the next transaction from the same account
+			env.tcount++
+			txHeap.Shift()
+			transactions = append(transactions, tx)
+
+		default:
+			// Strange error, discard the transaction and get the next in line (note, the
+			// nonce-too-high clause will prevent us from executing in vain).
+			log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
+			txHeap.Shift()
+		}
+	}
+
+	// Create the block.
+	block, _, err := api.eth.Engine().FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts)
+	if err != nil {
+		return nil, err
+	}
+	return &executableData{
+		BlockHash:    block.Hash(),
+		ParentHash:   block.ParentHash(),
+		Miner:        block.Coinbase(),
+		StateRoot:    block.Root(),
+		Number:       block.NumberU64(),
+		GasLimit:     block.GasLimit(),
+		GasUsed:      block.GasUsed(),
+		Timestamp:    block.Time(),
+		ReceiptRoot:  block.ReceiptHash(),
+		LogsBloom:    block.Bloom().Bytes(),
+		Transactions: encodeTransactions(block.Transactions()),
+	}, nil
+}
+
+func encodeTransactions(txs []*types.Transaction) [][]byte {
+	var enc = make([][]byte, len(txs))
+	for i, tx := range txs {
+		enc[i], _ = tx.MarshalBinary()
+	}
+	return enc
+}
+
+func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
+	var txs = make([]*types.Transaction, len(enc))
+	for i, encTx := range enc {
+		var tx types.Transaction
+		if err := tx.UnmarshalBinary(encTx); err != nil {
+			return nil, fmt.Errorf("invalid transaction %d: %v", i, err)
+		}
+		txs[i] = &tx
+	}
+	return txs, nil
+}
+
+func insertBlockParamsToBlock(params executableData) (*types.Block, error) {
+	txs, err := decodeTransactions(params.Transactions)
+	if err != nil {
+		return nil, err
+	}
+
+	number := big.NewInt(0)
+	number.SetUint64(params.Number)
+	header := &types.Header{
+		ParentHash:  params.ParentHash,
+		UncleHash:   types.EmptyUncleHash,
+		Coinbase:    params.Miner,
+		Root:        params.StateRoot,
+		TxHash:      types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
+		ReceiptHash: params.ReceiptRoot,
+		Bloom:       types.BytesToBloom(params.LogsBloom),
+		Difficulty:  big.NewInt(1),
+		Number:      number,
+		GasLimit:    params.GasLimit,
+		GasUsed:     params.GasUsed,
+		Time:        params.Timestamp,
+	}
+	block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
+	return block, nil
+}
+
+// NewBlock creates an Eth1 block, inserts it in the chain, and either returns true,
+// or false + an error. This is a bit redundant for go, but simplifies things on the
+// eth2 side.
+func (api *consensusAPI) NewBlock(params executableData) (*newBlockResponse, error) {
+	parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
+	if parent == nil {
+		return &newBlockResponse{false}, fmt.Errorf("could not find parent %x", params.ParentHash)
+	}
+	block, err := insertBlockParamsToBlock(params)
+	if err != nil {
+		return nil, err
+	}
+
+	_, err = api.eth.BlockChain().InsertChainWithoutSealVerification(block)
+	return &newBlockResponse{err == nil}, err
+}
+
+// Used in tests to add a the list of transactions from a block to the tx pool.
+func (api *consensusAPI) addBlockTxs(block *types.Block) error {
+	for _, tx := range block.Transactions() {
+		api.eth.TxPool().AddLocal(tx)
+	}
+	return nil
+}
+
+// FinalizeBlock is called to mark a block as synchronized, so
+// that data that is no longer needed can be removed.
+func (api *consensusAPI) FinalizeBlock(blockHash common.Hash) (*genericResponse, error) {
+	return &genericResponse{true}, nil
+}
+
+// SetHead is called to perform a force choice.
+func (api *consensusAPI) SetHead(newHead common.Hash) (*genericResponse, error) {
+	return &genericResponse{true}, nil
+}
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
new file mode 100644
index 0000000000..b8a6e43fcd
--- /dev/null
+++ b/eth/catalyst/api_test.go
@@ -0,0 +1,241 @@
+// 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 .
+
+package catalyst
+
+import (
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
+	"github.com/ethereum/go-ethereum/node"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+var (
+	// testKey is a private key to use for funding a tester account.
+	testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+
+	// testAddr is the Ethereum address of the tester account.
+	testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+
+	testBalance = big.NewInt(2e10)
+)
+
+func generateTestChain() (*core.Genesis, []*types.Block) {
+	db := rawdb.NewMemoryDatabase()
+	config := params.AllEthashProtocolChanges
+	genesis := &core.Genesis{
+		Config:    config,
+		Alloc:     core.GenesisAlloc{testAddr: {Balance: testBalance}},
+		ExtraData: []byte("test genesis"),
+		Timestamp: 9000,
+	}
+	generate := func(i int, g *core.BlockGen) {
+		g.OffsetTime(5)
+		g.SetExtra([]byte("test"))
+	}
+	gblock := genesis.ToBlock(db)
+	engine := ethash.NewFaker()
+	blocks, _ := core.GenerateChain(config, gblock, engine, db, 10, generate)
+	blocks = append([]*types.Block{gblock}, blocks...)
+	return genesis, blocks
+}
+
+func generateTestChainWithFork(n int, fork int) (*core.Genesis, []*types.Block, []*types.Block) {
+	if fork >= n {
+		fork = n - 1
+	}
+	db := rawdb.NewMemoryDatabase()
+	config := ¶ms.ChainConfig{
+		ChainID:             big.NewInt(1337),
+		HomesteadBlock:      big.NewInt(0),
+		EIP150Block:         big.NewInt(0),
+		EIP155Block:         big.NewInt(0),
+		EIP158Block:         big.NewInt(0),
+		ByzantiumBlock:      big.NewInt(0),
+		ConstantinopleBlock: big.NewInt(0),
+		PetersburgBlock:     big.NewInt(0),
+		IstanbulBlock:       big.NewInt(0),
+		MuirGlacierBlock:    big.NewInt(0),
+		BerlinBlock:         big.NewInt(0),
+		CatalystBlock:       big.NewInt(0),
+		Ethash:              new(params.EthashConfig),
+	}
+	genesis := &core.Genesis{
+		Config:    config,
+		Alloc:     core.GenesisAlloc{testAddr: {Balance: testBalance}},
+		ExtraData: []byte("test genesis"),
+		Timestamp: 9000,
+	}
+	generate := func(i int, g *core.BlockGen) {
+		g.OffsetTime(5)
+		g.SetExtra([]byte("test"))
+	}
+	generateFork := func(i int, g *core.BlockGen) {
+		g.OffsetTime(5)
+		g.SetExtra([]byte("testF"))
+	}
+	gblock := genesis.ToBlock(db)
+	engine := ethash.NewFaker()
+	blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate)
+	blocks = append([]*types.Block{gblock}, blocks...)
+	forkedBlocks, _ := core.GenerateChain(config, blocks[fork], engine, db, n-fork, generateFork)
+	return genesis, blocks, forkedBlocks
+}
+
+func TestEth2AssembleBlock(t *testing.T) {
+	genesis, blocks := generateTestChain()
+	n, ethservice := startEthService(t, genesis, blocks[1:9])
+	defer n.Close()
+
+	api := newConsensusAPI(ethservice)
+	signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
+	tx, err := types.SignTx(types.NewTransaction(0, blocks[8].Coinbase(), big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
+	if err != nil {
+		t.Fatalf("error signing transaction, err=%v", err)
+	}
+	ethservice.TxPool().AddLocal(tx)
+	blockParams := assembleBlockParams{
+		ParentHash: blocks[8].ParentHash(),
+		Timestamp:  blocks[8].Time(),
+	}
+	execData, err := api.AssembleBlock(blockParams)
+
+	if err != nil {
+		t.Fatalf("error producing block, err=%v", err)
+	}
+
+	if len(execData.Transactions) != 1 {
+		t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
+	}
+}
+
+func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
+	genesis, blocks := generateTestChain()
+	n, ethservice := startEthService(t, genesis, blocks[1:9])
+	defer n.Close()
+
+	api := newConsensusAPI(ethservice)
+
+	// Put the 10th block's tx in the pool and produce a new block
+	api.addBlockTxs(blocks[9])
+	blockParams := assembleBlockParams{
+		ParentHash: blocks[9].ParentHash(),
+		Timestamp:  blocks[9].Time(),
+	}
+	execData, err := api.AssembleBlock(blockParams)
+	if err != nil {
+		t.Fatalf("error producing block, err=%v", err)
+	}
+
+	if len(execData.Transactions) != blocks[9].Transactions().Len() {
+		t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
+	}
+}
+
+func TestEth2NewBlock(t *testing.T) {
+	genesis, blocks, forkedBlocks := generateTestChainWithFork(10, 4)
+	n, ethservice := startEthService(t, genesis, blocks[1:5])
+	defer n.Close()
+
+	api := newConsensusAPI(ethservice)
+	for i := 5; i < 10; i++ {
+		p := executableData{
+			ParentHash:   ethservice.BlockChain().CurrentBlock().Hash(),
+			Miner:        blocks[i].Coinbase(),
+			StateRoot:    blocks[i].Root(),
+			GasLimit:     blocks[i].GasLimit(),
+			GasUsed:      blocks[i].GasUsed(),
+			Transactions: encodeTransactions(blocks[i].Transactions()),
+			ReceiptRoot:  blocks[i].ReceiptHash(),
+			LogsBloom:    blocks[i].Bloom().Bytes(),
+			BlockHash:    blocks[i].Hash(),
+			Timestamp:    blocks[i].Time(),
+			Number:       uint64(i),
+		}
+		success, err := api.NewBlock(p)
+		if err != nil || !success.Valid {
+			t.Fatalf("Failed to insert block: %v", err)
+		}
+	}
+
+	exp := ethservice.BlockChain().CurrentBlock().Hash()
+
+	// Introduce the fork point.
+	lastBlockNum := blocks[4].Number()
+	lastBlock := blocks[4]
+	for i := 0; i < 4; i++ {
+		lastBlockNum.Add(lastBlockNum, big.NewInt(1))
+		p := executableData{
+			ParentHash:   lastBlock.Hash(),
+			Miner:        forkedBlocks[i].Coinbase(),
+			StateRoot:    forkedBlocks[i].Root(),
+			Number:       lastBlockNum.Uint64(),
+			GasLimit:     forkedBlocks[i].GasLimit(),
+			GasUsed:      forkedBlocks[i].GasUsed(),
+			Transactions: encodeTransactions(blocks[i].Transactions()),
+			ReceiptRoot:  forkedBlocks[i].ReceiptHash(),
+			LogsBloom:    forkedBlocks[i].Bloom().Bytes(),
+			BlockHash:    forkedBlocks[i].Hash(),
+			Timestamp:    forkedBlocks[i].Time(),
+		}
+		success, err := api.NewBlock(p)
+		if err != nil || !success.Valid {
+			t.Fatalf("Failed to insert forked block #%d: %v", i, err)
+		}
+		lastBlock, err = insertBlockParamsToBlock(p)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	if ethservice.BlockChain().CurrentBlock().Hash() != exp {
+		t.Fatalf("Wrong head after inserting fork %x != %x", exp, ethservice.BlockChain().CurrentBlock().Hash())
+	}
+}
+
+// startEthService creates a full node instance for testing.
+func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
+	t.Helper()
+
+	n, err := node.New(&node.Config{})
+	if err != nil {
+		t.Fatal("can't create node:", err)
+	}
+
+	ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}}
+	ethservice, err := eth.New(n, ethcfg)
+	if err != nil {
+		t.Fatal("can't create eth service:", err)
+	}
+	if err := n.Start(); err != nil {
+		t.Fatal("can't start node:", err)
+	}
+	if _, err := ethservice.BlockChain().InsertChain(blocks); err != nil {
+		n.Close()
+		t.Fatal("can't import test blocks:", err)
+	}
+	ethservice.SetEtherbase(testAddr)
+
+	return n, ethservice
+}
diff --git a/eth/catalyst/api_types.go b/eth/catalyst/api_types.go
new file mode 100644
index 0000000000..d5d351a991
--- /dev/null
+++ b/eth/catalyst/api_types.go
@@ -0,0 +1,70 @@
+// 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 .
+
+package catalyst
+
+import (
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+//go:generate go run github.com/fjl/gencodec -type assembleBlockParams -field-override assembleBlockParamsMarshaling -out gen_blockparams.go
+
+// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ
+type assembleBlockParams struct {
+	ParentHash common.Hash `json:"parentHash"    gencodec:"required"`
+	Timestamp  uint64      `json:"timestamp"     gencodec:"required"`
+}
+
+// JSON type overrides for assembleBlockParams.
+type assembleBlockParamsMarshaling struct {
+	Timestamp hexutil.Uint64
+}
+
+//go:generate go run github.com/fjl/gencodec -type executableData -field-override executableDataMarshaling -out gen_ed.go
+
+// Structure described at https://notes.ethereum.org/@n0ble/rayonism-the-merge-spec#Parameters1
+type executableData struct {
+	BlockHash    common.Hash    `json:"blockHash"     gencodec:"required"`
+	ParentHash   common.Hash    `json:"parentHash"    gencodec:"required"`
+	Miner        common.Address `json:"miner"         gencodec:"required"`
+	StateRoot    common.Hash    `json:"stateRoot"     gencodec:"required"`
+	Number       uint64         `json:"number"        gencodec:"required"`
+	GasLimit     uint64         `json:"gasLimit"      gencodec:"required"`
+	GasUsed      uint64         `json:"gasUsed"       gencodec:"required"`
+	Timestamp    uint64         `json:"timestamp"     gencodec:"required"`
+	ReceiptRoot  common.Hash    `json:"receiptsRoot"  gencodec:"required"`
+	LogsBloom    []byte         `json:"logsBloom"     gencodec:"required"`
+	Transactions [][]byte       `json:"transactions"  gencodec:"required"`
+}
+
+// JSON type overrides for executableData.
+type executableDataMarshaling struct {
+	Number       hexutil.Uint64
+	GasLimit     hexutil.Uint64
+	GasUsed      hexutil.Uint64
+	Timestamp    hexutil.Uint64
+	LogsBloom    hexutil.Bytes
+	Transactions []hexutil.Bytes
+}
+
+type newBlockResponse struct {
+	Valid bool `json:"valid"`
+}
+
+type genericResponse struct {
+	Success bool `json:"success"`
+}
diff --git a/eth/catalyst/gen_blockparams.go b/eth/catalyst/gen_blockparams.go
new file mode 100644
index 0000000000..a9a08ec3a8
--- /dev/null
+++ b/eth/catalyst/gen_blockparams.go
@@ -0,0 +1,46 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package catalyst
+
+import (
+	"encoding/json"
+	"errors"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*assembleBlockParamsMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (a assembleBlockParams) MarshalJSON() ([]byte, error) {
+	type assembleBlockParams struct {
+		ParentHash common.Hash    `json:"parentHash"    gencodec:"required"`
+		Timestamp  hexutil.Uint64 `json:"timestamp"     gencodec:"required"`
+	}
+	var enc assembleBlockParams
+	enc.ParentHash = a.ParentHash
+	enc.Timestamp = hexutil.Uint64(a.Timestamp)
+	return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (a *assembleBlockParams) UnmarshalJSON(input []byte) error {
+	type assembleBlockParams struct {
+		ParentHash *common.Hash    `json:"parentHash"    gencodec:"required"`
+		Timestamp  *hexutil.Uint64 `json:"timestamp"     gencodec:"required"`
+	}
+	var dec assembleBlockParams
+	if err := json.Unmarshal(input, &dec); err != nil {
+		return err
+	}
+	if dec.ParentHash == nil {
+		return errors.New("missing required field 'parentHash' for assembleBlockParams")
+	}
+	a.ParentHash = *dec.ParentHash
+	if dec.Timestamp == nil {
+		return errors.New("missing required field 'timestamp' for assembleBlockParams")
+	}
+	a.Timestamp = uint64(*dec.Timestamp)
+	return nil
+}
diff --git a/eth/catalyst/gen_ed.go b/eth/catalyst/gen_ed.go
new file mode 100644
index 0000000000..4c2e4c8ead
--- /dev/null
+++ b/eth/catalyst/gen_ed.go
@@ -0,0 +1,117 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package catalyst
+
+import (
+	"encoding/json"
+	"errors"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*executableDataMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (e executableData) MarshalJSON() ([]byte, error) {
+	type executableData struct {
+		BlockHash    common.Hash     `json:"blockHash"     gencodec:"required"`
+		ParentHash   common.Hash     `json:"parentHash"    gencodec:"required"`
+		Miner        common.Address  `json:"miner"         gencodec:"required"`
+		StateRoot    common.Hash     `json:"stateRoot"     gencodec:"required"`
+		Number       hexutil.Uint64  `json:"number"        gencodec:"required"`
+		GasLimit     hexutil.Uint64  `json:"gasLimit"      gencodec:"required"`
+		GasUsed      hexutil.Uint64  `json:"gasUsed"       gencodec:"required"`
+		Timestamp    hexutil.Uint64  `json:"timestamp"     gencodec:"required"`
+		ReceiptRoot  common.Hash     `json:"receiptsRoot"  gencodec:"required"`
+		LogsBloom    hexutil.Bytes   `json:"logsBloom"     gencodec:"required"`
+		Transactions []hexutil.Bytes `json:"transactions"  gencodec:"required"`
+	}
+	var enc executableData
+	enc.BlockHash = e.BlockHash
+	enc.ParentHash = e.ParentHash
+	enc.Miner = e.Miner
+	enc.StateRoot = e.StateRoot
+	enc.Number = hexutil.Uint64(e.Number)
+	enc.GasLimit = hexutil.Uint64(e.GasLimit)
+	enc.GasUsed = hexutil.Uint64(e.GasUsed)
+	enc.Timestamp = hexutil.Uint64(e.Timestamp)
+	enc.ReceiptRoot = e.ReceiptRoot
+	enc.LogsBloom = e.LogsBloom
+	if e.Transactions != nil {
+		enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
+		for k, v := range e.Transactions {
+			enc.Transactions[k] = v
+		}
+	}
+	return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (e *executableData) UnmarshalJSON(input []byte) error {
+	type executableData struct {
+		BlockHash    *common.Hash    `json:"blockHash"     gencodec:"required"`
+		ParentHash   *common.Hash    `json:"parentHash"    gencodec:"required"`
+		Miner        *common.Address `json:"miner"         gencodec:"required"`
+		StateRoot    *common.Hash    `json:"stateRoot"     gencodec:"required"`
+		Number       *hexutil.Uint64 `json:"number"        gencodec:"required"`
+		GasLimit     *hexutil.Uint64 `json:"gasLimit"      gencodec:"required"`
+		GasUsed      *hexutil.Uint64 `json:"gasUsed"       gencodec:"required"`
+		Timestamp    *hexutil.Uint64 `json:"timestamp"     gencodec:"required"`
+		ReceiptRoot  *common.Hash    `json:"receiptsRoot"  gencodec:"required"`
+		LogsBloom    *hexutil.Bytes  `json:"logsBloom"     gencodec:"required"`
+		Transactions []hexutil.Bytes `json:"transactions"  gencodec:"required"`
+	}
+	var dec executableData
+	if err := json.Unmarshal(input, &dec); err != nil {
+		return err
+	}
+	if dec.BlockHash == nil {
+		return errors.New("missing required field 'blockHash' for executableData")
+	}
+	e.BlockHash = *dec.BlockHash
+	if dec.ParentHash == nil {
+		return errors.New("missing required field 'parentHash' for executableData")
+	}
+	e.ParentHash = *dec.ParentHash
+	if dec.Miner == nil {
+		return errors.New("missing required field 'miner' for executableData")
+	}
+	e.Miner = *dec.Miner
+	if dec.StateRoot == nil {
+		return errors.New("missing required field 'stateRoot' for executableData")
+	}
+	e.StateRoot = *dec.StateRoot
+	if dec.Number == nil {
+		return errors.New("missing required field 'number' for executableData")
+	}
+	e.Number = uint64(*dec.Number)
+	if dec.GasLimit == nil {
+		return errors.New("missing required field 'gasLimit' for executableData")
+	}
+	e.GasLimit = uint64(*dec.GasLimit)
+	if dec.GasUsed == nil {
+		return errors.New("missing required field 'gasUsed' for executableData")
+	}
+	e.GasUsed = uint64(*dec.GasUsed)
+	if dec.Timestamp == nil {
+		return errors.New("missing required field 'timestamp' for executableData")
+	}
+	e.Timestamp = uint64(*dec.Timestamp)
+	if dec.ReceiptRoot == nil {
+		return errors.New("missing required field 'receiptsRoot' for executableData")
+	}
+	e.ReceiptRoot = *dec.ReceiptRoot
+	if dec.LogsBloom == nil {
+		return errors.New("missing required field 'logsBloom' for executableData")
+	}
+	e.LogsBloom = *dec.LogsBloom
+	if dec.Transactions == nil {
+		return errors.New("missing required field 'transactions' for executableData")
+	}
+	e.Transactions = make([][]byte, len(dec.Transactions))
+	for k, v := range dec.Transactions {
+		e.Transactions[k] = v
+	}
+	return nil
+}
diff --git a/eth/config.go b/eth/config.go
deleted file mode 100644
index 05342a6306..0000000000
--- a/eth/config.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2017 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 .
-
-package eth
-
-import (
-	"math/big"
-	"os"
-	"os/user"
-	"path/filepath"
-	"runtime"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/consensus/ethash"
-	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/eth/downloader"
-	"github.com/ethereum/go-ethereum/eth/gasprice"
-	"github.com/ethereum/go-ethereum/miner"
-	"github.com/ethereum/go-ethereum/params"
-)
-
-// DefaultConfig contains default settings for use on the Ethereum main net.
-var DefaultConfig = Config{
-	SyncMode: downloader.FastSync,
-	Ethash: ethash.Config{
-		CacheDir:         "ethash",
-		CachesInMem:      2,
-		CachesOnDisk:     3,
-		CachesLockMmap:   false,
-		DatasetsInMem:    1,
-		DatasetsOnDisk:   2,
-		DatasetsLockMmap: false,
-	},
-	NetworkId:          1,
-	LightPeers:         100,
-	UltraLightFraction: 75,
-	DatabaseCache:      512,
-	TrieCleanCache:     256,
-	TrieDirtyCache:     256,
-	TrieTimeout:        60 * time.Minute,
-	SnapshotCache:      256,
-	Miner: miner.Config{
-		GasFloor:      8000000,
-		GasCeil:       8000000,
-		GasPrice:      big.NewInt(params.GWei),
-		Recommit:      3 * time.Second,
-		DelayLeftOver: 50 * time.Millisecond,
-	},
-	TxPool: core.DefaultTxPoolConfig,
-	GPO: gasprice.Config{
-		Blocks:          20,
-		Percentile:      60,
-		OracleThreshold: 1000,
-	},
-}
-
-func init() {
-	home := os.Getenv("HOME")
-	if home == "" {
-		if user, err := user.Current(); err == nil {
-			home = user.HomeDir
-		}
-	}
-	if runtime.GOOS == "darwin" {
-		DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash")
-	} else if runtime.GOOS == "windows" {
-		localappdata := os.Getenv("LOCALAPPDATA")
-		if localappdata != "" {
-			DefaultConfig.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash")
-		} else {
-			DefaultConfig.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash")
-		}
-	} else {
-		DefaultConfig.Ethash.DatasetDir = filepath.Join(home, ".ethash")
-	}
-}
-
-//go:generate gencodec -type Config -formats toml -out gen_config.go
-
-type Config struct {
-	// The genesis block, which is inserted if the database is empty.
-	// If nil, the Ethereum main net block is used.
-	Genesis *core.Genesis `toml:",omitempty"`
-
-	// Protocol options
-	NetworkId uint64 // Network ID to use for selecting peers to connect to
-	SyncMode  downloader.SyncMode
-
-	// This can be set to list of enrtree:// URLs which will be queried for
-	// for nodes to connect to.
-	DiscoveryURLs []string
-
-	NoPruning       bool // Whether to disable pruning and flush everything to disk
-	NoPrefetch      bool // Whether to disable prefetching and only load state on demand
-	DirectBroadcast bool
-	RangeLimit      bool
-
-	// Whitelist of required block number -> hash values to accept
-	Whitelist map[uint64]common.Hash `toml:"-"`
-
-	// Light client options
-	LightServ    int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
-	LightIngress int `toml:",omitempty"` // Incoming bandwidth limit for light servers
-	LightEgress  int `toml:",omitempty"` // Outgoing bandwidth limit for light servers
-	LightPeers   int `toml:",omitempty"` // Maximum number of LES client peers
-
-	// Ultra Light client options
-	UltraLightServers      []string `toml:",omitempty"` // List of trusted ultra light servers
-	UltraLightFraction     int      `toml:",omitempty"` // Percentage of trusted servers to accept an announcement
-	UltraLightOnlyAnnounce bool     `toml:",omitempty"` // Whether to only announce headers, or also serve them
-
-	// Database options
-	SkipBcVersionCheck bool `toml:"-"`
-	DatabaseHandles    int  `toml:"-"`
-	DatabaseCache      int
-	DatabaseFreezer    string
-
-	TrieCleanCache int
-	TrieDirtyCache int
-	TrieTimeout    time.Duration
-	SnapshotCache  int
-
-	// Mining options
-	Miner miner.Config
-
-	// Ethash options
-	Ethash ethash.Config `toml:",omitempty"`
-
-	// Transaction pool options
-	TxPool core.TxPoolConfig
-
-	// Gas Price Oracle options
-	GPO gasprice.Config
-
-	// Enables tracking of SHA3 preimages in the VM
-	EnablePreimageRecording bool
-
-	// Miscellaneous options
-	DocRoot string `toml:"-"`
-
-	// Type of the EWASM interpreter ("" for default)
-	EWASMInterpreter string
-
-	// Type of the EVM interpreter ("" for default)
-	EVMInterpreter string
-
-	// RPCGasCap is the global gas cap for eth-call variants.
-	RPCGasCap *big.Int `toml:",omitempty"`
-
-	// Checkpoint is a hardcoded checkpoint which can be nil.
-	Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
-
-	// CheckpointOracle is the configuration for checkpoint oracle.
-	CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
-
-	// Istanbul block override (TODO: remove after the fork)
-	OverrideIstanbul *big.Int `toml:",omitempty"`
-
-	// MuirGlacier block override (TODO: remove after the fork)
-	OverrideMuirGlacier *big.Int `toml:",omitempty"`
-}
diff --git a/eth/discovery.go b/eth/discovery.go
index 97d6322ca1..70668b2b70 100644
--- a/eth/discovery.go
+++ b/eth/discovery.go
@@ -19,8 +19,6 @@ package eth
 import (
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/forkid"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/dnsdisc"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/rlp"
 )
@@ -60,14 +58,6 @@ func (eth *Ethereum) startEthEntryUpdate(ln *enode.LocalNode) {
 }
 
 func (eth *Ethereum) currentEthEntry() *ethEntry {
-	return ðEntry{ForkID: forkid.NewID(eth.blockchain)}
-}
-
-// setupDiscovery creates the node discovery source for the eth protocol.
-func (eth *Ethereum) setupDiscovery(cfg *p2p.Config) (enode.Iterator, error) {
-	if cfg.NoDiscovery || len(eth.config.DiscoveryURLs) == 0 {
-		return nil, nil
-	}
-	client := dnsdisc.NewClient(dnsdisc.Config{})
-	return client.NewIterator(eth.config.DiscoveryURLs...)
+	return ðEntry{ForkID: forkid.NewID(eth.blockchain.Config(), eth.blockchain.Genesis().Hash(),
+		eth.blockchain.CurrentHeader().Number.Uint64())}
 }
diff --git a/eth/downloader/api.go b/eth/downloader/api.go
index 57ff3d71af..2024d23dea 100644
--- a/eth/downloader/api.go
+++ b/eth/downloader/api.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"sync"
 
-	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/rpc"
 )
diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go
index f8982f696f..6f59b29a5e 100644
--- a/eth/downloader/downloader.go
+++ b/eth/downloader/downloader.go
@@ -28,7 +28,10 @@ import (
 	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state/snapshot"
 	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/eth/protocols/snap"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/log"
@@ -38,11 +41,9 @@ import (
 )
 
 var (
-	MaxHashFetch    = 512 // Amount of hashes to be fetched per retrieval request
 	MaxBlockFetch   = 128 // Amount of blocks to be fetched per retrieval request
 	MaxHeaderFetch  = 192 // Amount of block headers to be fetched per retrieval request
 	MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly
-	MaxBodyFetch    = 128 // Amount of block bodies to be fetched per retrieval request
 	MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request
 	MaxStateFetch   = 384 // Amount of node state values to allow fetching per request
 
@@ -56,10 +57,11 @@ var (
 	qosConfidenceCap = 10   // Number of peers above which not to modify RTT confidence
 	qosTuningImpact  = 0.25 // Impact that a new tuning target has on the previous value
 
-	maxQueuedHeaders         = 32 * 1024                    // [eth/62] Maximum number of headers to queue for import (DOS protection)
-	maxHeadersProcess        = 2048                         // Number of header download results to import at once into the chain
-	maxResultsProcess        = 2048                         // Number of content download results to import at once into the chain
-	maxForkAncestry   uint64 = params.ImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
+	maxQueuedHeaders            = 32 * 1024                         // [eth/62] Maximum number of headers to queue for import (DOS protection)
+	maxHeadersProcess           = 2048                              // Number of header download results to import at once into the chain
+	maxResultsProcess           = 2048                              // Number of content download results to import at once into the chain
+	fullMaxForkAncestry  uint64 = params.FullImmutabilityThreshold  // Maximum chain reorganisation (locally redeclared so tests can reduce it)
+	lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it)
 
 	reorgProtThreshold   = 48 // Threshold number of recent blocks to disable mini reorg protection
 	reorgProtHeaderDelay = 2  // Number of headers to delay delivering to cover mini reorgs
@@ -89,7 +91,8 @@ var (
 	errCancelContentProcessing = errors.New("content processing canceled (requested)")
 	errCanceled                = errors.New("syncing canceled (requested)")
 	errNoSyncActive            = errors.New("no sync active")
-	errTooOld                  = errors.New("peer doesn't speak recent enough protocol version (need version >= 62)")
+	errTooOld                  = errors.New("peer's protocol version too old")
+	errNoAncestorFound         = errors.New("no common ancestor found")
 )
 
 type Downloader struct {
@@ -100,7 +103,7 @@ type Downloader struct {
 	rttEstimate   uint64 // Round trip time to target for download requests
 	rttConfidence uint64 // Confidence in the estimated RTT (unit: millionths to allow atomic ops)
 
-	mode SyncMode       // Synchronisation mode defining the strategy used (per sync cycle)
+	mode uint32         // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
 	mux  *event.TypeMux // Event multiplexer to announce sync operation events
 
 	checkpoint uint64   // Checkpoint block number to enforce head against (e.g. fast sync)
@@ -109,7 +112,7 @@ type Downloader struct {
 	peers      *peerSet // Set of active peers from which download can proceed
 
 	stateDB    ethdb.Database  // Database to state sync into (and deduplicate via)
-	stateBloom *trie.SyncBloom // Bloom filter for fast trie node existence checks
+	stateBloom *trie.SyncBloom // Bloom filter for fast trie node and contract code existence checks
 
 	// Statistics
 	syncStatsChainOrigin uint64 // Origin block number where syncing started at
@@ -131,17 +134,22 @@ type Downloader struct {
 	ancientLimit    uint64 // The maximum block number which can be regarded as ancient data.
 
 	// Channels
-	headerCh      chan dataPack        // [eth/62] Channel receiving inbound block headers
-	bodyCh        chan dataPack        // [eth/62] Channel receiving inbound block bodies
-	receiptCh     chan dataPack        // [eth/63] Channel receiving inbound receipts
-	bodyWakeCh    chan bool            // [eth/62] Channel to signal the block body fetcher of new tasks
-	receiptWakeCh chan bool            // [eth/63] Channel to signal the receipt fetcher of new tasks
-	headerProcCh  chan []*types.Header // [eth/62] Channel to feed the header processor new tasks
-
-	// for stateFetcher
+	headerCh      chan dataPack        // Channel receiving inbound block headers
+	bodyCh        chan dataPack        // Channel receiving inbound block bodies
+	receiptCh     chan dataPack        // Channel receiving inbound receipts
+	bodyWakeCh    chan bool            // Channel to signal the block body fetcher of new tasks
+	receiptWakeCh chan bool            // Channel to signal the receipt fetcher of new tasks
+	headerProcCh  chan []*types.Header // Channel to feed the header processor new tasks
+
+	// State sync
+	pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root
+	pivotLock   sync.RWMutex  // Lock protecting pivot header reads from updates
+
+	snapSync       bool         // Whether to run state sync over the snap protocol
+	SnapSyncer     *snap.Syncer // TODO(karalabe): make private! hack for now
 	stateSyncStart chan *stateSync
 	trackStateReq  chan *stateReq
-	stateCh        chan dataPack // [eth/63] Channel receiving inbound node state data
+	stateCh        chan dataPack // Channel receiving inbound node state data
 
 	// Cancellation and termination
 	cancelPeer string         // Identifier of the peer currently being used as the master (cancel on drop)
@@ -150,7 +158,7 @@ type Downloader struct {
 	cancelWg   sync.WaitGroup // Make sure all fetcher goroutines have exited.
 
 	quitCh   chan struct{} // Quit channel to signal termination
-	quitLock sync.RWMutex  // Lock to prevent double closes
+	quitLock sync.Mutex    // Lock to prevent double closes
 
 	// Testing hooks
 	syncInitHook     func(uint64, uint64)  // Method to call upon initiating a new sync run
@@ -176,8 +184,8 @@ type LightChain interface {
 	// InsertHeaderChain inserts a batch of headers into the local chain.
 	InsertHeaderChain([]*types.Header, int) (int, error)
 
-	// Rollback removes a few recently added elements from the local chain.
-	Rollback([]common.Hash)
+	// SetHead rewinds the local chain to a new head.
+	SetHead(uint64) error
 }
 
 // BlockChain encapsulates functions required to sync a (full or fast) blockchain.
@@ -207,6 +215,9 @@ type BlockChain interface {
 
 	// InsertReceiptChain inserts a batch of receipts into the local chain.
 	InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error)
+
+	// Snapshots returns the blockchain snapshot tree to paused it during sync.
+	Snapshots() *snapshot.Tree
 }
 
 // New creates a new downloader to fetch hashes and blocks from remote peers.
@@ -219,7 +230,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom,
 		stateBloom:     stateBloom,
 		mux:            mux,
 		checkpoint:     checkpoint,
-		queue:          newQueue(),
+		queue:          newQueue(blockCacheMaxItems, blockCacheInitialItems),
 		peers:          newPeerSet(),
 		rttEstimate:    uint64(rttMaxEstimate),
 		rttConfidence:  uint64(1000000),
@@ -234,6 +245,7 @@ func New(checkpoint uint64, stateDb ethdb.Database, stateBloom *trie.SyncBloom,
 		headerProcCh:   make(chan []*types.Header, 1),
 		quitCh:         make(chan struct{}),
 		stateCh:        make(chan dataPack),
+		SnapSyncer:     snap.NewSyncer(stateDb),
 		stateSyncStart: make(chan *stateSync),
 		syncStatsState: stateSyncStats{
 			processed: rawdb.ReadFastTrieProgress(stateDb),
@@ -258,15 +270,16 @@ func (d *Downloader) Progress() ethereum.SyncProgress {
 	defer d.syncStatsLock.RUnlock()
 
 	current := uint64(0)
+	mode := d.getMode()
 	switch {
-	case d.blockchain != nil && d.mode == FullSync:
+	case d.blockchain != nil && mode == FullSync:
 		current = d.blockchain.CurrentBlock().NumberU64()
-	case d.blockchain != nil && d.mode == FastSync:
+	case d.blockchain != nil && mode == FastSync:
 		current = d.blockchain.CurrentFastBlock().NumberU64()
 	case d.lightchain != nil:
 		current = d.lightchain.CurrentHeader().Number.Uint64()
 	default:
-		log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", d.mode)
+		log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode)
 	}
 	return ethereum.SyncProgress{
 		StartingBlock: d.syncStatsChainOrigin,
@@ -284,8 +297,14 @@ func (d *Downloader) Synchronising() bool {
 
 // RegisterPeer injects a new download peer into the set of block source to be
 // used for fetching hashes and blocks from.
-func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error {
-	logger := log.New("peer", id)
+func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error {
+	var logger log.Logger
+	if len(id) < 16 {
+		// Tests use short IDs, don't choke on them
+		logger = log.New("peer", id)
+	} else {
+		logger = log.New("peer", id[:8])
+	}
 	logger.Trace("Registering sync peer")
 	if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil {
 		logger.Error("Failed to register sync peer", "err", err)
@@ -297,7 +316,7 @@ func (d *Downloader) RegisterPeer(id string, version int, peer Peer) error {
 }
 
 // RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer.
-func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) error {
+func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error {
 	return d.RegisterPeer(id, version, &lightPeerWrapper{peer})
 }
 
@@ -306,7 +325,13 @@ func (d *Downloader) RegisterLightPeer(id string, version int, peer LightPeer) e
 // the queue.
 func (d *Downloader) UnregisterPeer(id string) error {
 	// Unregister the peer from the active peer set and revoke any fetch tasks
-	logger := log.New("peer", id)
+	var logger log.Logger
+	if len(id) < 16 {
+		// Tests use short IDs, don't choke on them
+		logger = log.New("peer", id)
+	} else {
+		logger = log.New("peer", id[:8])
+	}
 	logger.Trace("Unregistering sync peer")
 	if err := d.peers.Unregister(id); err != nil {
 		logger.Error("Failed to unregister sync peer", "err", err)
@@ -321,13 +346,14 @@ func (d *Downloader) UnregisterPeer(id string) error {
 // adding various sanity checks as well as wrapping it with various log entries.
 func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode SyncMode) error {
 	err := d.synchronise(id, head, td, mode)
-	switch err {
-	case nil:
-	case errBusy, errCanceled:
 
-	case errTimeout, errBadPeer, errStallingPeer, errUnsyncedPeer,
-		errEmptyHeaderSet, errPeersUnavailable, errTooOld,
-		errInvalidAncestor, errInvalidChain:
+	switch err {
+	case nil, errBusy, errCanceled:
+		return err
+	}
+	if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) ||
+		errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) ||
+		errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) {
 		log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err)
 		if d.dropPeer == nil {
 			// The dropPeer method is nil when `--copydb` is used for a local copy.
@@ -336,9 +362,9 @@ func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode
 		} else {
 			d.dropPeer(id)
 		}
-	default:
-		log.Warn("Synchronisation failed, retrying", "err", err)
+		return err
 	}
+	log.Warn("Synchronisation failed, retrying", "err", err)
 	return err
 }
 
@@ -361,13 +387,29 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
 		log.Info("Block synchronisation started")
 	}
 	// If we are already full syncing, but have a fast-sync bloom filter laying
-	// around, make sure it does't use memory any more. This is a special case
+	// around, make sure it doesn't use memory any more. This is a special case
 	// when the user attempts to fast sync a new empty network.
 	if mode == FullSync && d.stateBloom != nil {
 		d.stateBloom.Close()
 	}
+	// If snap sync was requested, create the snap scheduler and switch to fast
+	// sync mode. Long term we could drop fast sync or merge the two together,
+	// but until snap becomes prevalent, we should support both. TODO(karalabe).
+	if mode == SnapSync {
+		if !d.snapSync {
+			// Snap sync uses the snapshot namespace to store potentially flakey data until
+			// sync completely heals and finishes. Pause snapshot maintenance in the mean
+			// time to prevent access.
+			if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests
+				snapshots.Disable()
+			}
+			log.Warn("Enabling snapshot sync prototype")
+			d.snapSync = true
+		}
+		mode = FastSync
+	}
 	// Reset the queue, peer set and wake channels to clean any internal leftover state
-	d.queue.Reset()
+	d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems)
 	d.peers.Reset()
 
 	for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh} {
@@ -400,8 +442,8 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
 
 	defer d.Cancel() // No matter what, we can't leave the cancel channel open
 
-	// Set the requested sync mode, unless it's forbidden
-	d.mode = mode
+	// Atomically set the requested sync mode
+	atomic.StoreUint32(&d.mode, uint32(mode))
 
 	// Retrieve the origin peer and initiate the downloading process
 	p := d.peers.Peer(id)
@@ -411,6 +453,10 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode
 	return d.syncWithPeer(p, hash, td)
 }
 
+func (d *Downloader) getMode() SyncMode {
+	return SyncMode(atomic.LoadUint32(&d.mode))
+}
+
 // syncWithPeer starts a block synchronization based on the hash chain from the
 // specified peer and head hash.
 func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.Int) (err error) {
@@ -424,20 +470,28 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
 			d.mux.Post(DoneEvent{latest})
 		}
 	}()
-	if p.version < 62 {
-		return errTooOld
+	if p.version < eth.ETH65 {
+		return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, eth.ETH65)
 	}
+	mode := d.getMode()
 
-	log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", d.mode)
+	log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode)
 	defer func(start time.Time) {
 		log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start)))
 	}(time.Now())
 
 	// Look up the sync boundaries: the common ancestor and the target block
-	latest, err := d.fetchHeight(p)
+	latest, pivot, err := d.fetchHead(p)
 	if err != nil {
 		return err
 	}
+	if mode == FastSync && pivot == nil {
+		// If no pivot block was returned, the head is below the min full block
+		// threshold (i.e. new chian). In that case we won't really fast sync
+		// anyway, but still need a valid pivot block to avoid some code hitting
+		// nil panics on an access.
+		pivot = d.blockchain.CurrentBlock().Header()
+	}
 	height := latest.Number.Uint64()
 
 	origin, err := d.findAncestor(p, latest)
@@ -452,22 +506,24 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
 	d.syncStatsLock.Unlock()
 
 	// Ensure our origin point is below any fast sync pivot point
-	pivot := uint64(0)
-	if d.mode == FastSync {
+	if mode == FastSync {
 		if height <= uint64(fsMinFullBlocks) {
 			origin = 0
 		} else {
-			pivot = height - uint64(fsMinFullBlocks)
-			if pivot <= origin {
-				origin = pivot - 1
+			pivotNumber := pivot.Number.Uint64()
+			if pivotNumber <= origin {
+				origin = pivotNumber - 1
 			}
+			// Write out the pivot into the database so a rollback beyond it will
+			// reenable fast sync
+			rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber)
 		}
 	}
 	d.committed = 1
-	if d.mode == FastSync && pivot != 0 {
+	if mode == FastSync && pivot.Number.Uint64() != 0 {
 		d.committed = 0
 	}
-	if d.mode == FastSync {
+	if mode == FastSync {
 		// Set the ancient data limitation.
 		// If we are running fast sync, all block data older than ancientLimit will be
 		// written to the ancient store. More recent data will be written to the active
@@ -482,12 +538,15 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
 		// The peer would start to feed us valid blocks until head, resulting in all of
 		// the blocks might be written into the ancient store. A following mini-reorg
 		// could cause issues.
-		if d.checkpoint != 0 && d.checkpoint > maxForkAncestry+1 {
+		if d.checkpoint != 0 && d.checkpoint > fullMaxForkAncestry+1 {
 			d.ancientLimit = d.checkpoint
-		} else if height > maxForkAncestry+1 {
-			d.ancientLimit = height - maxForkAncestry - 1
+		} else if height > fullMaxForkAncestry+1 {
+			d.ancientLimit = height - fullMaxForkAncestry - 1
+		} else {
+			d.ancientLimit = 0
 		}
 		frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here.
+
 		// If a part of blockchain data has already been written into active store,
 		// disable the ancient style insertion explicitly.
 		if origin >= frozen && frozen != 0 {
@@ -498,27 +557,29 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I
 		}
 		// Rewind the ancient store and blockchain if reorg happens.
 		if origin+1 < frozen {
-			var hashes []common.Hash
-			for i := origin + 1; i < d.lightchain.CurrentHeader().Number.Uint64(); i++ {
-				hashes = append(hashes, rawdb.ReadCanonicalHash(d.stateDB, i))
+			if err := d.lightchain.SetHead(origin + 1); err != nil {
+				return err
 			}
-			d.lightchain.Rollback(hashes)
 		}
 	}
 	// Initiate the sync using a concurrent header and content retrieval algorithm
-	d.queue.Prepare(origin+1, d.mode)
+	d.queue.Prepare(origin+1, mode)
 	if d.syncInitHook != nil {
 		d.syncInitHook(origin, height)
 	}
 	fetchers := []func() error{
-		func() error { return d.fetchHeaders(p, origin+1, pivot) }, // Headers are always retrieved
-		func() error { return d.fetchBodies(origin + 1) },          // Bodies are retrieved during normal and fast sync
-		func() error { return d.fetchReceipts(origin + 1) },        // Receipts are retrieved during fast sync
-		func() error { return d.processHeaders(origin+1, pivot, td) },
-	}
-	if d.mode == FastSync {
-		fetchers = append(fetchers, func() error { return d.processFastSyncContent(latest) })
-	} else if d.mode == FullSync {
+		func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved
+		func() error { return d.fetchBodies(origin + 1) },   // Bodies are retrieved during normal and fast sync
+		func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync
+		func() error { return d.processHeaders(origin+1, td) },
+	}
+	if mode == FastSync {
+		d.pivotLock.Lock()
+		d.pivotHeader = pivot
+		d.pivotLock.Unlock()
+
+		fetchers = append(fetchers, func() error { return d.processFastSyncContent() })
+	} else if mode == FullSync {
 		fetchers = append(fetchers, d.processFullSyncContent)
 	}
 	return d.spawnSync(fetchers)
@@ -557,6 +618,8 @@ func (d *Downloader) spawnSync(fetchers []func() error) error {
 func (d *Downloader) cancel() {
 	// Close the current cancel channel
 	d.cancelLock.Lock()
+	defer d.cancelLock.Unlock()
+
 	if d.cancelCh != nil {
 		select {
 		case <-d.cancelCh:
@@ -565,7 +628,6 @@ func (d *Downloader) cancel() {
 			close(d.cancelCh)
 		}
 	}
-	d.cancelLock.Unlock()
 }
 
 // Cancel aborts all of the operations and waits for all download goroutines to
@@ -573,9 +635,6 @@ func (d *Downloader) cancel() {
 func (d *Downloader) Cancel() {
 	d.cancel()
 	d.cancelWg.Wait()
-
-	d.ancientLimit = 0
-	log.Debug("Reset ancient limit to zero")
 }
 
 // Terminate interrupts the downloader, canceling all pending operations.
@@ -588,27 +647,35 @@ func (d *Downloader) Terminate() {
 	default:
 		close(d.quitCh)
 	}
+	if d.stateBloom != nil {
+		d.stateBloom.Close()
+	}
 	d.quitLock.Unlock()
 
 	// Cancel any pending download requests
 	d.Cancel()
 }
 
-// fetchHeight retrieves the head header of the remote peer to aid in estimating
-// the total time a pending synchronisation would take.
-func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) {
-	p.log.Debug("Retrieving remote chain height")
+// fetchHead retrieves the head header and prior pivot block (if available) from
+// a remote peer.
+func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) {
+	p.log.Debug("Retrieving remote chain head")
+	mode := d.getMode()
 
 	// Request the advertised remote head block and wait for the response
-	head, _ := p.peer.Head()
-	go p.peer.RequestHeadersByHash(head, 1, 0, false)
+	latest, _ := p.peer.Head()
+	fetch := 1
+	if mode == FastSync {
+		fetch = 2 // head + pivot headers
+	}
+	go p.peer.RequestHeadersByHash(latest, fetch, fsMinFullBlocks-1, true)
 
 	ttl := d.requestTTL()
 	timeout := time.After(ttl)
 	for {
 		select {
 		case <-d.cancelCh:
-			return nil, errCanceled
+			return nil, nil, errCanceled
 
 		case packet := <-d.headerCh:
 			// Discard anything not from the origin peer
@@ -616,23 +683,36 @@ func (d *Downloader) fetchHeight(p *peerConnection) (*types.Header, error) {
 				log.Debug("Received headers from incorrect peer", "peer", packet.PeerId())
 				break
 			}
-			// Make sure the peer actually gave something valid
+			// Make sure the peer gave us at least one and at most the requested headers
 			headers := packet.(*headerPack).headers
-			if len(headers) != 1 {
-				p.log.Debug("Multiple headers for single request", "headers", len(headers))
-				return nil, errBadPeer
+			if len(headers) == 0 || len(headers) > fetch {
+				return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch)
 			}
+			// The first header needs to be the head, validate against the checkpoint
+			// and request. If only 1 header was returned, make sure there's no pivot
+			// or there was not one requested.
 			head := headers[0]
-			if (d.mode == FastSync || d.mode == LightSync) && head.Number.Uint64() < d.checkpoint {
-				p.log.Warn("Remote head below checkpoint", "number", head.Number, "hash", head.Hash())
-				return nil, errUnsyncedPeer
+			if (mode == FastSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint {
+				return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint)
+			}
+			if len(headers) == 1 {
+				if mode == FastSync && head.Number.Uint64() > uint64(fsMinFullBlocks) {
+					return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer)
+				}
+				p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", head.Hash())
+				return head, nil, nil
+			}
+			// At this point we have 2 headers in total and the first is the
+			// validated head of the chian. Check the pivot number and return,
+			pivot := headers[1]
+			if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) {
+				return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks))
 			}
-			p.log.Debug("Remote head header identified", "number", head.Number, "hash", head.Hash())
-			return head, nil
+			return head, pivot, nil
 
 		case <-timeout:
 			p.log.Debug("Waiting for head header timed out", "elapsed", ttl)
-			return nil, errTimeout
+			return nil, nil, errTimeout
 
 		case <-d.bodyCh:
 		case <-d.receiptCh:
@@ -664,7 +744,7 @@ func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, ui
 		requestHead = 0
 	}
 	// requestBottom is the lowest block we want included in the query
-	// Ideally, we want to include just below own head
+	// Ideally, we want to include the one just below our own head
 	requestBottom := int(localHeight - 1)
 	if requestBottom < 0 {
 		requestBottom = 0
@@ -705,7 +785,8 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 		localHeight  uint64
 		remoteHeight = remoteHeader.Number.Uint64()
 	)
-	switch d.mode {
+	mode := d.getMode()
+	switch mode {
 	case FullSync:
 		localHeight = d.blockchain.CurrentBlock().NumberU64()
 	case FastSync:
@@ -716,14 +797,18 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 	p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight)
 
 	// Recap floor value for binary search
+	maxForkAncestry := fullMaxForkAncestry
+	if d.getMode() == LightSync {
+		maxForkAncestry = lightMaxForkAncestry
+	}
 	if localHeight >= maxForkAncestry {
 		// We're above the max reorg threshold, find the earliest fork point
 		floor = int64(localHeight - maxForkAncestry)
 	}
 	// If we're doing a light sync, ensure the floor doesn't go below the CHT, as
 	// all headers before that point will be missing.
-	if d.mode == LightSync {
-		// If we dont know the current CHT position, find it
+	if mode == LightSync {
+		// If we don't know the current CHT position, find it
 		if d.genesis == 0 {
 			header := d.lightchain.CurrentHeader()
 			for header != nil {
@@ -740,6 +825,26 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 		}
 	}
 
+	ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor)
+	if err == nil {
+		return ancestor, nil
+	}
+	// The returned error was not nil.
+	// If the error returned does not reflect that a common ancestor was not found, return it.
+	// If the error reflects that a common ancestor was not found, continue to binary search,
+	// where the error value will be reassigned.
+	if !errors.Is(err, errNoAncestorFound) {
+		return 0, err
+	}
+
+	ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor)
+	if err != nil {
+		return 0, err
+	}
+	return ancestor, nil
+}
+
+func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (commonAncestor uint64, err error) {
 	from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight)
 
 	p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip)
@@ -773,7 +878,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 				expectNumber := from + int64(i)*int64(skip+1)
 				if number := header.Number.Int64(); number != expectNumber {
 					p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number)
-					return 0, errInvalidChain
+					return 0, fmt.Errorf("%w: %v", errInvalidChain, errors.New("head headers broke chain ordering"))
 				}
 			}
 			// Check if a common ancestor was found
@@ -788,7 +893,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 				n := headers[i].Number.Uint64()
 
 				var known bool
-				switch d.mode {
+				switch mode {
 				case FullSync:
 					known = d.blockchain.HasBlock(h, n)
 				case FastSync:
@@ -820,6 +925,12 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 		p.log.Debug("Found common ancestor", "number", number, "hash", hash)
 		return number, nil
 	}
+	return 0, errNoAncestorFound
+}
+
+func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (commonAncestor uint64, err error) {
+	hash := common.Hash{}
+
 	// Ancestor not found, we need to binary search over our chain
 	start, end := uint64(0), remoteHeight
 	if floor > 0 {
@@ -842,17 +953,17 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 			case <-d.cancelCh:
 				return 0, errCanceled
 
-			case packer := <-d.headerCh:
+			case packet := <-d.headerCh:
 				// Discard anything not from the origin peer
-				if packer.PeerId() != p.id {
-					log.Debug("Received headers from incorrect peer", "peer", packer.PeerId())
+				if packet.PeerId() != p.id {
+					log.Debug("Received headers from incorrect peer", "peer", packet.PeerId())
 					break
 				}
 				// Make sure the peer actually gave something valid
-				headers := packer.(*headerPack).headers
+				headers := packet.(*headerPack).headers
 				if len(headers) != 1 {
-					p.log.Debug("Multiple headers for single request", "headers", len(headers))
-					return 0, errBadPeer
+					p.log.Warn("Multiple headers for single request", "headers", len(headers))
+					return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers))
 				}
 				arrived = true
 
@@ -861,7 +972,7 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 				n := headers[0].Number.Uint64()
 
 				var known bool
-				switch d.mode {
+				switch mode {
 				case FullSync:
 					known = d.blockchain.HasBlock(h, n)
 				case FastSync:
@@ -875,8 +986,8 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 				}
 				header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists
 				if header.Number.Uint64() != check {
-					p.log.Debug("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check)
-					return 0, errBadPeer
+					p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check)
+					return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number)
 				}
 				start = check
 				hash = h
@@ -908,12 +1019,13 @@ func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header)
 // other peers are only accepted if they map cleanly to the skeleton. If no one
 // can fill in the skeleton - not even the origin peer - it's assumed invalid and
 // the origin is dropped.
-func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) error {
+func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error {
 	p.log.Debug("Directing header downloads", "origin", from)
 	defer p.log.Debug("Header download terminated")
 
 	// Create a timeout timer, and the associated header fetcher
 	skeleton := true            // Skeleton assembly phase or finishing up
+	pivoting := false           // Whether the next request is pivot verification
 	request := time.Now()       // time of the last skeleton fetch request
 	timeout := time.NewTimer(0) // timer to dump a non-responsive active peer
 	<-timeout.C                 // timeout channel should be initially empty
@@ -934,10 +1046,25 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64)
 			go p.peer.RequestHeadersByNumber(from, MaxHeaderFetch, 0, false)
 		}
 	}
+	getNextPivot := func() {
+		pivoting = true
+		request = time.Now()
+
+		ttl = d.requestTTL()
+		timeout.Reset(ttl)
+
+		d.pivotLock.RLock()
+		pivot := d.pivotHeader.Number.Uint64()
+		d.pivotLock.RUnlock()
+
+		p.log.Trace("Fetching next pivot header", "number", pivot+uint64(fsMinFullBlocks))
+		go p.peer.RequestHeadersByNumber(pivot+uint64(fsMinFullBlocks), 2, fsMinFullBlocks-9, false) // move +64 when it's 2x64-8 deep
+	}
 	// Start pulling the header chain skeleton until all is done
 	ancestor := from
 	getHeaders(from)
 
+	mode := d.getMode()
 	for {
 		select {
 		case <-d.cancelCh:
@@ -952,8 +1079,46 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64)
 			headerReqTimer.UpdateSince(request)
 			timeout.Stop()
 
+			// If the pivot is being checked, move if it became stale and run the real retrieval
+			var pivot uint64
+
+			d.pivotLock.RLock()
+			if d.pivotHeader != nil {
+				pivot = d.pivotHeader.Number.Uint64()
+			}
+			d.pivotLock.RUnlock()
+
+			if pivoting {
+				if packet.Items() == 2 {
+					// Retrieve the headers and do some sanity checks, just in case
+					headers := packet.(*headerPack).headers
+
+					if have, want := headers[0].Number.Uint64(), pivot+uint64(fsMinFullBlocks); have != want {
+						log.Warn("Peer sent invalid next pivot", "have", have, "want", want)
+						return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want)
+					}
+					if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want {
+						log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want)
+						return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want)
+					}
+					log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number)
+					pivot = headers[0].Number.Uint64()
+
+					d.pivotLock.Lock()
+					d.pivotHeader = headers[0]
+					d.pivotLock.Unlock()
+
+					// Write out the pivot into the database so a rollback beyond
+					// it will reenable fast sync and update the state root that
+					// the state syncer will be downloading.
+					rawdb.WriteLastPivotNumber(d.stateDB, pivot)
+				}
+				pivoting = false
+				getHeaders(from)
+				continue
+			}
 			// If the skeleton's finished, pull any remaining head headers directly from the origin
-			if packet.Items() == 0 && skeleton {
+			if skeleton && packet.Items() == 0 {
 				skeleton = false
 				getHeaders(from)
 				continue
@@ -987,7 +1152,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64)
 				filled, proced, err := d.fillHeaderSkeleton(from, headers)
 				if err != nil {
 					p.log.Debug("Skeleton chain invalid", "err", err)
-					return errInvalidChain
+					return fmt.Errorf("%w: %v", errInvalidChain, err)
 				}
 				headers = filled[proced:]
 				from += uint64(proced)
@@ -998,7 +1163,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64)
 				if n := len(headers); n > 0 {
 					// Retrieve the current head we're at
 					var head uint64
-					if d.mode == LightSync {
+					if mode == LightSync {
 						head = d.lightchain.CurrentHeader().Number.Uint64()
 					} else {
 						head = d.blockchain.CurrentFastBlock().NumberU64()
@@ -1031,7 +1196,14 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64)
 					return errCanceled
 				}
 				from += uint64(len(headers))
-				getHeaders(from)
+
+				// If we're still skeleton filling fast sync, check pivot staleness
+				// before continuing to the next skeleton filling
+				if skeleton && pivot > 0 {
+					getNextPivot()
+				} else {
+					getHeaders(from)
+				}
 			} else {
 				// No headers delivered, or all of them being delayed, sleep a bit and retry
 				p.log.Trace("All headers delayed, waiting")
@@ -1067,7 +1239,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64)
 			case d.headerProcCh <- nil:
 			case <-d.cancelCh:
 			}
-			return errBadPeer
+			return fmt.Errorf("%w: header request timed out", errBadPeer)
 		}
 	}
 }
@@ -1090,17 +1262,18 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) (
 			pack := packet.(*headerPack)
 			return d.queue.DeliverHeaders(pack.peerID, pack.headers, d.headerProcCh)
 		}
-		expire   = func() map[string]int { return d.queue.ExpireHeaders(d.requestTTL()) }
-		throttle = func() bool { return false }
-		reserve  = func(p *peerConnection, count int) (*fetchRequest, bool, error) {
-			return d.queue.ReserveHeaders(p, count), false, nil
+		expire  = func() map[string]int { return d.queue.ExpireHeaders(d.requestTTL()) }
+		reserve = func(p *peerConnection, count int) (*fetchRequest, bool, bool) {
+			return d.queue.ReserveHeaders(p, count), false, false
 		}
 		fetch    = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) }
 		capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.requestRTT()) }
-		setIdle  = func(p *peerConnection, accepted int) { p.SetHeadersIdle(accepted) }
+		setIdle  = func(p *peerConnection, accepted int, deliveryTime time.Time) {
+			p.SetHeadersIdle(accepted, deliveryTime)
+		}
 	)
 	err := d.fetchParts(d.headerCh, deliver, d.queue.headerContCh, expire,
-		d.queue.PendingHeaders, d.queue.InFlightHeaders, throttle, reserve,
+		d.queue.PendingHeaders, d.queue.InFlightHeaders, reserve,
 		nil, fetch, d.queue.CancelHeaders, capacity, d.peers.HeaderIdlePeers, setIdle, "headers")
 
 	log.Debug("Skeleton fill terminated", "err", err)
@@ -1123,10 +1296,10 @@ func (d *Downloader) fetchBodies(from uint64) error {
 		expire   = func() map[string]int { return d.queue.ExpireBodies(d.requestTTL()) }
 		fetch    = func(p *peerConnection, req *fetchRequest) error { return p.FetchBodies(req) }
 		capacity = func(p *peerConnection) int { return p.BlockCapacity(d.requestRTT()) }
-		setIdle  = func(p *peerConnection, accepted int) { p.SetBodiesIdle(accepted) }
+		setIdle  = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) }
 	)
 	err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire,
-		d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ShouldThrottleBlocks, d.queue.ReserveBodies,
+		d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ReserveBodies,
 		d.bodyFetchHook, fetch, d.queue.CancelBodies, capacity, d.peers.BodyIdlePeers, setIdle, "bodies")
 
 	log.Debug("Block body download terminated", "err", err)
@@ -1147,10 +1320,12 @@ func (d *Downloader) fetchReceipts(from uint64) error {
 		expire   = func() map[string]int { return d.queue.ExpireReceipts(d.requestTTL()) }
 		fetch    = func(p *peerConnection, req *fetchRequest) error { return p.FetchReceipts(req) }
 		capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.requestRTT()) }
-		setIdle  = func(p *peerConnection, accepted int) { p.SetReceiptsIdle(accepted) }
+		setIdle  = func(p *peerConnection, accepted int, deliveryTime time.Time) {
+			p.SetReceiptsIdle(accepted, deliveryTime)
+		}
 	)
 	err := d.fetchParts(d.receiptCh, deliver, d.receiptWakeCh, expire,
-		d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ShouldThrottleReceipts, d.queue.ReserveReceipts,
+		d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ReserveReceipts,
 		d.receiptFetchHook, fetch, d.queue.CancelReceipts, capacity, d.peers.ReceiptIdlePeers, setIdle, "receipts")
 
 	log.Debug("Transaction receipt download terminated", "err", err)
@@ -1181,11 +1356,11 @@ func (d *Downloader) fetchReceipts(from uint64) error {
 //  - capacity:    network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping)
 //  - idle:        network callback to retrieve the currently (type specific) idle peers that can be assigned tasks
 //  - setIdle:     network callback to set a peer back to idle and update its estimated capacity (traffic shaping)
-//  - kind:        textual label of the type being downloaded to display in log mesages
+//  - kind:        textual label of the type being downloaded to display in log messages
 func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool,
-	expire func() map[string]int, pending func() int, inFlight func() bool, throttle func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, error),
+	expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool),
 	fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int,
-	idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int), kind string) error {
+	idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int, time.Time), kind string) error {
 
 	// Create a ticker to detect expired retrieval tasks
 	ticker := time.NewTicker(100 * time.Millisecond)
@@ -1201,19 +1376,20 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
 			return errCanceled
 
 		case packet := <-deliveryCh:
+			deliveryTime := time.Now()
 			// If the peer was previously banned and failed to deliver its pack
 			// in a reasonable time frame, ignore its message.
 			if peer := d.peers.Peer(packet.PeerId()); peer != nil {
 				// Deliver the received chunk of data and check chain validity
 				accepted, err := deliver(packet)
-				if err == errInvalidChain {
+				if errors.Is(err, errInvalidChain) {
 					return err
 				}
 				// Unless a peer delivered something completely else than requested (usually
 				// caused by a timed out request which came through in the end), set it to
 				// idle. If the delivery's stale, the peer should have already been idled.
-				if err != errStaleDelivery {
-					setIdle(peer, accepted)
+				if !errors.Is(err, errStaleDelivery) {
+					setIdle(peer, accepted, deliveryTime)
 				}
 				// Issue a log to the user to see what's going on
 				switch {
@@ -1222,7 +1398,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
 				case err == nil:
 					peer.log.Trace("Delivered new batch of data", "type", kind, "count", packet.Stats())
 				default:
-					peer.log.Trace("Failed to deliver retrieved data", "type", kind, "err", err)
+					peer.log.Debug("Failed to deliver retrieved data", "type", kind, "err", err)
 				}
 			}
 			// Blocks assembled, try to update the progress
@@ -1266,7 +1442,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
 					// how response times reacts, to it always requests one more than the minimum (i.e. min 2).
 					if fails > 2 {
 						peer.log.Trace("Data delivery timed out", "type", kind)
-						setIdle(peer, 0)
+						setIdle(peer, 0, time.Now())
 					} else {
 						peer.log.Debug("Stalling delivery, dropping", "type", kind)
 
@@ -1301,27 +1477,27 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
 			// Send a download request to all idle peers, until throttled
 			progressed, throttled, running := false, false, inFlight()
 			idles, total := idle()
-
+			pendCount := pending()
 			for _, peer := range idles {
 				// Short circuit if throttling activated
-				if throttle() {
-					throttled = true
+				if throttled {
 					break
 				}
 				// Short circuit if there is no more available task.
-				if pending() == 0 {
+				if pendCount = pending(); pendCount == 0 {
 					break
 				}
 				// Reserve a chunk of fetches for a peer. A nil can mean either that
 				// no more headers are available, or that the peer is known not to
 				// have them.
-				request, progress, err := reserve(peer, capacity(peer))
-				if err != nil {
-					return err
-				}
+				request, progress, throttle := reserve(peer, capacity(peer))
 				if progress {
 					progressed = true
 				}
+				if throttle {
+					throttled = true
+					throttleCounter.Inc(1)
+				}
 				if request == nil {
 					continue
 				}
@@ -1346,7 +1522,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
 			}
 			// Make sure that we have peers available for fetching. If all peers have been tried
 			// and all failed throw an error
-			if !progressed && !throttled && !running && len(idles) == total && pending() > 0 {
+			if !progressed && !throttled && !running && len(idles) == total && pendCount > 0 {
 				return errPeersUnavailable
 			}
 		}
@@ -1356,40 +1532,42 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
 // processHeaders takes batches of retrieved headers from an input channel and
 // keeps processing and scheduling them into the header chain and downloader's
 // queue until the stream ends or a failure occurs.
-func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) error {
+func (d *Downloader) processHeaders(origin uint64, td *big.Int) error {
 	// Keep a count of uncertain headers to roll back
-	var rollback []*types.Header
+	var (
+		rollback    uint64 // Zero means no rollback (fine as you can't unroll the genesis)
+		rollbackErr error
+		mode        = d.getMode()
+	)
 	defer func() {
-		if len(rollback) > 0 {
-			// Flatten the headers and roll them back
-			hashes := make([]common.Hash, len(rollback))
-			for i, header := range rollback {
-				hashes[i] = header.Hash()
-			}
+		if rollback > 0 {
 			lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0
-			if d.mode != LightSync {
+			if mode != LightSync {
 				lastFastBlock = d.blockchain.CurrentFastBlock().Number()
 				lastBlock = d.blockchain.CurrentBlock().Number()
 			}
-			d.lightchain.Rollback(hashes)
+			if err := d.lightchain.SetHead(rollback - 1); err != nil { // -1 to target the parent of the first uncertain block
+				// We're already unwinding the stack, only print the error to make it more visible
+				log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err)
+			}
 			curFastBlock, curBlock := common.Big0, common.Big0
-			if d.mode != LightSync {
+			if mode != LightSync {
 				curFastBlock = d.blockchain.CurrentFastBlock().Number()
 				curBlock = d.blockchain.CurrentBlock().Number()
 			}
-			log.Warn("Rolled back headers", "count", len(hashes),
+			log.Warn("Rolled back chain segment",
 				"header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number),
 				"fast", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock),
-				"block", fmt.Sprintf("%d->%d", lastBlock, curBlock))
+				"block", fmt.Sprintf("%d->%d", lastBlock, curBlock), "reason", rollbackErr)
 		}
 	}()
-
 	// Wait for batches of headers to process
 	gotHeaders := false
 
 	for {
 		select {
 		case <-d.cancelCh:
+			rollbackErr = errCanceled
 			return errCanceled
 
 		case headers := <-d.headerProcCh:
@@ -1414,7 +1592,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
 				// L: Sync begins, and finds common ancestor at 11
 				// L: Request new headers up from 11 (R's TD was higher, it must have something)
 				// R: Nothing to give
-				if d.mode != LightSync {
+				if mode != LightSync {
 					head := d.blockchain.CurrentBlock()
 					if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.NumberU64())) > 0 {
 						return errStallingPeer
@@ -1427,14 +1605,14 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
 				// This check cannot be executed "as is" for full imports, since blocks may still be
 				// queued for processing when the header download completes. However, as long as the
 				// peer gave us something useful, we're already happy/progressed (above check).
-				if d.mode == FastSync || d.mode == LightSync {
+				if mode == FastSync || mode == LightSync {
 					head := d.lightchain.CurrentHeader()
 					if td.Cmp(d.lightchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 {
 						return errStallingPeer
 					}
 				}
 				// Disable any rollback and return
-				rollback = nil
+				rollback = 0
 				return nil
 			}
 			// Otherwise split the chunk of headers into batches and process them
@@ -1443,6 +1621,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
 				// Terminate if something failed in between processing chunks
 				select {
 				case <-d.cancelCh:
+					rollbackErr = errCanceled
 					return errCanceled
 				default:
 				}
@@ -1452,40 +1631,49 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
 					limit = len(headers)
 				}
 				chunk := headers[:limit]
+
 				// In case of header only syncing, validate the chunk immediately
-				if d.mode == FastSync || d.mode == LightSync {
-					// Collect the yet unknown headers to mark them as uncertain
-					unknown := make([]*types.Header, 0, len(chunk))
-					for _, header := range chunk {
-						if !d.lightchain.HasHeader(header.Hash(), header.Number.Uint64()) {
-							unknown = append(unknown, header)
-						}
-					}
+				if mode == FastSync || mode == LightSync {
 					// If we're importing pure headers, verify based on their recentness
+					var pivot uint64
+
+					d.pivotLock.RLock()
+					if d.pivotHeader != nil {
+						pivot = d.pivotHeader.Number.Uint64()
+					}
+					d.pivotLock.RUnlock()
+
 					frequency := fsHeaderCheckFrequency
 					if chunk[len(chunk)-1].Number.Uint64()+uint64(fsHeaderForceVerify) > pivot {
 						frequency = 1
 					}
 					if n, err := d.lightchain.InsertHeaderChain(chunk, frequency); err != nil {
-						// If some headers were inserted, add them too to the rollback list
-						if n > 0 {
-							rollback = append(rollback, chunk[:n]...)
+						rollbackErr = err
+
+						// If some headers were inserted, track them as uncertain
+						if (mode == FastSync || frequency > 1) && n > 0 && rollback == 0 {
+							rollback = chunk[0].Number.Uint64()
 						}
-						log.Debug("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "err", err)
-						return errInvalidChain
+						log.Warn("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "parent", chunk[n].ParentHash, "err", err)
+						return fmt.Errorf("%w: %v", errInvalidChain, err)
 					}
-					// All verifications passed, store newly found uncertain headers
-					rollback = append(rollback, unknown...)
-					if len(rollback) > fsHeaderSafetyNet {
-						rollback = append(rollback[:0], rollback[len(rollback)-fsHeaderSafetyNet:]...)
+					// All verifications passed, track all headers within the alloted limits
+					if mode == FastSync {
+						head := chunk[len(chunk)-1].Number.Uint64()
+						if head-rollback > uint64(fsHeaderSafetyNet) {
+							rollback = head - uint64(fsHeaderSafetyNet)
+						} else {
+							rollback = 1
+						}
 					}
 				}
 				// Unless we're doing light chains, schedule the headers for associated content retrieval
-				if d.mode == FullSync || d.mode == FastSync {
+				if mode == FullSync || mode == FastSync {
 					// If we've reached the allowed number of pending headers, stall a bit
 					for d.queue.PendingBlocks() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders {
 						select {
 						case <-d.cancelCh:
+							rollbackErr = errCanceled
 							return errCanceled
 						case <-time.After(time.Second):
 						}
@@ -1493,8 +1681,8 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
 					// Otherwise insert the headers for content retrieval
 					inserts := d.queue.Schedule(chunk, origin)
 					if len(inserts) != len(chunk) {
-						log.Debug("Stale headers")
-						return errBadPeer
+						rollbackErr = fmt.Errorf("stale headers: len inserts %v len(chunk) %v", len(inserts), len(chunk))
+						return fmt.Errorf("%w: stale headers", errBadPeer)
 					}
 				}
 				headers = headers[limit:]
@@ -1564,30 +1752,34 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
 			// of the blocks delivered from the downloader, and the indexing will be off.
 			log.Debug("Downloaded item processing failed on sidechain import", "index", index, "err", err)
 		}
-		return errInvalidChain
+		return fmt.Errorf("%w: %v", errInvalidChain, err)
 	}
 	return nil
 }
 
 // processFastSyncContent takes fetch results from the queue and writes them to the
 // database. It also controls the synchronisation of state nodes of the pivot block.
-func (d *Downloader) processFastSyncContent(latest *types.Header) error {
+func (d *Downloader) processFastSyncContent() error {
 	// Start syncing state of the reported head block. This should get us most of
 	// the state of the pivot block.
-	sync := d.syncState(latest.Root)
-	defer sync.Cancel()
+	d.pivotLock.RLock()
+	sync := d.syncState(d.pivotHeader.Root)
+	d.pivotLock.RUnlock()
+
+	defer func() {
+		// The `sync` object is replaced every time the pivot moves. We need to
+		// defer close the very last active one, hence the lazy evaluation vs.
+		// calling defer sync.Cancel() !!!
+		sync.Cancel()
+	}()
+
 	closeOnErr := func(s *stateSync) {
-		if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled {
+		if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled && err != snap.ErrCancelled {
 			d.queue.Close() // wake up Results
 		}
 	}
 	go closeOnErr(sync)
-	// Figure out the ideal pivot block. Note, that this goalpost may move if the
-	// sync takes long enough for the chain head to move significantly.
-	pivot := uint64(0)
-	if height := latest.Number.Uint64(); height > uint64(fsMinFullBlocks) {
-		pivot = height - uint64(fsMinFullBlocks)
-	}
+
 	// To cater for moving pivot points, track the pivot block and subsequently
 	// accumulated download results separately.
 	var (
@@ -1614,18 +1806,46 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
 		if d.chainInsertHook != nil {
 			d.chainInsertHook(results)
 		}
-		if oldPivot != nil {
+		// If we haven't downloaded the pivot block yet, check pivot staleness
+		// notifications from the header downloader
+		d.pivotLock.RLock()
+		pivot := d.pivotHeader
+		d.pivotLock.RUnlock()
+
+		if oldPivot == nil {
+			if pivot.Root != sync.root {
+				sync.Cancel()
+				sync = d.syncState(pivot.Root)
+
+				go closeOnErr(sync)
+			}
+		} else {
 			results = append(append([]*fetchResult{oldPivot}, oldTail...), results...)
 		}
 		// Split around the pivot block and process the two sides via fast/full sync
 		if atomic.LoadInt32(&d.committed) == 0 {
-			latest = results[len(results)-1].Header
-			if height := latest.Number.Uint64(); height > pivot+2*uint64(fsMinFullBlocks) {
-				log.Warn("Pivot became stale, moving", "old", pivot, "new", height-uint64(fsMinFullBlocks))
-				pivot = height - uint64(fsMinFullBlocks)
+			latest := results[len(results)-1].Header
+			// If the height is above the pivot block by 2 sets, it means the pivot
+			// become stale in the network and it was garbage collected, move to a
+			// new pivot.
+			//
+			// Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those
+			// need to be taken into account, otherwise we're detecting the pivot move
+			// late and will drop peers due to unavailable state!!!
+			if height := latest.Number.Uint64(); height >= pivot.Number.Uint64()+2*uint64(fsMinFullBlocks)-uint64(reorgProtHeaderDelay) {
+				log.Warn("Pivot became stale, moving", "old", pivot.Number.Uint64(), "new", height-uint64(fsMinFullBlocks)+uint64(reorgProtHeaderDelay))
+				pivot = results[len(results)-1-fsMinFullBlocks+reorgProtHeaderDelay].Header // must exist as lower old pivot is uncommitted
+
+				d.pivotLock.Lock()
+				d.pivotHeader = pivot
+				d.pivotLock.Unlock()
+
+				// Write out the pivot into the database so a rollback beyond it will
+				// reenable fast sync
+				rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64())
 			}
 		}
-		P, beforeP, afterP := splitAroundPivot(pivot, results)
+		P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results)
 		if err := d.commitFastSyncData(beforeP, sync); err != nil {
 			return err
 		}
@@ -1633,9 +1853,8 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
 			// If new pivot block found, cancel old state retrieval and restart
 			if oldPivot != P {
 				sync.Cancel()
-
 				sync = d.syncState(P.Header.Root)
-				defer sync.Cancel()
+
 				go closeOnErr(sync)
 				oldPivot = P
 			}
@@ -1663,6 +1882,14 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
 }
 
 func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, before, after []*fetchResult) {
+	if len(results) == 0 {
+		return nil, nil, nil
+	}
+	if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot {
+		// the pivot is somewhere in the future
+		return nil, results, nil
+	}
+	// This can also be optimized, but only happens very seldom
 	for _, result := range results {
 		num := result.Header.Number.Uint64()
 		switch {
@@ -1705,7 +1932,7 @@ func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *state
 	}
 	if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil {
 		log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
-		return errInvalidChain
+		return fmt.Errorf("%w: %v", errInvalidChain, err)
 	}
 	return nil
 }
@@ -1736,27 +1963,53 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
 
 // DeliverHeaders injects a new batch of block headers received from a remote
 // node into the download schedule.
-func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) (err error) {
-	return d.deliver(id, d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter)
+func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) error {
+	return d.deliver(d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter)
 }
 
 // DeliverBodies injects a new batch of block bodies received from a remote node.
-func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) (err error) {
-	return d.deliver(id, d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter)
+func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) error {
+	return d.deliver(d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter)
 }
 
 // DeliverReceipts injects a new batch of receipts received from a remote node.
-func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) (err error) {
-	return d.deliver(id, d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter)
+func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) error {
+	return d.deliver(d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter)
 }
 
 // DeliverNodeData injects a new batch of node state data received from a remote node.
-func (d *Downloader) DeliverNodeData(id string, data [][]byte) (err error) {
-	return d.deliver(id, d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter)
+func (d *Downloader) DeliverNodeData(id string, data [][]byte) error {
+	return d.deliver(d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter)
+}
+
+// DeliverSnapPacket is invoked from a peer's message handler when it transmits a
+// data packet for the local node to consume.
+func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error {
+	switch packet := packet.(type) {
+	case *snap.AccountRangePacket:
+		hashes, accounts, err := packet.Unpack()
+		if err != nil {
+			return err
+		}
+		return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof)
+
+	case *snap.StorageRangesPacket:
+		hashset, slotset := packet.Unpack()
+		return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof)
+
+	case *snap.ByteCodesPacket:
+		return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes)
+
+	case *snap.TrieNodesPacket:
+		return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes)
+
+	default:
+		return fmt.Errorf("unexpected snap packet type: %T", packet)
+	}
 }
 
 // deliver injects a new batch of data received from a remote node.
-func (d *Downloader) deliver(id string, destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) {
+func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) {
 	// Update the delivery metrics for both good and failed deliveries
 	inMeter.Mark(int64(packet.Items()))
 	defer func() {
diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go
index f359b703b5..794160993b 100644
--- a/eth/downloader/downloader_test.go
+++ b/eth/downloader/downloader_test.go
@@ -29,7 +29,9 @@ import (
 	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state/snapshot"
 	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/trie"
@@ -37,8 +39,9 @@ import (
 
 // Reduce some of the parameters to make the tester faster.
 func init() {
-	maxForkAncestry = 10000
-	blockCacheItems = 1024
+	fullMaxForkAncestry = 10000
+	lightMaxForkAncestry = 10000
+	blockCacheMaxItems = 1024
 	fsHeaderContCheck = 500 * time.Millisecond
 }
 
@@ -144,7 +147,12 @@ func (dl *downloadTester) HasFastBlock(hash common.Hash, number uint64) bool {
 func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header {
 	dl.lock.RLock()
 	defer dl.lock.RUnlock()
+	return dl.getHeaderByHash(hash)
+}
 
+// getHeaderByHash returns the header if found either within ancients or own blocks)
+// This method assumes that the caller holds at least the read-lock (dl.lock)
+func (dl *downloadTester) getHeaderByHash(hash common.Hash) *types.Header {
 	header := dl.ancientHeaders[hash]
 	if header != nil {
 		return header
@@ -232,6 +240,13 @@ func (dl *downloadTester) GetTd(hash common.Hash, number uint64) *big.Int {
 	dl.lock.RLock()
 	defer dl.lock.RUnlock()
 
+	return dl.getTd(hash)
+}
+
+// getTd retrieves the block's total difficulty if found either within
+// ancients or own blocks).
+// This method assumes that the caller holds at least the read-lock (dl.lock)
+func (dl *downloadTester) getTd(hash common.Hash) *big.Int {
 	if td := dl.ancientChainTd[hash]; td != nil {
 		return td
 	}
@@ -242,27 +257,34 @@ func (dl *downloadTester) GetTd(hash common.Hash, number uint64) *big.Int {
 func (dl *downloadTester) InsertHeaderChain(headers []*types.Header, checkFreq int) (i int, err error) {
 	dl.lock.Lock()
 	defer dl.lock.Unlock()
-
 	// Do a quick check, as the blockchain.InsertHeaderChain doesn't insert anything in case of errors
-	if _, ok := dl.ownHeaders[headers[0].ParentHash]; !ok {
-		return 0, errors.New("unknown parent")
+	if dl.getHeaderByHash(headers[0].ParentHash) == nil {
+		return 0, fmt.Errorf("InsertHeaderChain: unknown parent at first position, parent of number %d", headers[0].Number)
 	}
+	var hashes []common.Hash
 	for i := 1; i < len(headers); i++ {
+		hash := headers[i-1].Hash()
 		if headers[i].ParentHash != headers[i-1].Hash() {
-			return i, errors.New("unknown parent")
+			return i, fmt.Errorf("non-contiguous import at position %d", i)
 		}
+		hashes = append(hashes, hash)
 	}
+	hashes = append(hashes, headers[len(headers)-1].Hash())
 	// Do a full insert if pre-checks passed
 	for i, header := range headers {
-		if _, ok := dl.ownHeaders[header.Hash()]; ok {
+		hash := hashes[i]
+		if dl.getHeaderByHash(hash) != nil {
 			continue
 		}
-		if _, ok := dl.ownHeaders[header.ParentHash]; !ok {
-			return i, errors.New("unknown parent")
+		if dl.getHeaderByHash(header.ParentHash) == nil {
+			// This _should_ be impossible, due to precheck and induction
+			return i, fmt.Errorf("InsertHeaderChain: unknown parent at position %d", i)
 		}
-		dl.ownHashes = append(dl.ownHashes, header.Hash())
-		dl.ownHeaders[header.Hash()] = header
-		dl.ownChainTd[header.Hash()] = new(big.Int).Add(dl.ownChainTd[header.ParentHash], header.Difficulty)
+		dl.ownHashes = append(dl.ownHashes, hash)
+		dl.ownHeaders[hash] = header
+
+		td := dl.getTd(header.ParentHash)
+		dl.ownChainTd[hash] = new(big.Int).Add(td, header.Difficulty)
 	}
 	return len(headers), nil
 }
@@ -271,21 +293,21 @@ func (dl *downloadTester) InsertHeaderChain(headers []*types.Header, checkFreq i
 func (dl *downloadTester) InsertChain(blocks types.Blocks) (i int, err error) {
 	dl.lock.Lock()
 	defer dl.lock.Unlock()
-
 	for i, block := range blocks {
 		if parent, ok := dl.ownBlocks[block.ParentHash()]; !ok {
-			return i, errors.New("unknown parent")
+			return i, fmt.Errorf("InsertChain: unknown parent at position %d / %d", i, len(blocks))
 		} else if _, err := dl.stateDb.Get(parent.Root().Bytes()); err != nil {
-			return i, fmt.Errorf("unknown parent state %x: %v", parent.Root(), err)
+			return i, fmt.Errorf("InsertChain: unknown parent state %x: %v", parent.Root(), err)
 		}
-		if _, ok := dl.ownHeaders[block.Hash()]; !ok {
+		if hdr := dl.getHeaderByHash(block.Hash()); hdr == nil {
 			dl.ownHashes = append(dl.ownHashes, block.Hash())
 			dl.ownHeaders[block.Hash()] = block.Header()
 		}
 		dl.ownBlocks[block.Hash()] = block
 		dl.ownReceipts[block.Hash()] = make(types.Receipts, 0)
 		dl.stateDb.Put(block.Root().Bytes(), []byte{0x00})
-		dl.ownChainTd[block.Hash()] = new(big.Int).Add(dl.ownChainTd[block.ParentHash()], block.Difficulty())
+		td := dl.getTd(block.ParentHash())
+		dl.ownChainTd[block.Hash()] = new(big.Int).Add(td, block.Difficulty())
 	}
 	return len(blocks), nil
 }
@@ -301,7 +323,7 @@ func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []typ
 		}
 		if _, ok := dl.ancientBlocks[blocks[i].ParentHash()]; !ok {
 			if _, ok := dl.ownBlocks[blocks[i].ParentHash()]; !ok {
-				return i, errors.New("unknown parent")
+				return i, errors.New("InsertReceiptChain: unknown parent")
 			}
 		}
 		if blocks[i].NumberU64() <= ancientLimit {
@@ -311,7 +333,6 @@ func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []typ
 			// Migrate from active db to ancient db
 			dl.ancientHeaders[blocks[i].Hash()] = blocks[i].Header()
 			dl.ancientChainTd[blocks[i].Hash()] = new(big.Int).Add(dl.ancientChainTd[blocks[i].ParentHash()], blocks[i].Difficulty())
-
 			delete(dl.ownHeaders, blocks[i].Hash())
 			delete(dl.ownChainTd, blocks[i].Hash())
 		} else {
@@ -322,29 +343,56 @@ func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []typ
 	return len(blocks), nil
 }
 
-// Rollback removes some recently added elements from the chain.
-func (dl *downloadTester) Rollback(hashes []common.Hash) {
+// SetHead rewinds the local chain to a new head.
+func (dl *downloadTester) SetHead(head uint64) error {
 	dl.lock.Lock()
 	defer dl.lock.Unlock()
 
-	for i := len(hashes) - 1; i >= 0; i-- {
-		if dl.ownHashes[len(dl.ownHashes)-1] == hashes[i] {
-			dl.ownHashes = dl.ownHashes[:len(dl.ownHashes)-1]
+	// Find the hash of the head to reset to
+	var hash common.Hash
+	for h, header := range dl.ownHeaders {
+		if header.Number.Uint64() == head {
+			hash = h
+		}
+	}
+	for h, header := range dl.ancientHeaders {
+		if header.Number.Uint64() == head {
+			hash = h
+		}
+	}
+	if hash == (common.Hash{}) {
+		return fmt.Errorf("unknown head to set: %d", head)
+	}
+	// Find the offset in the header chain
+	var offset int
+	for o, h := range dl.ownHashes {
+		if h == hash {
+			offset = o
+			break
 		}
-		delete(dl.ownChainTd, hashes[i])
-		delete(dl.ownHeaders, hashes[i])
-		delete(dl.ownReceipts, hashes[i])
-		delete(dl.ownBlocks, hashes[i])
+	}
+	// Remove all the hashes and associated data afterwards
+	for i := offset + 1; i < len(dl.ownHashes); i++ {
+		delete(dl.ownChainTd, dl.ownHashes[i])
+		delete(dl.ownHeaders, dl.ownHashes[i])
+		delete(dl.ownReceipts, dl.ownHashes[i])
+		delete(dl.ownBlocks, dl.ownHashes[i])
 
-		delete(dl.ancientChainTd, hashes[i])
-		delete(dl.ancientHeaders, hashes[i])
-		delete(dl.ancientReceipts, hashes[i])
-		delete(dl.ancientBlocks, hashes[i])
+		delete(dl.ancientChainTd, dl.ownHashes[i])
+		delete(dl.ancientHeaders, dl.ownHashes[i])
+		delete(dl.ancientReceipts, dl.ownHashes[i])
+		delete(dl.ancientBlocks, dl.ownHashes[i])
 	}
+	dl.ownHashes = dl.ownHashes[:offset+1]
+	return nil
+}
+
+// Rollback removes some recently added elements from the chain.
+func (dl *downloadTester) Rollback(hashes []common.Hash) {
 }
 
 // newPeer registers a new block download source into the downloader.
-func (dl *downloadTester) newPeer(id string, version int, chain *testChain) error {
+func (dl *downloadTester) newPeer(id string, version uint, chain *testChain) error {
 	dl.lock.Lock()
 	defer dl.lock.Unlock()
 
@@ -362,10 +410,14 @@ func (dl *downloadTester) dropPeer(id string) {
 	dl.downloader.UnregisterPeer(id)
 }
 
+// Snapshots implements the BlockChain interface for the downloader, but is a noop.
+func (dl *downloadTester) Snapshots() *snapshot.Tree {
+	return nil
+}
+
 type downloadTesterPeer struct {
 	dl            *downloadTester
 	id            string
-	lock          sync.RWMutex
 	chain         *testChain
 	missingStates map[common.Hash]bool // State entries that fast sync should not return
 }
@@ -381,11 +433,7 @@ func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) {
 // origin; associated with a particular peer in the download tester. The returned
 // function can be used to retrieve batches of headers from the particular peer.
 func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error {
-	if reverse {
-		panic("reverse header requests not supported")
-	}
-
-	result := dlp.chain.headersByHash(origin, amount, skip)
+	result := dlp.chain.headersByHash(origin, amount, skip, reverse)
 	go dlp.dl.downloader.DeliverHeaders(dlp.id, result)
 	return nil
 }
@@ -394,11 +442,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i
 // origin; associated with a particular peer in the download tester. The returned
 // function can be used to retrieve batches of headers from the particular peer.
 func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
-	if reverse {
-		panic("reverse header requests not supported")
-	}
-
-	result := dlp.chain.headersByNumber(origin, amount, skip)
+	result := dlp.chain.headersByNumber(origin, amount, skip, reverse)
 	go dlp.dl.downloader.DeliverHeaders(dlp.id, result)
 	return nil
 }
@@ -464,7 +508,7 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
 		blocks += length - common
 		receipts += length - common
 	}
-	if tester.downloader.mode == LightSync {
+	if tester.downloader.getMode() == LightSync {
 		blocks, receipts = 1, 1
 	}
 	if hs := len(tester.ownHeaders) + len(tester.ancientHeaders) - 1; hs != headers {
@@ -478,26 +522,22 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng
 	}
 }
 
-// Tests that simple synchronization against a canonical chain works correctly.
-// In this test common ancestor lookup should be short circuited and not require
-// binary searching.
-func TestCanonicalSynchronisation62(t *testing.T)     { testCanonicalSynchronisation(t, 62, FullSync) }
-func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) }
-func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) }
-func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) }
-func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) }
-func TestCanonicalSynchronisation64Light(t *testing.T) {
-	testCanonicalSynchronisation(t, 64, LightSync)
-}
+func TestCanonicalSynchronisation65Full(t *testing.T)  { testCanonSync(t, eth.ETH65, FullSync) }
+func TestCanonicalSynchronisation65Fast(t *testing.T)  { testCanonSync(t, eth.ETH65, FastSync) }
+func TestCanonicalSynchronisation65Light(t *testing.T) { testCanonSync(t, eth.ETH65, LightSync) }
+
+func TestCanonicalSynchronisation66Full(t *testing.T)  { testCanonSync(t, eth.ETH66, FullSync) }
+func TestCanonicalSynchronisation66Fast(t *testing.T)  { testCanonSync(t, eth.ETH66, FastSync) }
+func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, eth.ETH66, LightSync) }
 
-func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
+func testCanonSync(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
 
 	// Create a small enough block chain to download
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 	tester.newPeer("peer", protocol, chain)
 
 	// Synchronise with the peer and make sure all relevant data was retrieved
@@ -509,16 +549,15 @@ func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that if a large batch of blocks are being downloaded, it is throttled
 // until the cached blocks are retrieved.
-func TestThrottling62(t *testing.T)     { testThrottling(t, 62, FullSync) }
-func TestThrottling63Full(t *testing.T) { testThrottling(t, 63, FullSync) }
-func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) }
-func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) }
-func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) }
+func TestThrottling65Full(t *testing.T) { testThrottling(t, eth.ETH65, FullSync) }
+func TestThrottling65Fast(t *testing.T) { testThrottling(t, eth.ETH65, FastSync) }
 
-func testThrottling(t *testing.T, protocol int, mode SyncMode) {
+func TestThrottling66Full(t *testing.T) { testThrottling(t, eth.ETH66, FullSync) }
+func TestThrottling66Fast(t *testing.T) { testThrottling(t, eth.ETH66, FastSync) }
+
+func testThrottling(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 	tester := newTester()
-	defer tester.terminate()
 
 	// Create a long block chain to download and the tester
 	targetBlocks := testChainBase.len() - 1
@@ -531,7 +570,7 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
 		<-proceed
 	}
 	// Start a synchronisation concurrently
-	errc := make(chan error)
+	errc := make(chan error, 1)
 	go func() {
 		errc <- tester.sync("peer", nil, mode)
 	}()
@@ -551,30 +590,32 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
 
 			tester.lock.Lock()
 			tester.downloader.queue.lock.Lock()
-			cached = len(tester.downloader.queue.blockDonePool)
-			if mode == FastSync {
-				if receipts := len(tester.downloader.queue.receiptDonePool); receipts < cached {
-					cached = receipts
-				}
+			tester.downloader.queue.resultCache.lock.Lock()
+			{
+				cached = tester.downloader.queue.resultCache.countCompleted()
+				frozen = int(atomic.LoadUint32(&blocked))
+				retrieved = len(tester.ownBlocks)
 			}
-			frozen = int(atomic.LoadUint32(&blocked))
-			retrieved = len(tester.ownBlocks)
+			tester.downloader.queue.resultCache.lock.Unlock()
 			tester.downloader.queue.lock.Unlock()
 			tester.lock.Unlock()
 
-			if cached == blockCacheItems || cached == blockCacheItems-reorgProtHeaderDelay || retrieved+cached+frozen == targetBlocks+1 || retrieved+cached+frozen == targetBlocks+1-reorgProtHeaderDelay {
+			if cached == blockCacheMaxItems ||
+				cached == blockCacheMaxItems-reorgProtHeaderDelay ||
+				retrieved+cached+frozen == targetBlocks+1 ||
+				retrieved+cached+frozen == targetBlocks+1-reorgProtHeaderDelay {
 				break
 			}
 		}
 		// Make sure we filled up the cache, then exhaust it
 		time.Sleep(25 * time.Millisecond) // give it a chance to screw up
-
 		tester.lock.RLock()
 		retrieved = len(tester.ownBlocks)
 		tester.lock.RUnlock()
-		if cached != blockCacheItems && cached != blockCacheItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay {
-			t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheItems, retrieved, frozen, targetBlocks+1)
+		if cached != blockCacheMaxItems && cached != blockCacheMaxItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay {
+			t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheMaxItems, retrieved, frozen, targetBlocks+1)
 		}
+
 		// Permit the blocked blocks to import
 		if atomic.LoadUint32(&blocked) > 0 {
 			atomic.StoreUint32(&blocked, uint32(0))
@@ -586,19 +627,22 @@ func testThrottling(t *testing.T, protocol int, mode SyncMode) {
 	if err := <-errc; err != nil {
 		t.Fatalf("block synchronization failed: %v", err)
 	}
+	tester.terminate()
+
 }
 
 // Tests that simple synchronization against a forked chain works correctly. In
 // this test common ancestor lookup should *not* be short circuited, and a full
 // binary search should be executed.
-func TestForkedSync62(t *testing.T)      { testForkedSync(t, 62, FullSync) }
-func TestForkedSync63Full(t *testing.T)  { testForkedSync(t, 63, FullSync) }
-func TestForkedSync63Fast(t *testing.T)  { testForkedSync(t, 63, FastSync) }
-func TestForkedSync64Full(t *testing.T)  { testForkedSync(t, 64, FullSync) }
-func TestForkedSync64Fast(t *testing.T)  { testForkedSync(t, 64, FastSync) }
-func TestForkedSync64Light(t *testing.T) { testForkedSync(t, 64, LightSync) }
-
-func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
+func TestForkedSync65Full(t *testing.T)  { testForkedSync(t, eth.ETH65, FullSync) }
+func TestForkedSync65Fast(t *testing.T)  { testForkedSync(t, eth.ETH65, FastSync) }
+func TestForkedSync65Light(t *testing.T) { testForkedSync(t, eth.ETH65, LightSync) }
+
+func TestForkedSync66Full(t *testing.T)  { testForkedSync(t, eth.ETH66, FullSync) }
+func TestForkedSync66Fast(t *testing.T)  { testForkedSync(t, eth.ETH66, FastSync) }
+func TestForkedSync66Light(t *testing.T) { testForkedSync(t, eth.ETH66, LightSync) }
+
+func testForkedSync(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
@@ -608,7 +652,6 @@ func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
 	chainB := testChainForkLightB.shorten(testChainBase.len() + 80)
 	tester.newPeer("fork A", protocol, chainA)
 	tester.newPeer("fork B", protocol, chainB)
-
 	// Synchronise with the peer and make sure all blocks were retrieved
 	if err := tester.sync("fork A", nil, mode); err != nil {
 		t.Fatalf("failed to synchronise blocks: %v", err)
@@ -624,14 +667,15 @@ func testForkedSync(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that synchronising against a much shorter but much heavyer fork works
 // corrently and is not dropped.
-func TestHeavyForkedSync62(t *testing.T)      { testHeavyForkedSync(t, 62, FullSync) }
-func TestHeavyForkedSync63Full(t *testing.T)  { testHeavyForkedSync(t, 63, FullSync) }
-func TestHeavyForkedSync63Fast(t *testing.T)  { testHeavyForkedSync(t, 63, FastSync) }
-func TestHeavyForkedSync64Full(t *testing.T)  { testHeavyForkedSync(t, 64, FullSync) }
-func TestHeavyForkedSync64Fast(t *testing.T)  { testHeavyForkedSync(t, 64, FastSync) }
-func TestHeavyForkedSync64Light(t *testing.T) { testHeavyForkedSync(t, 64, LightSync) }
-
-func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
+func TestHeavyForkedSync65Full(t *testing.T)  { testHeavyForkedSync(t, eth.ETH65, FullSync) }
+func TestHeavyForkedSync65Fast(t *testing.T)  { testHeavyForkedSync(t, eth.ETH65, FastSync) }
+func TestHeavyForkedSync65Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH65, LightSync) }
+
+func TestHeavyForkedSync66Full(t *testing.T)  { testHeavyForkedSync(t, eth.ETH66, FullSync) }
+func TestHeavyForkedSync66Fast(t *testing.T)  { testHeavyForkedSync(t, eth.ETH66, FastSync) }
+func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, LightSync) }
+
+func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
@@ -658,14 +702,15 @@ func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
 // Tests that chain forks are contained within a certain interval of the current
 // chain head, ensuring that malicious peers cannot waste resources by feeding
 // long dead chains.
-func TestBoundedForkedSync62(t *testing.T)      { testBoundedForkedSync(t, 62, FullSync) }
-func TestBoundedForkedSync63Full(t *testing.T)  { testBoundedForkedSync(t, 63, FullSync) }
-func TestBoundedForkedSync63Fast(t *testing.T)  { testBoundedForkedSync(t, 63, FastSync) }
-func TestBoundedForkedSync64Full(t *testing.T)  { testBoundedForkedSync(t, 64, FullSync) }
-func TestBoundedForkedSync64Fast(t *testing.T)  { testBoundedForkedSync(t, 64, FastSync) }
-func TestBoundedForkedSync64Light(t *testing.T) { testBoundedForkedSync(t, 64, LightSync) }
-
-func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
+func TestBoundedForkedSync65Full(t *testing.T)  { testBoundedForkedSync(t, eth.ETH65, FullSync) }
+func TestBoundedForkedSync65Fast(t *testing.T)  { testBoundedForkedSync(t, eth.ETH65, FastSync) }
+func TestBoundedForkedSync65Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH65, LightSync) }
+
+func TestBoundedForkedSync66Full(t *testing.T)  { testBoundedForkedSync(t, eth.ETH66, FullSync) }
+func TestBoundedForkedSync66Fast(t *testing.T)  { testBoundedForkedSync(t, eth.ETH66, FastSync) }
+func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, LightSync) }
+
+func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
@@ -691,24 +736,34 @@ func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) {
 // Tests that chain forks are contained within a certain interval of the current
 // chain head for short but heavy forks too. These are a bit special because they
 // take different ancestor lookup paths.
-func TestBoundedHeavyForkedSync62(t *testing.T)      { testBoundedHeavyForkedSync(t, 62, FullSync) }
-func TestBoundedHeavyForkedSync63Full(t *testing.T)  { testBoundedHeavyForkedSync(t, 63, FullSync) }
-func TestBoundedHeavyForkedSync63Fast(t *testing.T)  { testBoundedHeavyForkedSync(t, 63, FastSync) }
-func TestBoundedHeavyForkedSync64Full(t *testing.T)  { testBoundedHeavyForkedSync(t, 64, FullSync) }
-func TestBoundedHeavyForkedSync64Fast(t *testing.T)  { testBoundedHeavyForkedSync(t, 64, FastSync) }
-func TestBoundedHeavyForkedSync64Light(t *testing.T) { testBoundedHeavyForkedSync(t, 64, LightSync) }
-
-func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
-	t.Parallel()
+func TestBoundedHeavyForkedSync65Full(t *testing.T) {
+	testBoundedHeavyForkedSync(t, eth.ETH65, FullSync)
+}
+func TestBoundedHeavyForkedSync65Fast(t *testing.T) {
+	testBoundedHeavyForkedSync(t, eth.ETH65, FastSync)
+}
+func TestBoundedHeavyForkedSync65Light(t *testing.T) {
+	testBoundedHeavyForkedSync(t, eth.ETH65, LightSync)
+}
 
+func TestBoundedHeavyForkedSync66Full(t *testing.T) {
+	testBoundedHeavyForkedSync(t, eth.ETH66, FullSync)
+}
+func TestBoundedHeavyForkedSync66Fast(t *testing.T) {
+	testBoundedHeavyForkedSync(t, eth.ETH66, FastSync)
+}
+func TestBoundedHeavyForkedSync66Light(t *testing.T) {
+	testBoundedHeavyForkedSync(t, eth.ETH66, LightSync)
+}
+
+func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) {
+	t.Parallel()
 	tester := newTester()
-	defer tester.terminate()
 
 	// Create a long enough forked chain
 	chainA := testChainForkLightA
 	chainB := testChainForkHeavy
 	tester.newPeer("original", protocol, chainA)
-	tester.newPeer("heavy-rewriter", protocol, chainB)
 
 	// Synchronise with the peer and make sure all blocks were retrieved
 	if err := tester.sync("original", nil, mode); err != nil {
@@ -716,27 +771,12 @@ func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) {
 	}
 	assertOwnChain(t, tester, chainA.len())
 
+	tester.newPeer("heavy-rewriter", protocol, chainB)
 	// Synchronise with the second peer and ensure that the fork is rejected to being too old
 	if err := tester.sync("heavy-rewriter", nil, mode); err != errInvalidAncestor {
 		t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor)
 	}
-}
-
-// Tests that an inactive downloader will not accept incoming block headers and
-// bodies.
-func TestInactiveDownloader62(t *testing.T) {
-	t.Parallel()
-
-	tester := newTester()
-	defer tester.terminate()
-
-	// Check that neither block headers nor bodies are accepted
-	if err := tester.downloader.DeliverHeaders("bad peer", []*types.Header{}); err != errNoSyncActive {
-		t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive)
-	}
-	if err := tester.downloader.DeliverBodies("bad peer", [][]*types.Transaction{}, [][]*types.Header{}); err != errNoSyncActive {
-		t.Errorf("error mismatch: have %v, want  %v", err, errNoSyncActive)
-	}
+	tester.terminate()
 }
 
 // Tests that an inactive downloader will not accept incoming block headers,
@@ -760,14 +800,15 @@ func TestInactiveDownloader63(t *testing.T) {
 }
 
 // Tests that a canceled download wipes all previously accumulated state.
-func TestCancel62(t *testing.T)      { testCancel(t, 62, FullSync) }
-func TestCancel63Full(t *testing.T)  { testCancel(t, 63, FullSync) }
-func TestCancel63Fast(t *testing.T)  { testCancel(t, 63, FastSync) }
-func TestCancel64Full(t *testing.T)  { testCancel(t, 64, FullSync) }
-func TestCancel64Fast(t *testing.T)  { testCancel(t, 64, FastSync) }
-func TestCancel64Light(t *testing.T) { testCancel(t, 64, LightSync) }
-
-func testCancel(t *testing.T, protocol int, mode SyncMode) {
+func TestCancel65Full(t *testing.T)  { testCancel(t, eth.ETH65, FullSync) }
+func TestCancel65Fast(t *testing.T)  { testCancel(t, eth.ETH65, FastSync) }
+func TestCancel65Light(t *testing.T) { testCancel(t, eth.ETH65, LightSync) }
+
+func TestCancel66Full(t *testing.T)  { testCancel(t, eth.ETH66, FullSync) }
+func TestCancel66Fast(t *testing.T)  { testCancel(t, eth.ETH66, FastSync) }
+func TestCancel66Light(t *testing.T) { testCancel(t, eth.ETH66, LightSync) }
+
+func testCancel(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
@@ -792,14 +833,15 @@ func testCancel(t *testing.T, protocol int, mode SyncMode) {
 }
 
 // Tests that synchronisation from multiple peers works as intended (multi thread sanity test).
-func TestMultiSynchronisation62(t *testing.T)      { testMultiSynchronisation(t, 62, FullSync) }
-func TestMultiSynchronisation63Full(t *testing.T)  { testMultiSynchronisation(t, 63, FullSync) }
-func TestMultiSynchronisation63Fast(t *testing.T)  { testMultiSynchronisation(t, 63, FastSync) }
-func TestMultiSynchronisation64Full(t *testing.T)  { testMultiSynchronisation(t, 64, FullSync) }
-func TestMultiSynchronisation64Fast(t *testing.T)  { testMultiSynchronisation(t, 64, FastSync) }
-func TestMultiSynchronisation64Light(t *testing.T) { testMultiSynchronisation(t, 64, LightSync) }
-
-func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
+func TestMultiSynchronisation65Full(t *testing.T)  { testMultiSynchronisation(t, eth.ETH65, FullSync) }
+func TestMultiSynchronisation65Fast(t *testing.T)  { testMultiSynchronisation(t, eth.ETH65, FastSync) }
+func TestMultiSynchronisation65Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH65, LightSync) }
+
+func TestMultiSynchronisation66Full(t *testing.T)  { testMultiSynchronisation(t, eth.ETH66, FullSync) }
+func TestMultiSynchronisation66Fast(t *testing.T)  { testMultiSynchronisation(t, eth.ETH66, FastSync) }
+func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, LightSync) }
+
+func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
@@ -821,26 +863,26 @@ func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that synchronisations behave well in multi-version protocol environments
 // and not wreak havoc on other nodes in the network.
-func TestMultiProtoSynchronisation62(t *testing.T)      { testMultiProtoSync(t, 62, FullSync) }
-func TestMultiProtoSynchronisation63Full(t *testing.T)  { testMultiProtoSync(t, 63, FullSync) }
-func TestMultiProtoSynchronisation63Fast(t *testing.T)  { testMultiProtoSync(t, 63, FastSync) }
-func TestMultiProtoSynchronisation64Full(t *testing.T)  { testMultiProtoSync(t, 64, FullSync) }
-func TestMultiProtoSynchronisation64Fast(t *testing.T)  { testMultiProtoSync(t, 64, FastSync) }
-func TestMultiProtoSynchronisation64Light(t *testing.T) { testMultiProtoSync(t, 64, LightSync) }
-
-func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
+func TestMultiProtoSynchronisation65Full(t *testing.T)  { testMultiProtoSync(t, eth.ETH65, FullSync) }
+func TestMultiProtoSynchronisation65Fast(t *testing.T)  { testMultiProtoSync(t, eth.ETH65, FastSync) }
+func TestMultiProtoSynchronisation65Light(t *testing.T) { testMultiProtoSync(t, eth.ETH65, LightSync) }
+
+func TestMultiProtoSynchronisation66Full(t *testing.T)  { testMultiProtoSync(t, eth.ETH66, FullSync) }
+func TestMultiProtoSynchronisation66Fast(t *testing.T)  { testMultiProtoSync(t, eth.ETH66, FastSync) }
+func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, eth.ETH66, LightSync) }
+
+func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
 
 	// Create a small enough block chain to download
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 
 	// Create peers of every type
-	tester.newPeer("peer 62", 62, chain)
-	tester.newPeer("peer 63", 63, chain)
-	tester.newPeer("peer 64", 64, chain)
+	tester.newPeer("peer 65", eth.ETH65, chain)
+	tester.newPeer("peer 66", eth.ETH66, chain)
 
 	// Synchronise with the requested peer and make sure all blocks were retrieved
 	if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil {
@@ -849,7 +891,7 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
 	assertOwnChain(t, tester, chain.len())
 
 	// Check that no peers have been dropped off
-	for _, version := range []int{62, 63, 64} {
+	for _, version := range []int{65, 66} {
 		peer := fmt.Sprintf("peer %d", version)
 		if _, ok := tester.peers[peer]; !ok {
 			t.Errorf("%s dropped", peer)
@@ -859,14 +901,15 @@ func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that if a block is empty (e.g. header only), no body request should be
 // made, and instead the header should be assembled into a whole block in itself.
-func TestEmptyShortCircuit62(t *testing.T)      { testEmptyShortCircuit(t, 62, FullSync) }
-func TestEmptyShortCircuit63Full(t *testing.T)  { testEmptyShortCircuit(t, 63, FullSync) }
-func TestEmptyShortCircuit63Fast(t *testing.T)  { testEmptyShortCircuit(t, 63, FastSync) }
-func TestEmptyShortCircuit64Full(t *testing.T)  { testEmptyShortCircuit(t, 64, FullSync) }
-func TestEmptyShortCircuit64Fast(t *testing.T)  { testEmptyShortCircuit(t, 64, FastSync) }
-func TestEmptyShortCircuit64Light(t *testing.T) { testEmptyShortCircuit(t, 64, LightSync) }
-
-func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
+func TestEmptyShortCircuit65Full(t *testing.T)  { testEmptyShortCircuit(t, eth.ETH65, FullSync) }
+func TestEmptyShortCircuit65Fast(t *testing.T)  { testEmptyShortCircuit(t, eth.ETH65, FastSync) }
+func TestEmptyShortCircuit65Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH65, LightSync) }
+
+func TestEmptyShortCircuit66Full(t *testing.T)  { testEmptyShortCircuit(t, eth.ETH66, FullSync) }
+func TestEmptyShortCircuit66Fast(t *testing.T)  { testEmptyShortCircuit(t, eth.ETH66, FastSync) }
+func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, LightSync) }
+
+func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
@@ -912,20 +955,21 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that headers are enqueued continuously, preventing malicious nodes from
 // stalling the downloader by feeding gapped header chains.
-func TestMissingHeaderAttack62(t *testing.T)      { testMissingHeaderAttack(t, 62, FullSync) }
-func TestMissingHeaderAttack63Full(t *testing.T)  { testMissingHeaderAttack(t, 63, FullSync) }
-func TestMissingHeaderAttack63Fast(t *testing.T)  { testMissingHeaderAttack(t, 63, FastSync) }
-func TestMissingHeaderAttack64Full(t *testing.T)  { testMissingHeaderAttack(t, 64, FullSync) }
-func TestMissingHeaderAttack64Fast(t *testing.T)  { testMissingHeaderAttack(t, 64, FastSync) }
-func TestMissingHeaderAttack64Light(t *testing.T) { testMissingHeaderAttack(t, 64, LightSync) }
-
-func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
+func TestMissingHeaderAttack65Full(t *testing.T)  { testMissingHeaderAttack(t, eth.ETH65, FullSync) }
+func TestMissingHeaderAttack65Fast(t *testing.T)  { testMissingHeaderAttack(t, eth.ETH65, FastSync) }
+func TestMissingHeaderAttack65Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH65, LightSync) }
+
+func TestMissingHeaderAttack66Full(t *testing.T)  { testMissingHeaderAttack(t, eth.ETH66, FullSync) }
+func TestMissingHeaderAttack66Fast(t *testing.T)  { testMissingHeaderAttack(t, eth.ETH66, FastSync) }
+func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, LightSync) }
+
+func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
 
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 	brokenChain := chain.shorten(chain.len())
 	delete(brokenChain.headerm, brokenChain.chain[brokenChain.len()/2])
 	tester.newPeer("attack", protocol, brokenChain)
@@ -943,20 +987,21 @@ func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that if requested headers are shifted (i.e. first is missing), the queue
 // detects the invalid numbering.
-func TestShiftedHeaderAttack62(t *testing.T)      { testShiftedHeaderAttack(t, 62, FullSync) }
-func TestShiftedHeaderAttack63Full(t *testing.T)  { testShiftedHeaderAttack(t, 63, FullSync) }
-func TestShiftedHeaderAttack63Fast(t *testing.T)  { testShiftedHeaderAttack(t, 63, FastSync) }
-func TestShiftedHeaderAttack64Full(t *testing.T)  { testShiftedHeaderAttack(t, 64, FullSync) }
-func TestShiftedHeaderAttack64Fast(t *testing.T)  { testShiftedHeaderAttack(t, 64, FastSync) }
-func TestShiftedHeaderAttack64Light(t *testing.T) { testShiftedHeaderAttack(t, 64, LightSync) }
-
-func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
+func TestShiftedHeaderAttack65Full(t *testing.T)  { testShiftedHeaderAttack(t, eth.ETH65, FullSync) }
+func TestShiftedHeaderAttack65Fast(t *testing.T)  { testShiftedHeaderAttack(t, eth.ETH65, FastSync) }
+func TestShiftedHeaderAttack65Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH65, LightSync) }
+
+func TestShiftedHeaderAttack66Full(t *testing.T)  { testShiftedHeaderAttack(t, eth.ETH66, FullSync) }
+func TestShiftedHeaderAttack66Fast(t *testing.T)  { testShiftedHeaderAttack(t, eth.ETH66, FastSync) }
+func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, LightSync) }
+
+func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
 
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 
 	// Attempt a full sync with an attacker feeding shifted headers
 	brokenChain := chain.shorten(chain.len())
@@ -979,15 +1024,13 @@ func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) {
 // Tests that upon detecting an invalid header, the recent ones are rolled back
 // for various failure scenarios. Afterwards a full sync is attempted to make
 // sure no state was corrupted.
-func TestInvalidHeaderRollback63Fast(t *testing.T)  { testInvalidHeaderRollback(t, 63, FastSync) }
-func TestInvalidHeaderRollback64Fast(t *testing.T)  { testInvalidHeaderRollback(t, 64, FastSync) }
-func TestInvalidHeaderRollback64Light(t *testing.T) { testInvalidHeaderRollback(t, 64, LightSync) }
+func TestInvalidHeaderRollback65Fast(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH65, FastSync) }
+func TestInvalidHeaderRollback66Fast(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH66, FastSync) }
 
-func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
+func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
-	defer tester.terminate()
 
 	// Create a small enough block chain to download
 	targetBlocks := 3*fsHeaderSafetyNet + 256 + fsMinFullBlocks
@@ -1067,36 +1110,49 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) {
 			t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, chain.len())
 		}
 	}
+	tester.terminate()
 }
 
-// Tests that a peer advertising an high TD doesn't get to stall the downloader
+// Tests that a peer advertising a high TD doesn't get to stall the downloader
 // afterwards by not sending any useful hashes.
-func TestHighTDStarvationAttack62(t *testing.T)      { testHighTDStarvationAttack(t, 62, FullSync) }
-func TestHighTDStarvationAttack63Full(t *testing.T)  { testHighTDStarvationAttack(t, 63, FullSync) }
-func TestHighTDStarvationAttack63Fast(t *testing.T)  { testHighTDStarvationAttack(t, 63, FastSync) }
-func TestHighTDStarvationAttack64Full(t *testing.T)  { testHighTDStarvationAttack(t, 64, FullSync) }
-func TestHighTDStarvationAttack64Fast(t *testing.T)  { testHighTDStarvationAttack(t, 64, FastSync) }
-func TestHighTDStarvationAttack64Light(t *testing.T) { testHighTDStarvationAttack(t, 64, LightSync) }
-
-func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) {
+func TestHighTDStarvationAttack65Full(t *testing.T) {
+	testHighTDStarvationAttack(t, eth.ETH65, FullSync)
+}
+func TestHighTDStarvationAttack65Fast(t *testing.T) {
+	testHighTDStarvationAttack(t, eth.ETH65, FastSync)
+}
+func TestHighTDStarvationAttack65Light(t *testing.T) {
+	testHighTDStarvationAttack(t, eth.ETH65, LightSync)
+}
+
+func TestHighTDStarvationAttack66Full(t *testing.T) {
+	testHighTDStarvationAttack(t, eth.ETH66, FullSync)
+}
+func TestHighTDStarvationAttack66Fast(t *testing.T) {
+	testHighTDStarvationAttack(t, eth.ETH66, FastSync)
+}
+func TestHighTDStarvationAttack66Light(t *testing.T) {
+	testHighTDStarvationAttack(t, eth.ETH66, LightSync)
+}
+
+func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
-	defer tester.terminate()
 
 	chain := testChainBase.shorten(1)
 	tester.newPeer("attack", protocol, chain)
 	if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer {
 		t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer)
 	}
+	tester.terminate()
 }
 
 // Tests that misbehaving peers are disconnected, whilst behaving ones are not.
-func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDropping(t, 62) }
-func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) }
-func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) }
+func TestBlockHeaderAttackerDropping65(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH65) }
+func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH66) }
 
-func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
+func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) {
 	t.Parallel()
 
 	// Define the disconnection requirement for individual hash fetch errors
@@ -1126,7 +1182,7 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
 	chain := testChainBase.shorten(1)
 
 	for i, tt := range tests {
-		// Register a new peer and ensure it's presence
+		// Register a new peer and ensure its presence
 		id := fmt.Sprintf("test %d", i)
 		if err := tester.newPeer(id, protocol, chain); err != nil {
 			t.Fatalf("test %d: failed to register new peer: %v", i, err)
@@ -1146,19 +1202,20 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) {
 
 // Tests that synchronisation progress (origin block number, current block number
 // and highest block number) is tracked and updated correctly.
-func TestSyncProgress62(t *testing.T)      { testSyncProgress(t, 62, FullSync) }
-func TestSyncProgress63Full(t *testing.T)  { testSyncProgress(t, 63, FullSync) }
-func TestSyncProgress63Fast(t *testing.T)  { testSyncProgress(t, 63, FastSync) }
-func TestSyncProgress64Full(t *testing.T)  { testSyncProgress(t, 64, FullSync) }
-func TestSyncProgress64Fast(t *testing.T)  { testSyncProgress(t, 64, FastSync) }
-func TestSyncProgress64Light(t *testing.T) { testSyncProgress(t, 64, LightSync) }
-
-func testSyncProgress(t *testing.T, protocol int, mode SyncMode) {
+func TestSyncProgress65Full(t *testing.T)  { testSyncProgress(t, eth.ETH65, FullSync) }
+func TestSyncProgress65Fast(t *testing.T)  { testSyncProgress(t, eth.ETH65, FastSync) }
+func TestSyncProgress65Light(t *testing.T) { testSyncProgress(t, eth.ETH65, LightSync) }
+
+func TestSyncProgress66Full(t *testing.T)  { testSyncProgress(t, eth.ETH66, FullSync) }
+func TestSyncProgress66Fast(t *testing.T)  { testSyncProgress(t, eth.ETH66, FastSync) }
+func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, eth.ETH66, LightSync) }
+
+func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 
 	// Set a sync init hook to catch progress changes
 	starting := make(chan struct{})
@@ -1229,20 +1286,21 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync
 // Tests that synchronisation progress (origin block number and highest block
 // number) is tracked and updated correctly in case of a fork (or manual head
 // revertal).
-func TestForkedSyncProgress62(t *testing.T)      { testForkedSyncProgress(t, 62, FullSync) }
-func TestForkedSyncProgress63Full(t *testing.T)  { testForkedSyncProgress(t, 63, FullSync) }
-func TestForkedSyncProgress63Fast(t *testing.T)  { testForkedSyncProgress(t, 63, FastSync) }
-func TestForkedSyncProgress64Full(t *testing.T)  { testForkedSyncProgress(t, 64, FullSync) }
-func TestForkedSyncProgress64Fast(t *testing.T)  { testForkedSyncProgress(t, 64, FastSync) }
-func TestForkedSyncProgress64Light(t *testing.T) { testForkedSyncProgress(t, 64, LightSync) }
-
-func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
+func TestForkedSyncProgress65Full(t *testing.T)  { testForkedSyncProgress(t, eth.ETH65, FullSync) }
+func TestForkedSyncProgress65Fast(t *testing.T)  { testForkedSyncProgress(t, eth.ETH65, FastSync) }
+func TestForkedSyncProgress65Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH65, LightSync) }
+
+func TestForkedSyncProgress66Full(t *testing.T)  { testForkedSyncProgress(t, eth.ETH66, FullSync) }
+func TestForkedSyncProgress66Fast(t *testing.T)  { testForkedSyncProgress(t, eth.ETH66, FastSync) }
+func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, LightSync) }
+
+func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
-	chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHashFetch)
-	chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHashFetch)
+	chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHeaderFetch)
+	chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHeaderFetch)
 
 	// Set a sync init hook to catch progress changes
 	starting := make(chan struct{})
@@ -1304,19 +1362,20 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
 // Tests that if synchronisation is aborted due to some failure, then the progress
 // origin is not updated in the next sync cycle, as it should be considered the
 // continuation of the previous sync and not a new instance.
-func TestFailedSyncProgress62(t *testing.T)      { testFailedSyncProgress(t, 62, FullSync) }
-func TestFailedSyncProgress63Full(t *testing.T)  { testFailedSyncProgress(t, 63, FullSync) }
-func TestFailedSyncProgress63Fast(t *testing.T)  { testFailedSyncProgress(t, 63, FastSync) }
-func TestFailedSyncProgress64Full(t *testing.T)  { testFailedSyncProgress(t, 64, FullSync) }
-func TestFailedSyncProgress64Fast(t *testing.T)  { testFailedSyncProgress(t, 64, FastSync) }
-func TestFailedSyncProgress64Light(t *testing.T) { testFailedSyncProgress(t, 64, LightSync) }
-
-func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
+func TestFailedSyncProgress65Full(t *testing.T)  { testFailedSyncProgress(t, eth.ETH65, FullSync) }
+func TestFailedSyncProgress65Fast(t *testing.T)  { testFailedSyncProgress(t, eth.ETH65, FastSync) }
+func TestFailedSyncProgress65Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH65, LightSync) }
+
+func TestFailedSyncProgress66Full(t *testing.T)  { testFailedSyncProgress(t, eth.ETH66, FullSync) }
+func TestFailedSyncProgress66Fast(t *testing.T)  { testFailedSyncProgress(t, eth.ETH66, FastSync) }
+func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, LightSync) }
+
+func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 
 	// Set a sync init hook to catch progress changes
 	starting := make(chan struct{})
@@ -1376,19 +1435,20 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
 
 // Tests that if an attacker fakes a chain height, after the attack is detected,
 // the progress height is successfully reduced at the next sync invocation.
-func TestFakedSyncProgress62(t *testing.T)      { testFakedSyncProgress(t, 62, FullSync) }
-func TestFakedSyncProgress63Full(t *testing.T)  { testFakedSyncProgress(t, 63, FullSync) }
-func TestFakedSyncProgress63Fast(t *testing.T)  { testFakedSyncProgress(t, 63, FastSync) }
-func TestFakedSyncProgress64Full(t *testing.T)  { testFakedSyncProgress(t, 64, FullSync) }
-func TestFakedSyncProgress64Fast(t *testing.T)  { testFakedSyncProgress(t, 64, FastSync) }
-func TestFakedSyncProgress64Light(t *testing.T) { testFakedSyncProgress(t, 64, LightSync) }
-
-func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
+func TestFakedSyncProgress65Full(t *testing.T)  { testFakedSyncProgress(t, eth.ETH65, FullSync) }
+func TestFakedSyncProgress65Fast(t *testing.T)  { testFakedSyncProgress(t, eth.ETH65, FastSync) }
+func TestFakedSyncProgress65Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH65, LightSync) }
+
+func TestFakedSyncProgress66Full(t *testing.T)  { testFakedSyncProgress(t, eth.ETH66, FullSync) }
+func TestFakedSyncProgress66Fast(t *testing.T)  { testFakedSyncProgress(t, eth.ETH66, FastSync) }
+func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, LightSync) }
+
+func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	tester := newTester()
 	defer tester.terminate()
-	chain := testChainBase.shorten(blockCacheItems - 15)
+	chain := testChainBase.shorten(blockCacheMaxItems - 15)
 
 	// Set a sync init hook to catch progress changes
 	starting := make(chan struct{})
@@ -1452,29 +1512,17 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
 
 // This test reproduces an issue where unexpected deliveries would
 // block indefinitely if they arrived at the right time.
-func TestDeliverHeadersHang(t *testing.T) {
-	t.Parallel()
+func TestDeliverHeadersHang65Full(t *testing.T)  { testDeliverHeadersHang(t, eth.ETH65, FullSync) }
+func TestDeliverHeadersHang65Fast(t *testing.T)  { testDeliverHeadersHang(t, eth.ETH65, FastSync) }
+func TestDeliverHeadersHang65Light(t *testing.T) { testDeliverHeadersHang(t, eth.ETH65, LightSync) }
 
-	testCases := []struct {
-		protocol int
-		syncMode SyncMode
-	}{
-		{62, FullSync},
-		{63, FullSync},
-		{63, FastSync},
-		{64, FullSync},
-		{64, FastSync},
-		{64, LightSync},
-	}
-	for _, tc := range testCases {
-		t.Run(fmt.Sprintf("protocol %d mode %v", tc.protocol, tc.syncMode), func(t *testing.T) {
-			t.Parallel()
-			testDeliverHeadersHang(t, tc.protocol, tc.syncMode)
-		})
-	}
-}
+func TestDeliverHeadersHang66Full(t *testing.T)  { testDeliverHeadersHang(t, eth.ETH66, FullSync) }
+func TestDeliverHeadersHang66Fast(t *testing.T)  { testDeliverHeadersHang(t, eth.ETH66, FastSync) }
+func TestDeliverHeadersHang66Light(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, LightSync) }
+
+func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) {
+	t.Parallel()
 
-func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) {
 	master := newTester()
 	defer master.terminate()
 	chain := testChainBase.shorten(15)
@@ -1561,7 +1609,7 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
 		{15000, 13006,
 			[]int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999},
 		},
-		//Remote is pretty close to us. We don't have to fetch as many
+		// Remote is pretty close to us. We don't have to fetch as many
 		{1200, 1150,
 			[]int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199},
 		},
@@ -1625,14 +1673,19 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
 
 // Tests that peers below a pre-configured checkpoint block are prevented from
 // being fast-synced from, avoiding potential cheap eclipse attacks.
-func TestCheckpointEnforcement62(t *testing.T)      { testCheckpointEnforcement(t, 62, FullSync) }
-func TestCheckpointEnforcement63Full(t *testing.T)  { testCheckpointEnforcement(t, 63, FullSync) }
-func TestCheckpointEnforcement63Fast(t *testing.T)  { testCheckpointEnforcement(t, 63, FastSync) }
-func TestCheckpointEnforcement64Full(t *testing.T)  { testCheckpointEnforcement(t, 64, FullSync) }
-func TestCheckpointEnforcement64Fast(t *testing.T)  { testCheckpointEnforcement(t, 64, FastSync) }
-func TestCheckpointEnforcement64Light(t *testing.T) { testCheckpointEnforcement(t, 64, LightSync) }
-
-func testCheckpointEnforcement(t *testing.T, protocol int, mode SyncMode) {
+func TestCheckpointEnforcement65Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH65, FullSync) }
+func TestCheckpointEnforcement65Fast(t *testing.T) { testCheckpointEnforcement(t, eth.ETH65, FastSync) }
+func TestCheckpointEnforcement65Light(t *testing.T) {
+	testCheckpointEnforcement(t, eth.ETH65, LightSync)
+}
+
+func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FullSync) }
+func TestCheckpointEnforcement66Fast(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FastSync) }
+func TestCheckpointEnforcement66Light(t *testing.T) {
+	testCheckpointEnforcement(t, eth.ETH66, LightSync)
+}
+
+func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) {
 	t.Parallel()
 
 	// Create a new tester with a particular hard coded checkpoint block
@@ -1649,7 +1702,7 @@ func testCheckpointEnforcement(t *testing.T, protocol int, mode SyncMode) {
 	if mode == FastSync || mode == LightSync {
 		expect = errUnsyncedPeer
 	}
-	if err := tester.sync("peer", nil, mode); err != expect {
+	if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) {
 		t.Fatalf("block sync error mismatch: have %v, want %v", err, expect)
 	}
 	if mode == FastSync || mode == LightSync {
diff --git a/eth/downloader/fakepeer.go b/eth/downloader/fakepeer.go
deleted file mode 100644
index 3ec90bc9ef..0000000000
--- a/eth/downloader/fakepeer.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright 2017 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 .
-
-package downloader
-
-import (
-	"math/big"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/ethdb"
-)
-
-// FakePeer is a mock downloader peer that operates on a local database instance
-// instead of being an actual live node. It's useful for testing and to implement
-// sync commands from an existing local database.
-type FakePeer struct {
-	id string
-	db ethdb.Database
-	hc *core.HeaderChain
-	dl *Downloader
-}
-
-// NewFakePeer creates a new mock downloader peer with the given data sources.
-func NewFakePeer(id string, db ethdb.Database, hc *core.HeaderChain, dl *Downloader) *FakePeer {
-	return &FakePeer{id: id, db: db, hc: hc, dl: dl}
-}
-
-// Head implements downloader.Peer, returning the current head hash and number
-// of the best known header.
-func (p *FakePeer) Head() (common.Hash, *big.Int) {
-	header := p.hc.CurrentHeader()
-	return header.Hash(), header.Number
-}
-
-// RequestHeadersByHash implements downloader.Peer, returning a batch of headers
-// defined by the origin hash and the associated query parameters.
-func (p *FakePeer) RequestHeadersByHash(hash common.Hash, amount int, skip int, reverse bool) error {
-	var (
-		headers []*types.Header
-		unknown bool
-	)
-	for !unknown && len(headers) < amount {
-		origin := p.hc.GetHeaderByHash(hash)
-		if origin == nil {
-			break
-		}
-		number := origin.Number.Uint64()
-		headers = append(headers, origin)
-		if reverse {
-			for i := 0; i <= skip; i++ {
-				if header := p.hc.GetHeader(hash, number); header != nil {
-					hash = header.ParentHash
-					number--
-				} else {
-					unknown = true
-					break
-				}
-			}
-		} else {
-			var (
-				current = origin.Number.Uint64()
-				next    = current + uint64(skip) + 1
-			)
-			if header := p.hc.GetHeaderByNumber(next); header != nil {
-				if p.hc.GetBlockHashesFromHash(header.Hash(), uint64(skip+1))[skip] == hash {
-					hash = header.Hash()
-				} else {
-					unknown = true
-				}
-			} else {
-				unknown = true
-			}
-		}
-	}
-	p.dl.DeliverHeaders(p.id, headers)
-	return nil
-}
-
-// RequestHeadersByNumber implements downloader.Peer, returning a batch of headers
-// defined by the origin number and the associated query parameters.
-func (p *FakePeer) RequestHeadersByNumber(number uint64, amount int, skip int, reverse bool) error {
-	var (
-		headers []*types.Header
-		unknown bool
-	)
-	for !unknown && len(headers) < amount {
-		origin := p.hc.GetHeaderByNumber(number)
-		if origin == nil {
-			break
-		}
-		if reverse {
-			if number >= uint64(skip+1) {
-				number -= uint64(skip + 1)
-			} else {
-				unknown = true
-			}
-		} else {
-			number += uint64(skip + 1)
-		}
-		headers = append(headers, origin)
-	}
-	p.dl.DeliverHeaders(p.id, headers)
-	return nil
-}
-
-// RequestBodies implements downloader.Peer, returning a batch of block bodies
-// corresponding to the specified block hashes.
-func (p *FakePeer) RequestBodies(hashes []common.Hash) error {
-	var (
-		txs    [][]*types.Transaction
-		uncles [][]*types.Header
-	)
-	for _, hash := range hashes {
-		block := rawdb.ReadBlock(p.db, hash, *p.hc.GetBlockNumber(hash))
-
-		txs = append(txs, block.Transactions())
-		uncles = append(uncles, block.Uncles())
-	}
-	p.dl.DeliverBodies(p.id, txs, uncles)
-	return nil
-}
-
-// RequestReceipts implements downloader.Peer, returning a batch of transaction
-// receipts corresponding to the specified block hashes.
-func (p *FakePeer) RequestReceipts(hashes []common.Hash) error {
-	var receipts [][]*types.Receipt
-	for _, hash := range hashes {
-		receipts = append(receipts, rawdb.ReadRawReceipts(p.db, hash, *p.hc.GetBlockNumber(hash)))
-	}
-	p.dl.DeliverReceipts(p.id, receipts)
-	return nil
-}
-
-// RequestNodeData implements downloader.Peer, returning a batch of state trie
-// nodes corresponding to the specified trie hashes.
-func (p *FakePeer) RequestNodeData(hashes []common.Hash) error {
-	var data [][]byte
-	for _, hash := range hashes {
-		if entry, err := p.db.Get(hash.Bytes()); err == nil {
-			data = append(data, entry)
-		}
-	}
-	p.dl.DeliverNodeData(p.id, data)
-	return nil
-}
diff --git a/eth/downloader/metrics.go b/eth/downloader/metrics.go
index d4eb337946..c38732043a 100644
--- a/eth/downloader/metrics.go
+++ b/eth/downloader/metrics.go
@@ -40,4 +40,6 @@ var (
 
 	stateInMeter   = metrics.NewRegisteredMeter("eth/downloader/states/in", nil)
 	stateDropMeter = metrics.NewRegisteredMeter("eth/downloader/states/drop", nil)
+
+	throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil)
 )
diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go
index 8ecdf91f11..3ea14d22d7 100644
--- a/eth/downloader/modes.go
+++ b/eth/downloader/modes.go
@@ -19,11 +19,13 @@ package downloader
 import "fmt"
 
 // SyncMode represents the synchronisation mode of the downloader.
-type SyncMode int
+// It is a uint32 as it is used with atomic operations.
+type SyncMode uint32
 
 const (
 	FullSync  SyncMode = iota // Synchronise the entire blockchain history from full blocks
-	FastSync                  // Quickly download the headers, full sync only at the chain head
+	FastSync                  // Quickly download the headers, full sync only at the chain
+	SnapSync                  // Download the chain and the state via compact snapshots
 	LightSync                 // Download only the headers and terminate afterwards
 )
 
@@ -38,6 +40,8 @@ func (mode SyncMode) String() string {
 		return "full"
 	case FastSync:
 		return "fast"
+	case SnapSync:
+		return "snap"
 	case LightSync:
 		return "light"
 	default:
@@ -51,6 +55,8 @@ func (mode SyncMode) MarshalText() ([]byte, error) {
 		return []byte("full"), nil
 	case FastSync:
 		return []byte("fast"), nil
+	case SnapSync:
+		return []byte("snap"), nil
 	case LightSync:
 		return []byte("light"), nil
 	default:
@@ -64,6 +70,8 @@ func (mode *SyncMode) UnmarshalText(text []byte) error {
 		*mode = FullSync
 	case "fast":
 		*mode = FastSync
+	case "snap":
+		*mode = SnapSync
 	case "light":
 		*mode = LightSync
 	default:
diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go
index 5c2020d7d8..b3b6cc95a0 100644
--- a/eth/downloader/peer.go
+++ b/eth/downloader/peer.go
@@ -21,7 +21,6 @@ package downloader
 
 import (
 	"errors"
-	"fmt"
 	"math"
 	"math/big"
 	"sort"
@@ -30,6 +29,7 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/log"
 )
@@ -70,7 +70,7 @@ type peerConnection struct {
 
 	peer Peer
 
-	version int        // Eth protocol version number to switch strategies
+	version uint       // Eth protocol version number to switch strategies
 	log     log.Logger // Contextual logger to add extra infos to peer logs
 	lock    sync.RWMutex
 }
@@ -113,13 +113,11 @@ func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error {
 }
 
 // newPeerConnection creates a new downloader peer.
-func newPeerConnection(id string, version int, peer Peer, logger log.Logger) *peerConnection {
+func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection {
 	return &peerConnection{
 		id:      id,
 		lacking: make(map[common.Hash]struct{}),
-
-		peer: peer,
-
+		peer:    peer,
 		version: version,
 		log:     logger,
 	}
@@ -145,17 +143,13 @@ func (p *peerConnection) Reset() {
 
 // FetchHeaders sends a header retrieval request to the remote peer.
 func (p *peerConnection) FetchHeaders(from uint64, count int) error {
-	// Sanity check the protocol version
-	if p.version < 62 {
-		panic(fmt.Sprintf("header fetch [eth/62+] requested on eth/%d", p.version))
-	}
 	// Short circuit if the peer is already fetching
 	if !atomic.CompareAndSwapInt32(&p.headerIdle, 0, 1) {
 		return errAlreadyFetching
 	}
 	p.headerStarted = time.Now()
 
-	// Issue the header retrieval request (absolut upwards without gaps)
+	// Issue the header retrieval request (absolute upwards without gaps)
 	go p.peer.RequestHeadersByNumber(from, count, 0, false)
 
 	return nil
@@ -163,54 +157,46 @@ func (p *peerConnection) FetchHeaders(from uint64, count int) error {
 
 // FetchBodies sends a block body retrieval request to the remote peer.
 func (p *peerConnection) FetchBodies(request *fetchRequest) error {
-	// Sanity check the protocol version
-	if p.version < 62 {
-		panic(fmt.Sprintf("body fetch [eth/62+] requested on eth/%d", p.version))
-	}
 	// Short circuit if the peer is already fetching
 	if !atomic.CompareAndSwapInt32(&p.blockIdle, 0, 1) {
 		return errAlreadyFetching
 	}
 	p.blockStarted = time.Now()
 
-	// Convert the header set to a retrievable slice
-	hashes := make([]common.Hash, 0, len(request.Headers))
-	for _, header := range request.Headers {
-		hashes = append(hashes, header.Hash())
-	}
-	go p.peer.RequestBodies(hashes)
+	go func() {
+		// Convert the header set to a retrievable slice
+		hashes := make([]common.Hash, 0, len(request.Headers))
+		for _, header := range request.Headers {
+			hashes = append(hashes, header.Hash())
+		}
+		p.peer.RequestBodies(hashes)
+	}()
 
 	return nil
 }
 
 // FetchReceipts sends a receipt retrieval request to the remote peer.
 func (p *peerConnection) FetchReceipts(request *fetchRequest) error {
-	// Sanity check the protocol version
-	if p.version < 63 {
-		panic(fmt.Sprintf("body fetch [eth/63+] requested on eth/%d", p.version))
-	}
 	// Short circuit if the peer is already fetching
 	if !atomic.CompareAndSwapInt32(&p.receiptIdle, 0, 1) {
 		return errAlreadyFetching
 	}
 	p.receiptStarted = time.Now()
 
-	// Convert the header set to a retrievable slice
-	hashes := make([]common.Hash, 0, len(request.Headers))
-	for _, header := range request.Headers {
-		hashes = append(hashes, header.Hash())
-	}
-	go p.peer.RequestReceipts(hashes)
+	go func() {
+		// Convert the header set to a retrievable slice
+		hashes := make([]common.Hash, 0, len(request.Headers))
+		for _, header := range request.Headers {
+			hashes = append(hashes, header.Hash())
+		}
+		p.peer.RequestReceipts(hashes)
+	}()
 
 	return nil
 }
 
 // FetchNodeData sends a node state data retrieval request to the remote peer.
 func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
-	// Sanity check the protocol version
-	if p.version < 63 {
-		panic(fmt.Sprintf("node data fetch [eth/63+] requested on eth/%d", p.version))
-	}
 	// Short circuit if the peer is already fetching
 	if !atomic.CompareAndSwapInt32(&p.stateIdle, 0, 1) {
 		return errAlreadyFetching
@@ -225,34 +211,34 @@ func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
 // SetHeadersIdle sets the peer to idle, allowing it to execute new header retrieval
 // requests. Its estimated header retrieval throughput is updated with that measured
 // just now.
-func (p *peerConnection) SetHeadersIdle(delivered int) {
-	p.setIdle(p.headerStarted, delivered, &p.headerThroughput, &p.headerIdle)
+func (p *peerConnection) SetHeadersIdle(delivered int, deliveryTime time.Time) {
+	p.setIdle(deliveryTime.Sub(p.headerStarted), delivered, &p.headerThroughput, &p.headerIdle)
 }
 
 // SetBodiesIdle sets the peer to idle, allowing it to execute block body retrieval
 // requests. Its estimated body retrieval throughput is updated with that measured
 // just now.
-func (p *peerConnection) SetBodiesIdle(delivered int) {
-	p.setIdle(p.blockStarted, delivered, &p.blockThroughput, &p.blockIdle)
+func (p *peerConnection) SetBodiesIdle(delivered int, deliveryTime time.Time) {
+	p.setIdle(deliveryTime.Sub(p.blockStarted), delivered, &p.blockThroughput, &p.blockIdle)
 }
 
 // SetReceiptsIdle sets the peer to idle, allowing it to execute new receipt
 // retrieval requests. Its estimated receipt retrieval throughput is updated
 // with that measured just now.
-func (p *peerConnection) SetReceiptsIdle(delivered int) {
-	p.setIdle(p.receiptStarted, delivered, &p.receiptThroughput, &p.receiptIdle)
+func (p *peerConnection) SetReceiptsIdle(delivered int, deliveryTime time.Time) {
+	p.setIdle(deliveryTime.Sub(p.receiptStarted), delivered, &p.receiptThroughput, &p.receiptIdle)
 }
 
 // SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie
 // data retrieval requests. Its estimated state retrieval throughput is updated
 // with that measured just now.
-func (p *peerConnection) SetNodeDataIdle(delivered int) {
-	p.setIdle(p.stateStarted, delivered, &p.stateThroughput, &p.stateIdle)
+func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) {
+	p.setIdle(deliveryTime.Sub(p.stateStarted), delivered, &p.stateThroughput, &p.stateIdle)
 }
 
 // setIdle sets the peer to idle, allowing it to execute new retrieval requests.
 // Its estimated retrieval throughput is updated with that measured just now.
-func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *float64, idle *int32) {
+func (p *peerConnection) setIdle(elapsed time.Duration, delivered int, throughput *float64, idle *int32) {
 	// Irrelevant of the scaling, make sure the peer ends up idle
 	defer atomic.StoreInt32(idle, 0)
 
@@ -265,7 +251,9 @@ func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *f
 		return
 	}
 	// Otherwise update the throughput with a new measurement
-	elapsed := time.Since(started) + 1 // +1 (ns) to ensure non-zero divisor
+	if elapsed <= 0 {
+		elapsed = 1 // +1 (ns) to ensure non-zero divisor
+	}
 	measured := float64(delivered) / (float64(elapsed) / float64(time.Second))
 
 	*throughput = (1-measurementImpact)*(*throughput) + measurementImpact*measured
@@ -421,7 +409,7 @@ func (ps *peerSet) Unregister(id string) error {
 	ps.lock.Lock()
 	p, ok := ps.peers[id]
 	if !ok {
-		defer ps.lock.Unlock()
+		ps.lock.Unlock()
 		return errNotRegistered
 	}
 	delete(ps.peers, id)
@@ -470,7 +458,7 @@ func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) {
 		defer p.lock.RUnlock()
 		return p.headerThroughput
 	}
-	return ps.idlePeers(62, 65, idle, throughput)
+	return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
 }
 
 // BodyIdlePeers retrieves a flat list of all the currently body-idle peers within
@@ -484,7 +472,7 @@ func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) {
 		defer p.lock.RUnlock()
 		return p.blockThroughput
 	}
-	return ps.idlePeers(62, 65, idle, throughput)
+	return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
 }
 
 // ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers
@@ -498,7 +486,7 @@ func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) {
 		defer p.lock.RUnlock()
 		return p.receiptThroughput
 	}
-	return ps.idlePeers(63, 65, idle, throughput)
+	return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
 }
 
 // NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle
@@ -512,33 +500,31 @@ func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) {
 		defer p.lock.RUnlock()
 		return p.stateThroughput
 	}
-	return ps.idlePeers(63, 65, idle, throughput)
+	return ps.idlePeers(eth.ETH65, eth.ETH66, idle, throughput)
 }
 
 // idlePeers retrieves a flat list of all currently idle peers satisfying the
 // protocol version constraints, using the provided function to check idleness.
 // The resulting set of peers are sorted by their measure throughput.
-func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) {
+func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, throughput func(*peerConnection) float64) ([]*peerConnection, int) {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
 	idle, total := make([]*peerConnection, 0, len(ps.peers)), 0
+	tps := make([]float64, 0, len(ps.peers))
 	for _, p := range ps.peers {
 		if p.version >= minProtocol && p.version <= maxProtocol {
 			if idleCheck(p) {
 				idle = append(idle, p)
+				tps = append(tps, throughput(p))
 			}
 			total++
 		}
 	}
-	for i := 0; i < len(idle); i++ {
-		for j := i + 1; j < len(idle); j++ {
-			if throughput(idle[i]) < throughput(idle[j]) {
-				idle[i], idle[j] = idle[j], idle[i]
-			}
-		}
-	}
-	return idle, total
+	// And sort them
+	sortPeers := &peerThroughputSort{idle, tps}
+	sort.Sort(sortPeers)
+	return sortPeers.p, total
 }
 
 // medianRTT returns the median RTT of the peerset, considering only the tuning
@@ -571,3 +557,24 @@ func (ps *peerSet) medianRTT() time.Duration {
 	}
 	return median
 }
+
+// peerThroughputSort implements the Sort interface, and allows for
+// sorting a set of peers by their throughput
+// The sorted data is with the _highest_ throughput first
+type peerThroughputSort struct {
+	p  []*peerConnection
+	tp []float64
+}
+
+func (ps *peerThroughputSort) Len() int {
+	return len(ps.p)
+}
+
+func (ps *peerThroughputSort) Less(i, j int) bool {
+	return ps.tp[i] > ps.tp[j]
+}
+
+func (ps *peerThroughputSort) Swap(i, j int) {
+	ps.p[i], ps.p[j] = ps.p[j], ps.p[i]
+	ps.tp[i], ps.tp[j] = ps.tp[j], ps.tp[i]
+}
diff --git a/eth/downloader/peer_test.go b/eth/downloader/peer_test.go
new file mode 100644
index 0000000000..4bf0e200bb
--- /dev/null
+++ b/eth/downloader/peer_test.go
@@ -0,0 +1,53 @@
+// 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 .
+
+package downloader
+
+import (
+	"sort"
+	"testing"
+)
+
+func TestPeerThroughputSorting(t *testing.T) {
+	a := &peerConnection{
+		id:               "a",
+		headerThroughput: 1.25,
+	}
+	b := &peerConnection{
+		id:               "b",
+		headerThroughput: 1.21,
+	}
+	c := &peerConnection{
+		id:               "c",
+		headerThroughput: 1.23,
+	}
+
+	peers := []*peerConnection{a, b, c}
+	tps := []float64{a.headerThroughput,
+		b.headerThroughput, c.headerThroughput}
+	sortPeers := &peerThroughputSort{peers, tps}
+	sort.Sort(sortPeers)
+	if got, exp := sortPeers.p[0].id, "a"; got != exp {
+		t.Errorf("sort fail, got %v exp %v", got, exp)
+	}
+	if got, exp := sortPeers.p[1].id, "c"; got != exp {
+		t.Errorf("sort fail, got %v exp %v", got, exp)
+	}
+	if got, exp := sortPeers.p[2].id, "b"; got != exp {
+		t.Errorf("sort fail, got %v exp %v", got, exp)
+	}
+
+}
diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go
index 1ec432faff..ac7edc2c68 100644
--- a/eth/downloader/queue.go
+++ b/eth/downloader/queue.go
@@ -23,6 +23,7 @@ import (
 	"errors"
 	"fmt"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -30,12 +31,19 @@ import (
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+const (
+	bodyType    = uint(0)
+	receiptType = uint(1)
 )
 
 var (
-	blockCacheItems      = 8192             // Maximum number of blocks to cache before throttling the download
-	blockCacheMemory     = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching
-	blockCacheSizeWeight = 0.1              // Multiplier to approximate the average block size based on past ones
+	blockCacheMaxItems     = 8192             // Maximum number of blocks to cache before throttling the download
+	blockCacheInitialItems = 2048             // Initial number of blocks to start fetching, before we know the sizes of the blocks
+	blockCacheMemory       = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching
+	blockCacheSizeWeight   = 0.1              // Multiplier to approximate the average block size based on past ones
 )
 
 var (
@@ -54,8 +62,7 @@ type fetchRequest struct {
 // fetchResult is a struct collecting partial results from data fetchers until
 // all outstanding pieces complete and the result as a whole can be processed.
 type fetchResult struct {
-	Pending int         // Number of data fetches still pending
-	Hash    common.Hash // Hash of the header to prevent recalculating
+	pending int32 // Flag telling what deliveries are outstanding
 
 	Header       *types.Header
 	Uncles       []*types.Header
@@ -63,63 +70,94 @@ type fetchResult struct {
 	Receipts     types.Receipts
 }
 
+func newFetchResult(header *types.Header, fastSync bool) *fetchResult {
+	item := &fetchResult{
+		Header: header,
+	}
+	if !header.EmptyBody() {
+		item.pending |= (1 << bodyType)
+	}
+	if fastSync && !header.EmptyReceipts() {
+		item.pending |= (1 << receiptType)
+	}
+	return item
+}
+
+// SetBodyDone flags the body as finished.
+func (f *fetchResult) SetBodyDone() {
+	if v := atomic.LoadInt32(&f.pending); (v & (1 << bodyType)) != 0 {
+		atomic.AddInt32(&f.pending, -1)
+	}
+}
+
+// AllDone checks if item is done.
+func (f *fetchResult) AllDone() bool {
+	return atomic.LoadInt32(&f.pending) == 0
+}
+
+// SetReceiptsDone flags the receipts as finished.
+func (f *fetchResult) SetReceiptsDone() {
+	if v := atomic.LoadInt32(&f.pending); (v & (1 << receiptType)) != 0 {
+		atomic.AddInt32(&f.pending, -2)
+	}
+}
+
+// Done checks if the given type is done already
+func (f *fetchResult) Done(kind uint) bool {
+	v := atomic.LoadInt32(&f.pending)
+	return v&(1< common.StorageSize(blockCacheMemory) {
-		limit = int((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
-	}
-	// Calculate the number of slots already finished
-	finished := 0
-	for _, result := range q.resultCache[:limit] {
-		if result == nil {
-			break
-		}
-		if _, ok := donePool[result.Hash]; ok {
-			finished++
-		}
-	}
-	// Calculate the number of slots currently downloading
-	pending := 0
-	for _, request := range pendPool {
-		for _, header := range request.Headers {
-			if header.Number.Uint64() < q.resultOffset+uint64(limit) {
-				pending++
-			}
-		}
-	}
-	// Return the free slots to distribute
-	return limit - finished - pending
+	return (queued + pending) == 0
 }
 
 // ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
@@ -324,21 +309,22 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
 			break
 		}
 		// Make sure no duplicate requests are executed
+		// We cannot skip this, even if the block is empty, since this is
+		// what triggers the fetchResult creation.
 		if _, ok := q.blockTaskPool[hash]; ok {
 			log.Warn("Header already scheduled for block fetch", "number", header.Number, "hash", hash)
-			continue
-		}
-		if _, ok := q.receiptTaskPool[hash]; ok {
-			log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash)
-			continue
+		} else {
+			q.blockTaskPool[hash] = header
+			q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
 		}
-		// Queue the header for content retrieval
-		q.blockTaskPool[hash] = header
-		q.blockTaskQueue.Push(header, -int64(header.Number.Uint64()))
-
-		if q.mode == FastSync {
-			q.receiptTaskPool[hash] = header
-			q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64()))
+		// Queue for receipt retrieval
+		if q.mode == FastSync && !header.EmptyReceipts() {
+			if _, ok := q.receiptTaskPool[hash]; ok {
+				log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash)
+			} else {
+				q.receiptTaskPool[hash] = header
+				q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64()))
+			}
 		}
 		inserts = append(inserts, header)
 		q.headerHead = hash
@@ -349,66 +335,77 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
 
 // Results retrieves and permanently removes a batch of fetch results from
 // the cache. the result slice will be empty if the queue has been closed.
+// Results can be called concurrently with Deliver and Schedule,
+// but assumes that there are not two simultaneous callers to Results
 func (q *queue) Results(block bool) []*fetchResult {
-	q.lock.Lock()
-	defer q.lock.Unlock()
-
-	// Count the number of items available for processing
-	nproc := q.countProcessableItems()
-	for nproc == 0 && !q.closed {
-		if !block {
-			return nil
+	// Abort early if there are no items and non-blocking requested
+	if !block && !q.resultCache.HasCompletedItems() {
+		return nil
+	}
+	closed := false
+	for !closed && !q.resultCache.HasCompletedItems() {
+		// In order to wait on 'active', we need to obtain the lock.
+		// That may take a while, if someone is delivering at the same
+		// time, so after obtaining the lock, we check again if there
+		// are any results to fetch.
+		// Also, in-between we ask for the lock and the lock is obtained,
+		// someone can have closed the queue. In that case, we should
+		// return the available results and stop blocking
+		q.lock.Lock()
+		if q.resultCache.HasCompletedItems() || q.closed {
+			q.lock.Unlock()
+			break
 		}
+		// No items available, and not closed
 		q.active.Wait()
-		nproc = q.countProcessableItems()
-	}
-	// Since we have a batch limit, don't pull more into "dangling" memory
-	if nproc > maxResultsProcess {
-		nproc = maxResultsProcess
-	}
-	results := make([]*fetchResult, nproc)
-	copy(results, q.resultCache[:nproc])
-	if len(results) > 0 {
-		// Mark results as done before dropping them from the cache.
-		for _, result := range results {
-			hash := result.Header.Hash()
-			delete(q.blockDonePool, hash)
-			delete(q.receiptDonePool, hash)
+		closed = q.closed
+		q.lock.Unlock()
+	}
+	// Regardless if closed or not, we can still deliver whatever we have
+	results := q.resultCache.GetCompleted(maxResultsProcess)
+	for _, result := range results {
+		// Recalculate the result item weights to prevent memory exhaustion
+		size := result.Header.Size()
+		for _, uncle := range result.Uncles {
+			size += uncle.Size()
 		}
-		// Delete the results from the cache and clear the tail.
-		copy(q.resultCache, q.resultCache[nproc:])
-		for i := len(q.resultCache) - nproc; i < len(q.resultCache); i++ {
-			q.resultCache[i] = nil
+		for _, receipt := range result.Receipts {
+			size += receipt.Size()
 		}
-		// Advance the expected block number of the first cache entry.
-		q.resultOffset += uint64(nproc)
-
-		// Recalculate the result item weights to prevent memory exhaustion
-		for _, result := range results {
-			size := result.Header.Size()
-			for _, uncle := range result.Uncles {
-				size += uncle.Size()
-			}
-			for _, receipt := range result.Receipts {
-				size += receipt.Size()
-			}
-			for _, tx := range result.Transactions {
-				size += tx.Size()
-			}
-			q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
+		for _, tx := range result.Transactions {
+			size += tx.Size()
 		}
+		q.resultSize = common.StorageSize(blockCacheSizeWeight)*size +
+			(1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
+	}
+	// Using the newly calibrated resultsize, figure out the new throttle limit
+	// on the result cache
+	throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
+	throttleThreshold = q.resultCache.SetThrottleThreshold(throttleThreshold)
+
+	// Log some info at certain times
+	if time.Since(q.lastStatLog) > 60*time.Second {
+		q.lastStatLog = time.Now()
+		info := q.Stats()
+		info = append(info, "throttle", throttleThreshold)
+		log.Info("Downloader queue stats", info...)
 	}
 	return results
 }
 
-// countProcessableItems counts the processable items.
-func (q *queue) countProcessableItems() int {
-	for i, result := range q.resultCache {
-		if result == nil || result.Pending > 0 {
-			return i
-		}
+func (q *queue) Stats() []interface{} {
+	q.lock.RLock()
+	defer q.lock.RUnlock()
+
+	return q.stats()
+}
+
+func (q *queue) stats() []interface{} {
+	return []interface{}{
+		"receiptTasks", q.receiptTaskQueue.Size(),
+		"blockTasks", q.blockTaskQueue.Size(),
+		"itemSize", q.resultSize,
 	}
-	return len(q.resultCache)
 }
 
 // ReserveHeaders reserves a set of headers for the given peer, skipping any
@@ -454,27 +451,21 @@ func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest {
 // ReserveBodies reserves a set of body fetches for the given peer, skipping any
 // previously failed downloads. Beside the next batch of needed fetches, it also
 // returns a flag whether empty blocks were queued requiring processing.
-func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, error) {
-	isNoop := func(header *types.Header) bool {
-		return header.TxHash == types.EmptyRootHash && header.UncleHash == types.EmptyUncleHash
-	}
+func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, bool) {
 	q.lock.Lock()
 	defer q.lock.Unlock()
 
-	return q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, q.blockDonePool, isNoop)
+	return q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, bodyType)
 }
 
 // ReserveReceipts reserves a set of receipt fetches for the given peer, skipping
 // any previously failed downloads. Beside the next batch of needed fetches, it
 // also returns a flag whether empty receipts were queued requiring importing.
-func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, error) {
-	isNoop := func(header *types.Header) bool {
-		return header.ReceiptHash == types.EmptyRootHash
-	}
+func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, bool) {
 	q.lock.Lock()
 	defer q.lock.Unlock()
 
-	return q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, q.receiptDonePool, isNoop)
+	return q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, receiptType)
 }
 
 // reserveHeaders reserves a set of data download operations for a given peer,
@@ -484,57 +475,71 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo
 // Note, this method expects the queue lock to be already held for writing. The
 // reason the lock is not obtained in here is because the parameters already need
 // to access the queue, so they already need a lock anyway.
+//
+// Returns:
+//   item     - the fetchRequest
+//   progress - whether any progress was made
+//   throttle - if the caller should throttle for a while
 func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque,
-	pendPool map[string]*fetchRequest, donePool map[common.Hash]struct{}, isNoop func(*types.Header) bool) (*fetchRequest, bool, error) {
+	pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) {
 	// Short circuit if the pool has been depleted, or if the peer's already
 	// downloading something (sanity check not to corrupt state)
 	if taskQueue.Empty() {
-		return nil, false, nil
+		return nil, false, true
 	}
 	if _, ok := pendPool[p.id]; ok {
-		return nil, false, nil
+		return nil, false, false
 	}
-	// Calculate an upper limit on the items we might fetch (i.e. throttling)
-	space := q.resultSlots(pendPool, donePool)
-
 	// Retrieve a batch of tasks, skipping previously failed ones
 	send := make([]*types.Header, 0, count)
 	skip := make([]*types.Header, 0)
-
 	progress := false
-	for proc := 0; proc < space && len(send) < count && !taskQueue.Empty(); proc++ {
-		header := taskQueue.PopItem().(*types.Header)
-		hash := header.Hash()
-
-		// If we're the first to request this task, initialise the result container
-		index := int(header.Number.Int64() - int64(q.resultOffset))
-		if index >= len(q.resultCache) || index < 0 {
-			common.Report("index allocation went beyond available resultCache space")
-			return nil, false, errInvalidChain
+	throttled := false
+	for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ {
+		// the task queue will pop items in order, so the highest prio block
+		// is also the lowest block number.
+		h, _ := taskQueue.Peek()
+		header := h.(*types.Header)
+		// we can ask the resultcache if this header is within the
+		// "prioritized" segment of blocks. If it is not, we need to throttle
+
+		stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == FastSync)
+		if stale {
+			// Don't put back in the task queue, this item has already been
+			// delivered upstream
+			taskQueue.PopItem()
+			progress = true
+			delete(taskPool, header.Hash())
+			proc = proc - 1
+			log.Error("Fetch reservation already delivered", "number", header.Number.Uint64())
+			continue
 		}
-		if q.resultCache[index] == nil {
-			components := 1
-			if q.mode == FastSync {
-				components = 2
-			}
-			q.resultCache[index] = &fetchResult{
-				Pending: components,
-				Hash:    hash,
-				Header:  header,
-			}
+		if throttle {
+			// There are no resultslots available. Leave it in the task queue
+			// However, if there are any left as 'skipped', we should not tell
+			// the caller to throttle, since we still want some other
+			// peer to fetch those for us
+			throttled = len(skip) == 0
+			break
 		}
-		// If this fetch task is a noop, skip this fetch operation
-		if isNoop(header) {
-			donePool[hash] = struct{}{}
-			delete(taskPool, hash)
-
-			space, proc = space-1, proc-1
-			q.resultCache[index].Pending--
+		if err != nil {
+			// this most definitely should _not_ happen
+			log.Warn("Failed to reserve headers", "err", err)
+			// There are no resultslots available. Leave it in the task queue
+			break
+		}
+		if item.Done(kind) {
+			// If it's a noop, we can skip this task
+			delete(taskPool, header.Hash())
+			taskQueue.PopItem()
+			proc = proc - 1
 			progress = true
 			continue
 		}
+		// Remove it from the task queue
+		taskQueue.PopItem()
 		// Otherwise unless the peer is known not to have the data, add to the retrieve list
-		if p.Lacks(hash) {
+		if p.Lacks(header.Hash()) {
 			skip = append(skip, header)
 		} else {
 			send = append(send, header)
@@ -544,13 +549,13 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
 	for _, header := range skip {
 		taskQueue.Push(header, -int64(header.Number.Uint64()))
 	}
-	if progress {
+	if q.resultCache.HasCompletedItems() {
 		// Wake Results, resultCache was modified
 		q.active.Signal()
 	}
 	// Assemble and return the block download request
 	if len(send) == 0 {
-		return nil, progress, nil
+		return nil, progress, throttled
 	}
 	request := &fetchRequest{
 		Peer:    p,
@@ -558,8 +563,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
 		Time:    time.Now(),
 	}
 	pendPool[p.id] = request
-
-	return request, progress, nil
+	return request, progress, throttled
 }
 
 // CancelHeaders aborts a fetch request, returning all pending skeleton indexes to the queue.
@@ -686,6 +690,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
 	q.lock.Lock()
 	defer q.lock.Unlock()
 
+	var logger log.Logger
+	if len(id) < 16 {
+		// Tests use short IDs, don't choke on them
+		logger = log.New("peer", id)
+	} else {
+		logger = log.New("peer", id[:16])
+	}
 	// Short circuit if the data was never requested
 	request := q.headerPendPool[id]
 	if request == nil {
@@ -700,31 +711,34 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
 	accepted := len(headers) == MaxHeaderFetch
 	if accepted {
 		if headers[0].Number.Uint64() != request.From {
-			log.Trace("First header broke chain ordering", "peer", id, "number", headers[0].Number, "hash", headers[0].Hash(), request.From)
+			logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", headers[0].Hash(), "expected", request.From)
 			accepted = false
 		} else if headers[len(headers)-1].Hash() != target {
-			log.Trace("Last header broke skeleton structure ", "peer", id, "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target)
+			logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target)
 			accepted = false
 		}
 	}
 	if accepted {
+		parentHash := headers[0].Hash()
 		for i, header := range headers[1:] {
 			hash := header.Hash()
 			if want := request.From + 1 + uint64(i); header.Number.Uint64() != want {
-				log.Warn("Header broke chain ordering", "peer", id, "number", header.Number, "hash", hash, "expected", want)
+				logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want)
 				accepted = false
 				break
 			}
-			if headers[i].Hash() != header.ParentHash {
-				log.Warn("Header broke chain ancestry", "peer", id, "number", header.Number, "hash", hash)
+			if parentHash != header.ParentHash {
+				logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash)
 				accepted = false
 				break
 			}
+			// Set-up parent hash for next round
+			parentHash = hash
 		}
 	}
 	// If the batch of headers wasn't accepted, mark as unavailable
 	if !accepted {
-		log.Trace("Skeleton filling not accepted", "peer", id, "from", request.From)
+		logger.Trace("Skeleton filling not accepted", "from", request.From)
 
 		miss := q.headerPeerMiss[id]
 		if miss == nil {
@@ -751,7 +765,7 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
 
 		select {
 		case headerProcCh <- process:
-			log.Trace("Pre-scheduled new headers", "peer", id, "count", len(process), "from", process[0].Number)
+			logger.Trace("Pre-scheduled new headers", "count", len(process), "from", process[0].Number)
 			q.headerProced += len(process)
 		default:
 		}
@@ -769,16 +783,23 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
 func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLists [][]*types.Header) (int, error) {
 	q.lock.Lock()
 	defer q.lock.Unlock()
-
-	reconstruct := func(header *types.Header, index int, result *fetchResult) error {
-		if types.DeriveSha(types.Transactions(txLists[index])) != header.TxHash || types.CalcUncleHash(uncleLists[index]) != header.UncleHash {
+	validate := func(index int, header *types.Header) error {
+		if types.DeriveSha(types.Transactions(txLists[index]), trie.NewStackTrie(nil)) != header.TxHash {
 			return errInvalidBody
 		}
+		if types.CalcUncleHash(uncleLists[index]) != header.UncleHash {
+			return errInvalidBody
+		}
+		return nil
+	}
+
+	reconstruct := func(index int, result *fetchResult) {
 		result.Transactions = txLists[index]
 		result.Uncles = uncleLists[index]
-		return nil
+		result.SetBodyDone()
 	}
-	return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, q.blockDonePool, bodyReqTimer, len(txLists), reconstruct)
+	return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool,
+		bodyReqTimer, len(txLists), validate, reconstruct)
 }
 
 // DeliverReceipts injects a receipt retrieval response into the results queue.
@@ -787,25 +808,29 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi
 func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, error) {
 	q.lock.Lock()
 	defer q.lock.Unlock()
-
-	reconstruct := func(header *types.Header, index int, result *fetchResult) error {
-		if types.DeriveSha(types.Receipts(receiptList[index])) != header.ReceiptHash {
+	validate := func(index int, header *types.Header) error {
+		if types.DeriveSha(types.Receipts(receiptList[index]), trie.NewStackTrie(nil)) != header.ReceiptHash {
 			return errInvalidReceipt
 		}
-		result.Receipts = receiptList[index]
 		return nil
 	}
-	return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, q.receiptDonePool, receiptReqTimer, len(receiptList), reconstruct)
+	reconstruct := func(index int, result *fetchResult) {
+		result.Receipts = receiptList[index]
+		result.SetReceiptsDone()
+	}
+	return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool,
+		receiptReqTimer, len(receiptList), validate, reconstruct)
 }
 
 // deliver injects a data retrieval response into the results queue.
 //
 // Note, this method expects the queue lock to be already held for writing. The
-// reason the lock is not obtained in here is because the parameters already need
+// reason this lock is not obtained in here is because the parameters already need
 // to access the queue, so they already need a lock anyway.
-func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque,
-	pendPool map[string]*fetchRequest, donePool map[common.Hash]struct{}, reqTimer metrics.Timer,
-	results int, reconstruct func(header *types.Header, index int, result *fetchResult) error) (int, error) {
+func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
+	taskQueue *prque.Prque, pendPool map[string]*fetchRequest, reqTimer metrics.Timer,
+	results int, validate func(index int, header *types.Header) error,
+	reconstruct func(index int, result *fetchResult)) (int, error) {
 
 	// Short circuit if the data was never requested
 	request := pendPool[id]
@@ -825,53 +850,53 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, taskQ
 	var (
 		accepted int
 		failure  error
-		useful   bool
+		i        int
+		hashes   []common.Hash
 	)
-	for i, header := range request.Headers {
+	for _, header := range request.Headers {
 		// Short circuit assembly if no more fetch results are found
 		if i >= results {
 			break
 		}
-		// Reconstruct the next result if contents match up
-		index := int(header.Number.Int64() - int64(q.resultOffset))
-		if index >= len(q.resultCache) || index < 0 || q.resultCache[index] == nil {
-			failure = errInvalidChain
-			break
-		}
-		if err := reconstruct(header, i, q.resultCache[index]); err != nil {
+		// Validate the fields
+		if err := validate(i, header); err != nil {
 			failure = err
 			break
 		}
-		hash := header.Hash()
-
-		donePool[hash] = struct{}{}
-		q.resultCache[index].Pending--
-		useful = true
-		accepted++
+		hashes = append(hashes, header.Hash())
+		i++
+	}
 
+	for _, header := range request.Headers[:i] {
+		if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil {
+			reconstruct(accepted, res)
+		} else {
+			// else: betweeen here and above, some other peer filled this result,
+			// or it was indeed a no-op. This should not happen, but if it does it's
+			// not something to panic about
+			log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err)
+			failure = errStaleDelivery
+		}
 		// Clean up a successful fetch
-		request.Headers[i] = nil
-		delete(taskPool, hash)
+		delete(taskPool, hashes[accepted])
+		accepted++
 	}
 	// Return all failed or missing fetches to the queue
-	for _, header := range request.Headers {
-		if header != nil {
-			taskQueue.Push(header, -int64(header.Number.Uint64()))
-		}
+	for _, header := range request.Headers[accepted:] {
+		taskQueue.Push(header, -int64(header.Number.Uint64()))
 	}
 	// Wake up Results
 	if accepted > 0 {
 		q.active.Signal()
 	}
+	if failure == nil {
+		return accepted, nil
+	}
 	// If none of the data was good, it's a stale delivery
-	switch {
-	case failure == nil || failure == errInvalidChain:
-		return accepted, failure
-	case useful:
+	if accepted > 0 {
 		return accepted, fmt.Errorf("partial failure: %v", failure)
-	default:
-		return accepted, errStaleDelivery
 	}
+	return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery)
 }
 
 // Prepare configures the result cache to allow accepting and caching inbound
@@ -881,8 +906,6 @@ func (q *queue) Prepare(offset uint64, mode SyncMode) {
 	defer q.lock.Unlock()
 
 	// Prepare the queue for sync results
-	if q.resultOffset < offset {
-		q.resultOffset = offset
-	}
+	q.resultCache.Prepare(offset)
 	q.mode = mode
 }
diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go
new file mode 100644
index 0000000000..f43ad67a41
--- /dev/null
+++ b/eth/downloader/queue_test.go
@@ -0,0 +1,452 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package downloader
+
+import (
+	"fmt"
+	"math/big"
+	"math/rand"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+var (
+	testdb  = rawdb.NewMemoryDatabase()
+	genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000))
+)
+
+// makeChain creates a chain of n blocks starting at and including parent.
+// the returned hash chain is ordered head->parent. In addition, every 3rd block
+// contains a transaction and every 5th an uncle to allow testing correct block
+// reassembly.
+func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Block, []types.Receipts) {
+	blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) {
+		block.SetCoinbase(common.Address{seed})
+		// Add one tx to every secondblock
+		if !empty && i%2 == 0 {
+			signer := types.MakeSigner(params.TestChainConfig, block.Number())
+			tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
+			if err != nil {
+				panic(err)
+			}
+			block.AddTx(tx)
+		}
+	})
+	return blocks, receipts
+}
+
+type chainData struct {
+	blocks []*types.Block
+	offset int
+}
+
+var chain *chainData
+var emptyChain *chainData
+
+func init() {
+	// Create a chain of blocks to import
+	targetBlocks := 128
+	blocks, _ := makeChain(targetBlocks, 0, genesis, false)
+	chain = &chainData{blocks, 0}
+
+	blocks, _ = makeChain(targetBlocks, 0, genesis, true)
+	emptyChain = &chainData{blocks, 0}
+}
+
+func (chain *chainData) headers() []*types.Header {
+	hdrs := make([]*types.Header, len(chain.blocks))
+	for i, b := range chain.blocks {
+		hdrs[i] = b.Header()
+	}
+	return hdrs
+}
+
+func (chain *chainData) Len() int {
+	return len(chain.blocks)
+}
+
+func dummyPeer(id string) *peerConnection {
+	p := &peerConnection{
+		id:      id,
+		lacking: make(map[common.Hash]struct{}),
+	}
+	return p
+}
+
+func TestBasics(t *testing.T) {
+	numOfBlocks := len(emptyChain.blocks)
+	numOfReceipts := len(emptyChain.blocks) / 2
+
+	q := newQueue(10, 10)
+	if !q.Idle() {
+		t.Errorf("new queue should be idle")
+	}
+	q.Prepare(1, FastSync)
+	if res := q.Results(false); len(res) != 0 {
+		t.Fatal("new queue should have 0 results")
+	}
+
+	// Schedule a batch of headers
+	q.Schedule(chain.headers(), 1)
+	if q.Idle() {
+		t.Errorf("queue should not be idle")
+	}
+	if got, exp := q.PendingBlocks(), chain.Len(); got != exp {
+		t.Errorf("wrong pending block count, got %d, exp %d", got, exp)
+	}
+	// Only non-empty receipts get added to task-queue
+	if got, exp := q.PendingReceipts(), 64; got != exp {
+		t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp)
+	}
+	// Items are now queued for downloading, next step is that we tell the
+	// queue that a certain peer will deliver them for us
+	{
+		peer := dummyPeer("peer-1")
+		fetchReq, _, throttle := q.ReserveBodies(peer, 50)
+		if !throttle {
+			// queue size is only 10, so throttling should occur
+			t.Fatal("should throttle")
+		}
+		// But we should still get the first things to fetch
+		if got, exp := len(fetchReq.Headers), 5; got != exp {
+			t.Fatalf("expected %d requests, got %d", exp, got)
+		}
+		if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp {
+			t.Fatalf("expected header %d, got %d", exp, got)
+		}
+	}
+	if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got {
+		t.Errorf("expected block task queue to be %d, got %d", exp, got)
+	}
+	if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got {
+		t.Errorf("expected receipt task queue to be %d, got %d", exp, got)
+	}
+	{
+		peer := dummyPeer("peer-2")
+		fetchReq, _, throttle := q.ReserveBodies(peer, 50)
+
+		// The second peer should hit throttling
+		if !throttle {
+			t.Fatalf("should not throttle")
+		}
+		// And not get any fetches at all, since it was throttled to begin with
+		if fetchReq != nil {
+			t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers))
+		}
+	}
+	if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got {
+		t.Errorf("expected block task queue to be %d, got %d", exp, got)
+	}
+	if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got {
+		t.Errorf("expected receipt task queue to be %d, got %d", exp, got)
+	}
+	{
+		// The receipt delivering peer should not be affected
+		// by the throttling of body deliveries
+		peer := dummyPeer("peer-3")
+		fetchReq, _, throttle := q.ReserveReceipts(peer, 50)
+		if !throttle {
+			// queue size is only 10, so throttling should occur
+			t.Fatal("should throttle")
+		}
+		// But we should still get the first things to fetch
+		if got, exp := len(fetchReq.Headers), 5; got != exp {
+			t.Fatalf("expected %d requests, got %d", exp, got)
+		}
+		if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp {
+			t.Fatalf("expected header %d, got %d", exp, got)
+		}
+
+	}
+	if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got {
+		t.Errorf("expected block task queue to be %d, got %d", exp, got)
+	}
+	if exp, got := q.receiptTaskQueue.Size(), numOfReceipts-5; exp != got {
+		t.Errorf("expected receipt task queue to be %d, got %d", exp, got)
+	}
+	if got, exp := q.resultCache.countCompleted(), 0; got != exp {
+		t.Errorf("wrong processable count, got %d, exp %d", got, exp)
+	}
+}
+
+func TestEmptyBlocks(t *testing.T) {
+	numOfBlocks := len(emptyChain.blocks)
+
+	q := newQueue(10, 10)
+
+	q.Prepare(1, FastSync)
+	// Schedule a batch of headers
+	q.Schedule(emptyChain.headers(), 1)
+	if q.Idle() {
+		t.Errorf("queue should not be idle")
+	}
+	if got, exp := q.PendingBlocks(), len(emptyChain.blocks); got != exp {
+		t.Errorf("wrong pending block count, got %d, exp %d", got, exp)
+	}
+	if got, exp := q.PendingReceipts(), 0; got != exp {
+		t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp)
+	}
+	// They won't be processable, because the fetchresults haven't been
+	// created yet
+	if got, exp := q.resultCache.countCompleted(), 0; got != exp {
+		t.Errorf("wrong processable count, got %d, exp %d", got, exp)
+	}
+
+	// Items are now queued for downloading, next step is that we tell the
+	// queue that a certain peer will deliver them for us
+	// That should trigger all of them to suddenly become 'done'
+	{
+		// Reserve blocks
+		peer := dummyPeer("peer-1")
+		fetchReq, _, _ := q.ReserveBodies(peer, 50)
+
+		// there should be nothing to fetch, blocks are empty
+		if fetchReq != nil {
+			t.Fatal("there should be no body fetch tasks remaining")
+		}
+
+	}
+	if q.blockTaskQueue.Size() != numOfBlocks-10 {
+		t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size())
+	}
+	if q.receiptTaskQueue.Size() != 0 {
+		t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size())
+	}
+	{
+		peer := dummyPeer("peer-3")
+		fetchReq, _, _ := q.ReserveReceipts(peer, 50)
+
+		// there should be nothing to fetch, blocks are empty
+		if fetchReq != nil {
+			t.Fatal("there should be no body fetch tasks remaining")
+		}
+	}
+	if q.blockTaskQueue.Size() != numOfBlocks-10 {
+		t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size())
+	}
+	if q.receiptTaskQueue.Size() != 0 {
+		t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size())
+	}
+	if got, exp := q.resultCache.countCompleted(), 10; got != exp {
+		t.Errorf("wrong processable count, got %d, exp %d", got, exp)
+	}
+}
+
+// XTestDelivery does some more extensive testing of events that happen,
+// blocks that become known and peers that make reservations and deliveries.
+// disabled since it's not really a unit-test, but can be executed to test
+// some more advanced scenarios
+func XTestDelivery(t *testing.T) {
+	// the outside network, holding blocks
+	blo, rec := makeChain(128, 0, genesis, false)
+	world := newNetwork()
+	world.receipts = rec
+	world.chain = blo
+	world.progress(10)
+	if false {
+		log.Root().SetHandler(log.StdoutHandler)
+
+	}
+	q := newQueue(10, 10)
+	var wg sync.WaitGroup
+	q.Prepare(1, FastSync)
+	wg.Add(1)
+	go func() {
+		// deliver headers
+		defer wg.Done()
+		c := 1
+		for {
+			//fmt.Printf("getting headers from %d\n", c)
+			hdrs := world.headers(c)
+			l := len(hdrs)
+			//fmt.Printf("scheduling %d headers, first %d last %d\n",
+			//	l, hdrs[0].Number.Uint64(), hdrs[len(hdrs)-1].Number.Uint64())
+			q.Schedule(hdrs, uint64(c))
+			c += l
+		}
+	}()
+	wg.Add(1)
+	go func() {
+		// collect results
+		defer wg.Done()
+		tot := 0
+		for {
+			res := q.Results(true)
+			tot += len(res)
+			fmt.Printf("got %d results, %d tot\n", len(res), tot)
+			// Now we can forget about these
+			world.forget(res[len(res)-1].Header.Number.Uint64())
+
+		}
+	}()
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		// reserve body fetch
+		i := 4
+		for {
+			peer := dummyPeer(fmt.Sprintf("peer-%d", i))
+			f, _, _ := q.ReserveBodies(peer, rand.Intn(30))
+			if f != nil {
+				var emptyList []*types.Header
+				var txs [][]*types.Transaction
+				var uncles [][]*types.Header
+				numToSkip := rand.Intn(len(f.Headers))
+				for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] {
+					txs = append(txs, world.getTransactions(hdr.Number.Uint64()))
+					uncles = append(uncles, emptyList)
+				}
+				time.Sleep(100 * time.Millisecond)
+				_, err := q.DeliverBodies(peer.id, txs, uncles)
+				if err != nil {
+					fmt.Printf("delivered %d bodies %v\n", len(txs), err)
+				}
+			} else {
+				i++
+				time.Sleep(200 * time.Millisecond)
+			}
+		}
+	}()
+	go func() {
+		defer wg.Done()
+		// reserve receiptfetch
+		peer := dummyPeer("peer-3")
+		for {
+			f, _, _ := q.ReserveReceipts(peer, rand.Intn(50))
+			if f != nil {
+				var rcs [][]*types.Receipt
+				for _, hdr := range f.Headers {
+					rcs = append(rcs, world.getReceipts(hdr.Number.Uint64()))
+				}
+				_, err := q.DeliverReceipts(peer.id, rcs)
+				if err != nil {
+					fmt.Printf("delivered %d receipts %v\n", len(rcs), err)
+				}
+				time.Sleep(100 * time.Millisecond)
+			} else {
+				time.Sleep(200 * time.Millisecond)
+			}
+		}
+	}()
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		for i := 0; i < 50; i++ {
+			time.Sleep(300 * time.Millisecond)
+			//world.tick()
+			//fmt.Printf("trying to progress\n")
+			world.progress(rand.Intn(100))
+		}
+		for i := 0; i < 50; i++ {
+			time.Sleep(2990 * time.Millisecond)
+
+		}
+	}()
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		for {
+			time.Sleep(990 * time.Millisecond)
+			fmt.Printf("world block tip is %d\n",
+				world.chain[len(world.chain)-1].Header().Number.Uint64())
+			fmt.Println(q.Stats())
+		}
+	}()
+	wg.Wait()
+}
+
+func newNetwork() *network {
+	var l sync.RWMutex
+	return &network{
+		cond:   sync.NewCond(&l),
+		offset: 1, // block 1 is at blocks[0]
+	}
+}
+
+// represents the network
+type network struct {
+	offset   int
+	chain    []*types.Block
+	receipts []types.Receipts
+	lock     sync.RWMutex
+	cond     *sync.Cond
+}
+
+func (n *network) getTransactions(blocknum uint64) types.Transactions {
+	index := blocknum - uint64(n.offset)
+	return n.chain[index].Transactions()
+}
+func (n *network) getReceipts(blocknum uint64) types.Receipts {
+	index := blocknum - uint64(n.offset)
+	if got := n.chain[index].Header().Number.Uint64(); got != blocknum {
+		fmt.Printf("Err, got %d exp %d\n", got, blocknum)
+		panic("sd")
+	}
+	return n.receipts[index]
+}
+
+func (n *network) forget(blocknum uint64) {
+	index := blocknum - uint64(n.offset)
+	n.chain = n.chain[index:]
+	n.receipts = n.receipts[index:]
+	n.offset = int(blocknum)
+
+}
+func (n *network) progress(numBlocks int) {
+
+	n.lock.Lock()
+	defer n.lock.Unlock()
+	//fmt.Printf("progressing...\n")
+	newBlocks, newR := makeChain(numBlocks, 0, n.chain[len(n.chain)-1], false)
+	n.chain = append(n.chain, newBlocks...)
+	n.receipts = append(n.receipts, newR...)
+	n.cond.Broadcast()
+
+}
+
+func (n *network) headers(from int) []*types.Header {
+	numHeaders := 128
+	var hdrs []*types.Header
+	index := from - n.offset
+
+	for index >= len(n.chain) {
+		// wait for progress
+		n.cond.L.Lock()
+		//fmt.Printf("header going into wait\n")
+		n.cond.Wait()
+		index = from - n.offset
+		n.cond.L.Unlock()
+	}
+	n.lock.RLock()
+	defer n.lock.RUnlock()
+	for i, b := range n.chain[index:] {
+		hdrs = append(hdrs, b.Header())
+		if i >= numHeaders {
+			break
+		}
+	}
+	return hdrs
+}
diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go
new file mode 100644
index 0000000000..21928c2a00
--- /dev/null
+++ b/eth/downloader/resultstore.go
@@ -0,0 +1,194 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package downloader
+
+import (
+	"fmt"
+	"sync"
+	"sync/atomic"
+
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+// resultStore implements a structure for maintaining fetchResults, tracking their
+// download-progress and delivering (finished) results.
+type resultStore struct {
+	items        []*fetchResult // Downloaded but not yet delivered fetch results
+	resultOffset uint64         // Offset of the first cached fetch result in the block chain
+
+	// Internal index of first non-completed entry, updated atomically when needed.
+	// If all items are complete, this will equal length(items), so
+	// *important* : is not safe to use for indexing without checking against length
+	indexIncomplete int32 // atomic access
+
+	// throttleThreshold is the limit up to which we _want_ to fill the
+	// results. If blocks are large, we want to limit the results to less
+	// than the number of available slots, and maybe only fill 1024 out of
+	// 8192 possible places. The queue will, at certain times, recalibrate
+	// this index.
+	throttleThreshold uint64
+
+	lock sync.RWMutex
+}
+
+func newResultStore(size int) *resultStore {
+	return &resultStore{
+		resultOffset:      0,
+		items:             make([]*fetchResult, size),
+		throttleThreshold: uint64(size),
+	}
+}
+
+// SetThrottleThreshold updates the throttling threshold based on the requested
+// limit and the total queue capacity. It returns the (possibly capped) threshold
+func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+
+	limit := uint64(len(r.items))
+	if threshold >= limit {
+		threshold = limit
+	}
+	r.throttleThreshold = threshold
+	return r.throttleThreshold
+}
+
+// AddFetch adds a header for body/receipt fetching. This is used when the queue
+// wants to reserve headers for fetching.
+//
+// It returns the following:
+//   stale     - if true, this item is already passed, and should not be requested again
+//   throttled - if true, the store is at capacity, this particular header is not prio now
+//   item      - the result to store data into
+//   err       - any error that occurred
+func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+
+	var index int
+	item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64())
+	if err != nil || stale || throttled {
+		return stale, throttled, item, err
+	}
+	if item == nil {
+		item = newFetchResult(header, fastSync)
+		r.items[index] = item
+	}
+	return stale, throttled, item, err
+}
+
+// GetDeliverySlot returns the fetchResult for the given header. If the 'stale' flag
+// is true, that means the header has already been delivered 'upstream'. This method
+// does not bubble up the 'throttle' flag, since it's moot at the point in time when
+// the item is downloaded and ready for delivery
+func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, error) {
+	r.lock.RLock()
+	defer r.lock.RUnlock()
+
+	res, _, stale, _, err := r.getFetchResult(headerNumber)
+	return res, stale, err
+}
+
+// getFetchResult returns the fetchResult corresponding to the given item, and
+// the index where the result is stored.
+func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, index int, stale, throttle bool, err error) {
+	index = int(int64(headerNumber) - int64(r.resultOffset))
+	throttle = index >= int(r.throttleThreshold)
+	stale = index < 0
+
+	if index >= len(r.items) {
+		err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+
+			"(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain,
+			index, headerNumber, r.resultOffset, len(r.items))
+		return nil, index, stale, throttle, err
+	}
+	if stale {
+		return nil, index, stale, throttle, nil
+	}
+	item = r.items[index]
+	return item, index, stale, throttle, nil
+}
+
+// hasCompletedItems returns true if there are processable items available
+// this method is cheaper than countCompleted
+func (r *resultStore) HasCompletedItems() bool {
+	r.lock.RLock()
+	defer r.lock.RUnlock()
+
+	if len(r.items) == 0 {
+		return false
+	}
+	if item := r.items[0]; item != nil && item.AllDone() {
+		return true
+	}
+	return false
+}
+
+// countCompleted returns the number of items ready for delivery, stopping at
+// the first non-complete item.
+//
+// The mthod assumes (at least) rlock is held.
+func (r *resultStore) countCompleted() int {
+	// We iterate from the already known complete point, and see
+	// if any more has completed since last count
+	index := atomic.LoadInt32(&r.indexIncomplete)
+	for ; ; index++ {
+		if index >= int32(len(r.items)) {
+			break
+		}
+		result := r.items[index]
+		if result == nil || !result.AllDone() {
+			break
+		}
+	}
+	atomic.StoreInt32(&r.indexIncomplete, index)
+	return int(index)
+}
+
+// GetCompleted returns the next batch of completed fetchResults
+func (r *resultStore) GetCompleted(limit int) []*fetchResult {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+
+	completed := r.countCompleted()
+	if limit > completed {
+		limit = completed
+	}
+	results := make([]*fetchResult, limit)
+	copy(results, r.items[:limit])
+
+	// Delete the results from the cache and clear the tail.
+	copy(r.items, r.items[limit:])
+	for i := len(r.items) - limit; i < len(r.items); i++ {
+		r.items[i] = nil
+	}
+	// Advance the expected block number of the first cache entry
+	r.resultOffset += uint64(limit)
+	atomic.AddInt32(&r.indexIncomplete, int32(-limit))
+
+	return results
+}
+
+// Prepare initialises the offset with the given block number
+func (r *resultStore) Prepare(offset uint64) {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+
+	if r.resultOffset < offset {
+		r.resultOffset = offset
+	}
+}
diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go
index f875b3a84c..ff84a3a8f0 100644
--- a/eth/downloader/statesync.go
+++ b/eth/downloader/statesync.go
@@ -18,13 +18,13 @@ package downloader
 
 import (
 	"fmt"
-	"hash"
 	"sync"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/trie"
@@ -34,13 +34,15 @@ import (
 // stateReq represents a batch of state fetch requests grouped together into
 // a single data retrieval network packet.
 type stateReq struct {
-	items    []common.Hash              // Hashes of the state items to download
-	tasks    map[common.Hash]*stateTask // Download tasks to track previous attempts
-	timeout  time.Duration              // Maximum round trip time for this to complete
-	timer    *time.Timer                // Timer to fire when the RTT timeout expires
-	peer     *peerConnection            // Peer that we're requesting from
-	response [][]byte                   // Response data of the peer (nil for timeouts)
-	dropped  bool                       // Flag whether the peer dropped off early
+	nItems    uint16                    // Number of items requested for download (max is 384, so uint16 is sufficient)
+	trieTasks map[common.Hash]*trieTask // Trie node download tasks to track previous attempts
+	codeTasks map[common.Hash]*codeTask // Byte code download tasks to track previous attempts
+	timeout   time.Duration             // Maximum round trip time for this to complete
+	timer     *time.Timer               // Timer to fire when the RTT timeout expires
+	peer      *peerConnection           // Peer that we're requesting from
+	delivered time.Time                 // Time when the packet was delivered (independent when we process it)
+	response  [][]byte                  // Response data of the peer (nil for timeouts)
+	dropped   bool                      // Flag whether the peer dropped off early
 }
 
 // timedOut returns if this request timed out.
@@ -63,6 +65,10 @@ func (d *Downloader) syncState(root common.Hash) *stateSync {
 	s := newStateSync(d, root)
 	select {
 	case d.stateSyncStart <- s:
+		// If we tell the statesync to restart with a new root, we also need
+		// to wait for it to actually also start -- when old requests have timed
+		// out or been delivered
+		<-s.started
 	case <-d.quitCh:
 		s.err = errCancelStateFetch
 		close(s.done)
@@ -95,15 +101,16 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
 		finished []*stateReq                  // Completed or failed requests
 		timeout  = make(chan *stateReq)       // Timed out active requests
 	)
+	log.Trace("State sync starting", "root", s.root)
+
 	defer func() {
 		// Cancel active request timers on exit. Also set peers to idle so they're
 		// available for the next sync.
 		for _, req := range active {
 			req.timer.Stop()
-			req.peer.SetNodeDataIdle(len(req.items))
+			req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
 		}
 	}()
-	// Run the state sync.
 	go s.run()
 	defer s.Cancel()
 
@@ -126,9 +133,11 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
 		select {
 		// The stateSync lifecycle:
 		case next := <-d.stateSyncStart:
+			d.spindownStateSync(active, finished, timeout, peerDrop)
 			return next
 
 		case <-s.done:
+			d.spindownStateSync(active, finished, timeout, peerDrop)
 			return nil
 
 		// Send the next finished request to the current sync:
@@ -149,6 +158,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
 			// Finalize the request and queue up for processing
 			req.timer.Stop()
 			req.response = pack.(*statePack).states
+			req.delivered = time.Now()
 
 			finished = append(finished, req)
 			delete(active, pack.PeerId())
@@ -163,6 +173,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
 			// Finalize the request and queue up for processing
 			req.timer.Stop()
 			req.dropped = true
+			req.delivered = time.Now()
 
 			finished = append(finished, req)
 			delete(active, p.id)
@@ -175,6 +186,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
 			if active[req.peer.id] != req {
 				continue
 			}
+			req.delivered = time.Now()
 			// Move the timed out data back into the download queue
 			finished = append(finished, req)
 			delete(active, req.peer.id)
@@ -189,39 +201,77 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
 			// causes valid requests to go missing and sync to get stuck.
 			if old := active[req.peer.id]; old != nil {
 				log.Warn("Busy peer assigned new state fetch", "peer", old.peer.id)
-
-				// Make sure the previous one doesn't get siletly lost
+				// Move the previous request to the finished set
 				old.timer.Stop()
 				old.dropped = true
-
+				old.delivered = time.Now()
 				finished = append(finished, old)
 			}
 			// Start a timer to notify the sync loop if the peer stalled.
 			req.timer = time.AfterFunc(req.timeout, func() {
-				select {
-				case timeout <- req:
-				case <-s.done:
-					// Prevent leaking of timer goroutines in the unlikely case where a
-					// timer is fired just before exiting runStateSync.
-				}
+				timeout <- req
 			})
 			active[req.peer.id] = req
 		}
 	}
 }
 
+// spindownStateSync 'drains' the outstanding requests; some will be delivered and other
+// will time out. This is to ensure that when the next stateSync starts working, all peers
+// are marked as idle and de facto _are_ idle.
+func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*stateReq, timeout chan *stateReq, peerDrop chan *peerConnection) {
+	log.Trace("State sync spinning down", "active", len(active), "finished", len(finished))
+	for len(active) > 0 {
+		var (
+			req    *stateReq
+			reason string
+		)
+		select {
+		// Handle (drop) incoming state packs:
+		case pack := <-d.stateCh:
+			req = active[pack.PeerId()]
+			reason = "delivered"
+		// Handle dropped peer connections:
+		case p := <-peerDrop:
+			req = active[p.id]
+			reason = "peerdrop"
+		// Handle timed-out requests:
+		case req = <-timeout:
+			reason = "timeout"
+		}
+		if req == nil {
+			continue
+		}
+		req.peer.log.Trace("State peer marked idle (spindown)", "req.items", int(req.nItems), "reason", reason)
+		req.timer.Stop()
+		delete(active, req.peer.id)
+		req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
+	}
+	// The 'finished' set contains deliveries that we were going to pass to processing.
+	// Those are now moot, but we still need to set those peers as idle, which would
+	// otherwise have been done after processing
+	for _, req := range finished {
+		req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
+	}
+}
+
 // stateSync schedules requests for downloading a particular state trie defined
 // by a given state root.
 type stateSync struct {
 	d *Downloader // Downloader instance to access and manage current peerset
 
-	sched  *trie.Sync                 // State trie sync scheduler defining the tasks
-	keccak hash.Hash                  // Keccak256 hasher to verify deliveries with
-	tasks  map[common.Hash]*stateTask // Set of tasks currently queued for retrieval
+	root   common.Hash        // State root currently being synced
+	sched  *trie.Sync         // State trie sync scheduler defining the tasks
+	keccak crypto.KeccakState // Keccak256 hasher to verify deliveries with
+
+	trieTasks map[common.Hash]*trieTask // Set of trie node tasks currently queued for retrieval
+	codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval
 
 	numUncommitted   int
 	bytesUncommitted int
 
+	started chan struct{} // Started is signalled once the sync loop starts
+
 	deliver    chan *stateReq // Delivery channel multiplexing peer responses
 	cancel     chan struct{}  // Channel to signal a termination request
 	cancelOnce sync.Once      // Ensures cancel only ever gets called once
@@ -229,9 +279,16 @@ type stateSync struct {
 	err        error          // Any error hit during sync (set before completion)
 }
 
-// stateTask represents a single trie node download task, containing a set of
+// trieTask represents a single trie node download task, containing a set of
+// peers already attempted retrieval from to detect stalled syncs and abort.
+type trieTask struct {
+	path     [][]byte
+	attempts map[string]struct{}
+}
+
+// codeTask represents a single byte code download task, containing a set of
 // peers already attempted retrieval from to detect stalled syncs and abort.
-type stateTask struct {
+type codeTask struct {
 	attempts map[string]struct{}
 }
 
@@ -239,13 +296,16 @@ type stateTask struct {
 // yet start the sync. The user needs to call run to initiate.
 func newStateSync(d *Downloader, root common.Hash) *stateSync {
 	return &stateSync{
-		d:       d,
-		sched:   state.NewStateSync(root, d.stateDB, d.stateBloom),
-		keccak:  sha3.NewLegacyKeccak256(),
-		tasks:   make(map[common.Hash]*stateTask),
-		deliver: make(chan *stateReq),
-		cancel:  make(chan struct{}),
-		done:    make(chan struct{}),
+		d:         d,
+		root:      root,
+		sched:     state.NewStateSync(root, d.stateDB, d.stateBloom, nil),
+		keccak:    sha3.NewLegacyKeccak256().(crypto.KeccakState),
+		trieTasks: make(map[common.Hash]*trieTask),
+		codeTasks: make(map[common.Hash]*codeTask),
+		deliver:   make(chan *stateReq),
+		cancel:    make(chan struct{}),
+		done:      make(chan struct{}),
+		started:   make(chan struct{}),
 	}
 }
 
@@ -253,7 +313,12 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync {
 // it finishes, and finally notifying any goroutines waiting for the loop to
 // finish.
 func (s *stateSync) run() {
-	s.err = s.loop()
+	close(s.started)
+	if s.d.snapSync {
+		s.err = s.d.SnapSyncer.Sync(s.root, s.cancel)
+	} else {
+		s.err = s.loop()
+	}
 	close(s.done)
 }
 
@@ -265,7 +330,9 @@ func (s *stateSync) Wait() error {
 
 // Cancel cancels the sync and waits until it has shut down.
 func (s *stateSync) Cancel() error {
-	s.cancelOnce.Do(func() { close(s.cancel) })
+	s.cancelOnce.Do(func() {
+		close(s.cancel)
+	})
 	return s.Wait()
 }
 
@@ -307,7 +374,7 @@ func (s *stateSync) loop() (err error) {
 		case req := <-s.deliver:
 			// Response, disconnect or timeout triggered, drop the peer if stalling
 			log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.response), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut())
-			if len(req.items) <= 2 && !req.dropped && req.timedOut() {
+			if req.nItems <= 2 && !req.dropped && req.timedOut() {
 				// 2 items are the minimum requested, if even that times out, we've no use of
 				// this peer at the moment.
 				log.Warn("Stalling state sync, dropping peer", "peer", req.peer.id)
@@ -331,11 +398,11 @@ func (s *stateSync) loop() (err error) {
 			}
 			// Process all the received blobs and check for stale delivery
 			delivered, err := s.process(req)
+			req.peer.SetNodeDataIdle(delivered, req.delivered)
 			if err != nil {
 				log.Warn("Node data write error", "err", err)
 				return err
 			}
-			req.peer.SetNodeDataIdle(delivered)
 		}
 	}
 	return nil
@@ -368,14 +435,15 @@ func (s *stateSync) assignTasks() {
 		// Assign a batch of fetches proportional to the estimated latency/bandwidth
 		cap := p.NodeDataCapacity(s.d.requestRTT())
 		req := &stateReq{peer: p, timeout: s.d.requestTTL()}
-		s.fillTasks(cap, req)
+
+		nodes, _, codes := s.fillTasks(cap, req)
 
 		// If the peer was assigned tasks to fetch, send the network request
-		if len(req.items) > 0 {
-			req.peer.log.Trace("Requesting new batch of data", "type", "state", "count", len(req.items))
+		if len(nodes)+len(codes) > 0 {
+			req.peer.log.Trace("Requesting batch of state data", "nodes", len(nodes), "codes", len(codes), "root", s.root)
 			select {
 			case s.d.trackStateReq <- req:
-				req.peer.FetchNodeData(req.items)
+				req.peer.FetchNodeData(append(nodes, codes...)) // Unified retrieval under eth/6x
 			case <-s.cancel:
 			case <-s.d.cancelCh:
 			}
@@ -385,20 +453,34 @@ func (s *stateSync) assignTasks() {
 
 // fillTasks fills the given request object with a maximum of n state download
 // tasks to send to the remote peer.
-func (s *stateSync) fillTasks(n int, req *stateReq) {
+func (s *stateSync) fillTasks(n int, req *stateReq) (nodes []common.Hash, paths []trie.SyncPath, codes []common.Hash) {
 	// Refill available tasks from the scheduler.
-	if len(s.tasks) < n {
-		new := s.sched.Missing(n - len(s.tasks))
-		for _, hash := range new {
-			s.tasks[hash] = &stateTask{make(map[string]struct{})}
+	if fill := n - (len(s.trieTasks) + len(s.codeTasks)); fill > 0 {
+		nodes, paths, codes := s.sched.Missing(fill)
+		for i, hash := range nodes {
+			s.trieTasks[hash] = &trieTask{
+				path:     paths[i],
+				attempts: make(map[string]struct{}),
+			}
+		}
+		for _, hash := range codes {
+			s.codeTasks[hash] = &codeTask{
+				attempts: make(map[string]struct{}),
+			}
 		}
 	}
-	// Find tasks that haven't been tried with the request's peer.
-	req.items = make([]common.Hash, 0, n)
-	req.tasks = make(map[common.Hash]*stateTask, n)
-	for hash, t := range s.tasks {
+	// Find tasks that haven't been tried with the request's peer. Prefer code
+	// over trie nodes as those can be written to disk and forgotten about.
+	nodes = make([]common.Hash, 0, n)
+	paths = make([]trie.SyncPath, 0, n)
+	codes = make([]common.Hash, 0, n)
+
+	req.trieTasks = make(map[common.Hash]*trieTask, n)
+	req.codeTasks = make(map[common.Hash]*codeTask, n)
+
+	for hash, t := range s.codeTasks {
 		// Stop when we've gathered enough requests
-		if len(req.items) == n {
+		if len(nodes)+len(codes) == n {
 			break
 		}
 		// Skip any requests we've already tried from this peer
@@ -407,10 +489,30 @@ func (s *stateSync) fillTasks(n int, req *stateReq) {
 		}
 		// Assign the request to this peer
 		t.attempts[req.peer.id] = struct{}{}
-		req.items = append(req.items, hash)
-		req.tasks[hash] = t
-		delete(s.tasks, hash)
+		codes = append(codes, hash)
+		req.codeTasks[hash] = t
+		delete(s.codeTasks, hash)
 	}
+	for hash, t := range s.trieTasks {
+		// Stop when we've gathered enough requests
+		if len(nodes)+len(codes) == n {
+			break
+		}
+		// Skip any requests we've already tried from this peer
+		if _, ok := t.attempts[req.peer.id]; ok {
+			continue
+		}
+		// Assign the request to this peer
+		t.attempts[req.peer.id] = struct{}{}
+
+		nodes = append(nodes, hash)
+		paths = append(paths, t.path)
+
+		req.trieTasks[hash] = t
+		delete(s.trieTasks, hash)
+	}
+	req.nItems = uint16(len(nodes) + len(codes))
+	return nodes, paths, codes
 }
 
 // process iterates over a batch of delivered state data, injecting each item
@@ -429,7 +531,7 @@ func (s *stateSync) process(req *stateReq) (int, error) {
 
 	// Iterate over all the delivered data and inject one-by-one into the trie
 	for _, blob := range req.response {
-		_, hash, err := s.processNodeData(blob)
+		hash, err := s.processNodeData(blob)
 		switch err {
 		case nil:
 			s.numUncommitted++
@@ -442,11 +544,28 @@ func (s *stateSync) process(req *stateReq) (int, error) {
 		default:
 			return successful, fmt.Errorf("invalid state node %s: %v", hash.TerminalString(), err)
 		}
-		delete(req.tasks, hash)
+		// Delete from both queues (one delivery is enough for the syncer)
+		delete(req.trieTasks, hash)
+		delete(req.codeTasks, hash)
 	}
 	// Put unfulfilled tasks back into the retry queue
 	npeers := s.d.peers.Len()
-	for hash, task := range req.tasks {
+	for hash, task := range req.trieTasks {
+		// If the node did deliver something, missing items may be due to a protocol
+		// limit or a previous timeout + delayed delivery. Both cases should permit
+		// the node to retry the missing items (to avoid single-peer stalls).
+		if len(req.response) > 0 || req.timedOut() {
+			delete(task.attempts, req.peer.id)
+		}
+		// If we've requested the node too many times already, it may be a malicious
+		// sync where nobody has the right data. Abort.
+		if len(task.attempts) >= npeers {
+			return successful, fmt.Errorf("trie node %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
+		}
+		// Missing item, place into the retry queue.
+		s.trieTasks[hash] = task
+	}
+	for hash, task := range req.codeTasks {
 		// If the node did deliver something, missing items may be due to a protocol
 		// limit or a previous timeout + delayed delivery. Both cases should permit
 		// the node to retry the missing items (to avoid single-peer stalls).
@@ -456,10 +575,10 @@ func (s *stateSync) process(req *stateReq) (int, error) {
 		// If we've requested the node too many times already, it may be a malicious
 		// sync where nobody has the right data. Abort.
 		if len(task.attempts) >= npeers {
-			return successful, fmt.Errorf("state node %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
+			return successful, fmt.Errorf("byte code %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers)
 		}
 		// Missing item, place into the retry queue.
-		s.tasks[hash] = task
+		s.codeTasks[hash] = task
 	}
 	return successful, nil
 }
@@ -467,13 +586,13 @@ func (s *stateSync) process(req *stateReq) (int, error) {
 // processNodeData tries to inject a trie node data blob delivered from a remote
 // peer into the state trie, returning whether anything useful was written or any
 // error occurred.
-func (s *stateSync) processNodeData(blob []byte) (bool, common.Hash, error) {
+func (s *stateSync) processNodeData(blob []byte) (common.Hash, error) {
 	res := trie.SyncResult{Data: blob}
 	s.keccak.Reset()
 	s.keccak.Write(blob)
-	s.keccak.Sum(res.Hash[:0])
-	committed, _, err := s.sched.Process([]trie.SyncResult{res})
-	return committed, res.Hash, err
+	s.keccak.Read(res.Hash[:])
+	err := s.sched.Process(res)
+	return res.Hash, err
 }
 
 // updateStats bumps the various state sync progress counters and displays a log
@@ -488,7 +607,7 @@ func (s *stateSync) updateStats(written, duplicate, unexpected int, duration tim
 	s.d.syncStatsState.unexpected += uint64(unexpected)
 
 	if written > 0 || duplicate > 0 || unexpected > 0 {
-		log.Info("Imported new state entries", "count", written, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "retry", len(s.tasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected)
+		log.Info("Imported new state entries", "count", written, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "trieretry", len(s.trieTasks), "coderetry", len(s.codeTasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected)
 	}
 	if written > 0 {
 		rawdb.WriteFastTrieProgress(s.d.stateDB, s.d.syncStatsState.processed)
diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go
index f410152f5b..2d7b4d1f10 100644
--- a/eth/downloader/testchain_test.go
+++ b/eth/downloader/testchain_test.go
@@ -39,13 +39,13 @@ var (
 )
 
 // The common prefix of all test chains:
-var testChainBase = newTestChain(blockCacheItems+200, testGenesis)
+var testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis)
 
 // Different forks on top of the base chain:
 var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain
 
 func init() {
-	var forkLen = int(maxForkAncestry + 50)
+	var forkLen = int(fullMaxForkAncestry + 50)
 	var wg sync.WaitGroup
 	wg.Add(3)
 	go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }()
@@ -170,18 +170,27 @@ func (tc *testChain) td(hash common.Hash) *big.Int {
 	return tc.tdm[hash]
 }
 
-// headersByHash returns headers in ascending order from the given hash.
-func (tc *testChain) headersByHash(origin common.Hash, amount int, skip int) []*types.Header {
+// headersByHash returns headers in order from the given hash.
+func (tc *testChain) headersByHash(origin common.Hash, amount int, skip int, reverse bool) []*types.Header {
 	num, _ := tc.hashToNumber(origin)
-	return tc.headersByNumber(num, amount, skip)
+	return tc.headersByNumber(num, amount, skip, reverse)
 }
 
-// headersByNumber returns headers in ascending order from the given number.
-func (tc *testChain) headersByNumber(origin uint64, amount int, skip int) []*types.Header {
+// headersByNumber returns headers from the given number.
+func (tc *testChain) headersByNumber(origin uint64, amount int, skip int, reverse bool) []*types.Header {
 	result := make([]*types.Header, 0, amount)
-	for num := origin; num < uint64(len(tc.chain)) && len(result) < amount; num += uint64(skip) + 1 {
-		if header, ok := tc.headerm[tc.chain[int(num)]]; ok {
-			result = append(result, header)
+
+	if !reverse {
+		for num := origin; num < uint64(len(tc.chain)) && len(result) < amount; num += uint64(skip) + 1 {
+			if header, ok := tc.headerm[tc.chain[int(num)]]; ok {
+				result = append(result, header)
+			}
+		}
+	} else {
+		for num := int64(origin); num >= 0 && len(result) < amount; num -= int64(skip) + 1 {
+			if header, ok := tc.headerm[tc.chain[int(num)]]; ok {
+				result = append(result, header)
+			}
 		}
 	}
 	return result
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
new file mode 100644
index 0000000000..1c4b796e9f
--- /dev/null
+++ b/eth/ethconfig/config.go
@@ -0,0 +1,244 @@
+// Copyright 2017 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 .
+
+// Package ethconfig contains the configuration of the ETH and LES protocols.
+package ethconfig
+
+import (
+	"github.com/ethereum/go-ethereum/consensus/parlia"
+	"github.com/ethereum/go-ethereum/internal/ethapi"
+	"math/big"
+	"os"
+	"os/user"
+	"path/filepath"
+	"runtime"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/consensus/clique"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/gasprice"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/miner"
+	"github.com/ethereum/go-ethereum/node"
+	"github.com/ethereum/go-ethereum/params"
+)
+
+// FullNodeGPO contains default gasprice oracle settings for full node.
+var FullNodeGPO = gasprice.Config{
+	Blocks:          20,
+	Percentile:      60,
+	MaxPrice:        gasprice.DefaultMaxPrice,
+	OracleThreshold: 1000,
+}
+
+// LightClientGPO contains default gasprice oracle settings for light client.
+var LightClientGPO = gasprice.Config{
+	Blocks:     2,
+	Percentile: 60,
+	MaxPrice:   gasprice.DefaultMaxPrice,
+}
+
+// Defaults contains default settings for use on the Ethereum main net.
+var Defaults = Config{
+	SyncMode: downloader.FastSync,
+	Ethash: ethash.Config{
+		CacheDir:         "ethash",
+		CachesInMem:      2,
+		CachesOnDisk:     3,
+		CachesLockMmap:   false,
+		DatasetsInMem:    1,
+		DatasetsOnDisk:   2,
+		DatasetsLockMmap: false,
+	},
+	NetworkId:               1,
+	TxLookupLimit:           2350000,
+	LightPeers:              100,
+	UltraLightFraction:      75,
+	DatabaseCache:           512,
+	TrieCleanCache:          154,
+	TrieCleanCacheJournal:   "triecache",
+	TrieCleanCacheRejournal: 60 * time.Minute,
+	TrieDirtyCache:          256,
+	TrieTimeout:             60 * time.Minute,
+	SnapshotCache:           102,
+	Miner: miner.Config{
+		GasFloor:      8000000,
+		GasCeil:       8000000,
+		GasPrice:      big.NewInt(params.GWei),
+		Recommit:      3 * time.Second,
+		DelayLeftOver: 50 * time.Millisecond,
+	},
+	TxPool:      core.DefaultTxPoolConfig,
+	RPCGasCap:   25000000,
+	GPO:         FullNodeGPO,
+	RPCTxFeeCap: 1, // 1 ether
+}
+
+func init() {
+	home := os.Getenv("HOME")
+	if home == "" {
+		if user, err := user.Current(); err == nil {
+			home = user.HomeDir
+		}
+	}
+	if runtime.GOOS == "darwin" {
+		Defaults.Ethash.DatasetDir = filepath.Join(home, "Library", "Ethash")
+	} else if runtime.GOOS == "windows" {
+		localappdata := os.Getenv("LOCALAPPDATA")
+		if localappdata != "" {
+			Defaults.Ethash.DatasetDir = filepath.Join(localappdata, "Ethash")
+		} else {
+			Defaults.Ethash.DatasetDir = filepath.Join(home, "AppData", "Local", "Ethash")
+		}
+	} else {
+		Defaults.Ethash.DatasetDir = filepath.Join(home, ".ethash")
+	}
+}
+
+//go:generate gencodec -type Config -formats toml -out gen_config.go
+
+// Config contains configuration options for of the ETH and LES protocols.
+type Config struct {
+	// The genesis block, which is inserted if the database is empty.
+	// If nil, the Ethereum main net block is used.
+	Genesis *core.Genesis `toml:",omitempty"`
+
+	// Protocol options
+	NetworkId uint64 // Network ID to use for selecting peers to connect to
+	SyncMode  downloader.SyncMode
+
+	// This can be set to list of enrtree:// URLs which will be queried for
+	// for nodes to connect to.
+	EthDiscoveryURLs  []string
+	SnapDiscoveryURLs []string
+
+	NoPruning       bool // Whether to disable pruning and flush everything to disk
+	NoPrefetch      bool // Whether to disable prefetching and only load state on demand
+	DirectBroadcast bool
+	RangeLimit      bool
+
+	TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved.
+
+	// Whitelist of required block number -> hash values to accept
+	Whitelist map[uint64]common.Hash `toml:"-"`
+
+	// Light client options
+	LightServ          int  `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
+	LightIngress       int  `toml:",omitempty"` // Incoming bandwidth limit for light servers
+	LightEgress        int  `toml:",omitempty"` // Outgoing bandwidth limit for light servers
+	LightPeers         int  `toml:",omitempty"` // Maximum number of LES client peers
+	LightNoPrune       bool `toml:",omitempty"` // Whether to disable light chain pruning
+	LightNoSyncServe   bool `toml:",omitempty"` // Whether to serve light clients before syncing
+	SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint
+
+	// Ultra Light client options
+	UltraLightServers      []string `toml:",omitempty"` // List of trusted ultra light servers
+	UltraLightFraction     int      `toml:",omitempty"` // Percentage of trusted servers to accept an announcement
+	UltraLightOnlyAnnounce bool     `toml:",omitempty"` // Whether to only announce headers, or also serve them
+
+	// Database options
+	SkipBcVersionCheck bool `toml:"-"`
+	DatabaseHandles    int  `toml:"-"`
+	DatabaseCache      int
+	DatabaseFreezer    string
+
+	TrieCleanCache          int
+	TrieCleanCacheJournal   string        `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts
+	TrieCleanCacheRejournal time.Duration `toml:",omitempty"` // Time interval to regenerate the journal for clean cache
+	TrieDirtyCache          int
+	TrieTimeout             time.Duration
+	SnapshotCache           int
+	Preimages               bool
+
+	// Mining options
+	Miner miner.Config
+
+	// Ethash options
+	Ethash ethash.Config `toml:",omitempty"`
+
+	// Transaction pool options
+	TxPool core.TxPoolConfig
+
+	// Gas Price Oracle options
+	GPO gasprice.Config
+
+	// Enables tracking of SHA3 preimages in the VM
+	EnablePreimageRecording bool
+
+	// Miscellaneous options
+	DocRoot string `toml:"-"`
+
+	// Type of the EWASM interpreter ("" for default)
+	EWASMInterpreter string
+
+	// Type of the EVM interpreter ("" for default)
+	EVMInterpreter string
+
+	// RPCGasCap is the global gas cap for eth-call variants.
+	RPCGasCap uint64
+
+	// RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for
+	// send-transction variants. The unit is ether.
+	RPCTxFeeCap float64
+
+	// Checkpoint is a hardcoded checkpoint which can be nil.
+	Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
+
+	// CheckpointOracle is the configuration for checkpoint oracle.
+	CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
+
+	// Berlin block override (TODO: remove after the fork)
+	OverrideBerlin *big.Int `toml:",omitempty"`
+}
+
+// CreateConsensusEngine creates a consensus engine for the given chain configuration.
+func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, ee *ethapi.PublicBlockChainAPI, genesisHash common.Hash) consensus.Engine {
+	// If proof-of-authority is requested, set it up
+	if chainConfig.Clique != nil {
+		return clique.New(chainConfig.Clique, db)
+	}
+	if chainConfig.Parlia != nil {
+		return parlia.New(chainConfig, db, ee, genesisHash)
+	}
+	// Otherwise assume proof-of-work
+	switch config.PowMode {
+	case ethash.ModeFake:
+		log.Warn("Ethash used in fake mode")
+	case ethash.ModeTest:
+		log.Warn("Ethash used in test mode")
+	case ethash.ModeShared:
+		log.Warn("Ethash used in shared mode")
+	}
+	engine := ethash.New(ethash.Config{
+		PowMode:          config.PowMode,
+		CacheDir:         stack.ResolvePath(config.CacheDir),
+		CachesInMem:      config.CachesInMem,
+		CachesOnDisk:     config.CachesOnDisk,
+		CachesLockMmap:   config.CachesLockMmap,
+		DatasetDir:       config.DatasetDir,
+		DatasetsInMem:    config.DatasetsInMem,
+		DatasetsOnDisk:   config.DatasetsOnDisk,
+		DatasetsLockMmap: config.DatasetsLockMmap,
+		NotifyFull:       config.NotifyFull,
+	}, notify, noverify)
+	engine.SetThreads(-1) // Disable CPU mining
+	return engine
+}
diff --git a/eth/gen_config.go b/eth/ethconfig/gen_config.go
similarity index 72%
rename from eth/gen_config.go
rename to eth/ethconfig/gen_config.go
index 1c659c393c..ca93b2ad00 100644
--- a/eth/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -1,9 +1,8 @@
 // Code generated by github.com/fjl/gencodec. DO NOT EDIT.
 
-package eth
+package ethconfig
 
 import (
-	"math/big"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -21,14 +20,19 @@ func (c Config) MarshalTOML() (interface{}, error) {
 		Genesis                 *core.Genesis `toml:",omitempty"`
 		NetworkId               uint64
 		SyncMode                downloader.SyncMode
-		DiscoveryURLs           []string
+		EthDiscoveryURLs        []string
+		SnapDiscoveryURLs       []string
 		NoPruning               bool
 		NoPrefetch              bool
+		TxLookupLimit           uint64                 `toml:",omitempty"`
 		Whitelist               map[uint64]common.Hash `toml:"-"`
 		LightServ               int                    `toml:",omitempty"`
 		LightIngress            int                    `toml:",omitempty"`
 		LightEgress             int                    `toml:",omitempty"`
 		LightPeers              int                    `toml:",omitempty"`
+		LightNoPrune            bool                   `toml:",omitempty"`
+		LightNoSyncServe        bool                   `toml:",omitempty"`
+		SyncFromCheckpoint      bool                   `toml:",omitempty"`
 		UltraLightServers       []string               `toml:",omitempty"`
 		UltraLightFraction      int                    `toml:",omitempty"`
 		UltraLightOnlyAnnounce  bool                   `toml:",omitempty"`
@@ -37,8 +41,12 @@ func (c Config) MarshalTOML() (interface{}, error) {
 		DatabaseCache           int
 		DatabaseFreezer         string
 		TrieCleanCache          int
+		TrieCleanCacheJournal   string        `toml:",omitempty"`
+		TrieCleanCacheRejournal time.Duration `toml:",omitempty"`
 		TrieDirtyCache          int
 		TrieTimeout             time.Duration
+		SnapshotCache           int
+		Preimages               bool
 		Miner                   miner.Config
 		Ethash                  ethash.Config
 		TxPool                  core.TxPoolConfig
@@ -47,24 +55,28 @@ func (c Config) MarshalTOML() (interface{}, error) {
 		DocRoot                 string `toml:"-"`
 		EWASMInterpreter        string
 		EVMInterpreter          string
-		RPCGasCap               *big.Int                       `toml:",omitempty"`
+		RPCGasCap               uint64                         `toml:",omitempty"`
+		RPCTxFeeCap             float64                        `toml:",omitempty"`
 		Checkpoint              *params.TrustedCheckpoint      `toml:",omitempty"`
 		CheckpointOracle        *params.CheckpointOracleConfig `toml:",omitempty"`
-		OverrideIstanbul        *big.Int                       `toml:",omitempty"`
-		OverrideMuirGlacier     *big.Int                       `toml:",omitempty"`
 	}
 	var enc Config
 	enc.Genesis = c.Genesis
 	enc.NetworkId = c.NetworkId
 	enc.SyncMode = c.SyncMode
-	enc.DiscoveryURLs = c.DiscoveryURLs
+	enc.EthDiscoveryURLs = c.EthDiscoveryURLs
+	enc.SnapDiscoveryURLs = c.SnapDiscoveryURLs
 	enc.NoPruning = c.NoPruning
 	enc.NoPrefetch = c.NoPrefetch
+	enc.TxLookupLimit = c.TxLookupLimit
 	enc.Whitelist = c.Whitelist
 	enc.LightServ = c.LightServ
 	enc.LightIngress = c.LightIngress
 	enc.LightEgress = c.LightEgress
 	enc.LightPeers = c.LightPeers
+	enc.LightNoPrune = c.LightNoPrune
+	enc.LightNoSyncServe = c.LightNoSyncServe
+	enc.SyncFromCheckpoint = c.SyncFromCheckpoint
 	enc.UltraLightServers = c.UltraLightServers
 	enc.UltraLightFraction = c.UltraLightFraction
 	enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce
@@ -73,8 +85,12 @@ func (c Config) MarshalTOML() (interface{}, error) {
 	enc.DatabaseCache = c.DatabaseCache
 	enc.DatabaseFreezer = c.DatabaseFreezer
 	enc.TrieCleanCache = c.TrieCleanCache
+	enc.TrieCleanCacheJournal = c.TrieCleanCacheJournal
+	enc.TrieCleanCacheRejournal = c.TrieCleanCacheRejournal
 	enc.TrieDirtyCache = c.TrieDirtyCache
 	enc.TrieTimeout = c.TrieTimeout
+	enc.SnapshotCache = c.SnapshotCache
+	enc.Preimages = c.Preimages
 	enc.Miner = c.Miner
 	enc.Ethash = c.Ethash
 	enc.TxPool = c.TxPool
@@ -84,10 +100,9 @@ func (c Config) MarshalTOML() (interface{}, error) {
 	enc.EWASMInterpreter = c.EWASMInterpreter
 	enc.EVMInterpreter = c.EVMInterpreter
 	enc.RPCGasCap = c.RPCGasCap
+	enc.RPCTxFeeCap = c.RPCTxFeeCap
 	enc.Checkpoint = c.Checkpoint
 	enc.CheckpointOracle = c.CheckpointOracle
-	enc.OverrideIstanbul = c.OverrideIstanbul
-	enc.OverrideMuirGlacier = c.OverrideMuirGlacier
 	return &enc, nil
 }
 
@@ -97,14 +112,19 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		Genesis                 *core.Genesis `toml:",omitempty"`
 		NetworkId               *uint64
 		SyncMode                *downloader.SyncMode
-		DiscoveryURLs           []string
+		EthDiscoveryURLs        []string
+		SnapDiscoveryURLs       []string
 		NoPruning               *bool
 		NoPrefetch              *bool
+		TxLookupLimit           *uint64                `toml:",omitempty"`
 		Whitelist               map[uint64]common.Hash `toml:"-"`
 		LightServ               *int                   `toml:",omitempty"`
 		LightIngress            *int                   `toml:",omitempty"`
 		LightEgress             *int                   `toml:",omitempty"`
 		LightPeers              *int                   `toml:",omitempty"`
+		LightNoPrune            *bool                  `toml:",omitempty"`
+		LightNoSyncServe        *bool                  `toml:",omitempty"`
+		SyncFromCheckpoint      *bool                  `toml:",omitempty"`
 		UltraLightServers       []string               `toml:",omitempty"`
 		UltraLightFraction      *int                   `toml:",omitempty"`
 		UltraLightOnlyAnnounce  *bool                  `toml:",omitempty"`
@@ -113,8 +133,12 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		DatabaseCache           *int
 		DatabaseFreezer         *string
 		TrieCleanCache          *int
+		TrieCleanCacheJournal   *string        `toml:",omitempty"`
+		TrieCleanCacheRejournal *time.Duration `toml:",omitempty"`
 		TrieDirtyCache          *int
 		TrieTimeout             *time.Duration
+		SnapshotCache           *int
+		Preimages               *bool
 		Miner                   *miner.Config
 		Ethash                  *ethash.Config
 		TxPool                  *core.TxPoolConfig
@@ -123,11 +147,10 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		DocRoot                 *string `toml:"-"`
 		EWASMInterpreter        *string
 		EVMInterpreter          *string
-		RPCGasCap               *big.Int                       `toml:",omitempty"`
+		RPCGasCap               *uint64                        `toml:",omitempty"`
+		RPCTxFeeCap             *float64                       `toml:",omitempty"`
 		Checkpoint              *params.TrustedCheckpoint      `toml:",omitempty"`
 		CheckpointOracle        *params.CheckpointOracleConfig `toml:",omitempty"`
-		OverrideIstanbul        *big.Int                       `toml:",omitempty"`
-		OverrideMuirGlacier     *big.Int                       `toml:",omitempty"`
 	}
 	var dec Config
 	if err := unmarshal(&dec); err != nil {
@@ -142,8 +165,11 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.SyncMode != nil {
 		c.SyncMode = *dec.SyncMode
 	}
-	if dec.DiscoveryURLs != nil {
-		c.DiscoveryURLs = dec.DiscoveryURLs
+	if dec.EthDiscoveryURLs != nil {
+		c.EthDiscoveryURLs = dec.EthDiscoveryURLs
+	}
+	if dec.SnapDiscoveryURLs != nil {
+		c.SnapDiscoveryURLs = dec.SnapDiscoveryURLs
 	}
 	if dec.NoPruning != nil {
 		c.NoPruning = *dec.NoPruning
@@ -151,6 +177,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.NoPrefetch != nil {
 		c.NoPrefetch = *dec.NoPrefetch
 	}
+	if dec.TxLookupLimit != nil {
+		c.TxLookupLimit = *dec.TxLookupLimit
+	}
 	if dec.Whitelist != nil {
 		c.Whitelist = dec.Whitelist
 	}
@@ -166,6 +195,15 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.LightPeers != nil {
 		c.LightPeers = *dec.LightPeers
 	}
+	if dec.LightNoPrune != nil {
+		c.LightNoPrune = *dec.LightNoPrune
+	}
+	if dec.LightNoSyncServe != nil {
+		c.LightNoSyncServe = *dec.LightNoSyncServe
+	}
+	if dec.SyncFromCheckpoint != nil {
+		c.SyncFromCheckpoint = *dec.SyncFromCheckpoint
+	}
 	if dec.UltraLightServers != nil {
 		c.UltraLightServers = dec.UltraLightServers
 	}
@@ -190,12 +228,24 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.TrieCleanCache != nil {
 		c.TrieCleanCache = *dec.TrieCleanCache
 	}
+	if dec.TrieCleanCacheJournal != nil {
+		c.TrieCleanCacheJournal = *dec.TrieCleanCacheJournal
+	}
+	if dec.TrieCleanCacheRejournal != nil {
+		c.TrieCleanCacheRejournal = *dec.TrieCleanCacheRejournal
+	}
 	if dec.TrieDirtyCache != nil {
 		c.TrieDirtyCache = *dec.TrieDirtyCache
 	}
 	if dec.TrieTimeout != nil {
 		c.TrieTimeout = *dec.TrieTimeout
 	}
+	if dec.SnapshotCache != nil {
+		c.SnapshotCache = *dec.SnapshotCache
+	}
+	if dec.Preimages != nil {
+		c.Preimages = *dec.Preimages
+	}
 	if dec.Miner != nil {
 		c.Miner = *dec.Miner
 	}
@@ -221,7 +271,10 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 		c.EVMInterpreter = *dec.EVMInterpreter
 	}
 	if dec.RPCGasCap != nil {
-		c.RPCGasCap = dec.RPCGasCap
+		c.RPCGasCap = *dec.RPCGasCap
+	}
+	if dec.RPCTxFeeCap != nil {
+		c.RPCTxFeeCap = *dec.RPCTxFeeCap
 	}
 	if dec.Checkpoint != nil {
 		c.Checkpoint = dec.Checkpoint
@@ -229,11 +282,5 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
 	if dec.CheckpointOracle != nil {
 		c.CheckpointOracle = dec.CheckpointOracle
 	}
-	if dec.OverrideIstanbul != nil {
-		c.OverrideIstanbul = dec.OverrideIstanbul
-	}
-	if dec.OverrideMuirGlacier != nil {
-		c.OverrideMuirGlacier = dec.OverrideMuirGlacier
-	}
 	return nil
 }
diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go
index d96957d418..fc75dd4310 100644
--- a/eth/fetcher/block_fetcher.go
+++ b/eth/fetcher/block_fetcher.go
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// Package fetcher contains the announcement based blocks or transaction synchronisation.
+// Package fetcher contains the announcement based header, blocks or transaction synchronisation.
 package fetcher
 
 import (
@@ -28,9 +28,11 @@ import (
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 const (
+	lightTimeout  = time.Millisecond       // Time allowance before an announced header is explicitly requested
 	arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block/transaction is explicitly requested
 	gatherSlack   = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches
 	fetchTimeout  = 5 * time.Second        // Maximum allotted time to return an explicitly requested block/transaction
@@ -39,7 +41,7 @@ const (
 const (
 	maxUncleDist = 11  // Maximum allowed backward distance from the chain head
 	maxQueueDist = 32  // Maximum allowed distance from the chain head to queue
-	hashLimit    = 256 // Maximum number of unique blocks a peer may have announced
+	hashLimit    = 256 // Maximum number of unique blocks or headers a peer may have announced
 	blockLimit   = 64  // Maximum number of unique blocks a peer may have delivered
 )
 
@@ -63,9 +65,10 @@ var (
 	bodyFilterOutMeter   = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil)
 )
 
-var (
-	errTerminated = errors.New("terminated")
-)
+var errTerminated = errors.New("terminated")
+
+// HeaderRetrievalFn is a callback type for retrieving a header from the local chain.
+type HeaderRetrievalFn func(common.Hash) *types.Header
 
 // blockRetrievalFn is a callback type for retrieving a block from the local chain.
 type blockRetrievalFn func(common.Hash) *types.Block
@@ -85,6 +88,9 @@ type blockBroadcasterFn func(block *types.Block, propagate bool)
 // chainHeightFn is a callback type to retrieve the current chain height.
 type chainHeightFn func() uint64
 
+// headersInsertFn is a callback type to insert a batch of headers into the local chain.
+type headersInsertFn func(headers []*types.Header) (int, error)
+
 // chainInsertFn is a callback type to insert a batch of blocks into the local chain.
 type chainInsertFn func(types.Blocks) (int, error)
 
@@ -121,18 +127,38 @@ type bodyFilterTask struct {
 	time         time.Time              // Arrival time of the blocks' contents
 }
 
-// blockInject represents a schedules import operation.
-type blockInject struct {
+// blockOrHeaderInject represents a schedules import operation.
+type blockOrHeaderInject struct {
 	origin string
-	block  *types.Block
+
+	header *types.Header // Used for light mode fetcher which only cares about header.
+	block  *types.Block  // Used for normal mode fetcher which imports full block.
+}
+
+// number returns the block number of the injected object.
+func (inject *blockOrHeaderInject) number() uint64 {
+	if inject.header != nil {
+		return inject.header.Number.Uint64()
+	}
+	return inject.block.NumberU64()
+}
+
+// number returns the block hash of the injected object.
+func (inject *blockOrHeaderInject) hash() common.Hash {
+	if inject.header != nil {
+		return inject.header.Hash()
+	}
+	return inject.block.Hash()
 }
 
 // BlockFetcher is responsible for accumulating block announcements from various peers
 // and scheduling them for retrieval.
 type BlockFetcher struct {
+	light bool // The indicator whether it's a light fetcher or normal one.
+
 	// Various event channels
 	notify chan *blockAnnounce
-	inject chan *blockInject
+	inject chan *blockOrHeaderInject
 
 	headerFilter chan chan *headerFilterTask
 	bodyFilter   chan chan *bodyFilterTask
@@ -148,31 +174,34 @@ type BlockFetcher struct {
 	completing map[common.Hash]*blockAnnounce   // Blocks with headers, currently body-completing
 
 	// Block cache
-	queue  *prque.Prque                 // Queue containing the import operations (block number sorted)
-	queues map[string]int               // Per peer block counts to prevent memory exhaustion
-	queued map[common.Hash]*blockInject // Set of already queued blocks (to dedupe imports)
+	queue  *prque.Prque                         // Queue containing the import operations (block number sorted)
+	queues map[string]int                       // Per peer block counts to prevent memory exhaustion
+	queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports)
 
 	// Callbacks
+	getHeader      HeaderRetrievalFn  // Retrieves a header from the local chain
 	getBlock       blockRetrievalFn   // Retrieves a block from the local chain
 	verifyHeader   headerVerifierFn   // Checks if a block's headers have a valid proof of work
 	broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers
 	chainHeight    chainHeightFn      // Retrieves the current chain's height
+	insertHeaders  headersInsertFn    // Injects a batch of headers into the chain
 	insertChain    chainInsertFn      // Injects a batch of blocks into the chain
 	dropPeer       peerDropFn         // Drops a peer for misbehaving
 
 	// Testing hooks
-	announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the blockAnnounce list
-	queueChangeHook    func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue
-	fetchingHook       func([]common.Hash)     // Method to call upon starting a block (eth/61) or header (eth/62) fetch
-	completingHook     func([]common.Hash)     // Method to call upon starting a block body fetch (eth/62)
-	importedHook       func(*types.Block)      // Method to call upon successful block import (both eth/61 and eth/62)
+	announceChangeHook func(common.Hash, bool)           // Method to call upon adding or deleting a hash from the blockAnnounce list
+	queueChangeHook    func(common.Hash, bool)           // Method to call upon adding or deleting a block from the import queue
+	fetchingHook       func([]common.Hash)               // Method to call upon starting a block (eth/61) or header (eth/62) fetch
+	completingHook     func([]common.Hash)               // Method to call upon starting a block body fetch (eth/62)
+	importedHook       func(*types.Header, *types.Block) // Method to call upon successful header or block import (both eth/61 and eth/62)
 }
 
 // NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements.
-func NewBlockFetcher(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher {
+func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertHeaders headersInsertFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher {
 	return &BlockFetcher{
+		light:          light,
 		notify:         make(chan *blockAnnounce),
-		inject:         make(chan *blockInject),
+		inject:         make(chan *blockOrHeaderInject),
 		headerFilter:   make(chan chan *headerFilterTask),
 		bodyFilter:     make(chan chan *bodyFilterTask),
 		done:           make(chan common.Hash),
@@ -184,11 +213,13 @@ func NewBlockFetcher(getBlock blockRetrievalFn, verifyHeader headerVerifierFn, b
 		completing:     make(map[common.Hash]*blockAnnounce),
 		queue:          prque.New(nil),
 		queues:         make(map[string]int),
-		queued:         make(map[common.Hash]*blockInject),
+		queued:         make(map[common.Hash]*blockOrHeaderInject),
+		getHeader:      getHeader,
 		getBlock:       getBlock,
 		verifyHeader:   verifyHeader,
 		broadcastBlock: broadcastBlock,
 		chainHeight:    chainHeight,
+		insertHeaders:  insertHeaders,
 		insertChain:    insertChain,
 		dropPeer:       dropPeer,
 	}
@@ -228,7 +259,7 @@ func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time
 
 // Enqueue tries to fill gaps the fetcher's future import queue.
 func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error {
-	op := &blockInject{
+	op := &blockOrHeaderInject{
 		origin: peer,
 		block:  block,
 	}
@@ -300,8 +331,12 @@ func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transac
 // events.
 func (f *BlockFetcher) loop() {
 	// Iterate the block fetching until a quit is requested
-	fetchTimer := time.NewTimer(0)
-	completeTimer := time.NewTimer(0)
+	var (
+		fetchTimer    = time.NewTimer(0)
+		completeTimer = time.NewTimer(0)
+	)
+	<-fetchTimer.C // clear out the channel
+	<-completeTimer.C
 	defer fetchTimer.Stop()
 	defer completeTimer.Stop()
 
@@ -315,13 +350,13 @@ func (f *BlockFetcher) loop() {
 		// Import any queued blocks that could potentially fit
 		height := f.chainHeight()
 		for !f.queue.Empty() {
-			op := f.queue.PopItem().(*blockInject)
-			hash := op.block.Hash()
+			op := f.queue.PopItem().(*blockOrHeaderInject)
+			hash := op.hash()
 			if f.queueChangeHook != nil {
 				f.queueChangeHook(hash, false)
 			}
 			// If too high up the chain or phase, continue later
-			number := op.block.NumberU64()
+			number := op.number()
 			if number > height+1 {
 				f.queue.Push(op, -int64(number))
 				if f.queueChangeHook != nil {
@@ -330,11 +365,15 @@ func (f *BlockFetcher) loop() {
 				break
 			}
 			// Otherwise if fresh and still unknown, try and import
-			if number+maxUncleDist < height || f.getBlock(hash) != nil {
+			if (number+maxUncleDist < height) || (f.light && f.getHeader(hash) != nil) || (!f.light && f.getBlock(hash) != nil) {
 				f.forgetBlock(hash)
 				continue
 			}
-			f.insert(op.origin, op.block)
+			if f.light {
+				f.importHeaders(op.origin, op.header)
+			} else {
+				f.importBlocks(op.origin, op.block)
+			}
 		}
 		// Wait for an outside event to occur
 		select {
@@ -379,7 +418,13 @@ func (f *BlockFetcher) loop() {
 		case op := <-f.inject:
 			// A direct block insertion was requested, try and fill any pending gaps
 			blockBroadcastInMeter.Mark(1)
-			f.enqueue(op.origin, op.block)
+
+			// Now only direct block injection is allowed, drop the header injection
+			// here silently if we receive.
+			if f.light {
+				continue
+			}
+			f.enqueue(op.origin, nil, op.block)
 
 		case hash := <-f.done:
 			// A pending import finished, remove all traces of the notification
@@ -391,13 +436,19 @@ func (f *BlockFetcher) loop() {
 			request := make(map[string][]common.Hash)
 
 			for hash, announces := range f.announced {
-				if time.Since(announces[0].time) > arriveTimeout-gatherSlack {
+				// In current LES protocol(les2/les3), only header announce is
+				// available, no need to wait too much time for header broadcast.
+				timeout := arriveTimeout - gatherSlack
+				if f.light {
+					timeout = 0
+				}
+				if time.Since(announces[0].time) > timeout {
 					// Pick a random peer to retrieve from, reset all others
 					announce := announces[rand.Intn(len(announces))]
 					f.forgetHash(hash)
 
 					// If the block still didn't arrive, queue for fetching
-					if f.getBlock(hash) == nil {
+					if (f.light && f.getHeader(hash) == nil) || (!f.light && f.getBlock(hash) == nil) {
 						request[announce.origin] = append(request[announce.origin], hash)
 						f.fetching[hash] = announce
 					}
@@ -465,7 +516,7 @@ func (f *BlockFetcher) loop() {
 
 			// Split the batch of headers into unknown ones (to return to the caller),
 			// known incomplete ones (requiring body retrievals) and completed blocks.
-			unknown, incomplete, complete := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}
+			unknown, incomplete, complete, lightHeaders := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}, []*blockAnnounce{}
 			for _, header := range task.headers {
 				hash := header.Hash()
 
@@ -478,13 +529,23 @@ func (f *BlockFetcher) loop() {
 						f.forgetHash(hash)
 						continue
 					}
+					// Collect all headers only if we are running in light
+					// mode and the headers are not imported by other means.
+					if f.light {
+						if f.getHeader(hash) == nil {
+							announce.header = header
+							lightHeaders = append(lightHeaders, announce)
+						}
+						f.forgetHash(hash)
+						continue
+					}
 					// Only keep if not imported by other means
 					if f.getBlock(hash) == nil {
 						announce.header = header
 						announce.time = task.time
 
 						// If the block is empty (header only), short circuit into the final import queue
-						if header.TxHash == types.DeriveSha(types.Transactions{}) && header.UncleHash == types.CalcUncleHash([]*types.Header{}) {
+						if header.TxHash == types.EmptyRootHash && header.UncleHash == types.EmptyUncleHash {
 							log.Trace("Block empty, skipping body retrieval", "peer", announce.origin, "number", header.Number, "hash", header.Hash())
 
 							block := types.NewBlockWithHeader(header)
@@ -522,10 +583,14 @@ func (f *BlockFetcher) loop() {
 					f.rescheduleComplete(completeTimer)
 				}
 			}
+			// Schedule the header for light fetcher import
+			for _, announce := range lightHeaders {
+				f.enqueue(announce.origin, announce.header, nil)
+			}
 			// Schedule the header-only blocks for import
 			for _, block := range complete {
 				if announce := f.completing[block.Hash()]; announce != nil {
-					f.enqueue(announce.origin, block)
+					f.enqueue(announce.origin, nil, block)
 				}
 			}
 
@@ -538,40 +603,51 @@ func (f *BlockFetcher) loop() {
 				return
 			}
 			bodyFilterInMeter.Mark(int64(len(task.transactions)))
-
 			blocks := []*types.Block{}
-			for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ {
-				// Match up a body to any possible completion request
-				matched := false
-
-				for hash, announce := range f.completing {
-					if f.queued[hash] == nil {
-						txnHash := types.DeriveSha(types.Transactions(task.transactions[i]))
-						uncleHash := types.CalcUncleHash(task.uncles[i])
-
-						if txnHash == announce.header.TxHash && uncleHash == announce.header.UncleHash && announce.origin == task.peer {
-							// Mark the body matched, reassemble if still unknown
-							matched = true
-
-							if f.getBlock(hash) == nil {
-								block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i])
-								block.ReceivedAt = task.time
-
-								blocks = append(blocks, block)
-							} else {
-								f.forgetHash(hash)
-							}
+			// abort early if there's nothing explicitly requested
+			if len(f.completing) > 0 {
+				for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ {
+					// Match up a body to any possible completion request
+					var (
+						matched   = false
+						uncleHash common.Hash // calculated lazily and reused
+						txnHash   common.Hash // calculated lazily and reused
+					)
+					for hash, announce := range f.completing {
+						if f.queued[hash] != nil || announce.origin != task.peer {
+							continue
+						}
+						if uncleHash == (common.Hash{}) {
+							uncleHash = types.CalcUncleHash(task.uncles[i])
+						}
+						if uncleHash != announce.header.UncleHash {
+							continue
 						}
+						if txnHash == (common.Hash{}) {
+							txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil))
+						}
+						if txnHash != announce.header.TxHash {
+							continue
+						}
+						// Mark the body matched, reassemble if still unknown
+						matched = true
+						if f.getBlock(hash) == nil {
+							block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i])
+							block.ReceivedAt = task.time
+							blocks = append(blocks, block)
+						} else {
+							f.forgetHash(hash)
+						}
+
+					}
+					if matched {
+						task.transactions = append(task.transactions[:i], task.transactions[i+1:]...)
+						task.uncles = append(task.uncles[:i], task.uncles[i+1:]...)
+						i--
+						continue
 					}
-				}
-				if matched {
-					task.transactions = append(task.transactions[:i], task.transactions[i+1:]...)
-					task.uncles = append(task.uncles[:i], task.uncles[i+1:]...)
-					i--
-					continue
 				}
 			}
-
 			bodyFilterOutMeter.Mark(int64(len(task.transactions)))
 			select {
 			case filter <- task:
@@ -581,7 +657,7 @@ func (f *BlockFetcher) loop() {
 			// Schedule the retrieved blocks for ordered import
 			for _, block := range blocks {
 				if announce := f.completing[block.Hash()]; announce != nil {
-					f.enqueue(announce.origin, block)
+					f.enqueue(announce.origin, nil, block)
 				}
 			}
 		}
@@ -594,6 +670,12 @@ func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) {
 	if len(f.announced) == 0 {
 		return
 	}
+	// Schedule announcement retrieval quickly for light mode
+	// since server won't send any headers to client.
+	if f.light {
+		fetch.Reset(lightTimeout)
+		return
+	}
 	// Otherwise find the earliest expiring announcement
 	earliest := time.Now()
 	for _, announces := range f.announced {
@@ -620,46 +702,88 @@ func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) {
 	complete.Reset(gatherSlack - time.Since(earliest))
 }
 
-// enqueue schedules a new future import operation, if the block to be imported
-// has not yet been seen.
-func (f *BlockFetcher) enqueue(peer string, block *types.Block) {
-	hash := block.Hash()
-
+// enqueue schedules a new header or block import operation, if the component
+// to be imported has not yet been seen.
+func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.Block) {
+	var (
+		hash   common.Hash
+		number uint64
+	)
+	if header != nil {
+		hash, number = header.Hash(), header.Number.Uint64()
+	} else {
+		hash, number = block.Hash(), block.NumberU64()
+	}
 	// Ensure the peer isn't DOSing us
 	count := f.queues[peer] + 1
 	if count > blockLimit {
-		log.Debug("Discarded propagated block, exceeded allowance", "peer", peer, "number", block.Number(), "hash", hash, "limit", blockLimit)
+		log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit)
 		blockBroadcastDOSMeter.Mark(1)
 		f.forgetHash(hash)
 		return
 	}
 	// Discard any past or too distant blocks
-	if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
-		log.Debug("Discarded propagated block, too far away", "peer", peer, "number", block.Number(), "hash", hash, "distance", dist)
+	if dist := int64(number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
+		log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist)
 		blockBroadcastDropMeter.Mark(1)
 		f.forgetHash(hash)
 		return
 	}
 	// Schedule the block for future importing
 	if _, ok := f.queued[hash]; !ok {
-		op := &blockInject{
-			origin: peer,
-			block:  block,
+		op := &blockOrHeaderInject{origin: peer}
+		if header != nil {
+			op.header = header
+		} else {
+			op.block = block
 		}
 		f.queues[peer] = count
 		f.queued[hash] = op
-		f.queue.Push(op, -int64(block.NumberU64()))
+		f.queue.Push(op, -int64(number))
 		if f.queueChangeHook != nil {
-			f.queueChangeHook(op.block.Hash(), true)
+			f.queueChangeHook(hash, true)
 		}
-		log.Debug("Queued propagated block", "peer", peer, "number", block.Number(), "hash", hash, "queued", f.queue.Size())
+		log.Debug("Queued delivered header or block", "peer", peer, "number", number, "hash", hash, "queued", f.queue.Size())
 	}
 }
 
-// insert spawns a new goroutine to run a block insertion into the chain. If the
+// importHeaders spawns a new goroutine to run a header insertion into the chain.
+// If the header's number is at the same height as the current import phase, it
+// updates the phase states accordingly.
+func (f *BlockFetcher) importHeaders(peer string, header *types.Header) {
+	hash := header.Hash()
+	log.Debug("Importing propagated header", "peer", peer, "number", header.Number, "hash", hash)
+
+	go func() {
+		defer func() { f.done <- hash }()
+		// If the parent's unknown, abort insertion
+		parent := f.getHeader(header.ParentHash)
+		if parent == nil {
+			log.Debug("Unknown parent of propagated header", "peer", peer, "number", header.Number, "hash", hash, "parent", header.ParentHash)
+			return
+		}
+		// Validate the header and if something went wrong, drop the peer
+		if err := f.verifyHeader(header); err != nil && err != consensus.ErrFutureBlock {
+			log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
+			f.dropPeer(peer)
+			return
+		}
+		// Run the actual import and log any issues
+		if _, err := f.insertHeaders([]*types.Header{header}); err != nil {
+			log.Debug("Propagated header import failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
+			return
+		}
+		// Invoke the testing hook if needed
+		if f.importedHook != nil {
+			f.importedHook(header, nil)
+		}
+	}()
+}
+
+// importBlocks spawns a new goroutine to run a block insertion into the chain. If the
 // block's number is at the same height as the current import phase, it updates
 // the phase states accordingly.
-func (f *BlockFetcher) insert(peer string, block *types.Block) {
+func (f *BlockFetcher) importBlocks(peer string, block *types.Block) {
 	hash := block.Hash()
 
 	// Run the import on a new thread
@@ -701,7 +825,7 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
 
 		// Invoke the testing hook if needed
 		if f.importedHook != nil {
-			f.importedHook(block)
+			f.importedHook(nil, block)
 		}
 	}()
 }
diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go
index 038ead12e7..a6eef71da0 100644
--- a/eth/fetcher/block_fetcher_test.go
+++ b/eth/fetcher/block_fetcher_test.go
@@ -31,6 +31,7 @@ import (
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 var (
@@ -38,7 +39,7 @@ var (
 	testKey, _   = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 	testAddress  = crypto.PubkeyToAddress(testKey.PublicKey)
 	genesis      = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000))
-	unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit}, nil, nil, nil)
+	unknownBlock = types.NewBlock(&types.Header{GasLimit: params.GenesisGasLimit}, nil, nil, nil, trie.NewStackTrie(nil))
 )
 
 // makeChain creates a chain of n blocks starting at and including parent.
@@ -78,26 +79,36 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
 type fetcherTester struct {
 	fetcher *BlockFetcher
 
-	hashes []common.Hash                // Hash chain belonging to the tester
-	blocks map[common.Hash]*types.Block // Blocks belonging to the tester
-	drops  map[string]bool              // Map of peers dropped by the fetcher
+	hashes  []common.Hash                 // Hash chain belonging to the tester
+	headers map[common.Hash]*types.Header // Headers belonging to the tester
+	blocks  map[common.Hash]*types.Block  // Blocks belonging to the tester
+	drops   map[string]bool               // Map of peers dropped by the fetcher
 
 	lock sync.RWMutex
 }
 
 // newTester creates a new fetcher test mocker.
-func newTester() *fetcherTester {
+func newTester(light bool) *fetcherTester {
 	tester := &fetcherTester{
-		hashes: []common.Hash{genesis.Hash()},
-		blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
-		drops:  make(map[string]bool),
+		hashes:  []common.Hash{genesis.Hash()},
+		headers: map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()},
+		blocks:  map[common.Hash]*types.Block{genesis.Hash(): genesis},
+		drops:   make(map[string]bool),
 	}
-	tester.fetcher = NewBlockFetcher(tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertChain, tester.dropPeer)
+	tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertHeaders, tester.insertChain, tester.dropPeer)
 	tester.fetcher.Start()
 
 	return tester
 }
 
+// getHeader retrieves a header from the tester's block chain.
+func (f *fetcherTester) getHeader(hash common.Hash) *types.Header {
+	f.lock.RLock()
+	defer f.lock.RUnlock()
+
+	return f.headers[hash]
+}
+
 // getBlock retrieves a block from the tester's block chain.
 func (f *fetcherTester) getBlock(hash common.Hash) *types.Block {
 	f.lock.RLock()
@@ -120,9 +131,33 @@ func (f *fetcherTester) chainHeight() uint64 {
 	f.lock.RLock()
 	defer f.lock.RUnlock()
 
+	if f.fetcher.light {
+		return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64()
+	}
 	return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64()
 }
 
+// insertChain injects a new headers into the simulated chain.
+func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) {
+	f.lock.Lock()
+	defer f.lock.Unlock()
+
+	for i, header := range headers {
+		// Make sure the parent in known
+		if _, ok := f.headers[header.ParentHash]; !ok {
+			return i, errors.New("unknown parent")
+		}
+		// Discard any new blocks if the same height already exists
+		if header.Number.Uint64() <= f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() {
+			return i, nil
+		}
+		// Otherwise build our current chain
+		f.hashes = append(f.hashes, header.Hash())
+		f.headers[header.Hash()] = header
+	}
+	return 0, nil
+}
+
 // insertChain injects a new blocks into the simulated chain.
 func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) {
 	f.lock.Lock()
@@ -233,7 +268,7 @@ func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive b
 }
 
 // verifyImportEvent verifies that one single event arrive on an import channel.
-func verifyImportEvent(t *testing.T, imported chan *types.Block, arrive bool) {
+func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
 	if arrive {
 		select {
 		case <-imported:
@@ -251,7 +286,7 @@ func verifyImportEvent(t *testing.T, imported chan *types.Block, arrive bool) {
 
 // verifyImportCount verifies that exactly count number of events arrive on an
 // import hook channel.
-func verifyImportCount(t *testing.T, imported chan *types.Block, count int) {
+func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
 	for i := 0; i < count; i++ {
 		select {
 		case <-imported:
@@ -263,7 +298,7 @@ func verifyImportCount(t *testing.T, imported chan *types.Block, count int) {
 }
 
 // verifyImportDone verifies that no more events are arriving on an import channel.
-func verifyImportDone(t *testing.T, imported chan *types.Block) {
+func verifyImportDone(t *testing.T, imported chan interface{}) {
 	select {
 	case <-imported:
 		t.Fatalf("extra block imported")
@@ -271,45 +306,62 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
 	}
 }
 
-// Tests that a fetcher accepts block announcements and initiates retrievals for
-// them, successfully importing into the local chain.
-func TestSequentialAnnouncements62(t *testing.T) { testSequentialAnnouncements(t, 62) }
-func TestSequentialAnnouncements63(t *testing.T) { testSequentialAnnouncements(t, 63) }
-func TestSequentialAnnouncements64(t *testing.T) { testSequentialAnnouncements(t, 64) }
+// verifyChainHeight verifies the chain height is as expected.
+func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) {
+	if fetcher.chainHeight() != height {
+		t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height)
+	}
+}
 
-func testSequentialAnnouncements(t *testing.T, protocol int) {
+// Tests that a fetcher accepts block/header announcements and initiates retrievals
+// for them, successfully importing into the local chain.
+func TestFullSequentialAnnouncements(t *testing.T)  { testSequentialAnnouncements(t, false) }
+func TestLightSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, true) }
+
+func testSequentialAnnouncements(t *testing.T, light bool) {
 	// Create a chain of blocks to import
 	targetBlocks := 4 * hashLimit
 	hashes, blocks := makeChain(targetBlocks, 0, genesis)
 
-	tester := newTester()
+	tester := newTester(light)
 	headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
 
 	// Iteratively announce blocks until all are imported
-	imported := make(chan *types.Block)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
-
+	imported := make(chan interface{})
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
+		if light {
+			if header == nil {
+				t.Fatalf("Fetcher try to import empty header")
+			}
+			imported <- header
+		} else {
+			if block == nil {
+				t.Fatalf("Fetcher try to import empty block")
+			}
+			imported <- block
+		}
+	}
 	for i := len(hashes) - 2; i >= 0; i-- {
 		tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
 		verifyImportEvent(t, imported, true)
 	}
 	verifyImportDone(t, imported)
+	verifyChainHeight(t, tester, uint64(len(hashes)-1))
 }
 
 // Tests that if blocks are announced by multiple peers (or even the same buggy
 // peer), they will only get downloaded at most once.
-func TestConcurrentAnnouncements62(t *testing.T) { testConcurrentAnnouncements(t, 62) }
-func TestConcurrentAnnouncements63(t *testing.T) { testConcurrentAnnouncements(t, 63) }
-func TestConcurrentAnnouncements64(t *testing.T) { testConcurrentAnnouncements(t, 64) }
+func TestFullConcurrentAnnouncements(t *testing.T)  { testConcurrentAnnouncements(t, false) }
+func TestLightConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, true) }
 
-func testConcurrentAnnouncements(t *testing.T, protocol int) {
+func testConcurrentAnnouncements(t *testing.T, light bool) {
 	// Create a chain of blocks to import
 	targetBlocks := 4 * hashLimit
 	hashes, blocks := makeChain(targetBlocks, 0, genesis)
 
 	// Assemble a tester with a built in counter for the requests
-	tester := newTester()
+	tester := newTester(light)
 	firstHeaderFetcher := tester.makeHeaderFetcher("first", blocks, -gatherSlack)
 	firstBodyFetcher := tester.makeBodyFetcher("first", blocks, 0)
 	secondHeaderFetcher := tester.makeHeaderFetcher("second", blocks, -gatherSlack)
@@ -325,9 +377,20 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
 		return secondHeaderFetcher(hash)
 	}
 	// Iteratively announce blocks until all are imported
-	imported := make(chan *types.Block)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
-
+	imported := make(chan interface{})
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
+		if light {
+			if header == nil {
+				t.Fatalf("Fetcher try to import empty header")
+			}
+			imported <- header
+		} else {
+			if block == nil {
+				t.Fatalf("Fetcher try to import empty block")
+			}
+			imported <- block
+		}
+	}
 	for i := len(hashes) - 2; i >= 0; i-- {
 		tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher)
 		tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher)
@@ -340,30 +403,42 @@ func testConcurrentAnnouncements(t *testing.T, protocol int) {
 	if int(counter) != targetBlocks {
 		t.Fatalf("retrieval count mismatch: have %v, want %v", counter, targetBlocks)
 	}
+	verifyChainHeight(t, tester, uint64(len(hashes)-1))
 }
 
 // Tests that announcements arriving while a previous is being fetched still
 // results in a valid import.
-func TestOverlappingAnnouncements62(t *testing.T) { testOverlappingAnnouncements(t, 62) }
-func TestOverlappingAnnouncements63(t *testing.T) { testOverlappingAnnouncements(t, 63) }
-func TestOverlappingAnnouncements64(t *testing.T) { testOverlappingAnnouncements(t, 64) }
+func TestFullOverlappingAnnouncements(t *testing.T)  { testOverlappingAnnouncements(t, false) }
+func TestLightOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, true) }
 
-func testOverlappingAnnouncements(t *testing.T, protocol int) {
+func testOverlappingAnnouncements(t *testing.T, light bool) {
 	// Create a chain of blocks to import
 	targetBlocks := 4 * hashLimit
 	hashes, blocks := makeChain(targetBlocks, 0, genesis)
 
-	tester := newTester()
+	tester := newTester(light)
 	headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
 
 	// Iteratively announce blocks, but overlap them continuously
 	overlap := 16
-	imported := make(chan *types.Block, len(hashes)-1)
+	imported := make(chan interface{}, len(hashes)-1)
 	for i := 0; i < overlap; i++ {
 		imported <- nil
 	}
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
+		if light {
+			if header == nil {
+				t.Fatalf("Fetcher try to import empty header")
+			}
+			imported <- header
+		} else {
+			if block == nil {
+				t.Fatalf("Fetcher try to import empty block")
+			}
+			imported <- block
+		}
+	}
 
 	for i := len(hashes) - 2; i >= 0; i-- {
 		tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
@@ -375,19 +450,19 @@ func testOverlappingAnnouncements(t *testing.T, protocol int) {
 	}
 	// Wait for all the imports to complete and check count
 	verifyImportCount(t, imported, overlap)
+	verifyChainHeight(t, tester, uint64(len(hashes)-1))
 }
 
 // Tests that announces already being retrieved will not be duplicated.
-func TestPendingDeduplication62(t *testing.T) { testPendingDeduplication(t, 62) }
-func TestPendingDeduplication63(t *testing.T) { testPendingDeduplication(t, 63) }
-func TestPendingDeduplication64(t *testing.T) { testPendingDeduplication(t, 64) }
+func TestFullPendingDeduplication(t *testing.T)  { testPendingDeduplication(t, false) }
+func TestLightPendingDeduplication(t *testing.T) { testPendingDeduplication(t, true) }
 
-func testPendingDeduplication(t *testing.T, protocol int) {
+func testPendingDeduplication(t *testing.T, light bool) {
 	// Create a hash and corresponding block
 	hashes, blocks := makeChain(1, 0, genesis)
 
 	// Assemble a tester with a built in counter and delayed fetcher
-	tester := newTester()
+	tester := newTester(light)
 	headerFetcher := tester.makeHeaderFetcher("repeater", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("repeater", blocks, 0)
 
@@ -403,42 +478,58 @@ func testPendingDeduplication(t *testing.T, protocol int) {
 		}()
 		return nil
 	}
+	checkNonExist := func() bool {
+		return tester.getBlock(hashes[0]) == nil
+	}
+	if light {
+		checkNonExist = func() bool {
+			return tester.getHeader(hashes[0]) == nil
+		}
+	}
 	// Announce the same block many times until it's fetched (wait for any pending ops)
-	for tester.getBlock(hashes[0]) == nil {
+	for checkNonExist() {
 		tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher)
 		time.Sleep(time.Millisecond)
 	}
 	time.Sleep(delay)
 
 	// Check that all blocks were imported and none fetched twice
-	if imported := len(tester.blocks); imported != 2 {
-		t.Fatalf("synchronised block mismatch: have %v, want %v", imported, 2)
-	}
 	if int(counter) != 1 {
 		t.Fatalf("retrieval count mismatch: have %v, want %v", counter, 1)
 	}
+	verifyChainHeight(t, tester, 1)
 }
 
 // Tests that announcements retrieved in a random order are cached and eventually
 // imported when all the gaps are filled in.
-func TestRandomArrivalImport62(t *testing.T) { testRandomArrivalImport(t, 62) }
-func TestRandomArrivalImport63(t *testing.T) { testRandomArrivalImport(t, 63) }
-func TestRandomArrivalImport64(t *testing.T) { testRandomArrivalImport(t, 64) }
+func TestFullRandomArrivalImport(t *testing.T)  { testRandomArrivalImport(t, false) }
+func TestLightRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, true) }
 
-func testRandomArrivalImport(t *testing.T, protocol int) {
+func testRandomArrivalImport(t *testing.T, light bool) {
 	// Create a chain of blocks to import, and choose one to delay
 	targetBlocks := maxQueueDist
 	hashes, blocks := makeChain(targetBlocks, 0, genesis)
 	skip := targetBlocks / 2
 
-	tester := newTester()
+	tester := newTester(light)
 	headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
 
 	// Iteratively announce blocks, skipping one entry
-	imported := make(chan *types.Block, len(hashes)-1)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
-
+	imported := make(chan interface{}, len(hashes)-1)
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
+		if light {
+			if header == nil {
+				t.Fatalf("Fetcher try to import empty header")
+			}
+			imported <- header
+		} else {
+			if block == nil {
+				t.Fatalf("Fetcher try to import empty block")
+			}
+			imported <- block
+		}
+	}
 	for i := len(hashes) - 1; i >= 0; i-- {
 		if i != skip {
 			tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
@@ -448,27 +539,24 @@ func testRandomArrivalImport(t *testing.T, protocol int) {
 	// Finally announce the skipped entry and check full import
 	tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
 	verifyImportCount(t, imported, len(hashes)-1)
+	verifyChainHeight(t, tester, uint64(len(hashes)-1))
 }
 
 // Tests that direct block enqueues (due to block propagation vs. hash announce)
 // are correctly schedule, filling and import queue gaps.
-func TestQueueGapFill62(t *testing.T) { testQueueGapFill(t, 62) }
-func TestQueueGapFill63(t *testing.T) { testQueueGapFill(t, 63) }
-func TestQueueGapFill64(t *testing.T) { testQueueGapFill(t, 64) }
-
-func testQueueGapFill(t *testing.T, protocol int) {
+func TestQueueGapFill(t *testing.T) {
 	// Create a chain of blocks to import, and choose one to not announce at all
 	targetBlocks := maxQueueDist
 	hashes, blocks := makeChain(targetBlocks, 0, genesis)
 	skip := targetBlocks / 2
 
-	tester := newTester()
+	tester := newTester(false)
 	headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
 
 	// Iteratively announce blocks, skipping one entry
-	imported := make(chan *types.Block, len(hashes)-1)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
+	imported := make(chan interface{}, len(hashes)-1)
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
 
 	for i := len(hashes) - 1; i >= 0; i-- {
 		if i != skip {
@@ -479,20 +567,17 @@ func testQueueGapFill(t *testing.T, protocol int) {
 	// Fill the missing block directly as if propagated
 	tester.fetcher.Enqueue("valid", blocks[hashes[skip]])
 	verifyImportCount(t, imported, len(hashes)-1)
+	verifyChainHeight(t, tester, uint64(len(hashes)-1))
 }
 
 // Tests that blocks arriving from various sources (multiple propagations, hash
 // announces, etc) do not get scheduled for import multiple times.
-func TestImportDeduplication62(t *testing.T) { testImportDeduplication(t, 62) }
-func TestImportDeduplication63(t *testing.T) { testImportDeduplication(t, 63) }
-func TestImportDeduplication64(t *testing.T) { testImportDeduplication(t, 64) }
-
-func testImportDeduplication(t *testing.T, protocol int) {
+func TestImportDeduplication(t *testing.T) {
 	// Create two blocks to import (one for duplication, the other for stalling)
 	hashes, blocks := makeChain(2, 0, genesis)
 
 	// Create the tester and wrap the importer with a counter
-	tester := newTester()
+	tester := newTester(false)
 	headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
 
@@ -503,9 +588,9 @@ func testImportDeduplication(t *testing.T, protocol int) {
 	}
 	// Instrument the fetching and imported events
 	fetching := make(chan []common.Hash)
-	imported := make(chan *types.Block, len(hashes)-1)
+	imported := make(chan interface{}, len(hashes)-1)
 	tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes }
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
 
 	// Announce the duplicating block, wait for retrieval, and also propagate directly
 	tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
@@ -534,7 +619,7 @@ func TestDistantPropagationDiscarding(t *testing.T) {
 	low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1
 
 	// Create a tester and simulate a head block being the middle of the above chain
-	tester := newTester()
+	tester := newTester(false)
 
 	tester.lock.Lock()
 	tester.hashes = []common.Hash{head}
@@ -558,11 +643,10 @@ func TestDistantPropagationDiscarding(t *testing.T) {
 // Tests that announcements with numbers much lower or higher than out current
 // head get discarded to prevent wasting resources on useless blocks from faulty
 // peers.
-func TestDistantAnnouncementDiscarding62(t *testing.T) { testDistantAnnouncementDiscarding(t, 62) }
-func TestDistantAnnouncementDiscarding63(t *testing.T) { testDistantAnnouncementDiscarding(t, 63) }
-func TestDistantAnnouncementDiscarding64(t *testing.T) { testDistantAnnouncementDiscarding(t, 64) }
+func TestFullDistantAnnouncementDiscarding(t *testing.T)  { testDistantAnnouncementDiscarding(t, false) }
+func TestLightDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, true) }
 
-func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
+func testDistantAnnouncementDiscarding(t *testing.T, light bool) {
 	// Create a long chain to import and define the discard boundaries
 	hashes, blocks := makeChain(3*maxQueueDist, 0, genesis)
 	head := hashes[len(hashes)/2]
@@ -570,10 +654,11 @@ func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
 	low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1
 
 	// Create a tester and simulate a head block being the middle of the above chain
-	tester := newTester()
+	tester := newTester(light)
 
 	tester.lock.Lock()
 	tester.hashes = []common.Hash{head}
+	tester.headers = map[common.Hash]*types.Header{head: blocks[head].Header()}
 	tester.blocks = map[common.Hash]*types.Block{head: blocks[head]}
 	tester.lock.Unlock()
 
@@ -601,21 +686,31 @@ func testDistantAnnouncementDiscarding(t *testing.T, protocol int) {
 
 // Tests that peers announcing blocks with invalid numbers (i.e. not matching
 // the headers provided afterwards) get dropped as malicious.
-func TestInvalidNumberAnnouncement62(t *testing.T) { testInvalidNumberAnnouncement(t, 62) }
-func TestInvalidNumberAnnouncement63(t *testing.T) { testInvalidNumberAnnouncement(t, 63) }
-func TestInvalidNumberAnnouncement64(t *testing.T) { testInvalidNumberAnnouncement(t, 64) }
+func TestFullInvalidNumberAnnouncement(t *testing.T)  { testInvalidNumberAnnouncement(t, false) }
+func TestLightInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, true) }
 
-func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
+func testInvalidNumberAnnouncement(t *testing.T, light bool) {
 	// Create a single block to import and check numbers against
 	hashes, blocks := makeChain(1, 0, genesis)
 
-	tester := newTester()
+	tester := newTester(light)
 	badHeaderFetcher := tester.makeHeaderFetcher("bad", blocks, -gatherSlack)
 	badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0)
 
-	imported := make(chan *types.Block)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
-
+	imported := make(chan interface{})
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
+		if light {
+			if header == nil {
+				t.Fatalf("Fetcher try to import empty header")
+			}
+			imported <- header
+		} else {
+			if block == nil {
+				t.Fatalf("Fetcher try to import empty block")
+			}
+			imported <- block
+		}
+	}
 	// Announce a block with a bad number, check for immediate drop
 	tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher)
 	verifyImportEvent(t, imported, false)
@@ -646,15 +741,11 @@ func testInvalidNumberAnnouncement(t *testing.T, protocol int) {
 
 // Tests that if a block is empty (i.e. header only), no body request should be
 // made, and instead the header should be assembled into a whole block in itself.
-func TestEmptyBlockShortCircuit62(t *testing.T) { testEmptyBlockShortCircuit(t, 62) }
-func TestEmptyBlockShortCircuit63(t *testing.T) { testEmptyBlockShortCircuit(t, 63) }
-func TestEmptyBlockShortCircuit64(t *testing.T) { testEmptyBlockShortCircuit(t, 64) }
-
-func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
+func TestEmptyBlockShortCircuit(t *testing.T) {
 	// Create a chain of blocks to import
 	hashes, blocks := makeChain(32, 0, genesis)
 
-	tester := newTester()
+	tester := newTester(false)
 	headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack)
 	bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0)
 
@@ -665,9 +756,13 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
 	completing := make(chan []common.Hash)
 	tester.fetcher.completingHook = func(hashes []common.Hash) { completing <- hashes }
 
-	imported := make(chan *types.Block)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
-
+	imported := make(chan interface{})
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) {
+		if block == nil {
+			t.Fatalf("Fetcher try to import empty block")
+		}
+		imported <- block
+	}
 	// Iteratively announce blocks until all are imported
 	for i := len(hashes) - 2; i >= 0; i-- {
 		tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher)
@@ -687,16 +782,12 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) {
 // Tests that a peer is unable to use unbounded memory with sending infinite
 // block announcements to a node, but that even in the face of such an attack,
 // the fetcher remains operational.
-func TestHashMemoryExhaustionAttack62(t *testing.T) { testHashMemoryExhaustionAttack(t, 62) }
-func TestHashMemoryExhaustionAttack63(t *testing.T) { testHashMemoryExhaustionAttack(t, 63) }
-func TestHashMemoryExhaustionAttack64(t *testing.T) { testHashMemoryExhaustionAttack(t, 64) }
-
-func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
+func TestHashMemoryExhaustionAttack(t *testing.T) {
 	// Create a tester with instrumented import hooks
-	tester := newTester()
+	tester := newTester(false)
 
-	imported, announces := make(chan *types.Block), int32(0)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
+	imported, announces := make(chan interface{}), int32(0)
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
 	tester.fetcher.announceChangeHook = func(hash common.Hash, added bool) {
 		if added {
 			atomic.AddInt32(&announces, 1)
@@ -740,10 +831,10 @@ func testHashMemoryExhaustionAttack(t *testing.T, protocol int) {
 // system memory.
 func TestBlockMemoryExhaustionAttack(t *testing.T) {
 	// Create a tester with instrumented import hooks
-	tester := newTester()
+	tester := newTester(false)
 
-	imported, enqueued := make(chan *types.Block), int32(0)
-	tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
+	imported, enqueued := make(chan interface{}), int32(0)
+	tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block }
 	tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) {
 		if added {
 			atomic.AddInt32(&enqueued, 1)
diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go
index c497cebb45..3ba7753916 100644
--- a/eth/fetcher/tx_fetcher.go
+++ b/eth/fetcher/tx_fetcher.go
@@ -387,7 +387,7 @@ func (f *TxFetcher) loop() {
 					if announces := f.announces[ann.origin]; announces != nil {
 						announces[hash] = struct{}{}
 					} else {
-						f.announces[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
+						f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}}
 					}
 					continue
 				}
@@ -400,7 +400,7 @@ func (f *TxFetcher) loop() {
 					if announces := f.announces[ann.origin]; announces != nil {
 						announces[hash] = struct{}{}
 					} else {
-						f.announces[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
+						f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}}
 					}
 					continue
 				}
@@ -413,18 +413,18 @@ func (f *TxFetcher) loop() {
 					if waitslots := f.waitslots[ann.origin]; waitslots != nil {
 						waitslots[hash] = struct{}{}
 					} else {
-						f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
+						f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}}
 					}
 					continue
 				}
 				// Transaction unknown to the fetcher, insert it into the waiting list
-				f.waitlist[hash] = map[string]struct{}{ann.origin: struct{}{}}
+				f.waitlist[hash] = map[string]struct{}{ann.origin: {}}
 				f.waittime[hash] = f.clock.Now()
 
 				if waitslots := f.waitslots[ann.origin]; waitslots != nil {
 					waitslots[hash] = struct{}{}
 				} else {
-					f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: struct{}{}}
+					f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}}
 				}
 			}
 			// If a new item was added to the waitlist, schedule it into the fetcher
@@ -434,7 +434,7 @@ func (f *TxFetcher) loop() {
 			// If this peer is new and announced something already queued, maybe
 			// request transactions from them
 			if !oldPeer && len(f.announces[ann.origin]) > 0 {
-				f.scheduleFetches(timeoutTimer, timeoutTrigger, map[string]struct{}{ann.origin: struct{}{}})
+				f.scheduleFetches(timeoutTimer, timeoutTrigger, map[string]struct{}{ann.origin: {}})
 			}
 
 		case <-waitTrigger:
@@ -452,7 +452,7 @@ func (f *TxFetcher) loop() {
 						if announces := f.announces[peer]; announces != nil {
 							announces[hash] = struct{}{}
 						} else {
-							f.announces[peer] = map[common.Hash]struct{}{hash: struct{}{}}
+							f.announces[peer] = map[common.Hash]struct{}{hash: {}}
 						}
 						delete(f.waitslots[peer], hash)
 						if len(f.waitslots[peer]) == 0 {
@@ -515,7 +515,7 @@ func (f *TxFetcher) loop() {
 			// Schedule a new transaction retrieval
 			f.scheduleFetches(timeoutTimer, timeoutTrigger, nil)
 
-			// No idea if we sheduled something or not, trigger the timer if needed
+			// No idea if we scheduled something or not, trigger the timer if needed
 			// TODO(karalabe): this is kind of lame, can't we dump it into scheduleFetches somehow?
 			f.rescheduleTimeout(timeoutTimer, timeoutTrigger)
 
diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go
index a50906ed7c..796d4caf0f 100644
--- a/eth/fetcher/tx_fetcher_test.go
+++ b/eth/fetcher/tx_fetcher_test.go
@@ -30,7 +30,7 @@ import (
 )
 
 var (
-	// testTxs is a set of transactions to use during testing that have meaninful hashes.
+	// testTxs is a set of transactions to use during testing that have meaningful hashes.
 	testTxs = []*types.Transaction{
 		types.NewTransaction(5577006791947779410, common.Address{0x0f}, new(big.Int), 0, new(big.Int), nil),
 		types.NewTransaction(15352856648520921629, common.Address{0xbb}, new(big.Int), 0, new(big.Int), nil),
@@ -449,7 +449,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) {
 	})
 }
 
-// Tests that non-returned transactions are either re-sheduled from a
+// Tests that non-returned transactions are either re-scheduled from a
 // different peer, or self if they are after the cutoff point.
 func TestTransactionFetcherMissingRescheduling(t *testing.T) {
 	testTransactionFetcherParallel(t, txFetcherTest{
diff --git a/eth/filters/api.go b/eth/filters/api.go
index c299988dbd..00e28d8955 100644
--- a/eth/filters/api.go
+++ b/eth/filters/api.go
@@ -25,7 +25,7 @@ import (
 	"sync"
 	"time"
 
-	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/core/types"
@@ -34,10 +34,6 @@ import (
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
-var (
-	deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline
-)
-
 // filter is a helper struct that holds meta information over the filter type
 // and associated subscription in the event system.
 type filter struct {
@@ -59,27 +55,30 @@ type PublicFilterAPI struct {
 	events     *EventSystem
 	filtersMu  sync.Mutex
 	filters    map[rpc.ID]*filter
+	timeout    time.Duration
 	rangeLimit bool
 }
 
 // NewPublicFilterAPI returns a new PublicFilterAPI instance.
-func NewPublicFilterAPI(backend Backend, lightMode, rangeLimit bool) *PublicFilterAPI {
+func NewPublicFilterAPI(backend Backend, lightMode bool, timeout time.Duration, rangeLimit bool) *PublicFilterAPI {
 	api := &PublicFilterAPI{
 		backend:    backend,
 		chainDb:    backend.ChainDb(),
 		events:     NewEventSystem(backend, lightMode),
 		filters:    make(map[rpc.ID]*filter),
+		timeout:    timeout,
 		rangeLimit: rangeLimit,
 	}
-	go api.timeoutLoop()
+	go api.timeoutLoop(timeout)
 
 	return api
 }
 
-// timeoutLoop runs every 5 minutes and deletes filters that have not been recently used.
-// Tt is started when the api is created.
-func (api *PublicFilterAPI) timeoutLoop() {
-	ticker := time.NewTicker(5 * time.Minute)
+// timeoutLoop runs at the interval set by 'timeout' and deletes filters
+// that have not been recently used. It is started when the API is created.
+func (api *PublicFilterAPI) timeoutLoop(timeout time.Duration) {
+	var toUninstall []*Subscription
+	ticker := time.NewTicker(timeout)
 	defer ticker.Stop()
 	for {
 		<-ticker.C
@@ -87,13 +86,21 @@ func (api *PublicFilterAPI) timeoutLoop() {
 		for id, f := range api.filters {
 			select {
 			case <-f.deadline.C:
-				f.s.Unsubscribe()
+				toUninstall = append(toUninstall, f.s)
 				delete(api.filters, id)
 			default:
 				continue
 			}
 		}
 		api.filtersMu.Unlock()
+
+		// Unsubscribes are processed outside the lock to avoid the following scenario:
+		// event loop attempts broadcasting events to still active filters while
+		// Unsubscribe is waiting for it to process the uninstall request.
+		for _, s := range toUninstall {
+			s.Unsubscribe()
+		}
+		toUninstall = nil
 	}
 }
 
@@ -103,13 +110,13 @@ func (api *PublicFilterAPI) timeoutLoop() {
 // It is part of the filter package because this filter can be used through the
 // `eth_getFilterChanges` polling method that is also used for log filters.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter
+// https://eth.wiki/json-rpc/API#eth_newpendingtransactionfilter
 func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
 	var (
 		pendingTxs   = make(chan []common.Hash)
 		pendingTxSub = api.events.SubscribePendingTxs(pendingTxs)
 	)
-	f := &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
+	f := &filter{typ: PendingTransactionsSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: pendingTxSub}
 	api.filtersMu.Lock()
 	api.filters[pendingTxSub.ID] = f
 	api.filtersMu.Unlock()
@@ -169,7 +176,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
 // NewBlockFilter creates a filter that fetches blocks that are imported into the chain.
 // It is part of the filter package since polling goes with eth_getFilterChanges.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter
+// https://eth.wiki/json-rpc/API#eth_newblockfilter
 func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
 	var (
 		headers   = make(chan *types.Header)
@@ -177,7 +184,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
 	)
 
 	api.filtersMu.Lock()
-	api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub}
+	api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub}
 	api.filtersMu.Unlock()
 
 	go func() {
@@ -285,16 +292,16 @@ type FilterCriteria ethereum.FilterQuery
 //
 // In case "fromBlock" > "toBlock" an error is returned.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
+// https://eth.wiki/json-rpc/API#eth_newfilter
 func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
 	logs := make(chan []*types.Log)
 	logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs)
 	if err != nil {
-		return rpc.ID(""), err
+		return "", err
 	}
 
 	api.filtersMu.Lock()
-	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub}
+	api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub}
 	api.filtersMu.Unlock()
 
 	go func() {
@@ -320,7 +327,7 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
 
 // GetLogs returns logs matching the given argument that are stored within the state.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
+// https://eth.wiki/json-rpc/API#eth_getlogs
 func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
 	var filter *Filter
 	if crit.BlockHash != nil {
@@ -349,7 +356,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([
 
 // UninstallFilter removes the filter with the given filter id.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter
+// https://eth.wiki/json-rpc/API#eth_uninstallfilter
 func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
 	api.filtersMu.Lock()
 	f, found := api.filters[id]
@@ -367,7 +374,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
 // GetFilterLogs returns the logs for the filter with the given id.
 // If the filter could not be found an empty array of logs is returned.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs
+// https://eth.wiki/json-rpc/API#eth_getfilterlogs
 func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
 	api.filtersMu.Lock()
 	f, found := api.filters[id]
@@ -408,7 +415,7 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ty
 // For pending transaction and block filters the result is []common.Hash.
 // (pending)Log filters return []Log.
 //
-// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges
+// https://eth.wiki/json-rpc/API#eth_getfilterchanges
 func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
 	api.filtersMu.Lock()
 	defer api.filtersMu.Unlock()
@@ -419,7 +426,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
 			// receive timer value and reset timer
 			<-f.deadline.C
 		}
-		f.deadline.Reset(deadline)
+		f.deadline.Reset(api.timeout)
 
 		switch f.typ {
 		case PendingTransactionsSubscription, BlocksSubscription:
diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go
index e55ff0ca13..bff73b3968 100644
--- a/eth/filters/bench_test.go
+++ b/eth/filters/bench_test.go
@@ -65,7 +65,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) {
 	benchDataDir := node.DefaultDataDir() + "/geth/chaindata"
 	b.Log("Running bloombits benchmark   section size:", sectionSize)
 
-	db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "")
+	db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
 	if err != nil {
 		b.Fatalf("error opening database at %v: %v", benchDataDir, err)
 	}
@@ -126,7 +126,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) {
 	for i := 0; i < benchFilterCnt; i++ {
 		if i%20 == 0 {
 			db.Close()
-			db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "")
+			db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
 			backend = &testBackend{db: db, sections: cnt}
 		}
 		var addr common.Address
@@ -157,7 +157,7 @@ func clearBloomBits(db ethdb.Database) {
 func BenchmarkNoBloomBits(b *testing.B) {
 	benchDataDir := node.DefaultDataDir() + "/geth/chaindata"
 	b.Log("Running benchmark without bloombits")
-	db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "")
+	db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
 	if err != nil {
 		b.Fatalf("error opening database at %v: %v", benchDataDir, err)
 	}
diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go
index ca360ec86a..4be7ceb449 100644
--- a/eth/filters/filter_system.go
+++ b/eth/filters/filter_system.go
@@ -24,7 +24,7 @@ import (
 	"sync"
 	"time"
 
-	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go
index 4aba9621cf..050bbcc012 100644
--- a/eth/filters/filter_system_test.go
+++ b/eth/filters/filter_system_test.go
@@ -22,10 +22,11 @@ import (
 	"math/big"
 	"math/rand"
 	"reflect"
+	"runtime"
 	"testing"
 	"time"
 
-	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
@@ -38,6 +39,10 @@ import (
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
+var (
+	deadline = 5 * time.Minute
+)
+
 type testBackend struct {
 	mux             *event.TypeMux
 	db              ethdb.Database
@@ -163,7 +168,7 @@ func TestBlockSubscription(t *testing.T) {
 	var (
 		db          = rawdb.NewMemoryDatabase()
 		backend     = &testBackend{db: db}
-		api         = NewPublicFilterAPI(backend, false, false)
+		api         = NewPublicFilterAPI(backend, false, deadline, false)
 		genesis     = new(core.Genesis).MustCommit(db)
 		chain, _    = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {})
 		chainEvents = []core.ChainEvent{}
@@ -215,7 +220,7 @@ func TestPendingTxFilter(t *testing.T) {
 	var (
 		db      = rawdb.NewMemoryDatabase()
 		backend = &testBackend{db: db}
-		api     = NewPublicFilterAPI(backend, false, false)
+		api     = NewPublicFilterAPI(backend, false, deadline, false)
 
 		transactions = []*types.Transaction{
 			types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
@@ -270,7 +275,7 @@ func TestLogFilterCreation(t *testing.T) {
 	var (
 		db      = rawdb.NewMemoryDatabase()
 		backend = &testBackend{db: db}
-		api     = NewPublicFilterAPI(backend, false, false)
+		api     = NewPublicFilterAPI(backend, false, deadline, false)
 
 		testCases = []struct {
 			crit    FilterCriteria
@@ -314,7 +319,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
 	var (
 		db      = rawdb.NewMemoryDatabase()
 		backend = &testBackend{db: db}
-		api     = NewPublicFilterAPI(backend, false, false)
+		api     = NewPublicFilterAPI(backend, false, deadline, false)
 	)
 
 	// different situations where log filter creation should fail.
@@ -336,7 +341,7 @@ func TestInvalidGetLogsRequest(t *testing.T) {
 	var (
 		db        = rawdb.NewMemoryDatabase()
 		backend   = &testBackend{db: db}
-		api       = NewPublicFilterAPI(backend, false, false)
+		api       = NewPublicFilterAPI(backend, false, deadline, false)
 		blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
 	)
 
@@ -361,7 +366,7 @@ func TestLogFilter(t *testing.T) {
 	var (
 		db      = rawdb.NewMemoryDatabase()
 		backend = &testBackend{db: db}
-		api     = NewPublicFilterAPI(backend, false, false)
+		api     = NewPublicFilterAPI(backend, false, deadline, false)
 
 		firstAddr      = common.HexToAddress("0x1111111111111111111111111111111111111111")
 		secondAddr     = common.HexToAddress("0x2222222222222222222222222222222222222222")
@@ -475,7 +480,7 @@ func TestPendingLogsSubscription(t *testing.T) {
 	var (
 		db      = rawdb.NewMemoryDatabase()
 		backend = &testBackend{db: db}
-		api     = NewPublicFilterAPI(backend, false, false)
+		api     = NewPublicFilterAPI(backend, false, deadline, false)
 
 		firstAddr      = common.HexToAddress("0x1111111111111111111111111111111111111111")
 		secondAddr     = common.HexToAddress("0x2222222222222222222222222222222222222222")
@@ -601,6 +606,73 @@ func TestPendingLogsSubscription(t *testing.T) {
 	}
 }
 
+// TestPendingTxFilterDeadlock tests if the event loop hangs when pending
+// txes arrive at the same time that one of multiple filters is timing out.
+// Please refer to #22131 for more details.
+func TestPendingTxFilterDeadlock(t *testing.T) {
+	t.Parallel()
+	timeout := 100 * time.Millisecond
+
+	var (
+		db      = rawdb.NewMemoryDatabase()
+		backend = &testBackend{db: db}
+		api     = NewPublicFilterAPI(backend, false, timeout, false)
+		done    = make(chan struct{})
+	)
+
+	go func() {
+		// Bombard feed with txes until signal was received to stop
+		i := uint64(0)
+		for {
+			select {
+			case <-done:
+				return
+			default:
+			}
+
+			tx := types.NewTransaction(i, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil)
+			backend.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}})
+			i++
+		}
+	}()
+
+	// Create a bunch of filters that will
+	// timeout either in 100ms or 200ms
+	fids := make([]rpc.ID, 20)
+	for i := 0; i < len(fids); i++ {
+		fid := api.NewPendingTransactionFilter()
+		fids[i] = fid
+		// Wait for at least one tx to arrive in filter
+		for {
+			hashes, err := api.GetFilterChanges(fid)
+			if err != nil {
+				t.Fatalf("Filter should exist: %v\n", err)
+			}
+			if len(hashes.([]common.Hash)) > 0 {
+				break
+			}
+			runtime.Gosched()
+		}
+	}
+
+	// Wait until filters have timed out
+	time.Sleep(3 * timeout)
+
+	// If tx loop doesn't consume `done` after a second
+	// it's hanging.
+	select {
+	case done <- struct{}{}:
+		// Check that all filters have been uninstalled
+		for _, fid := range fids {
+			if _, err := api.GetFilterChanges(fid); err == nil {
+				t.Errorf("Filter %s should have been uninstalled\n", fid)
+			}
+		}
+	case <-time.After(1 * time.Second):
+		t.Error("Tx sending loop hangs")
+	}
+}
+
 func flattenLogs(pl [][]*types.Log) []*types.Log {
 	var logs []*types.Log
 	for _, l := range pl {
diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go
index b12efd6e29..d9f59a0999 100644
--- a/eth/filters/filter_test.go
+++ b/eth/filters/filter_test.go
@@ -49,7 +49,7 @@ func BenchmarkFilters(b *testing.B) {
 	defer os.RemoveAll(dir)
 
 	var (
-		db, _   = rawdb.NewLevelDBDatabase(dir, 0, 0, "")
+		db, _   = rawdb.NewLevelDBDatabase(dir, 0, 0, "", false)
 		backend = &testBackend{db: db}
 		key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 		addr1   = crypto.PubkeyToAddress(key1.PublicKey)
@@ -103,7 +103,7 @@ func TestFilters(t *testing.T) {
 	defer os.RemoveAll(dir)
 
 	var (
-		db, _   = rawdb.NewLevelDBDatabase(dir, 0, 0, "")
+		db, _   = rawdb.NewLevelDBDatabase(dir, 0, 0, "", false)
 		backend = &testBackend{db: db}
 		key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 		addr    = crypto.PubkeyToAddress(key1.PublicKey)
diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go
index ecde8ac705..bc8fa81a44 100644
--- a/eth/gasprice/gasprice.go
+++ b/eth/gasprice/gasprice.go
@@ -24,132 +24,156 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/internal/ethapi"
+	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
-var maxPrice = big.NewInt(500 * params.GWei)
+const sampleNumber = 3 // Number of transactions sampled in a block
+
+var DefaultMaxPrice = big.NewInt(500 * params.GWei)
 
 type Config struct {
 	Blocks          int
 	Percentile      int
 	Default         *big.Int `toml:",omitempty"`
+	MaxPrice        *big.Int `toml:",omitempty"`
 	OracleThreshold int      `toml:",omitempty"`
 }
 
+// OracleBackend includes all necessary background APIs for oracle.
+type OracleBackend interface {
+	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
+	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
+	ChainConfig() *params.ChainConfig
+}
+
 // Oracle recommends gas prices based on the content of recent
 // blocks. Suitable for both light and full clients.
 type Oracle struct {
-	backend   ethapi.Backend
+	backend   OracleBackend
 	lastHead  common.Hash
 	lastPrice *big.Int
+	maxPrice  *big.Int
 	cacheLock sync.RWMutex
 	fetchLock sync.Mutex
 
 	defaultPrice      *big.Int
 	sampleTxThreshold int
 
-	checkBlocks, maxEmpty, maxBlocks int
-	percentile                       int
+	checkBlocks int
+	percentile  int
 }
 
-// NewOracle returns a new oracle.
-func NewOracle(backend ethapi.Backend, params Config) *Oracle {
+// NewOracle returns a new gasprice oracle which can recommend suitable
+// gasprice for newly created transaction.
+func NewOracle(backend OracleBackend, params Config) *Oracle {
 	blocks := params.Blocks
 	if blocks < 1 {
 		blocks = 1
+		log.Warn("Sanitizing invalid gasprice oracle sample blocks", "provided", params.Blocks, "updated", blocks)
 	}
 	percent := params.Percentile
 	if percent < 0 {
 		percent = 0
+		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
 	}
 	if percent > 100 {
 		percent = 100
+		log.Warn("Sanitizing invalid gasprice oracle sample percentile", "provided", params.Percentile, "updated", percent)
+	}
+	maxPrice := params.MaxPrice
+	if maxPrice == nil || maxPrice.Int64() <= 0 {
+		maxPrice = DefaultMaxPrice
+		log.Warn("Sanitizing invalid gasprice oracle price cap", "provided", params.MaxPrice, "updated", maxPrice)
 	}
 	return &Oracle{
 		backend:           backend,
 		lastPrice:         params.Default,
-		defaultPrice:      params.Default,
+		maxPrice:          maxPrice,
 		checkBlocks:       blocks,
-		maxEmpty:          blocks / 2,
-		maxBlocks:         blocks * 5,
 		percentile:        percent,
+		defaultPrice:      params.Default,
 		sampleTxThreshold: params.OracleThreshold,
 	}
 }
 
-// SuggestPrice returns the recommended gas price.
+// SuggestPrice returns a gasprice so that newly created transaction can
+// have a very high chance to be included in the following blocks.
 func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
-	gpo.cacheLock.RLock()
-	lastHead := gpo.lastHead
-	lastPrice := gpo.lastPrice
-	gpo.cacheLock.RUnlock()
-
 	head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
 	headHash := head.Hash()
+
+	// If the latest gasprice is still available, return it.
+	gpo.cacheLock.RLock()
+	lastHead, lastPrice := gpo.lastHead, gpo.lastPrice
+	gpo.cacheLock.RUnlock()
 	if headHash == lastHead {
 		return lastPrice, nil
 	}
-
 	gpo.fetchLock.Lock()
 	defer gpo.fetchLock.Unlock()
 
-	// try checking the cache again, maybe the last fetch fetched what we need
+	// Try checking the cache again, maybe the laest fetch fetched what we need
 	gpo.cacheLock.RLock()
-	lastHead = gpo.lastHead
-	lastPrice = gpo.lastPrice
+	lastHead, lastPrice = gpo.lastHead, gpo.lastPrice
 	gpo.cacheLock.RUnlock()
 	if headHash == lastHead {
 		return lastPrice, nil
 	}
 
-	blockNum := head.Number.Uint64()
-	ch := make(chan getBlockPricesResult, gpo.checkBlocks)
-	sent := 0
-	exp := 0
-	var blockPrices []*big.Int
-	var totalTxSamples int
-	for sent < gpo.checkBlocks && blockNum > 0 {
-		go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
+	var (
+		sent, exp      int
+		number         = head.Number.Uint64()
+		result         = make(chan getBlockPricesResult, gpo.checkBlocks)
+		quit           = make(chan struct{})
+		txPrices       []*big.Int
+		totalTxSamples int
+	)
+	for sent < gpo.checkBlocks && number > 0 {
+		go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
 		sent++
 		exp++
-		blockNum--
+		number--
 	}
-	maxEmpty := gpo.maxEmpty
 	for exp > 0 {
-		res := <-ch
+		res := <-result
 		if res.err != nil {
+			close(quit)
 			return lastPrice, res.err
 		}
 		exp--
-		if res.price != nil {
-			blockPrices = append(blockPrices, res.price)
+
+		// Nothing returned. There are two special cases here:
+		// - The block is empty
+		// - All the transactions included are sent by the miner itself.
+		// In these cases, use the latest calculated price for samping.
+		if len(res.prices) == 0 {
+			res.prices = []*big.Int{lastPrice}
+		} else {
 			totalTxSamples = totalTxSamples + res.number
-			continue
 		}
-		if maxEmpty > 0 {
-			maxEmpty--
-			continue
-		}
-		if blockNum > 0 && sent < gpo.maxBlocks {
-			go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
+		// Besides, in order to collect enough data for sampling, if nothing
+		// meaningful returned, try to query more blocks. But the maximum
+		// is 2*checkBlocks.
+		if len(res.prices) == 1 && len(txPrices)+1+exp < gpo.checkBlocks*2 && number > 0 {
+			go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(number))), number, sampleNumber, result, quit)
 			sent++
 			exp++
-			blockNum--
+			number--
 		}
+		txPrices = append(txPrices, res.prices...)
 	}
 	price := lastPrice
-	if len(blockPrices) > 0 && totalTxSamples > gpo.sampleTxThreshold {
-		sort.Sort(bigIntArray(blockPrices))
-		price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100]
+	if len(txPrices) > 0 && totalTxSamples > gpo.sampleTxThreshold {
+		sort.Sort(bigIntArray(txPrices))
+		price = txPrices[(len(txPrices)-1)*gpo.percentile/100]
 	} else {
 		price = gpo.defaultPrice
 	}
-	if price.Cmp(maxPrice) > 0 {
-		price = new(big.Int).Set(maxPrice)
+	if price.Cmp(gpo.maxPrice) > 0 {
+		price = new(big.Int).Set(gpo.maxPrice)
 	}
-
 	gpo.cacheLock.Lock()
 	gpo.lastHead = headHash
 	gpo.lastPrice = price
@@ -159,7 +183,7 @@ func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
 
 type getBlockPricesResult struct {
 	number int
-	price  *big.Int
+	prices []*big.Int
 	err    error
 }
 
@@ -167,30 +191,43 @@ type transactionsByGasPrice []*types.Transaction
 
 func (t transactionsByGasPrice) Len() int           { return len(t) }
 func (t transactionsByGasPrice) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
-func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 }
+func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 }
 
 // getBlockPrices calculates the lowest transaction gas price in a given block
-// and sends it to the result channel. If the block is empty, price is nil.
-func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) {
+// and sends it to the result channel. If the block is empty or all transactions
+// are sent by the miner itself(it doesn't make any sense to include this kind of
+// transaction prices for sampling), nil gasprice is returned.
+func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, limit int, result chan getBlockPricesResult, quit chan struct{}) {
 	block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
 	if block == nil {
-		ch <- getBlockPricesResult{0, nil, err}
+		select {
+		case result <- getBlockPricesResult{0, nil, err}:
+		case <-quit:
+		}
 		return
 	}
-
 	blockTxs := block.Transactions()
 	txs := make([]*types.Transaction, len(blockTxs))
 	copy(txs, blockTxs)
 	sort.Sort(transactionsByGasPrice(txs))
 
+	var prices []*big.Int
 	for _, tx := range txs {
+		if tx.GasPriceIntCmp(common.Big1) <= 0 {
+			continue
+		}
 		sender, err := types.Sender(signer, tx)
 		if err == nil && sender != block.Coinbase() {
-			ch <- getBlockPricesResult{len(txs), tx.GasPrice(), nil}
-			return
+			prices = append(prices, tx.GasPrice())
+			if len(prices) >= limit {
+				break
+			}
 		}
 	}
-	ch <- getBlockPricesResult{0, nil, nil}
+	select {
+	case result <- getBlockPricesResult{len(prices), prices, nil}:
+	case <-quit:
+	}
 }
 
 type bigIntArray []*big.Int
diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go
new file mode 100644
index 0000000000..4fd2df10e2
--- /dev/null
+++ b/eth/gasprice/gasprice_test.go
@@ -0,0 +1,118 @@
+// 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 .
+
+package gasprice
+
+import (
+	"context"
+	"math"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rpc"
+)
+
+type testBackend struct {
+	chain *core.BlockChain
+}
+
+func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
+	if number == rpc.LatestBlockNumber {
+		return b.chain.CurrentBlock().Header(), nil
+	}
+	return b.chain.GetHeaderByNumber(uint64(number)), nil
+}
+
+func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
+	if number == rpc.LatestBlockNumber {
+		return b.chain.CurrentBlock(), nil
+	}
+	return b.chain.GetBlockByNumber(uint64(number)), nil
+}
+
+func (b *testBackend) ChainConfig() *params.ChainConfig {
+	return b.chain.Config()
+}
+
+func newTestBackend(t *testing.T) *testBackend {
+	var (
+		key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+		addr   = crypto.PubkeyToAddress(key.PublicKey)
+		gspec  = &core.Genesis{
+			Config: params.TestChainConfig,
+			Alloc:  core.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
+		}
+		signer = types.LatestSigner(gspec.Config)
+	)
+	engine := ethash.NewFaker()
+	db := rawdb.NewMemoryDatabase()
+	genesis, _ := gspec.Commit(db)
+
+	// Generate testing blocks
+	blocks, _ := core.GenerateChain(params.TestChainConfig, genesis, engine, db, 32, func(i int, b *core.BlockGen) {
+		b.SetCoinbase(common.Address{1})
+		tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr), common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key)
+		if err != nil {
+			t.Fatalf("failed to create tx: %v", err)
+		}
+		b.AddTx(tx)
+	})
+	// Construct testing chain
+	diskdb := rawdb.NewMemoryDatabase()
+	gspec.Commit(diskdb)
+	chain, err := core.NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("Failed to create local chain, %v", err)
+	}
+	chain.InsertChain(blocks)
+	return &testBackend{chain: chain}
+}
+
+func (b *testBackend) CurrentHeader() *types.Header {
+	return b.chain.CurrentHeader()
+}
+
+func (b *testBackend) GetBlockByNumber(number uint64) *types.Block {
+	return b.chain.GetBlockByNumber(number)
+}
+
+func TestSuggestPrice(t *testing.T) {
+	config := Config{
+		Blocks:     3,
+		Percentile: 60,
+		Default:    big.NewInt(params.GWei),
+	}
+	backend := newTestBackend(t)
+	oracle := NewOracle(backend, config)
+
+	// The gas price sampled is: 32G, 31G, 30G, 29G, 28G, 27G
+	got, err := oracle.SuggestPrice(context.Background())
+	if err != nil {
+		t.Fatalf("Failed to retrieve recommended gas price: %v", err)
+	}
+	expect := big.NewInt(params.GWei * int64(30))
+	if got.Cmp(expect) != 0 {
+		t.Fatalf("Gas price mismatch, want %d, got %d", expect, got)
+	}
+}
diff --git a/eth/handler.go b/eth/handler.go
index 6a1bc369a4..f42109753d 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -17,9 +17,7 @@
 package eth
 
 import (
-	"encoding/json"
 	"errors"
-	"fmt"
 	"math"
 	"math/big"
 	"sync"
@@ -27,26 +25,22 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/forkid"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/eth/downloader"
 	"github.com/ethereum/go-ethereum/eth/fetcher"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/eth/protocols/snap"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/params"
-	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/trie"
 )
 
 const (
-	softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
-	estHeaderRlpSize  = 500             // Approximate size of an RLP encoded block header
-
 	// txChanSize is the size of channel listening to NewTxsEvent.
 	// The number is referenced from the size of tx pool.
 	txChanSize = 4096
@@ -56,26 +50,63 @@ var (
 	syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge
 )
 
-func errResp(code errCode, format string, v ...interface{}) error {
-	return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
+// txPool defines the methods needed from a transaction pool implementation to
+// support all the operations needed by the Ethereum chain protocols.
+type txPool interface {
+	// Has returns an indicator whether txpool has a transaction
+	// cached with the given hash.
+	Has(hash common.Hash) bool
+
+	// Get retrieves the transaction from local txpool with given
+	// tx hash.
+	Get(hash common.Hash) *types.Transaction
+
+	// AddRemotes should add the given transactions to the pool.
+	AddRemotes([]*types.Transaction) []error
+
+	// Pending should return pending transactions.
+	// The slice should be modifiable by the caller.
+	Pending() (map[common.Address]types.Transactions, error)
+
+	// SubscribeNewTxsEvent should return an event subscription of
+	// NewTxsEvent and send events to the given channel.
+	SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+}
+
+// handlerConfig is the collection of initialization parameters to create a full
+// node network handler.
+type handlerConfig struct {
+	Database        ethdb.Database            // Database for direct sync insertions
+	Chain           *core.BlockChain          // Blockchain to serve data from
+	TxPool          txPool                    // Transaction pool to propagate from
+	Network         uint64                    // Network identifier to adfvertise
+	Sync            downloader.SyncMode       // Whether to fast or full sync
+	BloomCache      uint64                    // Megabytes to alloc for fast sync bloom
+	EventMux        *event.TypeMux            // Legacy event mux, deprecate for `feed`
+	Checkpoint      *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
+	Whitelist       map[uint64]common.Hash    // Hard coded whitelist for sync challenged
+	DirectBroadcast bool
 }
 
-type ProtocolManager struct {
+type handler struct {
 	networkID  uint64
 	forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node
 
 	fastSync        uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks)
+	snapSync        uint32 // Flag whether fast sync should operate on top of the snap protocol
 	acceptTxs       uint32 // Flag whether we're considered synchronised (enables transaction processing)
 	directBroadcast bool
 
 	checkpointNumber uint64      // Block number for the sync progress validator to cross reference
 	checkpointHash   common.Hash // Block hash for the sync progress validator to cross reference
 
-	txpool     txPool
-	blockchain *core.BlockChain
-	maxPeers   int
+	database ethdb.Database
+	txpool   txPool
+	chain    *core.BlockChain
+	maxPeers int
 
 	downloader   *downloader.Downloader
+	stateBloom   *trie.SyncBloom
 	blockFetcher *fetcher.BlockFetcher
 	txFetcher    *fetcher.TxFetcher
 	peers        *peerSet
@@ -94,29 +125,28 @@ type ProtocolManager struct {
 	chainSync *chainSyncer
 	wg        sync.WaitGroup
 	peerWG    sync.WaitGroup
-
-	// Test fields or hooks
-	broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation
 }
 
-// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
-// with the Ethereum network.
-func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash, directBroadcast bool) (*ProtocolManager, error) {
+// newHandler returns a handler for all Ethereum chain management protocol.
+func newHandler(config *handlerConfig) (*handler, error) {
 	// Create the protocol manager with the base fields
-	manager := &ProtocolManager{
-		directBroadcast: directBroadcast,
-		networkID:       networkID,
-		forkFilter:      forkid.NewFilter(blockchain),
-		eventMux:        mux,
-		txpool:          txpool,
-		blockchain:      blockchain,
+	if config.EventMux == nil {
+		config.EventMux = new(event.TypeMux) // Nicety initialization for tests
+	}
+	h := &handler{
+		networkID:       config.Network,
+		forkFilter:      forkid.NewFilter(config.Chain),
+		eventMux:        config.EventMux,
+		database:        config.Database,
+		txpool:          config.TxPool,
+		chain:           config.Chain,
 		peers:           newPeerSet(),
-		whitelist:       whitelist,
+		whitelist:       config.Whitelist,
+		directBroadcast: config.DirectBroadcast,
 		txsyncCh:        make(chan *txsync),
 		quitSync:        make(chan struct{}),
 	}
-
-	if mode == downloader.FullSync {
+	if config.Sync == downloader.FullSync {
 		// The database seems empty as the current block is the genesis. Yet the fast
 		// block is ahead, so fast sync was enabled for this node at a certain point.
 		// The scenarios where this can happen is
@@ -125,42 +155,46 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh
 		// * the last fast sync is not finished while user specifies a full sync this
 		//   time. But we don't have any recent state for full sync.
 		// In these cases however it's safe to reenable fast sync.
-		fullBlock, fastBlock := blockchain.CurrentBlock(), blockchain.CurrentFastBlock()
+		fullBlock, fastBlock := h.chain.CurrentBlock(), h.chain.CurrentFastBlock()
 		if fullBlock.NumberU64() == 0 && fastBlock.NumberU64() > 0 {
-			manager.fastSync = uint32(1)
+			h.fastSync = uint32(1)
 			log.Warn("Switch sync mode from full sync to fast sync")
 		}
 	} else {
-		if blockchain.CurrentBlock().NumberU64() > 0 {
+		if h.chain.CurrentBlock().NumberU64() > 0 {
 			// Print warning log if database is not empty to run fast sync.
 			log.Warn("Switch sync mode from fast sync to full sync")
 		} else {
 			// If fast sync was requested and our database is empty, grant it
-			manager.fastSync = uint32(1)
+			h.fastSync = uint32(1)
+			if config.Sync == downloader.SnapSync {
+				h.snapSync = uint32(1)
+			}
 		}
 	}
-
 	// If we have trusted checkpoints, enforce them on the chain
-	if checkpoint != nil {
-		manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1
-		manager.checkpointHash = checkpoint.SectionHead
+	if config.Checkpoint != nil {
+		h.checkpointNumber = (config.Checkpoint.SectionIndex+1)*params.CHTFrequency - 1
+		h.checkpointHash = config.Checkpoint.SectionHead
 	}
-
 	// Construct the downloader (long sync) and its backing state bloom if fast
 	// sync is requested. The downloader is responsible for deallocating the state
 	// bloom when it's done.
-	var stateBloom *trie.SyncBloom
-	if atomic.LoadUint32(&manager.fastSync) == 1 {
-		stateBloom = trie.NewSyncBloom(uint64(cacheLimit), chaindb)
+	// Note: we don't enable it if snap-sync is performed, since it's very heavy
+	// and the heal-portion of the snap sync is much lighter than fast. What we particularly
+	// want to avoid, is a 90%-finished (but restarted) snap-sync to begin
+	// indexing the entire trie
+	if atomic.LoadUint32(&h.fastSync) == 1 && atomic.LoadUint32(&h.snapSync) == 0 {
+		h.stateBloom = trie.NewSyncBloom(config.BloomCache, config.Database)
 	}
-	manager.downloader = downloader.New(manager.checkpointNumber, chaindb, stateBloom, manager.eventMux, blockchain, nil, manager.removePeer)
+	h.downloader = downloader.New(h.checkpointNumber, config.Database, h.stateBloom, h.eventMux, h.chain, nil, h.removePeer)
 
 	// Construct the fetcher (short sync)
 	validator := func(header *types.Header) error {
-		return engine.VerifyHeader(blockchain, header, true)
+		return h.chain.Engine().VerifyHeader(h.chain, header, true)
 	}
 	heighter := func() uint64 {
-		return blockchain.CurrentBlock().NumberU64()
+		return h.chain.CurrentBlock().NumberU64()
 	}
 	inserter := func(blocks types.Blocks) (int, error) {
 		// If sync hasn't reached the checkpoint yet, deny importing weird blocks.
@@ -169,7 +203,7 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh
 		// the propagated block if the head is too old. Unfortunately there is a corner
 		// case when starting new networks, where the genesis might be ancient (0 unix)
 		// which would prevent full nodes from accepting it.
-		if manager.blockchain.CurrentBlock().NumberU64() < manager.checkpointNumber {
+		if h.chain.CurrentBlock().NumberU64() < h.checkpointNumber {
 			log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
 			return 0, nil
 		}
@@ -178,179 +212,117 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh
 		// accept each others' blocks until a restart. Unfortunately we haven't figured
 		// out a way yet where nodes can decide unilaterally whether the network is new
 		// or not. This should be fixed if we figure out a solution.
-		if atomic.LoadUint32(&manager.fastSync) == 1 {
+		if atomic.LoadUint32(&h.fastSync) == 1 {
 			log.Warn("Fast syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
 			return 0, nil
 		}
-		n, err := manager.blockchain.InsertChain(blocks)
+		n, err := h.chain.InsertChain(blocks)
 		if err == nil {
-			atomic.StoreUint32(&manager.acceptTxs, 1) // Mark initial sync done on any fetcher import
+			atomic.StoreUint32(&h.acceptTxs, 1) // Mark initial sync done on any fetcher import
 		}
 		return n, err
 	}
-	manager.blockFetcher = fetcher.NewBlockFetcher(blockchain.GetBlockByHash, validator, manager.BroadcastBlock, heighter, inserter, manager.removePeer)
+	h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer)
 
 	fetchTx := func(peer string, hashes []common.Hash) error {
-		p := manager.peers.Peer(peer)
+		p := h.peers.peer(peer)
 		if p == nil {
 			return errors.New("unknown peer")
 		}
 		return p.RequestTxs(hashes)
 	}
-	manager.txFetcher = fetcher.NewTxFetcher(txpool.Has, txpool.AddRemotes, fetchTx)
-
-	manager.chainSync = newChainSyncer(manager)
-
-	return manager, nil
-}
-
-func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol {
-	length, ok := protocolLengths[version]
-	if !ok {
-		panic("makeProtocol for unknown version")
-	}
-
-	return p2p.Protocol{
-		Name:    protocolName,
-		Version: version,
-		Length:  length,
-		Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
-			return pm.runPeer(pm.newPeer(int(version), p, rw, pm.txpool.Get))
-		},
-		NodeInfo: func() interface{} {
-			return pm.NodeInfo()
-		},
-		PeerInfo: func(id enode.ID) interface{} {
-			if p := pm.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
-				return p.Info()
-			}
-			return nil
-		},
-	}
+	h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx)
+	h.chainSync = newChainSyncer(h)
+	return h, nil
 }
 
-func (pm *ProtocolManager) removePeer(id string) {
-	// Short circuit if the peer was already removed
-	peer := pm.peers.Peer(id)
-	if peer == nil {
-		return
-	}
-	log.Debug("Removing Ethereum peer", "peer", id)
-
-	// Unregister the peer from the downloader and Ethereum peer set
-	pm.downloader.UnregisterPeer(id)
-	pm.txFetcher.Drop(id)
-
-	if err := pm.peers.Unregister(id); err != nil {
-		log.Error("Peer removal failed", "peer", id, "err", err)
-	}
-	// Hard disconnect at the networking layer
-	if peer != nil {
-		peer.Peer.Disconnect(p2p.DiscUselessPeer)
+// runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to
+// various subsistems and starts handling messages.
+func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
+	// If the peer has a `snap` extension, wait for it to connect so we can have
+	// a uniform initialization/teardown mechanism
+	snap, err := h.peers.waitSnapExtension(peer)
+	if err != nil {
+		peer.Log().Error("Snapshot extension barrier failed", "err", err)
+		return err
 	}
-}
-
-func (pm *ProtocolManager) Start(maxPeers int) {
-	pm.maxPeers = maxPeers
-
-	// broadcast transactions
-	pm.wg.Add(1)
-	pm.txsCh = make(chan core.NewTxsEvent, txChanSize)
-	pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh)
-	go pm.txBroadcastLoop()
-
-	// broadcast mined blocks
-	pm.wg.Add(1)
-	pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{})
-	go pm.minedBroadcastLoop()
-
-	// start sync handlers
-	pm.wg.Add(2)
-	go pm.chainSync.loop()
-	go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64.
-}
-
-func (pm *ProtocolManager) Stop() {
-	pm.txsSub.Unsubscribe()        // quits txBroadcastLoop
-	pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
-
-	// Quit chainSync and txsync64.
-	// After this is done, no new peers will be accepted.
-	close(pm.quitSync)
-	pm.wg.Wait()
-
-	// 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.
-	pm.peers.Close()
-	pm.peerWG.Wait()
-
-	log.Info("Ethereum protocol stopped")
-}
-
-func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer {
-	return newPeer(pv, p, rw, getPooledTx)
-}
-
-func (pm *ProtocolManager) runPeer(p *peer) error {
-	if !pm.chainSync.handlePeerEvent(p) {
+	// TODO(karalabe): Not sure why this is needed
+	if !h.chainSync.handlePeerEvent(peer) {
 		return p2p.DiscQuitting
 	}
-	pm.peerWG.Add(1)
-	defer pm.peerWG.Done()
-	return pm.handle(p)
-}
-
-// handle is the callback invoked to manage the life cycle of an eth peer. When
-// this function terminates, the peer is disconnected.
-func (pm *ProtocolManager) handle(p *peer) error {
-	// Ignore maxPeers if this is a trusted peer
-	if pm.peers.Len() >= pm.maxPeers && !p.Peer.Info().Network.Trusted {
-		return p2p.DiscTooManyPeers
-	}
-	p.Log().Debug("Ethereum peer connected", "name", p.Name())
+	h.peerWG.Add(1)
+	defer h.peerWG.Done()
 
 	// Execute the Ethereum handshake
 	var (
-		genesis = pm.blockchain.Genesis()
-		head    = pm.blockchain.CurrentHeader()
+		genesis = h.chain.Genesis()
+		head    = h.chain.CurrentHeader()
 		hash    = head.Hash()
 		number  = head.Number.Uint64()
-		td      = pm.blockchain.GetTd(hash, number)
+		td      = h.chain.GetTd(hash, number)
 	)
-	if err := p.Handshake(pm.networkID, td, hash, genesis.Hash(), forkid.NewID(pm.blockchain), pm.forkFilter); err != nil {
-		p.Log().Debug("Ethereum handshake failed", "err", err)
+	forkID := forkid.NewID(h.chain.Config(), h.chain.Genesis().Hash(), h.chain.CurrentHeader().Number.Uint64())
+	if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil {
+		peer.Log().Debug("Ethereum handshake failed", "err", err)
 		return err
 	}
+	reject := false // reserved peer slots
+	if atomic.LoadUint32(&h.snapSync) == 1 {
+		if snap == nil {
+			// If we are running snap-sync, we want to reserve roughly half the peer
+			// slots for peers supporting the snap protocol.
+			// The logic here is; we only allow up to 5 more non-snap peers than snap-peers.
+			if all, snp := h.peers.len(), h.peers.snapLen(); all-snp > snp+5 {
+				reject = true
+			}
+		}
+	}
+	// Ignore maxPeers if this is a trusted peer
+	if !peer.Peer.Info().Network.Trusted {
+		if reject || h.peers.len() >= h.maxPeers {
+			return p2p.DiscTooManyPeers
+		}
+	}
+	peer.Log().Debug("Ethereum peer connected", "name", peer.Name())
 
 	// Register the peer locally
-	if err := pm.peers.Register(p); err != nil {
-		p.Log().Error("Ethereum peer registration failed", "err", err)
+	if err := h.peers.registerPeer(peer, snap); err != nil {
+		peer.Log().Error("Ethereum peer registration failed", "err", err)
 		return err
 	}
-	defer pm.removePeer(p.id)
+	defer h.removePeer(peer.ID())
 
+	p := h.peers.peer(peer.ID())
+	if p == nil {
+		return errors.New("peer dropped during handling")
+	}
 	// Register the peer in the downloader. If the downloader considers it banned, we disconnect
-	if err := pm.downloader.RegisterPeer(p.id, p.version, p); err != nil {
+	if err := h.downloader.RegisterPeer(peer.ID(), peer.Version(), peer); err != nil {
+		peer.Log().Error("Failed to register peer in eth syncer", "err", err)
 		return err
 	}
-	pm.chainSync.handlePeerEvent(p)
+	if snap != nil {
+		if err := h.downloader.SnapSyncer.Register(snap); err != nil {
+			peer.Log().Error("Failed to register peer in snap syncer", "err", err)
+			return err
+		}
+	}
+	h.chainSync.handlePeerEvent(peer)
 
 	// Propagate existing transactions. new transactions appearing
 	// after this will be sent via broadcasts.
-	pm.syncTransactions(p)
+	h.syncTransactions(peer)
 
 	// If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
-	if pm.checkpointHash != (common.Hash{}) {
+	if h.checkpointHash != (common.Hash{}) {
 		// Request the peer's checkpoint header for chain height/weight validation
-		if err := p.RequestHeadersByNumber(pm.checkpointNumber, 1, 0, false); err != nil {
+		if err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false); err != nil {
 			return err
 		}
 		// Start a timer to disconnect if the peer doesn't reply in time
 		p.syncDrop = time.AfterFunc(syncChallengeTimeout, func() {
-			p.Log().Warn("Checkpoint challenge timed out, dropping", "addr", p.RemoteAddr(), "type", p.Name())
-			pm.removePeer(p.id)
+			peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
+			h.removePeer(peer.ID())
 		})
 		// Make sure it's cleaned up if the peer dies off
 		defer func() {
@@ -361,470 +333,122 @@ func (pm *ProtocolManager) handle(p *peer) error {
 		}()
 	}
 	// If we have any explicit whitelist block hashes, request them
-	for number := range pm.whitelist {
-		if err := p.RequestHeadersByNumber(number, 1, 0, false); err != nil {
+	for number := range h.whitelist {
+		if err := peer.RequestHeadersByNumber(number, 1, 0, false); err != nil {
 			return err
 		}
 	}
 	// Handle incoming messages until the connection is torn down
-	for {
-		if err := pm.handleMsg(p); err != nil {
-			p.Log().Debug("Ethereum message handling failed", "err", err)
-			return err
-		}
-	}
+	return handler(peer)
 }
 
-// handleMsg is invoked whenever an inbound message is received from a remote
-// peer. The remote connection is torn down upon returning any error.
-func (pm *ProtocolManager) handleMsg(p *peer) error {
-	// Read the next message from the remote peer, and ensure it's fully consumed
-	msg, err := p.rw.ReadMsg()
-	if err != nil {
+// runSnapExtension registers a `snap` peer into the joint eth/snap peerset and
+// starts handling inbound messages. As `snap` is only a satellite protocol to
+// `eth`, all subsystem registrations and lifecycle management will be done by
+// the main `eth` handler to prevent strange races.
+func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error {
+	h.peerWG.Add(1)
+	defer h.peerWG.Done()
+
+	if err := h.peers.registerSnapExtension(peer); err != nil {
+		peer.Log().Error("Snapshot extension registration failed", "err", err)
 		return err
 	}
-	if msg.Size > protocolMaxMsgSize {
-		return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
-	}
-	defer msg.Discard()
-
-	// Handle the message depending on its contents
-	switch {
-	case msg.Code == StatusMsg:
-		// Status messages should never arrive after the handshake
-		return errResp(ErrExtraStatusMsg, "uncontrolled status message")
-
-	// Block header query, collect the requested headers and reply
-	case msg.Code == GetBlockHeadersMsg:
-		// Decode the complex header query
-		var query getBlockHeadersData
-		if err := msg.Decode(&query); err != nil {
-			return errResp(ErrDecode, "%v: %v", msg, err)
-		}
-		hashMode := query.Origin.Hash != (common.Hash{})
-		first := true
-		maxNonCanonical := uint64(100)
-
-		// Gather headers until the fetch or network limits is reached
-		var (
-			bytes   common.StorageSize
-			headers []*types.Header
-			unknown bool
-		)
-		for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit && len(headers) < downloader.MaxHeaderFetch {
-			// Retrieve the next header satisfying the query
-			var origin *types.Header
-			if hashMode {
-				if first {
-					first = false
-					origin = pm.blockchain.GetHeaderByHash(query.Origin.Hash)
-					if origin != nil {
-						query.Origin.Number = origin.Number.Uint64()
-					}
-				} else {
-					origin = pm.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number)
-				}
-			} else {
-				origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number)
-			}
-			if origin == nil {
-				break
-			}
-			headers = append(headers, origin)
-			bytes += estHeaderRlpSize
-
-			// Advance to the next header of the query
-			switch {
-			case hashMode && query.Reverse:
-				// Hash based traversal towards the genesis block
-				ancestor := query.Skip + 1
-				if ancestor == 0 {
-					unknown = true
-				} else {
-					query.Origin.Hash, query.Origin.Number = pm.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
-					unknown = (query.Origin.Hash == common.Hash{})
-				}
-			case hashMode && !query.Reverse:
-				// Hash based traversal towards the leaf block
-				var (
-					current = origin.Number.Uint64()
-					next    = current + query.Skip + 1
-				)
-				if next <= current {
-					infos, _ := json.MarshalIndent(p.Peer.Info(), "", "  ")
-					p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
-					unknown = true
-				} else {
-					if header := pm.blockchain.GetHeaderByNumber(next); header != nil {
-						nextHash := header.Hash()
-						expOldHash, _ := pm.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
-						if expOldHash == query.Origin.Hash {
-							query.Origin.Hash, query.Origin.Number = nextHash, next
-						} else {
-							unknown = true
-						}
-					} else {
-						unknown = true
-					}
-				}
-			case query.Reverse:
-				// Number based traversal towards the genesis block
-				if query.Origin.Number >= query.Skip+1 {
-					query.Origin.Number -= query.Skip + 1
-				} else {
-					unknown = true
-				}
-
-			case !query.Reverse:
-				// Number based traversal towards the leaf block
-				query.Origin.Number += query.Skip + 1
-			}
-		}
-		return p.SendBlockHeaders(headers)
-
-	case msg.Code == BlockHeadersMsg:
-		// A batch of headers arrived to one of our previous requests
-		var headers []*types.Header
-		if err := msg.Decode(&headers); err != nil {
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// If no headers were received, but we're expencting a checkpoint header, consider it that
-		if len(headers) == 0 && p.syncDrop != nil {
-			// Stop the timer either way, decide later to drop or not
-			p.syncDrop.Stop()
-			p.syncDrop = nil
-
-			// If we're doing a fast sync, we must enforce the checkpoint block to avoid
-			// eclipse attacks. Unsynced nodes are welcome to connect after we're done
-			// joining the network
-			if atomic.LoadUint32(&pm.fastSync) == 1 {
-				p.Log().Warn("Dropping unsynced node during fast sync", "addr", p.RemoteAddr(), "type", p.Name())
-				return errors.New("unsynced node cannot serve fast sync")
-			}
-		}
-		// Filter out any explicitly requested headers, deliver the rest to the downloader
-		filter := len(headers) == 1
-		if filter {
-			// If it's a potential sync progress check, validate the content and advertised chain weight
-			if p.syncDrop != nil && headers[0].Number.Uint64() == pm.checkpointNumber {
-				// Disable the sync drop timer
-				p.syncDrop.Stop()
-				p.syncDrop = nil
-
-				// Validate the header and either drop the peer or continue
-				if headers[0].Hash() != pm.checkpointHash {
-					return errors.New("checkpoint hash mismatch")
-				}
-				return nil
-			}
-			// Otherwise if it's a whitelisted block, validate against the set
-			if want, ok := pm.whitelist[headers[0].Number.Uint64()]; ok {
-				if hash := headers[0].Hash(); want != hash {
-					p.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want)
-					return errors.New("whitelist block mismatch")
-				}
-				p.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want)
-			}
-			// Irrelevant of the fork checks, send the header to the fetcher just in case
-			headers = pm.blockFetcher.FilterHeaders(p.id, headers, time.Now())
-		}
-		if len(headers) > 0 || !filter {
-			err := pm.downloader.DeliverHeaders(p.id, headers)
-			if err != nil {
-				log.Debug("Failed to deliver headers", "err", err)
-			}
-		}
-
-	case msg.Code == GetBlockBodiesMsg:
-		// Decode the retrieval message
-		msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
-		if _, err := msgStream.List(); err != nil {
-			return err
-		}
-		// Gather blocks until the fetch or network limits is reached
-		var (
-			hash   common.Hash
-			bytes  int
-			bodies []rlp.RawValue
-		)
-		for bytes < softResponseLimit && len(bodies) < downloader.MaxBlockFetch {
-			// Retrieve the hash of the next block
-			if err := msgStream.Decode(&hash); err == rlp.EOL {
-				break
-			} else if err != nil {
-				return errResp(ErrDecode, "msg %v: %v", msg, err)
-			}
-			// Retrieve the requested block body, stopping if enough was found
-			if data := pm.blockchain.GetBodyRLP(hash); len(data) != 0 {
-				bodies = append(bodies, data)
-				bytes += len(data)
-			}
-		}
-		return p.SendBlockBodiesRLP(bodies)
-
-	case msg.Code == BlockBodiesMsg:
-		// A batch of block bodies arrived to one of our previous requests
-		var request blockBodiesData
-		if err := msg.Decode(&request); err != nil {
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// Deliver them all to the downloader for queuing
-		transactions := make([][]*types.Transaction, len(request))
-		uncles := make([][]*types.Header, len(request))
+	return handler(peer)
+}
 
-		for i, body := range request {
-			transactions[i] = body.Transactions
-			uncles[i] = body.Uncles
-		}
-		// Filter out any explicitly requested bodies, deliver the rest to the downloader
-		filter := len(transactions) > 0 || len(uncles) > 0
-		if filter {
-			transactions, uncles = pm.blockFetcher.FilterBodies(p.id, transactions, uncles, time.Now())
-		}
-		if len(transactions) > 0 || len(uncles) > 0 || !filter {
-			err := pm.downloader.DeliverBodies(p.id, transactions, uncles)
-			if err != nil {
-				log.Debug("Failed to deliver bodies", "err", err)
-			}
-		}
+// removePeer unregisters a peer from the downloader and fetchers, removes it from
+// the set of tracked peers and closes the network connection to it.
+func (h *handler) removePeer(id string) {
+	// Create a custom logger to avoid printing the entire id
+	var logger log.Logger
+	if len(id) < 16 {
+		// Tests use short IDs, don't choke on them
+		logger = log.New("peer", id)
+	} else {
+		logger = log.New("peer", id[:8])
+	}
+	// Abort if the peer does not exist
+	peer := h.peers.peer(id)
+	if peer == nil {
+		logger.Error("Ethereum peer removal failed", "err", errPeerNotRegistered)
+		return
+	}
+	// Remove the `eth` peer if it exists
+	logger.Debug("Removing Ethereum peer", "snap", peer.snapExt != nil)
 
-	case p.version >= eth63 && msg.Code == GetNodeDataMsg:
-		// Decode the retrieval message
-		msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
-		if _, err := msgStream.List(); err != nil {
-			return err
-		}
-		// Gather state data until the fetch or network limits is reached
-		var (
-			hash  common.Hash
-			bytes int
-			data  [][]byte
-		)
-		for bytes < softResponseLimit && len(data) < downloader.MaxStateFetch {
-			// Retrieve the hash of the next state entry
-			if err := msgStream.Decode(&hash); err == rlp.EOL {
-				break
-			} else if err != nil {
-				return errResp(ErrDecode, "msg %v: %v", msg, err)
-			}
-			// Retrieve the requested state entry, stopping if enough was found
-			if entry, err := pm.blockchain.TrieNode(hash); err == nil {
-				data = append(data, entry)
-				bytes += len(entry)
-			}
-		}
-		return p.SendNodeData(data)
+	// Remove the `snap` extension if it exists
+	if peer.snapExt != nil {
+		h.downloader.SnapSyncer.Unregister(id)
+	}
+	h.downloader.UnregisterPeer(id)
+	h.txFetcher.Drop(id)
 
-	case p.version >= eth63 && msg.Code == NodeDataMsg:
-		// A batch of node state data arrived to one of our previous requests
-		var data [][]byte
-		if err := msg.Decode(&data); err != nil {
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// Deliver all to the downloader
-		if err := pm.downloader.DeliverNodeData(p.id, data); err != nil {
-			log.Debug("Failed to deliver node state data", "err", err)
-		}
+	if err := h.peers.unregisterPeer(id); err != nil {
+		logger.Error("Ethereum peer removal failed", "err", err)
+	}
+	// Hard disconnect at the networking layer
+	peer.Peer.Disconnect(p2p.DiscUselessPeer)
+}
 
-	case p.version >= eth63 && msg.Code == GetReceiptsMsg:
-		// Decode the retrieval message
-		msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
-		if _, err := msgStream.List(); err != nil {
-			return err
-		}
-		// Gather state data until the fetch or network limits is reached
-		var (
-			hash     common.Hash
-			bytes    int
-			receipts []rlp.RawValue
-		)
-		for bytes < softResponseLimit && len(receipts) < downloader.MaxReceiptFetch {
-			// Retrieve the hash of the next block
-			if err := msgStream.Decode(&hash); err == rlp.EOL {
-				break
-			} else if err != nil {
-				return errResp(ErrDecode, "msg %v: %v", msg, err)
-			}
-			// Retrieve the requested block's receipts, skipping if unknown to us
-			results := pm.blockchain.GetReceiptsByHash(hash)
-			if results == nil {
-				if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
-					continue
-				}
-			}
-			// If known, encode and queue for response packet
-			if encoded, err := rlp.EncodeToBytes(results); err != nil {
-				log.Error("Failed to encode receipt", "err", err)
-			} else {
-				receipts = append(receipts, encoded)
-				bytes += len(encoded)
-			}
-		}
-		return p.SendReceiptsRLP(receipts)
+func (h *handler) Start(maxPeers int) {
+	h.maxPeers = maxPeers
 
-	case p.version >= eth63 && msg.Code == ReceiptsMsg:
-		// A batch of receipts arrived to one of our previous requests
-		var receipts [][]*types.Receipt
-		if err := msg.Decode(&receipts); err != nil {
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// Deliver all to the downloader
-		if err := pm.downloader.DeliverReceipts(p.id, receipts); err != nil {
-			log.Debug("Failed to deliver receipts", "err", err)
-		}
+	// broadcast transactions
+	h.wg.Add(1)
+	h.txsCh = make(chan core.NewTxsEvent, txChanSize)
+	h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh)
+	go h.txBroadcastLoop()
 
-	case msg.Code == NewBlockHashesMsg:
-		var announces newBlockHashesData
-		if err := msg.Decode(&announces); err != nil {
-			return errResp(ErrDecode, "%v: %v", msg, err)
-		}
-		// Mark the hashes as present at the remote node
-		for _, block := range announces {
-			p.MarkBlock(block.Hash)
-		}
-		// Schedule all the unknown hashes for retrieval
-		unknown := make(newBlockHashesData, 0, len(announces))
-		for _, block := range announces {
-			if !pm.blockchain.HasBlock(block.Hash, block.Number) {
-				unknown = append(unknown, block)
-			}
-		}
-		for _, block := range unknown {
-			pm.blockFetcher.Notify(p.id, block.Hash, block.Number, time.Now(), p.RequestOneHeader, p.RequestBodies)
-		}
+	// broadcast mined blocks
+	h.wg.Add(1)
+	h.minedBlockSub = h.eventMux.Subscribe(core.NewMinedBlockEvent{})
+	go h.minedBroadcastLoop()
 
-	case msg.Code == NewBlockMsg:
-		// Retrieve and decode the propagated block
-		var request newBlockData
-		if err := msg.Decode(&request); err != nil {
-			return errResp(ErrDecode, "%v: %v", msg, err)
-		}
-		if hash := types.CalcUncleHash(request.Block.Uncles()); hash != request.Block.UncleHash() {
-			log.Warn("Propagated block has invalid uncles", "have", hash, "exp", request.Block.UncleHash())
-			break // TODO(karalabe): return error eventually, but wait a few releases
-		}
-		if hash := types.DeriveSha(request.Block.Transactions()); hash != request.Block.TxHash() {
-			log.Warn("Propagated block has invalid body", "have", hash, "exp", request.Block.TxHash())
-			break // TODO(karalabe): return error eventually, but wait a few releases
-		}
-		if err := request.sanityCheck(); err != nil {
-			return err
-		}
-		request.Block.ReceivedAt = msg.ReceivedAt
-		request.Block.ReceivedFrom = p
-
-		// Mark the peer as owning the block and schedule it for import
-		p.MarkBlock(request.Block.Hash())
-		pm.blockFetcher.Enqueue(p.id, request.Block)
-
-		// Assuming the block is importable by the peer, but possibly not yet done so,
-		// calculate the head hash and TD that the peer truly must have.
-		var (
-			trueHead = request.Block.ParentHash()
-			trueTD   = new(big.Int).Sub(request.TD, request.Block.Difficulty())
-		)
-		// Update the peer's total difficulty if better than the previous
-		if _, td := p.Head(); trueTD.Cmp(td) > 0 {
-			p.SetHead(trueHead, trueTD)
-			pm.chainSync.handlePeerEvent(p)
-		}
+	// start sync handlers
+	h.wg.Add(2)
+	go h.chainSync.loop()
+	go h.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64.
+}
 
-	case msg.Code == NewPooledTransactionHashesMsg && p.version >= eth65:
-		// New transaction announcement arrived, make sure we have
-		// a valid and fresh chain to handle them
-		if atomic.LoadUint32(&pm.acceptTxs) == 0 {
-			break
-		}
-		var hashes []common.Hash
-		if err := msg.Decode(&hashes); err != nil {
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// Schedule all the unknown hashes for retrieval
-		for _, hash := range hashes {
-			p.MarkTransaction(hash)
-		}
-		pm.txFetcher.Notify(p.id, hashes)
+func (h *handler) Stop() {
+	h.txsSub.Unsubscribe()        // quits txBroadcastLoop
+	h.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop
 
-	case msg.Code == GetPooledTransactionsMsg && p.version >= eth65:
-		// Decode the retrieval message
-		msgStream := rlp.NewStream(msg.Payload, uint64(msg.Size))
-		if _, err := msgStream.List(); err != nil {
-			return err
-		}
-		// Gather transactions until the fetch or network limits is reached
-		var (
-			hash   common.Hash
-			bytes  int
-			hashes []common.Hash
-			txs    []rlp.RawValue
-		)
-		for bytes < softResponseLimit {
-			// Retrieve the hash of the next block
-			if err := msgStream.Decode(&hash); err == rlp.EOL {
-				break
-			} else if err != nil {
-				return errResp(ErrDecode, "msg %v: %v", msg, err)
-			}
-			// Retrieve the requested transaction, skipping if unknown to us
-			tx := pm.txpool.Get(hash)
-			if tx == nil {
-				continue
-			}
-			// If known, encode and queue for response packet
-			if encoded, err := rlp.EncodeToBytes(tx); err != nil {
-				log.Error("Failed to encode transaction", "err", err)
-			} else {
-				hashes = append(hashes, hash)
-				txs = append(txs, encoded)
-				bytes += len(encoded)
-			}
-		}
-		return p.SendPooledTransactionsRLP(hashes, txs)
+	// Quit chainSync and txsync64.
+	// After this is done, no new peers will be accepted.
+	close(h.quitSync)
+	h.wg.Wait()
 
-	case msg.Code == TransactionMsg || (msg.Code == PooledTransactionsMsg && p.version >= eth65):
-		// Transactions arrived, make sure we have a valid and fresh chain to handle them
-		if atomic.LoadUint32(&pm.acceptTxs) == 0 {
-			break
-		}
-		// Transactions can be processed, parse all of them and deliver to the pool
-		var txs []*types.Transaction
-		if err := msg.Decode(&txs); err != nil {
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		for i, tx := range txs {
-			// Validate and mark the remote transaction
-			if tx == nil {
-				return errResp(ErrDecode, "transaction %d is nil", i)
-			}
-			p.MarkTransaction(tx.Hash())
-		}
-		pm.txFetcher.Enqueue(p.id, txs, msg.Code == PooledTransactionsMsg)
+	// 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 h.peers yet
+	// will exit when they try to register.
+	h.peers.close()
+	h.peerWG.Wait()
 
-	default:
-		return errResp(ErrInvalidMsgCode, "%v", msg.Code)
-	}
-	return nil
+	log.Info("Ethereum protocol stopped")
 }
 
 // BroadcastBlock will either propagate a block to a subset of its peers, or
 // will only announce its availability (depending what's requested).
-func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
+func (h *handler) BroadcastBlock(block *types.Block, propagate bool) {
 	hash := block.Hash()
-	peers := pm.peers.PeersWithoutBlock(hash)
+	peers := h.peers.peersWithoutBlock(hash)
 
 	// If propagation is requested, send to a subset of the peer
 	if propagate {
 		// Calculate the TD of the block (it's not imported yet, so block.Td is not valid)
 		var td *big.Int
-		if parent := pm.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil {
-			td = new(big.Int).Add(block.Difficulty(), pm.blockchain.GetTd(block.ParentHash(), block.NumberU64()-1))
+		if parent := h.chain.GetBlock(block.ParentHash(), block.NumberU64()-1); parent != nil {
+			td = new(big.Int).Add(block.Difficulty(), h.chain.GetTd(block.ParentHash(), block.NumberU64()-1))
 		} else {
 			log.Error("Propagating dangling block", "number", block.Number(), "hash", hash)
 			return
 		}
 		// Send the block to a subset of our peers
-		var transfer []*peer
-		if pm.directBroadcast {
+		var transfer []*ethPeer
+		if h.directBroadcast {
 			transfer = peers[:int(len(peers))]
 		} else {
 			transfer = peers[:int(math.Sqrt(float64(len(peers))))]
@@ -836,7 +460,7 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
 		return
 	}
 	// Otherwise if the block is indeed in out own chain, announce it
-	if pm.blockchain.HasBlock(hash, block.NumberU64()) {
+	if h.chain.HasBlock(hash, block.NumberU64()) {
 		for _, peer := range peers {
 			peer.AsyncSendNewBlockHash(block)
 		}
@@ -844,97 +468,70 @@ func (pm *ProtocolManager) BroadcastBlock(block *types.Block, propagate bool) {
 	}
 }
 
-// BroadcastTransactions will propagate a batch of transactions to all peers which are not known to
+// BroadcastTransactions will propagate a batch of transactions
+// - To a square root of all peers
+// - And, separately, as announcements to all peers which are not known to
 // already have the given transaction.
-func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propagate bool) {
+func (h *handler) BroadcastTransactions(txs types.Transactions) {
 	var (
-		txset = make(map[*peer][]common.Hash)
-		annos = make(map[*peer][]common.Hash)
+		annoCount   int // Count of announcements made
+		annoPeers   int
+		directCount int // Count of the txs sent directly to peers
+		directPeers int // Count of the peers that were sent transactions directly
+
+		txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly
+		annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce
+
 	)
 	// Broadcast transactions to a batch of peers not knowing about it
-	if propagate {
-		for _, tx := range txs {
-			peers := pm.peers.PeersWithoutTx(tx.Hash())
-
-			// Send the block to a subset of our peers
-			transfer := peers[:int(math.Sqrt(float64(len(peers))))]
-			for _, peer := range transfer {
-				txset[peer] = append(txset[peer], tx.Hash())
-			}
-			log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
-		}
-		for peer, hashes := range txset {
-			peer.AsyncSendTransactions(hashes)
-		}
-		return
-	}
-	// Otherwise only broadcast the announcement to peers
 	for _, tx := range txs {
-		peers := pm.peers.PeersWithoutTx(tx.Hash())
-		for _, peer := range peers {
+		peers := h.peers.peersWithoutTransaction(tx.Hash())
+		// Send the tx unconditionally to a subset of our peers
+		numDirect := int(math.Sqrt(float64(len(peers))))
+		for _, peer := range peers[:numDirect] {
+			txset[peer] = append(txset[peer], tx.Hash())
+		}
+		// For the remaining peers, send announcement only
+		for _, peer := range peers[numDirect:] {
 			annos[peer] = append(annos[peer], tx.Hash())
 		}
 	}
+	for peer, hashes := range txset {
+		directPeers++
+		directCount += len(hashes)
+		peer.AsyncSendTransactions(hashes)
+	}
 	for peer, hashes := range annos {
-		if peer.version >= eth65 {
-			peer.AsyncSendPooledTransactionHashes(hashes)
-		} else {
-			peer.AsyncSendTransactions(hashes)
-		}
+		annoPeers++
+		annoCount += len(hashes)
+		peer.AsyncSendPooledTransactionHashes(hashes)
 	}
+	log.Debug("Transaction broadcast", "txs", len(txs),
+		"announce packs", annoPeers, "announced hashes", annoCount,
+		"tx packs", directPeers, "broadcast txs", directCount)
 }
 
 // minedBroadcastLoop sends mined blocks to connected peers.
-func (pm *ProtocolManager) minedBroadcastLoop() {
-	defer pm.wg.Done()
+func (h *handler) minedBroadcastLoop() {
+	defer h.wg.Done()
 
-	for obj := range pm.minedBlockSub.Chan() {
+	for obj := range h.minedBlockSub.Chan() {
 		if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok {
-			pm.BroadcastBlock(ev.Block, true)  // First propagate block to peers
-			pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest
+			h.BroadcastBlock(ev.Block, true)  // First propagate block to peers
+			h.BroadcastBlock(ev.Block, false) // Only then announce to the rest
 		}
 	}
 }
 
 // txBroadcastLoop announces new transactions to connected peers.
-func (pm *ProtocolManager) txBroadcastLoop() {
-	defer pm.wg.Done()
-
+func (h *handler) txBroadcastLoop() {
+	defer h.wg.Done()
 	for {
 		select {
-		case event := <-pm.txsCh:
-			// For testing purpose only, disable propagation
-			if pm.broadcastTxAnnouncesOnly {
-				pm.BroadcastTransactions(event.Txs, false)
-				continue
-			}
-			pm.BroadcastTransactions(event.Txs, true)  // First propagate transactions to peers
-			pm.BroadcastTransactions(event.Txs, false) // Only then announce to the rest
-
-		case <-pm.txsSub.Err():
+		case event := <-h.txsCh:
+			h.BroadcastTransactions(event.Txs)
+		case <-h.txsSub.Err():
 			return
 		}
 	}
 }
-
-// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
-// known about the host peer.
-type NodeInfo struct {
-	Network    uint64              `json:"network"`    // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
-	Difficulty *big.Int            `json:"difficulty"` // Total difficulty of the host's blockchain
-	Genesis    common.Hash         `json:"genesis"`    // SHA3 hash of the host's genesis block
-	Config     *params.ChainConfig `json:"config"`     // Chain configuration for the fork rules
-	Head       common.Hash         `json:"head"`       // SHA3 hash of the host's best owned block
-}
-
-// NodeInfo retrieves some protocol metadata about the running host node.
-func (pm *ProtocolManager) NodeInfo() *NodeInfo {
-	currentBlock := pm.blockchain.CurrentBlock()
-	return &NodeInfo{
-		Network:    pm.networkID,
-		Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()),
-		Genesis:    pm.blockchain.Genesis().Hash(),
-		Config:     pm.blockchain.Config(),
-		Head:       currentBlock.Hash(),
-	}
-}
diff --git a/eth/handler_eth.go b/eth/handler_eth.go
new file mode 100644
index 0000000000..3ff9f2245b
--- /dev/null
+++ b/eth/handler_eth.go
@@ -0,0 +1,218 @@
+// Copyright 2015 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 .
+
+package eth
+
+import (
+	"errors"
+	"fmt"
+	"math/big"
+	"sync/atomic"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+// ethHandler implements the eth.Backend interface to handle the various network
+// packets that are sent as replies or broadcasts.
+type ethHandler handler
+
+func (h *ethHandler) Chain() *core.BlockChain     { return h.chain }
+func (h *ethHandler) StateBloom() *trie.SyncBloom { return h.stateBloom }
+func (h *ethHandler) TxPool() eth.TxPool          { return h.txpool }
+
+// RunPeer is invoked when a peer joins on the `eth` protocol.
+func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error {
+	return (*handler)(h).runEthPeer(peer, hand)
+}
+
+// PeerInfo retrieves all known `eth` information about a peer.
+func (h *ethHandler) PeerInfo(id enode.ID) interface{} {
+	if p := h.peers.peer(id.String()); p != nil {
+		return p.info()
+	}
+	return nil
+}
+
+// AcceptTxs retrieves whether transaction processing is enabled on the node
+// or if inbound transactions should simply be dropped.
+func (h *ethHandler) AcceptTxs() bool {
+	return atomic.LoadUint32(&h.acceptTxs) == 1
+}
+
+// Handle is invoked from a peer's message handler when it receives a new remote
+// message that the handler couldn't consume and serve itself.
+func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
+	// Consume any broadcasts and announces, forwarding the rest to the downloader
+	switch packet := packet.(type) {
+	case *eth.BlockHeadersPacket:
+		return h.handleHeaders(peer, *packet)
+
+	case *eth.BlockBodiesPacket:
+		txset, uncleset := packet.Unpack()
+		return h.handleBodies(peer, txset, uncleset)
+
+	case *eth.NodeDataPacket:
+		if err := h.downloader.DeliverNodeData(peer.ID(), *packet); err != nil {
+			log.Debug("Failed to deliver node state data", "err", err)
+		}
+		return nil
+
+	case *eth.ReceiptsPacket:
+		if err := h.downloader.DeliverReceipts(peer.ID(), *packet); err != nil {
+			log.Debug("Failed to deliver receipts", "err", err)
+		}
+		return nil
+
+	case *eth.NewBlockHashesPacket:
+		hashes, numbers := packet.Unpack()
+		return h.handleBlockAnnounces(peer, hashes, numbers)
+
+	case *eth.NewBlockPacket:
+		return h.handleBlockBroadcast(peer, packet.Block, packet.TD)
+
+	case *eth.NewPooledTransactionHashesPacket:
+		return h.txFetcher.Notify(peer.ID(), *packet)
+
+	case *eth.TransactionsPacket:
+		return h.txFetcher.Enqueue(peer.ID(), *packet, false)
+
+	case *eth.PooledTransactionsPacket:
+		return h.txFetcher.Enqueue(peer.ID(), *packet, true)
+
+	default:
+		return fmt.Errorf("unexpected eth packet type: %T", packet)
+	}
+}
+
+// handleHeaders is invoked from a peer's message handler when it transmits a batch
+// of headers for the local node to process.
+func (h *ethHandler) handleHeaders(peer *eth.Peer, headers []*types.Header) error {
+	p := h.peers.peer(peer.ID())
+	if p == nil {
+		return errors.New("unregistered during callback")
+	}
+	// If no headers were received, but we're expencting a checkpoint header, consider it that
+	if len(headers) == 0 && p.syncDrop != nil {
+		// Stop the timer either way, decide later to drop or not
+		p.syncDrop.Stop()
+		p.syncDrop = nil
+
+		// If we're doing a fast (or snap) sync, we must enforce the checkpoint block to avoid
+		// eclipse attacks. Unsynced nodes are welcome to connect after we're done
+		// joining the network
+		if atomic.LoadUint32(&h.fastSync) == 1 {
+			peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name())
+			return errors.New("unsynced node cannot serve sync")
+		}
+	}
+	// Filter out any explicitly requested headers, deliver the rest to the downloader
+	filter := len(headers) == 1
+	if filter {
+		// If it's a potential sync progress check, validate the content and advertised chain weight
+		if p.syncDrop != nil && headers[0].Number.Uint64() == h.checkpointNumber {
+			// Disable the sync drop timer
+			p.syncDrop.Stop()
+			p.syncDrop = nil
+
+			// Validate the header and either drop the peer or continue
+			if headers[0].Hash() != h.checkpointHash {
+				return errors.New("checkpoint hash mismatch")
+			}
+			return nil
+		}
+		// Otherwise if it's a whitelisted block, validate against the set
+		if want, ok := h.whitelist[headers[0].Number.Uint64()]; ok {
+			if hash := headers[0].Hash(); want != hash {
+				peer.Log().Info("Whitelist mismatch, dropping peer", "number", headers[0].Number.Uint64(), "hash", hash, "want", want)
+				return errors.New("whitelist block mismatch")
+			}
+			peer.Log().Debug("Whitelist block verified", "number", headers[0].Number.Uint64(), "hash", want)
+		}
+		// Irrelevant of the fork checks, send the header to the fetcher just in case
+		headers = h.blockFetcher.FilterHeaders(peer.ID(), headers, time.Now())
+	}
+	if len(headers) > 0 || !filter {
+		err := h.downloader.DeliverHeaders(peer.ID(), headers)
+		if err != nil {
+			log.Debug("Failed to deliver headers", "err", err)
+		}
+	}
+	return nil
+}
+
+// handleBodies is invoked from a peer's message handler when it transmits a batch
+// of block bodies for the local node to process.
+func (h *ethHandler) handleBodies(peer *eth.Peer, txs [][]*types.Transaction, uncles [][]*types.Header) error {
+	// Filter out any explicitly requested bodies, deliver the rest to the downloader
+	filter := len(txs) > 0 || len(uncles) > 0
+	if filter {
+		txs, uncles = h.blockFetcher.FilterBodies(peer.ID(), txs, uncles, time.Now())
+	}
+	if len(txs) > 0 || len(uncles) > 0 || !filter {
+		err := h.downloader.DeliverBodies(peer.ID(), txs, uncles)
+		if err != nil {
+			log.Debug("Failed to deliver bodies", "err", err)
+		}
+	}
+	return nil
+}
+
+// handleBlockAnnounces is invoked from a peer's message handler when it transmits a
+// batch of block announcements for the local node to process.
+func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, numbers []uint64) error {
+	// Schedule all the unknown hashes for retrieval
+	var (
+		unknownHashes  = make([]common.Hash, 0, len(hashes))
+		unknownNumbers = make([]uint64, 0, len(numbers))
+	)
+	for i := 0; i < len(hashes); i++ {
+		if !h.chain.HasBlock(hashes[i], numbers[i]) {
+			unknownHashes = append(unknownHashes, hashes[i])
+			unknownNumbers = append(unknownNumbers, numbers[i])
+		}
+	}
+	for i := 0; i < len(unknownHashes); i++ {
+		h.blockFetcher.Notify(peer.ID(), unknownHashes[i], unknownNumbers[i], time.Now(), peer.RequestOneHeader, peer.RequestBodies)
+	}
+	return nil
+}
+
+// handleBlockBroadcast is invoked from a peer's message handler when it transmits a
+// block broadcast for the local node to process.
+func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td *big.Int) error {
+	// Schedule the block for import
+	h.blockFetcher.Enqueue(peer.ID(), block)
+
+	// Assuming the block is importable by the peer, but possibly not yet done so,
+	// calculate the head hash and TD that the peer truly must have.
+	var (
+		trueHead = block.ParentHash()
+		trueTD   = new(big.Int).Sub(td, block.Difficulty())
+	)
+	// Update the peer's total difficulty if better than the previous
+	if _, td := peer.Head(); trueTD.Cmp(td) > 0 {
+		peer.SetHead(trueHead, trueTD)
+		h.chainSync.handlePeerEvent(peer)
+	}
+	return nil
+}
diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go
new file mode 100644
index 0000000000..1d38e3b666
--- /dev/null
+++ b/eth/handler_eth_test.go
@@ -0,0 +1,728 @@
+// Copyright 2014 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 .
+
+package eth
+
+import (
+	"fmt"
+	"math/big"
+	"math/rand"
+	"sync/atomic"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+// testEthHandler is a mock event handler to listen for inbound network requests
+// on the `eth` protocol and convert them into a more easily testable form.
+type testEthHandler struct {
+	blockBroadcasts event.Feed
+	txAnnounces     event.Feed
+	txBroadcasts    event.Feed
+}
+
+func (h *testEthHandler) Chain() *core.BlockChain              { panic("no backing chain") }
+func (h *testEthHandler) StateBloom() *trie.SyncBloom          { panic("no backing state bloom") }
+func (h *testEthHandler) TxPool() eth.TxPool                   { panic("no backing tx pool") }
+func (h *testEthHandler) AcceptTxs() bool                      { return true }
+func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") }
+func (h *testEthHandler) PeerInfo(enode.ID) interface{}        { panic("not used in tests") }
+
+func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
+	switch packet := packet.(type) {
+	case *eth.NewBlockPacket:
+		h.blockBroadcasts.Send(packet.Block)
+		return nil
+
+	case *eth.NewPooledTransactionHashesPacket:
+		h.txAnnounces.Send(([]common.Hash)(*packet))
+		return nil
+
+	case *eth.TransactionsPacket:
+		h.txBroadcasts.Send(([]*types.Transaction)(*packet))
+		return nil
+
+	case *eth.PooledTransactionsPacket:
+		h.txBroadcasts.Send(([]*types.Transaction)(*packet))
+		return nil
+
+	default:
+		panic(fmt.Sprintf("unexpected eth packet type in tests: %T", packet))
+	}
+}
+
+// Tests that peers are correctly accepted (or rejected) based on the advertised
+// fork IDs in the protocol handshake.
+func TestForkIDSplit65(t *testing.T) { testForkIDSplit(t, eth.ETH65) }
+func TestForkIDSplit66(t *testing.T) { testForkIDSplit(t, eth.ETH66) }
+
+func testForkIDSplit(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	var (
+		engine = ethash.NewFaker()
+
+		configNoFork  = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}
+		configProFork = ¶ms.ChainConfig{
+			HomesteadBlock: big.NewInt(1),
+			EIP150Block:    big.NewInt(2),
+			EIP155Block:    big.NewInt(2),
+			EIP158Block:    big.NewInt(2),
+			ByzantiumBlock: big.NewInt(3),
+		}
+		dbNoFork  = rawdb.NewMemoryDatabase()
+		dbProFork = rawdb.NewMemoryDatabase()
+
+		gspecNoFork  = &core.Genesis{Config: configNoFork}
+		gspecProFork = &core.Genesis{Config: configProFork}
+
+		genesisNoFork  = gspecNoFork.MustCommit(dbNoFork)
+		genesisProFork = gspecProFork.MustCommit(dbProFork)
+
+		chainNoFork, _  = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil, nil)
+		chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil, nil)
+
+		blocksNoFork, _  = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
+		blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
+
+		ethNoFork, _ = newHandler(&handlerConfig{
+			Database:   dbNoFork,
+			Chain:      chainNoFork,
+			TxPool:     newTestTxPool(),
+			Network:    1,
+			Sync:       downloader.FullSync,
+			BloomCache: 1,
+		})
+		ethProFork, _ = newHandler(&handlerConfig{
+			Database:   dbProFork,
+			Chain:      chainProFork,
+			TxPool:     newTestTxPool(),
+			Network:    1,
+			Sync:       downloader.FullSync,
+			BloomCache: 1,
+		})
+	)
+	ethNoFork.Start(1000)
+	ethProFork.Start(1000)
+
+	// Clean up everything after ourselves
+	defer chainNoFork.Stop()
+	defer chainProFork.Stop()
+
+	defer ethNoFork.Stop()
+	defer ethProFork.Stop()
+
+	// Both nodes should allow the other to connect (same genesis, next fork is the same)
+	p2pNoFork, p2pProFork := p2p.MsgPipe()
+	defer p2pNoFork.Close()
+	defer p2pProFork.Close()
+
+	peerNoFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
+	peerProFork := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
+	defer peerNoFork.Close()
+	defer peerProFork.Close()
+
+	errc := make(chan error, 2)
+	go func(errc chan error) {
+		errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
+	}(errc)
+	go func(errc chan error) {
+		errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
+	}(errc)
+
+	for i := 0; i < 2; i++ {
+		select {
+		case err := <-errc:
+			if err != nil {
+				t.Fatalf("frontier nofork <-> profork failed: %v", err)
+			}
+		case <-time.After(250 * time.Millisecond):
+			t.Fatalf("frontier nofork <-> profork handler timeout")
+		}
+	}
+	// Progress into Homestead. Fork's match, so we don't care what the future holds
+	chainNoFork.InsertChain(blocksNoFork[:1])
+	chainProFork.InsertChain(blocksProFork[:1])
+
+	p2pNoFork, p2pProFork = p2p.MsgPipe()
+	defer p2pNoFork.Close()
+	defer p2pProFork.Close()
+
+	peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
+	peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
+	defer peerNoFork.Close()
+	defer peerProFork.Close()
+
+	errc = make(chan error, 2)
+	go func(errc chan error) {
+		errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
+	}(errc)
+	go func(errc chan error) {
+		errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
+	}(errc)
+
+	for i := 0; i < 2; i++ {
+		select {
+		case err := <-errc:
+			if err != nil {
+				t.Fatalf("homestead nofork <-> profork failed: %v", err)
+			}
+		case <-time.After(250 * time.Millisecond):
+			t.Fatalf("homestead nofork <-> profork handler timeout")
+		}
+	}
+	// Progress into Spurious. Forks mismatch, signalling differing chains, reject
+	chainNoFork.InsertChain(blocksNoFork[1:2])
+	chainProFork.InsertChain(blocksProFork[1:2])
+
+	p2pNoFork, p2pProFork = p2p.MsgPipe()
+	defer p2pNoFork.Close()
+	defer p2pProFork.Close()
+
+	peerNoFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
+	peerProFork = eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
+	defer peerNoFork.Close()
+	defer peerProFork.Close()
+
+	errc = make(chan error, 2)
+	go func(errc chan error) {
+		errc <- ethNoFork.runEthPeer(peerProFork, func(peer *eth.Peer) error { return nil })
+	}(errc)
+	go func(errc chan error) {
+		errc <- ethProFork.runEthPeer(peerNoFork, func(peer *eth.Peer) error { return nil })
+	}(errc)
+
+	var successes int
+	for i := 0; i < 2; i++ {
+		select {
+		case err := <-errc:
+			if err == nil {
+				successes++
+				if successes == 2 { // Only one side disconnects
+					t.Fatalf("fork ID rejection didn't happen")
+				}
+			}
+		case <-time.After(250 * time.Millisecond):
+			t.Fatalf("split peers not rejected")
+		}
+	}
+}
+
+// Tests that received transactions are added to the local pool.
+func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, eth.ETH65) }
+func TestRecvTransactions66(t *testing.T) { testRecvTransactions(t, eth.ETH66) }
+
+func testRecvTransactions(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Create a message handler, configure it to accept transactions and watch them
+	handler := newTestHandler()
+	defer handler.close()
+
+	handler.handler.acceptTxs = 1 // mark synced to accept transactions
+
+	txs := make(chan core.NewTxsEvent)
+	sub := handler.txpool.SubscribeNewTxsEvent(txs)
+	defer sub.Unsubscribe()
+
+	// Create a source peer to send messages through and a sink handler to receive them
+	p2pSrc, p2pSink := p2p.MsgPipe()
+	defer p2pSrc.Close()
+	defer p2pSink.Close()
+
+	src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool)
+	sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool)
+	defer src.Close()
+	defer sink.Close()
+
+	go handler.handler.runEthPeer(sink, func(peer *eth.Peer) error {
+		return eth.Handle((*ethHandler)(handler.handler), peer)
+	})
+	// Run the handshake locally to avoid spinning up a source handler
+	var (
+		genesis = handler.chain.Genesis()
+		head    = handler.chain.CurrentBlock()
+		td      = handler.chain.GetTd(head.Hash(), head.NumberU64())
+	)
+	if err := src.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
+		t.Fatalf("failed to run protocol handshake")
+	}
+	// Send the transaction to the sink and verify that it's added to the tx pool
+	tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil)
+	tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
+
+	if err := src.SendTransactions([]*types.Transaction{tx}); err != nil {
+		t.Fatalf("failed to send transaction: %v", err)
+	}
+	select {
+	case event := <-txs:
+		if len(event.Txs) != 1 {
+			t.Errorf("wrong number of added transactions: got %d, want 1", len(event.Txs))
+		} else if event.Txs[0].Hash() != tx.Hash() {
+			t.Errorf("added wrong tx hash: got %v, want %v", event.Txs[0].Hash(), tx.Hash())
+		}
+	case <-time.After(2 * time.Second):
+		t.Errorf("no NewTxsEvent received within 2 seconds")
+	}
+}
+
+// This test checks that pending transactions are sent.
+func TestSendTransactions65(t *testing.T) { testSendTransactions(t, eth.ETH65) }
+func TestSendTransactions66(t *testing.T) { testSendTransactions(t, eth.ETH66) }
+
+func testSendTransactions(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Create a message handler and fill the pool with big transactions
+	handler := newTestHandler()
+	defer handler.close()
+
+	insert := make([]*types.Transaction, 100)
+	for nonce := range insert {
+		tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, txsyncPackSize/10))
+		tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
+
+		insert[nonce] = tx
+	}
+	go handler.txpool.AddRemotes(insert) // Need goroutine to not block on feed
+	time.Sleep(250 * time.Millisecond)   // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join)
+
+	// Create a source handler to send messages through and a sink peer to receive them
+	p2pSrc, p2pSink := p2p.MsgPipe()
+	defer p2pSrc.Close()
+	defer p2pSink.Close()
+
+	src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, handler.txpool)
+	sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, handler.txpool)
+	defer src.Close()
+	defer sink.Close()
+
+	go handler.handler.runEthPeer(src, func(peer *eth.Peer) error {
+		return eth.Handle((*ethHandler)(handler.handler), peer)
+	})
+	// Run the handshake locally to avoid spinning up a source handler
+	var (
+		genesis = handler.chain.Genesis()
+		head    = handler.chain.CurrentBlock()
+		td      = handler.chain.GetTd(head.Hash(), head.NumberU64())
+	)
+	if err := sink.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
+		t.Fatalf("failed to run protocol handshake")
+	}
+	// After the handshake completes, the source handler should stream the sink
+	// the transactions, subscribe to all inbound network events
+	backend := new(testEthHandler)
+
+	anns := make(chan []common.Hash)
+	annSub := backend.txAnnounces.Subscribe(anns)
+	defer annSub.Unsubscribe()
+
+	bcasts := make(chan []*types.Transaction)
+	bcastSub := backend.txBroadcasts.Subscribe(bcasts)
+	defer bcastSub.Unsubscribe()
+
+	go eth.Handle(backend, sink)
+
+	// Make sure we get all the transactions on the correct channels
+	seen := make(map[common.Hash]struct{})
+	for len(seen) < len(insert) {
+		switch protocol {
+		case 65, 66:
+			select {
+			case hashes := <-anns:
+				for _, hash := range hashes {
+					if _, ok := seen[hash]; ok {
+						t.Errorf("duplicate transaction announced: %x", hash)
+					}
+					seen[hash] = struct{}{}
+				}
+			case <-bcasts:
+				t.Errorf("initial tx broadcast received on post eth/65")
+			}
+
+		default:
+			panic("unsupported protocol, please extend test")
+		}
+	}
+	for _, tx := range insert {
+		if _, ok := seen[tx.Hash()]; !ok {
+			t.Errorf("missing transaction: %x", tx.Hash())
+		}
+	}
+}
+
+// Tests that transactions get propagated to all attached peers, either via direct
+// broadcasts or via announcements/retrievals.
+func TestTransactionPropagation65(t *testing.T) { testTransactionPropagation(t, eth.ETH65) }
+func TestTransactionPropagation66(t *testing.T) { testTransactionPropagation(t, eth.ETH66) }
+
+func testTransactionPropagation(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Create a source handler to send transactions from and a number of sinks
+	// to receive them. We need multiple sinks since a one-to-one peering would
+	// broadcast all transactions without announcement.
+	source := newTestHandler()
+	defer source.close()
+
+	sinks := make([]*testHandler, 10)
+	for i := 0; i < len(sinks); i++ {
+		sinks[i] = newTestHandler()
+		defer sinks[i].close()
+
+		sinks[i].handler.acceptTxs = 1 // mark synced to accept transactions
+	}
+	// Interconnect all the sink handlers with the source handler
+	for i, sink := range sinks {
+		sink := sink // Closure for gorotuine below
+
+		sourcePipe, sinkPipe := p2p.MsgPipe()
+		defer sourcePipe.Close()
+		defer sinkPipe.Close()
+
+		sourcePeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, source.txpool)
+		sinkPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, sink.txpool)
+		defer sourcePeer.Close()
+		defer sinkPeer.Close()
+
+		go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
+			return eth.Handle((*ethHandler)(source.handler), peer)
+		})
+		go sink.handler.runEthPeer(sinkPeer, func(peer *eth.Peer) error {
+			return eth.Handle((*ethHandler)(sink.handler), peer)
+		})
+	}
+	// Subscribe to all the transaction pools
+	txChs := make([]chan core.NewTxsEvent, len(sinks))
+	for i := 0; i < len(sinks); i++ {
+		txChs[i] = make(chan core.NewTxsEvent, 1024)
+
+		sub := sinks[i].txpool.SubscribeNewTxsEvent(txChs[i])
+		defer sub.Unsubscribe()
+	}
+	// Fill the source pool with transactions and wait for them at the sinks
+	txs := make([]*types.Transaction, 1024)
+	for nonce := range txs {
+		tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil)
+		tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
+
+		txs[nonce] = tx
+	}
+	source.txpool.AddRemotes(txs)
+
+	// Iterate through all the sinks and ensure they all got the transactions
+	for i := range sinks {
+		for arrived := 0; arrived < len(txs); {
+			select {
+			case event := <-txChs[i]:
+				arrived += len(event.Txs)
+			case <-time.NewTimer(time.Second).C:
+				t.Errorf("sink %d: transaction propagation timed out: have %d, want %d", i, arrived, len(txs))
+			}
+		}
+	}
+}
+
+// Tests that post eth protocol handshake, clients perform a mutual checkpoint
+// challenge to validate each other's chains. Hash mismatches, or missing ones
+// during a fast sync should lead to the peer getting dropped.
+func TestCheckpointChallenge(t *testing.T) {
+	tests := []struct {
+		syncmode   downloader.SyncMode
+		checkpoint bool
+		timeout    bool
+		empty      bool
+		match      bool
+		drop       bool
+	}{
+		// If checkpointing is not enabled locally, don't challenge and don't drop
+		{downloader.FullSync, false, false, false, false, false},
+		{downloader.FastSync, false, false, false, false, false},
+
+		// If checkpointing is enabled locally and remote response is empty, only drop during fast sync
+		{downloader.FullSync, true, false, true, false, false},
+		{downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer
+
+		// If checkpointing is enabled locally and remote response mismatches, always drop
+		{downloader.FullSync, true, false, false, false, true},
+		{downloader.FastSync, true, false, false, false, true},
+
+		// If checkpointing is enabled locally and remote response matches, never drop
+		{downloader.FullSync, true, false, false, true, false},
+		{downloader.FastSync, true, false, false, true, false},
+
+		// If checkpointing is enabled locally and remote times out, always drop
+		{downloader.FullSync, true, true, false, true, true},
+		{downloader.FastSync, true, true, false, true, true},
+	}
+	for _, tt := range tests {
+		t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) {
+			testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop)
+		})
+	}
+}
+
+func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
+	// Reduce the checkpoint handshake challenge timeout
+	defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
+	syncChallengeTimeout = 250 * time.Millisecond
+
+	// Create a test handler and inject a CHT into it. The injection is a bit
+	// ugly, but it beats creating everything manually just to avoid reaching
+	// into the internals a bit.
+	handler := newTestHandler()
+	defer handler.close()
+
+	if syncmode == downloader.FastSync {
+		atomic.StoreUint32(&handler.handler.fastSync, 1)
+	} else {
+		atomic.StoreUint32(&handler.handler.fastSync, 0)
+	}
+	var response *types.Header
+	if checkpoint {
+		number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1
+		response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
+
+		handler.handler.checkpointNumber = number
+		handler.handler.checkpointHash = response.Hash()
+	}
+	// Create a challenger peer and a challenged one
+	p2pLocal, p2pRemote := p2p.MsgPipe()
+	defer p2pLocal.Close()
+	defer p2pRemote.Close()
+
+	local := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{1}, "", nil), p2pLocal, handler.txpool)
+	remote := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{2}, "", nil), p2pRemote, handler.txpool)
+	defer local.Close()
+	defer remote.Close()
+
+	go handler.handler.runEthPeer(local, func(peer *eth.Peer) error {
+		return eth.Handle((*ethHandler)(handler.handler), peer)
+	})
+	// Run the handshake locally to avoid spinning up a remote handler
+	var (
+		genesis = handler.chain.Genesis()
+		head    = handler.chain.CurrentBlock()
+		td      = handler.chain.GetTd(head.Hash(), head.NumberU64())
+	)
+	if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
+		t.Fatalf("failed to run protocol handshake")
+	}
+	// Connect a new peer and check that we receive the checkpoint challenge
+	if checkpoint {
+		if err := remote.ExpectRequestHeadersByNumber(response.Number.Uint64(), 1, 0, false); err != nil {
+			t.Fatalf("challenge mismatch: %v", err)
+		}
+		// Create a block to reply to the challenge if no timeout is simulated
+		if !timeout {
+			if empty {
+				if err := remote.SendBlockHeaders([]*types.Header{}); err != nil {
+					t.Fatalf("failed to answer challenge: %v", err)
+				}
+			} else if match {
+				if err := remote.SendBlockHeaders([]*types.Header{response}); err != nil {
+					t.Fatalf("failed to answer challenge: %v", err)
+				}
+			} else {
+				if err := remote.SendBlockHeaders([]*types.Header{{Number: response.Number}}); err != nil {
+					t.Fatalf("failed to answer challenge: %v", err)
+				}
+			}
+		}
+	}
+	// Wait until the test timeout passes to ensure proper cleanup
+	time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
+
+	// Verify that the remote peer is maintained or dropped
+	if drop {
+		if peers := handler.handler.peers.len(); peers != 0 {
+			t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
+		}
+	} else {
+		if peers := handler.handler.peers.len(); peers != 1 {
+			t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
+		}
+	}
+}
+
+// Tests that blocks are broadcast to a sqrt number of peers only.
+func TestBroadcastBlock1Peer(t *testing.T)    { testBroadcastBlock(t, 1, 1) }
+func TestBroadcastBlock2Peers(t *testing.T)   { testBroadcastBlock(t, 2, 1) }
+func TestBroadcastBlock3Peers(t *testing.T)   { testBroadcastBlock(t, 3, 1) }
+func TestBroadcastBlock4Peers(t *testing.T)   { testBroadcastBlock(t, 4, 2) }
+func TestBroadcastBlock5Peers(t *testing.T)   { testBroadcastBlock(t, 5, 2) }
+func TestBroadcastBlock8Peers(t *testing.T)   { testBroadcastBlock(t, 9, 3) }
+func TestBroadcastBlock12Peers(t *testing.T)  { testBroadcastBlock(t, 12, 3) }
+func TestBroadcastBlock16Peers(t *testing.T)  { testBroadcastBlock(t, 16, 4) }
+func TestBroadcastBloc26Peers(t *testing.T)   { testBroadcastBlock(t, 26, 5) }
+func TestBroadcastBlock100Peers(t *testing.T) { testBroadcastBlock(t, 100, 10) }
+
+func testBroadcastBlock(t *testing.T, peers, bcasts int) {
+	t.Parallel()
+
+	// Create a source handler to broadcast blocks from and a number of sinks
+	// to receive them.
+	source := newTestHandlerWithBlocks(1)
+	defer source.close()
+
+	sinks := make([]*testEthHandler, peers)
+	for i := 0; i < len(sinks); i++ {
+		sinks[i] = new(testEthHandler)
+	}
+	// Interconnect all the sink handlers with the source handler
+	var (
+		genesis = source.chain.Genesis()
+		td      = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
+	)
+	for i, sink := range sinks {
+		sink := sink // Closure for gorotuine below
+
+		sourcePipe, sinkPipe := p2p.MsgPipe()
+		defer sourcePipe.Close()
+		defer sinkPipe.Close()
+
+		sourcePeer := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{byte(i)}, "", nil), sourcePipe, nil)
+		sinkPeer := eth.NewPeer(eth.ETH65, p2p.NewPeer(enode.ID{0}, "", nil), sinkPipe, nil)
+		defer sourcePeer.Close()
+		defer sinkPeer.Close()
+
+		go source.handler.runEthPeer(sourcePeer, func(peer *eth.Peer) error {
+			return eth.Handle((*ethHandler)(source.handler), peer)
+		})
+		if err := sinkPeer.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
+			t.Fatalf("failed to run protocol handshake")
+		}
+		go eth.Handle(sink, sinkPeer)
+	}
+	// Subscribe to all the transaction pools
+	blockChs := make([]chan *types.Block, len(sinks))
+	for i := 0; i < len(sinks); i++ {
+		blockChs[i] = make(chan *types.Block, 1)
+		defer close(blockChs[i])
+
+		sub := sinks[i].blockBroadcasts.Subscribe(blockChs[i])
+		defer sub.Unsubscribe()
+	}
+	// Initiate a block propagation across the peers
+	time.Sleep(100 * time.Millisecond)
+	source.handler.BroadcastBlock(source.chain.CurrentBlock(), true)
+
+	// Iterate through all the sinks and ensure the correct number got the block
+	done := make(chan struct{}, peers)
+	for _, ch := range blockChs {
+		ch := ch
+		go func() {
+			<-ch
+			done <- struct{}{}
+		}()
+	}
+	var received int
+	for {
+		select {
+		case <-done:
+			received++
+
+		case <-time.After(100 * time.Millisecond):
+			if received != bcasts {
+				t.Errorf("broadcast count mismatch: have %d, want %d", received, bcasts)
+			}
+			return
+		}
+	}
+}
+
+// Tests that a propagated malformed block (uncles or transactions don't match
+// with the hashes in the header) gets discarded and not broadcast forward.
+func TestBroadcastMalformedBlock65(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH65) }
+func TestBroadcastMalformedBlock66(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH66) }
+
+func testBroadcastMalformedBlock(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Create a source handler to broadcast blocks from and a number of sinks
+	// to receive them.
+	source := newTestHandlerWithBlocks(1)
+	defer source.close()
+
+	// Create a source handler to send messages through and a sink peer to receive them
+	p2pSrc, p2pSink := p2p.MsgPipe()
+	defer p2pSrc.Close()
+	defer p2pSink.Close()
+
+	src := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), p2pSrc, source.txpool)
+	sink := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), p2pSink, source.txpool)
+	defer src.Close()
+	defer sink.Close()
+
+	go source.handler.runEthPeer(src, func(peer *eth.Peer) error {
+		return eth.Handle((*ethHandler)(source.handler), peer)
+	})
+	// Run the handshake locally to avoid spinning up a sink handler
+	var (
+		genesis = source.chain.Genesis()
+		td      = source.chain.GetTd(genesis.Hash(), genesis.NumberU64())
+	)
+	if err := sink.Handshake(1, td, genesis.Hash(), genesis.Hash(), forkid.NewIDWithChain(source.chain), forkid.NewFilter(source.chain)); err != nil {
+		t.Fatalf("failed to run protocol handshake")
+	}
+	// After the handshake completes, the source handler should stream the sink
+	// the blocks, subscribe to inbound network events
+	backend := new(testEthHandler)
+
+	blocks := make(chan *types.Block, 1)
+	sub := backend.blockBroadcasts.Subscribe(blocks)
+	defer sub.Unsubscribe()
+
+	go eth.Handle(backend, sink)
+
+	// Create various combinations of malformed blocks
+	head := source.chain.CurrentBlock()
+
+	malformedUncles := head.Header()
+	malformedUncles.UncleHash[0]++
+	malformedTransactions := head.Header()
+	malformedTransactions.TxHash[0]++
+	malformedEverything := head.Header()
+	malformedEverything.UncleHash[0]++
+	malformedEverything.TxHash[0]++
+
+	// Try to broadcast all malformations and ensure they all get discarded
+	for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} {
+		block := types.NewBlockWithHeader(header).WithBody(head.Transactions(), head.Uncles())
+		if err := src.SendNewBlock(block, big.NewInt(131136)); err != nil {
+			t.Fatalf("failed to broadcast block: %v", err)
+		}
+		select {
+		case <-blocks:
+			t.Fatalf("malformed block forwarded")
+		case <-time.After(100 * time.Millisecond):
+		}
+	}
+}
diff --git a/eth/handler_snap.go b/eth/handler_snap.go
new file mode 100644
index 0000000000..767416ffd6
--- /dev/null
+++ b/eth/handler_snap.go
@@ -0,0 +1,50 @@
+// 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 .
+
+package eth
+
+import (
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/eth/protocols/snap"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+// snapHandler implements the snap.Backend interface to handle the various network
+// packets that are sent as replies or broadcasts.
+type snapHandler handler
+
+func (h *snapHandler) Chain() *core.BlockChain { return h.chain }
+
+// RunPeer is invoked when a peer joins on the `snap` protocol.
+func (h *snapHandler) RunPeer(peer *snap.Peer, hand snap.Handler) error {
+	return (*handler)(h).runSnapExtension(peer, hand)
+}
+
+// PeerInfo retrieves all known `snap` information about a peer.
+func (h *snapHandler) PeerInfo(id enode.ID) interface{} {
+	if p := h.peers.peer(id.String()); p != nil {
+		if p.snapExt != nil {
+			return p.snapExt.info()
+		}
+	}
+	return nil
+}
+
+// Handle is invoked from a peer's message handler when it receives a new remote
+// message that the handler couldn't consume and serve itself.
+func (h *snapHandler) Handle(peer *snap.Peer, packet snap.Packet) error {
+	return h.downloader.DeliverSnapPacket(peer, packet)
+}
diff --git a/eth/handler_test.go b/eth/handler_test.go
index fd5898d6e6..a90ef5c348 100644
--- a/eth/handler_test.go
+++ b/eth/handler_test.go
@@ -17,675 +17,154 @@
 package eth
 
 import (
-	"fmt"
-	"math"
 	"math/big"
-	"math/rand"
-	"testing"
-	"time"
+	"sort"
+	"sync"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
-	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/params"
 )
 
-// Tests that block headers can be retrieved from a remote chain based on user queries.
-func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) }
-func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) }
+var (
+	// testKey is a private key to use for funding a tester account.
+	testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 
-func testGetBlockHeaders(t *testing.T, protocol int) {
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxHashFetch+15, nil, nil)
-	peer, _ := newTestPeer("peer", protocol, pm, true)
-	defer peer.close()
+	// testAddr is the Ethereum address of the tester account.
+	testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+)
 
-	// Create a "random" unknown hash for testing
-	var unknown common.Hash
-	for i := range unknown {
-		unknown[i] = byte(i)
-	}
-	// Create a batch of tests for various scenarios
-	limit := uint64(downloader.MaxHeaderFetch)
-	tests := []struct {
-		query  *getBlockHeadersData // The query to execute for header retrieval
-		expect []common.Hash        // The hashes of the block whose headers are expected
-	}{
-		// A single random block should be retrievable by hash and number too
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
-			[]common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1},
-			[]common.Hash{pm.blockchain.GetBlockByNumber(limit / 2).Hash()},
-		},
-		// Multiple headers should be retrievable in both directions
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 + 1).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 + 2).Hash(),
-			},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 - 1).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 - 2).Hash(),
-			},
-		},
-		// Multiple headers with skip lists should be retrievable
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 + 4).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 + 8).Hash(),
-			},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(limit / 2).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 - 4).Hash(),
-				pm.blockchain.GetBlockByNumber(limit/2 - 8).Hash(),
-			},
-		},
-		// The chain endpoints should be retrievable
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1},
-			[]common.Hash{pm.blockchain.GetBlockByNumber(0).Hash()},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64()}, Amount: 1},
-			[]common.Hash{pm.blockchain.CurrentBlock().Hash()},
-		},
-		// Ensure protocol limits are honored
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
-			pm.blockchain.GetBlockHashesFromHash(pm.blockchain.CurrentBlock().Hash(), limit),
-		},
-		// Check that requesting more than available is handled gracefully
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(),
-				pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64()).Hash(),
-			},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(4).Hash(),
-				pm.blockchain.GetBlockByNumber(0).Hash(),
-			},
-		},
-		// Check that requesting more than available is handled gracefully, even if mid skip
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 4).Hash(),
-				pm.blockchain.GetBlockByNumber(pm.blockchain.CurrentBlock().NumberU64() - 1).Hash(),
-			},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(4).Hash(),
-				pm.blockchain.GetBlockByNumber(1).Hash(),
-			},
-		},
-		// Check a corner case where requesting more can iterate past the endpoints
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 2}, Amount: 5, Reverse: true},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(2).Hash(),
-				pm.blockchain.GetBlockByNumber(1).Hash(),
-				pm.blockchain.GetBlockByNumber(0).Hash(),
-			},
-		},
-		// Check a corner case where skipping overflow loops back into the chain start
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(3).Hash(),
-			},
-		},
-		// Check a corner case where skipping overflow loops back to the same header
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Hash: pm.blockchain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64},
-			[]common.Hash{
-				pm.blockchain.GetBlockByNumber(1).Hash(),
-			},
-		},
-		// Check that non existing headers aren't returned
-		{
-			&getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
-			[]common.Hash{},
-		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: pm.blockchain.CurrentBlock().NumberU64() + 1}, Amount: 1},
-			[]common.Hash{},
-		},
-	}
-	// Run each of the tests and verify the results against the chain
-	for i, tt := range tests {
-		// Collect the headers to expect in the response
-		headers := []*types.Header{}
-		for _, hash := range tt.expect {
-			headers = append(headers, pm.blockchain.GetBlockByHash(hash).Header())
-		}
-		// Send the hash request and verify the response
-		p2p.Send(peer.app, 0x03, tt.query)
-		if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
-			t.Errorf("test %d: headers mismatch: %v", i, err)
-		}
-		// If the test used number origins, repeat with hashes as the too
-		if tt.query.Origin.Hash == (common.Hash{}) {
-			if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
-				tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
+// testTxPool is a mock transaction pool that blindly accepts all transactions.
+// Its goal is to get around setting up a valid statedb for the balance and nonce
+// checks.
+type testTxPool struct {
+	pool map[common.Hash]*types.Transaction // Hash map of collected transactions
 
-				p2p.Send(peer.app, 0x03, tt.query)
-				if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
-					t.Errorf("test %d: headers mismatch: %v", i, err)
-				}
-			}
-		}
-	}
+	txFeed event.Feed   // Notification feed to allow waiting for inclusion
+	lock   sync.RWMutex // Protects the transaction pool
 }
 
-// Tests that block contents can be retrieved from a remote chain based on their hashes.
-func TestGetBlockBodies63(t *testing.T) { testGetBlockBodies(t, 63) }
-func TestGetBlockBodies64(t *testing.T) { testGetBlockBodies(t, 64) }
-
-func testGetBlockBodies(t *testing.T, protocol int) {
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, downloader.MaxBlockFetch+15, nil, nil)
-	peer, _ := newTestPeer("peer", protocol, pm, true)
-	defer peer.close()
-
-	// Create a batch of tests for various scenarios
-	limit := downloader.MaxBlockFetch
-	tests := []struct {
-		random    int           // Number of blocks to fetch randomly from the chain
-		explicit  []common.Hash // Explicitly requested blocks
-		available []bool        // Availability of explicitly requested blocks
-		expected  int           // Total number of existing blocks to expect
-	}{
-		{1, nil, nil, 1},             // A single random block should be retrievable
-		{10, nil, nil, 10},           // Multiple random blocks should be retrievable
-		{limit, nil, nil, limit},     // The maximum possible blocks should be retrievable
-		{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
-		{0, []common.Hash{pm.blockchain.Genesis().Hash()}, []bool{true}, 1},      // The genesis block should be retrievable
-		{0, []common.Hash{pm.blockchain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
-		{0, []common.Hash{{}}, []bool{false}, 0},                                 // A non existent block should not be returned
-
-		// Existing and non-existing blocks interleaved should not cause problems
-		{0, []common.Hash{
-			{},
-			pm.blockchain.GetBlockByNumber(1).Hash(),
-			{},
-			pm.blockchain.GetBlockByNumber(10).Hash(),
-			{},
-			pm.blockchain.GetBlockByNumber(100).Hash(),
-			{},
-		}, []bool{false, true, false, true, false, true, false}, 3},
-	}
-	// Run each of the tests and verify the results against the chain
-	for i, tt := range tests {
-		// Collect the hashes to request, and the response to expect
-		hashes, seen := []common.Hash{}, make(map[int64]bool)
-		bodies := []*blockBody{}
-
-		for j := 0; j < tt.random; j++ {
-			for {
-				num := rand.Int63n(int64(pm.blockchain.CurrentBlock().NumberU64()))
-				if !seen[num] {
-					seen[num] = true
-
-					block := pm.blockchain.GetBlockByNumber(uint64(num))
-					hashes = append(hashes, block.Hash())
-					if len(bodies) < tt.expected {
-						bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
-					}
-					break
-				}
-			}
-		}
-		for j, hash := range tt.explicit {
-			hashes = append(hashes, hash)
-			if tt.available[j] && len(bodies) < tt.expected {
-				block := pm.blockchain.GetBlockByHash(hash)
-				bodies = append(bodies, &blockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
-			}
-		}
-		// Send the hash request and verify the response
-		p2p.Send(peer.app, 0x05, hashes)
-		if err := p2p.ExpectMsg(peer.app, 0x06, bodies); err != nil {
-			t.Errorf("test %d: bodies mismatch: %v", i, err)
-		}
+// newTestTxPool creates a mock transaction pool.
+func newTestTxPool() *testTxPool {
+	return &testTxPool{
+		pool: make(map[common.Hash]*types.Transaction),
 	}
 }
 
-// Tests that the node state database can be retrieved based on hashes.
-func TestGetNodeData63(t *testing.T) { testGetNodeData(t, 63) }
-func TestGetNodeData64(t *testing.T) { testGetNodeData(t, 64) }
-
-func testGetNodeData(t *testing.T, protocol int) {
-	// Define three accounts to simulate transactions with
-	acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
-	acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
-	acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
-	acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
-
-	signer := types.HomesteadSigner{}
-	// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
-	generator := func(i int, block *core.BlockGen) {
-		switch i {
-		case 0:
-			// In block 1, the test bank sends account #1 some ether.
-			tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
-			block.AddTx(tx)
-		case 1:
-			// In block 2, the test bank sends some more ether to account #1.
-			// acc1Addr passes it on to account #2.
-			tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey)
-			tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
-			block.AddTx(tx1)
-			block.AddTx(tx2)
-		case 2:
-			// Block 3 is empty but was mined by account #2.
-			block.SetCoinbase(acc2Addr)
-			block.SetExtra([]byte("yeehaw"))
-		case 3:
-			// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
-			b2 := block.PrevBlock(1).Header()
-			b2.Extra = []byte("foo")
-			block.AddUncle(b2)
-			b3 := block.PrevBlock(2).Header()
-			b3.Extra = []byte("foo")
-			block.AddUncle(b3)
-		}
-	}
-	// Assemble the test environment
-	pm, db := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil)
-	peer, _ := newTestPeer("peer", protocol, pm, true)
-	defer peer.close()
-
-	// Fetch for now the entire chain db
-	hashes := []common.Hash{}
-
-	it := db.NewIterator(nil, nil)
-	for it.Next() {
-		if key := it.Key(); len(key) == common.HashLength {
-			hashes = append(hashes, common.BytesToHash(key))
-		}
-	}
-	it.Release()
-
-	p2p.Send(peer.app, 0x0d, hashes)
-	msg, err := peer.app.ReadMsg()
-	if err != nil {
-		t.Fatalf("failed to read node data response: %v", err)
-	}
-	if msg.Code != 0x0e {
-		t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, 0x0c)
-	}
-	var data [][]byte
-	if err := msg.Decode(&data); err != nil {
-		t.Fatalf("failed to decode response node data: %v", err)
-	}
-	// Verify that all hashes correspond to the requested data, and reconstruct a state tree
-	for i, want := range hashes {
-		if hash := crypto.Keccak256Hash(data[i]); hash != want {
-			t.Errorf("data hash mismatch: have %x, want %x", hash, want)
-		}
-	}
-	statedb := rawdb.NewMemoryDatabase()
-	for i := 0; i < len(data); i++ {
-		statedb.Put(hashes[i].Bytes(), data[i])
-	}
-	accounts := []common.Address{testBank, acc1Addr, acc2Addr}
-	for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ {
-		trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil)
-
-		for j, acc := range accounts {
-			state, _ := pm.blockchain.State()
-			bw := state.GetBalance(acc)
-			bh := trie.GetBalance(acc)
+// Has returns an indicator whether txpool has a transaction
+// cached with the given hash.
+func (p *testTxPool) Has(hash common.Hash) bool {
+	p.lock.Lock()
+	defer p.lock.Unlock()
 
-			if (bw != nil && bh == nil) || (bw == nil && bh != nil) {
-				t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
-			}
-			if bw != nil && bh != nil && bw.Cmp(bw) != 0 {
-				t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
-			}
-		}
-	}
+	return p.pool[hash] != nil
 }
 
-// Tests that the transaction receipts can be retrieved based on hashes.
-func TestGetReceipt63(t *testing.T) { testGetReceipt(t, 63) }
-func TestGetReceipt64(t *testing.T) { testGetReceipt(t, 64) }
-
-func testGetReceipt(t *testing.T, protocol int) {
-	// Define three accounts to simulate transactions with
-	acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
-	acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
-	acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
-	acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
-
-	signer := types.HomesteadSigner{}
-	// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
-	generator := func(i int, block *core.BlockGen) {
-		switch i {
-		case 0:
-			// In block 1, the test bank sends account #1 some ether.
-			tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
-			block.AddTx(tx)
-		case 1:
-			// In block 2, the test bank sends some more ether to account #1.
-			// acc1Addr passes it on to account #2.
-			tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBank), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey)
-			tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
-			block.AddTx(tx1)
-			block.AddTx(tx2)
-		case 2:
-			// Block 3 is empty but was mined by account #2.
-			block.SetCoinbase(acc2Addr)
-			block.SetExtra([]byte("yeehaw"))
-		case 3:
-			// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
-			b2 := block.PrevBlock(1).Header()
-			b2.Extra = []byte("foo")
-			block.AddUncle(b2)
-			b3 := block.PrevBlock(2).Header()
-			b3.Extra = []byte("foo")
-			block.AddUncle(b3)
-		}
-	}
-	// Assemble the test environment
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 4, generator, nil)
-	peer, _ := newTestPeer("peer", protocol, pm, true)
-	defer peer.close()
+// Get retrieves the transaction from local txpool with given
+// tx hash.
+func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
+	p.lock.Lock()
+	defer p.lock.Unlock()
 
-	// Collect the hashes to request, and the response to expect
-	hashes, receipts := []common.Hash{}, []types.Receipts{}
-	for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ {
-		block := pm.blockchain.GetBlockByNumber(i)
-
-		hashes = append(hashes, block.Hash())
-		receipts = append(receipts, pm.blockchain.GetReceiptsByHash(block.Hash()))
-	}
-	// Send the hash request and verify the response
-	p2p.Send(peer.app, 0x0f, hashes)
-	if err := p2p.ExpectMsg(peer.app, 0x10, receipts); err != nil {
-		t.Errorf("receipts mismatch: %v", err)
-	}
+	return p.pool[hash]
 }
 
-// Tests that post eth protocol handshake, clients perform a mutual checkpoint
-// challenge to validate each other's chains. Hash mismatches, or missing ones
-// during a fast sync should lead to the peer getting dropped.
-func TestCheckpointChallenge(t *testing.T) {
-	tests := []struct {
-		syncmode   downloader.SyncMode
-		checkpoint bool
-		timeout    bool
-		empty      bool
-		match      bool
-		drop       bool
-	}{
-		// If checkpointing is not enabled locally, don't challenge and don't drop
-		{downloader.FullSync, false, false, false, false, false},
-		{downloader.FastSync, false, false, false, false, false},
+// AddRemotes appends a batch of transactions to the pool, and notifies any
+// listeners if the addition channel is non nil
+func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
+	p.lock.Lock()
+	defer p.lock.Unlock()
 
-		// If checkpointing is enabled locally and remote response is empty, only drop during fast sync
-		{downloader.FullSync, true, false, true, false, false},
-		{downloader.FastSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer
-
-		// If checkpointing is enabled locally and remote response mismatches, always drop
-		{downloader.FullSync, true, false, false, false, true},
-		{downloader.FastSync, true, false, false, false, true},
-
-		// If checkpointing is enabled locally and remote response matches, never drop
-		{downloader.FullSync, true, false, false, true, false},
-		{downloader.FastSync, true, false, false, true, false},
-
-		// If checkpointing is enabled locally and remote times out, always drop
-		{downloader.FullSync, true, true, false, true, true},
-		{downloader.FastSync, true, true, false, true, true},
-	}
-	for _, tt := range tests {
-		t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) {
-			testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop)
-		})
+	for _, tx := range txs {
+		p.pool[tx.Hash()] = tx
 	}
+	p.txFeed.Send(core.NewTxsEvent{Txs: txs})
+	return make([]error, len(txs))
 }
 
-func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
-	// Reduce the checkpoint handshake challenge timeout
-	defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
-	syncChallengeTimeout = 250 * time.Millisecond
+// Pending returns all the transactions known to the pool
+func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
 
-	// Initialize a chain and generate a fake CHT if checkpointing is enabled
-	var (
-		db     = rawdb.NewMemoryDatabase()
-		config = new(params.ChainConfig)
-	)
-	(&core.Genesis{Config: config}).MustCommit(db) // Commit genesis block
-	// If checkpointing is enabled, create and inject a fake CHT and the corresponding
-	// chllenge response.
-	var response *types.Header
-	var cht *params.TrustedCheckpoint
-	if checkpoint {
-		index := uint64(rand.Intn(500))
-		number := (index+1)*params.CHTFrequency - 1
-		response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
-
-		cht = ¶ms.TrustedCheckpoint{
-			SectionIndex: index,
-			SectionHead:  response.Hash(),
-		}
-	}
-	// Create a checkpoint aware protocol manager
-	blockchain, err := core.NewBlockChain(db, nil, config, ethash.NewFaker(), vm.Config{}, nil)
-	if err != nil {
-		t.Fatalf("failed to create new blockchain: %v", err)
+	batches := make(map[common.Address]types.Transactions)
+	for _, tx := range p.pool {
+		from, _ := types.Sender(types.HomesteadSigner{}, tx)
+		batches[from] = append(batches[from], tx)
 	}
-	pm, err := NewProtocolManager(config, cht, syncmode, DefaultConfig.NetworkId, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, ethash.NewFaker(), blockchain, db, 1, nil, false)
-	if err != nil {
-		t.Fatalf("failed to start test protocol manager: %v", err)
-	}
-	pm.Start(1000)
-	defer pm.Stop()
-
-	// Connect a new peer and check that we receive the checkpoint challenge
-	peer, _ := newTestPeer("peer", eth63, pm, true)
-	defer peer.close()
-
-	if checkpoint {
-		challenge := &getBlockHeadersData{
-			Origin:  hashOrNumber{Number: response.Number.Uint64()},
-			Amount:  1,
-			Skip:    0,
-			Reverse: false,
-		}
-		if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil {
-			t.Fatalf("challenge mismatch: %v", err)
-		}
-		// Create a block to reply to the challenge if no timeout is simulated
-		if !timeout {
-			if empty {
-				if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{}); err != nil {
-					t.Fatalf("failed to answer challenge: %v", err)
-				}
-			} else if match {
-				if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{response}); err != nil {
-					t.Fatalf("failed to answer challenge: %v", err)
-				}
-			} else {
-				if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{{Number: response.Number}}); err != nil {
-					t.Fatalf("failed to answer challenge: %v", err)
-				}
-			}
-		}
-	}
-	// Wait until the test timeout passes to ensure proper cleanup
-	time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
-
-	// Verify that the remote peer is maintained or dropped
-	if drop {
-		if peers := pm.peers.Len(); peers != 0 {
-			t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
-		}
-	} else {
-		if peers := pm.peers.Len(); peers != 1 {
-			t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
-		}
+	for _, batch := range batches {
+		sort.Sort(types.TxByNonce(batch))
 	}
+	return batches, nil
 }
 
-func TestBroadcastBlock(t *testing.T) {
-	var tests = []struct {
-		totalPeers        int
-		broadcastExpected int
-	}{
-		{1, 1},
-		{2, 1},
-		{3, 1},
-		{4, 2},
-		{5, 2},
-		{9, 3},
-		{12, 3},
-		{16, 4},
-		{26, 5},
-		{100, 10},
-	}
-	for _, test := range tests {
-		testBroadcastBlock(t, test.totalPeers, test.broadcastExpected)
-	}
+// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and
+// send events to the given channel.
+func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
+	return p.txFeed.Subscribe(ch)
 }
 
-func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) {
-	var (
-		evmux   = new(event.TypeMux)
-		pow     = ethash.NewFaker()
-		db      = rawdb.NewMemoryDatabase()
-		config  = ¶ms.ChainConfig{}
-		gspec   = &core.Genesis{Config: config}
-		genesis = gspec.MustCommit(db)
-	)
-	blockchain, err := core.NewBlockChain(db, nil, config, pow, vm.Config{}, nil)
-	if err != nil {
-		t.Fatalf("failed to create new blockchain: %v", err)
-	}
-	pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, evmux, &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, pow, blockchain, db, 1, nil, false)
-	if err != nil {
-		t.Fatalf("failed to start test protocol manager: %v", err)
-	}
-	pm.Start(1000)
-	defer pm.Stop()
-	var peers []*testPeer
-	for i := 0; i < totalPeers; i++ {
-		peer, _ := newTestPeer(fmt.Sprintf("peer %d", i), eth63, pm, true)
-		defer peer.close()
-
-		peers = append(peers, peer)
-	}
-	chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
-	pm.BroadcastBlock(chain[0], true /*propagate*/)
-
-	errCh := make(chan error, totalPeers)
-	doneCh := make(chan struct{}, totalPeers)
-	for _, peer := range peers {
-		go func(p *testPeer) {
-			if err := p2p.ExpectMsg(p.app, NewBlockMsg, &newBlockData{Block: chain[0], TD: big.NewInt(131136)}); err != nil {
-				errCh <- err
-			} else {
-				doneCh <- struct{}{}
-			}
-		}(peer)
-	}
-	var received int
-	for {
-		select {
-		case <-doneCh:
-			received++
-
-		case <-time.After(time.Second):
-			if received != broadcastExpected {
-				t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected)
-			}
-			return
-
-		case err = <-errCh:
-			t.Fatalf("broadcast failed: %v", err)
-		}
-	}
+// testHandler is a live implementation of the Ethereum protocol handler, just
+// preinitialized with some sane testing defaults and the transaction pool mocked
+// out.
+type testHandler struct {
+	db      ethdb.Database
+	chain   *core.BlockChain
+	txpool  *testTxPool
+	handler *handler
+}
 
+// newTestHandler creates a new handler for testing purposes with no blocks.
+func newTestHandler() *testHandler {
+	return newTestHandlerWithBlocks(0)
 }
 
-// Tests that a propagated malformed block (uncles or transactions don't match
-// with the hashes in the header) gets discarded and not broadcast forward.
-func TestBroadcastMalformedBlock(t *testing.T) {
-	// Create a live node to test propagation with
-	var (
-		engine  = ethash.NewFaker()
-		db      = rawdb.NewMemoryDatabase()
-		config  = ¶ms.ChainConfig{}
-		gspec   = &core.Genesis{Config: config}
-		genesis = gspec.MustCommit(db)
-	)
-	blockchain, err := core.NewBlockChain(db, nil, config, engine, vm.Config{}, nil)
-	if err != nil {
-		t.Fatalf("failed to create new blockchain: %v", err)
+// newTestHandlerWithBlocks creates a new handler for testing purposes, with a
+// given number of initial blocks.
+func newTestHandlerWithBlocks(blocks int) *testHandler {
+	// Create a database pre-initialize with a genesis block
+	db := rawdb.NewMemoryDatabase()
+	(&core.Genesis{
+		Config: params.TestChainConfig,
+		Alloc:  core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
+	}).MustCommit(db)
+
+	chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
+
+	bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, nil)
+	if _, err := chain.InsertChain(bs); err != nil {
+		panic(err)
+	}
+	txpool := newTestTxPool()
+
+	handler, _ := newHandler(&handlerConfig{
+		Database:   db,
+		Chain:      chain,
+		TxPool:     txpool,
+		Network:    1,
+		Sync:       downloader.FastSync,
+		BloomCache: 1,
+	})
+	handler.Start(1000)
+
+	return &testHandler{
+		db:      db,
+		chain:   chain,
+		txpool:  txpool,
+		handler: handler,
 	}
-	pm, err := NewProtocolManager(config, nil, downloader.FullSync, DefaultConfig.NetworkId, new(event.TypeMux), new(testTxPool), engine, blockchain, db, 1, nil, false)
-	if err != nil {
-		t.Fatalf("failed to start test protocol manager: %v", err)
-	}
-	pm.Start(2)
-	defer pm.Stop()
-
-	// Create two peers, one to send the malformed block with and one to check
-	// propagation
-	source, _ := newTestPeer("source", eth63, pm, true)
-	defer source.close()
-
-	sink, _ := newTestPeer("sink", eth63, pm, true)
-	defer sink.close()
-
-	// Create various combinations of malformed blocks
-	chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {})
-
-	malformedUncles := chain[0].Header()
-	malformedUncles.UncleHash[0]++
-	malformedTransactions := chain[0].Header()
-	malformedTransactions.TxHash[0]++
-	malformedEverything := chain[0].Header()
-	malformedEverything.UncleHash[0]++
-	malformedEverything.TxHash[0]++
+}
 
-	// Keep listening to broadcasts and notify if any arrives
-	notify := make(chan struct{}, 1)
-	go func() {
-		if _, err := sink.app.ReadMsg(); err == nil {
-			notify <- struct{}{}
-		}
-	}()
-	// Try to broadcast all malformations and ensure they all get discarded
-	for _, header := range []*types.Header{malformedUncles, malformedTransactions, malformedEverything} {
-		block := types.NewBlockWithHeader(header).WithBody(chain[0].Transactions(), chain[0].Uncles())
-		if err := p2p.Send(source.app, NewBlockMsg, []interface{}{block, big.NewInt(131136)}); err != nil {
-			t.Fatalf("failed to broadcast block: %v", err)
-		}
-		select {
-		case <-notify:
-			t.Fatalf("malformed block forwarded")
-		case <-time.After(100 * time.Millisecond):
-		}
-	}
+// close tears down the handler and all its internal constructs.
+func (b *testHandler) close() {
+	b.handler.Stop()
+	b.chain.Stop()
 }
diff --git a/eth/helper_test.go b/eth/helper_test.go
deleted file mode 100644
index 83ba7245a1..0000000000
--- a/eth/helper_test.go
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright 2015 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 .
-
-// This file contains some shares testing functionality, common to  multiple
-// different files and modules being tested.
-
-package eth
-
-import (
-	"crypto/ecdsa"
-	"crypto/rand"
-	"fmt"
-	"math/big"
-	"sort"
-	"sync"
-	"testing"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/consensus/ethash"
-	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/core/forkid"
-	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/core/vm"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/eth/downloader"
-	"github.com/ethereum/go-ethereum/ethdb"
-	"github.com/ethereum/go-ethereum/event"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/params"
-)
-
-var (
-	testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-	testBank       = crypto.PubkeyToAddress(testBankKey.PublicKey)
-)
-
-// newTestProtocolManager creates a new protocol manager for testing purposes,
-// with the given number of blocks already known, and potential notification
-// channels for different events.
-func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database, error) {
-	var (
-		evmux  = new(event.TypeMux)
-		engine = ethash.NewFaker()
-		db     = rawdb.NewMemoryDatabase()
-		gspec  = &core.Genesis{
-			Config: params.TestChainConfig,
-			Alloc:  core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}},
-		}
-		genesis       = gspec.MustCommit(db)
-		blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil)
-	)
-	chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator)
-	if _, err := blockchain.InsertChain(chain); err != nil {
-		panic(err)
-	}
-	pm, err := NewProtocolManager(gspec.Config, nil, mode, DefaultConfig.NetworkId, evmux, &testTxPool{added: newtx, pool: make(map[common.Hash]*types.Transaction)}, engine, blockchain, db, 1, nil, false)
-	if err != nil {
-		return nil, nil, err
-	}
-	pm.Start(1000)
-	return pm, db, nil
-}
-
-// newTestProtocolManagerMust creates a new protocol manager for testing purposes,
-// with the given number of blocks already known, and potential notification
-// channels for different events. In case of an error, the constructor force-
-// fails the test.
-func newTestProtocolManagerMust(t *testing.T, mode downloader.SyncMode, blocks int, generator func(int, *core.BlockGen), newtx chan<- []*types.Transaction) (*ProtocolManager, ethdb.Database) {
-	pm, db, err := newTestProtocolManager(mode, blocks, generator, newtx)
-	if err != nil {
-		t.Fatalf("Failed to create protocol manager: %v", err)
-	}
-	return pm, db
-}
-
-// testTxPool is a fake, helper transaction pool for testing purposes
-type testTxPool struct {
-	txFeed event.Feed
-	pool   map[common.Hash]*types.Transaction // Hash map of collected transactions
-	added  chan<- []*types.Transaction        // Notification channel for new transactions
-
-	lock sync.RWMutex // Protects the transaction pool
-}
-
-// Has returns an indicator whether txpool has a transaction
-// cached with the given hash.
-func (p *testTxPool) Has(hash common.Hash) bool {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
-	return p.pool[hash] != nil
-}
-
-// Get retrieves the transaction from local txpool with given
-// tx hash.
-func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
-	return p.pool[hash]
-}
-
-// AddRemotes appends a batch of transactions to the pool, and notifies any
-// listeners if the addition channel is non nil
-func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
-	for _, tx := range txs {
-		p.pool[tx.Hash()] = tx
-	}
-	if p.added != nil {
-		p.added <- txs
-	}
-	p.txFeed.Send(core.NewTxsEvent{Txs: txs})
-	return make([]error, len(txs))
-}
-
-// Pending returns all the transactions known to the pool
-func (p *testTxPool) Pending() (map[common.Address]types.Transactions, error) {
-	p.lock.RLock()
-	defer p.lock.RUnlock()
-
-	batches := make(map[common.Address]types.Transactions)
-	for _, tx := range p.pool {
-		from, _ := types.Sender(types.HomesteadSigner{}, tx)
-		batches[from] = append(batches[from], tx)
-	}
-	for _, batch := range batches {
-		sort.Sort(types.TxByNonce(batch))
-	}
-	return batches, nil
-}
-
-func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
-	return p.txFeed.Subscribe(ch)
-}
-
-// newTestTransaction create a new dummy transaction.
-func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction {
-	tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, datasize))
-	tx, _ = types.SignTx(tx, types.HomesteadSigner{}, from)
-	return tx
-}
-
-// testPeer is a simulated peer to allow testing direct network calls.
-type testPeer struct {
-	net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
-	app *p2p.MsgPipeRW    // Application layer reader/writer to simulate the local side
-	*peer
-}
-
-// newTestPeer creates a new peer registered at the given protocol manager.
-func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*testPeer, <-chan error) {
-	// Create a message pipe to communicate through
-	app, net := p2p.MsgPipe()
-
-	// Start the peer on a new thread
-	var id enode.ID
-	rand.Read(id[:])
-	peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get)
-	errc := make(chan error, 1)
-	go func() { errc <- pm.runPeer(peer) }()
-	tp := &testPeer{app: app, net: net, peer: peer}
-
-	// Execute any implicitly requested handshakes and return
-	if shake {
-		var (
-			genesis = pm.blockchain.Genesis()
-			head    = pm.blockchain.CurrentHeader()
-			td      = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
-		)
-		tp.handshake(nil, td, head.Hash(), genesis.Hash(), forkid.NewID(pm.blockchain), forkid.NewFilter(pm.blockchain))
-	}
-	return tp, errc
-}
-
-// handshake simulates a trivial handshake that expects the same state from the
-// remote side as we are simulating locally.
-func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) {
-	var msg interface{}
-	switch {
-	case p.version == eth63:
-		msg = &statusData63{
-			ProtocolVersion: uint32(p.version),
-			NetworkId:       DefaultConfig.NetworkId,
-			TD:              td,
-			CurrentBlock:    head,
-			GenesisBlock:    genesis,
-		}
-	case p.version >= eth64:
-		msg = &statusData{
-			ProtocolVersion: uint32(p.version),
-			NetworkID:       DefaultConfig.NetworkId,
-			TD:              td,
-			Head:            head,
-			Genesis:         genesis,
-			ForkID:          forkID,
-		}
-	default:
-		panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
-	}
-	if err := p2p.ExpectMsg(p.app, StatusMsg, msg); err != nil {
-		t.Fatalf("status recv: %v", err)
-	}
-	if err := p2p.Send(p.app, StatusMsg, msg); err != nil {
-		t.Fatalf("status send: %v", err)
-	}
-}
-
-// close terminates the local side of the peer, notifying the remote protocol
-// manager of termination.
-func (p *testPeer) close() {
-	p.app.Close()
-}
diff --git a/eth/peer.go b/eth/peer.go
index beb9b9f441..1cea9c640e 100644
--- a/eth/peer.go
+++ b/eth/peer.go
@@ -17,801 +17,57 @@
 package eth
 
 import (
-	"errors"
-	"fmt"
 	"math/big"
 	"sync"
 	"time"
 
-	mapset "github.com/deckarep/golang-set"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/core/forkid"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/eth/protocols/snap"
 )
 
-var (
-	errClosed            = errors.New("peer set is closed")
-	errAlreadyRegistered = errors.New("peer is already registered")
-	errNotRegistered     = errors.New("peer is not registered")
-)
-
-const (
-	maxKnownTxs    = 32768 // Maximum transactions hashes to keep in the known list (prevent DOS)
-	maxKnownBlocks = 1024  // Maximum block hashes to keep in the known list (prevent DOS)
-
-	// maxQueuedTxs is the maximum number of transactions to queue up before dropping
-	// older broadcasts.
-	maxQueuedTxs = 4096
-
-	// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
-	// before dropping older announcements.
-	maxQueuedTxAnns = 4096
-
-	// maxQueuedBlocks is the maximum number of block propagations to queue up before
-	// dropping broadcasts. There's not much point in queueing stale blocks, so a few
-	// that might cover uncles should be enough.
-	maxQueuedBlocks = 4
-
-	// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
-	// dropping broadcasts. Similarly to block propagations, there's no point to queue
-	// above some healthy uncle limit, so use that.
-	maxQueuedBlockAnns = 4
-
-	handshakeTimeout = 5 * time.Second
-)
-
-// max is a helper function which returns the larger of the two given integers.
-func max(a, b int) int {
-	if a > b {
-		return a
-	}
-	return b
-}
-
-// PeerInfo represents a short summary of the Ethereum sub-protocol metadata known
+// ethPeerInfo represents a short summary of the `eth` sub-protocol metadata known
 // about a connected peer.
-type PeerInfo struct {
-	Version    int      `json:"version"`    // Ethereum protocol version negotiated
+type ethPeerInfo struct {
+	Version    uint     `json:"version"`    // Ethereum protocol version negotiated
 	Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain
-	Head       string   `json:"head"`       // SHA3 hash of the peer's best owned block
-}
-
-// propEvent is a block propagation, waiting for its turn in the broadcast queue.
-type propEvent struct {
-	block *types.Block
-	td    *big.Int
-}
-
-type peer struct {
-	id string
-
-	*p2p.Peer
-	rw p2p.MsgReadWriter
-
-	version  int         // Protocol version negotiated
-	syncDrop *time.Timer // Timed connection dropper if sync progress isn't validated in time
-
-	head common.Hash
-	td   *big.Int
-	lock sync.RWMutex
-
-	knownBlocks     mapset.Set        // Set of block hashes known to be known by this peer
-	queuedBlocks    chan *propEvent   // Queue of blocks to broadcast to the peer
-	queuedBlockAnns chan *types.Block // Queue of blocks to announce to the peer
-
-	knownTxs    mapset.Set                           // Set of transaction hashes known to be known by this peer
-	txBroadcast chan []common.Hash                   // Channel used to queue transaction propagation requests
-	txAnnounce  chan []common.Hash                   // Channel used to queue transaction announcement requests
-	getPooledTx func(common.Hash) *types.Transaction // Callback used to retrieve transaction from txpool
-
-	term chan struct{} // Termination channel to stop the broadcaster
+	Head       string   `json:"head"`       // Hex hash of the peer's best owned block
 }
 
-func newPeer(version int, p *p2p.Peer, rw p2p.MsgReadWriter, getPooledTx func(hash common.Hash) *types.Transaction) *peer {
-	return &peer{
-		Peer:            p,
-		rw:              rw,
-		version:         version,
-		id:              fmt.Sprintf("%x", p.ID().Bytes()[:8]),
-		knownTxs:        mapset.NewSet(),
-		knownBlocks:     mapset.NewSet(),
-		queuedBlocks:    make(chan *propEvent, maxQueuedBlocks),
-		queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
-		txBroadcast:     make(chan []common.Hash),
-		txAnnounce:      make(chan []common.Hash),
-		getPooledTx:     getPooledTx,
-		term:            make(chan struct{}),
-	}
-}
-
-// broadcastBlocks is a write loop that multiplexes blocks and block accouncements
-// to the remote peer. The goal is to have an async writer that does not lock up
-// node internals and at the same time rate limits queued data.
-func (p *peer) broadcastBlocks() {
-	for {
-		select {
-		case prop := <-p.queuedBlocks:
-			if err := p.SendNewBlock(prop.block, prop.td); err != nil {
-				return
-			}
-			p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
-
-		case block := <-p.queuedBlockAnns:
-			if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
-				return
-			}
-			p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
-
-		case <-p.term:
-			return
-		}
-	}
-}
-
-// broadcastTransactions is a write loop that schedules transaction broadcasts
-// to the remote peer. The goal is to have an async writer that does not lock up
-// node internals and at the same time rate limits queued data.
-func (p *peer) broadcastTransactions() {
-	var (
-		queue []common.Hash         // Queue of hashes to broadcast as full transactions
-		done  chan struct{}         // Non-nil if background broadcaster is running
-		fail  = make(chan error, 1) // Channel used to receive network error
-	)
-	for {
-		// If there's no in-flight broadcast running, check if a new one is needed
-		if done == nil && len(queue) > 0 {
-			// Pile transaction until we reach our allowed network limit
-			var (
-				hashes []common.Hash
-				txs    []*types.Transaction
-				size   common.StorageSize
-			)
-			for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
-				if tx := p.getPooledTx(queue[i]); tx != nil {
-					txs = append(txs, tx)
-					size += tx.Size()
-				}
-				hashes = append(hashes, queue[i])
-			}
-			queue = queue[:copy(queue, queue[len(hashes):])]
-
-			// If there's anything available to transfer, fire up an async writer
-			if len(txs) > 0 {
-				done = make(chan struct{})
-				go func() {
-					if err := p.sendTransactions(txs); err != nil {
-						fail <- err
-						return
-					}
-					close(done)
-					p.Log().Trace("Sent transactions", "count", len(txs))
-				}()
-			}
-		}
-		// Transfer goroutine may or may not have been started, listen for events
-		select {
-		case hashes := <-p.txBroadcast:
-			// New batch of transactions to be broadcast, queue them (with cap)
-			queue = append(queue, hashes...)
-			if len(queue) > maxQueuedTxs {
-				// Fancy copy and resize to ensure buffer doesn't grow indefinitely
-				queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
-			}
-
-		case <-done:
-			done = nil
-
-		case <-fail:
-			p.Log().Trace("BroadcastTransactions failed")
+// ethPeer is a wrapper around eth.Peer to maintain a few extra metadata.
+type ethPeer struct {
+	*eth.Peer
+	snapExt *snapPeer // Satellite `snap` connection
 
-		case <-p.term:
-			return
-		}
-	}
+	syncDrop *time.Timer   // Connection dropper if `eth` sync progress isn't validated in time
+	snapWait chan struct{} // Notification channel for snap connections
+	lock     sync.RWMutex  // Mutex protecting the internal fields
 }
 
-// announceTransactions is a write loop that schedules transaction broadcasts
-// to the remote peer. The goal is to have an async writer that does not lock up
-// node internals and at the same time rate limits queued data.
-func (p *peer) announceTransactions() {
-	var (
-		queue []common.Hash         // Queue of hashes to announce as transaction stubs
-		done  chan struct{}         // Non-nil if background announcer is running
-		fail  = make(chan error, 1) // Channel used to receive network error
-	)
-	for {
-		// If there's no in-flight announce running, check if a new one is needed
-		if done == nil && len(queue) > 0 {
-			// Pile transaction hashes until we reach our allowed network limit
-			var (
-				hashes  []common.Hash
-				pending []common.Hash
-				size    common.StorageSize
-			)
-			for i := 0; i < len(queue) && size < txsyncPackSize; i++ {
-				if p.getPooledTx(queue[i]) != nil {
-					pending = append(pending, queue[i])
-					size += common.HashLength
-				}
-				hashes = append(hashes, queue[i])
-			}
-			queue = queue[:copy(queue, queue[len(hashes):])]
-
-			// If there's anything available to transfer, fire up an async writer
-			if len(pending) > 0 {
-				done = make(chan struct{})
-				go func() {
-					if err := p.sendPooledTransactionHashes(pending); err != nil {
-						fail <- err
-						return
-					}
-					close(done)
-					p.Log().Trace("Sent transaction announcements", "count", len(pending))
-				}()
-			}
-		}
-		// Transfer goroutine may or may not have been started, listen for events
-		select {
-		case hashes := <-p.txAnnounce:
-			// New batch of transactions to be broadcast, queue them (with cap)
-			queue = append(queue, hashes...)
-			if len(queue) > maxQueuedTxAnns {
-				// Fancy copy and resize to ensure buffer doesn't grow indefinitely
-				queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
-			}
-
-		case <-done:
-			done = nil
-
-		case <-fail:
-			return
-
-		case <-p.term:
-			return
-		}
-	}
-}
-
-// close signals the broadcast goroutine to terminate.
-func (p *peer) close() {
-	close(p.term)
-}
-
-// Info gathers and returns a collection of metadata known about a peer.
-func (p *peer) Info() *PeerInfo {
+// info gathers and returns some `eth` protocol metadata known about a peer.
+func (p *ethPeer) info() *ethPeerInfo {
 	hash, td := p.Head()
 
-	return &PeerInfo{
-		Version:    p.version,
+	return ðPeerInfo{
+		Version:    p.Version(),
 		Difficulty: td,
 		Head:       hash.Hex(),
 	}
 }
 
-// Head retrieves a copy of the current head hash and total difficulty of the
-// peer.
-func (p *peer) Head() (hash common.Hash, td *big.Int) {
-	p.lock.RLock()
-	defer p.lock.RUnlock()
-
-	copy(hash[:], p.head[:])
-	return hash, new(big.Int).Set(p.td)
-}
-
-// SetHead updates the head hash and total difficulty of the peer.
-func (p *peer) SetHead(hash common.Hash, td *big.Int) {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-
-	copy(p.head[:], hash[:])
-	p.td.Set(td)
-}
-
-// MarkBlock marks a block as known for the peer, ensuring that the block will
-// never be propagated to this particular peer.
-func (p *peer) MarkBlock(hash common.Hash) {
-	// If we reached the memory allowance, drop a previously known block hash
-	for p.knownBlocks.Cardinality() >= maxKnownBlocks {
-		p.knownBlocks.Pop()
-	}
-	p.knownBlocks.Add(hash)
-}
-
-// MarkTransaction marks a transaction as known for the peer, ensuring that it
-// will never be propagated to this particular peer.
-func (p *peer) MarkTransaction(hash common.Hash) {
-	// If we reached the memory allowance, drop a previously known transaction hash
-	for p.knownTxs.Cardinality() >= maxKnownTxs {
-		p.knownTxs.Pop()
-	}
-	p.knownTxs.Add(hash)
-}
-
-// SendTransactions64 sends transactions to the peer and includes the hashes
-// in its transaction hash set for future reference.
-//
-// This method is legacy support for initial transaction exchange in eth/64 and
-// prior. For eth/65 and higher use SendPooledTransactionHashes.
-func (p *peer) SendTransactions64(txs types.Transactions) error {
-	return p.sendTransactions(txs)
-}
-
-// sendTransactions sends transactions to the peer and includes the hashes
-// in its transaction hash set for future reference.
-//
-// This method is a helper used by the async transaction sender. Don't call it
-// directly as the queueing (memory) and transmission (bandwidth) costs should
-// not be managed directly.
-func (p *peer) sendTransactions(txs types.Transactions) error {
-	// Mark all the transactions as known, but ensure we don't overflow our limits
-	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) {
-		p.knownTxs.Pop()
-	}
-	for _, tx := range txs {
-		p.knownTxs.Add(tx.Hash())
-	}
-	return p2p.Send(p.rw, TransactionMsg, txs)
-}
-
-// AsyncSendTransactions queues a list of transactions (by hash) to eventually
-// propagate to a remote peer. The number of pending sends are capped (new ones
-// will force old sends to be dropped)
-func (p *peer) AsyncSendTransactions(hashes []common.Hash) {
-	select {
-	case p.txBroadcast <- hashes:
-		// Mark all the transactions as known, but ensure we don't overflow our limits
-		for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
-			p.knownTxs.Pop()
-		}
-		for _, hash := range hashes {
-			p.knownTxs.Add(hash)
-		}
-	case <-p.term:
-		p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
-	}
-}
-
-// sendPooledTransactionHashes sends transaction hashes to the peer and includes
-// them in its transaction hash set for future reference.
-//
-// This method is a helper used by the async transaction announcer. Don't call it
-// directly as the queueing (memory) and transmission (bandwidth) costs should
-// not be managed directly.
-func (p *peer) sendPooledTransactionHashes(hashes []common.Hash) error {
-	// Mark all the transactions as known, but ensure we don't overflow our limits
-	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
-		p.knownTxs.Pop()
-	}
-	for _, hash := range hashes {
-		p.knownTxs.Add(hash)
-	}
-	return p2p.Send(p.rw, NewPooledTransactionHashesMsg, hashes)
-}
-
-// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
-// announce to a remote peer.  The number of pending sends are capped (new ones
-// will force old sends to be dropped)
-func (p *peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
-	select {
-	case p.txAnnounce <- hashes:
-		// Mark all the transactions as known, but ensure we don't overflow our limits
-		for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
-			p.knownTxs.Pop()
-		}
-		for _, hash := range hashes {
-			p.knownTxs.Add(hash)
-		}
-	case <-p.term:
-		p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
-	}
-}
-
-// SendPooledTransactionsRLP sends requested transactions to the peer and adds the
-// hashes in its transaction hash set for future reference.
-//
-// Note, the method assumes the hashes are correct and correspond to the list of
-// transactions being sent.
-func (p *peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error {
-	// Mark all the transactions as known, but ensure we don't overflow our limits
-	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
-		p.knownTxs.Pop()
-	}
-	for _, hash := range hashes {
-		p.knownTxs.Add(hash)
-	}
-	return p2p.Send(p.rw, PooledTransactionsMsg, txs)
-}
-
-// SendNewBlockHashes announces the availability of a number of blocks through
-// a hash notification.
-func (p *peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
-	// Mark all the block hashes as known, but ensure we don't overflow our limits
-	for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) {
-		p.knownBlocks.Pop()
-	}
-	for _, hash := range hashes {
-		p.knownBlocks.Add(hash)
-	}
-	request := make(newBlockHashesData, len(hashes))
-	for i := 0; i < len(hashes); i++ {
-		request[i].Hash = hashes[i]
-		request[i].Number = numbers[i]
-	}
-	return p2p.Send(p.rw, NewBlockHashesMsg, request)
-}
-
-// AsyncSendNewBlockHash queues the availability of a block for propagation to a
-// remote peer. If the peer's broadcast queue is full, the event is silently
-// dropped.
-func (p *peer) AsyncSendNewBlockHash(block *types.Block) {
-	select {
-	case p.queuedBlockAnns <- block:
-		// Mark all the block hash as known, but ensure we don't overflow our limits
-		for p.knownBlocks.Cardinality() >= maxKnownBlocks {
-			p.knownBlocks.Pop()
-		}
-		p.knownBlocks.Add(block.Hash())
-	default:
-		p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
-	}
-}
-
-// SendNewBlock propagates an entire block to a remote peer.
-func (p *peer) SendNewBlock(block *types.Block, td *big.Int) error {
-	// Mark all the block hash as known, but ensure we don't overflow our limits
-	for p.knownBlocks.Cardinality() >= maxKnownBlocks {
-		p.knownBlocks.Pop()
-	}
-	p.knownBlocks.Add(block.Hash())
-	return p2p.Send(p.rw, NewBlockMsg, []interface{}{block, td})
-}
-
-// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
-// the peer's broadcast queue is full, the event is silently dropped.
-func (p *peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
-	select {
-	case p.queuedBlocks <- &propEvent{block: block, td: td}:
-		// Mark all the block hash as known, but ensure we don't overflow our limits
-		for p.knownBlocks.Cardinality() >= maxKnownBlocks {
-			p.knownBlocks.Pop()
-		}
-		p.knownBlocks.Add(block.Hash())
-	default:
-		p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
-	}
-}
-
-// SendBlockHeaders sends a batch of block headers to the remote peer.
-func (p *peer) SendBlockHeaders(headers []*types.Header) error {
-	return p2p.Send(p.rw, BlockHeadersMsg, headers)
-}
-
-// SendBlockBodies sends a batch of block contents to the remote peer.
-func (p *peer) SendBlockBodies(bodies []*blockBody) error {
-	return p2p.Send(p.rw, BlockBodiesMsg, blockBodiesData(bodies))
-}
-
-// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
-// an already RLP encoded format.
-func (p *peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error {
-	return p2p.Send(p.rw, BlockBodiesMsg, bodies)
-}
-
-// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the
-// hashes requested.
-func (p *peer) SendNodeData(data [][]byte) error {
-	return p2p.Send(p.rw, NodeDataMsg, data)
-}
-
-// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
-// ones requested from an already RLP encoded format.
-func (p *peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
-	return p2p.Send(p.rw, ReceiptsMsg, receipts)
-}
-
-// RequestOneHeader is a wrapper around the header query functions to fetch a
-// single header. It is used solely by the fetcher.
-func (p *peer) RequestOneHeader(hash common.Hash) error {
-	p.Log().Debug("Fetching single header", "hash", hash)
-	return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), Reverse: false})
-}
-
-// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
-// specified header query, based on the hash of an origin block.
-func (p *peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error {
-	p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
-	return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
-}
-
-// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the
-// specified header query, based on the number of an origin block.
-func (p *peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
-	p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
-	return p2p.Send(p.rw, GetBlockHeadersMsg, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse})
-}
-
-// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
-// specified.
-func (p *peer) RequestBodies(hashes []common.Hash) error {
-	p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
-	return p2p.Send(p.rw, GetBlockBodiesMsg, hashes)
-}
-
-// RequestNodeData fetches a batch of arbitrary data from a node's known state
-// data, corresponding to the specified hashes.
-func (p *peer) RequestNodeData(hashes []common.Hash) error {
-	p.Log().Debug("Fetching batch of state data", "count", len(hashes))
-	return p2p.Send(p.rw, GetNodeDataMsg, hashes)
-}
-
-// RequestReceipts fetches a batch of transaction receipts from a remote node.
-func (p *peer) RequestReceipts(hashes []common.Hash) error {
-	p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
-	return p2p.Send(p.rw, GetReceiptsMsg, hashes)
-}
-
-// RequestTxs fetches a batch of transactions from a remote node.
-func (p *peer) RequestTxs(hashes []common.Hash) error {
-	p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
-	return p2p.Send(p.rw, GetPooledTransactionsMsg, hashes)
-}
-
-// Handshake executes the eth protocol handshake, negotiating version number,
-// network IDs, difficulties, head and genesis blocks.
-func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
-	// Send out own handshake in a new thread
-	errc := make(chan error, 2)
-
-	var (
-		status63 statusData63 // safe to read after two values have been received from errc
-		status   statusData   // safe to read after two values have been received from errc
-	)
-	go func() {
-		switch {
-		case p.version == eth63:
-			errc <- p2p.Send(p.rw, StatusMsg, &statusData63{
-				ProtocolVersion: uint32(p.version),
-				NetworkId:       network,
-				TD:              td,
-				CurrentBlock:    head,
-				GenesisBlock:    genesis,
-			})
-		case p.version >= eth64:
-			errc <- p2p.Send(p.rw, StatusMsg, &statusData{
-				ProtocolVersion: uint32(p.version),
-				NetworkID:       network,
-				TD:              td,
-				Head:            head,
-				Genesis:         genesis,
-				ForkID:          forkID,
-			})
-		default:
-			panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
-		}
-	}()
-	go func() {
-		switch {
-		case p.version == eth63:
-			errc <- p.readStatusLegacy(network, &status63, genesis)
-		case p.version >= eth64:
-			errc <- p.readStatus(network, &status, genesis, forkFilter)
-		default:
-			panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
-		}
-	}()
-	timeout := time.NewTimer(handshakeTimeout)
-	defer timeout.Stop()
-	for i := 0; i < 2; i++ {
-		select {
-		case err := <-errc:
-			if err != nil {
-				return err
-			}
-		case <-timeout.C:
-			return p2p.DiscReadTimeout
-		}
-	}
-	switch {
-	case p.version == eth63:
-		p.td, p.head = status63.TD, status63.CurrentBlock
-	case p.version >= eth64:
-		p.td, p.head = status.TD, status.Head
-	default:
-		panic(fmt.Sprintf("unsupported eth protocol version: %d", p.version))
-	}
-	return nil
-}
-
-func (p *peer) readStatusLegacy(network uint64, status *statusData63, genesis common.Hash) error {
-	msg, err := p.rw.ReadMsg()
-	if err != nil {
-		return err
-	}
-	if msg.Code != StatusMsg {
-		return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
-	}
-	if msg.Size > protocolMaxMsgSize {
-		return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
-	}
-	// Decode the handshake and make sure everything matches
-	if err := msg.Decode(&status); err != nil {
-		return errResp(ErrDecode, "msg %v: %v", msg, err)
-	}
-	if status.GenesisBlock != genesis {
-		return errResp(ErrGenesisMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8])
-	}
-	if status.NetworkId != network {
-		return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkId, network)
-	}
-	if int(status.ProtocolVersion) != p.version {
-		return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
-	}
-	return nil
-}
-
-func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash, forkFilter forkid.Filter) error {
-	msg, err := p.rw.ReadMsg()
-	if err != nil {
-		return err
-	}
-	if msg.Code != StatusMsg {
-		return errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg)
-	}
-	if msg.Size > protocolMaxMsgSize {
-		return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, protocolMaxMsgSize)
-	}
-	// Decode the handshake and make sure everything matches
-	if err := msg.Decode(&status); err != nil {
-		return errResp(ErrDecode, "msg %v: %v", msg, err)
-	}
-	if status.NetworkID != network {
-		return errResp(ErrNetworkIDMismatch, "%d (!= %d)", status.NetworkID, network)
-	}
-	if int(status.ProtocolVersion) != p.version {
-		return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", status.ProtocolVersion, p.version)
-	}
-	if status.Genesis != genesis {
-		return errResp(ErrGenesisMismatch, "%x (!= %x)", status.Genesis, genesis)
-	}
-	if err := forkFilter(status.ForkID); err != nil {
-		return errResp(ErrForkIDRejected, "%v", err)
-	}
-	return nil
-}
-
-// String implements fmt.Stringer.
-func (p *peer) String() string {
-	return fmt.Sprintf("Peer %s [%s]", p.id,
-		fmt.Sprintf("eth/%2d", p.version),
-	)
-}
-
-// peerSet represents the collection of active peers currently participating in
-// the Ethereum sub-protocol.
-type peerSet struct {
-	peers  map[string]*peer
-	lock   sync.RWMutex
-	closed bool
-}
-
-// newPeerSet creates a new peer set to track the active participants.
-func newPeerSet() *peerSet {
-	return &peerSet{
-		peers: make(map[string]*peer),
-	}
-}
-
-// Register injects a new peer into the working set, or returns an error if the
-// peer is already known. If a new peer it registered, its broadcast loop is also
-// started.
-func (ps *peerSet) Register(p *peer) error {
-	ps.lock.Lock()
-	defer ps.lock.Unlock()
-
-	if ps.closed {
-		return errClosed
-	}
-	if _, ok := ps.peers[p.id]; ok {
-		return errAlreadyRegistered
-	}
-	ps.peers[p.id] = p
-
-	go p.broadcastBlocks()
-	go p.broadcastTransactions()
-	go p.announceTransactions()
-
-	return nil
-}
-
-// Unregister removes a remote peer from the active set, disabling any further
-// actions to/from that particular entity.
-func (ps *peerSet) Unregister(id string) error {
-	ps.lock.Lock()
-	defer ps.lock.Unlock()
-
-	p, ok := ps.peers[id]
-	if !ok {
-		return errNotRegistered
-	}
-	delete(ps.peers, id)
-	p.close()
-
-	return nil
-}
-
-// Peer retrieves the registered peer with the given id.
-func (ps *peerSet) Peer(id string) *peer {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
-
-	return ps.peers[id]
-}
-
-// Len returns if the current number of peers in the set.
-func (ps *peerSet) Len() int {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
-
-	return len(ps.peers)
-}
-
-// PeersWithoutBlock retrieves a list of peers that do not have a given block in
-// their set of known hashes.
-func (ps *peerSet) PeersWithoutBlock(hash common.Hash) []*peer {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
-
-	list := make([]*peer, 0, len(ps.peers))
-	for _, p := range ps.peers {
-		if !p.knownBlocks.Contains(hash) {
-			list = append(list, p)
-		}
-	}
-	return list
-}
-
-// PeersWithoutTx retrieves a list of peers that do not have a given transaction
-// in their set of known hashes.
-func (ps *peerSet) PeersWithoutTx(hash common.Hash) []*peer {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
-
-	list := make([]*peer, 0, len(ps.peers))
-	for _, p := range ps.peers {
-		if !p.knownTxs.Contains(hash) {
-			list = append(list, p)
-		}
-	}
-	return list
+// snapPeerInfo represents a short summary of the `snap` sub-protocol metadata known
+// about a connected peer.
+type snapPeerInfo struct {
+	Version uint `json:"version"` // Snapshot protocol version negotiated
 }
 
-// BestPeer retrieves the known peer with the currently highest total difficulty.
-func (ps *peerSet) BestPeer() *peer {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
-
-	var (
-		bestPeer *peer
-		bestTd   *big.Int
-	)
-	for _, p := range ps.peers {
-		if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
-			bestPeer, bestTd = p, td
-		}
-	}
-	return bestPeer
+// snapPeer is a wrapper around snap.Peer to maintain a few extra metadata.
+type snapPeer struct {
+	*snap.Peer
 }
 
-// Close disconnects all peers.
-// No new peers can be registered after Close has returned.
-func (ps *peerSet) Close() {
-	ps.lock.Lock()
-	defer ps.lock.Unlock()
-
-	for _, p := range ps.peers {
-		p.Disconnect(p2p.DiscQuitting)
+// info gathers and returns some `snap` protocol metadata known about a peer.
+func (p *snapPeer) info() *snapPeerInfo {
+	return &snapPeerInfo{
+		Version: p.Version(),
 	}
-	ps.closed = true
 }
diff --git a/eth/peerset.go b/eth/peerset.go
new file mode 100644
index 0000000000..1e864a8e46
--- /dev/null
+++ b/eth/peerset.go
@@ -0,0 +1,259 @@
+// 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 .
+
+package eth
+
+import (
+	"errors"
+	"math/big"
+	"sync"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
+	"github.com/ethereum/go-ethereum/eth/protocols/snap"
+	"github.com/ethereum/go-ethereum/p2p"
+)
+
+var (
+	// errPeerSetClosed is returned if a peer is attempted to be added or removed
+	// from the peer set after it has been terminated.
+	errPeerSetClosed = errors.New("peerset closed")
+
+	// errPeerAlreadyRegistered is returned if a peer is attempted to be added
+	// to the peer set, but one with the same id already exists.
+	errPeerAlreadyRegistered = errors.New("peer already registered")
+
+	// errPeerNotRegistered is returned if a peer is attempted to be removed from
+	// a peer set, but no peer with the given id exists.
+	errPeerNotRegistered = errors.New("peer not registered")
+
+	// errSnapWithoutEth is returned if a peer attempts to connect only on the
+	// snap protocol without advertizing the eth main protocol.
+	errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support")
+)
+
+// peerSet represents the collection of active peers currently participating in
+// the `eth` protocol, with or without the `snap` extension.
+type peerSet struct {
+	peers     map[string]*ethPeer // Peers connected on the `eth` protocol
+	snapPeers int                 // Number of `snap` compatible peers for connection prioritization
+
+	snapWait map[string]chan *snap.Peer // Peers connected on `eth` waiting for their snap extension
+	snapPend map[string]*snap.Peer      // Peers connected on the `snap` protocol, but not yet on `eth`
+
+	lock   sync.RWMutex
+	closed bool
+}
+
+// newPeerSet creates a new peer set to track the active participants.
+func newPeerSet() *peerSet {
+	return &peerSet{
+		peers:    make(map[string]*ethPeer),
+		snapWait: make(map[string]chan *snap.Peer),
+		snapPend: make(map[string]*snap.Peer),
+	}
+}
+
+// registerSnapExtension unblocks an already connected `eth` peer waiting for its
+// `snap` extension, or if no such peer exists, tracks the extension for the time
+// being until the `eth` main protocol starts looking for it.
+func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error {
+	// Reject the peer if it advertises `snap` without `eth` as `snap` is only a
+	// satellite protocol meaningful with the chain selection of `eth`
+	if !peer.RunningCap(eth.ProtocolName, eth.ProtocolVersions) {
+		return errSnapWithoutEth
+	}
+	// Ensure nobody can double connect
+	ps.lock.Lock()
+	defer ps.lock.Unlock()
+
+	id := peer.ID()
+	if _, ok := ps.peers[id]; ok {
+		return errPeerAlreadyRegistered // avoid connections with the same id as existing ones
+	}
+	if _, ok := ps.snapPend[id]; ok {
+		return errPeerAlreadyRegistered // avoid connections with the same id as pending ones
+	}
+	// Inject the peer into an `eth` counterpart is available, otherwise save for later
+	if wait, ok := ps.snapWait[id]; ok {
+		delete(ps.snapWait, id)
+		wait <- peer
+		return nil
+	}
+	ps.snapPend[id] = peer
+	return nil
+}
+
+// waitExtensions blocks until all satellite protocols are connected and tracked
+// by the peerset.
+func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) {
+	// If the peer does not support a compatible `snap`, don't wait
+	if !peer.RunningCap(snap.ProtocolName, snap.ProtocolVersions) {
+		return nil, nil
+	}
+	// Ensure nobody can double connect
+	ps.lock.Lock()
+
+	id := peer.ID()
+	if _, ok := ps.peers[id]; ok {
+		ps.lock.Unlock()
+		return nil, errPeerAlreadyRegistered // avoid connections with the same id as existing ones
+	}
+	if _, ok := ps.snapWait[id]; ok {
+		ps.lock.Unlock()
+		return nil, errPeerAlreadyRegistered // avoid connections with the same id as pending ones
+	}
+	// If `snap` already connected, retrieve the peer from the pending set
+	if snap, ok := ps.snapPend[id]; ok {
+		delete(ps.snapPend, id)
+
+		ps.lock.Unlock()
+		return snap, nil
+	}
+	// Otherwise wait for `snap` to connect concurrently
+	wait := make(chan *snap.Peer)
+	ps.snapWait[id] = wait
+	ps.lock.Unlock()
+
+	return <-wait, nil
+}
+
+// registerPeer injects a new `eth` peer into the working set, or returns an error
+// if the peer is already known.
+func (ps *peerSet) registerPeer(peer *eth.Peer, ext *snap.Peer) error {
+	// Start tracking the new peer
+	ps.lock.Lock()
+	defer ps.lock.Unlock()
+
+	if ps.closed {
+		return errPeerSetClosed
+	}
+	id := peer.ID()
+	if _, ok := ps.peers[id]; ok {
+		return errPeerAlreadyRegistered
+	}
+	eth := ðPeer{
+		Peer: peer,
+	}
+	if ext != nil {
+		eth.snapExt = &snapPeer{ext}
+		ps.snapPeers++
+	}
+	ps.peers[id] = eth
+	return nil
+}
+
+// unregisterPeer removes a remote peer from the active set, disabling any further
+// actions to/from that particular entity.
+func (ps *peerSet) unregisterPeer(id string) error {
+	ps.lock.Lock()
+	defer ps.lock.Unlock()
+
+	peer, ok := ps.peers[id]
+	if !ok {
+		return errPeerNotRegistered
+	}
+	delete(ps.peers, id)
+	if peer.snapExt != nil {
+		ps.snapPeers--
+	}
+	return nil
+}
+
+// peer retrieves the registered peer with the given id.
+func (ps *peerSet) peer(id string) *ethPeer {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	return ps.peers[id]
+}
+
+// peersWithoutBlock retrieves a list of peers that do not have a given block in
+// their set of known hashes so it might be propagated to them.
+func (ps *peerSet) peersWithoutBlock(hash common.Hash) []*ethPeer {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	list := make([]*ethPeer, 0, len(ps.peers))
+	for _, p := range ps.peers {
+		if !p.KnownBlock(hash) {
+			list = append(list, p)
+		}
+	}
+	return list
+}
+
+// peersWithoutTransaction retrieves a list of peers that do not have a given
+// transaction in their set of known hashes.
+func (ps *peerSet) peersWithoutTransaction(hash common.Hash) []*ethPeer {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	list := make([]*ethPeer, 0, len(ps.peers))
+	for _, p := range ps.peers {
+		if !p.KnownTransaction(hash) {
+			list = append(list, p)
+		}
+	}
+	return list
+}
+
+// len returns if the current number of `eth` peers in the set. Since the `snap`
+// peers are tied to the existence of an `eth` connection, that will always be a
+// subset of `eth`.
+func (ps *peerSet) len() int {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	return len(ps.peers)
+}
+
+// snapLen returns if the current number of `snap` peers in the set.
+func (ps *peerSet) snapLen() int {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	return ps.snapPeers
+}
+
+// peerWithHighestTD retrieves the known peer with the currently highest total
+// difficulty.
+func (ps *peerSet) peerWithHighestTD() *eth.Peer {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	var (
+		bestPeer *eth.Peer
+		bestTd   *big.Int
+	)
+	for _, p := range ps.peers {
+		if _, td := p.Head(); bestPeer == nil || td.Cmp(bestTd) > 0 {
+			bestPeer, bestTd = p.Peer, td
+		}
+	}
+	return bestPeer
+}
+
+// close disconnects all peers.
+func (ps *peerSet) close() {
+	ps.lock.Lock()
+	defer ps.lock.Unlock()
+
+	for _, p := range ps.peers {
+		p.Disconnect(p2p.DiscQuitting)
+	}
+	ps.closed = true
+}
diff --git a/eth/protocol.go b/eth/protocol.go
deleted file mode 100644
index dc75d6b31a..0000000000
--- a/eth/protocol.go
+++ /dev/null
@@ -1,221 +0,0 @@
-// Copyright 2014 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 .
-
-package eth
-
-import (
-	"fmt"
-	"io"
-	"math/big"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/core/forkid"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/event"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-// Constants to match up protocol versions and messages
-const (
-	eth63 = 63
-	eth64 = 64
-	eth65 = 65
-)
-
-// protocolName is the official short name of the protocol used during capability negotiation.
-const protocolName = "eth"
-
-// ProtocolVersions are the supported versions of the eth protocol (first is primary).
-var ProtocolVersions = []uint{eth65, eth64, eth63}
-
-// protocolLengths are the number of implemented message corresponding to different protocol versions.
-var protocolLengths = map[uint]uint64{eth65: 17, eth64: 17, eth63: 17}
-
-const protocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
-
-// eth protocol message codes
-const (
-	StatusMsg          = 0x00
-	NewBlockHashesMsg  = 0x01
-	TransactionMsg     = 0x02
-	GetBlockHeadersMsg = 0x03
-	BlockHeadersMsg    = 0x04
-	GetBlockBodiesMsg  = 0x05
-	BlockBodiesMsg     = 0x06
-	NewBlockMsg        = 0x07
-	GetNodeDataMsg     = 0x0d
-	NodeDataMsg        = 0x0e
-	GetReceiptsMsg     = 0x0f
-	ReceiptsMsg        = 0x10
-
-	// New protocol message codes introduced in eth65
-	//
-	// Previously these message ids were used by some legacy and unsupported
-	// eth protocols, reown them here.
-	NewPooledTransactionHashesMsg = 0x08
-	GetPooledTransactionsMsg      = 0x09
-	PooledTransactionsMsg         = 0x0a
-)
-
-type errCode int
-
-const (
-	ErrMsgTooLarge = iota
-	ErrDecode
-	ErrInvalidMsgCode
-	ErrProtocolVersionMismatch
-	ErrNetworkIDMismatch
-	ErrGenesisMismatch
-	ErrForkIDRejected
-	ErrNoStatusMsg
-	ErrExtraStatusMsg
-)
-
-func (e errCode) String() string {
-	return errorToString[int(e)]
-}
-
-// XXX change once legacy code is out
-var errorToString = map[int]string{
-	ErrMsgTooLarge:             "Message too long",
-	ErrDecode:                  "Invalid message",
-	ErrInvalidMsgCode:          "Invalid message code",
-	ErrProtocolVersionMismatch: "Protocol version mismatch",
-	ErrNetworkIDMismatch:       "Network ID mismatch",
-	ErrGenesisMismatch:         "Genesis mismatch",
-	ErrForkIDRejected:          "Fork ID rejected",
-	ErrNoStatusMsg:             "No status message",
-	ErrExtraStatusMsg:          "Extra status message",
-}
-
-type txPool interface {
-	// Has returns an indicator whether txpool has a transaction
-	// cached with the given hash.
-	Has(hash common.Hash) bool
-
-	// Get retrieves the transaction from local txpool with given
-	// tx hash.
-	Get(hash common.Hash) *types.Transaction
-
-	// AddRemotes should add the given transactions to the pool.
-	AddRemotes([]*types.Transaction) []error
-
-	// Pending should return pending transactions.
-	// The slice should be modifiable by the caller.
-	Pending() (map[common.Address]types.Transactions, error)
-
-	// SubscribeNewTxsEvent should return an event subscription of
-	// NewTxsEvent and send events to the given channel.
-	SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
-}
-
-// statusData63 is the network packet for the status message for eth/63.
-type statusData63 struct {
-	ProtocolVersion uint32
-	NetworkId       uint64
-	TD              *big.Int
-	CurrentBlock    common.Hash
-	GenesisBlock    common.Hash
-}
-
-// statusData is the network packet for the status message for eth/64 and later.
-type statusData struct {
-	ProtocolVersion uint32
-	NetworkID       uint64
-	TD              *big.Int
-	Head            common.Hash
-	Genesis         common.Hash
-	ForkID          forkid.ID
-}
-
-// newBlockHashesData is the network packet for the block announcements.
-type newBlockHashesData []struct {
-	Hash   common.Hash // Hash of one particular block being announced
-	Number uint64      // Number of one particular block being announced
-}
-
-// getBlockHeadersData represents a block header query.
-type getBlockHeadersData struct {
-	Origin  hashOrNumber // Block from which to retrieve headers
-	Amount  uint64       // Maximum number of headers to retrieve
-	Skip    uint64       // Blocks to skip between consecutive headers
-	Reverse bool         // Query direction (false = rising towards latest, true = falling towards genesis)
-}
-
-// hashOrNumber is a combined field for specifying an origin block.
-type hashOrNumber struct {
-	Hash   common.Hash // Block hash from which to retrieve headers (excludes Number)
-	Number uint64      // Block hash from which to retrieve headers (excludes Hash)
-}
-
-// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the
-// two contained union fields.
-func (hn *hashOrNumber) EncodeRLP(w io.Writer) error {
-	if hn.Hash == (common.Hash{}) {
-		return rlp.Encode(w, hn.Number)
-	}
-	if hn.Number != 0 {
-		return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
-	}
-	return rlp.Encode(w, hn.Hash)
-}
-
-// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents
-// into either a block hash or a block number.
-func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error {
-	_, size, _ := s.Kind()
-	origin, err := s.Raw()
-	if err == nil {
-		switch {
-		case size == 32:
-			err = rlp.DecodeBytes(origin, &hn.Hash)
-		case size <= 8:
-			err = rlp.DecodeBytes(origin, &hn.Number)
-		default:
-			err = fmt.Errorf("invalid input size %d for origin", size)
-		}
-	}
-	return err
-}
-
-// newBlockData is the network packet for the block propagation message.
-type newBlockData struct {
-	Block *types.Block
-	TD    *big.Int
-}
-
-// sanityCheck verifies that the values are reasonable, as a DoS protection
-func (request *newBlockData) sanityCheck() error {
-	if err := request.Block.SanityCheck(); err != nil {
-		return err
-	}
-	//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
-	// larger, it will still fit within 100 bits
-	if tdlen := request.TD.BitLen(); tdlen > 100 {
-		return fmt.Errorf("too large block TD: bitlen %d", tdlen)
-	}
-	return nil
-}
-
-// blockBody represents the data content of a single block.
-type blockBody struct {
-	Transactions []*types.Transaction // Transactions contained within a block
-	Uncles       []*types.Header      // Uncles contained within a block
-}
-
-// blockBodiesData is the network packet for block content distribution.
-type blockBodiesData []*blockBody
diff --git a/eth/protocol_test.go b/eth/protocol_test.go
deleted file mode 100644
index 5f57c87175..0000000000
--- a/eth/protocol_test.go
+++ /dev/null
@@ -1,459 +0,0 @@
-// Copyright 2014 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 .
-
-package eth
-
-import (
-	"fmt"
-	"math/big"
-	"sync"
-	"sync/atomic"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/consensus/ethash"
-	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/core/forkid"
-	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/core/vm"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/eth/downloader"
-	"github.com/ethereum/go-ethereum/event"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/params"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-func init() {
-	// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
-}
-
-var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-
-// Tests that handshake failures are detected and reported correctly.
-func TestStatusMsgErrors63(t *testing.T) {
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
-	var (
-		genesis = pm.blockchain.Genesis()
-		head    = pm.blockchain.CurrentHeader()
-		td      = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
-	)
-	defer pm.Stop()
-
-	tests := []struct {
-		code      uint64
-		data      interface{}
-		wantError error
-	}{
-		{
-			code: TransactionMsg, data: []interface{}{},
-			wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
-		},
-		{
-			code: StatusMsg, data: statusData63{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash()},
-			wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 63),
-		},
-		{
-			code: StatusMsg, data: statusData63{63, 999, td, head.Hash(), genesis.Hash()},
-			wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId),
-		},
-		{
-			code: StatusMsg, data: statusData63{63, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}},
-			wantError: errResp(ErrGenesisMismatch, "0300000000000000 (!= %x)", genesis.Hash().Bytes()[:8]),
-		},
-	}
-	for i, test := range tests {
-		p, errc := newTestPeer("peer", 63, pm, false)
-		// The send call might hang until reset because
-		// the protocol might not read the payload.
-		go p2p.Send(p.app, test.code, test.data)
-
-		select {
-		case err := <-errc:
-			if err == nil {
-				t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError)
-			} else if err.Error() != test.wantError.Error() {
-				t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError)
-			}
-		case <-time.After(2 * time.Second):
-			t.Errorf("protocol did not shut down within 2 seconds")
-		}
-		p.close()
-	}
-}
-
-func TestStatusMsgErrors64(t *testing.T) {
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
-	var (
-		genesis = pm.blockchain.Genesis()
-		head    = pm.blockchain.CurrentHeader()
-		td      = pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
-		forkID  = forkid.NewID(pm.blockchain)
-	)
-	defer pm.Stop()
-
-	tests := []struct {
-		code      uint64
-		data      interface{}
-		wantError error
-	}{
-		{
-			code: TransactionMsg, data: []interface{}{},
-			wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"),
-		},
-		{
-			code: StatusMsg, data: statusData{10, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkID},
-			wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", 64),
-		},
-		{
-			code: StatusMsg, data: statusData{64, 999, td, head.Hash(), genesis.Hash(), forkID},
-			wantError: errResp(ErrNetworkIDMismatch, "999 (!= %d)", DefaultConfig.NetworkId),
-		},
-		{
-			code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), common.Hash{3}, forkID},
-			wantError: errResp(ErrGenesisMismatch, "0300000000000000000000000000000000000000000000000000000000000000 (!= %x)", genesis.Hash()),
-		},
-		{
-			code: StatusMsg, data: statusData{64, DefaultConfig.NetworkId, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
-			wantError: errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()),
-		},
-	}
-	for i, test := range tests {
-		p, errc := newTestPeer("peer", 64, pm, false)
-		// The send call might hang until reset because
-		// the protocol might not read the payload.
-		go p2p.Send(p.app, test.code, test.data)
-
-		select {
-		case err := <-errc:
-			if err == nil {
-				t.Errorf("test %d: protocol returned nil error, want %q", i, test.wantError)
-			} else if err.Error() != test.wantError.Error() {
-				t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.wantError)
-			}
-		case <-time.After(2 * time.Second):
-			t.Errorf("protocol did not shut down within 2 seconds")
-		}
-		p.close()
-	}
-}
-
-func TestForkIDSplit(t *testing.T) {
-	var (
-		engine = ethash.NewFaker()
-
-		configNoFork  = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}
-		configProFork = ¶ms.ChainConfig{
-			HomesteadBlock: big.NewInt(1),
-			EIP150Block:    big.NewInt(2),
-			EIP155Block:    big.NewInt(2),
-			EIP158Block:    big.NewInt(2),
-			ByzantiumBlock: big.NewInt(3),
-		}
-		dbNoFork  = rawdb.NewMemoryDatabase()
-		dbProFork = rawdb.NewMemoryDatabase()
-
-		gspecNoFork  = &core.Genesis{Config: configNoFork}
-		gspecProFork = &core.Genesis{Config: configProFork}
-
-		genesisNoFork  = gspecNoFork.MustCommit(dbNoFork)
-		genesisProFork = gspecProFork.MustCommit(dbProFork)
-
-		chainNoFork, _  = core.NewBlockChain(dbNoFork, nil, configNoFork, engine, vm.Config{}, nil)
-		chainProFork, _ = core.NewBlockChain(dbProFork, nil, configProFork, engine, vm.Config{}, nil)
-
-		blocksNoFork, _  = core.GenerateChain(configNoFork, genesisNoFork, engine, dbNoFork, 2, nil)
-		blocksProFork, _ = core.GenerateChain(configProFork, genesisProFork, engine, dbProFork, 2, nil)
-
-		ethNoFork, _  = NewProtocolManager(configNoFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainNoFork, dbNoFork, 1, nil, false)
-		ethProFork, _ = NewProtocolManager(configProFork, nil, downloader.FullSync, 1, new(event.TypeMux), &testTxPool{pool: make(map[common.Hash]*types.Transaction)}, engine, chainProFork, dbProFork, 1, nil, false)
-	)
-	ethNoFork.Start(1000)
-	ethProFork.Start(1000)
-
-	// Both nodes should allow the other to connect (same genesis, next fork is the same)
-	p2pNoFork, p2pProFork := p2p.MsgPipe()
-	peerNoFork := newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
-	peerProFork := newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
-
-	errc := make(chan error, 2)
-	go func() { errc <- ethNoFork.handle(peerProFork) }()
-	go func() { errc <- ethProFork.handle(peerNoFork) }()
-
-	select {
-	case err := <-errc:
-		t.Fatalf("frontier nofork <-> profork failed: %v", err)
-	case <-time.After(250 * time.Millisecond):
-		p2pNoFork.Close()
-		p2pProFork.Close()
-	}
-	// Progress into Homestead. Fork's match, so we don't care what the future holds
-	chainNoFork.InsertChain(blocksNoFork[:1])
-	chainProFork.InsertChain(blocksProFork[:1])
-
-	p2pNoFork, p2pProFork = p2p.MsgPipe()
-	peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
-	peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
-
-	errc = make(chan error, 2)
-	go func() { errc <- ethNoFork.handle(peerProFork) }()
-	go func() { errc <- ethProFork.handle(peerNoFork) }()
-
-	select {
-	case err := <-errc:
-		t.Fatalf("homestead nofork <-> profork failed: %v", err)
-	case <-time.After(250 * time.Millisecond):
-		p2pNoFork.Close()
-		p2pProFork.Close()
-	}
-	// Progress into Spurious. Forks mismatch, signalling differing chains, reject
-	chainNoFork.InsertChain(blocksNoFork[1:2])
-	chainProFork.InsertChain(blocksProFork[1:2])
-
-	p2pNoFork, p2pProFork = p2p.MsgPipe()
-	peerNoFork = newPeer(64, p2p.NewPeer(enode.ID{1}, "", nil), p2pNoFork, nil)
-	peerProFork = newPeer(64, p2p.NewPeer(enode.ID{2}, "", nil), p2pProFork, nil)
-
-	errc = make(chan error, 2)
-	go func() { errc <- ethNoFork.handle(peerProFork) }()
-	go func() { errc <- ethProFork.handle(peerNoFork) }()
-
-	select {
-	case err := <-errc:
-		if want := errResp(ErrForkIDRejected, forkid.ErrLocalIncompatibleOrStale.Error()); err.Error() != want.Error() {
-			t.Fatalf("fork ID rejection error mismatch: have %v, want %v", err, want)
-		}
-	case <-time.After(250 * time.Millisecond):
-		t.Fatalf("split peers not rejected")
-	}
-}
-
-// This test checks that received transactions are added to the local pool.
-func TestRecvTransactions63(t *testing.T) { testRecvTransactions(t, 63) }
-func TestRecvTransactions64(t *testing.T) { testRecvTransactions(t, 64) }
-func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, 65) }
-
-func testRecvTransactions(t *testing.T, protocol int) {
-	txAdded := make(chan []*types.Transaction)
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, txAdded)
-	pm.acceptTxs = 1 // mark synced to accept transactions
-	p, _ := newTestPeer("peer", protocol, pm, true)
-	defer pm.Stop()
-	defer p.close()
-
-	tx := newTestTransaction(testAccount, 0, 0)
-	if err := p2p.Send(p.app, TransactionMsg, []interface{}{tx}); err != nil {
-		t.Fatalf("send error: %v", err)
-	}
-	select {
-	case added := <-txAdded:
-		if len(added) != 1 {
-			t.Errorf("wrong number of added transactions: got %d, want 1", len(added))
-		} else if added[0].Hash() != tx.Hash() {
-			t.Errorf("added wrong tx hash: got %v, want %v", added[0].Hash(), tx.Hash())
-		}
-	case <-time.After(2 * time.Second):
-		t.Errorf("no NewTxsEvent received within 2 seconds")
-	}
-}
-
-// This test checks that pending transactions are sent.
-func TestSendTransactions63(t *testing.T) { testSendTransactions(t, 63) }
-func TestSendTransactions64(t *testing.T) { testSendTransactions(t, 64) }
-func TestSendTransactions65(t *testing.T) { testSendTransactions(t, 65) }
-
-func testSendTransactions(t *testing.T, protocol int) {
-	pm, _ := newTestProtocolManagerMust(t, downloader.FullSync, 0, nil, nil)
-	defer pm.Stop()
-
-	// Fill the pool with big transactions (use a subscription to wait until all
-	// the transactions are announced to avoid spurious events causing extra
-	// broadcasts).
-	const txsize = txsyncPackSize / 10
-	alltxs := make([]*types.Transaction, 100)
-	for nonce := range alltxs {
-		alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), txsize)
-	}
-	pm.txpool.AddRemotes(alltxs)
-	time.Sleep(100 * time.Millisecond) // Wait until new tx even gets out of the system (lame)
-
-	// Connect several peers. They should all receive the pending transactions.
-	var wg sync.WaitGroup
-	checktxs := func(p *testPeer) {
-		defer wg.Done()
-		defer p.close()
-		seen := make(map[common.Hash]bool)
-		for _, tx := range alltxs {
-			seen[tx.Hash()] = false
-		}
-		for n := 0; n < len(alltxs) && !t.Failed(); {
-			var forAllHashes func(callback func(hash common.Hash))
-			switch protocol {
-			case 63:
-				fallthrough
-			case 64:
-				msg, err := p.app.ReadMsg()
-				if err != nil {
-					t.Errorf("%v: read error: %v", p.Peer, err)
-					continue
-				} else if msg.Code != TransactionMsg {
-					t.Errorf("%v: got code %d, want TxMsg", p.Peer, msg.Code)
-					continue
-				}
-				var txs []*types.Transaction
-				if err := msg.Decode(&txs); err != nil {
-					t.Errorf("%v: %v", p.Peer, err)
-					continue
-				}
-				forAllHashes = func(callback func(hash common.Hash)) {
-					for _, tx := range txs {
-						callback(tx.Hash())
-					}
-				}
-			case 65:
-				msg, err := p.app.ReadMsg()
-				if err != nil {
-					t.Errorf("%v: read error: %v", p.Peer, err)
-					continue
-				} else if msg.Code != NewPooledTransactionHashesMsg {
-					t.Errorf("%v: got code %d, want NewPooledTransactionHashesMsg", p.Peer, msg.Code)
-					continue
-				}
-				var hashes []common.Hash
-				if err := msg.Decode(&hashes); err != nil {
-					t.Errorf("%v: %v", p.Peer, err)
-					continue
-				}
-				forAllHashes = func(callback func(hash common.Hash)) {
-					for _, h := range hashes {
-						callback(h)
-					}
-				}
-			}
-			forAllHashes(func(hash common.Hash) {
-				seentx, want := seen[hash]
-				if seentx {
-					t.Errorf("%v: got tx more than once: %x", p.Peer, hash)
-				}
-				if !want {
-					t.Errorf("%v: got unexpected tx: %x", p.Peer, hash)
-				}
-				seen[hash] = true
-				n++
-			})
-		}
-	}
-	for i := 0; i < 3; i++ {
-		p, _ := newTestPeer(fmt.Sprintf("peer #%d", i), protocol, pm, true)
-		wg.Add(1)
-		go checktxs(p)
-	}
-	wg.Wait()
-}
-
-func TestTransactionPropagation(t *testing.T)  { testSyncTransaction(t, true) }
-func TestTransactionAnnouncement(t *testing.T) { testSyncTransaction(t, false) }
-
-func testSyncTransaction(t *testing.T, propagtion bool) {
-	// Create a protocol manager for transaction fetcher and sender
-	pmFetcher, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
-	defer pmFetcher.Stop()
-	pmSender, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil)
-	pmSender.broadcastTxAnnouncesOnly = !propagtion
-	defer pmSender.Stop()
-
-	// Sync up the two peers
-	io1, io2 := p2p.MsgPipe()
-
-	go pmSender.handle(pmSender.newPeer(65, p2p.NewPeer(enode.ID{}, "sender", nil), io2, pmSender.txpool.Get))
-	go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get))
-
-	time.Sleep(250 * time.Millisecond)
-	pmFetcher.doSync(peerToSyncOp(downloader.FullSync, pmFetcher.peers.BestPeer()))
-	atomic.StoreUint32(&pmFetcher.acceptTxs, 1)
-
-	newTxs := make(chan core.NewTxsEvent, 1024)
-	sub := pmFetcher.txpool.SubscribeNewTxsEvent(newTxs)
-	defer sub.Unsubscribe()
-
-	// Fill the pool with new transactions
-	alltxs := make([]*types.Transaction, 1024)
-	for nonce := range alltxs {
-		alltxs[nonce] = newTestTransaction(testAccount, uint64(nonce), 0)
-	}
-	pmSender.txpool.AddRemotes(alltxs)
-
-	var got int
-loop:
-	for {
-		select {
-		case ev := <-newTxs:
-			got += len(ev.Txs)
-			if got == 1024 {
-				break loop
-			}
-		case <-time.NewTimer(time.Second).C:
-			t.Fatal("Failed to retrieve all transaction")
-		}
-	}
-}
-
-// Tests that the custom union field encoder and decoder works correctly.
-func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
-	// Create a "random" hash for testing
-	var hash common.Hash
-	for i := range hash {
-		hash[i] = byte(i)
-	}
-	// Assemble some table driven tests
-	tests := []struct {
-		packet *getBlockHeadersData
-		fail   bool
-	}{
-		// Providing the origin as either a hash or a number should both work
-		{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}}},
-		{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}}},
-
-		// Providing arbitrary query field should also work
-		{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
-		{fail: false, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},
-
-		// Providing both the origin hash and origin number must fail
-		{fail: true, packet: &getBlockHeadersData{Origin: hashOrNumber{Hash: hash, Number: 314}}},
-	}
-	// Iterate over each of the tests and try to encode and then decode
-	for i, tt := range tests {
-		bytes, err := rlp.EncodeToBytes(tt.packet)
-		if err != nil && !tt.fail {
-			t.Fatalf("test %d: failed to encode packet: %v", i, err)
-		} else if err == nil && tt.fail {
-			t.Fatalf("test %d: encode should have failed", i)
-		}
-		if !tt.fail {
-			packet := new(getBlockHeadersData)
-			if err := rlp.DecodeBytes(bytes, packet); err != nil {
-				t.Fatalf("test %d: failed to decode packet: %v", i, err)
-			}
-			if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
-				packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
-				t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
-			}
-		}
-	}
-}
diff --git a/eth/protocols/eth/broadcast.go b/eth/protocols/eth/broadcast.go
new file mode 100644
index 0000000000..328396d510
--- /dev/null
+++ b/eth/protocols/eth/broadcast.go
@@ -0,0 +1,195 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package eth
+
+import (
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+const (
+	// This is the target size for the packs of transactions or announcements. A
+	// pack can get larger than this if a single transactions exceeds this size.
+	maxTxPacketSize = 100 * 1024
+)
+
+// blockPropagation is a block propagation event, waiting for its turn in the
+// broadcast queue.
+type blockPropagation struct {
+	block *types.Block
+	td    *big.Int
+}
+
+// broadcastBlocks is a write loop that multiplexes blocks and block accouncements
+// to the remote peer. The goal is to have an async writer that does not lock up
+// node internals and at the same time rate limits queued data.
+func (p *Peer) broadcastBlocks() {
+	for {
+		select {
+		case prop := <-p.queuedBlocks:
+			if err := p.SendNewBlock(prop.block, prop.td); err != nil {
+				return
+			}
+			p.Log().Trace("Propagated block", "number", prop.block.Number(), "hash", prop.block.Hash(), "td", prop.td)
+
+		case block := <-p.queuedBlockAnns:
+			if err := p.SendNewBlockHashes([]common.Hash{block.Hash()}, []uint64{block.NumberU64()}); err != nil {
+				return
+			}
+			p.Log().Trace("Announced block", "number", block.Number(), "hash", block.Hash())
+
+		case <-p.term:
+			return
+		}
+	}
+}
+
+// broadcastTransactions is a write loop that schedules transaction broadcasts
+// to the remote peer. The goal is to have an async writer that does not lock up
+// node internals and at the same time rate limits queued data.
+func (p *Peer) broadcastTransactions() {
+	var (
+		queue  []common.Hash         // Queue of hashes to broadcast as full transactions
+		done   chan struct{}         // Non-nil if background broadcaster is running
+		fail   = make(chan error, 1) // Channel used to receive network error
+		failed bool                  // Flag whether a send failed, discard everything onward
+	)
+	for {
+		// If there's no in-flight broadcast running, check if a new one is needed
+		if done == nil && len(queue) > 0 {
+			// Pile transaction until we reach our allowed network limit
+			var (
+				hashes []common.Hash
+				txs    []*types.Transaction
+				size   common.StorageSize
+			)
+			for i := 0; i < len(queue) && size < maxTxPacketSize; i++ {
+				if tx := p.txpool.Get(queue[i]); tx != nil {
+					txs = append(txs, tx)
+					size += tx.Size()
+				}
+				hashes = append(hashes, queue[i])
+			}
+			queue = queue[:copy(queue, queue[len(hashes):])]
+
+			// If there's anything available to transfer, fire up an async writer
+			if len(txs) > 0 {
+				done = make(chan struct{})
+				go func() {
+					if err := p.SendTransactions(txs); err != nil {
+						fail <- err
+						return
+					}
+					close(done)
+					p.Log().Trace("Sent transactions", "count", len(txs))
+				}()
+			}
+		}
+		// Transfer goroutine may or may not have been started, listen for events
+		select {
+		case hashes := <-p.txBroadcast:
+			// If the connection failed, discard all transaction events
+			if failed {
+				continue
+			}
+			// New batch of transactions to be broadcast, queue them (with cap)
+			queue = append(queue, hashes...)
+			if len(queue) > maxQueuedTxs {
+				// Fancy copy and resize to ensure buffer doesn't grow indefinitely
+				queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxs:])]
+			}
+
+		case <-done:
+			done = nil
+
+		case <-fail:
+			failed = true
+
+		case <-p.term:
+			return
+		}
+	}
+}
+
+// announceTransactions is a write loop that schedules transaction broadcasts
+// to the remote peer. The goal is to have an async writer that does not lock up
+// node internals and at the same time rate limits queued data.
+func (p *Peer) announceTransactions() {
+	var (
+		queue  []common.Hash         // Queue of hashes to announce as transaction stubs
+		done   chan struct{}         // Non-nil if background announcer is running
+		fail   = make(chan error, 1) // Channel used to receive network error
+		failed bool                  // Flag whether a send failed, discard everything onward
+	)
+	for {
+		// If there's no in-flight announce running, check if a new one is needed
+		if done == nil && len(queue) > 0 {
+			// Pile transaction hashes until we reach our allowed network limit
+			var (
+				count   int
+				pending []common.Hash
+				size    common.StorageSize
+			)
+			for count = 0; count < len(queue) && size < maxTxPacketSize; count++ {
+				if p.txpool.Get(queue[count]) != nil {
+					pending = append(pending, queue[count])
+					size += common.HashLength
+				}
+			}
+			// Shift and trim queue
+			queue = queue[:copy(queue, queue[count:])]
+
+			// If there's anything available to transfer, fire up an async writer
+			if len(pending) > 0 {
+				done = make(chan struct{})
+				go func() {
+					if err := p.sendPooledTransactionHashes(pending); err != nil {
+						fail <- err
+						return
+					}
+					close(done)
+					p.Log().Trace("Sent transaction announcements", "count", len(pending))
+				}()
+			}
+		}
+		// Transfer goroutine may or may not have been started, listen for events
+		select {
+		case hashes := <-p.txAnnounce:
+			// If the connection failed, discard all transaction events
+			if failed {
+				continue
+			}
+			// New batch of transactions to be broadcast, queue them (with cap)
+			queue = append(queue, hashes...)
+			if len(queue) > maxQueuedTxAnns {
+				// Fancy copy and resize to ensure buffer doesn't grow indefinitely
+				queue = queue[:copy(queue, queue[len(queue)-maxQueuedTxAnns:])]
+			}
+
+		case <-done:
+			done = nil
+
+		case <-fail:
+			failed = true
+
+		case <-p.term:
+			return
+		}
+	}
+}
diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go
new file mode 100644
index 0000000000..025479b423
--- /dev/null
+++ b/eth/protocols/eth/discovery.go
@@ -0,0 +1,65 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package eth
+
+import (
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// enrEntry is the ENR entry which advertises `eth` protocol on the discovery.
+type enrEntry struct {
+	ForkID forkid.ID // Fork identifier per EIP-2124
+
+	// Ignore additional fields (for forward compatibility).
+	Rest []rlp.RawValue `rlp:"tail"`
+}
+
+// ENRKey implements enr.Entry.
+func (e enrEntry) ENRKey() string {
+	return "eth"
+}
+
+// StartENRUpdater starts the `eth` ENR updater loop, which listens for chain
+// head events and updates the requested node record whenever a fork is passed.
+func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) {
+	var newHead = make(chan core.ChainHeadEvent, 10)
+	sub := chain.SubscribeChainHeadEvent(newHead)
+
+	go func() {
+		defer sub.Unsubscribe()
+		for {
+			select {
+			case <-newHead:
+				ln.Set(currentENREntry(chain))
+			case <-sub.Err():
+				// Would be nice to sync with Stop, but there is no
+				// good way to do that.
+				return
+			}
+		}
+	}()
+}
+
+// currentENREntry constructs an `eth` ENR entry based on the current state of the chain.
+func currentENREntry(chain *core.BlockChain) *enrEntry {
+	return &enrEntry{
+		ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), chain.CurrentHeader().Number.Uint64()),
+	}
+}
diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go
new file mode 100644
index 0000000000..6bbaa2f555
--- /dev/null
+++ b/eth/protocols/eth/handler.go
@@ -0,0 +1,242 @@
+// 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 .
+
+package eth
+
+import (
+	"fmt"
+	"math/big"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+const (
+	// softResponseLimit is the target maximum size of replies to data retrievals.
+	softResponseLimit = 2 * 1024 * 1024
+
+	// estHeaderSize is the approximate size of an RLP encoded block header.
+	estHeaderSize = 500
+
+	// maxHeadersServe is the maximum number of block headers to serve. This number
+	// is there to limit the number of disk lookups.
+	maxHeadersServe = 1024
+
+	// maxBodiesServe is the maximum number of block bodies to serve. This number
+	// is mostly there to limit the number of disk lookups. With 24KB block sizes
+	// nowadays, the practical limit will always be softResponseLimit.
+	maxBodiesServe = 1024
+
+	// maxNodeDataServe is the maximum number of state trie nodes to serve. This
+	// number is there to limit the number of disk lookups.
+	maxNodeDataServe = 1024
+
+	// maxReceiptsServe is the maximum number of block receipts to serve. This
+	// number is mostly there to limit the number of disk lookups. With block
+	// containing 200+ transactions nowadays, the practical limit will always
+	// be softResponseLimit.
+	maxReceiptsServe = 1024
+)
+
+// Handler is a callback to invoke from an outside runner after the boilerplate
+// exchanges have passed.
+type Handler func(peer *Peer) error
+
+// Backend defines the data retrieval methods to serve remote requests and the
+// callback methods to invoke on remote deliveries.
+type Backend interface {
+	// Chain retrieves the blockchain object to serve data.
+	Chain() *core.BlockChain
+
+	// StateBloom retrieves the bloom filter - if any - for state trie nodes.
+	StateBloom() *trie.SyncBloom
+
+	// TxPool retrieves the transaction pool object to serve data.
+	TxPool() TxPool
+
+	// AcceptTxs retrieves whether transaction processing is enabled on the node
+	// or if inbound transactions should simply be dropped.
+	AcceptTxs() bool
+
+	// RunPeer is invoked when a peer joins on the `eth` protocol. The handler
+	// should do any peer maintenance work, handshakes and validations. If all
+	// is passed, control should be given back to the `handler` to process the
+	// inbound messages going forward.
+	RunPeer(peer *Peer, handler Handler) error
+
+	// PeerInfo retrieves all known `eth` information about a peer.
+	PeerInfo(id enode.ID) interface{}
+
+	// Handle is a callback to be invoked when a data packet is received from
+	// the remote peer. Only packets not consumed by the protocol handler will
+	// be forwarded to the backend.
+	Handle(peer *Peer, packet Packet) error
+}
+
+// TxPool defines the methods needed by the protocol handler to serve transactions.
+type TxPool interface {
+	// Get retrieves the the transaction from the local txpool with the given hash.
+	Get(hash common.Hash) *types.Transaction
+}
+
+// MakeProtocols constructs the P2P protocol definitions for `eth`.
+func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol {
+	protocols := make([]p2p.Protocol, len(ProtocolVersions))
+	for i, version := range ProtocolVersions {
+		version := version // Closure
+
+		protocols[i] = p2p.Protocol{
+			Name:    ProtocolName,
+			Version: version,
+			Length:  protocolLengths[version],
+			Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
+				peer := NewPeer(version, p, rw, backend.TxPool())
+				defer peer.Close()
+
+				return backend.RunPeer(peer, func(peer *Peer) error {
+					return Handle(backend, peer)
+				})
+			},
+			NodeInfo: func() interface{} {
+				return nodeInfo(backend.Chain(), network)
+			},
+			PeerInfo: func(id enode.ID) interface{} {
+				return backend.PeerInfo(id)
+			},
+			Attributes:     []enr.Entry{currentENREntry(backend.Chain())},
+			DialCandidates: dnsdisc,
+		}
+	}
+	return protocols
+}
+
+// NodeInfo represents a short summary of the `eth` sub-protocol metadata
+// known about the host peer.
+type NodeInfo struct {
+	Network    uint64              `json:"network"`    // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
+	Difficulty *big.Int            `json:"difficulty"` // Total difficulty of the host's blockchain
+	Genesis    common.Hash         `json:"genesis"`    // SHA3 hash of the host's genesis block
+	Config     *params.ChainConfig `json:"config"`     // Chain configuration for the fork rules
+	Head       common.Hash         `json:"head"`       // Hex hash of the host's best owned block
+}
+
+// nodeInfo retrieves some `eth` protocol metadata about the running host node.
+func nodeInfo(chain *core.BlockChain, network uint64) *NodeInfo {
+	head := chain.CurrentBlock()
+	return &NodeInfo{
+		Network:    network,
+		Difficulty: chain.GetTd(head.Hash(), head.NumberU64()),
+		Genesis:    chain.Genesis().Hash(),
+		Config:     chain.Config(),
+		Head:       head.Hash(),
+	}
+}
+
+// Handle is invoked whenever an `eth` connection is made that successfully passes
+// the protocol handshake. This method will keep processing messages until the
+// connection is torn down.
+func Handle(backend Backend, peer *Peer) error {
+	for {
+		if err := handleMessage(backend, peer); err != nil {
+			peer.Log().Debug("Message handling failed in `eth`", "err", err)
+			return err
+		}
+	}
+}
+
+type msgHandler func(backend Backend, msg Decoder, peer *Peer) error
+type Decoder interface {
+	Decode(val interface{}) error
+	Time() time.Time
+}
+
+var eth65 = map[uint64]msgHandler{
+	GetBlockHeadersMsg:            handleGetBlockHeaders,
+	BlockHeadersMsg:               handleBlockHeaders,
+	GetBlockBodiesMsg:             handleGetBlockBodies,
+	BlockBodiesMsg:                handleBlockBodies,
+	GetNodeDataMsg:                handleGetNodeData,
+	NodeDataMsg:                   handleNodeData,
+	GetReceiptsMsg:                handleGetReceipts,
+	ReceiptsMsg:                   handleReceipts,
+	NewBlockHashesMsg:             handleNewBlockhashes,
+	NewBlockMsg:                   handleNewBlock,
+	TransactionsMsg:               handleTransactions,
+	NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
+	GetPooledTransactionsMsg:      handleGetPooledTransactions,
+	PooledTransactionsMsg:         handlePooledTransactions,
+}
+
+var eth66 = map[uint64]msgHandler{
+	NewBlockHashesMsg:             handleNewBlockhashes,
+	NewBlockMsg:                   handleNewBlock,
+	TransactionsMsg:               handleTransactions,
+	NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes,
+	// eth66 messages with request-id
+	GetBlockHeadersMsg:       handleGetBlockHeaders66,
+	BlockHeadersMsg:          handleBlockHeaders66,
+	GetBlockBodiesMsg:        handleGetBlockBodies66,
+	BlockBodiesMsg:           handleBlockBodies66,
+	GetNodeDataMsg:           handleGetNodeData66,
+	NodeDataMsg:              handleNodeData66,
+	GetReceiptsMsg:           handleGetReceipts66,
+	ReceiptsMsg:              handleReceipts66,
+	GetPooledTransactionsMsg: handleGetPooledTransactions66,
+	PooledTransactionsMsg:    handlePooledTransactions66,
+}
+
+// handleMessage is invoked whenever an inbound message is received from a remote
+// peer. The remote connection is torn down upon returning any error.
+func handleMessage(backend Backend, peer *Peer) error {
+	// Read the next message from the remote peer, and ensure it's fully consumed
+	msg, err := peer.rw.ReadMsg()
+	if err != nil {
+		return err
+	}
+	if msg.Size > maxMessageSize {
+		return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
+	}
+	defer msg.Discard()
+
+	var handlers = eth65
+	if peer.Version() >= ETH66 {
+		handlers = eth66
+	}
+	// Track the amount of time it takes to serve the request and run the handler
+	if metrics.Enabled {
+		h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code)
+		defer func(start time.Time) {
+			sampler := func() metrics.Sample {
+				return metrics.ResettingSample(
+					metrics.NewExpDecaySample(1028, 0.015),
+				)
+			}
+			metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds())
+		}(time.Now())
+	}
+	if handler := handlers[msg.Code]; handler != nil {
+		return handler(backend, msg, peer)
+	}
+	return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code)
+}
diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go
new file mode 100644
index 0000000000..2dd2446e3d
--- /dev/null
+++ b/eth/protocols/eth/handler_test.go
@@ -0,0 +1,586 @@
+// Copyright 2015 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 .
+
+package eth
+
+import (
+	"math"
+	"math/big"
+	"math/rand"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+var (
+	// testKey is a private key to use for funding a tester account.
+	testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+
+	// testAddr is the Ethereum address of the tester account.
+	testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+)
+
+// testBackend is a mock implementation of the live Ethereum message handler. Its
+// purpose is to allow testing the request/reply workflows and wire serialization
+// in the `eth` protocol without actually doing any data processing.
+type testBackend struct {
+	db     ethdb.Database
+	chain  *core.BlockChain
+	txpool *core.TxPool
+}
+
+// newTestBackend creates an empty chain and wraps it into a mock backend.
+func newTestBackend(blocks int) *testBackend {
+	return newTestBackendWithGenerator(blocks, nil)
+}
+
+// newTestBackend creates a chain with a number of explicitly defined blocks and
+// wraps it into a mock backend.
+func newTestBackendWithGenerator(blocks int, generator func(int, *core.BlockGen)) *testBackend {
+	// Create a database pre-initialize with a genesis block
+	db := rawdb.NewMemoryDatabase()
+	(&core.Genesis{
+		Config: params.TestChainConfig,
+		Alloc:  core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}},
+	}).MustCommit(db)
+
+	chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
+
+	bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator)
+	if _, err := chain.InsertChain(bs); err != nil {
+		panic(err)
+	}
+	txconfig := core.DefaultTxPoolConfig
+	txconfig.Journal = "" // Don't litter the disk with test journals
+
+	return &testBackend{
+		db:     db,
+		chain:  chain,
+		txpool: core.NewTxPool(txconfig, params.TestChainConfig, chain),
+	}
+}
+
+// close tears down the transaction pool and chain behind the mock backend.
+func (b *testBackend) close() {
+	b.txpool.Stop()
+	b.chain.Stop()
+}
+
+func (b *testBackend) Chain() *core.BlockChain     { return b.chain }
+func (b *testBackend) StateBloom() *trie.SyncBloom { return nil }
+func (b *testBackend) TxPool() TxPool              { return b.txpool }
+
+func (b *testBackend) RunPeer(peer *Peer, handler Handler) error {
+	// Normally the backend would do peer mainentance and handshakes. All that
+	// is omitted and we will just give control back to the handler.
+	return handler(peer)
+}
+func (b *testBackend) PeerInfo(enode.ID) interface{} { panic("not implemented") }
+
+func (b *testBackend) AcceptTxs() bool {
+	panic("data processing tests should be done in the handler package")
+}
+func (b *testBackend) Handle(*Peer, Packet) error {
+	panic("data processing tests should be done in the handler package")
+}
+
+// Tests that block headers can be retrieved from a remote chain based on user queries.
+func TestGetBlockHeaders65(t *testing.T) { testGetBlockHeaders(t, ETH65) }
+func TestGetBlockHeaders66(t *testing.T) { testGetBlockHeaders(t, ETH66) }
+
+func testGetBlockHeaders(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	backend := newTestBackend(maxHeadersServe + 15)
+	defer backend.close()
+
+	peer, _ := newTestPeer("peer", protocol, backend)
+	defer peer.close()
+
+	// Create a "random" unknown hash for testing
+	var unknown common.Hash
+	for i := range unknown {
+		unknown[i] = byte(i)
+	}
+	// Create a batch of tests for various scenarios
+	limit := uint64(maxHeadersServe)
+	tests := []struct {
+		query  *GetBlockHeadersPacket // The query to execute for header retrieval
+		expect []common.Hash          // The hashes of the block whose headers are expected
+	}{
+		// A single random block should be retrievable by hash and number too
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
+			[]common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 1},
+			[]common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()},
+		},
+		// Multiple headers should be retrievable in both directions
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(limit / 2).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 + 1).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 + 2).Hash(),
+			},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(limit / 2).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 - 1).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 - 2).Hash(),
+			},
+		},
+		// Multiple headers with skip lists should be retrievable
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(limit / 2).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 + 4).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 + 8).Hash(),
+			},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(limit / 2).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 - 4).Hash(),
+				backend.chain.GetBlockByNumber(limit/2 - 8).Hash(),
+			},
+		},
+		// The chain endpoints should be retrievable
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 0}, Amount: 1},
+			[]common.Hash{backend.chain.GetBlockByNumber(0).Hash()},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64()}, Amount: 1},
+			[]common.Hash{backend.chain.CurrentBlock().Hash()},
+		},
+		// Ensure protocol limits are honored
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
+			backend.chain.GetBlockHashesFromHash(backend.chain.CurrentBlock().Hash(), limit),
+		},
+		// Check that requesting more than available is handled gracefully
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(),
+				backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64()).Hash(),
+			},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(4).Hash(),
+				backend.chain.GetBlockByNumber(0).Hash(),
+			},
+		},
+		// Check that requesting more than available is handled gracefully, even if mid skip
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 4).Hash(),
+				backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().NumberU64() - 1).Hash(),
+			},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(4).Hash(),
+				backend.chain.GetBlockByNumber(1).Hash(),
+			},
+		},
+		// Check a corner case where requesting more can iterate past the endpoints
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(2).Hash(),
+				backend.chain.GetBlockByNumber(1).Hash(),
+				backend.chain.GetBlockByNumber(0).Hash(),
+			},
+		},
+		// Check a corner case where skipping overflow loops back into the chain start
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(3).Hash(),
+			},
+		},
+		// Check a corner case where skipping overflow loops back to the same header
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64},
+			[]common.Hash{
+				backend.chain.GetBlockByNumber(1).Hash(),
+			},
+		},
+		// Check that non existing headers aren't returned
+		{
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Hash: unknown}, Amount: 1},
+			[]common.Hash{},
+		}, {
+			&GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().NumberU64() + 1}, Amount: 1},
+			[]common.Hash{},
+		},
+	}
+	// Run each of the tests and verify the results against the chain
+	for i, tt := range tests {
+		// Collect the headers to expect in the response
+		var headers []*types.Header
+		for _, hash := range tt.expect {
+			headers = append(headers, backend.chain.GetBlockByHash(hash).Header())
+		}
+		// Send the hash request and verify the response
+		if protocol <= ETH65 {
+			p2p.Send(peer.app, GetBlockHeadersMsg, tt.query)
+			if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil {
+				t.Errorf("test %d: headers mismatch: %v", i, err)
+			}
+		} else {
+			p2p.Send(peer.app, GetBlockHeadersMsg, GetBlockHeadersPacket66{
+				RequestId:             123,
+				GetBlockHeadersPacket: tt.query,
+			})
+			if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, BlockHeadersPacket66{
+				RequestId:          123,
+				BlockHeadersPacket: headers,
+			}); err != nil {
+				t.Errorf("test %d: headers mismatch: %v", i, err)
+			}
+		}
+		// If the test used number origins, repeat with hashes as the too
+		if tt.query.Origin.Hash == (common.Hash{}) {
+			if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
+				tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
+
+				if protocol <= ETH65 {
+					p2p.Send(peer.app, GetBlockHeadersMsg, tt.query)
+					if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil {
+						t.Errorf("test %d: headers mismatch: %v", i, err)
+					}
+				} else {
+					p2p.Send(peer.app, GetBlockHeadersMsg, GetBlockHeadersPacket66{
+						RequestId:             456,
+						GetBlockHeadersPacket: tt.query,
+					})
+					if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, BlockHeadersPacket66{
+						RequestId:          456,
+						BlockHeadersPacket: headers,
+					}); err != nil {
+						t.Errorf("test %d: headers mismatch: %v", i, err)
+					}
+				}
+			}
+		}
+	}
+}
+
+// Tests that block contents can be retrieved from a remote chain based on their hashes.
+func TestGetBlockBodies65(t *testing.T) { testGetBlockBodies(t, ETH65) }
+func TestGetBlockBodies66(t *testing.T) { testGetBlockBodies(t, ETH66) }
+
+func testGetBlockBodies(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	backend := newTestBackend(maxBodiesServe + 15)
+	defer backend.close()
+
+	peer, _ := newTestPeer("peer", protocol, backend)
+	defer peer.close()
+
+	// Create a batch of tests for various scenarios
+	limit := maxBodiesServe
+	tests := []struct {
+		random    int           // Number of blocks to fetch randomly from the chain
+		explicit  []common.Hash // Explicitly requested blocks
+		available []bool        // Availability of explicitly requested blocks
+		expected  int           // Total number of existing blocks to expect
+	}{
+		{1, nil, nil, 1},             // A single random block should be retrievable
+		{10, nil, nil, 10},           // Multiple random blocks should be retrievable
+		{limit, nil, nil, limit},     // The maximum possible blocks should be retrievable
+		{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned
+		{0, []common.Hash{backend.chain.Genesis().Hash()}, []bool{true}, 1},      // The genesis block should be retrievable
+		{0, []common.Hash{backend.chain.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable
+		{0, []common.Hash{{}}, []bool{false}, 0},                                 // A non existent block should not be returned
+
+		// Existing and non-existing blocks interleaved should not cause problems
+		{0, []common.Hash{
+			{},
+			backend.chain.GetBlockByNumber(1).Hash(),
+			{},
+			backend.chain.GetBlockByNumber(10).Hash(),
+			{},
+			backend.chain.GetBlockByNumber(100).Hash(),
+			{},
+		}, []bool{false, true, false, true, false, true, false}, 3},
+	}
+	// Run each of the tests and verify the results against the chain
+	for i, tt := range tests {
+		// Collect the hashes to request, and the response to expectva
+		var (
+			hashes []common.Hash
+			bodies []*BlockBody
+			seen   = make(map[int64]bool)
+		)
+		for j := 0; j < tt.random; j++ {
+			for {
+				num := rand.Int63n(int64(backend.chain.CurrentBlock().NumberU64()))
+				if !seen[num] {
+					seen[num] = true
+
+					block := backend.chain.GetBlockByNumber(uint64(num))
+					hashes = append(hashes, block.Hash())
+					if len(bodies) < tt.expected {
+						bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
+					}
+					break
+				}
+			}
+		}
+		for j, hash := range tt.explicit {
+			hashes = append(hashes, hash)
+			if tt.available[j] && len(bodies) < tt.expected {
+				block := backend.chain.GetBlockByHash(hash)
+				bodies = append(bodies, &BlockBody{Transactions: block.Transactions(), Uncles: block.Uncles()})
+			}
+		}
+		// Send the hash request and verify the response
+		if protocol <= ETH65 {
+			p2p.Send(peer.app, GetBlockBodiesMsg, hashes)
+			if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, bodies); err != nil {
+				t.Errorf("test %d: bodies mismatch: %v", i, err)
+			}
+		} else {
+			p2p.Send(peer.app, GetBlockBodiesMsg, GetBlockBodiesPacket66{
+				RequestId:            123,
+				GetBlockBodiesPacket: hashes,
+			})
+			if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, BlockBodiesPacket66{
+				RequestId:         123,
+				BlockBodiesPacket: bodies,
+			}); err != nil {
+				t.Errorf("test %d: bodies mismatch: %v", i, err)
+			}
+		}
+	}
+}
+
+// Tests that the state trie nodes can be retrieved based on hashes.
+func TestGetNodeData65(t *testing.T) { testGetNodeData(t, ETH65) }
+func TestGetNodeData66(t *testing.T) { testGetNodeData(t, ETH66) }
+
+func testGetNodeData(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Define three accounts to simulate transactions with
+	acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
+	acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
+	acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
+	acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
+
+	signer := types.HomesteadSigner{}
+	// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
+	generator := func(i int, block *core.BlockGen) {
+		switch i {
+		case 0:
+			// In block 1, the test bank sends account #1 some ether.
+			tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey)
+			block.AddTx(tx)
+		case 1:
+			// In block 2, the test bank sends some more ether to account #1.
+			// acc1Addr passes it on to account #2.
+			tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
+			tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
+			block.AddTx(tx1)
+			block.AddTx(tx2)
+		case 2:
+			// Block 3 is empty but was mined by account #2.
+			block.SetCoinbase(acc2Addr)
+			block.SetExtra([]byte("yeehaw"))
+		case 3:
+			// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
+			b2 := block.PrevBlock(1).Header()
+			b2.Extra = []byte("foo")
+			block.AddUncle(b2)
+			b3 := block.PrevBlock(2).Header()
+			b3.Extra = []byte("foo")
+			block.AddUncle(b3)
+		}
+	}
+	// Assemble the test environment
+	backend := newTestBackendWithGenerator(4, generator)
+	defer backend.close()
+
+	peer, _ := newTestPeer("peer", protocol, backend)
+	defer peer.close()
+
+	// Fetch for now the entire chain db
+	var hashes []common.Hash
+
+	it := backend.db.NewIterator(nil, nil)
+	for it.Next() {
+		if key := it.Key(); len(key) == common.HashLength {
+			hashes = append(hashes, common.BytesToHash(key))
+		}
+	}
+	it.Release()
+
+	if protocol <= ETH65 {
+		p2p.Send(peer.app, GetNodeDataMsg, hashes)
+	} else {
+		p2p.Send(peer.app, GetNodeDataMsg, GetNodeDataPacket66{
+			RequestId:         123,
+			GetNodeDataPacket: hashes,
+		})
+	}
+	msg, err := peer.app.ReadMsg()
+	if err != nil {
+		t.Fatalf("failed to read node data response: %v", err)
+	}
+	if msg.Code != NodeDataMsg {
+		t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg)
+	}
+	var data [][]byte
+	if protocol <= ETH65 {
+		if err := msg.Decode(&data); err != nil {
+			t.Fatalf("failed to decode response node data: %v", err)
+		}
+	} else {
+		var res NodeDataPacket66
+		if err := msg.Decode(&res); err != nil {
+			t.Fatalf("failed to decode response node data: %v", err)
+		}
+		data = res.NodeDataPacket
+	}
+	// Verify that all hashes correspond to the requested data, and reconstruct a state tree
+	for i, want := range hashes {
+		if hash := crypto.Keccak256Hash(data[i]); hash != want {
+			t.Errorf("data hash mismatch: have %x, want %x", hash, want)
+		}
+	}
+	statedb := rawdb.NewMemoryDatabase()
+	for i := 0; i < len(data); i++ {
+		statedb.Put(hashes[i].Bytes(), data[i])
+	}
+	accounts := []common.Address{testAddr, acc1Addr, acc2Addr}
+	for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ {
+		trie, _ := state.New(backend.chain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil)
+
+		for j, acc := range accounts {
+			state, _ := backend.chain.State()
+			bw := state.GetBalance(acc)
+			bh := trie.GetBalance(acc)
+
+			if (bw != nil && bh == nil) || (bw == nil && bh != nil) {
+				t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
+			}
+			if bw != nil && bh != nil && bw.Cmp(bw) != 0 {
+				t.Errorf("test %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw)
+			}
+		}
+	}
+}
+
+// Tests that the transaction receipts can be retrieved based on hashes.
+func TestGetBlockReceipts65(t *testing.T) { testGetBlockReceipts(t, ETH65) }
+func TestGetBlockReceipts66(t *testing.T) { testGetBlockReceipts(t, ETH66) }
+
+func testGetBlockReceipts(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Define three accounts to simulate transactions with
+	acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
+	acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
+	acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey)
+	acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey)
+
+	signer := types.HomesteadSigner{}
+	// Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test)
+	generator := func(i int, block *core.BlockGen) {
+		switch i {
+		case 0:
+			// In block 1, the test bank sends account #1 some ether.
+			tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testKey)
+			block.AddTx(tx)
+		case 1:
+			// In block 2, the test bank sends some more ether to account #1.
+			// acc1Addr passes it on to account #2.
+			tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
+			tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, acc1Key)
+			block.AddTx(tx1)
+			block.AddTx(tx2)
+		case 2:
+			// Block 3 is empty but was mined by account #2.
+			block.SetCoinbase(acc2Addr)
+			block.SetExtra([]byte("yeehaw"))
+		case 3:
+			// Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data).
+			b2 := block.PrevBlock(1).Header()
+			b2.Extra = []byte("foo")
+			block.AddUncle(b2)
+			b3 := block.PrevBlock(2).Header()
+			b3.Extra = []byte("foo")
+			block.AddUncle(b3)
+		}
+	}
+	// Assemble the test environment
+	backend := newTestBackendWithGenerator(4, generator)
+	defer backend.close()
+
+	peer, _ := newTestPeer("peer", protocol, backend)
+	defer peer.close()
+
+	// Collect the hashes to request, and the response to expect
+	var (
+		hashes   []common.Hash
+		receipts [][]*types.Receipt
+	)
+	for i := uint64(0); i <= backend.chain.CurrentBlock().NumberU64(); i++ {
+		block := backend.chain.GetBlockByNumber(i)
+
+		hashes = append(hashes, block.Hash())
+		receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash()))
+	}
+	// Send the hash request and verify the response
+	if protocol <= ETH65 {
+		p2p.Send(peer.app, GetReceiptsMsg, hashes)
+		if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, receipts); err != nil {
+			t.Errorf("receipts mismatch: %v", err)
+		}
+	} else {
+		p2p.Send(peer.app, GetReceiptsMsg, GetReceiptsPacket66{
+			RequestId:         123,
+			GetReceiptsPacket: hashes,
+		})
+		if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, ReceiptsPacket66{
+			RequestId:      123,
+			ReceiptsPacket: receipts,
+		}); err != nil {
+			t.Errorf("receipts mismatch: %v", err)
+		}
+	}
+}
diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go
new file mode 100644
index 0000000000..d7d993a23d
--- /dev/null
+++ b/eth/protocols/eth/handlers.go
@@ -0,0 +1,520 @@
+// 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 .
+
+package eth
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+// handleGetBlockHeaders handles Block header query, collect the requested headers and reply
+func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the complex header query
+	var query GetBlockHeadersPacket
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetBlockHeadersQuery(backend, &query, peer)
+	return peer.SendBlockHeaders(response)
+}
+
+// handleGetBlockHeaders66 is the eth/66 version of handleGetBlockHeaders
+func handleGetBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the complex header query
+	var query GetBlockHeadersPacket66
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetBlockHeadersQuery(backend, query.GetBlockHeadersPacket, peer)
+	return peer.ReplyBlockHeaders(query.RequestId, response)
+}
+
+func answerGetBlockHeadersQuery(backend Backend, query *GetBlockHeadersPacket, peer *Peer) []*types.Header {
+	hashMode := query.Origin.Hash != (common.Hash{})
+	first := true
+	maxNonCanonical := uint64(100)
+
+	// Gather headers until the fetch or network limits is reached
+	var (
+		bytes   common.StorageSize
+		headers []*types.Header
+		unknown bool
+		lookups int
+	)
+	for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit &&
+		len(headers) < maxHeadersServe && lookups < 2*maxHeadersServe {
+		lookups++
+		// Retrieve the next header satisfying the query
+		var origin *types.Header
+		if hashMode {
+			if first {
+				first = false
+				origin = backend.Chain().GetHeaderByHash(query.Origin.Hash)
+				if origin != nil {
+					query.Origin.Number = origin.Number.Uint64()
+				}
+			} else {
+				origin = backend.Chain().GetHeader(query.Origin.Hash, query.Origin.Number)
+			}
+		} else {
+			origin = backend.Chain().GetHeaderByNumber(query.Origin.Number)
+		}
+		if origin == nil {
+			break
+		}
+		headers = append(headers, origin)
+		bytes += estHeaderSize
+
+		// Advance to the next header of the query
+		switch {
+		case hashMode && query.Reverse:
+			// Hash based traversal towards the genesis block
+			ancestor := query.Skip + 1
+			if ancestor == 0 {
+				unknown = true
+			} else {
+				query.Origin.Hash, query.Origin.Number = backend.Chain().GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
+				unknown = (query.Origin.Hash == common.Hash{})
+			}
+		case hashMode && !query.Reverse:
+			// Hash based traversal towards the leaf block
+			var (
+				current = origin.Number.Uint64()
+				next    = current + query.Skip + 1
+			)
+			if next <= current {
+				infos, _ := json.MarshalIndent(peer.Peer.Info(), "", "  ")
+				peer.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
+				unknown = true
+			} else {
+				if header := backend.Chain().GetHeaderByNumber(next); header != nil {
+					nextHash := header.Hash()
+					expOldHash, _ := backend.Chain().GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
+					if expOldHash == query.Origin.Hash {
+						query.Origin.Hash, query.Origin.Number = nextHash, next
+					} else {
+						unknown = true
+					}
+				} else {
+					unknown = true
+				}
+			}
+		case query.Reverse:
+			// Number based traversal towards the genesis block
+			if query.Origin.Number >= query.Skip+1 {
+				query.Origin.Number -= query.Skip + 1
+			} else {
+				unknown = true
+			}
+
+		case !query.Reverse:
+			// Number based traversal towards the leaf block
+			query.Origin.Number += query.Skip + 1
+		}
+	}
+	return headers
+}
+
+func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the block body retrieval message
+	var query GetBlockBodiesPacket
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetBlockBodiesQuery(backend, query, peer)
+	return peer.SendBlockBodiesRLP(response)
+}
+
+func handleGetBlockBodies66(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the block body retrieval message
+	var query GetBlockBodiesPacket66
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetBlockBodiesQuery(backend, query.GetBlockBodiesPacket, peer)
+	return peer.ReplyBlockBodiesRLP(query.RequestId, response)
+}
+
+func answerGetBlockBodiesQuery(backend Backend, query GetBlockBodiesPacket, peer *Peer) []rlp.RawValue {
+	// Gather blocks until the fetch or network limits is reached
+	var (
+		bytes  int
+		bodies []rlp.RawValue
+	)
+	for lookups, hash := range query {
+		if bytes >= softResponseLimit || len(bodies) >= maxBodiesServe ||
+			lookups >= 2*maxBodiesServe {
+			break
+		}
+		if data := backend.Chain().GetBodyRLP(hash); len(data) != 0 {
+			bodies = append(bodies, data)
+			bytes += len(data)
+		}
+	}
+	return bodies
+}
+
+func handleGetNodeData(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the trie node data retrieval message
+	var query GetNodeDataPacket
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetNodeDataQuery(backend, query, peer)
+	return peer.SendNodeData(response)
+}
+
+func handleGetNodeData66(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the trie node data retrieval message
+	var query GetNodeDataPacket66
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetNodeDataQuery(backend, query.GetNodeDataPacket, peer)
+	return peer.ReplyNodeData(query.RequestId, response)
+}
+
+func answerGetNodeDataQuery(backend Backend, query GetNodeDataPacket, peer *Peer) [][]byte {
+	// Gather state data until the fetch or network limits is reached
+	var (
+		bytes int
+		nodes [][]byte
+	)
+	for lookups, hash := range query {
+		if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe ||
+			lookups >= 2*maxNodeDataServe {
+			break
+		}
+		// Retrieve the requested state entry
+		if bloom := backend.StateBloom(); bloom != nil && !bloom.Contains(hash[:]) {
+			// Only lookup the trie node if there's chance that we actually have it
+			continue
+		}
+		entry, err := backend.Chain().TrieNode(hash)
+		if len(entry) == 0 || err != nil {
+			// Read the contract code with prefix only to save unnecessary lookups.
+			entry, err = backend.Chain().ContractCodeWithPrefix(hash)
+		}
+		if err == nil && len(entry) > 0 {
+			nodes = append(nodes, entry)
+			bytes += len(entry)
+		}
+	}
+	return nodes
+}
+
+func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the block receipts retrieval message
+	var query GetReceiptsPacket
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetReceiptsQuery(backend, query, peer)
+	return peer.SendReceiptsRLP(response)
+}
+
+func handleGetReceipts66(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the block receipts retrieval message
+	var query GetReceiptsPacket66
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	response := answerGetReceiptsQuery(backend, query.GetReceiptsPacket, peer)
+	return peer.ReplyReceiptsRLP(query.RequestId, response)
+}
+
+func answerGetReceiptsQuery(backend Backend, query GetReceiptsPacket, peer *Peer) []rlp.RawValue {
+	// Gather state data until the fetch or network limits is reached
+	var (
+		bytes    int
+		receipts []rlp.RawValue
+	)
+	for lookups, hash := range query {
+		if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe ||
+			lookups >= 2*maxReceiptsServe {
+			break
+		}
+		// Retrieve the requested block's receipts
+		results := backend.Chain().GetReceiptsByHash(hash)
+		if results == nil {
+			if header := backend.Chain().GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
+				continue
+			}
+		}
+		// If known, encode and queue for response packet
+		if encoded, err := rlp.EncodeToBytes(results); err != nil {
+			log.Error("Failed to encode receipt", "err", err)
+		} else {
+			receipts = append(receipts, encoded)
+			bytes += len(encoded)
+		}
+	}
+	return receipts
+}
+
+func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of new block announcements just arrived
+	ann := new(NewBlockHashesPacket)
+	if err := msg.Decode(ann); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	// Mark the hashes as present at the remote node
+	for _, block := range *ann {
+		peer.markBlock(block.Hash)
+	}
+	// Deliver them all to the backend for queuing
+	return backend.Handle(peer, ann)
+}
+
+func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
+	// Retrieve and decode the propagated block
+	ann := new(NewBlockPacket)
+	if err := msg.Decode(ann); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	if err := ann.sanityCheck(); err != nil {
+		return err
+	}
+	if hash := types.CalcUncleHash(ann.Block.Uncles()); hash != ann.Block.UncleHash() {
+		log.Warn("Propagated block has invalid uncles", "have", hash, "exp", ann.Block.UncleHash())
+		return nil // TODO(karalabe): return error eventually, but wait a few releases
+	}
+	if hash := types.DeriveSha(ann.Block.Transactions(), trie.NewStackTrie(nil)); hash != ann.Block.TxHash() {
+		log.Warn("Propagated block has invalid body", "have", hash, "exp", ann.Block.TxHash())
+		return nil // TODO(karalabe): return error eventually, but wait a few releases
+	}
+	ann.Block.ReceivedAt = msg.Time()
+	ann.Block.ReceivedFrom = peer
+
+	// Mark the peer as owning the block
+	peer.markBlock(ann.Block.Hash())
+
+	return backend.Handle(peer, ann)
+}
+
+func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of headers arrived to one of our previous requests
+	res := new(BlockHeadersPacket)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	return backend.Handle(peer, res)
+}
+
+func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of headers arrived to one of our previous requests
+	res := new(BlockHeadersPacket66)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	requestTracker.Fulfil(peer.id, peer.version, BlockHeadersMsg, res.RequestId)
+
+	return backend.Handle(peer, &res.BlockHeadersPacket)
+}
+
+func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of block bodies arrived to one of our previous requests
+	res := new(BlockBodiesPacket)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	return backend.Handle(peer, res)
+}
+
+func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of block bodies arrived to one of our previous requests
+	res := new(BlockBodiesPacket66)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	requestTracker.Fulfil(peer.id, peer.version, BlockBodiesMsg, res.RequestId)
+
+	return backend.Handle(peer, &res.BlockBodiesPacket)
+}
+
+func handleNodeData(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of node state data arrived to one of our previous requests
+	res := new(NodeDataPacket)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	return backend.Handle(peer, res)
+}
+
+func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of node state data arrived to one of our previous requests
+	res := new(NodeDataPacket66)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	requestTracker.Fulfil(peer.id, peer.version, NodeDataMsg, res.RequestId)
+
+	return backend.Handle(peer, &res.NodeDataPacket)
+}
+
+func handleReceipts(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of receipts arrived to one of our previous requests
+	res := new(ReceiptsPacket)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	return backend.Handle(peer, res)
+}
+
+func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error {
+	// A batch of receipts arrived to one of our previous requests
+	res := new(ReceiptsPacket66)
+	if err := msg.Decode(res); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	requestTracker.Fulfil(peer.id, peer.version, ReceiptsMsg, res.RequestId)
+
+	return backend.Handle(peer, &res.ReceiptsPacket)
+}
+
+func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error {
+	// New transaction announcement arrived, make sure we have
+	// a valid and fresh chain to handle them
+	if !backend.AcceptTxs() {
+		return nil
+	}
+	ann := new(NewPooledTransactionHashesPacket)
+	if err := msg.Decode(ann); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	// Schedule all the unknown hashes for retrieval
+	for _, hash := range *ann {
+		peer.markTransaction(hash)
+	}
+	return backend.Handle(peer, ann)
+}
+
+func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the pooled transactions retrieval message
+	var query GetPooledTransactionsPacket
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	hashes, txs := answerGetPooledTransactions(backend, query, peer)
+	return peer.SendPooledTransactionsRLP(hashes, txs)
+}
+
+func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) error {
+	// Decode the pooled transactions retrieval message
+	var query GetPooledTransactionsPacket66
+	if err := msg.Decode(&query); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsPacket, peer)
+	return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs)
+}
+
+func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPacket, peer *Peer) ([]common.Hash, []rlp.RawValue) {
+	// Gather transactions until the fetch or network limits is reached
+	var (
+		bytes  int
+		hashes []common.Hash
+		txs    []rlp.RawValue
+	)
+	for _, hash := range query {
+		if bytes >= softResponseLimit {
+			break
+		}
+		// Retrieve the requested transaction, skipping if unknown to us
+		tx := backend.TxPool().Get(hash)
+		if tx == nil {
+			continue
+		}
+		// If known, encode and queue for response packet
+		if encoded, err := rlp.EncodeToBytes(tx); err != nil {
+			log.Error("Failed to encode transaction", "err", err)
+		} else {
+			hashes = append(hashes, hash)
+			txs = append(txs, encoded)
+			bytes += len(encoded)
+		}
+	}
+	return hashes, txs
+}
+
+func handleTransactions(backend Backend, msg Decoder, peer *Peer) error {
+	// Transactions arrived, make sure we have a valid and fresh chain to handle them
+	if !backend.AcceptTxs() {
+		return nil
+	}
+	// Transactions can be processed, parse all of them and deliver to the pool
+	var txs TransactionsPacket
+	if err := msg.Decode(&txs); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	for i, tx := range txs {
+		// Validate and mark the remote transaction
+		if tx == nil {
+			return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
+		}
+		peer.markTransaction(tx.Hash())
+	}
+	return backend.Handle(peer, &txs)
+}
+
+func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error {
+	// Transactions arrived, make sure we have a valid and fresh chain to handle them
+	if !backend.AcceptTxs() {
+		return nil
+	}
+	// Transactions can be processed, parse all of them and deliver to the pool
+	var txs PooledTransactionsPacket
+	if err := msg.Decode(&txs); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	for i, tx := range txs {
+		// Validate and mark the remote transaction
+		if tx == nil {
+			return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
+		}
+		peer.markTransaction(tx.Hash())
+	}
+	return backend.Handle(peer, &txs)
+}
+
+func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error {
+	// Transactions arrived, make sure we have a valid and fresh chain to handle them
+	if !backend.AcceptTxs() {
+		return nil
+	}
+	// Transactions can be processed, parse all of them and deliver to the pool
+	var txs PooledTransactionsPacket66
+	if err := msg.Decode(&txs); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	for i, tx := range txs.PooledTransactionsPacket {
+		// Validate and mark the remote transaction
+		if tx == nil {
+			return fmt.Errorf("%w: transaction %d is nil", errDecode, i)
+		}
+		peer.markTransaction(tx.Hash())
+	}
+	requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId)
+
+	return backend.Handle(peer, &txs.PooledTransactionsPacket)
+}
diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go
new file mode 100644
index 0000000000..57a4e0bc34
--- /dev/null
+++ b/eth/protocols/eth/handshake.go
@@ -0,0 +1,107 @@
+// Copyright 2015 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 .
+
+package eth
+
+import (
+	"fmt"
+	"math/big"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/p2p"
+)
+
+const (
+	// handshakeTimeout is the maximum allowed time for the `eth` handshake to
+	// complete before dropping the connection.= as malicious.
+	handshakeTimeout = 5 * time.Second
+)
+
+// Handshake executes the eth protocol handshake, negotiating version number,
+// network IDs, difficulties, head and genesis blocks.
+func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter) error {
+	// Send out own handshake in a new thread
+	errc := make(chan error, 2)
+
+	var status StatusPacket // safe to read after two values have been received from errc
+
+	go func() {
+		errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{
+			ProtocolVersion: uint32(p.version),
+			NetworkID:       network,
+			TD:              td,
+			Head:            head,
+			Genesis:         genesis,
+			ForkID:          forkID,
+		})
+	}()
+	go func() {
+		errc <- p.readStatus(network, &status, genesis, forkFilter)
+	}()
+	timeout := time.NewTimer(handshakeTimeout)
+	defer timeout.Stop()
+	for i := 0; i < 2; i++ {
+		select {
+		case err := <-errc:
+			if err != nil {
+				return err
+			}
+		case <-timeout.C:
+			return p2p.DiscReadTimeout
+		}
+	}
+	p.td, p.head = status.TD, status.Head
+
+	// TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
+	// larger, it will still fit within 100 bits
+	if tdlen := p.td.BitLen(); tdlen > 100 {
+		return fmt.Errorf("too large total difficulty: bitlen %d", tdlen)
+	}
+	return nil
+}
+
+// readStatus reads the remote handshake message.
+func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.Hash, forkFilter forkid.Filter) error {
+	msg, err := p.rw.ReadMsg()
+	if err != nil {
+		return err
+	}
+	if msg.Code != StatusMsg {
+		return fmt.Errorf("%w: first msg has code %x (!= %x)", errNoStatusMsg, msg.Code, StatusMsg)
+	}
+	if msg.Size > maxMessageSize {
+		return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
+	}
+	// Decode the handshake and make sure everything matches
+	if err := msg.Decode(&status); err != nil {
+		return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+	}
+	if status.NetworkID != network {
+		return fmt.Errorf("%w: %d (!= %d)", errNetworkIDMismatch, status.NetworkID, network)
+	}
+	if uint(status.ProtocolVersion) != p.version {
+		return fmt.Errorf("%w: %d (!= %d)", errProtocolVersionMismatch, status.ProtocolVersion, p.version)
+	}
+	if status.Genesis != genesis {
+		return fmt.Errorf("%w: %x (!= %x)", errGenesisMismatch, status.Genesis, genesis)
+	}
+	if err := forkFilter(status.ForkID); err != nil {
+		return fmt.Errorf("%w: %v", errForkIDRejected, err)
+	}
+	return nil
+}
diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go
new file mode 100644
index 0000000000..3bebda2dcc
--- /dev/null
+++ b/eth/protocols/eth/handshake_test.go
@@ -0,0 +1,91 @@
+// Copyright 2014 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 .
+
+package eth
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+// Tests that handshake failures are detected and reported correctly.
+func TestHandshake65(t *testing.T) { testHandshake(t, ETH65) }
+func TestHandshake66(t *testing.T) { testHandshake(t, ETH66) }
+
+func testHandshake(t *testing.T, protocol uint) {
+	t.Parallel()
+
+	// Create a test backend only to have some valid genesis chain
+	backend := newTestBackend(3)
+	defer backend.close()
+
+	var (
+		genesis = backend.chain.Genesis()
+		head    = backend.chain.CurrentBlock()
+		td      = backend.chain.GetTd(head.Hash(), head.NumberU64())
+		forkID  = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64())
+	)
+	tests := []struct {
+		code uint64
+		data interface{}
+		want error
+	}{
+		{
+			code: TransactionsMsg, data: []interface{}{},
+			want: errNoStatusMsg,
+		},
+		{
+			code: StatusMsg, data: StatusPacket{10, 1, td, head.Hash(), genesis.Hash(), forkID},
+			want: errProtocolVersionMismatch,
+		},
+		{
+			code: StatusMsg, data: StatusPacket{uint32(protocol), 999, td, head.Hash(), genesis.Hash(), forkID},
+			want: errNetworkIDMismatch,
+		},
+		{
+			code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), common.Hash{3}, forkID},
+			want: errGenesisMismatch,
+		},
+		{
+			code: StatusMsg, data: StatusPacket{uint32(protocol), 1, td, head.Hash(), genesis.Hash(), forkid.ID{Hash: [4]byte{0x00, 0x01, 0x02, 0x03}}},
+			want: errForkIDRejected,
+		},
+	}
+	for i, test := range tests {
+		// Create the two peers to shake with each other
+		app, net := p2p.MsgPipe()
+		defer app.Close()
+		defer net.Close()
+
+		peer := NewPeer(protocol, p2p.NewPeer(enode.ID{}, "peer", nil), net, nil)
+		defer peer.Close()
+
+		// Send the junk test with one peer, check the handshake failure
+		go p2p.Send(app, test.code, test.data)
+
+		err := peer.Handshake(1, td, head.Hash(), genesis.Hash(), forkID, forkid.NewFilter(backend.chain))
+		if err == nil {
+			t.Errorf("test %d: protocol returned nil error, want %q", i, test.want)
+		} else if !errors.Is(err, test.want) {
+			t.Errorf("test %d: wrong error: got %q, want %q", i, err, test.want)
+		}
+	}
+}
diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go
new file mode 100644
index 0000000000..e619c183ba
--- /dev/null
+++ b/eth/protocols/eth/peer.go
@@ -0,0 +1,543 @@
+// 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 .
+
+package eth
+
+import (
+	"math/big"
+	"math/rand"
+	"sync"
+
+	mapset "github.com/deckarep/golang-set"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+const (
+	// maxKnownTxs is the maximum transactions hashes to keep in the known list
+	// before starting to randomly evict them.
+	maxKnownTxs = 32768
+
+	// maxKnownBlocks is the maximum block hashes to keep in the known list
+	// before starting to randomly evict them.
+	maxKnownBlocks = 1024
+
+	// maxQueuedTxs is the maximum number of transactions to queue up before dropping
+	// older broadcasts.
+	maxQueuedTxs = 4096
+
+	// maxQueuedTxAnns is the maximum number of transaction announcements to queue up
+	// before dropping older announcements.
+	maxQueuedTxAnns = 4096
+
+	// maxQueuedBlocks is the maximum number of block propagations to queue up before
+	// dropping broadcasts. There's not much point in queueing stale blocks, so a few
+	// that might cover uncles should be enough.
+	maxQueuedBlocks = 4
+
+	// maxQueuedBlockAnns is the maximum number of block announcements to queue up before
+	// dropping broadcasts. Similarly to block propagations, there's no point to queue
+	// above some healthy uncle limit, so use that.
+	maxQueuedBlockAnns = 4
+)
+
+// max is a helper function which returns the larger of the two given integers.
+func max(a, b int) int {
+	if a > b {
+		return a
+	}
+	return b
+}
+
+// Peer is a collection of relevant information we have about a `eth` peer.
+type Peer struct {
+	id string // Unique ID for the peer, cached
+
+	*p2p.Peer                   // The embedded P2P package peer
+	rw        p2p.MsgReadWriter // Input/output streams for snap
+	version   uint              // Protocol version negotiated
+
+	head common.Hash // Latest advertised head block hash
+	td   *big.Int    // Latest advertised head block total difficulty
+
+	knownBlocks     mapset.Set             // Set of block hashes known to be known by this peer
+	queuedBlocks    chan *blockPropagation // Queue of blocks to broadcast to the peer
+	queuedBlockAnns chan *types.Block      // Queue of blocks to announce to the peer
+
+	txpool      TxPool             // Transaction pool used by the broadcasters for liveness checks
+	knownTxs    mapset.Set         // Set of transaction hashes known to be known by this peer
+	txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests
+	txAnnounce  chan []common.Hash // Channel used to queue transaction announcement requests
+
+	term chan struct{} // Termination channel to stop the broadcasters
+	lock sync.RWMutex  // Mutex protecting the internal fields
+}
+
+// NewPeer create a wrapper for a network connection and negotiated  protocol
+// version.
+func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool) *Peer {
+	peer := &Peer{
+		id:              p.ID().String(),
+		Peer:            p,
+		rw:              rw,
+		version:         version,
+		knownTxs:        mapset.NewSet(),
+		knownBlocks:     mapset.NewSet(),
+		queuedBlocks:    make(chan *blockPropagation, maxQueuedBlocks),
+		queuedBlockAnns: make(chan *types.Block, maxQueuedBlockAnns),
+		txBroadcast:     make(chan []common.Hash),
+		txAnnounce:      make(chan []common.Hash),
+		txpool:          txpool,
+		term:            make(chan struct{}),
+	}
+	// Start up all the broadcasters
+	go peer.broadcastBlocks()
+	go peer.broadcastTransactions()
+	if version >= ETH65 {
+		go peer.announceTransactions()
+	}
+	return peer
+}
+
+// Close signals the broadcast goroutine to terminate. Only ever call this if
+// you created the peer yourself via NewPeer. Otherwise let whoever created it
+// clean it up!
+func (p *Peer) Close() {
+	close(p.term)
+}
+
+// ID retrieves the peer's unique identifier.
+func (p *Peer) ID() string {
+	return p.id
+}
+
+// Version retrieves the peer's negoatiated `eth` protocol version.
+func (p *Peer) Version() uint {
+	return p.version
+}
+
+// Head retrieves the current head hash and total difficulty of the peer.
+func (p *Peer) Head() (hash common.Hash, td *big.Int) {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	copy(hash[:], p.head[:])
+	return hash, new(big.Int).Set(p.td)
+}
+
+// SetHead updates the head hash and total difficulty of the peer.
+func (p *Peer) SetHead(hash common.Hash, td *big.Int) {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	copy(p.head[:], hash[:])
+	p.td.Set(td)
+}
+
+// KnownBlock returns whether peer is known to already have a block.
+func (p *Peer) KnownBlock(hash common.Hash) bool {
+	return p.knownBlocks.Contains(hash)
+}
+
+// KnownTransaction returns whether peer is known to already have a transaction.
+func (p *Peer) KnownTransaction(hash common.Hash) bool {
+	return p.knownTxs.Contains(hash)
+}
+
+// markBlock marks a block as known for the peer, ensuring that the block will
+// never be propagated to this particular peer.
+func (p *Peer) markBlock(hash common.Hash) {
+	// If we reached the memory allowance, drop a previously known block hash
+	for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+		p.knownBlocks.Pop()
+	}
+	p.knownBlocks.Add(hash)
+}
+
+// markTransaction marks a transaction as known for the peer, ensuring that it
+// will never be propagated to this particular peer.
+func (p *Peer) markTransaction(hash common.Hash) {
+	// If we reached the memory allowance, drop a previously known transaction hash
+	for p.knownTxs.Cardinality() >= maxKnownTxs {
+		p.knownTxs.Pop()
+	}
+	p.knownTxs.Add(hash)
+}
+
+// SendTransactions sends transactions to the peer and includes the hashes
+// in its transaction hash set for future reference.
+//
+// This method is a helper used by the async transaction sender. Don't call it
+// directly as the queueing (memory) and transmission (bandwidth) costs should
+// not be managed directly.
+//
+// The reasons this is public is to allow packages using this protocol to write
+// tests that directly send messages without having to do the asyn queueing.
+func (p *Peer) SendTransactions(txs types.Transactions) error {
+	// Mark all the transactions as known, but ensure we don't overflow our limits
+	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(txs)) {
+		p.knownTxs.Pop()
+	}
+	for _, tx := range txs {
+		p.knownTxs.Add(tx.Hash())
+	}
+	return p2p.Send(p.rw, TransactionsMsg, txs)
+}
+
+// AsyncSendTransactions queues a list of transactions (by hash) to eventually
+// propagate to a remote peer. The number of pending sends are capped (new ones
+// will force old sends to be dropped)
+func (p *Peer) AsyncSendTransactions(hashes []common.Hash) {
+	select {
+	case p.txBroadcast <- hashes:
+		// Mark all the transactions as known, but ensure we don't overflow our limits
+		for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
+			p.knownTxs.Pop()
+		}
+		for _, hash := range hashes {
+			p.knownTxs.Add(hash)
+		}
+	case <-p.term:
+		p.Log().Debug("Dropping transaction propagation", "count", len(hashes))
+	}
+}
+
+// sendPooledTransactionHashes sends transaction hashes to the peer and includes
+// them in its transaction hash set for future reference.
+//
+// This method is a helper used by the async transaction announcer. Don't call it
+// directly as the queueing (memory) and transmission (bandwidth) costs should
+// not be managed directly.
+func (p *Peer) sendPooledTransactionHashes(hashes []common.Hash) error {
+	// Mark all the transactions as known, but ensure we don't overflow our limits
+	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
+		p.knownTxs.Pop()
+	}
+	for _, hash := range hashes {
+		p.knownTxs.Add(hash)
+	}
+	return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket(hashes))
+}
+
+// AsyncSendPooledTransactionHashes queues a list of transactions hashes to eventually
+// announce to a remote peer.  The number of pending sends are capped (new ones
+// will force old sends to be dropped)
+func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) {
+	select {
+	case p.txAnnounce <- hashes:
+		// Mark all the transactions as known, but ensure we don't overflow our limits
+		for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
+			p.knownTxs.Pop()
+		}
+		for _, hash := range hashes {
+			p.knownTxs.Add(hash)
+		}
+	case <-p.term:
+		p.Log().Debug("Dropping transaction announcement", "count", len(hashes))
+	}
+}
+
+// SendPooledTransactionsRLP sends requested transactions to the peer and adds the
+// hashes in its transaction hash set for future reference.
+//
+// Note, the method assumes the hashes are correct and correspond to the list of
+// transactions being sent.
+func (p *Peer) SendPooledTransactionsRLP(hashes []common.Hash, txs []rlp.RawValue) error {
+	// Mark all the transactions as known, but ensure we don't overflow our limits
+	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
+		p.knownTxs.Pop()
+	}
+	for _, hash := range hashes {
+		p.knownTxs.Add(hash)
+	}
+	return p2p.Send(p.rw, PooledTransactionsMsg, txs) // Not packed into PooledTransactionsPacket to avoid RLP decoding
+}
+
+// ReplyPooledTransactionsRLP is the eth/66 version of SendPooledTransactionsRLP.
+func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs []rlp.RawValue) error {
+	// Mark all the transactions as known, but ensure we don't overflow our limits
+	for p.knownTxs.Cardinality() > max(0, maxKnownTxs-len(hashes)) {
+		p.knownTxs.Pop()
+	}
+	for _, hash := range hashes {
+		p.knownTxs.Add(hash)
+	}
+	// Not packed into PooledTransactionsPacket to avoid RLP decoding
+	return p2p.Send(p.rw, PooledTransactionsMsg, PooledTransactionsRLPPacket66{
+		RequestId:                   id,
+		PooledTransactionsRLPPacket: txs,
+	})
+}
+
+// SendNewBlockHashes announces the availability of a number of blocks through
+// a hash notification.
+func (p *Peer) SendNewBlockHashes(hashes []common.Hash, numbers []uint64) error {
+	// Mark all the block hashes as known, but ensure we don't overflow our limits
+	for p.knownBlocks.Cardinality() > max(0, maxKnownBlocks-len(hashes)) {
+		p.knownBlocks.Pop()
+	}
+	for _, hash := range hashes {
+		p.knownBlocks.Add(hash)
+	}
+	request := make(NewBlockHashesPacket, len(hashes))
+	for i := 0; i < len(hashes); i++ {
+		request[i].Hash = hashes[i]
+		request[i].Number = numbers[i]
+	}
+	return p2p.Send(p.rw, NewBlockHashesMsg, request)
+}
+
+// AsyncSendNewBlockHash queues the availability of a block for propagation to a
+// remote peer. If the peer's broadcast queue is full, the event is silently
+// dropped.
+func (p *Peer) AsyncSendNewBlockHash(block *types.Block) {
+	select {
+	case p.queuedBlockAnns <- block:
+		// Mark all the block hash as known, but ensure we don't overflow our limits
+		for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+			p.knownBlocks.Pop()
+		}
+		p.knownBlocks.Add(block.Hash())
+	default:
+		p.Log().Debug("Dropping block announcement", "number", block.NumberU64(), "hash", block.Hash())
+	}
+}
+
+// SendNewBlock propagates an entire block to a remote peer.
+func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error {
+	// Mark all the block hash as known, but ensure we don't overflow our limits
+	for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+		p.knownBlocks.Pop()
+	}
+	p.knownBlocks.Add(block.Hash())
+	return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{
+		Block: block,
+		TD:    td,
+	})
+}
+
+// AsyncSendNewBlock queues an entire block for propagation to a remote peer. If
+// the peer's broadcast queue is full, the event is silently dropped.
+func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
+	select {
+	case p.queuedBlocks <- &blockPropagation{block: block, td: td}:
+		// Mark all the block hash as known, but ensure we don't overflow our limits
+		for p.knownBlocks.Cardinality() >= maxKnownBlocks {
+			p.knownBlocks.Pop()
+		}
+		p.knownBlocks.Add(block.Hash())
+	default:
+		p.Log().Debug("Dropping block propagation", "number", block.NumberU64(), "hash", block.Hash())
+	}
+}
+
+// SendBlockHeaders sends a batch of block headers to the remote peer.
+func (p *Peer) SendBlockHeaders(headers []*types.Header) error {
+	return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket(headers))
+}
+
+// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders.
+func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error {
+	return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{
+		RequestId:          id,
+		BlockHeadersPacket: headers,
+	})
+}
+
+// SendBlockBodiesRLP sends a batch of block contents to the remote peer from
+// an already RLP encoded format.
+func (p *Peer) SendBlockBodiesRLP(bodies []rlp.RawValue) error {
+	return p2p.Send(p.rw, BlockBodiesMsg, bodies) // Not packed into BlockBodiesPacket to avoid RLP decoding
+}
+
+// ReplyBlockBodiesRLP is the eth/66 version of SendBlockBodiesRLP.
+func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error {
+	// Not packed into BlockBodiesPacket to avoid RLP decoding
+	return p2p.Send(p.rw, BlockBodiesMsg, BlockBodiesRLPPacket66{
+		RequestId:            id,
+		BlockBodiesRLPPacket: bodies,
+	})
+}
+
+// SendNodeDataRLP sends a batch of arbitrary internal data, corresponding to the
+// hashes requested.
+func (p *Peer) SendNodeData(data [][]byte) error {
+	return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket(data))
+}
+
+// ReplyNodeData is the eth/66 response to GetNodeData.
+func (p *Peer) ReplyNodeData(id uint64, data [][]byte) error {
+	return p2p.Send(p.rw, NodeDataMsg, NodeDataPacket66{
+		RequestId:      id,
+		NodeDataPacket: data,
+	})
+}
+
+// SendReceiptsRLP sends a batch of transaction receipts, corresponding to the
+// ones requested from an already RLP encoded format.
+func (p *Peer) SendReceiptsRLP(receipts []rlp.RawValue) error {
+	return p2p.Send(p.rw, ReceiptsMsg, receipts) // Not packed into ReceiptsPacket to avoid RLP decoding
+}
+
+// ReplyReceiptsRLP is the eth/66 response to GetReceipts.
+func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error {
+	return p2p.Send(p.rw, ReceiptsMsg, ReceiptsRLPPacket66{
+		RequestId:         id,
+		ReceiptsRLPPacket: receipts,
+	})
+}
+
+// RequestOneHeader is a wrapper around the header query functions to fetch a
+// single header. It is used solely by the fetcher.
+func (p *Peer) RequestOneHeader(hash common.Hash) error {
+	p.Log().Debug("Fetching single header", "hash", hash)
+	query := GetBlockHeadersPacket{
+		Origin:  HashOrNumber{Hash: hash},
+		Amount:  uint64(1),
+		Skip:    uint64(0),
+		Reverse: false,
+	}
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id)
+		return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{
+			RequestId:             id,
+			GetBlockHeadersPacket: &query,
+		})
+	}
+	return p2p.Send(p.rw, GetBlockHeadersMsg, &query)
+}
+
+// RequestHeadersByHash fetches a batch of blocks' headers corresponding to the
+// specified header query, based on the hash of an origin block.
+func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error {
+	p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
+	query := GetBlockHeadersPacket{
+		Origin:  HashOrNumber{Hash: origin},
+		Amount:  uint64(amount),
+		Skip:    uint64(skip),
+		Reverse: reverse,
+	}
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id)
+		return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{
+			RequestId:             id,
+			GetBlockHeadersPacket: &query,
+		})
+	}
+	return p2p.Send(p.rw, GetBlockHeadersMsg, &query)
+}
+
+// RequestHeadersByNumber fetches a batch of blocks' headers corresponding to the
+// specified header query, based on the number of an origin block.
+func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
+	p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
+	query := GetBlockHeadersPacket{
+		Origin:  HashOrNumber{Number: origin},
+		Amount:  uint64(amount),
+		Skip:    uint64(skip),
+		Reverse: reverse,
+	}
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetBlockHeadersMsg, BlockHeadersMsg, id)
+		return p2p.Send(p.rw, GetBlockHeadersMsg, &GetBlockHeadersPacket66{
+			RequestId:             id,
+			GetBlockHeadersPacket: &query,
+		})
+	}
+	return p2p.Send(p.rw, GetBlockHeadersMsg, &query)
+}
+
+// ExpectRequestHeadersByNumber is a testing method to mirror the recipient side
+// of the RequestHeadersByNumber operation.
+func (p *Peer) ExpectRequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error {
+	req := &GetBlockHeadersPacket{
+		Origin:  HashOrNumber{Number: origin},
+		Amount:  uint64(amount),
+		Skip:    uint64(skip),
+		Reverse: reverse,
+	}
+	return p2p.ExpectMsg(p.rw, GetBlockHeadersMsg, req)
+}
+
+// RequestBodies fetches a batch of blocks' bodies corresponding to the hashes
+// specified.
+func (p *Peer) RequestBodies(hashes []common.Hash) error {
+	p.Log().Debug("Fetching batch of block bodies", "count", len(hashes))
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetBlockBodiesMsg, BlockBodiesMsg, id)
+		return p2p.Send(p.rw, GetBlockBodiesMsg, &GetBlockBodiesPacket66{
+			RequestId:            id,
+			GetBlockBodiesPacket: hashes,
+		})
+	}
+	return p2p.Send(p.rw, GetBlockBodiesMsg, GetBlockBodiesPacket(hashes))
+}
+
+// RequestNodeData fetches a batch of arbitrary data from a node's known state
+// data, corresponding to the specified hashes.
+func (p *Peer) RequestNodeData(hashes []common.Hash) error {
+	p.Log().Debug("Fetching batch of state data", "count", len(hashes))
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetNodeDataMsg, NodeDataMsg, id)
+		return p2p.Send(p.rw, GetNodeDataMsg, &GetNodeDataPacket66{
+			RequestId:         id,
+			GetNodeDataPacket: hashes,
+		})
+	}
+	return p2p.Send(p.rw, GetNodeDataMsg, GetNodeDataPacket(hashes))
+}
+
+// RequestReceipts fetches a batch of transaction receipts from a remote node.
+func (p *Peer) RequestReceipts(hashes []common.Hash) error {
+	p.Log().Debug("Fetching batch of receipts", "count", len(hashes))
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetReceiptsMsg, ReceiptsMsg, id)
+		return p2p.Send(p.rw, GetReceiptsMsg, &GetReceiptsPacket66{
+			RequestId:         id,
+			GetReceiptsPacket: hashes,
+		})
+	}
+	return p2p.Send(p.rw, GetReceiptsMsg, GetReceiptsPacket(hashes))
+}
+
+// RequestTxs fetches a batch of transactions from a remote node.
+func (p *Peer) RequestTxs(hashes []common.Hash) error {
+	p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
+	if p.Version() >= ETH66 {
+		id := rand.Uint64()
+
+		requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id)
+		return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{
+			RequestId:                   id,
+			GetPooledTransactionsPacket: hashes,
+		})
+	}
+	return p2p.Send(p.rw, GetPooledTransactionsMsg, GetPooledTransactionsPacket(hashes))
+}
diff --git a/eth/protocols/eth/peer_test.go b/eth/protocols/eth/peer_test.go
new file mode 100644
index 0000000000..70e9959f82
--- /dev/null
+++ b/eth/protocols/eth/peer_test.go
@@ -0,0 +1,61 @@
+// Copyright 2015 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 .
+
+// This file contains some shares testing functionality, common to  multiple
+// different files and modules being tested.
+
+package eth
+
+import (
+	"crypto/rand"
+
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+// testPeer is a simulated peer to allow testing direct network calls.
+type testPeer struct {
+	*Peer
+
+	net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging
+	app *p2p.MsgPipeRW    // Application layer reader/writer to simulate the local side
+}
+
+// newTestPeer creates a new peer registered at the given data backend.
+func newTestPeer(name string, version uint, backend Backend) (*testPeer, <-chan error) {
+	// Create a message pipe to communicate through
+	app, net := p2p.MsgPipe()
+
+	// Start the peer on a new thread
+	var id enode.ID
+	rand.Read(id[:])
+
+	peer := NewPeer(version, p2p.NewPeer(id, name, nil), net, backend.TxPool())
+	errc := make(chan error, 1)
+	go func() {
+		errc <- backend.RunPeer(peer, func(peer *Peer) error {
+			return Handle(backend, peer)
+		})
+	}()
+	return &testPeer{app: app, net: net, Peer: peer}, errc
+}
+
+// close terminates the local side of the peer, notifying the remote protocol
+// manager of termination.
+func (p *testPeer) close() {
+	p.Peer.Close()
+	p.app.Close()
+}
diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go
new file mode 100644
index 0000000000..62c018ef8e
--- /dev/null
+++ b/eth/protocols/eth/protocol.go
@@ -0,0 +1,367 @@
+// Copyright 2014 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 .
+
+package eth
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// Constants to match up protocol versions and messages
+const (
+	ETH65 = 65
+	ETH66 = 66
+)
+
+// ProtocolName is the official short name of the `eth` protocol used during
+// devp2p capability negotiation.
+const ProtocolName = "eth"
+
+// ProtocolVersions are the supported versions of the `eth` protocol (first
+// is primary).
+var ProtocolVersions = []uint{ETH66, ETH65}
+
+// protocolLengths are the number of implemented message corresponding to
+// different protocol versions.
+var protocolLengths = map[uint]uint64{ETH66: 17, ETH65: 17}
+
+// maxMessageSize is the maximum cap on the size of a protocol message.
+const maxMessageSize = 10 * 1024 * 1024
+
+const (
+	// Protocol messages in eth/64
+	StatusMsg          = 0x00
+	NewBlockHashesMsg  = 0x01
+	TransactionsMsg    = 0x02
+	GetBlockHeadersMsg = 0x03
+	BlockHeadersMsg    = 0x04
+	GetBlockBodiesMsg  = 0x05
+	BlockBodiesMsg     = 0x06
+	NewBlockMsg        = 0x07
+	GetNodeDataMsg     = 0x0d
+	NodeDataMsg        = 0x0e
+	GetReceiptsMsg     = 0x0f
+	ReceiptsMsg        = 0x10
+
+	// Protocol messages overloaded in eth/65
+	NewPooledTransactionHashesMsg = 0x08
+	GetPooledTransactionsMsg      = 0x09
+	PooledTransactionsMsg         = 0x0a
+)
+
+var (
+	errNoStatusMsg             = errors.New("no status message")
+	errMsgTooLarge             = errors.New("message too long")
+	errDecode                  = errors.New("invalid message")
+	errInvalidMsgCode          = errors.New("invalid message code")
+	errProtocolVersionMismatch = errors.New("protocol version mismatch")
+	errNetworkIDMismatch       = errors.New("network ID mismatch")
+	errGenesisMismatch         = errors.New("genesis mismatch")
+	errForkIDRejected          = errors.New("fork ID rejected")
+)
+
+// Packet represents a p2p message in the `eth` protocol.
+type Packet interface {
+	Name() string // Name returns a string corresponding to the message type.
+	Kind() byte   // Kind returns the message type.
+}
+
+// StatusPacket is the network packet for the status message for eth/64 and later.
+type StatusPacket struct {
+	ProtocolVersion uint32
+	NetworkID       uint64
+	TD              *big.Int
+	Head            common.Hash
+	Genesis         common.Hash
+	ForkID          forkid.ID
+}
+
+// NewBlockHashesPacket is the network packet for the block announcements.
+type NewBlockHashesPacket []struct {
+	Hash   common.Hash // Hash of one particular block being announced
+	Number uint64      // Number of one particular block being announced
+}
+
+// Unpack retrieves the block hashes and numbers from the announcement packet
+// and returns them in a split flat format that's more consistent with the
+// internal data structures.
+func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) {
+	var (
+		hashes  = make([]common.Hash, len(*p))
+		numbers = make([]uint64, len(*p))
+	)
+	for i, body := range *p {
+		hashes[i], numbers[i] = body.Hash, body.Number
+	}
+	return hashes, numbers
+}
+
+// TransactionsPacket is the network packet for broadcasting new transactions.
+type TransactionsPacket []*types.Transaction
+
+// GetBlockHeadersPacket represents a block header query.
+type GetBlockHeadersPacket struct {
+	Origin  HashOrNumber // Block from which to retrieve headers
+	Amount  uint64       // Maximum number of headers to retrieve
+	Skip    uint64       // Blocks to skip between consecutive headers
+	Reverse bool         // Query direction (false = rising towards latest, true = falling towards genesis)
+}
+
+// GetBlockHeadersPacket represents a block header query over eth/66
+type GetBlockHeadersPacket66 struct {
+	RequestId uint64
+	*GetBlockHeadersPacket
+}
+
+// HashOrNumber is a combined field for specifying an origin block.
+type HashOrNumber struct {
+	Hash   common.Hash // Block hash from which to retrieve headers (excludes Number)
+	Number uint64      // Block hash from which to retrieve headers (excludes Hash)
+}
+
+// EncodeRLP is a specialized encoder for HashOrNumber to encode only one of the
+// two contained union fields.
+func (hn *HashOrNumber) EncodeRLP(w io.Writer) error {
+	if hn.Hash == (common.Hash{}) {
+		return rlp.Encode(w, hn.Number)
+	}
+	if hn.Number != 0 {
+		return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number)
+	}
+	return rlp.Encode(w, hn.Hash)
+}
+
+// DecodeRLP is a specialized decoder for HashOrNumber to decode the contents
+// into either a block hash or a block number.
+func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error {
+	_, size, _ := s.Kind()
+	origin, err := s.Raw()
+	if err == nil {
+		switch {
+		case size == 32:
+			err = rlp.DecodeBytes(origin, &hn.Hash)
+		case size <= 8:
+			err = rlp.DecodeBytes(origin, &hn.Number)
+		default:
+			err = fmt.Errorf("invalid input size %d for origin", size)
+		}
+	}
+	return err
+}
+
+// BlockHeadersPacket represents a block header response.
+type BlockHeadersPacket []*types.Header
+
+// BlockHeadersPacket represents a block header response over eth/66.
+type BlockHeadersPacket66 struct {
+	RequestId uint64
+	BlockHeadersPacket
+}
+
+// NewBlockPacket is the network packet for the block propagation message.
+type NewBlockPacket struct {
+	Block *types.Block
+	TD    *big.Int
+}
+
+// sanityCheck verifies that the values are reasonable, as a DoS protection
+func (request *NewBlockPacket) sanityCheck() error {
+	if err := request.Block.SanityCheck(); err != nil {
+		return err
+	}
+	//TD at mainnet block #7753254 is 76 bits. If it becomes 100 million times
+	// larger, it will still fit within 100 bits
+	if tdlen := request.TD.BitLen(); tdlen > 100 {
+		return fmt.Errorf("too large block TD: bitlen %d", tdlen)
+	}
+	return nil
+}
+
+// GetBlockBodiesPacket represents a block body query.
+type GetBlockBodiesPacket []common.Hash
+
+// GetBlockBodiesPacket represents a block body query over eth/66.
+type GetBlockBodiesPacket66 struct {
+	RequestId uint64
+	GetBlockBodiesPacket
+}
+
+// BlockBodiesPacket is the network packet for block content distribution.
+type BlockBodiesPacket []*BlockBody
+
+// BlockBodiesPacket is the network packet for block content distribution over eth/66.
+type BlockBodiesPacket66 struct {
+	RequestId uint64
+	BlockBodiesPacket
+}
+
+// BlockBodiesRLPPacket is used for replying to block body requests, in cases
+// where we already have them RLP-encoded, and thus can avoid the decode-encode
+// roundtrip.
+type BlockBodiesRLPPacket []rlp.RawValue
+
+// BlockBodiesRLPPacket66 is the BlockBodiesRLPPacket over eth/66
+type BlockBodiesRLPPacket66 struct {
+	RequestId uint64
+	BlockBodiesRLPPacket
+}
+
+// BlockBody represents the data content of a single block.
+type BlockBody struct {
+	Transactions []*types.Transaction // Transactions contained within a block
+	Uncles       []*types.Header      // Uncles contained within a block
+}
+
+// Unpack retrieves the transactions and uncles from the range packet and returns
+// them in a split flat format that's more consistent with the internal data structures.
+func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header) {
+	var (
+		txset    = make([][]*types.Transaction, len(*p))
+		uncleset = make([][]*types.Header, len(*p))
+	)
+	for i, body := range *p {
+		txset[i], uncleset[i] = body.Transactions, body.Uncles
+	}
+	return txset, uncleset
+}
+
+// GetNodeDataPacket represents a trie node data query.
+type GetNodeDataPacket []common.Hash
+
+// GetNodeDataPacket represents a trie node data query over eth/66.
+type GetNodeDataPacket66 struct {
+	RequestId uint64
+	GetNodeDataPacket
+}
+
+// NodeDataPacket is the network packet for trie node data distribution.
+type NodeDataPacket [][]byte
+
+// NodeDataPacket is the network packet for trie node data distribution over eth/66.
+type NodeDataPacket66 struct {
+	RequestId uint64
+	NodeDataPacket
+}
+
+// GetReceiptsPacket represents a block receipts query.
+type GetReceiptsPacket []common.Hash
+
+// GetReceiptsPacket represents a block receipts query over eth/66.
+type GetReceiptsPacket66 struct {
+	RequestId uint64
+	GetReceiptsPacket
+}
+
+// ReceiptsPacket is the network packet for block receipts distribution.
+type ReceiptsPacket [][]*types.Receipt
+
+// ReceiptsPacket is the network packet for block receipts distribution over eth/66.
+type ReceiptsPacket66 struct {
+	RequestId uint64
+	ReceiptsPacket
+}
+
+// ReceiptsRLPPacket is used for receipts, when we already have it encoded
+type ReceiptsRLPPacket []rlp.RawValue
+
+// ReceiptsPacket66 is the eth-66 version of ReceiptsRLPPacket
+type ReceiptsRLPPacket66 struct {
+	RequestId uint64
+	ReceiptsRLPPacket
+}
+
+// NewPooledTransactionHashesPacket represents a transaction announcement packet.
+type NewPooledTransactionHashesPacket []common.Hash
+
+// GetPooledTransactionsPacket represents a transaction query.
+type GetPooledTransactionsPacket []common.Hash
+
+type GetPooledTransactionsPacket66 struct {
+	RequestId uint64
+	GetPooledTransactionsPacket
+}
+
+// PooledTransactionsPacket is the network packet for transaction distribution.
+type PooledTransactionsPacket []*types.Transaction
+
+// PooledTransactionsPacket is the network packet for transaction distribution over eth/66.
+type PooledTransactionsPacket66 struct {
+	RequestId uint64
+	PooledTransactionsPacket
+}
+
+// PooledTransactionsPacket is the network packet for transaction distribution, used
+// in the cases we already have them in rlp-encoded form
+type PooledTransactionsRLPPacket []rlp.RawValue
+
+// PooledTransactionsRLPPacket66 is the eth/66 form of PooledTransactionsRLPPacket
+type PooledTransactionsRLPPacket66 struct {
+	RequestId uint64
+	PooledTransactionsRLPPacket
+}
+
+func (*StatusPacket) Name() string { return "Status" }
+func (*StatusPacket) Kind() byte   { return StatusMsg }
+
+func (*NewBlockHashesPacket) Name() string { return "NewBlockHashes" }
+func (*NewBlockHashesPacket) Kind() byte   { return NewBlockHashesMsg }
+
+func (*TransactionsPacket) Name() string { return "Transactions" }
+func (*TransactionsPacket) Kind() byte   { return TransactionsMsg }
+
+func (*GetBlockHeadersPacket) Name() string { return "GetBlockHeaders" }
+func (*GetBlockHeadersPacket) Kind() byte   { return GetBlockHeadersMsg }
+
+func (*BlockHeadersPacket) Name() string { return "BlockHeaders" }
+func (*BlockHeadersPacket) Kind() byte   { return BlockHeadersMsg }
+
+func (*GetBlockBodiesPacket) Name() string { return "GetBlockBodies" }
+func (*GetBlockBodiesPacket) Kind() byte   { return GetBlockBodiesMsg }
+
+func (*BlockBodiesPacket) Name() string { return "BlockBodies" }
+func (*BlockBodiesPacket) Kind() byte   { return BlockBodiesMsg }
+
+func (*NewBlockPacket) Name() string { return "NewBlock" }
+func (*NewBlockPacket) Kind() byte   { return NewBlockMsg }
+
+func (*GetNodeDataPacket) Name() string { return "GetNodeData" }
+func (*GetNodeDataPacket) Kind() byte   { return GetNodeDataMsg }
+
+func (*NodeDataPacket) Name() string { return "NodeData" }
+func (*NodeDataPacket) Kind() byte   { return NodeDataMsg }
+
+func (*GetReceiptsPacket) Name() string { return "GetReceipts" }
+func (*GetReceiptsPacket) Kind() byte   { return GetReceiptsMsg }
+
+func (*ReceiptsPacket) Name() string { return "Receipts" }
+func (*ReceiptsPacket) Kind() byte   { return ReceiptsMsg }
+
+func (*NewPooledTransactionHashesPacket) Name() string { return "NewPooledTransactionHashes" }
+func (*NewPooledTransactionHashesPacket) Kind() byte   { return NewPooledTransactionHashesMsg }
+
+func (*GetPooledTransactionsPacket) Name() string { return "GetPooledTransactions" }
+func (*GetPooledTransactionsPacket) Kind() byte   { return GetPooledTransactionsMsg }
+
+func (*PooledTransactionsPacket) Name() string { return "PooledTransactions" }
+func (*PooledTransactionsPacket) Kind() byte   { return PooledTransactionsMsg }
diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go
new file mode 100644
index 0000000000..7910c9b735
--- /dev/null
+++ b/eth/protocols/eth/protocol_test.go
@@ -0,0 +1,268 @@
+// Copyright 2014 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 .
+
+package eth
+
+import (
+	"bytes"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// Tests that the custom union field encoder and decoder works correctly.
+func TestGetBlockHeadersDataEncodeDecode(t *testing.T) {
+	// Create a "random" hash for testing
+	var hash common.Hash
+	for i := range hash {
+		hash[i] = byte(i)
+	}
+	// Assemble some table driven tests
+	tests := []struct {
+		packet *GetBlockHeadersPacket
+		fail   bool
+	}{
+		// Providing the origin as either a hash or a number should both work
+		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}}},
+		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}}},
+
+		// Providing arbitrary query field should also work
+		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}},
+		{fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}},
+
+		// Providing both the origin hash and origin number must fail
+		{fail: true, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash, Number: 314}}},
+	}
+	// Iterate over each of the tests and try to encode and then decode
+	for i, tt := range tests {
+		bytes, err := rlp.EncodeToBytes(tt.packet)
+		if err != nil && !tt.fail {
+			t.Fatalf("test %d: failed to encode packet: %v", i, err)
+		} else if err == nil && tt.fail {
+			t.Fatalf("test %d: encode should have failed", i)
+		}
+		if !tt.fail {
+			packet := new(GetBlockHeadersPacket)
+			if err := rlp.DecodeBytes(bytes, packet); err != nil {
+				t.Fatalf("test %d: failed to decode packet: %v", i, err)
+			}
+			if packet.Origin.Hash != tt.packet.Origin.Hash || packet.Origin.Number != tt.packet.Origin.Number || packet.Amount != tt.packet.Amount ||
+				packet.Skip != tt.packet.Skip || packet.Reverse != tt.packet.Reverse {
+				t.Fatalf("test %d: encode decode mismatch: have %+v, want %+v", i, packet, tt.packet)
+			}
+		}
+	}
+}
+
+// TestEth66EmptyMessages tests encoding of empty eth66 messages
+func TestEth66EmptyMessages(t *testing.T) {
+	// All empty messages encodes to the same format
+	want := common.FromHex("c4820457c0")
+
+	for i, msg := range []interface{}{
+		// Headers
+		GetBlockHeadersPacket66{1111, nil},
+		BlockHeadersPacket66{1111, nil},
+		// Bodies
+		GetBlockBodiesPacket66{1111, nil},
+		BlockBodiesPacket66{1111, nil},
+		BlockBodiesRLPPacket66{1111, nil},
+		// Node data
+		GetNodeDataPacket66{1111, nil},
+		NodeDataPacket66{1111, nil},
+		// Receipts
+		GetReceiptsPacket66{1111, nil},
+		ReceiptsPacket66{1111, nil},
+		// Transactions
+		GetPooledTransactionsPacket66{1111, nil},
+		PooledTransactionsPacket66{1111, nil},
+		PooledTransactionsRLPPacket66{1111, nil},
+
+		// Headers
+		BlockHeadersPacket66{1111, BlockHeadersPacket([]*types.Header{})},
+		// Bodies
+		GetBlockBodiesPacket66{1111, GetBlockBodiesPacket([]common.Hash{})},
+		BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{})},
+		BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{})},
+		// Node data
+		GetNodeDataPacket66{1111, GetNodeDataPacket([]common.Hash{})},
+		NodeDataPacket66{1111, NodeDataPacket([][]byte{})},
+		// Receipts
+		GetReceiptsPacket66{1111, GetReceiptsPacket([]common.Hash{})},
+		ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{})},
+		// Transactions
+		GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket([]common.Hash{})},
+		PooledTransactionsPacket66{1111, PooledTransactionsPacket([]*types.Transaction{})},
+		PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket([]rlp.RawValue{})},
+	} {
+		if have, _ := rlp.EncodeToBytes(msg); !bytes.Equal(have, want) {
+			t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, msg, have, want)
+		}
+	}
+
+}
+
+// TestEth66Messages tests the encoding of all redefined eth66 messages
+func TestEth66Messages(t *testing.T) {
+
+	// Some basic structs used during testing
+	var (
+		header       *types.Header
+		blockBody    *BlockBody
+		blockBodyRlp rlp.RawValue
+		txs          []*types.Transaction
+		txRlps       []rlp.RawValue
+		hashes       []common.Hash
+		receipts     []*types.Receipt
+		receiptsRlp  rlp.RawValue
+
+		err error
+	)
+	header = &types.Header{
+		Difficulty: big.NewInt(2222),
+		Number:     big.NewInt(3333),
+		GasLimit:   4444,
+		GasUsed:    5555,
+		Time:       6666,
+		Extra:      []byte{0x77, 0x88},
+	}
+	// Init the transactions, taken from a different test
+	{
+		for _, hexrlp := range []string{
+			"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
+			"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb",
+		} {
+			var tx *types.Transaction
+			rlpdata := common.FromHex(hexrlp)
+			if err := rlp.DecodeBytes(rlpdata, &tx); err != nil {
+				t.Fatal(err)
+			}
+			txs = append(txs, tx)
+			txRlps = append(txRlps, rlpdata)
+		}
+	}
+	// init the block body data, both object and rlp form
+	blockBody = &BlockBody{
+		Transactions: txs,
+		Uncles:       []*types.Header{header},
+	}
+	blockBodyRlp, err = rlp.EncodeToBytes(blockBody)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	hashes = []common.Hash{
+		common.HexToHash("deadc0de"),
+		common.HexToHash("feedbeef"),
+	}
+	byteSlices := [][]byte{
+		common.FromHex("deadc0de"),
+		common.FromHex("feedbeef"),
+	}
+	// init the receipts
+	{
+		receipts = []*types.Receipt{
+			{
+				Status:            types.ReceiptStatusFailed,
+				CumulativeGasUsed: 1,
+				Logs: []*types.Log{
+					{
+						Address: common.BytesToAddress([]byte{0x11}),
+						Topics:  []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")},
+						Data:    []byte{0x01, 0x00, 0xff},
+					},
+				},
+				TxHash:          hashes[0],
+				ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
+				GasUsed:         111111,
+			},
+		}
+		rlpData, err := rlp.EncodeToBytes(receipts)
+		if err != nil {
+			t.Fatal(err)
+		}
+		receiptsRlp = rlpData
+	}
+
+	for i, tc := range []struct {
+		message interface{}
+		want    []byte
+	}{
+		{
+			GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{hashes[0], 0}, 5, 5, false}},
+			common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"),
+		},
+		{
+			GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}},
+			common.FromHex("ca820457c682270f050580"),
+		},
+		{
+			BlockHeadersPacket66{1111, BlockHeadersPacket{header}},
+			common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
+		},
+		{
+			GetBlockBodiesPacket66{1111, GetBlockBodiesPacket(hashes)},
+			common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
+		},
+		{
+			BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{blockBody})},
+			common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
+		},
+		{ // Identical to non-rlp-shortcut version
+			BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{blockBodyRlp})},
+			common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"),
+		},
+		{
+			GetNodeDataPacket66{1111, GetNodeDataPacket(hashes)},
+			common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
+		},
+		{
+			NodeDataPacket66{1111, NodeDataPacket(byteSlices)},
+			common.FromHex("ce820457ca84deadc0de84feedbeef"),
+		},
+		{
+			GetReceiptsPacket66{1111, GetReceiptsPacket(hashes)},
+			common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
+		},
+		{
+			ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{receipts})},
+			common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
+		},
+		{
+			ReceiptsRLPPacket66{1111, ReceiptsRLPPacket([]rlp.RawValue{receiptsRlp})},
+			common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"),
+		},
+		{
+			GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket(hashes)},
+			common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"),
+		},
+		{
+			PooledTransactionsPacket66{1111, PooledTransactionsPacket(txs)},
+			common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"),
+		},
+		{
+			PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket(txRlps)},
+			common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"),
+		},
+	} {
+		if have, _ := rlp.EncodeToBytes(tc.message); !bytes.Equal(have, tc.want) {
+			t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, tc.message, have, tc.want)
+		}
+	}
+}
diff --git a/p2p/discv5/metrics.go b/eth/protocols/eth/tracker.go
similarity index 73%
rename from p2p/discv5/metrics.go
rename to eth/protocols/eth/tracker.go
index e68d53c13c..324fd22839 100644
--- a/p2p/discv5/metrics.go
+++ b/eth/protocols/eth/tracker.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The go-ethereum Authors
+// Copyright 2021 The go-ethereum Authors
 // This file is part of the go-ethereum library.
 //
 // The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,11 +14,13 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package discv5
+package eth
 
-import "github.com/ethereum/go-ethereum/metrics"
+import (
+	"time"
 
-var (
-	ingressTrafficMeter = metrics.NewRegisteredMeter("discv5/InboundTraffic", nil)
-	egressTrafficMeter  = metrics.NewRegisteredMeter("discv5/OutboundTraffic", nil)
+	"github.com/ethereum/go-ethereum/p2p/tracker"
 )
+
+// requestTracker is a singleton tracker for eth/66 and newer request times.
+var requestTracker = tracker.New(ProtocolName, 5*time.Minute)
diff --git a/eth/protocols/snap/discovery.go b/eth/protocols/snap/discovery.go
new file mode 100644
index 0000000000..684ec7e632
--- /dev/null
+++ b/eth/protocols/snap/discovery.go
@@ -0,0 +1,32 @@
+// 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 .
+
+package snap
+
+import (
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// enrEntry is the ENR entry which advertises `snap` protocol on the discovery.
+type enrEntry struct {
+	// Ignore additional fields (for forward compatibility).
+	Rest []rlp.RawValue `rlp:"tail"`
+}
+
+// ENRKey implements enr.Entry.
+func (e enrEntry) ENRKey() string {
+	return "snap"
+}
diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go
new file mode 100644
index 0000000000..3d668a2ebb
--- /dev/null
+++ b/eth/protocols/snap/handler.go
@@ -0,0 +1,528 @@
+// 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 .
+
+package snap
+
+import (
+	"bytes"
+	"fmt"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+const (
+	// softResponseLimit is the target maximum size of replies to data retrievals.
+	softResponseLimit = 2 * 1024 * 1024
+
+	// maxCodeLookups is the maximum number of bytecodes to serve. This number is
+	// there to limit the number of disk lookups.
+	maxCodeLookups = 1024
+
+	// stateLookupSlack defines the ratio by how much a state response can exceed
+	// the requested limit in order to try and avoid breaking up contracts into
+	// multiple packages and proving them.
+	stateLookupSlack = 0.1
+
+	// maxTrieNodeLookups is the maximum number of state trie nodes to serve. This
+	// number is there to limit the number of disk lookups.
+	maxTrieNodeLookups = 1024
+
+	// maxTrieNodeTimeSpent is the maximum time we should spend on looking up trie nodes.
+	// If we spend too much time, then it's a fairly high chance of timing out
+	// at the remote side, which means all the work is in vain.
+	maxTrieNodeTimeSpent = 5 * time.Second
+)
+
+// Handler is a callback to invoke from an outside runner after the boilerplate
+// exchanges have passed.
+type Handler func(peer *Peer) error
+
+// Backend defines the data retrieval methods to serve remote requests and the
+// callback methods to invoke on remote deliveries.
+type Backend interface {
+	// Chain retrieves the blockchain object to serve data.
+	Chain() *core.BlockChain
+
+	// RunPeer is invoked when a peer joins on the `eth` protocol. The handler
+	// should do any peer maintenance work, handshakes and validations. If all
+	// is passed, control should be given back to the `handler` to process the
+	// inbound messages going forward.
+	RunPeer(peer *Peer, handler Handler) error
+
+	// PeerInfo retrieves all known `snap` information about a peer.
+	PeerInfo(id enode.ID) interface{}
+
+	// Handle is a callback to be invoked when a data packet is received from
+	// the remote peer. Only packets not consumed by the protocol handler will
+	// be forwarded to the backend.
+	Handle(peer *Peer, packet Packet) error
+}
+
+// MakeProtocols constructs the P2P protocol definitions for `snap`.
+func MakeProtocols(backend Backend, dnsdisc enode.Iterator) []p2p.Protocol {
+	// Filter the discovery iterator for nodes advertising snap support.
+	dnsdisc = enode.Filter(dnsdisc, func(n *enode.Node) bool {
+		var snap enrEntry
+		return n.Load(&snap) == nil
+	})
+
+	protocols := make([]p2p.Protocol, len(ProtocolVersions))
+	for i, version := range ProtocolVersions {
+		version := version // Closure
+
+		protocols[i] = p2p.Protocol{
+			Name:    ProtocolName,
+			Version: version,
+			Length:  protocolLengths[version],
+			Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
+				return backend.RunPeer(newPeer(version, p, rw), func(peer *Peer) error {
+					return handle(backend, peer)
+				})
+			},
+			NodeInfo: func() interface{} {
+				return nodeInfo(backend.Chain())
+			},
+			PeerInfo: func(id enode.ID) interface{} {
+				return backend.PeerInfo(id)
+			},
+			Attributes:     []enr.Entry{&enrEntry{}},
+			DialCandidates: dnsdisc,
+		}
+	}
+	return protocols
+}
+
+// handle is the callback invoked to manage the life cycle of a `snap` peer.
+// When this function terminates, the peer is disconnected.
+func handle(backend Backend, peer *Peer) error {
+	for {
+		if err := handleMessage(backend, peer); err != nil {
+			peer.Log().Debug("Message handling failed in `snap`", "err", err)
+			return err
+		}
+	}
+}
+
+// handleMessage is invoked whenever an inbound message is received from a
+// remote peer on the `snap` protocol. The remote connection is torn down upon
+// returning any error.
+func handleMessage(backend Backend, peer *Peer) error {
+	// Read the next message from the remote peer, and ensure it's fully consumed
+	msg, err := peer.rw.ReadMsg()
+	if err != nil {
+		return err
+	}
+	if msg.Size > maxMessageSize {
+		return fmt.Errorf("%w: %v > %v", errMsgTooLarge, msg.Size, maxMessageSize)
+	}
+	defer msg.Discard()
+	start := time.Now()
+	// Track the emount of time it takes to serve the request and run the handler
+	if metrics.Enabled {
+		h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code)
+		defer func(start time.Time) {
+			sampler := func() metrics.Sample {
+				return metrics.ResettingSample(
+					metrics.NewExpDecaySample(1028, 0.015),
+				)
+			}
+			metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(start).Microseconds())
+		}(start)
+	}
+	// Handle the message depending on its contents
+	switch {
+	case msg.Code == GetAccountRangeMsg:
+		// Decode the account retrieval request
+		var req GetAccountRangePacket
+		if err := msg.Decode(&req); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		if req.Bytes > softResponseLimit {
+			req.Bytes = softResponseLimit
+		}
+		// Retrieve the requested state and bail out if non existent
+		tr, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB())
+		if err != nil {
+			return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
+		}
+		it, err := backend.Chain().Snapshots().AccountIterator(req.Root, req.Origin)
+		if err != nil {
+			return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
+		}
+		// Iterate over the requested range and pile accounts up
+		var (
+			accounts []*AccountData
+			size     uint64
+			last     common.Hash
+		)
+		for it.Next() && size < req.Bytes {
+			hash, account := it.Hash(), common.CopyBytes(it.Account())
+
+			// Track the returned interval for the Merkle proofs
+			last = hash
+
+			// Assemble the reply item
+			size += uint64(common.HashLength + len(account))
+			accounts = append(accounts, &AccountData{
+				Hash: hash,
+				Body: account,
+			})
+			// If we've exceeded the request threshold, abort
+			if bytes.Compare(hash[:], req.Limit[:]) >= 0 {
+				break
+			}
+		}
+		it.Release()
+
+		// Generate the Merkle proofs for the first and last account
+		proof := light.NewNodeSet()
+		if err := tr.Prove(req.Origin[:], 0, proof); err != nil {
+			log.Warn("Failed to prove account range", "origin", req.Origin, "err", err)
+			return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
+		}
+		if last != (common.Hash{}) {
+			if err := tr.Prove(last[:], 0, proof); err != nil {
+				log.Warn("Failed to prove account range", "last", last, "err", err)
+				return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{ID: req.ID})
+			}
+		}
+		var proofs [][]byte
+		for _, blob := range proof.NodeList() {
+			proofs = append(proofs, blob)
+		}
+		// Send back anything accumulated
+		return p2p.Send(peer.rw, AccountRangeMsg, &AccountRangePacket{
+			ID:       req.ID,
+			Accounts: accounts,
+			Proof:    proofs,
+		})
+
+	case msg.Code == AccountRangeMsg:
+		// A range of accounts arrived to one of our previous requests
+		res := new(AccountRangePacket)
+		if err := msg.Decode(res); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		// Ensure the range is monotonically increasing
+		for i := 1; i < len(res.Accounts); i++ {
+			if bytes.Compare(res.Accounts[i-1].Hash[:], res.Accounts[i].Hash[:]) >= 0 {
+				return fmt.Errorf("accounts not monotonically increasing: #%d [%x] vs #%d [%x]", i-1, res.Accounts[i-1].Hash[:], i, res.Accounts[i].Hash[:])
+			}
+		}
+		requestTracker.Fulfil(peer.id, peer.version, AccountRangeMsg, res.ID)
+
+		return backend.Handle(peer, res)
+
+	case msg.Code == GetStorageRangesMsg:
+		// Decode the storage retrieval request
+		var req GetStorageRangesPacket
+		if err := msg.Decode(&req); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		if req.Bytes > softResponseLimit {
+			req.Bytes = softResponseLimit
+		}
+		// TODO(karalabe): Do we want to enforce > 0 accounts and 1 account if origin is set?
+		// TODO(karalabe):   - Logging locally is not ideal as remote faulst annoy the local user
+		// TODO(karalabe):   - Dropping the remote peer is less flexible wrt client bugs (slow is better than non-functional)
+
+		// Calculate the hard limit at which to abort, even if mid storage trie
+		hardLimit := uint64(float64(req.Bytes) * (1 + stateLookupSlack))
+
+		// Retrieve storage ranges until the packet limit is reached
+		var (
+			slots  [][]*StorageData
+			proofs [][]byte
+			size   uint64
+		)
+		for _, account := range req.Accounts {
+			// If we've exceeded the requested data limit, abort without opening
+			// a new storage range (that we'd need to prove due to exceeded size)
+			if size >= req.Bytes {
+				break
+			}
+			// The first account might start from a different origin and end sooner
+			var origin common.Hash
+			if len(req.Origin) > 0 {
+				origin, req.Origin = common.BytesToHash(req.Origin), nil
+			}
+			var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+			if len(req.Limit) > 0 {
+				limit, req.Limit = common.BytesToHash(req.Limit), nil
+			}
+			// Retrieve the requested state and bail out if non existent
+			it, err := backend.Chain().Snapshots().StorageIterator(req.Root, account, origin)
+			if err != nil {
+				return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
+			}
+			// Iterate over the requested range and pile slots up
+			var (
+				storage []*StorageData
+				last    common.Hash
+				abort   bool
+			)
+			for it.Next() {
+				if size >= hardLimit {
+					abort = true
+					break
+				}
+				hash, slot := it.Hash(), common.CopyBytes(it.Slot())
+
+				// Track the returned interval for the Merkle proofs
+				last = hash
+
+				// Assemble the reply item
+				size += uint64(common.HashLength + len(slot))
+				storage = append(storage, &StorageData{
+					Hash: hash,
+					Body: slot,
+				})
+				// If we've exceeded the request threshold, abort
+				if bytes.Compare(hash[:], limit[:]) >= 0 {
+					break
+				}
+			}
+			slots = append(slots, storage)
+			it.Release()
+
+			// Generate the Merkle proofs for the first and last storage slot, but
+			// only if the response was capped. If the entire storage trie included
+			// in the response, no need for any proofs.
+			if origin != (common.Hash{}) || abort {
+				// Request started at a non-zero hash or was capped prematurely, add
+				// the endpoint Merkle proofs
+				accTrie, err := trie.New(req.Root, backend.Chain().StateCache().TrieDB())
+				if err != nil {
+					return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
+				}
+				var acc state.Account
+				if err := rlp.DecodeBytes(accTrie.Get(account[:]), &acc); err != nil {
+					return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
+				}
+				stTrie, err := trie.New(acc.Root, backend.Chain().StateCache().TrieDB())
+				if err != nil {
+					return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
+				}
+				proof := light.NewNodeSet()
+				if err := stTrie.Prove(origin[:], 0, proof); err != nil {
+					log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err)
+					return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
+				}
+				if last != (common.Hash{}) {
+					if err := stTrie.Prove(last[:], 0, proof); err != nil {
+						log.Warn("Failed to prove storage range", "last", last, "err", err)
+						return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{ID: req.ID})
+					}
+				}
+				for _, blob := range proof.NodeList() {
+					proofs = append(proofs, blob)
+				}
+				// Proof terminates the reply as proofs are only added if a node
+				// refuses to serve more data (exception when a contract fetch is
+				// finishing, but that's that).
+				break
+			}
+		}
+		// Send back anything accumulated
+		return p2p.Send(peer.rw, StorageRangesMsg, &StorageRangesPacket{
+			ID:    req.ID,
+			Slots: slots,
+			Proof: proofs,
+		})
+
+	case msg.Code == StorageRangesMsg:
+		// A range of storage slots arrived to one of our previous requests
+		res := new(StorageRangesPacket)
+		if err := msg.Decode(res); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		// Ensure the ranges are monotonically increasing
+		for i, slots := range res.Slots {
+			for j := 1; j < len(slots); j++ {
+				if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 {
+					return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:])
+				}
+			}
+		}
+		requestTracker.Fulfil(peer.id, peer.version, StorageRangesMsg, res.ID)
+
+		return backend.Handle(peer, res)
+
+	case msg.Code == GetByteCodesMsg:
+		// Decode bytecode retrieval request
+		var req GetByteCodesPacket
+		if err := msg.Decode(&req); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		if req.Bytes > softResponseLimit {
+			req.Bytes = softResponseLimit
+		}
+		if len(req.Hashes) > maxCodeLookups {
+			req.Hashes = req.Hashes[:maxCodeLookups]
+		}
+		// Retrieve bytecodes until the packet size limit is reached
+		var (
+			codes [][]byte
+			bytes uint64
+		)
+		for _, hash := range req.Hashes {
+			if hash == emptyCode {
+				// Peers should not request the empty code, but if they do, at
+				// least sent them back a correct response without db lookups
+				codes = append(codes, []byte{})
+			} else if blob, err := backend.Chain().ContractCode(hash); err == nil {
+				codes = append(codes, blob)
+				bytes += uint64(len(blob))
+			}
+			if bytes > req.Bytes {
+				break
+			}
+		}
+		// Send back anything accumulated
+		return p2p.Send(peer.rw, ByteCodesMsg, &ByteCodesPacket{
+			ID:    req.ID,
+			Codes: codes,
+		})
+
+	case msg.Code == ByteCodesMsg:
+		// A batch of byte codes arrived to one of our previous requests
+		res := new(ByteCodesPacket)
+		if err := msg.Decode(res); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		requestTracker.Fulfil(peer.id, peer.version, ByteCodesMsg, res.ID)
+
+		return backend.Handle(peer, res)
+
+	case msg.Code == GetTrieNodesMsg:
+		// Decode trie node retrieval request
+		var req GetTrieNodesPacket
+		if err := msg.Decode(&req); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		if req.Bytes > softResponseLimit {
+			req.Bytes = softResponseLimit
+		}
+		// Make sure we have the state associated with the request
+		triedb := backend.Chain().StateCache().TrieDB()
+
+		accTrie, err := trie.NewSecure(req.Root, triedb)
+		if err != nil {
+			// We don't have the requested state available, bail out
+			return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID})
+		}
+		snap := backend.Chain().Snapshots().Snapshot(req.Root)
+		if snap == nil {
+			// We don't have the requested state snapshotted yet, bail out.
+			// In reality we could still serve using the account and storage
+			// tries only, but let's protect the node a bit while it's doing
+			// snapshot generation.
+			return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{ID: req.ID})
+		}
+		// Retrieve trie nodes until the packet size limit is reached
+		var (
+			nodes [][]byte
+			bytes uint64
+			loads int // Trie hash expansions to cound database reads
+		)
+		for _, pathset := range req.Paths {
+			switch len(pathset) {
+			case 0:
+				// Ensure we penalize invalid requests
+				return fmt.Errorf("%w: zero-item pathset requested", errBadRequest)
+
+			case 1:
+				// If we're only retrieving an account trie node, fetch it directly
+				blob, resolved, err := accTrie.TryGetNode(pathset[0])
+				loads += resolved // always account database reads, even for failures
+				if err != nil {
+					break
+				}
+				nodes = append(nodes, blob)
+				bytes += uint64(len(blob))
+
+			default:
+				// Storage slots requested, open the storage trie and retrieve from there
+				account, err := snap.Account(common.BytesToHash(pathset[0]))
+				loads++ // always account database reads, even for failures
+				if err != nil {
+					break
+				}
+				stTrie, err := trie.NewSecure(common.BytesToHash(account.Root), triedb)
+				loads++ // always account database reads, even for failures
+				if err != nil {
+					break
+				}
+				for _, path := range pathset[1:] {
+					blob, resolved, err := stTrie.TryGetNode(path)
+					loads += resolved // always account database reads, even for failures
+					if err != nil {
+						break
+					}
+					nodes = append(nodes, blob)
+					bytes += uint64(len(blob))
+
+					// Sanity check limits to avoid DoS on the store trie loads
+					if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent {
+						break
+					}
+				}
+			}
+			// Abort request processing if we've exceeded our limits
+			if bytes > req.Bytes || loads > maxTrieNodeLookups || time.Since(start) > maxTrieNodeTimeSpent {
+				break
+			}
+		}
+		// Send back anything accumulated
+		return p2p.Send(peer.rw, TrieNodesMsg, &TrieNodesPacket{
+			ID:    req.ID,
+			Nodes: nodes,
+		})
+
+	case msg.Code == TrieNodesMsg:
+		// A batch of trie nodes arrived to one of our previous requests
+		res := new(TrieNodesPacket)
+		if err := msg.Decode(res); err != nil {
+			return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
+		}
+		requestTracker.Fulfil(peer.id, peer.version, TrieNodesMsg, res.ID)
+
+		return backend.Handle(peer, res)
+
+	default:
+		return fmt.Errorf("%w: %v", errInvalidMsgCode, msg.Code)
+	}
+}
+
+// NodeInfo represents a short summary of the `snap` sub-protocol metadata
+// known about the host peer.
+type NodeInfo struct{}
+
+// nodeInfo retrieves some `snap` protocol metadata about the running host node.
+func nodeInfo(chain *core.BlockChain) *NodeInfo {
+	return &NodeInfo{}
+}
diff --git a/eth/protocols/snap/peer.go b/eth/protocols/snap/peer.go
new file mode 100644
index 0000000000..cf0ce65bd7
--- /dev/null
+++ b/eth/protocols/snap/peer.go
@@ -0,0 +1,123 @@
+// 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 .
+
+package snap
+
+import (
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p"
+)
+
+// Peer is a collection of relevant information we have about a `snap` peer.
+type Peer struct {
+	id string // Unique ID for the peer, cached
+
+	*p2p.Peer                   // The embedded P2P package peer
+	rw        p2p.MsgReadWriter // Input/output streams for snap
+	version   uint              // Protocol version negotiated
+
+	logger log.Logger // Contextual logger with the peer id injected
+}
+
+// newPeer create a wrapper for a network connection and negotiated  protocol
+// version.
+func newPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
+	id := p.ID().String()
+	return &Peer{
+		id:      id,
+		Peer:    p,
+		rw:      rw,
+		version: version,
+		logger:  log.New("peer", id[:8]),
+	}
+}
+
+// ID retrieves the peer's unique identifier.
+func (p *Peer) ID() string {
+	return p.id
+}
+
+// Version retrieves the peer's negoatiated `snap` protocol version.
+func (p *Peer) Version() uint {
+	return p.version
+}
+
+// Log overrides the P2P logget with the higher level one containing only the id.
+func (p *Peer) Log() log.Logger {
+	return p.logger
+}
+
+// RequestAccountRange fetches a batch of accounts rooted in a specific account
+// trie, starting with the origin.
+func (p *Peer) RequestAccountRange(id uint64, root common.Hash, origin, limit common.Hash, bytes uint64) error {
+	p.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes))
+
+	requestTracker.Track(p.id, p.version, GetAccountRangeMsg, AccountRangeMsg, id)
+	return p2p.Send(p.rw, GetAccountRangeMsg, &GetAccountRangePacket{
+		ID:     id,
+		Root:   root,
+		Origin: origin,
+		Limit:  limit,
+		Bytes:  bytes,
+	})
+}
+
+// RequestStorageRange fetches a batch of storage slots belonging to one or more
+// accounts. If slots from only one accout is requested, an origin marker may also
+// be used to retrieve from there.
+func (p *Peer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
+	if len(accounts) == 1 && origin != nil {
+		p.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes))
+	} else {
+		p.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes))
+	}
+	requestTracker.Track(p.id, p.version, GetStorageRangesMsg, StorageRangesMsg, id)
+	return p2p.Send(p.rw, GetStorageRangesMsg, &GetStorageRangesPacket{
+		ID:       id,
+		Root:     root,
+		Accounts: accounts,
+		Origin:   origin,
+		Limit:    limit,
+		Bytes:    bytes,
+	})
+}
+
+// RequestByteCodes fetches a batch of bytecodes by hash.
+func (p *Peer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
+	p.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes))
+
+	requestTracker.Track(p.id, p.version, GetByteCodesMsg, ByteCodesMsg, id)
+	return p2p.Send(p.rw, GetByteCodesMsg, &GetByteCodesPacket{
+		ID:     id,
+		Hashes: hashes,
+		Bytes:  bytes,
+	})
+}
+
+// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in
+// a specificstate trie.
+func (p *Peer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error {
+	p.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes))
+
+	requestTracker.Track(p.id, p.version, GetTrieNodesMsg, TrieNodesMsg, id)
+	return p2p.Send(p.rw, GetTrieNodesMsg, &GetTrieNodesPacket{
+		ID:    id,
+		Root:  root,
+		Paths: paths,
+		Bytes: bytes,
+	})
+}
diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go
new file mode 100644
index 0000000000..5528e9212e
--- /dev/null
+++ b/eth/protocols/snap/protocol.go
@@ -0,0 +1,218 @@
+// 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 .
+
+package snap
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/state/snapshot"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// Constants to match up protocol versions and messages
+const (
+	snap1 = 1
+)
+
+// ProtocolName is the official short name of the `snap` protocol used during
+// devp2p capability negotiation.
+const ProtocolName = "snap"
+
+// ProtocolVersions are the supported versions of the `snap` protocol (first
+// is primary).
+var ProtocolVersions = []uint{snap1}
+
+// protocolLengths are the number of implemented message corresponding to
+// different protocol versions.
+var protocolLengths = map[uint]uint64{snap1: 8}
+
+// maxMessageSize is the maximum cap on the size of a protocol message.
+const maxMessageSize = 10 * 1024 * 1024
+
+const (
+	GetAccountRangeMsg  = 0x00
+	AccountRangeMsg     = 0x01
+	GetStorageRangesMsg = 0x02
+	StorageRangesMsg    = 0x03
+	GetByteCodesMsg     = 0x04
+	ByteCodesMsg        = 0x05
+	GetTrieNodesMsg     = 0x06
+	TrieNodesMsg        = 0x07
+)
+
+var (
+	errMsgTooLarge    = errors.New("message too long")
+	errDecode         = errors.New("invalid message")
+	errInvalidMsgCode = errors.New("invalid message code")
+	errBadRequest     = errors.New("bad request")
+)
+
+// Packet represents a p2p message in the `snap` protocol.
+type Packet interface {
+	Name() string // Name returns a string corresponding to the message type.
+	Kind() byte   // Kind returns the message type.
+}
+
+// GetAccountRangePacket represents an account query.
+type GetAccountRangePacket struct {
+	ID     uint64      // Request ID to match up responses with
+	Root   common.Hash // Root hash of the account trie to serve
+	Origin common.Hash // Hash of the first account to retrieve
+	Limit  common.Hash // Hash of the last account to retrieve
+	Bytes  uint64      // Soft limit at which to stop returning data
+}
+
+// AccountRangePacket represents an account query response.
+type AccountRangePacket struct {
+	ID       uint64         // ID of the request this is a response for
+	Accounts []*AccountData // List of consecutive accounts from the trie
+	Proof    [][]byte       // List of trie nodes proving the account range
+}
+
+// AccountData represents a single account in a query response.
+type AccountData struct {
+	Hash common.Hash  // Hash of the account
+	Body rlp.RawValue // Account body in slim format
+}
+
+// Unpack retrieves the accounts from the range packet and converts from slim
+// wire representation to consensus format. The returned data is RLP encoded
+// since it's expected to be serialized to disk without further interpretation.
+//
+// Note, this method does a round of RLP decoding and reencoding, so only use it
+// once and cache the results if need be. Ideally discard the packet afterwards
+// to not double the memory use.
+func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) {
+	var (
+		hashes   = make([]common.Hash, len(p.Accounts))
+		accounts = make([][]byte, len(p.Accounts))
+	)
+	for i, acc := range p.Accounts {
+		val, err := snapshot.FullAccountRLP(acc.Body)
+		if err != nil {
+			return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err)
+		}
+		hashes[i], accounts[i] = acc.Hash, val
+	}
+	return hashes, accounts, nil
+}
+
+// GetStorageRangesPacket represents an storage slot query.
+type GetStorageRangesPacket struct {
+	ID       uint64        // Request ID to match up responses with
+	Root     common.Hash   // Root hash of the account trie to serve
+	Accounts []common.Hash // Account hashes of the storage tries to serve
+	Origin   []byte        // Hash of the first storage slot to retrieve (large contract mode)
+	Limit    []byte        // Hash of the last storage slot to retrieve (large contract mode)
+	Bytes    uint64        // Soft limit at which to stop returning data
+}
+
+// StorageRangesPacket represents a storage slot query response.
+type StorageRangesPacket struct {
+	ID    uint64           // ID of the request this is a response for
+	Slots [][]*StorageData // Lists of consecutive storage slots for the requested accounts
+	Proof [][]byte         // Merkle proofs for the *last* slot range, if it's incomplete
+}
+
+// StorageData represents a single storage slot in a query response.
+type StorageData struct {
+	Hash common.Hash // Hash of the storage slot
+	Body []byte      // Data content of the slot
+}
+
+// Unpack retrieves the storage slots from the range packet and returns them in
+// a split flat format that's more consistent with the internal data structures.
+func (p *StorageRangesPacket) Unpack() ([][]common.Hash, [][][]byte) {
+	var (
+		hashset = make([][]common.Hash, len(p.Slots))
+		slotset = make([][][]byte, len(p.Slots))
+	)
+	for i, slots := range p.Slots {
+		hashset[i] = make([]common.Hash, len(slots))
+		slotset[i] = make([][]byte, len(slots))
+		for j, slot := range slots {
+			hashset[i][j] = slot.Hash
+			slotset[i][j] = slot.Body
+		}
+	}
+	return hashset, slotset
+}
+
+// GetByteCodesPacket represents a contract bytecode query.
+type GetByteCodesPacket struct {
+	ID     uint64        // Request ID to match up responses with
+	Hashes []common.Hash // Code hashes to retrieve the code for
+	Bytes  uint64        // Soft limit at which to stop returning data
+}
+
+// ByteCodesPacket represents a contract bytecode query response.
+type ByteCodesPacket struct {
+	ID    uint64   // ID of the request this is a response for
+	Codes [][]byte // Requested contract bytecodes
+}
+
+// GetTrieNodesPacket represents a state trie node query.
+type GetTrieNodesPacket struct {
+	ID    uint64            // Request ID to match up responses with
+	Root  common.Hash       // Root hash of the account trie to serve
+	Paths []TrieNodePathSet // Trie node hashes to retrieve the nodes for
+	Bytes uint64            // Soft limit at which to stop returning data
+}
+
+// TrieNodePathSet is a list of trie node paths to retrieve. A naive way to
+// represent trie nodes would be a simple list of `account || storage` path
+// segments concatenated, but that would be very wasteful on the network.
+//
+// Instead, this array special cases the first element as the path in the
+// account trie and the remaining elements as paths in the storage trie. To
+// address an account node, the slice should have a length of 1 consisting
+// of only the account path. There's no need to be able to address both an
+// account node and a storage node in the same request as it cannot happen
+// that a slot is accessed before the account path is fully expanded.
+type TrieNodePathSet [][]byte
+
+// TrieNodesPacket represents a state trie node query response.
+type TrieNodesPacket struct {
+	ID    uint64   // ID of the request this is a response for
+	Nodes [][]byte // Requested state trie nodes
+}
+
+func (*GetAccountRangePacket) Name() string { return "GetAccountRange" }
+func (*GetAccountRangePacket) Kind() byte   { return GetAccountRangeMsg }
+
+func (*AccountRangePacket) Name() string { return "AccountRange" }
+func (*AccountRangePacket) Kind() byte   { return AccountRangeMsg }
+
+func (*GetStorageRangesPacket) Name() string { return "GetStorageRanges" }
+func (*GetStorageRangesPacket) Kind() byte   { return GetStorageRangesMsg }
+
+func (*StorageRangesPacket) Name() string { return "StorageRanges" }
+func (*StorageRangesPacket) Kind() byte   { return StorageRangesMsg }
+
+func (*GetByteCodesPacket) Name() string { return "GetByteCodes" }
+func (*GetByteCodesPacket) Kind() byte   { return GetByteCodesMsg }
+
+func (*ByteCodesPacket) Name() string { return "ByteCodes" }
+func (*ByteCodesPacket) Kind() byte   { return ByteCodesMsg }
+
+func (*GetTrieNodesPacket) Name() string { return "GetTrieNodes" }
+func (*GetTrieNodesPacket) Kind() byte   { return GetTrieNodesMsg }
+
+func (*TrieNodesPacket) Name() string { return "TrieNodes" }
+func (*TrieNodesPacket) Kind() byte   { return TrieNodesMsg }
diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go
new file mode 100644
index 0000000000..dd380ff471
--- /dev/null
+++ b/eth/protocols/snap/range.go
@@ -0,0 +1,80 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package snap
+
+import (
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/holiman/uint256"
+)
+
+// hashRange is a utility to handle ranges of hashes, Split up the
+// hash-space into sections, and 'walk' over the sections
+type hashRange struct {
+	current *uint256.Int
+	step    *uint256.Int
+}
+
+// newHashRange creates a new hashRange, initiated at the start position,
+// and with the step set to fill the desired 'num' chunks
+func newHashRange(start common.Hash, num uint64) *hashRange {
+	left := new(big.Int).Sub(hashSpace, start.Big())
+	step := new(big.Int).Div(
+		new(big.Int).Add(left, new(big.Int).SetUint64(num-1)),
+		new(big.Int).SetUint64(num),
+	)
+	step256 := new(uint256.Int)
+	step256.SetFromBig(step)
+
+	return &hashRange{
+		current: uint256.NewInt().SetBytes32(start[:]),
+		step:    step256,
+	}
+}
+
+// Next pushes the hash range to the next interval.
+func (r *hashRange) Next() bool {
+	next := new(uint256.Int)
+	if overflow := next.AddOverflow(r.current, r.step); overflow {
+		return false
+	}
+	r.current = next
+	return true
+}
+
+// Start returns the first hash in the current interval.
+func (r *hashRange) Start() common.Hash {
+	return r.current.Bytes32()
+}
+
+// End returns the last hash in the current interval.
+func (r *hashRange) End() common.Hash {
+	// If the end overflows (non divisible range), return a shorter interval
+	next := new(uint256.Int)
+	if overflow := next.AddOverflow(r.current, r.step); overflow {
+		return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+	}
+	return new(uint256.Int).Sub(next, uint256.NewInt().SetOne()).Bytes32()
+}
+
+// incHash returns the next hash, in lexicographical order (a.k.a plus one)
+func incHash(h common.Hash) common.Hash {
+	a := uint256.NewInt().SetBytes32(h[:])
+	a.Add(a, uint256.NewInt().SetOne())
+	return common.Hash(a.Bytes32())
+}
diff --git a/eth/protocols/snap/range_test.go b/eth/protocols/snap/range_test.go
new file mode 100644
index 0000000000..23273e50bf
--- /dev/null
+++ b/eth/protocols/snap/range_test.go
@@ -0,0 +1,143 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package snap
+
+import (
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+)
+
+// Tests that given a starting hash and a density, the hash ranger can correctly
+// split up the remaining hash space into a fixed number of chunks.
+func TestHashRanges(t *testing.T) {
+	tests := []struct {
+		head   common.Hash
+		chunks uint64
+		starts []common.Hash
+		ends   []common.Hash
+	}{
+		// Simple test case to split the entire hash range into 4 chunks
+		{
+			head:   common.Hash{},
+			chunks: 4,
+			starts: []common.Hash{
+				{},
+				common.HexToHash("0x4000000000000000000000000000000000000000000000000000000000000000"),
+				common.HexToHash("0x8000000000000000000000000000000000000000000000000000000000000000"),
+				common.HexToHash("0xc000000000000000000000000000000000000000000000000000000000000000"),
+			},
+			ends: []common.Hash{
+				common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+				common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+				common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+				common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			},
+		},
+		// Split a divisible part of the hash range up into 2 chunks
+		{
+			head:   common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
+			chunks: 2,
+			starts: []common.Hash{
+				common.Hash{},
+				common.HexToHash("0x9000000000000000000000000000000000000000000000000000000000000000"),
+			},
+			ends: []common.Hash{
+				common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+				common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			},
+		},
+		// Split the entire hash range into a non divisible 3 chunks
+		{
+			head:   common.Hash{},
+			chunks: 3,
+			starts: []common.Hash{
+				{},
+				common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555556"),
+				common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"),
+			},
+			ends: []common.Hash{
+				common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"),
+				common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
+				common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			},
+		},
+		// Split a part of hash range into a non divisible 3 chunks
+		{
+			head:   common.HexToHash("0x2000000000000000000000000000000000000000000000000000000000000000"),
+			chunks: 3,
+			starts: []common.Hash{
+				{},
+				common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"),
+				common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555556"),
+			},
+			ends: []common.Hash{
+				common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+				common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"),
+				common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			},
+		},
+		// Split a part of hash range into a non divisible 3 chunks, but with a
+		// meaningful space size for manual verification.
+		//   - The head being 0xff...f0, we have 14 hashes left in the space
+		//   - Chunking up 14 into 3 pieces is 4.(6), but we need the ceil of 5 to avoid a micro-last-chunk
+		//   - Since the range is not divisible, the last interval will be shrter, capped at 0xff...f
+		//   - The chunk ranges thus needs to be [..0, ..5], [..6, ..b], [..c, ..f]
+		{
+			head:   common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"),
+			chunks: 3,
+			starts: []common.Hash{
+				{},
+				common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"),
+				common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"),
+			},
+			ends: []common.Hash{
+				common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"),
+				common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"),
+				common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			},
+		},
+	}
+	for i, tt := range tests {
+		r := newHashRange(tt.head, tt.chunks)
+
+		var (
+			starts = []common.Hash{{}}
+			ends   = []common.Hash{r.End()}
+		)
+		for r.Next() {
+			starts = append(starts, r.Start())
+			ends = append(ends, r.End())
+		}
+		if len(starts) != len(tt.starts) {
+			t.Errorf("test %d: starts count mismatch: have %d, want %d", i, len(starts), len(tt.starts))
+		}
+		for j := 0; j < len(starts) && j < len(tt.starts); j++ {
+			if starts[j] != tt.starts[j] {
+				t.Errorf("test %d, start %d: hash mismatch: have %x, want %x", i, j, starts[j], tt.starts[j])
+			}
+		}
+		if len(ends) != len(tt.ends) {
+			t.Errorf("test %d: ends count mismatch: have %d, want %d", i, len(ends), len(tt.ends))
+		}
+		for j := 0; j < len(ends) && j < len(tt.ends); j++ {
+			if ends[j] != tt.ends[j] {
+				t.Errorf("test %d, end %d: hash mismatch: have %x, want %x", i, j, ends[j], tt.ends[j])
+			}
+		}
+	}
+}
diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go
new file mode 100644
index 0000000000..e283473207
--- /dev/null
+++ b/eth/protocols/snap/sync.go
@@ -0,0 +1,2758 @@
+// 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 .
+
+package snap
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/big"
+	"math/rand"
+	"sort"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/state/snapshot"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+	"golang.org/x/crypto/sha3"
+)
+
+var (
+	// emptyRoot is the known root hash of an empty trie.
+	emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
+
+	// emptyCode is the known hash of the empty EVM bytecode.
+	emptyCode = crypto.Keccak256Hash(nil)
+)
+
+const (
+	// maxRequestSize is the maximum number of bytes to request from a remote peer.
+	maxRequestSize = 128 * 1024
+
+	// maxStorageSetRequestCount is the maximum number of contracts to request the
+	// storage of in a single query. If this number is too low, we're not filling
+	// responses fully and waste round trip times. If it's too high, we're capping
+	// responses and waste bandwidth.
+	maxStorageSetRequestCount = maxRequestSize / 1024
+
+	// maxCodeRequestCount is the maximum number of bytecode blobs to request in a
+	// single query. If this number is too low, we're not filling responses fully
+	// and waste round trip times. If it's too high, we're capping responses and
+	// waste bandwidth.
+	//
+	// Depoyed bytecodes are currently capped at 24KB, so the minimum request
+	// size should be maxRequestSize / 24K. Assuming that most contracts do not
+	// come close to that, requesting 4x should be a good approximation.
+	maxCodeRequestCount = maxRequestSize / (24 * 1024) * 4
+
+	// maxTrieRequestCount is the maximum number of trie node blobs to request in
+	// a single query. If this number is too low, we're not filling responses fully
+	// and waste round trip times. If it's too high, we're capping responses and
+	// waste bandwidth.
+	maxTrieRequestCount = 256
+)
+
+var (
+	// accountConcurrency is the number of chunks to split the account trie into
+	// to allow concurrent retrievals.
+	accountConcurrency = 16
+
+	// storageConcurrency is the number of chunks to split the a large contract
+	// storage trie into to allow concurrent retrievals.
+	storageConcurrency = 16
+
+	// requestTimeout is the maximum time a peer is allowed to spend on serving
+	// a single network request.
+	requestTimeout = 15 * time.Second // TODO(karalabe): Make it dynamic ala fast-sync?
+)
+
+// ErrCancelled is returned from snap syncing if the operation was prematurely
+// terminated.
+var ErrCancelled = errors.New("sync cancelled")
+
+// accountRequest tracks a pending account range request to ensure responses are
+// to actual requests and to validate any security constraints.
+//
+// Concurrency note: account requests and responses are handled concurrently from
+// the main runloop to allow Merkle proof verifications on the peer's thread and
+// to drop on invalid response. The request struct must contain all the data to
+// construct the response without accessing runloop internals (i.e. task). That
+// is only included to allow the runloop to match a response to the task being
+// synced without having yet another set of maps.
+type accountRequest struct {
+	peer string // Peer to which this request is assigned
+	id   uint64 // Request ID of this request
+
+	deliver chan *accountResponse // Channel to deliver successful response on
+	revert  chan *accountRequest  // Channel to deliver request failure on
+	cancel  chan struct{}         // Channel to track sync cancellation
+	timeout *time.Timer           // Timer to track delivery timeout
+	stale   chan struct{}         // Channel to signal the request was dropped
+
+	origin common.Hash // First account requested to allow continuation checks
+	limit  common.Hash // Last account requested to allow non-overlapping chunking
+
+	task *accountTask // Task which this request is filling (only access fields through the runloop!!)
+}
+
+// accountResponse is an already Merkle-verified remote response to an account
+// range request. It contains the subtrie for the requested account range and
+// the database that's going to be filled with the internal nodes on commit.
+type accountResponse struct {
+	task *accountTask // Task which this request is filling
+
+	hashes   []common.Hash    // Account hashes in the returned range
+	accounts []*state.Account // Expanded accounts in the returned range
+
+	cont bool // Whether the account range has a continuation
+}
+
+// bytecodeRequest tracks a pending bytecode request to ensure responses are to
+// actual requests and to validate any security constraints.
+//
+// Concurrency note: bytecode requests and responses are handled concurrently from
+// the main runloop to allow Keccak256 hash verifications on the peer's thread and
+// to drop on invalid response. The request struct must contain all the data to
+// construct the response without accessing runloop internals (i.e. task). That
+// is only included to allow the runloop to match a response to the task being
+// synced without having yet another set of maps.
+type bytecodeRequest struct {
+	peer string // Peer to which this request is assigned
+	id   uint64 // Request ID of this request
+
+	deliver chan *bytecodeResponse // Channel to deliver successful response on
+	revert  chan *bytecodeRequest  // Channel to deliver request failure on
+	cancel  chan struct{}          // Channel to track sync cancellation
+	timeout *time.Timer            // Timer to track delivery timeout
+	stale   chan struct{}          // Channel to signal the request was dropped
+
+	hashes []common.Hash // Bytecode hashes to validate responses
+	task   *accountTask  // Task which this request is filling (only access fields through the runloop!!)
+}
+
+// bytecodeResponse is an already verified remote response to a bytecode request.
+type bytecodeResponse struct {
+	task *accountTask // Task which this request is filling
+
+	hashes []common.Hash // Hashes of the bytecode to avoid double hashing
+	codes  [][]byte      // Actual bytecodes to store into the database (nil = missing)
+}
+
+// storageRequest tracks a pending storage ranges request to ensure responses are
+// to actual requests and to validate any security constraints.
+//
+// Concurrency note: storage requests and responses are handled concurrently from
+// the main runloop to allow Merkel proof verifications on the peer's thread and
+// to drop on invalid response. The request struct must contain all the data to
+// construct the response without accessing runloop internals (i.e. tasks). That
+// is only included to allow the runloop to match a response to the task being
+// synced without having yet another set of maps.
+type storageRequest struct {
+	peer string // Peer to which this request is assigned
+	id   uint64 // Request ID of this request
+
+	deliver chan *storageResponse // Channel to deliver successful response on
+	revert  chan *storageRequest  // Channel to deliver request failure on
+	cancel  chan struct{}         // Channel to track sync cancellation
+	timeout *time.Timer           // Timer to track delivery timeout
+	stale   chan struct{}         // Channel to signal the request was dropped
+
+	accounts []common.Hash // Account hashes to validate responses
+	roots    []common.Hash // Storage roots to validate responses
+
+	origin common.Hash // First storage slot requested to allow continuation checks
+	limit  common.Hash // Last storage slot requested to allow non-overlapping chunking
+
+	mainTask *accountTask // Task which this response belongs to (only access fields through the runloop!!)
+	subTask  *storageTask // Task which this response is filling (only access fields through the runloop!!)
+}
+
+// storageResponse is an already Merkle-verified remote response to a storage
+// range request. It contains the subtries for the requested storage ranges and
+// the databases that's going to be filled with the internal nodes on commit.
+type storageResponse struct {
+	mainTask *accountTask // Task which this response belongs to
+	subTask  *storageTask // Task which this response is filling
+
+	accounts []common.Hash // Account hashes requested, may be only partially filled
+	roots    []common.Hash // Storage roots requested, may be only partially filled
+
+	hashes [][]common.Hash // Storage slot hashes in the returned range
+	slots  [][][]byte      // Storage slot values in the returned range
+
+	cont bool // Whether the last storage range has a continuation
+}
+
+// trienodeHealRequest tracks a pending state trie request to ensure responses
+// are to actual requests and to validate any security constraints.
+//
+// Concurrency note: trie node requests and responses are handled concurrently from
+// the main runloop to allow Keccak256 hash verifications on the peer's thread and
+// to drop on invalid response. The request struct must contain all the data to
+// construct the response without accessing runloop internals (i.e. task). That
+// is only included to allow the runloop to match a response to the task being
+// synced without having yet another set of maps.
+type trienodeHealRequest struct {
+	peer string // Peer to which this request is assigned
+	id   uint64 // Request ID of this request
+
+	deliver chan *trienodeHealResponse // Channel to deliver successful response on
+	revert  chan *trienodeHealRequest  // Channel to deliver request failure on
+	cancel  chan struct{}              // Channel to track sync cancellation
+	timeout *time.Timer                // Timer to track delivery timeout
+	stale   chan struct{}              // Channel to signal the request was dropped
+
+	hashes []common.Hash   // Trie node hashes to validate responses
+	paths  []trie.SyncPath // Trie node paths requested for rescheduling
+
+	task *healTask // Task which this request is filling (only access fields through the runloop!!)
+}
+
+// trienodeHealResponse is an already verified remote response to a trie node request.
+type trienodeHealResponse struct {
+	task *healTask // Task which this request is filling
+
+	hashes []common.Hash   // Hashes of the trie nodes to avoid double hashing
+	paths  []trie.SyncPath // Trie node paths requested for rescheduling missing ones
+	nodes  [][]byte        // Actual trie nodes to store into the database (nil = missing)
+}
+
+// bytecodeHealRequest tracks a pending bytecode request to ensure responses are to
+// actual requests and to validate any security constraints.
+//
+// Concurrency note: bytecode requests and responses are handled concurrently from
+// the main runloop to allow Keccak256 hash verifications on the peer's thread and
+// to drop on invalid response. The request struct must contain all the data to
+// construct the response without accessing runloop internals (i.e. task). That
+// is only included to allow the runloop to match a response to the task being
+// synced without having yet another set of maps.
+type bytecodeHealRequest struct {
+	peer string // Peer to which this request is assigned
+	id   uint64 // Request ID of this request
+
+	deliver chan *bytecodeHealResponse // Channel to deliver successful response on
+	revert  chan *bytecodeHealRequest  // Channel to deliver request failure on
+	cancel  chan struct{}              // Channel to track sync cancellation
+	timeout *time.Timer                // Timer to track delivery timeout
+	stale   chan struct{}              // Channel to signal the request was dropped
+
+	hashes []common.Hash // Bytecode hashes to validate responses
+	task   *healTask     // Task which this request is filling (only access fields through the runloop!!)
+}
+
+// bytecodeHealResponse is an already verified remote response to a bytecode request.
+type bytecodeHealResponse struct {
+	task *healTask // Task which this request is filling
+
+	hashes []common.Hash // Hashes of the bytecode to avoid double hashing
+	codes  [][]byte      // Actual bytecodes to store into the database (nil = missing)
+}
+
+// accountTask represents the sync task for a chunk of the account snapshot.
+type accountTask struct {
+	// These fields get serialized to leveldb on shutdown
+	Next     common.Hash                    // Next account to sync in this interval
+	Last     common.Hash                    // Last account to sync in this interval
+	SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts
+
+	// These fields are internals used during runtime
+	req  *accountRequest  // Pending request to fill this task
+	res  *accountResponse // Validate response filling this task
+	pend int              // Number of pending subtasks for this round
+
+	needCode  []bool // Flags whether the filling accounts need code retrieval
+	needState []bool // Flags whether the filling accounts need storage retrieval
+	needHeal  []bool // Flags whether the filling accounts's state was chunked and need healing
+
+	codeTasks  map[common.Hash]struct{}    // Code hashes that need retrieval
+	stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval
+
+	genBatch ethdb.Batch     // Batch used by the node generator
+	genTrie  *trie.StackTrie // Node generator from storage slots
+
+	done bool // Flag whether the task can be removed
+}
+
+// storageTask represents the sync task for a chunk of the storage snapshot.
+type storageTask struct {
+	Next common.Hash // Next account to sync in this interval
+	Last common.Hash // Last account to sync in this interval
+
+	// These fields are internals used during runtime
+	root common.Hash     // Storage root hash for this instance
+	req  *storageRequest // Pending request to fill this task
+
+	genBatch ethdb.Batch     // Batch used by the node generator
+	genTrie  *trie.StackTrie // Node generator from storage slots
+
+	done bool // Flag whether the task can be removed
+}
+
+// healTask represents the sync task for healing the snap-synced chunk boundaries.
+type healTask struct {
+	scheduler *trie.Sync // State trie sync scheduler defining the tasks
+
+	trieTasks map[common.Hash]trie.SyncPath // Set of trie node tasks currently queued for retrieval
+	codeTasks map[common.Hash]struct{}      // Set of byte code tasks currently queued for retrieval
+}
+
+// syncProgress is a database entry to allow suspending and resuming a snapshot state
+// sync. Opposed to full and fast sync, there is no way to restart a suspended
+// snap sync without prior knowledge of the suspension point.
+type syncProgress struct {
+	Tasks []*accountTask // The suspended account tasks (contract tasks within)
+
+	// Status report during syncing phase
+	AccountSynced  uint64             // Number of accounts downloaded
+	AccountBytes   common.StorageSize // Number of account trie bytes persisted to disk
+	BytecodeSynced uint64             // Number of bytecodes downloaded
+	BytecodeBytes  common.StorageSize // Number of bytecode bytes downloaded
+	StorageSynced  uint64             // Number of storage slots downloaded
+	StorageBytes   common.StorageSize // Number of storage trie bytes persisted to disk
+
+	// Status report during healing phase
+	TrienodeHealSynced uint64             // Number of state trie nodes downloaded
+	TrienodeHealBytes  common.StorageSize // Number of state trie bytes persisted to disk
+	TrienodeHealDups   uint64             // Number of state trie nodes already processed
+	TrienodeHealNops   uint64             // Number of state trie nodes not requested
+	BytecodeHealSynced uint64             // Number of bytecodes downloaded
+	BytecodeHealBytes  common.StorageSize // Number of bytecodes persisted to disk
+	BytecodeHealDups   uint64             // Number of bytecodes already processed
+	BytecodeHealNops   uint64             // Number of bytecodes not requested
+}
+
+// SyncPeer abstracts out the methods required for a peer to be synced against
+// with the goal of allowing the construction of mock peers without the full
+// blown networking.
+type SyncPeer interface {
+	// ID retrieves the peer's unique identifier.
+	ID() string
+
+	// RequestAccountRange fetches a batch of accounts rooted in a specific account
+	// trie, starting with the origin.
+	RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error
+
+	// RequestStorageRanges fetches a batch of storage slots belonging to one or
+	// more accounts. If slots from only one accout is requested, an origin marker
+	// may also be used to retrieve from there.
+	RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error
+
+	// RequestByteCodes fetches a batch of bytecodes by hash.
+	RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error
+
+	// RequestTrieNodes fetches a batch of account or storage trie nodes rooted in
+	// a specificstate trie.
+	RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error
+
+	// Log retrieves the peer's own contextual logger.
+	Log() log.Logger
+}
+
+// Syncer is an Ethereum account and storage trie syncer based on snapshots and
+// the  snap protocol. It's purpose is to download all the accounts and storage
+// slots from remote peers and reassemble chunks of the state trie, on top of
+// which a state sync can be run to fix any gaps / overlaps.
+//
+// Every network request has a variety of failure events:
+//   - The peer disconnects after task assignment, failing to send the request
+//   - The peer disconnects after sending the request, before delivering on it
+//   - The peer remains connected, but does not deliver a response in time
+//   - The peer delivers a stale response after a previous timeout
+//   - The peer delivers a refusal to serve the requested state
+type Syncer struct {
+	db ethdb.KeyValueStore // Database to store the trie nodes into (and dedup)
+
+	root    common.Hash    // Current state trie root being synced
+	tasks   []*accountTask // Current account task set being synced
+	snapped bool           // Flag to signal that snap phase is done
+	healer  *healTask      // Current state healing task being executed
+	update  chan struct{}  // Notification channel for possible sync progression
+
+	peers    map[string]SyncPeer // Currently active peers to download from
+	peerJoin *event.Feed         // Event feed to react to peers joining
+	peerDrop *event.Feed         // Event feed to react to peers dropping
+
+	// Request tracking during syncing phase
+	statelessPeers map[string]struct{} // Peers that failed to deliver state data
+	accountIdlers  map[string]struct{} // Peers that aren't serving account requests
+	bytecodeIdlers map[string]struct{} // Peers that aren't serving bytecode requests
+	storageIdlers  map[string]struct{} // Peers that aren't serving storage requests
+
+	accountReqs  map[uint64]*accountRequest  // Account requests currently running
+	bytecodeReqs map[uint64]*bytecodeRequest // Bytecode requests currently running
+	storageReqs  map[uint64]*storageRequest  // Storage requests currently running
+
+	accountSynced  uint64             // Number of accounts downloaded
+	accountBytes   common.StorageSize // Number of account trie bytes persisted to disk
+	bytecodeSynced uint64             // Number of bytecodes downloaded
+	bytecodeBytes  common.StorageSize // Number of bytecode bytes downloaded
+	storageSynced  uint64             // Number of storage slots downloaded
+	storageBytes   common.StorageSize // Number of storage trie bytes persisted to disk
+
+	// Request tracking during healing phase
+	trienodeHealIdlers map[string]struct{} // Peers that aren't serving trie node requests
+	bytecodeHealIdlers map[string]struct{} // Peers that aren't serving bytecode requests
+
+	trienodeHealReqs map[uint64]*trienodeHealRequest // Trie node requests currently running
+	bytecodeHealReqs map[uint64]*bytecodeHealRequest // Bytecode requests currently running
+
+	trienodeHealSynced uint64             // Number of state trie nodes downloaded
+	trienodeHealBytes  common.StorageSize // Number of state trie bytes persisted to disk
+	trienodeHealDups   uint64             // Number of state trie nodes already processed
+	trienodeHealNops   uint64             // Number of state trie nodes not requested
+	bytecodeHealSynced uint64             // Number of bytecodes downloaded
+	bytecodeHealBytes  common.StorageSize // Number of bytecodes persisted to disk
+	bytecodeHealDups   uint64             // Number of bytecodes already processed
+	bytecodeHealNops   uint64             // Number of bytecodes not requested
+
+	stateWriter        ethdb.Batch        // Shared batch writer used for persisting raw states
+	accountHealed      uint64             // Number of accounts downloaded during the healing stage
+	accountHealedBytes common.StorageSize // Number of raw account bytes persisted to disk during the healing stage
+	storageHealed      uint64             // Number of storage slots downloaded during the healing stage
+	storageHealedBytes common.StorageSize // Number of raw storage bytes persisted to disk during the healing stage
+
+	startTime time.Time // Time instance when snapshot sync started
+	logTime   time.Time // Time instance when status was last reported
+
+	pend sync.WaitGroup // Tracks network request goroutines for graceful shutdown
+	lock sync.RWMutex   // Protects fields that can change outside of sync (peers, reqs, root)
+}
+
+// NewSyncer creates a new snapshot syncer to download the Ethereum state over the
+// snap protocol.
+func NewSyncer(db ethdb.KeyValueStore) *Syncer {
+	return &Syncer{
+		db: db,
+
+		peers:    make(map[string]SyncPeer),
+		peerJoin: new(event.Feed),
+		peerDrop: new(event.Feed),
+		update:   make(chan struct{}, 1),
+
+		accountIdlers:  make(map[string]struct{}),
+		storageIdlers:  make(map[string]struct{}),
+		bytecodeIdlers: make(map[string]struct{}),
+
+		accountReqs:  make(map[uint64]*accountRequest),
+		storageReqs:  make(map[uint64]*storageRequest),
+		bytecodeReqs: make(map[uint64]*bytecodeRequest),
+
+		trienodeHealIdlers: make(map[string]struct{}),
+		bytecodeHealIdlers: make(map[string]struct{}),
+
+		trienodeHealReqs: make(map[uint64]*trienodeHealRequest),
+		bytecodeHealReqs: make(map[uint64]*bytecodeHealRequest),
+		stateWriter:      db.NewBatch(),
+	}
+}
+
+// Register injects a new data source into the syncer's peerset.
+func (s *Syncer) Register(peer SyncPeer) error {
+	// Make sure the peer is not registered yet
+	id := peer.ID()
+
+	s.lock.Lock()
+	if _, ok := s.peers[id]; ok {
+		log.Error("Snap peer already registered", "id", id)
+
+		s.lock.Unlock()
+		return errors.New("already registered")
+	}
+	s.peers[id] = peer
+
+	// Mark the peer as idle, even if no sync is running
+	s.accountIdlers[id] = struct{}{}
+	s.storageIdlers[id] = struct{}{}
+	s.bytecodeIdlers[id] = struct{}{}
+	s.trienodeHealIdlers[id] = struct{}{}
+	s.bytecodeHealIdlers[id] = struct{}{}
+	s.lock.Unlock()
+
+	// Notify any active syncs that a new peer can be assigned data
+	s.peerJoin.Send(id)
+	return nil
+}
+
+// Unregister injects a new data source into the syncer's peerset.
+func (s *Syncer) Unregister(id string) error {
+	// Remove all traces of the peer from the registry
+	s.lock.Lock()
+	if _, ok := s.peers[id]; !ok {
+		log.Error("Snap peer not registered", "id", id)
+
+		s.lock.Unlock()
+		return errors.New("not registered")
+	}
+	delete(s.peers, id)
+
+	// Remove status markers, even if no sync is running
+	delete(s.statelessPeers, id)
+
+	delete(s.accountIdlers, id)
+	delete(s.storageIdlers, id)
+	delete(s.bytecodeIdlers, id)
+	delete(s.trienodeHealIdlers, id)
+	delete(s.bytecodeHealIdlers, id)
+	s.lock.Unlock()
+
+	// Notify any active syncs that pending requests need to be reverted
+	s.peerDrop.Send(id)
+	return nil
+}
+
+// Sync starts (or resumes a previous) sync cycle to iterate over an state trie
+// with the given root and reconstruct the nodes based on the snapshot leaves.
+// Previously downloaded segments will not be redownloaded of fixed, rather any
+// errors will be healed after the leaves are fully accumulated.
+func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error {
+	// Move the trie root from any previous value, revert stateless markers for
+	// any peers and initialize the syncer if it was not yet run
+	s.lock.Lock()
+	s.root = root
+	s.healer = &healTask{
+		scheduler: state.NewStateSync(root, s.db, nil, s.onHealState),
+		trieTasks: make(map[common.Hash]trie.SyncPath),
+		codeTasks: make(map[common.Hash]struct{}),
+	}
+	s.statelessPeers = make(map[string]struct{})
+	s.lock.Unlock()
+
+	if s.startTime == (time.Time{}) {
+		s.startTime = time.Now()
+	}
+	// Retrieve the previous sync status from LevelDB and abort if already synced
+	s.loadSyncStatus()
+	if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 {
+		log.Debug("Snapshot sync already completed")
+		return nil
+	}
+	defer func() { // Persist any progress, independent of failure
+		for _, task := range s.tasks {
+			s.forwardAccountTask(task)
+		}
+		s.cleanAccountTasks()
+		s.saveSyncStatus()
+	}()
+
+	log.Debug("Starting snapshot sync cycle", "root", root)
+
+	// Flush out the last committed raw states
+	defer func() {
+		if s.stateWriter.ValueSize() > 0 {
+			s.stateWriter.Write()
+			s.stateWriter.Reset()
+		}
+	}()
+	defer s.report(true)
+
+	// Whether sync completed or not, disregard any future packets
+	defer func() {
+		log.Debug("Terminating snapshot sync cycle", "root", root)
+		s.lock.Lock()
+		s.accountReqs = make(map[uint64]*accountRequest)
+		s.storageReqs = make(map[uint64]*storageRequest)
+		s.bytecodeReqs = make(map[uint64]*bytecodeRequest)
+		s.trienodeHealReqs = make(map[uint64]*trienodeHealRequest)
+		s.bytecodeHealReqs = make(map[uint64]*bytecodeHealRequest)
+		s.lock.Unlock()
+	}()
+	// Keep scheduling sync tasks
+	peerJoin := make(chan string, 16)
+	peerJoinSub := s.peerJoin.Subscribe(peerJoin)
+	defer peerJoinSub.Unsubscribe()
+
+	peerDrop := make(chan string, 16)
+	peerDropSub := s.peerDrop.Subscribe(peerDrop)
+	defer peerDropSub.Unsubscribe()
+
+	// Create a set of unique channels for this sync cycle. We need these to be
+	// ephemeral so a data race doesn't accidentally deliver something stale on
+	// a persistent channel across syncs (yup, this happened)
+	var (
+		accountReqFails      = make(chan *accountRequest)
+		storageReqFails      = make(chan *storageRequest)
+		bytecodeReqFails     = make(chan *bytecodeRequest)
+		accountResps         = make(chan *accountResponse)
+		storageResps         = make(chan *storageResponse)
+		bytecodeResps        = make(chan *bytecodeResponse)
+		trienodeHealReqFails = make(chan *trienodeHealRequest)
+		bytecodeHealReqFails = make(chan *bytecodeHealRequest)
+		trienodeHealResps    = make(chan *trienodeHealResponse)
+		bytecodeHealResps    = make(chan *bytecodeHealResponse)
+	)
+	for {
+		// Remove all completed tasks and terminate sync if everything's done
+		s.cleanStorageTasks()
+		s.cleanAccountTasks()
+		if len(s.tasks) == 0 && s.healer.scheduler.Pending() == 0 {
+			return nil
+		}
+		// Assign all the data retrieval tasks to any free peers
+		s.assignAccountTasks(accountResps, accountReqFails, cancel)
+		s.assignBytecodeTasks(bytecodeResps, bytecodeReqFails, cancel)
+		s.assignStorageTasks(storageResps, storageReqFails, cancel)
+
+		if len(s.tasks) == 0 {
+			// Sync phase done, run heal phase
+			s.assignTrienodeHealTasks(trienodeHealResps, trienodeHealReqFails, cancel)
+			s.assignBytecodeHealTasks(bytecodeHealResps, bytecodeHealReqFails, cancel)
+		}
+		// Wait for something to happen
+		select {
+		case <-s.update:
+			// Something happened (new peer, delivery, timeout), recheck tasks
+		case <-peerJoin:
+			// A new peer joined, try to schedule it new tasks
+		case id := <-peerDrop:
+			s.revertRequests(id)
+		case <-cancel:
+			return ErrCancelled
+
+		case req := <-accountReqFails:
+			s.revertAccountRequest(req)
+		case req := <-bytecodeReqFails:
+			s.revertBytecodeRequest(req)
+		case req := <-storageReqFails:
+			s.revertStorageRequest(req)
+		case req := <-trienodeHealReqFails:
+			s.revertTrienodeHealRequest(req)
+		case req := <-bytecodeHealReqFails:
+			s.revertBytecodeHealRequest(req)
+
+		case res := <-accountResps:
+			s.processAccountResponse(res)
+		case res := <-bytecodeResps:
+			s.processBytecodeResponse(res)
+		case res := <-storageResps:
+			s.processStorageResponse(res)
+		case res := <-trienodeHealResps:
+			s.processTrienodeHealResponse(res)
+		case res := <-bytecodeHealResps:
+			s.processBytecodeHealResponse(res)
+		}
+		// Report stats if something meaningful happened
+		s.report(false)
+	}
+}
+
+// loadSyncStatus retrieves a previously aborted sync status from the database,
+// or generates a fresh one if none is available.
+func (s *Syncer) loadSyncStatus() {
+	var progress syncProgress
+
+	if status := rawdb.ReadSnapshotSyncStatus(s.db); status != nil {
+		if err := json.Unmarshal(status, &progress); err != nil {
+			log.Error("Failed to decode snap sync status", "err", err)
+		} else {
+			for _, task := range progress.Tasks {
+				log.Debug("Scheduled account sync task", "from", task.Next, "last", task.Last)
+			}
+			s.tasks = progress.Tasks
+			for _, task := range s.tasks {
+				task.genBatch = ethdb.HookedBatch{
+					Batch: s.db.NewBatch(),
+					OnPut: func(key []byte, value []byte) {
+						s.accountBytes += common.StorageSize(len(key) + len(value))
+					},
+				}
+				task.genTrie = trie.NewStackTrie(task.genBatch)
+
+				for _, subtasks := range task.SubTasks {
+					for _, subtask := range subtasks {
+						subtask.genBatch = ethdb.HookedBatch{
+							Batch: s.db.NewBatch(),
+							OnPut: func(key []byte, value []byte) {
+								s.storageBytes += common.StorageSize(len(key) + len(value))
+							},
+						}
+						subtask.genTrie = trie.NewStackTrie(subtask.genBatch)
+					}
+				}
+			}
+			s.snapped = len(s.tasks) == 0
+
+			s.accountSynced = progress.AccountSynced
+			s.accountBytes = progress.AccountBytes
+			s.bytecodeSynced = progress.BytecodeSynced
+			s.bytecodeBytes = progress.BytecodeBytes
+			s.storageSynced = progress.StorageSynced
+			s.storageBytes = progress.StorageBytes
+
+			s.trienodeHealSynced = progress.TrienodeHealSynced
+			s.trienodeHealBytes = progress.TrienodeHealBytes
+			s.bytecodeHealSynced = progress.BytecodeHealSynced
+			s.bytecodeHealBytes = progress.BytecodeHealBytes
+			return
+		}
+	}
+	// Either we've failed to decode the previus state, or there was none.
+	// Start a fresh sync by chunking up the account range and scheduling
+	// them for retrieval.
+	s.tasks = nil
+	s.accountSynced, s.accountBytes = 0, 0
+	s.bytecodeSynced, s.bytecodeBytes = 0, 0
+	s.storageSynced, s.storageBytes = 0, 0
+	s.trienodeHealSynced, s.trienodeHealBytes = 0, 0
+	s.bytecodeHealSynced, s.bytecodeHealBytes = 0, 0
+
+	var next common.Hash
+	step := new(big.Int).Sub(
+		new(big.Int).Div(
+			new(big.Int).Exp(common.Big2, common.Big256, nil),
+			big.NewInt(int64(accountConcurrency)),
+		), common.Big1,
+	)
+	for i := 0; i < accountConcurrency; i++ {
+		last := common.BigToHash(new(big.Int).Add(next.Big(), step))
+		if i == accountConcurrency-1 {
+			// Make sure we don't overflow if the step is not a proper divisor
+			last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+		}
+		batch := ethdb.HookedBatch{
+			Batch: s.db.NewBatch(),
+			OnPut: func(key []byte, value []byte) {
+				s.accountBytes += common.StorageSize(len(key) + len(value))
+			},
+		}
+		s.tasks = append(s.tasks, &accountTask{
+			Next:     next,
+			Last:     last,
+			SubTasks: make(map[common.Hash][]*storageTask),
+			genBatch: batch,
+			genTrie:  trie.NewStackTrie(batch),
+		})
+		log.Debug("Created account sync task", "from", next, "last", last)
+		next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
+	}
+}
+
+// saveSyncStatus marshals the remaining sync tasks into leveldb.
+func (s *Syncer) saveSyncStatus() {
+	// Serialize any partial progress to disk before spinning down
+	for _, task := range s.tasks {
+		if err := task.genBatch.Write(); err != nil {
+			log.Error("Failed to persist account slots", "err", err)
+		}
+		for _, subtasks := range task.SubTasks {
+			for _, subtask := range subtasks {
+				if err := subtask.genBatch.Write(); err != nil {
+					log.Error("Failed to persist storage slots", "err", err)
+				}
+			}
+		}
+	}
+	// Store the actual progress markers
+	progress := &syncProgress{
+		Tasks:              s.tasks,
+		AccountSynced:      s.accountSynced,
+		AccountBytes:       s.accountBytes,
+		BytecodeSynced:     s.bytecodeSynced,
+		BytecodeBytes:      s.bytecodeBytes,
+		StorageSynced:      s.storageSynced,
+		StorageBytes:       s.storageBytes,
+		TrienodeHealSynced: s.trienodeHealSynced,
+		TrienodeHealBytes:  s.trienodeHealBytes,
+		BytecodeHealSynced: s.bytecodeHealSynced,
+		BytecodeHealBytes:  s.bytecodeHealBytes,
+	}
+	status, err := json.Marshal(progress)
+	if err != nil {
+		panic(err) // This can only fail during implementation
+	}
+	rawdb.WriteSnapshotSyncStatus(s.db, status)
+}
+
+// cleanAccountTasks removes account range retrieval tasks that have already been
+// completed.
+func (s *Syncer) cleanAccountTasks() {
+	// If the sync was already done before, don't even bother
+	if len(s.tasks) == 0 {
+		return
+	}
+	// Sync wasn't finished previously, check for any task that can be finalized
+	for i := 0; i < len(s.tasks); i++ {
+		if s.tasks[i].done {
+			s.tasks = append(s.tasks[:i], s.tasks[i+1:]...)
+			i--
+		}
+	}
+	// If everything was just finalized just, generate the account trie and start heal
+	if len(s.tasks) == 0 {
+		s.lock.Lock()
+		s.snapped = true
+		s.lock.Unlock()
+
+		// Push the final sync report
+		s.reportSyncProgress(true)
+	}
+}
+
+// cleanStorageTasks iterates over all the account tasks and storage sub-tasks
+// within, cleaning any that have been completed.
+func (s *Syncer) cleanStorageTasks() {
+	for _, task := range s.tasks {
+		for account, subtasks := range task.SubTasks {
+			// Remove storage range retrieval tasks that completed
+			for j := 0; j < len(subtasks); j++ {
+				if subtasks[j].done {
+					subtasks = append(subtasks[:j], subtasks[j+1:]...)
+					j--
+				}
+			}
+			if len(subtasks) > 0 {
+				task.SubTasks[account] = subtasks
+				continue
+			}
+			// If all storage chunks are done, mark the account as done too
+			for j, hash := range task.res.hashes {
+				if hash == account {
+					task.needState[j] = false
+				}
+			}
+			delete(task.SubTasks, account)
+			task.pend--
+
+			// If this was the last pending task, forward the account task
+			if task.pend == 0 {
+				s.forwardAccountTask(task)
+			}
+		}
+	}
+}
+
+// assignAccountTasks attempts to match idle peers to pending account range
+// retrievals.
+func (s *Syncer) assignAccountTasks(success chan *accountResponse, fail chan *accountRequest, cancel chan struct{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	// If there are no idle peers, short circuit assignment
+	if len(s.accountIdlers) == 0 {
+		return
+	}
+	// Iterate over all the tasks and try to find a pending one
+	for _, task := range s.tasks {
+		// Skip any tasks already filling
+		if task.req != nil || task.res != nil {
+			continue
+		}
+		// Task pending retrieval, try to find an idle peer. If no such peer
+		// exists, we probably assigned tasks for all (or they are stateless).
+		// Abort the entire assignment mechanism.
+		var idle string
+		for id := range s.accountIdlers {
+			// If the peer rejected a query in this sync cycle, don't bother asking
+			// again for anything, it's either out of sync or already pruned
+			if _, ok := s.statelessPeers[id]; ok {
+				continue
+			}
+			idle = id
+			break
+		}
+		if idle == "" {
+			return
+		}
+		peer := s.peers[idle]
+
+		// Matched a pending task to an idle peer, allocate a unique request id
+		var reqid uint64
+		for {
+			reqid = uint64(rand.Int63())
+			if reqid == 0 {
+				continue
+			}
+			if _, ok := s.accountReqs[reqid]; ok {
+				continue
+			}
+			break
+		}
+		// Generate the network query and send it to the peer
+		req := &accountRequest{
+			peer:    idle,
+			id:      reqid,
+			deliver: success,
+			revert:  fail,
+			cancel:  cancel,
+			stale:   make(chan struct{}),
+			origin:  task.Next,
+			limit:   task.Last,
+			task:    task,
+		}
+		req.timeout = time.AfterFunc(requestTimeout, func() {
+			peer.Log().Debug("Account range request timed out", "reqid", reqid)
+			s.scheduleRevertAccountRequest(req)
+		})
+		s.accountReqs[reqid] = req
+		delete(s.accountIdlers, idle)
+
+		s.pend.Add(1)
+		go func(root common.Hash) {
+			defer s.pend.Done()
+
+			// Attempt to send the remote request and revert if it fails
+			if err := peer.RequestAccountRange(reqid, root, req.origin, req.limit, maxRequestSize); err != nil {
+				peer.Log().Debug("Failed to request account range", "err", err)
+				s.scheduleRevertAccountRequest(req)
+			}
+		}(s.root)
+
+		// Inject the request into the task to block further assignments
+		task.req = req
+	}
+}
+
+// assignBytecodeTasks attempts to match idle peers to pending code retrievals.
+func (s *Syncer) assignBytecodeTasks(success chan *bytecodeResponse, fail chan *bytecodeRequest, cancel chan struct{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	// If there are no idle peers, short circuit assignment
+	if len(s.bytecodeIdlers) == 0 {
+		return
+	}
+	// Iterate over all the tasks and try to find a pending one
+	for _, task := range s.tasks {
+		// Skip any tasks not in the bytecode retrieval phase
+		if task.res == nil {
+			continue
+		}
+		// Skip tasks that are already retrieving (or done with) all codes
+		if len(task.codeTasks) == 0 {
+			continue
+		}
+		// Task pending retrieval, try to find an idle peer. If no such peer
+		// exists, we probably assigned tasks for all (or they are stateless).
+		// Abort the entire assignment mechanism.
+		var idle string
+		for id := range s.bytecodeIdlers {
+			// If the peer rejected a query in this sync cycle, don't bother asking
+			// again for anything, it's either out of sync or already pruned
+			if _, ok := s.statelessPeers[id]; ok {
+				continue
+			}
+			idle = id
+			break
+		}
+		if idle == "" {
+			return
+		}
+		peer := s.peers[idle]
+
+		// Matched a pending task to an idle peer, allocate a unique request id
+		var reqid uint64
+		for {
+			reqid = uint64(rand.Int63())
+			if reqid == 0 {
+				continue
+			}
+			if _, ok := s.bytecodeReqs[reqid]; ok {
+				continue
+			}
+			break
+		}
+		// Generate the network query and send it to the peer
+		hashes := make([]common.Hash, 0, maxCodeRequestCount)
+		for hash := range task.codeTasks {
+			delete(task.codeTasks, hash)
+			hashes = append(hashes, hash)
+			if len(hashes) >= maxCodeRequestCount {
+				break
+			}
+		}
+		req := &bytecodeRequest{
+			peer:    idle,
+			id:      reqid,
+			deliver: success,
+			revert:  fail,
+			cancel:  cancel,
+			stale:   make(chan struct{}),
+			hashes:  hashes,
+			task:    task,
+		}
+		req.timeout = time.AfterFunc(requestTimeout, func() {
+			peer.Log().Debug("Bytecode request timed out", "reqid", reqid)
+			s.scheduleRevertBytecodeRequest(req)
+		})
+		s.bytecodeReqs[reqid] = req
+		delete(s.bytecodeIdlers, idle)
+
+		s.pend.Add(1)
+		go func() {
+			defer s.pend.Done()
+
+			// Attempt to send the remote request and revert if it fails
+			if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil {
+				log.Debug("Failed to request bytecodes", "err", err)
+				s.scheduleRevertBytecodeRequest(req)
+			}
+		}()
+	}
+}
+
+// assignStorageTasks attempts to match idle peers to pending storage range
+// retrievals.
+func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *storageRequest, cancel chan struct{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	// If there are no idle peers, short circuit assignment
+	if len(s.storageIdlers) == 0 {
+		return
+	}
+	// Iterate over all the tasks and try to find a pending one
+	for _, task := range s.tasks {
+		// Skip any tasks not in the storage retrieval phase
+		if task.res == nil {
+			continue
+		}
+		// Skip tasks that are already retrieving (or done with) all small states
+		if len(task.SubTasks) == 0 && len(task.stateTasks) == 0 {
+			continue
+		}
+		// Task pending retrieval, try to find an idle peer. If no such peer
+		// exists, we probably assigned tasks for all (or they are stateless).
+		// Abort the entire assignment mechanism.
+		var idle string
+		for id := range s.storageIdlers {
+			// If the peer rejected a query in this sync cycle, don't bother asking
+			// again for anything, it's either out of sync or already pruned
+			if _, ok := s.statelessPeers[id]; ok {
+				continue
+			}
+			idle = id
+			break
+		}
+		if idle == "" {
+			return
+		}
+		peer := s.peers[idle]
+
+		// Matched a pending task to an idle peer, allocate a unique request id
+		var reqid uint64
+		for {
+			reqid = uint64(rand.Int63())
+			if reqid == 0 {
+				continue
+			}
+			if _, ok := s.storageReqs[reqid]; ok {
+				continue
+			}
+			break
+		}
+		// Generate the network query and send it to the peer. If there are
+		// large contract tasks pending, complete those before diving into
+		// even more new contracts.
+		var (
+			accounts = make([]common.Hash, 0, maxStorageSetRequestCount)
+			roots    = make([]common.Hash, 0, maxStorageSetRequestCount)
+			subtask  *storageTask
+		)
+		for account, subtasks := range task.SubTasks {
+			for _, st := range subtasks {
+				// Skip any subtasks already filling
+				if st.req != nil {
+					continue
+				}
+				// Found an incomplete storage chunk, schedule it
+				accounts = append(accounts, account)
+				roots = append(roots, st.root)
+				subtask = st
+				break // Large contract chunks are downloaded individually
+			}
+			if subtask != nil {
+				break // Large contract chunks are downloaded individually
+			}
+		}
+		if subtask == nil {
+			// No large contract required retrieval, but small ones available
+			for acccount, root := range task.stateTasks {
+				delete(task.stateTasks, acccount)
+
+				accounts = append(accounts, acccount)
+				roots = append(roots, root)
+
+				if len(accounts) >= maxStorageSetRequestCount {
+					break
+				}
+			}
+		}
+		// If nothing was found, it means this task is actually already fully
+		// retrieving, but large contracts are hard to detect. Skip to the next.
+		if len(accounts) == 0 {
+			continue
+		}
+		req := &storageRequest{
+			peer:     idle,
+			id:       reqid,
+			deliver:  success,
+			revert:   fail,
+			cancel:   cancel,
+			stale:    make(chan struct{}),
+			accounts: accounts,
+			roots:    roots,
+			mainTask: task,
+			subTask:  subtask,
+		}
+		if subtask != nil {
+			req.origin = subtask.Next
+			req.limit = subtask.Last
+		}
+		req.timeout = time.AfterFunc(requestTimeout, func() {
+			peer.Log().Debug("Storage request timed out", "reqid", reqid)
+			s.scheduleRevertStorageRequest(req)
+		})
+		s.storageReqs[reqid] = req
+		delete(s.storageIdlers, idle)
+
+		s.pend.Add(1)
+		go func(root common.Hash) {
+			defer s.pend.Done()
+
+			// Attempt to send the remote request and revert if it fails
+			var origin, limit []byte
+			if subtask != nil {
+				origin, limit = req.origin[:], req.limit[:]
+			}
+			if err := peer.RequestStorageRanges(reqid, root, accounts, origin, limit, maxRequestSize); err != nil {
+				log.Debug("Failed to request storage", "err", err)
+				s.scheduleRevertStorageRequest(req)
+			}
+		}(s.root)
+
+		// Inject the request into the subtask to block further assignments
+		if subtask != nil {
+			subtask.req = req
+		}
+	}
+}
+
+// assignTrienodeHealTasks attempts to match idle peers to trie node requests to
+// heal any trie errors caused by the snap sync's chunked retrieval model.
+func (s *Syncer) assignTrienodeHealTasks(success chan *trienodeHealResponse, fail chan *trienodeHealRequest, cancel chan struct{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	// If there are no idle peers, short circuit assignment
+	if len(s.trienodeHealIdlers) == 0 {
+		return
+	}
+	// Iterate over pending tasks and try to find a peer to retrieve with
+	for len(s.healer.trieTasks) > 0 || s.healer.scheduler.Pending() > 0 {
+		// If there are not enough trie tasks queued to fully assign, fill the
+		// queue from the state sync scheduler. The trie synced schedules these
+		// together with bytecodes, so we need to queue them combined.
+		var (
+			have = len(s.healer.trieTasks) + len(s.healer.codeTasks)
+			want = maxTrieRequestCount + maxCodeRequestCount
+		)
+		if have < want {
+			nodes, paths, codes := s.healer.scheduler.Missing(want - have)
+			for i, hash := range nodes {
+				s.healer.trieTasks[hash] = paths[i]
+			}
+			for _, hash := range codes {
+				s.healer.codeTasks[hash] = struct{}{}
+			}
+		}
+		// If all the heal tasks are bytecodes or already downloading, bail
+		if len(s.healer.trieTasks) == 0 {
+			return
+		}
+		// Task pending retrieval, try to find an idle peer. If no such peer
+		// exists, we probably assigned tasks for all (or they are stateless).
+		// Abort the entire assignment mechanism.
+		var idle string
+		for id := range s.trienodeHealIdlers {
+			// If the peer rejected a query in this sync cycle, don't bother asking
+			// again for anything, it's either out of sync or already pruned
+			if _, ok := s.statelessPeers[id]; ok {
+				continue
+			}
+			idle = id
+			break
+		}
+		if idle == "" {
+			return
+		}
+		peer := s.peers[idle]
+
+		// Matched a pending task to an idle peer, allocate a unique request id
+		var reqid uint64
+		for {
+			reqid = uint64(rand.Int63())
+			if reqid == 0 {
+				continue
+			}
+			if _, ok := s.trienodeHealReqs[reqid]; ok {
+				continue
+			}
+			break
+		}
+		// Generate the network query and send it to the peer
+		var (
+			hashes   = make([]common.Hash, 0, maxTrieRequestCount)
+			paths    = make([]trie.SyncPath, 0, maxTrieRequestCount)
+			pathsets = make([]TrieNodePathSet, 0, maxTrieRequestCount)
+		)
+		for hash, pathset := range s.healer.trieTasks {
+			delete(s.healer.trieTasks, hash)
+
+			hashes = append(hashes, hash)
+			paths = append(paths, pathset)
+			pathsets = append(pathsets, [][]byte(pathset)) // TODO(karalabe): group requests by account hash
+
+			if len(hashes) >= maxTrieRequestCount {
+				break
+			}
+		}
+		req := &trienodeHealRequest{
+			peer:    idle,
+			id:      reqid,
+			deliver: success,
+			revert:  fail,
+			cancel:  cancel,
+			stale:   make(chan struct{}),
+			hashes:  hashes,
+			paths:   paths,
+			task:    s.healer,
+		}
+		req.timeout = time.AfterFunc(requestTimeout, func() {
+			peer.Log().Debug("Trienode heal request timed out", "reqid", reqid)
+			s.scheduleRevertTrienodeHealRequest(req)
+		})
+		s.trienodeHealReqs[reqid] = req
+		delete(s.trienodeHealIdlers, idle)
+
+		s.pend.Add(1)
+		go func(root common.Hash) {
+			defer s.pend.Done()
+
+			// Attempt to send the remote request and revert if it fails
+			if err := peer.RequestTrieNodes(reqid, root, pathsets, maxRequestSize); err != nil {
+				log.Debug("Failed to request trienode healers", "err", err)
+				s.scheduleRevertTrienodeHealRequest(req)
+			}
+		}(s.root)
+	}
+}
+
+// assignBytecodeHealTasks attempts to match idle peers to bytecode requests to
+// heal any trie errors caused by the snap sync's chunked retrieval model.
+func (s *Syncer) assignBytecodeHealTasks(success chan *bytecodeHealResponse, fail chan *bytecodeHealRequest, cancel chan struct{}) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	// If there are no idle peers, short circuit assignment
+	if len(s.bytecodeHealIdlers) == 0 {
+		return
+	}
+	// Iterate over pending tasks and try to find a peer to retrieve with
+	for len(s.healer.codeTasks) > 0 || s.healer.scheduler.Pending() > 0 {
+		// If there are not enough trie tasks queued to fully assign, fill the
+		// queue from the state sync scheduler. The trie synced schedules these
+		// together with trie nodes, so we need to queue them combined.
+		var (
+			have = len(s.healer.trieTasks) + len(s.healer.codeTasks)
+			want = maxTrieRequestCount + maxCodeRequestCount
+		)
+		if have < want {
+			nodes, paths, codes := s.healer.scheduler.Missing(want - have)
+			for i, hash := range nodes {
+				s.healer.trieTasks[hash] = paths[i]
+			}
+			for _, hash := range codes {
+				s.healer.codeTasks[hash] = struct{}{}
+			}
+		}
+		// If all the heal tasks are trienodes or already downloading, bail
+		if len(s.healer.codeTasks) == 0 {
+			return
+		}
+		// Task pending retrieval, try to find an idle peer. If no such peer
+		// exists, we probably assigned tasks for all (or they are stateless).
+		// Abort the entire assignment mechanism.
+		var idle string
+		for id := range s.bytecodeHealIdlers {
+			// If the peer rejected a query in this sync cycle, don't bother asking
+			// again for anything, it's either out of sync or already pruned
+			if _, ok := s.statelessPeers[id]; ok {
+				continue
+			}
+			idle = id
+			break
+		}
+		if idle == "" {
+			return
+		}
+		peer := s.peers[idle]
+
+		// Matched a pending task to an idle peer, allocate a unique request id
+		var reqid uint64
+		for {
+			reqid = uint64(rand.Int63())
+			if reqid == 0 {
+				continue
+			}
+			if _, ok := s.bytecodeHealReqs[reqid]; ok {
+				continue
+			}
+			break
+		}
+		// Generate the network query and send it to the peer
+		hashes := make([]common.Hash, 0, maxCodeRequestCount)
+		for hash := range s.healer.codeTasks {
+			delete(s.healer.codeTasks, hash)
+
+			hashes = append(hashes, hash)
+			if len(hashes) >= maxCodeRequestCount {
+				break
+			}
+		}
+		req := &bytecodeHealRequest{
+			peer:    idle,
+			id:      reqid,
+			deliver: success,
+			revert:  fail,
+			cancel:  cancel,
+			stale:   make(chan struct{}),
+			hashes:  hashes,
+			task:    s.healer,
+		}
+		req.timeout = time.AfterFunc(requestTimeout, func() {
+			peer.Log().Debug("Bytecode heal request timed out", "reqid", reqid)
+			s.scheduleRevertBytecodeHealRequest(req)
+		})
+		s.bytecodeHealReqs[reqid] = req
+		delete(s.bytecodeHealIdlers, idle)
+
+		s.pend.Add(1)
+		go func() {
+			defer s.pend.Done()
+
+			// Attempt to send the remote request and revert if it fails
+			if err := peer.RequestByteCodes(reqid, hashes, maxRequestSize); err != nil {
+				log.Debug("Failed to request bytecode healers", "err", err)
+				s.scheduleRevertBytecodeHealRequest(req)
+			}
+		}()
+	}
+}
+
+// revertRequests locates all the currently pending reuqests from a particular
+// peer and reverts them, rescheduling for others to fulfill.
+func (s *Syncer) revertRequests(peer string) {
+	// Gather the requests first, revertals need the lock too
+	s.lock.Lock()
+	var accountReqs []*accountRequest
+	for _, req := range s.accountReqs {
+		if req.peer == peer {
+			accountReqs = append(accountReqs, req)
+		}
+	}
+	var bytecodeReqs []*bytecodeRequest
+	for _, req := range s.bytecodeReqs {
+		if req.peer == peer {
+			bytecodeReqs = append(bytecodeReqs, req)
+		}
+	}
+	var storageReqs []*storageRequest
+	for _, req := range s.storageReqs {
+		if req.peer == peer {
+			storageReqs = append(storageReqs, req)
+		}
+	}
+	var trienodeHealReqs []*trienodeHealRequest
+	for _, req := range s.trienodeHealReqs {
+		if req.peer == peer {
+			trienodeHealReqs = append(trienodeHealReqs, req)
+		}
+	}
+	var bytecodeHealReqs []*bytecodeHealRequest
+	for _, req := range s.bytecodeHealReqs {
+		if req.peer == peer {
+			bytecodeHealReqs = append(bytecodeHealReqs, req)
+		}
+	}
+	s.lock.Unlock()
+
+	// Revert all the requests matching the peer
+	for _, req := range accountReqs {
+		s.revertAccountRequest(req)
+	}
+	for _, req := range bytecodeReqs {
+		s.revertBytecodeRequest(req)
+	}
+	for _, req := range storageReqs {
+		s.revertStorageRequest(req)
+	}
+	for _, req := range trienodeHealReqs {
+		s.revertTrienodeHealRequest(req)
+	}
+	for _, req := range bytecodeHealReqs {
+		s.revertBytecodeHealRequest(req)
+	}
+}
+
+// scheduleRevertAccountRequest asks the event loop to clean up an account range
+// request and return all failed retrieval tasks to the scheduler for reassignment.
+func (s *Syncer) scheduleRevertAccountRequest(req *accountRequest) {
+	select {
+	case req.revert <- req:
+		// Sync event loop notified
+	case <-req.cancel:
+		// Sync cycle got cancelled
+	case <-req.stale:
+		// Request already reverted
+	}
+}
+
+// revertAccountRequest cleans up an account range request and returns all failed
+// retrieval tasks to the scheduler for reassignment.
+//
+// Note, this needs to run on the event runloop thread to reschedule to idle peers.
+// On peer threads, use scheduleRevertAccountRequest.
+func (s *Syncer) revertAccountRequest(req *accountRequest) {
+	log.Debug("Reverting account request", "peer", req.peer, "reqid", req.id)
+	select {
+	case <-req.stale:
+		log.Trace("Account request already reverted", "peer", req.peer, "reqid", req.id)
+		return
+	default:
+	}
+	close(req.stale)
+
+	// Remove the request from the tracked set
+	s.lock.Lock()
+	delete(s.accountReqs, req.id)
+	s.lock.Unlock()
+
+	// If there's a timeout timer still running, abort it and mark the account
+	// task as not-pending, ready for resheduling
+	req.timeout.Stop()
+	if req.task.req == req {
+		req.task.req = nil
+	}
+}
+
+// scheduleRevertBytecodeRequest asks the event loop to clean up a bytecode request
+// and return all failed retrieval tasks to the scheduler for reassignment.
+func (s *Syncer) scheduleRevertBytecodeRequest(req *bytecodeRequest) {
+	select {
+	case req.revert <- req:
+		// Sync event loop notified
+	case <-req.cancel:
+		// Sync cycle got cancelled
+	case <-req.stale:
+		// Request already reverted
+	}
+}
+
+// revertBytecodeRequest cleans up a bytecode request and returns all failed
+// retrieval tasks to the scheduler for reassignment.
+//
+// Note, this needs to run on the event runloop thread to reschedule to idle peers.
+// On peer threads, use scheduleRevertBytecodeRequest.
+func (s *Syncer) revertBytecodeRequest(req *bytecodeRequest) {
+	log.Debug("Reverting bytecode request", "peer", req.peer)
+	select {
+	case <-req.stale:
+		log.Trace("Bytecode request already reverted", "peer", req.peer, "reqid", req.id)
+		return
+	default:
+	}
+	close(req.stale)
+
+	// Remove the request from the tracked set
+	s.lock.Lock()
+	delete(s.bytecodeReqs, req.id)
+	s.lock.Unlock()
+
+	// If there's a timeout timer still running, abort it and mark the code
+	// retrievals as not-pending, ready for resheduling
+	req.timeout.Stop()
+	for _, hash := range req.hashes {
+		req.task.codeTasks[hash] = struct{}{}
+	}
+}
+
+// scheduleRevertStorageRequest asks the event loop to clean up a storage range
+// request and return all failed retrieval tasks to the scheduler for reassignment.
+func (s *Syncer) scheduleRevertStorageRequest(req *storageRequest) {
+	select {
+	case req.revert <- req:
+		// Sync event loop notified
+	case <-req.cancel:
+		// Sync cycle got cancelled
+	case <-req.stale:
+		// Request already reverted
+	}
+}
+
+// revertStorageRequest cleans up a storage range request and returns all failed
+// retrieval tasks to the scheduler for reassignment.
+//
+// Note, this needs to run on the event runloop thread to reschedule to idle peers.
+// On peer threads, use scheduleRevertStorageRequest.
+func (s *Syncer) revertStorageRequest(req *storageRequest) {
+	log.Debug("Reverting storage request", "peer", req.peer)
+	select {
+	case <-req.stale:
+		log.Trace("Storage request already reverted", "peer", req.peer, "reqid", req.id)
+		return
+	default:
+	}
+	close(req.stale)
+
+	// Remove the request from the tracked set
+	s.lock.Lock()
+	delete(s.storageReqs, req.id)
+	s.lock.Unlock()
+
+	// If there's a timeout timer still running, abort it and mark the storage
+	// task as not-pending, ready for resheduling
+	req.timeout.Stop()
+	if req.subTask != nil {
+		req.subTask.req = nil
+	} else {
+		for i, account := range req.accounts {
+			req.mainTask.stateTasks[account] = req.roots[i]
+		}
+	}
+}
+
+// scheduleRevertTrienodeHealRequest asks the event loop to clean up a trienode heal
+// request and return all failed retrieval tasks to the scheduler for reassignment.
+func (s *Syncer) scheduleRevertTrienodeHealRequest(req *trienodeHealRequest) {
+	select {
+	case req.revert <- req:
+		// Sync event loop notified
+	case <-req.cancel:
+		// Sync cycle got cancelled
+	case <-req.stale:
+		// Request already reverted
+	}
+}
+
+// revertTrienodeHealRequest cleans up a trienode heal request and returns all
+// failed retrieval tasks to the scheduler for reassignment.
+//
+// Note, this needs to run on the event runloop thread to reschedule to idle peers.
+// On peer threads, use scheduleRevertTrienodeHealRequest.
+func (s *Syncer) revertTrienodeHealRequest(req *trienodeHealRequest) {
+	log.Debug("Reverting trienode heal request", "peer", req.peer)
+	select {
+	case <-req.stale:
+		log.Trace("Trienode heal request already reverted", "peer", req.peer, "reqid", req.id)
+		return
+	default:
+	}
+	close(req.stale)
+
+	// Remove the request from the tracked set
+	s.lock.Lock()
+	delete(s.trienodeHealReqs, req.id)
+	s.lock.Unlock()
+
+	// If there's a timeout timer still running, abort it and mark the trie node
+	// retrievals as not-pending, ready for resheduling
+	req.timeout.Stop()
+	for i, hash := range req.hashes {
+		req.task.trieTasks[hash] = req.paths[i]
+	}
+}
+
+// scheduleRevertBytecodeHealRequest asks the event loop to clean up a bytecode heal
+// request and return all failed retrieval tasks to the scheduler for reassignment.
+func (s *Syncer) scheduleRevertBytecodeHealRequest(req *bytecodeHealRequest) {
+	select {
+	case req.revert <- req:
+		// Sync event loop notified
+	case <-req.cancel:
+		// Sync cycle got cancelled
+	case <-req.stale:
+		// Request already reverted
+	}
+}
+
+// revertBytecodeHealRequest cleans up a bytecode heal request and returns all
+// failed retrieval tasks to the scheduler for reassignment.
+//
+// Note, this needs to run on the event runloop thread to reschedule to idle peers.
+// On peer threads, use scheduleRevertBytecodeHealRequest.
+func (s *Syncer) revertBytecodeHealRequest(req *bytecodeHealRequest) {
+	log.Debug("Reverting bytecode heal request", "peer", req.peer)
+	select {
+	case <-req.stale:
+		log.Trace("Bytecode heal request already reverted", "peer", req.peer, "reqid", req.id)
+		return
+	default:
+	}
+	close(req.stale)
+
+	// Remove the request from the tracked set
+	s.lock.Lock()
+	delete(s.bytecodeHealReqs, req.id)
+	s.lock.Unlock()
+
+	// If there's a timeout timer still running, abort it and mark the code
+	// retrievals as not-pending, ready for resheduling
+	req.timeout.Stop()
+	for _, hash := range req.hashes {
+		req.task.codeTasks[hash] = struct{}{}
+	}
+}
+
+// processAccountResponse integrates an already validated account range response
+// into the account tasks.
+func (s *Syncer) processAccountResponse(res *accountResponse) {
+	// Switch the task from pending to filling
+	res.task.req = nil
+	res.task.res = res
+
+	// Ensure that the response doesn't overflow into the subsequent task
+	last := res.task.Last.Big()
+	for i, hash := range res.hashes {
+		// Mark the range complete if the last is already included.
+		// Keep iteration to delete the extra states if exists.
+		cmp := hash.Big().Cmp(last)
+		if cmp == 0 {
+			res.cont = false
+			continue
+		}
+		if cmp > 0 {
+			// Chunk overflown, cut off excess
+			res.hashes = res.hashes[:i]
+			res.accounts = res.accounts[:i]
+			res.cont = false // Mark range completed
+			break
+		}
+	}
+	// Iterate over all the accounts and assemble which ones need further sub-
+	// filling before the entire account range can be persisted.
+	res.task.needCode = make([]bool, len(res.accounts))
+	res.task.needState = make([]bool, len(res.accounts))
+	res.task.needHeal = make([]bool, len(res.accounts))
+
+	res.task.codeTasks = make(map[common.Hash]struct{})
+	res.task.stateTasks = make(map[common.Hash]common.Hash)
+
+	resumed := make(map[common.Hash]struct{})
+
+	res.task.pend = 0
+	for i, account := range res.accounts {
+		// Check if the account is a contract with an unknown code
+		if !bytes.Equal(account.CodeHash, emptyCode[:]) {
+			if code := rawdb.ReadCodeWithPrefix(s.db, common.BytesToHash(account.CodeHash)); code == nil {
+				res.task.codeTasks[common.BytesToHash(account.CodeHash)] = struct{}{}
+				res.task.needCode[i] = true
+				res.task.pend++
+			}
+		}
+		// Check if the account is a contract with an unknown storage trie
+		if account.Root != emptyRoot {
+			if node, err := s.db.Get(account.Root[:]); err != nil || node == nil {
+				// If there was a previous large state retrieval in progress,
+				// don't restart it from scratch. This happens if a sync cycle
+				// is interrupted and resumed later. However, *do* update the
+				// previous root hash.
+				if subtasks, ok := res.task.SubTasks[res.hashes[i]]; ok {
+					log.Debug("Resuming large storage retrieval", "account", res.hashes[i], "root", account.Root)
+					for _, subtask := range subtasks {
+						subtask.root = account.Root
+					}
+					res.task.needHeal[i] = true
+					resumed[res.hashes[i]] = struct{}{}
+				} else {
+					res.task.stateTasks[res.hashes[i]] = account.Root
+				}
+				res.task.needState[i] = true
+				res.task.pend++
+			}
+		}
+	}
+	// Delete any subtasks that have been aborted but not resumed. This may undo
+	// some progress if a new peer gives us less accounts than an old one, but for
+	// now we have to live with that.
+	for hash := range res.task.SubTasks {
+		if _, ok := resumed[hash]; !ok {
+			log.Debug("Aborting suspended storage retrieval", "account", hash)
+			delete(res.task.SubTasks, hash)
+		}
+	}
+	// If the account range contained no contracts, or all have been fully filled
+	// beforehand, short circuit storage filling and forward to the next task
+	if res.task.pend == 0 {
+		s.forwardAccountTask(res.task)
+		return
+	}
+	// Some accounts are incomplete, leave as is for the storage and contract
+	// task assigners to pick up and fill.
+}
+
+// processBytecodeResponse integrates an already validated bytecode response
+// into the account tasks.
+func (s *Syncer) processBytecodeResponse(res *bytecodeResponse) {
+	batch := s.db.NewBatch()
+
+	var (
+		codes uint64
+	)
+	for i, hash := range res.hashes {
+		code := res.codes[i]
+
+		// If the bytecode was not delivered, reschedule it
+		if code == nil {
+			res.task.codeTasks[hash] = struct{}{}
+			continue
+		}
+		// Code was delivered, mark it not needed any more
+		for j, account := range res.task.res.accounts {
+			if res.task.needCode[j] && hash == common.BytesToHash(account.CodeHash) {
+				res.task.needCode[j] = false
+				res.task.pend--
+			}
+		}
+		// Push the bytecode into a database batch
+		codes++
+		rawdb.WriteCode(batch, hash, code)
+	}
+	bytes := common.StorageSize(batch.ValueSize())
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to persist bytecodes", "err", err)
+	}
+	s.bytecodeSynced += codes
+	s.bytecodeBytes += bytes
+
+	log.Debug("Persisted set of bytecodes", "count", codes, "bytes", bytes)
+
+	// If this delivery completed the last pending task, forward the account task
+	// to the next chunk
+	if res.task.pend == 0 {
+		s.forwardAccountTask(res.task)
+		return
+	}
+	// Some accounts are still incomplete, leave as is for the storage and contract
+	// task assigners to pick up and fill.
+}
+
+// processStorageResponse integrates an already validated storage response
+// into the account tasks.
+func (s *Syncer) processStorageResponse(res *storageResponse) {
+	// Switch the subtask from pending to idle
+	if res.subTask != nil {
+		res.subTask.req = nil
+	}
+	batch := ethdb.HookedBatch{
+		Batch: s.db.NewBatch(),
+		OnPut: func(key []byte, value []byte) {
+			s.storageBytes += common.StorageSize(len(key) + len(value))
+		},
+	}
+	var (
+		slots           int
+		oldStorageBytes = s.storageBytes
+	)
+	// Iterate over all the accounts and reconstruct their storage tries from the
+	// delivered slots
+	for i, account := range res.accounts {
+		// If the account was not delivered, reschedule it
+		if i >= len(res.hashes) {
+			res.mainTask.stateTasks[account] = res.roots[i]
+			continue
+		}
+		// State was delivered, if complete mark as not needed any more, otherwise
+		// mark the account as needing healing
+		for j, hash := range res.mainTask.res.hashes {
+			if account != hash {
+				continue
+			}
+			acc := res.mainTask.res.accounts[j]
+
+			// If the packet contains multiple contract storage slots, all
+			// but the last are surely complete. The last contract may be
+			// chunked, so check it's continuation flag.
+			if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) {
+				res.mainTask.needState[j] = false
+				res.mainTask.pend--
+			}
+			// If the last contract was chunked, mark it as needing healing
+			// to avoid writing it out to disk prematurely.
+			if res.subTask == nil && !res.mainTask.needHeal[j] && i == len(res.hashes)-1 && res.cont {
+				res.mainTask.needHeal[j] = true
+			}
+			// If the last contract was chunked, we need to switch to large
+			// contract handling mode
+			if res.subTask == nil && i == len(res.hashes)-1 && res.cont {
+				// If we haven't yet started a large-contract retrieval, create
+				// the subtasks for it within the main account task
+				if tasks, ok := res.mainTask.SubTasks[account]; !ok {
+					var (
+						keys    = res.hashes[i]
+						chunks  = uint64(storageConcurrency)
+						lastKey common.Hash
+					)
+					if len(keys) > 0 {
+						lastKey = keys[len(keys)-1]
+					}
+					// If the number of slots remaining is low, decrease the
+					// number of chunks. Somewhere on the order of 10-15K slots
+					// fit into a packet of 500KB. A key/slot pair is maximum 64
+					// bytes, so pessimistically maxRequestSize/64 = 8K.
+					//
+					// Chunk so that at least 2 packets are needed to fill a task.
+					if estimate, err := estimateRemainingSlots(len(keys), lastKey); err == nil {
+						if n := estimate / (2 * (maxRequestSize / 64)); n+1 < chunks {
+							chunks = n + 1
+						}
+						log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "remaining", estimate, "chunks", chunks)
+					} else {
+						log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "chunks", chunks)
+					}
+					r := newHashRange(lastKey, chunks)
+
+					// Our first task is the one that was just filled by this response.
+					batch := ethdb.HookedBatch{
+						Batch: s.db.NewBatch(),
+						OnPut: func(key []byte, value []byte) {
+							s.storageBytes += common.StorageSize(len(key) + len(value))
+						},
+					}
+					tasks = append(tasks, &storageTask{
+						Next:     common.Hash{},
+						Last:     r.End(),
+						root:     acc.Root,
+						genBatch: batch,
+						genTrie:  trie.NewStackTrie(batch),
+					})
+					for r.Next() {
+						batch := ethdb.HookedBatch{
+							Batch: s.db.NewBatch(),
+							OnPut: func(key []byte, value []byte) {
+								s.storageBytes += common.StorageSize(len(key) + len(value))
+							},
+						}
+						tasks = append(tasks, &storageTask{
+							Next:     r.Start(),
+							Last:     r.End(),
+							root:     acc.Root,
+							genBatch: batch,
+							genTrie:  trie.NewStackTrie(batch),
+						})
+					}
+					for _, task := range tasks {
+						log.Debug("Created storage sync task", "account", account, "root", acc.Root, "from", task.Next, "last", task.Last)
+					}
+					res.mainTask.SubTasks[account] = tasks
+
+					// Since we've just created the sub-tasks, this response
+					// is surely for the first one (zero origin)
+					res.subTask = tasks[0]
+				}
+			}
+			// If we're in large contract delivery mode, forward the subtask
+			if res.subTask != nil {
+				// Ensure the response doesn't overflow into the subsequent task
+				last := res.subTask.Last.Big()
+				// Find the first overflowing key. While at it, mark res as complete
+				// if we find the range to include or pass the 'last'
+				index := sort.Search(len(res.hashes[i]), func(k int) bool {
+					cmp := res.hashes[i][k].Big().Cmp(last)
+					if cmp >= 0 {
+						res.cont = false
+					}
+					return cmp > 0
+				})
+				if index >= 0 {
+					// cut off excess
+					res.hashes[i] = res.hashes[i][:index]
+					res.slots[i] = res.slots[i][:index]
+				}
+				// Forward the relevant storage chunk (even if created just now)
+				if res.cont {
+					res.subTask.Next = incHash(res.hashes[i][len(res.hashes[i])-1])
+				} else {
+					res.subTask.done = true
+				}
+			}
+		}
+		// Iterate over all the complete contracts, reconstruct the trie nodes and
+		// push them to disk. If the contract is chunked, the trie nodes will be
+		// reconstructed later.
+		slots += len(res.hashes[i])
+
+		if i < len(res.hashes)-1 || res.subTask == nil {
+			tr := trie.NewStackTrie(batch)
+			for j := 0; j < len(res.hashes[i]); j++ {
+				tr.Update(res.hashes[i][j][:], res.slots[i][j])
+			}
+			tr.Commit()
+		}
+		// Persist the received storage segements. These flat state maybe
+		// outdated during the sync, but it can be fixed later during the
+		// snapshot generation.
+		for j := 0; j < len(res.hashes[i]); j++ {
+			rawdb.WriteStorageSnapshot(batch, account, res.hashes[i][j], res.slots[i][j])
+
+			// If we're storing large contracts, generate the trie nodes
+			// on the fly to not trash the gluing points
+			if i == len(res.hashes)-1 && res.subTask != nil {
+				res.subTask.genTrie.Update(res.hashes[i][j][:], res.slots[i][j])
+			}
+		}
+	}
+	// Large contracts could have generated new trie nodes, flush them to disk
+	if res.subTask != nil {
+		if res.subTask.done {
+			if root, err := res.subTask.genTrie.Commit(); err != nil {
+				log.Error("Failed to commit stack slots", "err", err)
+			} else if root == res.subTask.root {
+				// If the chunk's root is an overflown but full delivery, clear the heal request
+				for i, account := range res.mainTask.res.hashes {
+					if account == res.accounts[len(res.accounts)-1] {
+						res.mainTask.needHeal[i] = false
+					}
+				}
+			}
+		}
+		if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize || res.subTask.done {
+			if err := res.subTask.genBatch.Write(); err != nil {
+				log.Error("Failed to persist stack slots", "err", err)
+			}
+			res.subTask.genBatch.Reset()
+		}
+	}
+	// Flush anything written just now and update the stats
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to persist storage slots", "err", err)
+	}
+	s.storageSynced += uint64(slots)
+
+	log.Debug("Persisted set of storage slots", "accounts", len(res.hashes), "slots", slots, "bytes", s.storageBytes-oldStorageBytes)
+
+	// If this delivery completed the last pending task, forward the account task
+	// to the next chunk
+	if res.mainTask.pend == 0 {
+		s.forwardAccountTask(res.mainTask)
+		return
+	}
+	// Some accounts are still incomplete, leave as is for the storage and contract
+	// task assigners to pick up and fill.
+}
+
+// processTrienodeHealResponse integrates an already validated trienode response
+// into the healer tasks.
+func (s *Syncer) processTrienodeHealResponse(res *trienodeHealResponse) {
+	for i, hash := range res.hashes {
+		node := res.nodes[i]
+
+		// If the trie node was not delivered, reschedule it
+		if node == nil {
+			res.task.trieTasks[hash] = res.paths[i]
+			continue
+		}
+		// Push the trie node into the state syncer
+		s.trienodeHealSynced++
+		s.trienodeHealBytes += common.StorageSize(len(node))
+
+		err := s.healer.scheduler.Process(trie.SyncResult{Hash: hash, Data: node})
+		switch err {
+		case nil:
+		case trie.ErrAlreadyProcessed:
+			s.trienodeHealDups++
+		case trie.ErrNotRequested:
+			s.trienodeHealNops++
+		default:
+			log.Error("Invalid trienode processed", "hash", hash, "err", err)
+		}
+	}
+	batch := s.db.NewBatch()
+	if err := s.healer.scheduler.Commit(batch); err != nil {
+		log.Error("Failed to commit healing data", "err", err)
+	}
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to persist healing data", "err", err)
+	}
+	log.Debug("Persisted set of healing data", "type", "trienodes", "bytes", common.StorageSize(batch.ValueSize()))
+}
+
+// processBytecodeHealResponse integrates an already validated bytecode response
+// into the healer tasks.
+func (s *Syncer) processBytecodeHealResponse(res *bytecodeHealResponse) {
+	for i, hash := range res.hashes {
+		node := res.codes[i]
+
+		// If the trie node was not delivered, reschedule it
+		if node == nil {
+			res.task.codeTasks[hash] = struct{}{}
+			continue
+		}
+		// Push the trie node into the state syncer
+		s.bytecodeHealSynced++
+		s.bytecodeHealBytes += common.StorageSize(len(node))
+
+		err := s.healer.scheduler.Process(trie.SyncResult{Hash: hash, Data: node})
+		switch err {
+		case nil:
+		case trie.ErrAlreadyProcessed:
+			s.bytecodeHealDups++
+		case trie.ErrNotRequested:
+			s.bytecodeHealNops++
+		default:
+			log.Error("Invalid bytecode processed", "hash", hash, "err", err)
+		}
+	}
+	batch := s.db.NewBatch()
+	if err := s.healer.scheduler.Commit(batch); err != nil {
+		log.Error("Failed to commit healing data", "err", err)
+	}
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to persist healing data", "err", err)
+	}
+	log.Debug("Persisted set of healing data", "type", "bytecode", "bytes", common.StorageSize(batch.ValueSize()))
+}
+
+// forwardAccountTask takes a filled account task and persists anything available
+// into the database, after which it forwards the next account marker so that the
+// task's next chunk may be filled.
+func (s *Syncer) forwardAccountTask(task *accountTask) {
+	// Remove any pending delivery
+	res := task.res
+	if res == nil {
+		return // nothing to forward
+	}
+	task.res = nil
+
+	// Persist the received account segements. These flat state maybe
+	// outdated during the sync, but it can be fixed later during the
+	// snapshot generation.
+	oldAccountBytes := s.accountBytes
+
+	batch := ethdb.HookedBatch{
+		Batch: s.db.NewBatch(),
+		OnPut: func(key []byte, value []byte) {
+			s.accountBytes += common.StorageSize(len(key) + len(value))
+		},
+	}
+	for i, hash := range res.hashes {
+		if task.needCode[i] || task.needState[i] {
+			break
+		}
+		slim := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash)
+		rawdb.WriteAccountSnapshot(batch, hash, slim)
+
+		// If the task is complete, drop it into the stack trie to generate
+		// account trie nodes for it
+		if !task.needHeal[i] {
+			full, err := snapshot.FullAccountRLP(slim) // TODO(karalabe): Slim parsing can be omitted
+			if err != nil {
+				panic(err) // Really shouldn't ever happen
+			}
+			task.genTrie.Update(hash[:], full)
+		}
+	}
+	// Flush anything written just now and update the stats
+	if err := batch.Write(); err != nil {
+		log.Crit("Failed to persist accounts", "err", err)
+	}
+	s.accountSynced += uint64(len(res.accounts))
+
+	// Task filling persisted, push it the chunk marker forward to the first
+	// account still missing data.
+	for i, hash := range res.hashes {
+		if task.needCode[i] || task.needState[i] {
+			return
+		}
+		task.Next = incHash(hash)
+	}
+	// All accounts marked as complete, track if the entire task is done
+	task.done = !res.cont
+
+	// Stack trie could have generated trie nodes, push them to disk (we need to
+	// flush after finalizing task.done. It's fine even if we crash and lose this
+	// write as it will only cause more data to be downloaded during heal.
+	if task.done {
+		if _, err := task.genTrie.Commit(); err != nil {
+			log.Error("Failed to commit stack account", "err", err)
+		}
+	}
+	if task.genBatch.ValueSize() > ethdb.IdealBatchSize || task.done {
+		if err := task.genBatch.Write(); err != nil {
+			log.Error("Failed to persist stack account", "err", err)
+		}
+		task.genBatch.Reset()
+	}
+	log.Debug("Persisted range of accounts", "accounts", len(res.accounts), "bytes", s.accountBytes-oldAccountBytes)
+}
+
+// OnAccounts is a callback method to invoke when a range of accounts are
+// received from a remote peer.
+func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, accounts [][]byte, proof [][]byte) error {
+	size := common.StorageSize(len(hashes) * common.HashLength)
+	for _, account := range accounts {
+		size += common.StorageSize(len(account))
+	}
+	for _, node := range proof {
+		size += common.StorageSize(len(node))
+	}
+	logger := peer.Log().New("reqid", id)
+	logger.Trace("Delivering range of accounts", "hashes", len(hashes), "accounts", len(accounts), "proofs", len(proof), "bytes", size)
+
+	// Whether or not the response is valid, we can mark the peer as idle and
+	// notify the scheduler to assign a new task. If the response is invalid,
+	// we'll drop the peer in a bit.
+	s.lock.Lock()
+	if _, ok := s.peers[peer.ID()]; ok {
+		s.accountIdlers[peer.ID()] = struct{}{}
+	}
+	select {
+	case s.update <- struct{}{}:
+	default:
+	}
+	// Ensure the response is for a valid request
+	req, ok := s.accountReqs[id]
+	if !ok {
+		// Request stale, perhaps the peer timed out but came through in the end
+		logger.Warn("Unexpected account range packet")
+		s.lock.Unlock()
+		return nil
+	}
+	delete(s.accountReqs, id)
+
+	// Clean up the request timeout timer, we'll see how to proceed further based
+	// on the actual delivered content
+	if !req.timeout.Stop() {
+		// The timeout is already triggered, and this request will be reverted+rescheduled
+		s.lock.Unlock()
+		return nil
+	}
+	// Response is valid, but check if peer is signalling that it does not have
+	// the requested data. For account range queries that means the state being
+	// retrieved was either already pruned remotely, or the peer is not yet
+	// synced to our head.
+	if len(hashes) == 0 && len(accounts) == 0 && len(proof) == 0 {
+		logger.Debug("Peer rejected account range request", "root", s.root)
+		s.statelessPeers[peer.ID()] = struct{}{}
+		s.lock.Unlock()
+
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertAccountRequest(req)
+		return nil
+	}
+	root := s.root
+	s.lock.Unlock()
+
+	// Reconstruct a partial trie from the response and verify it
+	keys := make([][]byte, len(hashes))
+	for i, key := range hashes {
+		keys[i] = common.CopyBytes(key[:])
+	}
+	nodes := make(light.NodeList, len(proof))
+	for i, node := range proof {
+		nodes[i] = node
+	}
+	proofdb := nodes.NodeSet()
+
+	var end []byte
+	if len(keys) > 0 {
+		end = keys[len(keys)-1]
+	}
+	cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb)
+	if err != nil {
+		logger.Warn("Account range failed proof", "err", err)
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertAccountRequest(req)
+		return err
+	}
+	accs := make([]*state.Account, len(accounts))
+	for i, account := range accounts {
+		acc := new(state.Account)
+		if err := rlp.DecodeBytes(account, acc); err != nil {
+			panic(err) // We created these blobs, we must be able to decode them
+		}
+		accs[i] = acc
+	}
+	response := &accountResponse{
+		task:     req.task,
+		hashes:   hashes,
+		accounts: accs,
+		cont:     cont,
+	}
+	select {
+	case req.deliver <- response:
+	case <-req.cancel:
+	case <-req.stale:
+	}
+	return nil
+}
+
+// OnByteCodes is a callback method to invoke when a batch of contract
+// bytes codes are received from a remote peer.
+func (s *Syncer) OnByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error {
+	s.lock.RLock()
+	syncing := !s.snapped
+	s.lock.RUnlock()
+
+	if syncing {
+		return s.onByteCodes(peer, id, bytecodes)
+	}
+	return s.onHealByteCodes(peer, id, bytecodes)
+}
+
+// onByteCodes is a callback method to invoke when a batch of contract
+// bytes codes are received from a remote peer in the syncing phase.
+func (s *Syncer) onByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error {
+	var size common.StorageSize
+	for _, code := range bytecodes {
+		size += common.StorageSize(len(code))
+	}
+	logger := peer.Log().New("reqid", id)
+	logger.Trace("Delivering set of bytecodes", "bytecodes", len(bytecodes), "bytes", size)
+
+	// Whether or not the response is valid, we can mark the peer as idle and
+	// notify the scheduler to assign a new task. If the response is invalid,
+	// we'll drop the peer in a bit.
+	s.lock.Lock()
+	if _, ok := s.peers[peer.ID()]; ok {
+		s.bytecodeIdlers[peer.ID()] = struct{}{}
+	}
+	select {
+	case s.update <- struct{}{}:
+	default:
+	}
+	// Ensure the response is for a valid request
+	req, ok := s.bytecodeReqs[id]
+	if !ok {
+		// Request stale, perhaps the peer timed out but came through in the end
+		logger.Warn("Unexpected bytecode packet")
+		s.lock.Unlock()
+		return nil
+	}
+	delete(s.bytecodeReqs, id)
+
+	// Clean up the request timeout timer, we'll see how to proceed further based
+	// on the actual delivered content
+	if !req.timeout.Stop() {
+		// The timeout is already triggered, and this request will be reverted+rescheduled
+		s.lock.Unlock()
+		return nil
+	}
+
+	// Response is valid, but check if peer is signalling that it does not have
+	// the requested data. For bytecode range queries that means the peer is not
+	// yet synced.
+	if len(bytecodes) == 0 {
+		logger.Debug("Peer rejected bytecode request")
+		s.statelessPeers[peer.ID()] = struct{}{}
+		s.lock.Unlock()
+
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertBytecodeRequest(req)
+		return nil
+	}
+	s.lock.Unlock()
+
+	// Cross reference the requested bytecodes with the response to find gaps
+	// that the serving node is missing
+	hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
+	hash := make([]byte, 32)
+
+	codes := make([][]byte, len(req.hashes))
+	for i, j := 0, 0; i < len(bytecodes); i++ {
+		// Find the next hash that we've been served, leaving misses with nils
+		hasher.Reset()
+		hasher.Write(bytecodes[i])
+		hasher.Read(hash)
+
+		for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) {
+			j++
+		}
+		if j < len(req.hashes) {
+			codes[j] = bytecodes[i]
+			j++
+			continue
+		}
+		// We've either ran out of hashes, or got unrequested data
+		logger.Warn("Unexpected bytecodes", "count", len(bytecodes)-i)
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertBytecodeRequest(req)
+		return errors.New("unexpected bytecode")
+	}
+	// Response validated, send it to the scheduler for filling
+	response := &bytecodeResponse{
+		task:   req.task,
+		hashes: req.hashes,
+		codes:  codes,
+	}
+	select {
+	case req.deliver <- response:
+	case <-req.cancel:
+	case <-req.stale:
+	}
+	return nil
+}
+
+// OnStorage is a callback method to invoke when ranges of storage slots
+// are received from a remote peer.
+func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slots [][][]byte, proof [][]byte) error {
+	// Gather some trace stats to aid in debugging issues
+	var (
+		hashCount int
+		slotCount int
+		size      common.StorageSize
+	)
+	for _, hashset := range hashes {
+		size += common.StorageSize(common.HashLength * len(hashset))
+		hashCount += len(hashset)
+	}
+	for _, slotset := range slots {
+		for _, slot := range slotset {
+			size += common.StorageSize(len(slot))
+		}
+		slotCount += len(slotset)
+	}
+	for _, node := range proof {
+		size += common.StorageSize(len(node))
+	}
+	logger := peer.Log().New("reqid", id)
+	logger.Trace("Delivering ranges of storage slots", "accounts", len(hashes), "hashes", hashCount, "slots", slotCount, "proofs", len(proof), "size", size)
+
+	// Whether or not the response is valid, we can mark the peer as idle and
+	// notify the scheduler to assign a new task. If the response is invalid,
+	// we'll drop the peer in a bit.
+	s.lock.Lock()
+	if _, ok := s.peers[peer.ID()]; ok {
+		s.storageIdlers[peer.ID()] = struct{}{}
+	}
+	select {
+	case s.update <- struct{}{}:
+	default:
+	}
+	// Ensure the response is for a valid request
+	req, ok := s.storageReqs[id]
+	if !ok {
+		// Request stale, perhaps the peer timed out but came through in the end
+		logger.Warn("Unexpected storage ranges packet")
+		s.lock.Unlock()
+		return nil
+	}
+	delete(s.storageReqs, id)
+
+	// Clean up the request timeout timer, we'll see how to proceed further based
+	// on the actual delivered content
+	if !req.timeout.Stop() {
+		// The timeout is already triggered, and this request will be reverted+rescheduled
+		s.lock.Unlock()
+		return nil
+	}
+
+	// Reject the response if the hash sets and slot sets don't match, or if the
+	// peer sent more data than requested.
+	if len(hashes) != len(slots) {
+		s.lock.Unlock()
+		s.scheduleRevertStorageRequest(req) // reschedule request
+		logger.Warn("Hash and slot set size mismatch", "hashset", len(hashes), "slotset", len(slots))
+		return errors.New("hash and slot set size mismatch")
+	}
+	if len(hashes) > len(req.accounts) {
+		s.lock.Unlock()
+		s.scheduleRevertStorageRequest(req) // reschedule request
+		logger.Warn("Hash set larger than requested", "hashset", len(hashes), "requested", len(req.accounts))
+		return errors.New("hash set larger than requested")
+	}
+	// Response is valid, but check if peer is signalling that it does not have
+	// the requested data. For storage range queries that means the state being
+	// retrieved was either already pruned remotely, or the peer is not yet
+	// synced to our head.
+	if len(hashes) == 0 {
+		logger.Debug("Peer rejected storage request")
+		s.statelessPeers[peer.ID()] = struct{}{}
+		s.lock.Unlock()
+		s.scheduleRevertStorageRequest(req) // reschedule request
+		return nil
+	}
+	s.lock.Unlock()
+
+	// Reconstruct the partial tries from the response and verify them
+	var cont bool
+
+	for i := 0; i < len(hashes); i++ {
+		// Convert the keys and proofs into an internal format
+		keys := make([][]byte, len(hashes[i]))
+		for j, key := range hashes[i] {
+			keys[j] = common.CopyBytes(key[:])
+		}
+		nodes := make(light.NodeList, 0, len(proof))
+		if i == len(hashes)-1 {
+			for _, node := range proof {
+				nodes = append(nodes, node)
+			}
+		}
+		var err error
+		if len(nodes) == 0 {
+			// No proof has been attached, the response must cover the entire key
+			// space and hash to the origin root.
+			_, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil)
+			if err != nil {
+				s.scheduleRevertStorageRequest(req) // reschedule request
+				logger.Warn("Storage slots failed proof", "err", err)
+				return err
+			}
+		} else {
+			// A proof was attached, the response is only partial, check that the
+			// returned data is indeed part of the storage trie
+			proofdb := nodes.NodeSet()
+
+			var end []byte
+			if len(keys) > 0 {
+				end = keys[len(keys)-1]
+			}
+			cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb)
+			if err != nil {
+				s.scheduleRevertStorageRequest(req) // reschedule request
+				logger.Warn("Storage range failed proof", "err", err)
+				return err
+			}
+		}
+	}
+	// Partial tries reconstructed, send them to the scheduler for storage filling
+	response := &storageResponse{
+		mainTask: req.mainTask,
+		subTask:  req.subTask,
+		accounts: req.accounts,
+		roots:    req.roots,
+		hashes:   hashes,
+		slots:    slots,
+		cont:     cont,
+	}
+	select {
+	case req.deliver <- response:
+	case <-req.cancel:
+	case <-req.stale:
+	}
+	return nil
+}
+
+// OnTrieNodes is a callback method to invoke when a batch of trie nodes
+// are received from a remote peer.
+func (s *Syncer) OnTrieNodes(peer SyncPeer, id uint64, trienodes [][]byte) error {
+	var size common.StorageSize
+	for _, node := range trienodes {
+		size += common.StorageSize(len(node))
+	}
+	logger := peer.Log().New("reqid", id)
+	logger.Trace("Delivering set of healing trienodes", "trienodes", len(trienodes), "bytes", size)
+
+	// Whether or not the response is valid, we can mark the peer as idle and
+	// notify the scheduler to assign a new task. If the response is invalid,
+	// we'll drop the peer in a bit.
+	s.lock.Lock()
+	if _, ok := s.peers[peer.ID()]; ok {
+		s.trienodeHealIdlers[peer.ID()] = struct{}{}
+	}
+	select {
+	case s.update <- struct{}{}:
+	default:
+	}
+	// Ensure the response is for a valid request
+	req, ok := s.trienodeHealReqs[id]
+	if !ok {
+		// Request stale, perhaps the peer timed out but came through in the end
+		logger.Warn("Unexpected trienode heal packet")
+		s.lock.Unlock()
+		return nil
+	}
+	delete(s.trienodeHealReqs, id)
+
+	// Clean up the request timeout timer, we'll see how to proceed further based
+	// on the actual delivered content
+	if !req.timeout.Stop() {
+		// The timeout is already triggered, and this request will be reverted+rescheduled
+		s.lock.Unlock()
+		return nil
+	}
+
+	// Response is valid, but check if peer is signalling that it does not have
+	// the requested data. For bytecode range queries that means the peer is not
+	// yet synced.
+	if len(trienodes) == 0 {
+		logger.Debug("Peer rejected trienode heal request")
+		s.statelessPeers[peer.ID()] = struct{}{}
+		s.lock.Unlock()
+
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertTrienodeHealRequest(req)
+		return nil
+	}
+	s.lock.Unlock()
+
+	// Cross reference the requested trienodes with the response to find gaps
+	// that the serving node is missing
+	hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
+	hash := make([]byte, 32)
+
+	nodes := make([][]byte, len(req.hashes))
+	for i, j := 0, 0; i < len(trienodes); i++ {
+		// Find the next hash that we've been served, leaving misses with nils
+		hasher.Reset()
+		hasher.Write(trienodes[i])
+		hasher.Read(hash)
+
+		for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) {
+			j++
+		}
+		if j < len(req.hashes) {
+			nodes[j] = trienodes[i]
+			j++
+			continue
+		}
+		// We've either ran out of hashes, or got unrequested data
+		logger.Warn("Unexpected healing trienodes", "count", len(trienodes)-i)
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertTrienodeHealRequest(req)
+		return errors.New("unexpected healing trienode")
+	}
+	// Response validated, send it to the scheduler for filling
+	response := &trienodeHealResponse{
+		task:   req.task,
+		hashes: req.hashes,
+		paths:  req.paths,
+		nodes:  nodes,
+	}
+	select {
+	case req.deliver <- response:
+	case <-req.cancel:
+	case <-req.stale:
+	}
+	return nil
+}
+
+// onHealByteCodes is a callback method to invoke when a batch of contract
+// bytes codes are received from a remote peer in the healing phase.
+func (s *Syncer) onHealByteCodes(peer SyncPeer, id uint64, bytecodes [][]byte) error {
+	var size common.StorageSize
+	for _, code := range bytecodes {
+		size += common.StorageSize(len(code))
+	}
+	logger := peer.Log().New("reqid", id)
+	logger.Trace("Delivering set of healing bytecodes", "bytecodes", len(bytecodes), "bytes", size)
+
+	// Whether or not the response is valid, we can mark the peer as idle and
+	// notify the scheduler to assign a new task. If the response is invalid,
+	// we'll drop the peer in a bit.
+	s.lock.Lock()
+	if _, ok := s.peers[peer.ID()]; ok {
+		s.bytecodeHealIdlers[peer.ID()] = struct{}{}
+	}
+	select {
+	case s.update <- struct{}{}:
+	default:
+	}
+	// Ensure the response is for a valid request
+	req, ok := s.bytecodeHealReqs[id]
+	if !ok {
+		// Request stale, perhaps the peer timed out but came through in the end
+		logger.Warn("Unexpected bytecode heal packet")
+		s.lock.Unlock()
+		return nil
+	}
+	delete(s.bytecodeHealReqs, id)
+
+	// Clean up the request timeout timer, we'll see how to proceed further based
+	// on the actual delivered content
+	if !req.timeout.Stop() {
+		// The timeout is already triggered, and this request will be reverted+rescheduled
+		s.lock.Unlock()
+		return nil
+	}
+
+	// Response is valid, but check if peer is signalling that it does not have
+	// the requested data. For bytecode range queries that means the peer is not
+	// yet synced.
+	if len(bytecodes) == 0 {
+		logger.Debug("Peer rejected bytecode heal request")
+		s.statelessPeers[peer.ID()] = struct{}{}
+		s.lock.Unlock()
+
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertBytecodeHealRequest(req)
+		return nil
+	}
+	s.lock.Unlock()
+
+	// Cross reference the requested bytecodes with the response to find gaps
+	// that the serving node is missing
+	hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
+	hash := make([]byte, 32)
+
+	codes := make([][]byte, len(req.hashes))
+	for i, j := 0, 0; i < len(bytecodes); i++ {
+		// Find the next hash that we've been served, leaving misses with nils
+		hasher.Reset()
+		hasher.Write(bytecodes[i])
+		hasher.Read(hash)
+
+		for j < len(req.hashes) && !bytes.Equal(hash, req.hashes[j][:]) {
+			j++
+		}
+		if j < len(req.hashes) {
+			codes[j] = bytecodes[i]
+			j++
+			continue
+		}
+		// We've either ran out of hashes, or got unrequested data
+		logger.Warn("Unexpected healing bytecodes", "count", len(bytecodes)-i)
+		// Signal this request as failed, and ready for rescheduling
+		s.scheduleRevertBytecodeHealRequest(req)
+		return errors.New("unexpected healing bytecode")
+	}
+	// Response validated, send it to the scheduler for filling
+	response := &bytecodeHealResponse{
+		task:   req.task,
+		hashes: req.hashes,
+		codes:  codes,
+	}
+	select {
+	case req.deliver <- response:
+	case <-req.cancel:
+	case <-req.stale:
+	}
+	return nil
+}
+
+// onHealState is a callback method to invoke when a flat state(account
+// or storage slot) is downloded during the healing stage. The flat states
+// can be persisted blindly and can be fixed later in the generation stage.
+// Note it's not concurrent safe, please handle the concurrent issue outside.
+func (s *Syncer) onHealState(paths [][]byte, value []byte) error {
+	if len(paths) == 1 {
+		var account state.Account
+		if err := rlp.DecodeBytes(value, &account); err != nil {
+			return nil
+		}
+		blob := snapshot.SlimAccountRLP(account.Nonce, account.Balance, account.Root, account.CodeHash)
+		rawdb.WriteAccountSnapshot(s.stateWriter, common.BytesToHash(paths[0]), blob)
+		s.accountHealed += 1
+		s.accountHealedBytes += common.StorageSize(1 + common.HashLength + len(blob))
+	}
+	if len(paths) == 2 {
+		rawdb.WriteStorageSnapshot(s.stateWriter, common.BytesToHash(paths[0]), common.BytesToHash(paths[1]), value)
+		s.storageHealed += 1
+		s.storageHealedBytes += common.StorageSize(1 + 2*common.HashLength + len(value))
+	}
+	if s.stateWriter.ValueSize() > ethdb.IdealBatchSize {
+		s.stateWriter.Write() // It's fine to ignore the error here
+		s.stateWriter.Reset()
+	}
+	return nil
+}
+
+// hashSpace is the total size of the 256 bit hash space for accounts.
+var hashSpace = new(big.Int).Exp(common.Big2, common.Big256, nil)
+
+// report calculates various status reports and provides it to the user.
+func (s *Syncer) report(force bool) {
+	if len(s.tasks) > 0 {
+		s.reportSyncProgress(force)
+		return
+	}
+	s.reportHealProgress(force)
+}
+
+// reportSyncProgress calculates various status reports and provides it to the user.
+func (s *Syncer) reportSyncProgress(force bool) {
+	// Don't report all the events, just occasionally
+	if !force && time.Since(s.logTime) < 8*time.Second {
+		return
+	}
+	// Don't report anything until we have a meaningful progress
+	synced := s.accountBytes + s.bytecodeBytes + s.storageBytes
+	if synced == 0 {
+		return
+	}
+	accountGaps := new(big.Int)
+	for _, task := range s.tasks {
+		accountGaps.Add(accountGaps, new(big.Int).Sub(task.Last.Big(), task.Next.Big()))
+	}
+	accountFills := new(big.Int).Sub(hashSpace, accountGaps)
+	if accountFills.BitLen() == 0 {
+		return
+	}
+	s.logTime = time.Now()
+	estBytes := float64(new(big.Int).Div(
+		new(big.Int).Mul(new(big.Int).SetUint64(uint64(synced)), hashSpace),
+		accountFills,
+	).Uint64())
+
+	elapsed := time.Since(s.startTime)
+	estTime := elapsed / time.Duration(synced) * time.Duration(estBytes)
+
+	// Create a mega progress report
+	var (
+		progress = fmt.Sprintf("%.2f%%", float64(synced)*100/estBytes)
+		accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountSynced), s.accountBytes.TerminalString())
+		storage  = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageSynced), s.storageBytes.TerminalString())
+		bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeSynced), s.bytecodeBytes.TerminalString())
+	)
+	log.Info("State sync in progress", "synced", progress, "state", synced,
+		"accounts", accounts, "slots", storage, "codes", bytecode, "eta", common.PrettyDuration(estTime-elapsed))
+}
+
+// reportHealProgress calculates various status reports and provides it to the user.
+func (s *Syncer) reportHealProgress(force bool) {
+	// Don't report all the events, just occasionally
+	if !force && time.Since(s.logTime) < 8*time.Second {
+		return
+	}
+	s.logTime = time.Now()
+
+	// Create a mega progress report
+	var (
+		trienode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.trienodeHealSynced), s.trienodeHealBytes.TerminalString())
+		bytecode = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.bytecodeHealSynced), s.bytecodeHealBytes.TerminalString())
+		accounts = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.accountHealed), s.accountHealedBytes.TerminalString())
+		storage  = fmt.Sprintf("%v@%v", log.FormatLogfmtUint64(s.storageHealed), s.storageHealedBytes.TerminalString())
+	)
+	log.Info("State heal in progress", "accounts", accounts, "slots", storage,
+		"codes", bytecode, "nodes", trienode, "pending", s.healer.scheduler.Pending())
+}
+
+// estimateRemainingSlots tries to determine roughly how many slots are left in
+// a contract storage, based on the number of keys and the last hash. This method
+// assumes that the hashes are lexicographically ordered and evenly distributed.
+func estimateRemainingSlots(hashes int, last common.Hash) (uint64, error) {
+	if last == (common.Hash{}) {
+		return 0, errors.New("last hash empty")
+	}
+	space := new(big.Int).Mul(math.MaxBig256, big.NewInt(int64(hashes)))
+	space.Div(space, last.Big())
+	if !space.IsUint64() {
+		// Gigantic address space probably due to too few or malicious slots
+		return 0, errors.New("too few slots for estimation")
+	}
+	return space.Uint64() - uint64(hashes), nil
+}
diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go
new file mode 100644
index 0000000000..a1cc3581a8
--- /dev/null
+++ b/eth/protocols/snap/sync_test.go
@@ -0,0 +1,1716 @@
+// 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 .
+
+package snap
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"math/big"
+	"sort"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+	"golang.org/x/crypto/sha3"
+)
+
+func TestHashing(t *testing.T) {
+	t.Parallel()
+
+	var bytecodes = make([][]byte, 10)
+	for i := 0; i < len(bytecodes); i++ {
+		buf := make([]byte, 100)
+		rand.Read(buf)
+		bytecodes[i] = buf
+	}
+	var want, got string
+	var old = func() {
+		hasher := sha3.NewLegacyKeccak256()
+		for i := 0; i < len(bytecodes); i++ {
+			hasher.Reset()
+			hasher.Write(bytecodes[i])
+			hash := hasher.Sum(nil)
+			got = fmt.Sprintf("%v\n%v", got, hash)
+		}
+	}
+	var new = func() {
+		hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
+		var hash = make([]byte, 32)
+		for i := 0; i < len(bytecodes); i++ {
+			hasher.Reset()
+			hasher.Write(bytecodes[i])
+			hasher.Read(hash)
+			want = fmt.Sprintf("%v\n%v", want, hash)
+		}
+	}
+	old()
+	new()
+	if want != got {
+		t.Errorf("want\n%v\ngot\n%v\n", want, got)
+	}
+}
+
+func BenchmarkHashing(b *testing.B) {
+	var bytecodes = make([][]byte, 10000)
+	for i := 0; i < len(bytecodes); i++ {
+		buf := make([]byte, 100)
+		rand.Read(buf)
+		bytecodes[i] = buf
+	}
+	var old = func() {
+		hasher := sha3.NewLegacyKeccak256()
+		for i := 0; i < len(bytecodes); i++ {
+			hasher.Reset()
+			hasher.Write(bytecodes[i])
+			hasher.Sum(nil)
+		}
+	}
+	var new = func() {
+		hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
+		var hash = make([]byte, 32)
+		for i := 0; i < len(bytecodes); i++ {
+			hasher.Reset()
+			hasher.Write(bytecodes[i])
+			hasher.Read(hash)
+		}
+	}
+	b.Run("old", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			old()
+		}
+	})
+	b.Run("new", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			new()
+		}
+	})
+}
+
+type (
+	accountHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error
+	storageHandlerFunc func(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error
+	trieHandlerFunc    func(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error
+	codeHandlerFunc    func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error
+)
+
+type testPeer struct {
+	id            string
+	test          *testing.T
+	remote        *Syncer
+	logger        log.Logger
+	accountTrie   *trie.Trie
+	accountValues entrySlice
+	storageTries  map[common.Hash]*trie.Trie
+	storageValues map[common.Hash]entrySlice
+
+	accountRequestHandler accountHandlerFunc
+	storageRequestHandler storageHandlerFunc
+	trieRequestHandler    trieHandlerFunc
+	codeRequestHandler    codeHandlerFunc
+	term                  func()
+
+	// counters
+	nAccountRequests  int
+	nStorageRequests  int
+	nBytecodeRequests int
+	nTrienodeRequests int
+}
+
+func newTestPeer(id string, t *testing.T, term func()) *testPeer {
+	peer := &testPeer{
+		id:                    id,
+		test:                  t,
+		logger:                log.New("id", id),
+		accountRequestHandler: defaultAccountRequestHandler,
+		trieRequestHandler:    defaultTrieRequestHandler,
+		storageRequestHandler: defaultStorageRequestHandler,
+		codeRequestHandler:    defaultCodeRequestHandler,
+		term:                  term,
+	}
+	//stderrHandler := log.StreamHandler(os.Stderr, log.TerminalFormat(true))
+	//peer.logger.SetHandler(stderrHandler)
+	return peer
+}
+
+func (t *testPeer) ID() string      { return t.id }
+func (t *testPeer) Log() log.Logger { return t.logger }
+
+func (t *testPeer) Stats() string {
+	return fmt.Sprintf(`Account requests: %d
+Storage requests: %d
+Bytecode requests: %d
+Trienode requests: %d
+`, t.nAccountRequests, t.nStorageRequests, t.nBytecodeRequests, t.nTrienodeRequests)
+}
+
+func (t *testPeer) RequestAccountRange(id uint64, root, origin, limit common.Hash, bytes uint64) error {
+	t.logger.Trace("Fetching range of accounts", "reqid", id, "root", root, "origin", origin, "limit", limit, "bytes", common.StorageSize(bytes))
+	t.nAccountRequests++
+	go t.accountRequestHandler(t, id, root, origin, limit, bytes)
+	return nil
+}
+
+func (t *testPeer) RequestTrieNodes(id uint64, root common.Hash, paths []TrieNodePathSet, bytes uint64) error {
+	t.logger.Trace("Fetching set of trie nodes", "reqid", id, "root", root, "pathsets", len(paths), "bytes", common.StorageSize(bytes))
+	t.nTrienodeRequests++
+	go t.trieRequestHandler(t, id, root, paths, bytes)
+	return nil
+}
+
+func (t *testPeer) RequestStorageRanges(id uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, bytes uint64) error {
+	t.nStorageRequests++
+	if len(accounts) == 1 && origin != nil {
+		t.logger.Trace("Fetching range of large storage slots", "reqid", id, "root", root, "account", accounts[0], "origin", common.BytesToHash(origin), "limit", common.BytesToHash(limit), "bytes", common.StorageSize(bytes))
+	} else {
+		t.logger.Trace("Fetching ranges of small storage slots", "reqid", id, "root", root, "accounts", len(accounts), "first", accounts[0], "bytes", common.StorageSize(bytes))
+	}
+	go t.storageRequestHandler(t, id, root, accounts, origin, limit, bytes)
+	return nil
+}
+
+func (t *testPeer) RequestByteCodes(id uint64, hashes []common.Hash, bytes uint64) error {
+	t.nBytecodeRequests++
+	t.logger.Trace("Fetching set of byte codes", "reqid", id, "hashes", len(hashes), "bytes", common.StorageSize(bytes))
+	go t.codeRequestHandler(t, id, hashes, bytes)
+	return nil
+}
+
+// defaultTrieRequestHandler is a well-behaving handler for trie healing requests
+func defaultTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
+	// Pass the response
+	var nodes [][]byte
+	for _, pathset := range paths {
+		switch len(pathset) {
+		case 1:
+			blob, _, err := t.accountTrie.TryGetNode(pathset[0])
+			if err != nil {
+				t.logger.Info("Error handling req", "error", err)
+				break
+			}
+			nodes = append(nodes, blob)
+		default:
+			account := t.storageTries[(common.BytesToHash(pathset[0]))]
+			for _, path := range pathset[1:] {
+				blob, _, err := account.TryGetNode(path)
+				if err != nil {
+					t.logger.Info("Error handling req", "error", err)
+					break
+				}
+				nodes = append(nodes, blob)
+			}
+		}
+	}
+	t.remote.OnTrieNodes(t, requestId, nodes)
+	return nil
+}
+
+// defaultAccountRequestHandler is a well-behaving handler for AccountRangeRequests
+func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
+	keys, vals, proofs := createAccountRequestResponse(t, root, origin, limit, cap)
+	if err := t.remote.OnAccounts(t, id, keys, vals, proofs); err != nil {
+		t.test.Errorf("Remote side rejected our delivery: %v", err)
+		t.term()
+		return err
+	}
+	return nil
+}
+
+func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) {
+	var size uint64
+	if limit == (common.Hash{}) {
+		limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+	}
+	for _, entry := range t.accountValues {
+		if size > cap {
+			break
+		}
+		if bytes.Compare(origin[:], entry.k) <= 0 {
+			keys = append(keys, common.BytesToHash(entry.k))
+			vals = append(vals, entry.v)
+			size += uint64(32 + len(entry.v))
+		}
+		// If we've exceeded the request threshold, abort
+		if bytes.Compare(entry.k, limit[:]) >= 0 {
+			break
+		}
+	}
+	// Unless we send the entire trie, we need to supply proofs
+	// Actually, we need to supply proofs either way! This seems to be an implementation
+	// quirk in go-ethereum
+	proof := light.NewNodeSet()
+	if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil {
+		t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err)
+	}
+	if len(keys) > 0 {
+		lastK := (keys[len(keys)-1])[:]
+		if err := t.accountTrie.Prove(lastK, 0, proof); err != nil {
+			t.logger.Error("Could not prove last item", "error", err)
+		}
+	}
+	for _, blob := range proof.NodeList() {
+		proofs = append(proofs, blob)
+	}
+	return keys, vals, proofs
+}
+
+// defaultStorageRequestHandler is a well-behaving storage request handler
+func defaultStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) error {
+	hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, bOrigin, bLimit, max)
+	if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
+		t.test.Errorf("Remote side rejected our delivery: %v", err)
+		t.term()
+	}
+	return nil
+}
+
+func defaultCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
+	var bytecodes [][]byte
+	for _, h := range hashes {
+		bytecodes = append(bytecodes, getCodeByHash(h))
+	}
+	if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
+		t.test.Errorf("Remote side rejected our delivery: %v", err)
+		t.term()
+	}
+	return nil
+}
+
+func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
+	var size uint64
+	for _, account := range accounts {
+		// The first account might start from a different origin and end sooner
+		var originHash common.Hash
+		if len(origin) > 0 {
+			originHash = common.BytesToHash(origin)
+		}
+		var limitHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+		if len(limit) > 0 {
+			limitHash = common.BytesToHash(limit)
+		}
+		var (
+			keys  []common.Hash
+			vals  [][]byte
+			abort bool
+		)
+		for _, entry := range t.storageValues[account] {
+			if size >= max {
+				abort = true
+				break
+			}
+			if bytes.Compare(entry.k, originHash[:]) < 0 {
+				continue
+			}
+			keys = append(keys, common.BytesToHash(entry.k))
+			vals = append(vals, entry.v)
+			size += uint64(32 + len(entry.v))
+			if bytes.Compare(entry.k, limitHash[:]) >= 0 {
+				break
+			}
+		}
+		hashes = append(hashes, keys)
+		slots = append(slots, vals)
+
+		// Generate the Merkle proofs for the first and last storage slot, but
+		// only if the response was capped. If the entire storage trie included
+		// in the response, no need for any proofs.
+		if originHash != (common.Hash{}) || abort {
+			// If we're aborting, we need to prove the first and last item
+			// This terminates the response (and thus the loop)
+			proof := light.NewNodeSet()
+			stTrie := t.storageTries[account]
+
+			// Here's a potential gotcha: when constructing the proof, we cannot
+			// use the 'origin' slice directly, but must use the full 32-byte
+			// hash form.
+			if err := stTrie.Prove(originHash[:], 0, proof); err != nil {
+				t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "error", err)
+			}
+			if len(keys) > 0 {
+				lastK := (keys[len(keys)-1])[:]
+				if err := stTrie.Prove(lastK, 0, proof); err != nil {
+					t.logger.Error("Could not prove last item", "error", err)
+				}
+			}
+			for _, blob := range proof.NodeList() {
+				proofs = append(proofs, blob)
+			}
+			break
+		}
+	}
+	return hashes, slots, proofs
+}
+
+//  the createStorageRequestResponseAlwaysProve tests a cornercase, where it always
+// supplies the proof for the last account, even if it is 'complete'.h
+func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
+	var size uint64
+	max = max * 3 / 4
+
+	var origin common.Hash
+	if len(bOrigin) > 0 {
+		origin = common.BytesToHash(bOrigin)
+	}
+	var exit bool
+	for i, account := range accounts {
+		var keys []common.Hash
+		var vals [][]byte
+		for _, entry := range t.storageValues[account] {
+			if bytes.Compare(entry.k, origin[:]) < 0 {
+				exit = true
+			}
+			keys = append(keys, common.BytesToHash(entry.k))
+			vals = append(vals, entry.v)
+			size += uint64(32 + len(entry.v))
+			if size > max {
+				exit = true
+			}
+		}
+		if i == len(accounts)-1 {
+			exit = true
+		}
+		hashes = append(hashes, keys)
+		slots = append(slots, vals)
+
+		if exit {
+			// If we're aborting, we need to prove the first and last item
+			// This terminates the response (and thus the loop)
+			proof := light.NewNodeSet()
+			stTrie := t.storageTries[account]
+
+			// Here's a potential gotcha: when constructing the proof, we cannot
+			// use the 'origin' slice directly, but must use the full 32-byte
+			// hash form.
+			if err := stTrie.Prove(origin[:], 0, proof); err != nil {
+				t.logger.Error("Could not prove inexistence of origin", "origin", origin,
+					"error", err)
+			}
+			if len(keys) > 0 {
+				lastK := (keys[len(keys)-1])[:]
+				if err := stTrie.Prove(lastK, 0, proof); err != nil {
+					t.logger.Error("Could not prove last item", "error", err)
+				}
+			}
+			for _, blob := range proof.NodeList() {
+				proofs = append(proofs, blob)
+			}
+			break
+		}
+	}
+	return hashes, slots, proofs
+}
+
+// emptyRequestAccountRangeFn is a rejects AccountRangeRequests
+func emptyRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
+	t.remote.OnAccounts(t, requestId, nil, nil, nil)
+	return nil
+}
+
+func nonResponsiveRequestAccountRangeFn(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
+	return nil
+}
+
+func emptyTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
+	t.remote.OnTrieNodes(t, requestId, nil)
+	return nil
+}
+
+func nonResponsiveTrieRequestHandler(t *testPeer, requestId uint64, root common.Hash, paths []TrieNodePathSet, cap uint64) error {
+	return nil
+}
+
+func emptyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
+	t.remote.OnStorage(t, requestId, nil, nil, nil)
+	return nil
+}
+
+func nonResponsiveStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
+	return nil
+}
+
+func proofHappyStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
+	hashes, slots, proofs := createStorageRequestResponseAlwaysProve(t, root, accounts, origin, limit, max)
+	if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
+		t.test.Errorf("Remote side rejected our delivery: %v", err)
+		t.term()
+	}
+	return nil
+}
+
+//func emptyCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
+//	var bytecodes [][]byte
+//	t.remote.OnByteCodes(t, id, bytecodes)
+//	return nil
+//}
+
+func corruptCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
+	var bytecodes [][]byte
+	for _, h := range hashes {
+		// Send back the hashes
+		bytecodes = append(bytecodes, h[:])
+	}
+	if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
+		t.logger.Info("remote error on delivery (as expected)", "error", err)
+		// Mimic the real-life handler, which drops a peer on errors
+		t.remote.Unregister(t.id)
+	}
+	return nil
+}
+
+func cappedCodeRequestHandler(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
+	var bytecodes [][]byte
+	for _, h := range hashes[:1] {
+		bytecodes = append(bytecodes, getCodeByHash(h))
+	}
+	// Missing bytecode can be retrieved again, no error expected
+	if err := t.remote.OnByteCodes(t, id, bytecodes); err != nil {
+		t.test.Errorf("Remote side rejected our delivery: %v", err)
+		t.term()
+	}
+	return nil
+}
+
+// starvingStorageRequestHandler is somewhat well-behaving storage handler, but it caps the returned results to be very small
+func starvingStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
+	return defaultStorageRequestHandler(t, requestId, root, accounts, origin, limit, 500)
+}
+
+func starvingAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
+	return defaultAccountRequestHandler(t, requestId, root, origin, limit, 500)
+}
+
+//func misdeliveringAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, cap uint64) error {
+//	return defaultAccountRequestHandler(t, requestId-1, root, origin, 500)
+//}
+
+func corruptAccountRequestHandler(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
+	hashes, accounts, proofs := createAccountRequestResponse(t, root, origin, limit, cap)
+	if len(proofs) > 0 {
+		proofs = proofs[1:]
+	}
+	if err := t.remote.OnAccounts(t, requestId, hashes, accounts, proofs); err != nil {
+		t.logger.Info("remote error on delivery (as expected)", "error", err)
+		// Mimic the real-life handler, which drops a peer on errors
+		t.remote.Unregister(t.id)
+	}
+	return nil
+}
+
+// corruptStorageRequestHandler doesn't provide good proofs
+func corruptStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
+	hashes, slots, proofs := createStorageRequestResponse(t, root, accounts, origin, limit, max)
+	if len(proofs) > 0 {
+		proofs = proofs[1:]
+	}
+	if err := t.remote.OnStorage(t, requestId, hashes, slots, proofs); err != nil {
+		t.logger.Info("remote error on delivery (as expected)", "error", err)
+		// Mimic the real-life handler, which drops a peer on errors
+		t.remote.Unregister(t.id)
+	}
+	return nil
+}
+
+func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error {
+	hashes, slots, _ := createStorageRequestResponse(t, root, accounts, origin, limit, max)
+	if err := t.remote.OnStorage(t, requestId, hashes, slots, nil); err != nil {
+		t.logger.Info("remote error on delivery (as expected)", "error", err)
+		// Mimic the real-life handler, which drops a peer on errors
+		t.remote.Unregister(t.id)
+	}
+	return nil
+}
+
+// TestSyncBloatedProof tests a scenario where we provide only _one_ value, but
+// also ship the entire trie inside the proof. If the attack is successful,
+// the remote side does not do any follow-up requests
+func TestSyncBloatedProof(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
+	source := newTestPeer("source", t, term)
+	source.accountTrie = sourceAccountTrie
+	source.accountValues = elems
+
+	source.accountRequestHandler = func(t *testPeer, requestId uint64, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) error {
+		var (
+			proofs [][]byte
+			keys   []common.Hash
+			vals   [][]byte
+		)
+		// The values
+		for _, entry := range t.accountValues {
+			if bytes.Compare(entry.k, origin[:]) < 0 {
+				continue
+			}
+			if bytes.Compare(entry.k, limit[:]) > 0 {
+				continue
+			}
+			keys = append(keys, common.BytesToHash(entry.k))
+			vals = append(vals, entry.v)
+		}
+		// The proofs
+		proof := light.NewNodeSet()
+		if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil {
+			t.logger.Error("Could not prove origin", "origin", origin, "error", err)
+		}
+		// The bloat: add proof of every single element
+		for _, entry := range t.accountValues {
+			if err := t.accountTrie.Prove(entry.k, 0, proof); err != nil {
+				t.logger.Error("Could not prove item", "error", err)
+			}
+		}
+		// And remove one item from the elements
+		if len(keys) > 2 {
+			keys = append(keys[:1], keys[2:]...)
+			vals = append(vals[:1], vals[2:]...)
+		}
+		for _, blob := range proof.NodeList() {
+			proofs = append(proofs, blob)
+		}
+		if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil {
+			t.logger.Info("remote error on delivery (as expected)", "error", err)
+			t.term()
+			// This is actually correct, signal to exit the test successfully
+		}
+		return nil
+	}
+	syncer := setupSyncer(source)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err == nil {
+		t.Fatal("No error returned from incomplete/cancelled sync")
+	}
+}
+
+func setupSyncer(peers ...*testPeer) *Syncer {
+	stateDb := rawdb.NewMemoryDatabase()
+	syncer := NewSyncer(stateDb)
+	for _, peer := range peers {
+		syncer.Register(peer)
+		peer.remote = syncer
+	}
+	return syncer
+}
+
+// TestSync tests a basic sync with one peer
+func TestSync(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		return source
+	}
+	syncer := setupSyncer(mkSource("source"))
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a
+// panic within the prover
+func TestSyncTinyTriePanic(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(1)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		return source
+	}
+	syncer := setupSyncer(mkSource("source"))
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestMultiSync tests a basic sync with multiple peers
+func TestMultiSync(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		return source
+	}
+	syncer := setupSyncer(mkSource("sourceA"), mkSource("sourceB"))
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncWithStorage tests  basic sync using accounts + storage + code
+func TestSyncWithStorage(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+		return source
+	}
+	syncer := setupSyncer(mkSource("sourceA"))
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all
+func TestMultiSyncManyUseless(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
+
+	mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+
+		if !noAccount {
+			source.accountRequestHandler = emptyRequestAccountRangeFn
+		}
+		if !noStorage {
+			source.storageRequestHandler = emptyStorageRequestHandler
+		}
+		if !noTrieNode {
+			source.trieRequestHandler = emptyTrieRequestHandler
+		}
+		return source
+	}
+
+	syncer := setupSyncer(
+		mkSource("full", true, true, true),
+		mkSource("noAccounts", false, true, true),
+		mkSource("noStorage", true, false, true),
+		mkSource("noTrie", true, true, false),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all
+func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) {
+	// We're setting the timeout to very low, to increase the chance of the timeout
+	// being triggered. This was previously a cause of panic, when a response
+	// arrived simultaneously as a timeout was triggered.
+	defer func(old time.Duration) { requestTimeout = old }(requestTimeout)
+	requestTimeout = time.Millisecond
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
+
+	mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+
+		if !noAccount {
+			source.accountRequestHandler = emptyRequestAccountRangeFn
+		}
+		if !noStorage {
+			source.storageRequestHandler = emptyStorageRequestHandler
+		}
+		if !noTrieNode {
+			source.trieRequestHandler = emptyTrieRequestHandler
+		}
+		return source
+	}
+
+	syncer := setupSyncer(
+		mkSource("full", true, true, true),
+		mkSource("noAccounts", false, true, true),
+		mkSource("noStorage", true, false, true),
+		mkSource("noTrie", true, true, false),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all
+func TestMultiSyncManyUnresponsive(t *testing.T) {
+	// We're setting the timeout to very low, to make the test run a bit faster
+	defer func(old time.Duration) { requestTimeout = old }(requestTimeout)
+	requestTimeout = time.Millisecond
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
+
+	mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+
+		if !noAccount {
+			source.accountRequestHandler = nonResponsiveRequestAccountRangeFn
+		}
+		if !noStorage {
+			source.storageRequestHandler = nonResponsiveStorageRequestHandler
+		}
+		if !noTrieNode {
+			source.trieRequestHandler = nonResponsiveTrieRequestHandler
+		}
+		return source
+	}
+
+	syncer := setupSyncer(
+		mkSource("full", true, true, true),
+		mkSource("noAccounts", false, true, true),
+		mkSource("noStorage", true, false, true),
+		mkSource("noTrie", true, true, false),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+func checkStall(t *testing.T, term func()) chan struct{} {
+	testDone := make(chan struct{})
+	go func() {
+		select {
+		case <-time.After(time.Minute): // TODO(karalabe): Make tests smaller, this is too much
+			t.Log("Sync stalled")
+			term()
+		case <-testDone:
+			return
+		}
+	}()
+	return testDone
+}
+
+// TestSyncBoundaryAccountTrie tests sync against a few normal peers, but the
+// account trie has a few boundary elements.
+func TestSyncBoundaryAccountTrie(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeBoundaryAccountTrie(3000)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		return source
+	}
+	syncer := setupSyncer(
+		mkSource("peer-a"),
+		mkSource("peer-b"),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is
+// consistently returning very small results
+func TestSyncNoStorageAndOneCappedPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
+
+	mkSource := func(name string, slow bool) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+
+		if slow {
+			source.accountRequestHandler = starvingAccountRequestHandler
+		}
+		return source
+	}
+
+	syncer := setupSyncer(
+		mkSource("nice-a", false),
+		mkSource("nice-b", false),
+		mkSource("nice-c", false),
+		mkSource("capped", true),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver
+// code requests properly.
+func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
+
+	mkSource := func(name string, codeFn codeHandlerFunc) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.codeRequestHandler = codeFn
+		return source
+	}
+	// One is capped, one is corrupt. If we don't use a capped one, there's a 50%
+	// chance that the full set of codes requested are sent only to the
+	// non-corrupt peer, which delivers everything in one go, and makes the
+	// test moot
+	syncer := setupSyncer(
+		mkSource("capped", cappedCodeRequestHandler),
+		mkSource("corrupt", corruptCodeRequestHandler),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
+
+	mkSource := func(name string, accFn accountHandlerFunc) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.accountRequestHandler = accFn
+		return source
+	}
+	// One is capped, one is corrupt. If we don't use a capped one, there's a 50%
+	// chance that the full set of codes requested are sent only to the
+	// non-corrupt peer, which delivers everything in one go, and makes the
+	// test moot
+	syncer := setupSyncer(
+		mkSource("capped", defaultAccountRequestHandler),
+		mkSource("corrupt", corruptAccountRequestHandler),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes
+// one by one
+func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(3000)
+
+	mkSource := func(name string, codeFn codeHandlerFunc) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.codeRequestHandler = codeFn
+		return source
+	}
+	// Count how many times it's invoked. Remember, there are only 8 unique hashes,
+	// so it shouldn't be more than that
+	var counter int
+	syncer := setupSyncer(
+		mkSource("capped", func(t *testPeer, id uint64, hashes []common.Hash, max uint64) error {
+			counter++
+			return cappedCodeRequestHandler(t, id, hashes, max)
+		}),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	// There are only 8 unique hashes, and 3K accounts. However, the code
+	// deduplication is per request batch. If it were a perfect global dedup,
+	// we would expect only 8 requests. If there were no dedup, there would be
+	// 3k requests.
+	// We expect somewhere below 100 requests for these 8 unique hashes.
+	if threshold := 100; counter > threshold {
+		t.Fatalf("Error, expected < %d invocations, got %d", threshold, counter)
+	}
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncBoundaryStorageTrie tests sync against a few normal peers, but the
+// storage trie has a few boundary elements.
+func TestSyncBoundaryStorageTrie(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+		return source
+	}
+	syncer := setupSyncer(
+		mkSource("peer-a"),
+		mkSource("peer-b"),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is
+// consistently returning very small results
+func TestSyncWithStorageAndOneCappedPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false)
+
+	mkSource := func(name string, slow bool) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+
+		if slow {
+			source.storageRequestHandler = starvingStorageRequestHandler
+		}
+		return source
+	}
+
+	syncer := setupSyncer(
+		mkSource("nice-a", false),
+		mkSource("slow", true),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is
+// sometimes sending bad proofs
+func TestSyncWithStorageAndCorruptPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
+
+	mkSource := func(name string, handler storageHandlerFunc) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+		source.storageRequestHandler = handler
+		return source
+	}
+
+	syncer := setupSyncer(
+		mkSource("nice-a", defaultStorageRequestHandler),
+		mkSource("nice-b", defaultStorageRequestHandler),
+		mkSource("nice-c", defaultStorageRequestHandler),
+		mkSource("corrupt", corruptStorageRequestHandler),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+func TestSyncWithStorageAndNonProvingPeer(t *testing.T) {
+	t.Parallel()
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false)
+
+	mkSource := func(name string, handler storageHandlerFunc) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+		source.storageRequestHandler = handler
+		return source
+	}
+	syncer := setupSyncer(
+		mkSource("nice-a", defaultStorageRequestHandler),
+		mkSource("nice-b", defaultStorageRequestHandler),
+		mkSource("nice-c", defaultStorageRequestHandler),
+		mkSource("corrupt", noProofStorageRequestHandler),
+	)
+	done := checkStall(t, term)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	close(done)
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+// TestSyncWithStorage tests  basic sync using accounts + storage + code, against
+// a peer who insists on delivering full storage sets _and_ proofs. This triggered
+// an error, where the recipient erroneously clipped the boundary nodes, but
+// did not mark the account for healing.
+func TestSyncWithStorageMisbehavingProve(t *testing.T) {
+	t.Parallel()
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(10, 30, false)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		source.storageTries = storageTries
+		source.storageValues = storageElems
+		source.storageRequestHandler = proofHappyStorageRequestHandler
+		return source
+	}
+	syncer := setupSyncer(mkSource("sourceA"))
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+}
+
+type kv struct {
+	k, v []byte
+}
+
+// Some helpers for sorting
+type entrySlice []*kv
+
+func (p entrySlice) Len() int           { return len(p) }
+func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 }
+func (p entrySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+func key32(i uint64) []byte {
+	key := make([]byte, 32)
+	binary.LittleEndian.PutUint64(key, i)
+	return key
+}
+
+var (
+	codehashes = []common.Hash{
+		crypto.Keccak256Hash([]byte{0}),
+		crypto.Keccak256Hash([]byte{1}),
+		crypto.Keccak256Hash([]byte{2}),
+		crypto.Keccak256Hash([]byte{3}),
+		crypto.Keccak256Hash([]byte{4}),
+		crypto.Keccak256Hash([]byte{5}),
+		crypto.Keccak256Hash([]byte{6}),
+		crypto.Keccak256Hash([]byte{7}),
+	}
+)
+
+// getCodeHash returns a pseudo-random code hash
+func getCodeHash(i uint64) []byte {
+	h := codehashes[int(i)%len(codehashes)]
+	return common.CopyBytes(h[:])
+}
+
+// getCodeByHash convenience function to lookup the code from the code hash
+func getCodeByHash(hash common.Hash) []byte {
+	if hash == emptyCode {
+		return nil
+	}
+	for i, h := range codehashes {
+		if h == hash {
+			return []byte{byte(i)}
+		}
+	}
+	return nil
+}
+
+// makeAccountTrieNoStorage spits out a trie, along with the leafs
+func makeAccountTrieNoStorage(n int) (*trie.Trie, entrySlice) {
+	db := trie.NewDatabase(rawdb.NewMemoryDatabase())
+	accTrie, _ := trie.New(common.Hash{}, db)
+	var entries entrySlice
+	for i := uint64(1); i <= uint64(n); i++ {
+		value, _ := rlp.EncodeToBytes(state.Account{
+			Nonce:    i,
+			Balance:  big.NewInt(int64(i)),
+			Root:     emptyRoot,
+			CodeHash: getCodeHash(i),
+		})
+		key := key32(i)
+		elem := &kv{key, value}
+		accTrie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+	}
+	sort.Sort(entries)
+	accTrie.Commit(nil)
+	return accTrie, entries
+}
+
+// makeBoundaryAccountTrie constructs an account trie. Instead of filling
+// accounts normally, this function will fill a few accounts which have
+// boundary hash.
+func makeBoundaryAccountTrie(n int) (*trie.Trie, entrySlice) {
+	var (
+		entries    entrySlice
+		boundaries []common.Hash
+
+		db      = trie.NewDatabase(rawdb.NewMemoryDatabase())
+		trie, _ = trie.New(common.Hash{}, db)
+	)
+	// Initialize boundaries
+	var next common.Hash
+	step := new(big.Int).Sub(
+		new(big.Int).Div(
+			new(big.Int).Exp(common.Big2, common.Big256, nil),
+			big.NewInt(int64(accountConcurrency)),
+		), common.Big1,
+	)
+	for i := 0; i < accountConcurrency; i++ {
+		last := common.BigToHash(new(big.Int).Add(next.Big(), step))
+		if i == accountConcurrency-1 {
+			last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+		}
+		boundaries = append(boundaries, last)
+		next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
+	}
+	// Fill boundary accounts
+	for i := 0; i < len(boundaries); i++ {
+		value, _ := rlp.EncodeToBytes(state.Account{
+			Nonce:    uint64(0),
+			Balance:  big.NewInt(int64(i)),
+			Root:     emptyRoot,
+			CodeHash: getCodeHash(uint64(i)),
+		})
+		elem := &kv{boundaries[i].Bytes(), value}
+		trie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+	}
+	// Fill other accounts if required
+	for i := uint64(1); i <= uint64(n); i++ {
+		value, _ := rlp.EncodeToBytes(state.Account{
+			Nonce:    i,
+			Balance:  big.NewInt(int64(i)),
+			Root:     emptyRoot,
+			CodeHash: getCodeHash(i),
+		})
+		elem := &kv{key32(i), value}
+		trie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+	}
+	sort.Sort(entries)
+	trie.Commit(nil)
+	return trie, entries
+}
+
+// makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts
+// has a unique storage set.
+func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) {
+	var (
+		db             = trie.NewDatabase(rawdb.NewMemoryDatabase())
+		accTrie, _     = trie.New(common.Hash{}, db)
+		entries        entrySlice
+		storageTries   = make(map[common.Hash]*trie.Trie)
+		storageEntries = make(map[common.Hash]entrySlice)
+	)
+	// Create n accounts in the trie
+	for i := uint64(1); i <= uint64(accounts); i++ {
+		key := key32(i)
+		codehash := emptyCode[:]
+		if code {
+			codehash = getCodeHash(i)
+		}
+		// Create a storage trie
+		stTrie, stEntries := makeStorageTrieWithSeed(uint64(slots), i, db)
+		stRoot := stTrie.Hash()
+		stTrie.Commit(nil)
+		value, _ := rlp.EncodeToBytes(state.Account{
+			Nonce:    i,
+			Balance:  big.NewInt(int64(i)),
+			Root:     stRoot,
+			CodeHash: codehash,
+		})
+		elem := &kv{key, value}
+		accTrie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+
+		storageTries[common.BytesToHash(key)] = stTrie
+		storageEntries[common.BytesToHash(key)] = stEntries
+	}
+	sort.Sort(entries)
+
+	accTrie.Commit(nil)
+	return accTrie, entries, storageTries, storageEntries
+}
+
+// makeAccountTrieWithStorage spits out a trie, along with the leafs
+func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (*trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) {
+	var (
+		db             = trie.NewDatabase(rawdb.NewMemoryDatabase())
+		accTrie, _     = trie.New(common.Hash{}, db)
+		entries        entrySlice
+		storageTries   = make(map[common.Hash]*trie.Trie)
+		storageEntries = make(map[common.Hash]entrySlice)
+	)
+	// Make a storage trie which we reuse for the whole lot
+	var (
+		stTrie    *trie.Trie
+		stEntries entrySlice
+	)
+	if boundary {
+		stTrie, stEntries = makeBoundaryStorageTrie(slots, db)
+	} else {
+		stTrie, stEntries = makeStorageTrieWithSeed(uint64(slots), 0, db)
+	}
+	stRoot := stTrie.Hash()
+
+	// Create n accounts in the trie
+	for i := uint64(1); i <= uint64(accounts); i++ {
+		key := key32(i)
+		codehash := emptyCode[:]
+		if code {
+			codehash = getCodeHash(i)
+		}
+		value, _ := rlp.EncodeToBytes(state.Account{
+			Nonce:    i,
+			Balance:  big.NewInt(int64(i)),
+			Root:     stRoot,
+			CodeHash: codehash,
+		})
+		elem := &kv{key, value}
+		accTrie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+		// we reuse the same one for all accounts
+		storageTries[common.BytesToHash(key)] = stTrie
+		storageEntries[common.BytesToHash(key)] = stEntries
+	}
+	sort.Sort(entries)
+	stTrie.Commit(nil)
+	accTrie.Commit(nil)
+	return accTrie, entries, storageTries, storageEntries
+}
+
+// makeStorageTrieWithSeed fills a storage trie with n items, returning the
+// not-yet-committed trie and the sorted entries. The seeds can be used to ensure
+// that tries are unique.
+func makeStorageTrieWithSeed(n, seed uint64, db *trie.Database) (*trie.Trie, entrySlice) {
+	trie, _ := trie.New(common.Hash{}, db)
+	var entries entrySlice
+	for i := uint64(1); i <= n; i++ {
+		// store 'x' at slot 'x'
+		slotValue := key32(i + seed)
+		rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:]))
+
+		slotKey := key32(i)
+		key := crypto.Keccak256Hash(slotKey[:])
+
+		elem := &kv{key[:], rlpSlotValue}
+		trie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+	}
+	sort.Sort(entries)
+	trie.Commit(nil)
+	return trie, entries
+}
+
+// makeBoundaryStorageTrie constructs a storage trie. Instead of filling
+// storage slots normally, this function will fill a few slots which have
+// boundary hash.
+func makeBoundaryStorageTrie(n int, db *trie.Database) (*trie.Trie, entrySlice) {
+	var (
+		entries    entrySlice
+		boundaries []common.Hash
+		trie, _    = trie.New(common.Hash{}, db)
+	)
+	// Initialize boundaries
+	var next common.Hash
+	step := new(big.Int).Sub(
+		new(big.Int).Div(
+			new(big.Int).Exp(common.Big2, common.Big256, nil),
+			big.NewInt(int64(accountConcurrency)),
+		), common.Big1,
+	)
+	for i := 0; i < accountConcurrency; i++ {
+		last := common.BigToHash(new(big.Int).Add(next.Big(), step))
+		if i == accountConcurrency-1 {
+			last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+		}
+		boundaries = append(boundaries, last)
+		next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1))
+	}
+	// Fill boundary slots
+	for i := 0; i < len(boundaries); i++ {
+		key := boundaries[i]
+		val := []byte{0xde, 0xad, 0xbe, 0xef}
+
+		elem := &kv{key[:], val}
+		trie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+	}
+	// Fill other slots if required
+	for i := uint64(1); i <= uint64(n); i++ {
+		slotKey := key32(i)
+		key := crypto.Keccak256Hash(slotKey[:])
+
+		slotValue := key32(i)
+		rlpSlotValue, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(slotValue[:]))
+
+		elem := &kv{key[:], rlpSlotValue}
+		trie.Update(elem.k, elem.v)
+		entries = append(entries, elem)
+	}
+	sort.Sort(entries)
+	trie.Commit(nil)
+	return trie, entries
+}
+
+func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) {
+	t.Helper()
+	triedb := trie.NewDatabase(db)
+	accTrie, err := trie.New(root, triedb)
+	if err != nil {
+		t.Fatal(err)
+	}
+	accounts, slots := 0, 0
+	accIt := trie.NewIterator(accTrie.NodeIterator(nil))
+	for accIt.Next() {
+		var acc struct {
+			Nonce    uint64
+			Balance  *big.Int
+			Root     common.Hash
+			CodeHash []byte
+		}
+		if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil {
+			log.Crit("Invalid account encountered during snapshot creation", "err", err)
+		}
+		accounts++
+		if acc.Root != emptyRoot {
+			storeTrie, err := trie.NewSecure(acc.Root, triedb)
+			if err != nil {
+				t.Fatal(err)
+			}
+			storeIt := trie.NewIterator(storeTrie.NodeIterator(nil))
+			for storeIt.Next() {
+				slots++
+			}
+			if err := storeIt.Err; err != nil {
+				t.Fatal(err)
+			}
+		}
+	}
+	if err := accIt.Err; err != nil {
+		t.Fatal(err)
+	}
+	t.Logf("accounts: %d, slots: %d", accounts, slots)
+}
+
+// TestSyncAccountPerformance tests how efficient the snap algo is at minimizing
+// state healing
+func TestSyncAccountPerformance(t *testing.T) {
+	// Set the account concurrency to 1. This _should_ result in the
+	// range root to become correct, and there should be no healing needed
+	defer func(old int) { accountConcurrency = old }(accountConcurrency)
+	accountConcurrency = 1
+
+	var (
+		once   sync.Once
+		cancel = make(chan struct{})
+		term   = func() {
+			once.Do(func() {
+				close(cancel)
+			})
+		}
+	)
+	sourceAccountTrie, elems := makeAccountTrieNoStorage(100)
+
+	mkSource := func(name string) *testPeer {
+		source := newTestPeer(name, t, term)
+		source.accountTrie = sourceAccountTrie
+		source.accountValues = elems
+		return source
+	}
+	src := mkSource("source")
+	syncer := setupSyncer(src)
+	if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil {
+		t.Fatalf("sync failed: %v", err)
+	}
+	verifyTrie(syncer.db, sourceAccountTrie.Hash(), t)
+	// The trie root will always be requested, since it is added when the snap
+	// sync cycle starts. When popping the queue, we do not look it up again.
+	// Doing so would bring this number down to zero in this artificial testcase,
+	// but only add extra IO for no reason in practice.
+	if have, want := src.nTrienodeRequests, 1; have != want {
+		fmt.Printf(src.Stats())
+		t.Errorf("trie node heal requests wrong, want %d, have %d", want, have)
+	}
+}
+
+func TestSlotEstimation(t *testing.T) {
+	for i, tc := range []struct {
+		last  common.Hash
+		count int
+		want  uint64
+	}{
+		{
+			// Half the space
+			common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			100,
+			100,
+		},
+		{
+			// 1 / 16th
+			common.HexToHash("0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
+			100,
+			1500,
+		},
+		{
+			// Bit more than 1 / 16th
+			common.HexToHash("0x1000000000000000000000000000000000000000000000000000000000000000"),
+			100,
+			1499,
+		},
+		{
+			// Almost everything
+			common.HexToHash("0xF000000000000000000000000000000000000000000000000000000000000000"),
+			100,
+			6,
+		},
+		{
+			// Almost nothing -- should lead to error
+			common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
+			1,
+			0,
+		},
+		{
+			// Nothing -- should lead to error
+			common.Hash{},
+			100,
+			0,
+		},
+	} {
+		have, _ := estimateRemainingSlots(tc.count, tc.last)
+		if want := tc.want; have != want {
+			t.Errorf("test %d: have %d want %d", i, have, want)
+		}
+	}
+}
diff --git a/accounts/abi/numbers_test.go b/eth/protocols/snap/tracker.go
similarity index 72%
rename from accounts/abi/numbers_test.go
rename to eth/protocols/snap/tracker.go
index d25a5abcb5..2cf59cc23a 100644
--- a/accounts/abi/numbers_test.go
+++ b/eth/protocols/snap/tracker.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The go-ethereum Authors
+// Copyright 2021 The go-ethereum Authors
 // This file is part of the go-ethereum library.
 //
 // The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,20 +14,13 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package abi
+package snap
 
 import (
-	"bytes"
-	"math/big"
-	"testing"
-)
+	"time"
 
-func TestNumberTypes(t *testing.T) {
-	ubytes := make([]byte, 32)
-	ubytes[31] = 1
+	"github.com/ethereum/go-ethereum/p2p/tracker"
+)
 
-	unsigned := U256(big.NewInt(1))
-	if !bytes.Equal(unsigned, ubytes) {
-		t.Errorf("expected %x got %x", ubytes, unsigned)
-	}
-}
+// requestTracker is a singleton tracker for request times.
+var requestTracker = tracker.New(ProtocolName, time.Minute)
diff --git a/eth/state_accessor.go b/eth/state_accessor.go
new file mode 100644
index 0000000000..84cfaf4d73
--- /dev/null
+++ b/eth/state_accessor.go
@@ -0,0 +1,182 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package eth
+
+import (
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+// stateAtBlock retrieves the state database associated with a certain block.
+// If no state is locally available for the given block, a number of blocks
+// are attempted to be reexecuted to generate the desired state. The optional
+// base layer statedb can be passed then it's regarded as the statedb of the
+// parent block.
+func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (statedb *state.StateDB, err error) {
+	var (
+		current  *types.Block
+		database state.Database
+		report   = true
+		origin   = block.NumberU64()
+	)
+	// Check the live database first if we have the state fully available, use that.
+	if checkLive {
+		statedb, err = eth.blockchain.StateAt(block.Root())
+		if err == nil {
+			return statedb, nil
+		}
+	}
+	if base != nil {
+		// The optional base statedb is given, mark the start point as parent block
+		statedb, database, report = base, base.Database(), false
+		current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+	} else {
+		// Otherwise try to reexec blocks until we find a state or reach our limit
+		current = block
+
+		// Create an ephemeral trie.Database for isolating the live one. Otherwise
+		// the internal junks created by tracing will be persisted into the disk.
+		database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16})
+
+		// If we didn't check the dirty database, do check the clean one, otherwise
+		// we would rewind past a persisted block (specific corner case is chain
+		// tracing from the genesis).
+		if !checkLive {
+			statedb, err = state.New(current.Root(), database, nil)
+			if err == nil {
+				return statedb, nil
+			}
+		}
+		// Database does not have the state for the given block, try to regenerate
+		for i := uint64(0); i < reexec; i++ {
+			if current.NumberU64() == 0 {
+				return nil, errors.New("genesis state is missing")
+			}
+			parent := eth.blockchain.GetBlock(current.ParentHash(), current.NumberU64()-1)
+			if parent == nil {
+				return nil, fmt.Errorf("missing block %v %d", current.ParentHash(), current.NumberU64()-1)
+			}
+			current = parent
+
+			statedb, err = state.New(current.Root(), database, nil)
+			if err == nil {
+				break
+			}
+		}
+		if err != nil {
+			switch err.(type) {
+			case *trie.MissingNodeError:
+				return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
+			default:
+				return nil, err
+			}
+		}
+	}
+	// State was available at historical point, regenerate
+	var (
+		start  = time.Now()
+		logged time.Time
+		parent common.Hash
+	)
+	for current.NumberU64() < origin {
+		// Print progress logs if long enough time elapsed
+		if time.Since(logged) > 8*time.Second && report {
+			log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start))
+			logged = time.Now()
+		}
+		// Retrieve the next block to regenerate and process it
+		next := current.NumberU64() + 1
+		if current = eth.blockchain.GetBlockByNumber(next); current == nil {
+			return nil, fmt.Errorf("block #%d not found", next)
+		}
+		_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
+		if err != nil {
+			return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
+		}
+		// Finalize the state so any modifications are written to the trie
+		root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number()))
+		if err != nil {
+			return nil, err
+		}
+		statedb, err = state.New(root, database, nil)
+		if err != nil {
+			return nil, fmt.Errorf("state reset after block %d failed: %v", current.NumberU64(), err)
+		}
+		database.TrieDB().Reference(root, common.Hash{})
+		if parent != (common.Hash{}) {
+			database.TrieDB().Dereference(parent)
+		}
+		parent = root
+	}
+	if report {
+		nodes, imgs := database.TrieDB().Size()
+		log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
+	}
+	return statedb, nil
+}
+
+// stateAtTransaction returns the execution environment of a certain transaction.
+func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
+	// Short circuit if it's genesis block.
+	if block.NumberU64() == 0 {
+		return nil, vm.BlockContext{}, nil, errors.New("no transaction in genesis")
+	}
+	// Create the parent state database
+	parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+	if parent == nil {
+		return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
+	}
+	// Lookup the statedb of parent block from the live database,
+	// otherwise regenerate it on the flight.
+	statedb, err := eth.stateAtBlock(parent, reexec, nil, true)
+	if err != nil {
+		return nil, vm.BlockContext{}, nil, err
+	}
+	if txIndex == 0 && len(block.Transactions()) == 0 {
+		return nil, vm.BlockContext{}, statedb, nil
+	}
+	// Recompute transactions up to the target index.
+	signer := types.MakeSigner(eth.blockchain.Config(), block.Number())
+	for idx, tx := range block.Transactions() {
+		// Assemble the transaction call message and return if the requested offset
+		msg, _ := tx.AsMessage(signer)
+		txContext := core.NewEVMTxContext(msg)
+		context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
+		if idx == txIndex {
+			return msg, context, statedb, nil
+		}
+		// Not yet the searched for transaction, execute on top of the current state
+		vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
+		statedb.Prepare(tx.Hash(), block.Hash(), idx)
+		if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
+			return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
+		}
+		// Ensure any modifications are committed to the state
+		// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+	}
+	return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
+}
diff --git a/eth/sync.go b/eth/sync.go
index d689200dce..4520ec6879 100644
--- a/eth/sync.go
+++ b/eth/sync.go
@@ -23,8 +23,10 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 )
@@ -39,12 +41,12 @@ const (
 )
 
 type txsync struct {
-	p   *peer
+	p   *eth.Peer
 	txs []*types.Transaction
 }
 
 // syncTransactions starts sending all currently pending transactions to the given peer.
-func (pm *ProtocolManager) syncTransactions(p *peer) {
+func (h *handler) syncTransactions(p *eth.Peer) {
 	// Assemble the set of transaction to broadcast or announce to the remote
 	// peer. Fun fact, this is quite an expensive operation as it needs to sort
 	// the transactions if the sorting is not cached yet. However, with a random
@@ -52,7 +54,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
 	//
 	// TODO(karalabe): Figure out if we could get away with random order somehow
 	var txs types.Transactions
-	pending, _ := pm.txpool.Pending()
+	pending, _ := h.txpool.Pending()
 	for _, batch := range pending {
 		txs = append(txs, batch...)
 	}
@@ -62,7 +64,7 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
 	// The eth/65 protocol introduces proper transaction announcements, so instead
 	// of dripping transactions across multiple peers, just send the entire list as
 	// an announcement and let the remote side decide what they need (likely nothing).
-	if p.version >= eth65 {
+	if p.Version() >= eth.ETH65 {
 		hashes := make([]common.Hash, len(txs))
 		for i, tx := range txs {
 			hashes[i] = tx.Hash()
@@ -72,8 +74,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
 	}
 	// Out of luck, peer is running legacy protocols, drop the txs over
 	select {
-	case pm.txsyncCh <- &txsync{p: p, txs: txs}:
-	case <-pm.quitSync:
+	case h.txsyncCh <- &txsync{p: p, txs: txs}:
+	case <-h.quitSync:
 	}
 }
 
@@ -81,8 +83,8 @@ func (pm *ProtocolManager) syncTransactions(p *peer) {
 // connection. When a new peer appears, we relay all currently pending
 // transactions. In order to minimise egress bandwidth usage, we send
 // the transactions in small packs to one peer at a time.
-func (pm *ProtocolManager) txsyncLoop64() {
-	defer pm.wg.Done()
+func (h *handler) txsyncLoop64() {
+	defer h.wg.Done()
 
 	var (
 		pending = make(map[enode.ID]*txsync)
@@ -93,7 +95,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
 
 	// send starts a sending a pack of transactions from the sync.
 	send := func(s *txsync) {
-		if s.p.version >= eth65 {
+		if s.p.Version() >= eth.ETH65 {
 			panic("initial transaction syncer running on eth/65+")
 		}
 		// Fill pack with transactions up to the target size.
@@ -107,14 +109,13 @@ func (pm *ProtocolManager) txsyncLoop64() {
 		// Remove the transactions that will be sent.
 		s.txs = s.txs[:copy(s.txs, s.txs[len(pack.txs):])]
 		if len(s.txs) == 0 {
-			delete(pending, s.p.ID())
+			delete(pending, s.p.Peer.ID())
 		}
 		// Send the pack in the background.
 		s.p.Log().Trace("Sending batch of transactions", "count", len(pack.txs), "bytes", size)
 		sending = true
-		go func() { done <- pack.p.SendTransactions64(pack.txs) }()
+		go func() { done <- pack.p.SendTransactions(pack.txs) }()
 	}
-
 	// pick chooses the next pending sync.
 	pick := func() *txsync {
 		if len(pending) == 0 {
@@ -131,8 +132,8 @@ func (pm *ProtocolManager) txsyncLoop64() {
 
 	for {
 		select {
-		case s := <-pm.txsyncCh:
-			pending[s.p.ID()] = s
+		case s := <-h.txsyncCh:
+			pending[s.p.Peer.ID()] = s
 			if !sending {
 				send(s)
 			}
@@ -141,13 +142,13 @@ func (pm *ProtocolManager) txsyncLoop64() {
 			// Stop tracking peers that cause send failures.
 			if err != nil {
 				pack.p.Log().Debug("Transaction send failed", "err", err)
-				delete(pending, pack.p.ID())
+				delete(pending, pack.p.Peer.ID())
 			}
 			// Schedule the next send.
 			if s := pick(); s != nil {
 				send(s)
 			}
-		case <-pm.quitSync:
+		case <-h.quitSync:
 			return
 		}
 	}
@@ -155,7 +156,7 @@ func (pm *ProtocolManager) txsyncLoop64() {
 
 // chainSyncer coordinates blockchain sync components.
 type chainSyncer struct {
-	pm          *ProtocolManager
+	handler     *handler
 	force       *time.Timer
 	forced      bool // true when force timer fired
 	peerEventCh chan struct{}
@@ -165,15 +166,15 @@ type chainSyncer struct {
 // chainSyncOp is a scheduled sync operation.
 type chainSyncOp struct {
 	mode downloader.SyncMode
-	peer *peer
+	peer *eth.Peer
 	td   *big.Int
 	head common.Hash
 }
 
 // newChainSyncer creates a chainSyncer.
-func newChainSyncer(pm *ProtocolManager) *chainSyncer {
+func newChainSyncer(handler *handler) *chainSyncer {
 	return &chainSyncer{
-		pm:          pm,
+		handler:     handler,
 		peerEventCh: make(chan struct{}),
 	}
 }
@@ -181,24 +182,24 @@ func newChainSyncer(pm *ProtocolManager) *chainSyncer {
 // handlePeerEvent notifies the syncer about a change in the peer set.
 // This is called for new peers and every time a peer announces a new
 // chain head.
-func (cs *chainSyncer) handlePeerEvent(p *peer) bool {
+func (cs *chainSyncer) handlePeerEvent(peer *eth.Peer) bool {
 	select {
 	case cs.peerEventCh <- struct{}{}:
 		return true
-	case <-cs.pm.quitSync:
+	case <-cs.handler.quitSync:
 		return false
 	}
 }
 
 // loop runs in its own goroutine and launches the sync when necessary.
 func (cs *chainSyncer) loop() {
-	defer cs.pm.wg.Done()
+	defer cs.handler.wg.Done()
 
-	cs.pm.blockFetcher.Start()
-	cs.pm.txFetcher.Start()
-	defer cs.pm.blockFetcher.Stop()
-	defer cs.pm.txFetcher.Stop()
-	defer cs.pm.downloader.Terminate()
+	cs.handler.blockFetcher.Start()
+	cs.handler.txFetcher.Start()
+	defer cs.handler.blockFetcher.Stop()
+	defer cs.handler.txFetcher.Stop()
+	defer cs.handler.downloader.Terminate()
 
 	// The force timer lowers the peer count threshold down to one when it fires.
 	// This ensures we'll always start sync even if there aren't enough peers.
@@ -209,7 +210,6 @@ func (cs *chainSyncer) loop() {
 		if op := cs.nextSyncOp(); op != nil {
 			cs.startSync(op)
 		}
-
 		select {
 		case <-cs.peerEventCh:
 			// Peer information changed, recheck.
@@ -220,9 +220,13 @@ func (cs *chainSyncer) loop() {
 		case <-cs.force.C:
 			cs.forced = true
 
-		case <-cs.pm.quitSync:
+		case <-cs.handler.quitSync:
+			// Disable all insertion on the blockchain. This needs to happen before
+			// terminating the downloader because the downloader waits for blockchain
+			// inserts, and these can take a long time to finish.
+			cs.handler.chain.StopInsert()
+			cs.handler.downloader.Terminate()
 			if cs.doneCh != nil {
-				cs.pm.downloader.Cancel()
 				<-cs.doneCh
 			}
 			return
@@ -236,23 +240,26 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
 		return nil // Sync already running.
 	}
 
-	// Ensure we're at mininum peer count.
+	// Ensure we're at minimum peer count.
 	minPeers := defaultMinSyncPeers
 	if cs.forced {
 		minPeers = 1
-	} else if minPeers > cs.pm.maxPeers {
-		minPeers = cs.pm.maxPeers
+	} else if minPeers > cs.handler.maxPeers {
+		minPeers = cs.handler.maxPeers
 	}
-	if cs.pm.peers.Len() < minPeers {
+	if cs.handler.peers.len() < minPeers {
 		return nil
 	}
-
-	// We have enough peers, check TD.
-	peer := cs.pm.peers.BestPeer()
+	// We have enough peers, check TD
+	peer := cs.handler.peers.peerWithHighestTD()
 	if peer == nil {
 		return nil
 	}
 	mode, ourTD := cs.modeAndLocalHead()
+	if mode == downloader.FastSync && atomic.LoadUint32(&cs.handler.snapSync) == 1 {
+		// Fast sync via the snap protocol
+		mode = downloader.SnapSync
+	}
 	op := peerToSyncOp(mode, peer)
 	if op.td.Cmp(ourTD) <= 0 {
 		return nil // We're in sync.
@@ -260,52 +267,82 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
 	return op
 }
 
-func peerToSyncOp(mode downloader.SyncMode, p *peer) *chainSyncOp {
+func peerToSyncOp(mode downloader.SyncMode, p *eth.Peer) *chainSyncOp {
 	peerHead, peerTD := p.Head()
 	return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead}
 }
 
 func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) {
-	if atomic.LoadUint32(&cs.pm.fastSync) == 1 {
-		block := cs.pm.blockchain.CurrentFastBlock()
-		td := cs.pm.blockchain.GetTdByHash(block.Hash())
+	// If we're in fast sync mode, return that directly
+	if atomic.LoadUint32(&cs.handler.fastSync) == 1 {
+		block := cs.handler.chain.CurrentFastBlock()
+		td := cs.handler.chain.GetTdByHash(block.Hash())
 		return downloader.FastSync, td
-	} else {
-		head := cs.pm.blockchain.CurrentHeader()
-		td := cs.pm.blockchain.GetTd(head.Hash(), head.Number.Uint64())
-		return downloader.FullSync, td
 	}
+	// We are probably in full sync, but we might have rewound to before the
+	// fast sync pivot, check if we should reenable
+	if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil {
+		if head := cs.handler.chain.CurrentBlock(); head.NumberU64() < *pivot {
+			block := cs.handler.chain.CurrentFastBlock()
+			td := cs.handler.chain.GetTdByHash(block.Hash())
+			return downloader.FastSync, td
+		}
+	}
+	// Nope, we're really full syncing
+	head := cs.handler.chain.CurrentBlock()
+	td := cs.handler.chain.GetTd(head.Hash(), head.NumberU64())
+	return downloader.FullSync, td
 }
 
 // startSync launches doSync in a new goroutine.
 func (cs *chainSyncer) startSync(op *chainSyncOp) {
 	cs.doneCh = make(chan error, 1)
-	go func() { cs.doneCh <- cs.pm.doSync(op) }()
+	go func() { cs.doneCh <- cs.handler.doSync(op) }()
 }
 
 // doSync synchronizes the local blockchain with a remote peer.
-func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
+func (h *handler) doSync(op *chainSyncOp) error {
+	if op.mode == downloader.FastSync || op.mode == downloader.SnapSync {
+		// Before launch the fast sync, we have to ensure user uses the same
+		// txlookup limit.
+		// The main concern here is: during the fast sync Geth won't index the
+		// block(generate tx indices) before the HEAD-limit. But if user changes
+		// the limit in the next fast sync(e.g. user kill Geth manually and
+		// restart) then it will be hard for Geth to figure out the oldest block
+		// has been indexed. So here for the user-experience wise, it's non-optimal
+		// that user can't change limit during the fast sync. If changed, Geth
+		// will just blindly use the original one.
+		limit := h.chain.TxLookupLimit()
+		if stored := rawdb.ReadFastTxLookupLimit(h.database); stored == nil {
+			rawdb.WriteFastTxLookupLimit(h.database, limit)
+		} else if *stored != limit {
+			h.chain.SetTxLookupLimit(*stored)
+			log.Warn("Update txLookup limit", "provided", limit, "updated", *stored)
+		}
+	}
 	// Run the sync cycle, and disable fast sync if we're past the pivot block
-	err := pm.downloader.Synchronise(op.peer.id, op.head, op.td, op.mode)
+	err := h.downloader.Synchronise(op.peer.ID(), op.head, op.td, op.mode)
 	if err != nil {
 		return err
 	}
-	if atomic.LoadUint32(&pm.fastSync) == 1 {
+	if atomic.LoadUint32(&h.fastSync) == 1 {
 		log.Info("Fast sync complete, auto disabling")
-		atomic.StoreUint32(&pm.fastSync, 0)
+		atomic.StoreUint32(&h.fastSync, 0)
+	}
+	if atomic.LoadUint32(&h.snapSync) == 1 {
+		log.Info("Snap sync complete, auto disabling")
+		atomic.StoreUint32(&h.snapSync, 0)
 	}
-
 	// If we've successfully finished a sync cycle and passed any required checkpoint,
 	// enable accepting transactions from the network.
-	head := pm.blockchain.CurrentBlock()
-	if head.NumberU64() >= pm.checkpointNumber {
+	head := h.chain.CurrentBlock()
+	if head.NumberU64() >= h.checkpointNumber {
 		// Checkpoint passed, sanity check the timestamp to have a fallback mechanism
 		// for non-checkpointed (number = 0) private networks.
 		if head.Time() >= uint64(time.Now().AddDate(0, -1, 0).Unix()) {
-			atomic.StoreUint32(&pm.acceptTxs, 1)
+			atomic.StoreUint32(&h.acceptTxs, 1)
 		}
 	}
-
 	if head.NumberU64() > 0 {
 		// We've completed a sync cycle, notify all peers of new state. This path is
 		// essential in star-topology networks where a gateway node needs to notify
@@ -313,8 +350,7 @@ func (pm *ProtocolManager) doSync(op *chainSyncOp) error {
 		// scenario will most often crop up in private and hackathon networks with
 		// degenerate connectivity, but it should be healthy for the mainnet too to
 		// more reliably update peers or the local TD state.
-		pm.BroadcastBlock(head, false)
+		h.BroadcastBlock(head, false)
 	}
-
 	return nil
 }
diff --git a/eth/sync_test.go b/eth/sync_test.go
index ac1e5fad1b..a0c6f86023 100644
--- a/eth/sync_test.go
+++ b/eth/sync_test.go
@@ -22,43 +22,59 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
-func TestFastSyncDisabling63(t *testing.T) { testFastSyncDisabling(t, 63) }
-func TestFastSyncDisabling64(t *testing.T) { testFastSyncDisabling(t, 64) }
-func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) }
+// Tests that fast sync is disabled after a successful sync cycle.
+func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, eth.ETH65) }
+func TestFastSyncDisabling66(t *testing.T) { testFastSyncDisabling(t, eth.ETH66) }
 
 // Tests that fast sync gets disabled as soon as a real block is successfully
 // imported into the blockchain.
-func testFastSyncDisabling(t *testing.T, protocol int) {
+func testFastSyncDisabling(t *testing.T, protocol uint) {
 	t.Parallel()
 
-	// Create a pristine protocol manager, check that fast sync is left enabled
-	pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil)
-	if atomic.LoadUint32(&pmEmpty.fastSync) == 0 {
+	// Create an empty handler and ensure it's in fast sync mode
+	empty := newTestHandler()
+	if atomic.LoadUint32(&empty.handler.fastSync) == 0 {
 		t.Fatalf("fast sync disabled on pristine blockchain")
 	}
-	// Create a full protocol manager, check that fast sync gets disabled
-	pmFull, _ := newTestProtocolManagerMust(t, downloader.FastSync, 1024, nil, nil)
-	if atomic.LoadUint32(&pmFull.fastSync) == 1 {
+	defer empty.close()
+
+	// Create a full handler and ensure fast sync ends up disabled
+	full := newTestHandlerWithBlocks(1024)
+	if atomic.LoadUint32(&full.handler.fastSync) == 1 {
 		t.Fatalf("fast sync not disabled on non-empty blockchain")
 	}
+	defer full.close()
+
+	// Sync up the two handlers
+	emptyPipe, fullPipe := p2p.MsgPipe()
+	defer emptyPipe.Close()
+	defer fullPipe.Close()
 
-	// Sync up the two peers
-	io1, io2 := p2p.MsgPipe()
-	go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get))
-	go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get))
+	emptyPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{1}, "", nil), emptyPipe, empty.txpool)
+	fullPeer := eth.NewPeer(protocol, p2p.NewPeer(enode.ID{2}, "", nil), fullPipe, full.txpool)
+	defer emptyPeer.Close()
+	defer fullPeer.Close()
 
+	go empty.handler.runEthPeer(emptyPeer, func(peer *eth.Peer) error {
+		return eth.Handle((*ethHandler)(empty.handler), peer)
+	})
+	go full.handler.runEthPeer(fullPeer, func(peer *eth.Peer) error {
+		return eth.Handle((*ethHandler)(full.handler), peer)
+	})
+	// Wait a bit for the above handlers to start
 	time.Sleep(250 * time.Millisecond)
-	op := peerToSyncOp(downloader.FastSync, pmEmpty.peers.BestPeer())
-	if err := pmEmpty.doSync(op); err != nil {
-		t.Fatal("sync failed:", err)
-	}
 
 	// Check that fast sync was disabled
-	if atomic.LoadUint32(&pmEmpty.fastSync) == 1 {
+	op := peerToSyncOp(downloader.FastSync, empty.handler.peers.peerWithHighestTD())
+	if err := empty.handler.doSync(op); err != nil {
+		t.Fatal("sync failed:", err)
+	}
+	if atomic.LoadUint32(&empty.handler.fastSync) == 1 {
 		t.Fatalf("fast sync not disabled after successful synchronisation")
 	}
 }
diff --git a/eth/api_tracer.go b/eth/tracers/api.go
similarity index 51%
rename from eth/api_tracer.go
rename to eth/tracers/api.go
index 7db3327990..40def8d288 100644
--- a/eth/api_tracer.go
+++ b/eth/tracers/api.go
@@ -1,4 +1,4 @@
-// Copyright 2017 The go-ethereum Authors
+// Copyright 2021 The go-ethereum Authors
 // This file is part of the go-ethereum library.
 //
 // The go-ethereum library is free software: you can redistribute it and/or modify
@@ -14,7 +14,7 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package eth
+package tracers
 
 import (
 	"bufio"
@@ -22,7 +22,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"github.com/ethereum/go-ethereum/consensus"
 	"io/ioutil"
 	"math/big"
 	"os"
@@ -32,17 +31,18 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
-	"github.com/ethereum/go-ethereum/eth/tracers"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/internal/ethapi"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
-	"github.com/ethereum/go-ethereum/trie"
 )
 
 const (
@@ -56,6 +56,104 @@ const (
 	defaultTraceReexec = uint64(128)
 )
 
+// Backend interface provides the common API services (that are provided by
+// both full and light clients) with access to necessary functions.
+type Backend interface {
+	HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
+	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
+	BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
+	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
+	GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
+	RPCGasCap() uint64
+	ChainConfig() *params.ChainConfig
+	Engine() consensus.Engine
+	ChainDb() ethdb.Database
+	StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error)
+	StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error)
+}
+
+// API is the collection of tracing APIs exposed over the private debugging endpoint.
+type API struct {
+	backend Backend
+}
+
+// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
+func NewAPI(backend Backend) *API {
+	return &API{backend: backend}
+}
+
+type chainContext struct {
+	api *API
+	ctx context.Context
+}
+
+func (context *chainContext) Engine() consensus.Engine {
+	return context.api.backend.Engine()
+}
+
+func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
+	header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
+	if err != nil {
+		return nil
+	}
+	if header.Hash() == hash {
+		return header
+	}
+	header, err = context.api.backend.HeaderByHash(context.ctx, hash)
+	if err != nil {
+		return nil
+	}
+	return header
+}
+
+// chainContext construts the context reader which is used by the evm for reading
+// the necessary chain context.
+func (api *API) chainContext(ctx context.Context) core.ChainContext {
+	return &chainContext{api: api, ctx: ctx}
+}
+
+// blockByNumber is the wrapper of the chain access function offered by the backend.
+// It will return an error if the block is not found.
+func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
+	block, err := api.backend.BlockByNumber(ctx, number)
+	if err != nil {
+		return nil, err
+	}
+	if block == nil {
+		return nil, fmt.Errorf("block #%d not found", number)
+	}
+	return block, nil
+}
+
+// blockByHash is the wrapper of the chain access function offered by the backend.
+// It will return an error if the block is not found.
+func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
+	block, err := api.backend.BlockByHash(ctx, hash)
+	if err != nil {
+		return nil, err
+	}
+	if block == nil {
+		return nil, fmt.Errorf("block %s not found", hash.Hex())
+	}
+	return block, nil
+}
+
+// blockByNumberAndHash is the wrapper of the chain access function offered by
+// the backend. It will return an error if the block is not found.
+//
+// Note this function is friendly for the light client which can only retrieve the
+// historical(before the CHT) header/block by number.
+func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) {
+	block, err := api.blockByNumber(ctx, number)
+	if err != nil {
+		return nil, err
+	}
+	if block.Hash() == hash {
+		return block, nil
+	}
+	return api.blockByHash(ctx, hash)
+}
+
 // TraceConfig holds extra parameters to trace functions.
 type TraceConfig struct {
 	*vm.LogConfig
@@ -64,13 +162,30 @@ type TraceConfig struct {
 	Reexec  *uint64
 }
 
+// TraceCallConfig is the config for traceCall API. It holds one more
+// field to override the state for tracing.
+type TraceCallConfig struct {
+	*vm.LogConfig
+	Tracer         *string
+	Timeout        *string
+	Reexec         *uint64
+	StateOverrides *ethapi.StateOverride
+}
+
 // StdTraceConfig holds extra parameters to standard-json trace functions.
 type StdTraceConfig struct {
-	*vm.LogConfig
+	vm.LogConfig
 	Reexec *uint64
 	TxHash common.Hash
 }
 
+// txTraceContext is the contextual infos about a transaction before it gets run.
+type txTraceContext struct {
+	index int         // Index of the transaction within the block
+	hash  common.Hash // Hash of the transaction
+	block common.Hash // Hash of the block containing the transaction
+}
+
 // txTraceResult is the result of a single transaction trace.
 type txTraceResult struct {
 	Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
@@ -103,32 +218,14 @@ type txTraceTask struct {
 
 // TraceChain returns the structured logs created during the execution of EVM
 // between two blocks (excluding start) and returns them as a JSON object.
-func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
-	// Fetch the block interval that we want to trace
-	var from, to *types.Block
-
-	switch start {
-	case rpc.PendingBlockNumber:
-		from = api.eth.miner.PendingBlock()
-	case rpc.LatestBlockNumber:
-		from = api.eth.blockchain.CurrentBlock()
-	default:
-		from = api.eth.blockchain.GetBlockByNumber(uint64(start))
-	}
-	switch end {
-	case rpc.PendingBlockNumber:
-		to = api.eth.miner.PendingBlock()
-	case rpc.LatestBlockNumber:
-		to = api.eth.blockchain.CurrentBlock()
-	default:
-		to = api.eth.blockchain.GetBlockByNumber(uint64(end))
-	}
-	// Trace the chain if we've found all our blocks
-	if from == nil {
-		return nil, fmt.Errorf("starting block #%d not found", start)
+func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace
+	from, err := api.blockByNumber(ctx, start)
+	if err != nil {
+		return nil, err
 	}
-	if to == nil {
-		return nil, fmt.Errorf("end block #%d not found", end)
+	to, err := api.blockByNumber(ctx, end)
+	if err != nil {
+		return nil, err
 	}
 	if from.Number().Cmp(to.Number()) >= 0 {
 		return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start)
@@ -139,7 +236,7 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
 // traceChain configures a new tracer according to the provided configuration, and
 // executes all the transactions contained within. The return value will be one item
 // per transaction, dependent on the requested tracer.
-func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) {
+func (api *API) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) {
 	// Tracing a chain is a **long** operation, only do with subscriptions
 	notifier, supported := rpc.NotifierFromContext(ctx)
 	if !supported {
@@ -147,54 +244,22 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 	}
 	sub := notifier.CreateSubscription()
 
-	// Ensure we have a valid starting state before doing any work
-	origin := start.NumberU64()
-	database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16) // Chain tracing will probably start at genesis
-
-	if number := start.NumberU64(); number > 0 {
-		start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
-		if start == nil {
-			return nil, fmt.Errorf("parent block #%d not found", number-1)
-		}
-	}
-	statedb, err := state.New(start.Root(), database, nil)
-	if err != nil {
-		// If the starting state is missing, allow some number of blocks to be reexecuted
-		reexec := defaultTraceReexec
-		if config != nil && config.Reexec != nil {
-			reexec = *config.Reexec
-		}
-		// Find the most recent block that has the state available
-		for i := uint64(0); i < reexec; i++ {
-			start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
-			if start == nil {
-				break
-			}
-			if statedb, err = state.New(start.Root(), database, nil); err == nil {
-				break
-			}
-		}
-		// If we still don't have the state available, bail out
-		if err != nil {
-			switch err.(type) {
-			case *trie.MissingNodeError:
-				return nil, errors.New("required historical state unavailable")
-			default:
-				return nil, err
-			}
-		}
+	// Prepare all the states for tracing. Note this procedure can take very
+	// long time. Timeout mechanism is necessary.
+	reexec := defaultTraceReexec
+	if config != nil && config.Reexec != nil {
+		reexec = *config.Reexec
 	}
-	// Execute all the transaction contained within the chain concurrently for each block
-	blocks := int(end.NumberU64() - origin)
-
+	blocks := int(end.NumberU64() - start.NumberU64())
 	threads := runtime.NumCPU()
 	if threads > blocks {
 		threads = blocks
 	}
 	var (
-		pend    = new(sync.WaitGroup)
-		tasks   = make(chan *blockTraceTask, threads)
-		results = make(chan *blockTraceTask, threads)
+		pend     = new(sync.WaitGroup)
+		tasks    = make(chan *blockTraceTask, threads)
+		results  = make(chan *blockTraceTask, threads)
+		localctx = context.Background()
 	)
 	for th := 0; th < threads; th++ {
 		pend.Add(1)
@@ -203,21 +268,24 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 
 			// Fetch and execute the next block trace tasks
 			for task := range tasks {
-				signer := types.MakeSigner(api.eth.blockchain.Config(), task.block.Number())
-
+				signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number())
+				blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil)
 				// Trace all the transactions contained within
 				for i, tx := range task.block.Transactions() {
 					msg, _ := tx.AsMessage(signer)
-					vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil)
-
-					res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
+					txctx := &txTraceContext{
+						index: i,
+						hash:  tx.Hash(),
+						block: task.block.Hash(),
+					}
+					res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config)
 					if err != nil {
 						task.results[i] = &txTraceResult{Error: err.Error()}
 						log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
 						break
 					}
 					// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
-					task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number()))
+					task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number()))
 					task.results[i] = &txTraceResult{Result: res}
 				}
 				// Stream the result back to the user or abort on teardown
@@ -234,11 +302,12 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 
 	go func() {
 		var (
-			logged time.Time
-			number uint64
-			traced uint64
-			failed error
-			proot  common.Hash
+			logged  time.Time
+			number  uint64
+			traced  uint64
+			failed  error
+			parent  common.Hash
+			statedb *state.StateDB
 		)
 		// Ensure everything is properly cleaned up on any exit path
 		defer func() {
@@ -256,7 +325,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 			close(results)
 		}()
 		// Feed all the blocks both into the tracer, as well as fast process concurrently
-		for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ {
+		for number = start.NumberU64(); number < end.NumberU64(); number++ {
 			// Stop tracing if interruption was requested
 			select {
 			case <-notifier.Closed():
@@ -265,59 +334,46 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 			}
 			// Print progress logs if long enough time elapsed
 			if time.Since(logged) > 8*time.Second {
-				if number > origin {
-					nodes, imgs := database.TrieDB().Size()
-					log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", nodes+imgs)
-				} else {
-					log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin))
-				}
 				logged = time.Now()
+				log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin))
 			}
-			// Retrieve the next block to trace
-			block := api.eth.blockchain.GetBlockByNumber(number)
-			if block == nil {
-				failed = fmt.Errorf("block #%d not found", number)
-				break
-			}
-			// Send the block over to the concurrent tracers (if not in the fast-forward phase)
-			if number > origin {
-				txs := block.Transactions()
-
-				select {
-				case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))}:
-				case <-notifier.Closed():
-					return
-				}
-				traced += uint64(len(txs))
-			}
-			// Generate the next state snapshot fast without tracing
-			_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
+			// Retrieve the parent state to trace on top
+			block, err := api.blockByNumber(localctx, rpc.BlockNumber(number))
 			if err != nil {
 				failed = err
 				break
 			}
-			// Finalize the state so any modifications are written to the trie
-			root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
+			// Prepare the statedb for tracing. Don't use the live database for
+			// tracing to avoid persisting state junks into the database.
+			statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false)
 			if err != nil {
 				failed = err
 				break
 			}
-			if err := statedb.Reset(root); err != nil {
+			if statedb.Database().TrieDB() != nil {
+				// Hold the reference for tracer, will be released at the final stage
+				statedb.Database().TrieDB().Reference(block.Root(), common.Hash{})
+
+				// Release the parent state because it's already held by the tracer
+				if parent != (common.Hash{}) {
+					statedb.Database().TrieDB().Dereference(parent)
+				}
+			}
+			parent = block.Root()
+
+			next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1))
+			if err != nil {
 				failed = err
 				break
 			}
-			// Reference the trie twice, once for us, once for the tracer
-			database.TrieDB().Reference(root, common.Hash{})
-			if number >= origin {
-				database.TrieDB().Reference(root, common.Hash{})
-			}
-			// Dereference all past tries we ourselves are done working with
-			if proot != (common.Hash{}) {
-				database.TrieDB().Dereference(proot)
+			// Send the block over to the concurrent tracers (if not in the fast-forward phase)
+			txs := next.Transactions()
+			select {
+			case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))}:
+			case <-notifier.Closed():
+				return
 			}
-			proot = root
-
-			// TODO(karalabe): Do we need the preimages? Won't they accumulate too much?
+			traced += uint64(len(txs))
 		}
 	}()
 
@@ -325,7 +381,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 	go func() {
 		var (
 			done = make(map[uint64]*blockTraceResult)
-			next = origin + 1
+			next = start.NumberU64() + 1
 		)
 		for res := range results {
 			// Queue up next received result
@@ -336,9 +392,10 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 			}
 			done[uint64(result.Block)] = result
 
-			// Dereference any paret tries held in memory by this task
-			database.TrieDB().Dereference(res.rootref)
-
+			// Dereference any parent tries held in memory by this task
+			if res.statedb.Database().TrieDB() != nil {
+				res.statedb.Database().TrieDB().Dereference(res.rootref)
+			}
 			// Stream completed traces to the user, aborting on the first error
 			for result, ok := done[next]; ok; result, ok = done[next] {
 				if len(result.Traces) > 0 || next == end.NumberU64() {
@@ -354,38 +411,27 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
 
 // TraceBlockByNumber returns the structured logs created during the execution of
 // EVM and returns them as a JSON object.
-func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
-	// Fetch the block that we want to trace
-	var block *types.Block
-
-	switch number {
-	case rpc.PendingBlockNumber:
-		block = api.eth.miner.PendingBlock()
-	case rpc.LatestBlockNumber:
-		block = api.eth.blockchain.CurrentBlock()
-	default:
-		block = api.eth.blockchain.GetBlockByNumber(uint64(number))
-	}
-	// Trace the block if it was found
-	if block == nil {
-		return nil, fmt.Errorf("block #%d not found", number)
+func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
+	block, err := api.blockByNumber(ctx, number)
+	if err != nil {
+		return nil, err
 	}
 	return api.traceBlock(ctx, block, config)
 }
 
 // TraceBlockByHash returns the structured logs created during the execution of
 // EVM and returns them as a JSON object.
-func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
-	block := api.eth.blockchain.GetBlockByHash(hash)
-	if block == nil {
-		return nil, fmt.Errorf("block %#x not found", hash)
+func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
+	block, err := api.blockByHash(ctx, hash)
+	if err != nil {
+		return nil, err
 	}
 	return api.traceBlock(ctx, block, config)
 }
 
 // TraceBlock returns the structured logs created during the execution of EVM
 // and returns them as a JSON object.
-func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) {
+func (api *API) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) {
 	block := new(types.Block)
 	if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
 		return nil, fmt.Errorf("could not decode block: %v", err)
@@ -395,7 +441,7 @@ func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config
 
 // TraceBlockFromFile returns the structured logs created during the execution of
 // EVM and returns them as a JSON object.
-func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) {
+func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) {
 	blob, err := ioutil.ReadFile(file)
 	if err != nil {
 		return nil, fmt.Errorf("could not read file: %v", err)
@@ -403,12 +449,11 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
 	return api.TraceBlock(ctx, blob, config)
 }
 
-// TraceBadBlockByHash returns the structured logs created during the execution of
+// TraceBadBlock returns the structured logs created during the execution of
 // EVM against a block pulled from the pool of bad ones and returns them as a JSON
 // object.
-func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
-	blocks := api.eth.blockchain.BadBlocks()
-	for _, block := range blocks {
+func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
+	for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) {
 		if block.Hash() == hash {
 			return api.traceBlock(ctx, block, config)
 		}
@@ -419,10 +464,10 @@ func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash,
 // StandardTraceBlockToFile dumps the structured logs created during the
 // execution of EVM to the local file system and returns a list of files
 // to the caller.
-func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
-	block := api.eth.blockchain.GetBlockByHash(hash)
-	if block == nil {
-		return nil, fmt.Errorf("block %#x not found", hash)
+func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
+	block, err := api.blockByHash(ctx, hash)
+	if err != nil {
+		return nil, err
 	}
 	return api.standardTraceBlockToFile(ctx, block, config)
 }
@@ -430,9 +475,8 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c
 // StandardTraceBadBlockToFile dumps the structured logs created during the
 // execution of EVM against a block pulled from the pool of bad ones to the
 // local file system and returns a list of files to the caller.
-func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
-	blocks := api.eth.blockchain.BadBlocks()
-	for _, block := range blocks {
+func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
+	for _, block := range rawdb.ReadAllBadBlocks(api.backend.ChainDb()) {
 		if block.Hash() == hash {
 			return api.standardTraceBlockToFile(ctx, block, config)
 		}
@@ -443,27 +487,25 @@ func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, has
 // traceBlock configures a new tracer according to the provided configuration, and
 // executes all the transactions contained within. The return value will be one item
 // per transaction, dependent on the requestd tracer.
-func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
-	// Create the parent state database
-	if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
-		return nil, err
+func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
+	if block.NumberU64() == 0 {
+		return nil, errors.New("genesis is not traceable")
 	}
-	parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
-	if parent == nil {
-		return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
+	parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
+	if err != nil {
+		return nil, err
 	}
 	reexec := defaultTraceReexec
 	if config != nil && config.Reexec != nil {
 		reexec = *config.Reexec
 	}
-	statedb, err := api.computeStateDB(parent, reexec)
+	statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
 	if err != nil {
 		return nil, err
 	}
 	// Execute all the transaction contained within the block concurrently
 	var (
-		signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
-
+		signer  = types.MakeSigner(api.backend.ChainConfig(), block.Number())
 		txs     = block.Transactions()
 		results = make([]*txTraceResult, len(txs))
 
@@ -474,17 +516,21 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
 	if threads > len(txs) {
 		threads = len(txs)
 	}
+	blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
+	blockHash := block.Hash()
 	for th := 0; th < threads; th++ {
 		pend.Add(1)
 		go func() {
 			defer pend.Done()
-
 			// Fetch and execute the next transaction trace tasks
 			for task := range jobs {
 				msg, _ := txs[task.index].AsMessage(signer)
-				vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
-
-				res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
+				txctx := &txTraceContext{
+					index: task.index,
+					hash:  txs[task.index].Hash(),
+					block: blockHash,
+				}
+				res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config)
 				if err != nil {
 					results[task.index] = &txTraceResult{Error: err.Error()}
 					continue
@@ -501,8 +547,8 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
 
 		// Generate the next state snapshot fast without tracing
 		msg, _ := tx.AsMessage(signer)
-		vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
-		if posa, ok := api.eth.engine.(consensus.PoSA); ok {
+
+		if posa, ok := api.backend.Engine().(consensus.PoSA); ok {
 			if isSystem, _ := posa.IsSystemTransaction(tx, block.Header()); isSystem {
 				balance := statedb.GetBalance(consensus.SystemAddress)
 				if balance.Cmp(common.Big0) > 0 {
@@ -511,8 +557,10 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
 				}
 			}
 		}
-		vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{})
-		if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
+
+		statedb.Prepare(tx.Hash(), block.Hash(), i)
+		vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
+		if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
 			failed = err
 			break
 		}
@@ -533,26 +581,25 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
 // standardTraceBlockToFile configures a new tracer which uses standard JSON output,
 // and traces either a full block or an individual transaction. The return value will
 // be one filename per transaction traced.
-func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
+func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
 	// If we're tracing a single transaction, make sure it's present
 	if config != nil && config.TxHash != (common.Hash{}) {
 		if !containsTx(block, config.TxHash) {
 			return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash)
 		}
 	}
-	// Create the parent state database
-	if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
-		return nil, err
+	if block.NumberU64() == 0 {
+		return nil, errors.New("genesis is not traceable")
 	}
-	parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
-	if parent == nil {
-		return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
+	parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
+	if err != nil {
+		return nil, err
 	}
 	reexec := defaultTraceReexec
 	if config != nil && config.Reexec != nil {
 		reexec = *config.Reexec
 	}
-	statedb, err := api.computeStateDB(parent, reexec)
+	statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true)
 	if err != nil {
 		return nil, err
 	}
@@ -562,34 +609,53 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
 		txHash    common.Hash
 	)
 	if config != nil {
-		if config.LogConfig != nil {
-			logConfig = *config.LogConfig
-		}
+		logConfig = config.LogConfig
 		txHash = config.TxHash
 	}
 	logConfig.Debug = true
 
 	// Execute transaction, either tracing all or just the requested one
 	var (
-		signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
-		dumps  []string
+		dumps       []string
+		signer      = types.MakeSigner(api.backend.ChainConfig(), block.Number())
+		chainConfig = api.backend.ChainConfig()
+		vmctx       = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
+		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 berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil {
+			chainConfig.BerlinBlock = berlin
+			canon = false
+		}
+	}
 	for i, tx := range block.Transactions() {
 		// Prepare the trasaction for un-traced execution
 		var (
-			msg, _ = tx.AsMessage(signer)
-			vmctx  = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
-
-			vmConf vm.Config
-			dump   *os.File
-			writer *bufio.Writer
-			err    error
+			msg, _    = tx.AsMessage(signer)
+			txContext = core.NewEVMTxContext(msg)
+			vmConf    vm.Config
+			dump      *os.File
+			writer    *bufio.Writer
+			err       error
 		)
 		// If the transaction needs tracing, swap out the configs
 		if tx.Hash() == txHash || txHash == (common.Hash{}) {
 			// 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])
-
+			if !canon {
+				prefix = fmt.Sprintf("%valt-", prefix)
+			}
 			dump, err = ioutil.TempFile(os.TempDir(), prefix)
 			if err != nil {
 				return nil, err
@@ -605,8 +671,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
 			}
 		}
 		// Execute the transaction and flush any traces to disk
-		vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf)
-		_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
+		vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
+		statedb.Prepare(tx.Hash(), block.Hash(), i)
+		_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
 		if writer != nil {
 			writer.Flush()
 		}
@@ -640,103 +707,97 @@ func containsTx(block *types.Block, hash common.Hash) bool {
 	return false
 }
 
-// computeStateDB retrieves the state database associated with a certain block.
-// If no state is locally available for the given block, a number of blocks are
-// attempted to be reexecuted to generate the desired state.
-func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) {
-	// If we have the state fully available, use that
-	statedb, err := api.eth.blockchain.StateAt(block.Root())
-	if err == nil {
-		return statedb, nil
+// TraceTransaction returns the structured logs created during the execution of EVM
+// and returns them as a JSON object.
+func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
+	_, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash)
+	if err != nil {
+		return nil, err
 	}
-	// Otherwise try to reexec blocks until we find a state or reach our limit
-	origin := block.NumberU64()
-	database := state.NewDatabaseWithCache(api.eth.ChainDb(), 16)
-
-	for i := uint64(0); i < reexec; i++ {
-		block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
-		if block == nil {
-			break
-		}
-		if statedb, err = state.New(block.Root(), database, nil); err == nil {
-			break
-		}
+	// It shouldn't happen in practice.
+	if blockNumber == 0 {
+		return nil, errors.New("genesis is not traceable")
+	}
+	reexec := defaultTraceReexec
+	if config != nil && config.Reexec != nil {
+		reexec = *config.Reexec
 	}
+	block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash)
 	if err != nil {
-		switch err.(type) {
-		case *trie.MissingNodeError:
-			return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
-		default:
-			return nil, err
-		}
+		return nil, err
 	}
-	// State was available at historical point, regenerate
-	var (
-		start  = time.Now()
-		logged time.Time
-		proot  common.Hash
-	)
-	for block.NumberU64() < origin {
-		// Print progress logs if long enough time elapsed
-		if time.Since(logged) > 8*time.Second {
-			log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start))
-			logged = time.Now()
-		}
-		// Retrieve the next block to regenerate and process it
-		if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil {
-			return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1)
-		}
-		_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
-		if err != nil {
-			return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
-		}
-		// Finalize the state so any modifications are written to the trie
-		root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
-		if err != nil {
-			return nil, err
-		}
-		if err := statedb.Reset(root); err != nil {
-			return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err)
-		}
-		database.TrieDB().Reference(root, common.Hash{})
-		if proot != (common.Hash{}) {
-			database.TrieDB().Dereference(proot)
-		}
-		proot = root
+	msg, vmctx, statedb, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec)
+	if err != nil {
+		return nil, err
 	}
-	nodes, imgs := database.TrieDB().Size()
-	log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs)
-	return statedb, nil
+	txctx := &txTraceContext{
+		index: int(index),
+		hash:  hash,
+		block: blockHash,
+	}
+	return api.traceTx(ctx, msg, txctx, vmctx, statedb, config)
 }
 
-// TraceTransaction returns the structured logs created during the execution of EVM
-// and returns them as a JSON object.
-func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
-	// Retrieve the transaction and assemble its EVM context
-	tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash)
-	if tx == nil {
-		return nil, fmt.Errorf("transaction %#x not found", hash)
+// TraceCall lets you trace a given eth_call. It collects the structured logs
+// created during the execution of EVM if the given transaction was added on
+// top of the provided block and returns them as a JSON object.
+// You can provide -2 as a block number to trace on top of the pending block.
+func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
+	// Try to retrieve the specified block
+	var (
+		err   error
+		block *types.Block
+	)
+	if hash, ok := blockNrOrHash.Hash(); ok {
+		block, err = api.blockByHash(ctx, hash)
+	} else if number, ok := blockNrOrHash.Number(); ok {
+		block, err = api.blockByNumber(ctx, number)
+	} else {
+		return nil, errors.New("invalid arguments; neither block nor hash specified")
+	}
+	if err != nil {
+		return nil, err
 	}
+	// try to recompute the state
 	reexec := defaultTraceReexec
 	if config != nil && config.Reexec != nil {
 		reexec = *config.Reexec
 	}
-	msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec)
+	statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true)
 	if err != nil {
 		return nil, err
 	}
-	// Trace the transaction and return
-	return api.traceTx(ctx, msg, vmctx, statedb, config)
+	// Apply the customized state rules if required.
+	if config != nil {
+		if err := config.StateOverrides.Apply(statedb); err != nil {
+			return nil, err
+		}
+	}
+	// Execute the trace
+	msg := args.ToMessage(api.backend.RPCGasCap())
+	vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
+
+	var traceConfig *TraceConfig
+	if config != nil {
+		traceConfig = &TraceConfig{
+			LogConfig: config.LogConfig,
+			Tracer:    config.Tracer,
+			Timeout:   config.Timeout,
+			Reexec:    config.Reexec,
+		}
+	}
+	return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig)
 }
 
 // traceTx configures a new tracer according to the provided configuration, and
 // executes the given message in the provided environment. The return value will
 // be tracer dependent.
-func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
+func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTraceContext, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
 	// Assemble the structured logger or the JavaScript tracer
 	var (
-		tracer vm.Tracer
-		err    error
+		tracer    vm.Tracer
+		err       error
+		txContext = core.NewEVMTxContext(message)
 	)
 	switch {
 	case config != nil && config.Tracer != nil:
@@ -748,14 +809,16 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
 			}
 		}
 		// Constuct the JavaScript tracer to execute with
-		if tracer, err = tracers.New(*config.Tracer); err != nil {
+		if tracer, err = New(*config.Tracer, txContext); err != nil {
 			return nil, err
 		}
 		// Handle timeouts and RPC cancellations
 		deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
 		go func() {
 			<-deadlineCtx.Done()
-			tracer.(*tracers.Tracer).Stop(errors.New("execution timeout"))
+			if deadlineCtx.Err() == context.DeadlineExceeded {
+				tracer.(*Tracer).Stop(errors.New("execution timeout"))
+			}
 		}()
 		defer cancel()
 
@@ -766,8 +829,9 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
 		tracer = vm.NewStructLogger(config.LogConfig)
 	}
 	// Run the transaction with tracing enabled.
-	vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer})
-	if posa, ok := api.eth.engine.(consensus.PoSA); ok && message.From() == vmctx.Coinbase &&
+	vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer})
+
+	if posa, ok := api.backend.Engine().(consensus.PoSA); ok && message.From() == vmctx.Coinbase &&
 		posa.IsSystemContract(message.To()) && message.GasPrice().Cmp(big.NewInt(0)) == 0 {
 		balance := statedb.GetBalance(consensus.SystemAddress)
 		if balance.Cmp(common.Big0) > 0 {
@@ -775,21 +839,31 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
 			statedb.AddBalance(vmctx.Coinbase, balance)
 		}
 	}
-	ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
+
+	// Call Prepare to clear out the statedb access list
+	statedb.Prepare(txctx.hash, txctx.block, txctx.index)
+
+	result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
 	if err != nil {
-		return nil, fmt.Errorf("tracing failed: %v", err)
+		return nil, fmt.Errorf("tracing failed: %w", err)
 	}
-	// Depending on the tracer type, format and return the output
+
+	// Depending on the tracer type, format and return the output.
 	switch tracer := tracer.(type) {
 	case *vm.StructLogger:
+		// If the result contains a revert reason, return it.
+		returnVal := fmt.Sprintf("%x", result.Return())
+		if len(result.Revert()) > 0 {
+			returnVal = fmt.Sprintf("%x", result.Revert())
+		}
 		return ðapi.ExecutionResult{
-			Gas:         gas,
-			Failed:      failed,
-			ReturnValue: fmt.Sprintf("%x", ret),
+			Gas:         result.UsedGas,
+			Failed:      result.Failed(),
+			ReturnValue: returnVal,
 			StructLogs:  ethapi.FormatLogs(tracer.StructLogs()),
 		}, nil
 
-	case *tracers.Tracer:
+	case *Tracer:
 		return tracer.GetResult()
 
 	default:
@@ -797,53 +871,15 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
 	}
 }
 
-// computeTxEnv returns the execution environment of a certain transaction.
-func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) {
-	// Create the parent state database
-	block := api.eth.blockchain.GetBlockByHash(blockHash)
-	if block == nil {
-		return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash)
-	}
-	parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
-	if parent == nil {
-		return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
-	}
-	statedb, err := api.computeStateDB(parent, reexec)
-	if err != nil {
-		return nil, vm.Context{}, nil, err
-	}
-
-	if txIndex == 0 && len(block.Transactions()) == 0 {
-		return nil, vm.Context{}, statedb, nil
-	}
-
-	// Recompute transactions up to the target index.
-	signer := types.MakeSigner(api.eth.blockchain.Config(), block.Number())
-
-	for idx, tx := range block.Transactions() {
-		// Assemble the transaction call message and return if the requested offset
-		msg, _ := tx.AsMessage(signer)
-		if posa, ok := api.eth.engine.(consensus.PoSA); ok {
-			if isSystem, _ := posa.IsSystemTransaction(tx, block.Header()); isSystem {
-				balance := statedb.GetBalance(consensus.SystemAddress)
-				if balance.Cmp(common.Big0) > 0 {
-					statedb.SetBalance(consensus.SystemAddress, big.NewInt(0))
-					statedb.AddBalance(block.Header().Coinbase, balance)
-				}
-			}
-		}
-		context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
-		if idx == txIndex {
-			return msg, context, statedb, nil
-		}
-		// Not yet the searched for transaction, execute on top of the current state
-		vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{})
-		if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
-			return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
-		}
-		// Ensure any modifications are committed to the state
-		// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
-		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+// APIs return the collection of RPC services the tracer package offers.
+func APIs(backend Backend) []rpc.API {
+	// Append all the local APIs and return
+	return []rpc.API{
+		{
+			Namespace: "debug",
+			Version:   "1.0",
+			Service:   NewAPI(backend),
+			Public:    false,
+		},
 	}
-	return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash)
 }
diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go
new file mode 100644
index 0000000000..4c0240cd2c
--- /dev/null
+++ b/eth/tracers/api_test.go
@@ -0,0 +1,639 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package tracers
+
+import (
+	"bytes"
+	"context"
+	"crypto/ecdsa"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/big"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/internal/ethapi"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rpc"
+)
+
+var (
+	errStateNotFound       = errors.New("state not found")
+	errBlockNotFound       = errors.New("block not found")
+	errTransactionNotFound = errors.New("transaction not found")
+)
+
+type testBackend struct {
+	chainConfig *params.ChainConfig
+	engine      consensus.Engine
+	chaindb     ethdb.Database
+	chain       *core.BlockChain
+}
+
+func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend {
+	backend := &testBackend{
+		chainConfig: params.TestChainConfig,
+		engine:      ethash.NewFaker(),
+		chaindb:     rawdb.NewMemoryDatabase(),
+	}
+	// Generate blocks for testing
+	gspec.Config = backend.chainConfig
+	var (
+		gendb   = rawdb.NewMemoryDatabase()
+		genesis = gspec.MustCommit(gendb)
+	)
+	blocks, _ := core.GenerateChain(backend.chainConfig, genesis, backend.engine, gendb, n, generator)
+
+	// Import the canonical chain
+	gspec.MustCommit(backend.chaindb)
+	cacheConfig := &core.CacheConfig{
+		TrieCleanLimit:    256,
+		TrieDirtyLimit:    256,
+		TrieTimeLimit:     5 * time.Minute,
+		SnapshotLimit:     0,
+		TrieDirtyDisabled: true, // Archive mode
+	}
+	chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, backend.chainConfig, backend.engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("failed to create tester chain: %v", err)
+	}
+	if n, err := chain.InsertChain(blocks); err != nil {
+		t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+	}
+	backend.chain = chain
+	return backend
+}
+
+func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
+	return b.chain.GetHeaderByHash(hash), nil
+}
+
+func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
+	if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber {
+		return b.chain.CurrentHeader(), nil
+	}
+	return b.chain.GetHeaderByNumber(uint64(number)), nil
+}
+
+func (b *testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
+	return b.chain.GetBlockByHash(hash), nil
+}
+
+func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
+	if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber {
+		return b.chain.CurrentBlock(), nil
+	}
+	return b.chain.GetBlockByNumber(uint64(number)), nil
+}
+
+func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
+	tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash)
+	if tx == nil {
+		return nil, common.Hash{}, 0, 0, errTransactionNotFound
+	}
+	return tx, hash, blockNumber, index, nil
+}
+
+func (b *testBackend) RPCGasCap() uint64 {
+	return 25000000
+}
+
+func (b *testBackend) ChainConfig() *params.ChainConfig {
+	return b.chainConfig
+}
+
+func (b *testBackend) Engine() consensus.Engine {
+	return b.engine
+}
+
+func (b *testBackend) ChainDb() ethdb.Database {
+	return b.chaindb
+}
+
+func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
+	statedb, err := b.chain.StateAt(block.Root())
+	if err != nil {
+		return nil, errStateNotFound
+	}
+	return statedb, nil
+}
+
+func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
+	parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+	if parent == nil {
+		return nil, vm.BlockContext{}, nil, errBlockNotFound
+	}
+	statedb, err := b.chain.StateAt(parent.Root())
+	if err != nil {
+		return nil, vm.BlockContext{}, nil, errStateNotFound
+	}
+	if txIndex == 0 && len(block.Transactions()) == 0 {
+		return nil, vm.BlockContext{}, statedb, nil
+	}
+	// Recompute transactions up to the target index.
+	signer := types.MakeSigner(b.chainConfig, block.Number())
+	for idx, tx := range block.Transactions() {
+		msg, _ := tx.AsMessage(signer)
+		txContext := core.NewEVMTxContext(msg)
+		context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
+		if idx == txIndex {
+			return msg, context, statedb, nil
+		}
+		vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{})
+		if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
+			return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
+		}
+		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+	}
+	return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
+}
+
+func TestTraceCall(t *testing.T) {
+	t.Parallel()
+
+	// Initialize test accounts
+	accounts := newAccounts(3)
+	genesis := &core.Genesis{Alloc: core.GenesisAlloc{
+		accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[1].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[2].addr: {Balance: big.NewInt(params.Ether)},
+	}}
+	genBlocks := 10
+	signer := types.HomesteadSigner{}
+	api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
+		// Transfer from account[0] to account[1]
+		//    value: 1000 wei
+		//    fee:   0 wei
+		tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key)
+		b.AddTx(tx)
+	}))
+
+	var testSuite = []struct {
+		blockNumber rpc.BlockNumber
+		call        ethapi.CallArgs
+		config      *TraceCallConfig
+		expectErr   error
+		expect      interface{}
+	}{
+		// Standard JSON trace upon the genesis, plain transfer.
+		{
+			blockNumber: rpc.BlockNumber(0),
+			call: ethapi.CallArgs{
+				From:  &accounts[0].addr,
+				To:    &accounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config:    nil,
+			expectErr: nil,
+			expect: ðapi.ExecutionResult{
+				Gas:         params.TxGas,
+				Failed:      false,
+				ReturnValue: "",
+				StructLogs:  []ethapi.StructLogRes{},
+			},
+		},
+		// Standard JSON trace upon the head, plain transfer.
+		{
+			blockNumber: rpc.BlockNumber(genBlocks),
+			call: ethapi.CallArgs{
+				From:  &accounts[0].addr,
+				To:    &accounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config:    nil,
+			expectErr: nil,
+			expect: ðapi.ExecutionResult{
+				Gas:         params.TxGas,
+				Failed:      false,
+				ReturnValue: "",
+				StructLogs:  []ethapi.StructLogRes{},
+			},
+		},
+		// Standard JSON trace upon the non-existent block, error expects
+		{
+			blockNumber: rpc.BlockNumber(genBlocks + 1),
+			call: ethapi.CallArgs{
+				From:  &accounts[0].addr,
+				To:    &accounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config:    nil,
+			expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
+			expect:    nil,
+		},
+		// Standard JSON trace upon the latest block
+		{
+			blockNumber: rpc.LatestBlockNumber,
+			call: ethapi.CallArgs{
+				From:  &accounts[0].addr,
+				To:    &accounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config:    nil,
+			expectErr: nil,
+			expect: ðapi.ExecutionResult{
+				Gas:         params.TxGas,
+				Failed:      false,
+				ReturnValue: "",
+				StructLogs:  []ethapi.StructLogRes{},
+			},
+		},
+		// Standard JSON trace upon the pending block
+		{
+			blockNumber: rpc.PendingBlockNumber,
+			call: ethapi.CallArgs{
+				From:  &accounts[0].addr,
+				To:    &accounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config:    nil,
+			expectErr: nil,
+			expect: ðapi.ExecutionResult{
+				Gas:         params.TxGas,
+				Failed:      false,
+				ReturnValue: "",
+				StructLogs:  []ethapi.StructLogRes{},
+			},
+		},
+	}
+	for _, testspec := range testSuite {
+		result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
+		if testspec.expectErr != nil {
+			if err == nil {
+				t.Errorf("Expect error %v, get nothing", testspec.expectErr)
+				continue
+			}
+			if !reflect.DeepEqual(err, testspec.expectErr) {
+				t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("Expect no error, get %v", err)
+				continue
+			}
+			if !reflect.DeepEqual(result, testspec.expect) {
+				t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result)
+			}
+		}
+	}
+}
+
+func TestOverridenTraceCall(t *testing.T) {
+	t.Parallel()
+
+	// Initialize test accounts
+	accounts := newAccounts(3)
+	genesis := &core.Genesis{Alloc: core.GenesisAlloc{
+		accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[1].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[2].addr: {Balance: big.NewInt(params.Ether)},
+	}}
+	genBlocks := 10
+	signer := types.HomesteadSigner{}
+	api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
+		// Transfer from account[0] to account[1]
+		//    value: 1000 wei
+		//    fee:   0 wei
+		tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key)
+		b.AddTx(tx)
+	}))
+	randomAccounts, tracer := newAccounts(3), "callTracer"
+
+	var testSuite = []struct {
+		blockNumber rpc.BlockNumber
+		call        ethapi.CallArgs
+		config      *TraceCallConfig
+		expectErr   error
+		expect      *callTrace
+	}{
+		// Succcessful call with state overriding
+		{
+			blockNumber: rpc.PendingBlockNumber,
+			call: ethapi.CallArgs{
+				From:  &randomAccounts[0].addr,
+				To:    &randomAccounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config: &TraceCallConfig{
+				Tracer: &tracer,
+				StateOverrides: ðapi.StateOverride{
+					randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
+				},
+			},
+			expectErr: nil,
+			expect: &callTrace{
+				Type:    "CALL",
+				From:    randomAccounts[0].addr,
+				To:      randomAccounts[1].addr,
+				Gas:     newRPCUint64(24979000),
+				GasUsed: newRPCUint64(0),
+				Value:   (*hexutil.Big)(big.NewInt(1000)),
+			},
+		},
+		// Invalid call without state overriding
+		{
+			blockNumber: rpc.PendingBlockNumber,
+			call: ethapi.CallArgs{
+				From:  &randomAccounts[0].addr,
+				To:    &randomAccounts[1].addr,
+				Value: (*hexutil.Big)(big.NewInt(1000)),
+			},
+			config: &TraceCallConfig{
+				Tracer: &tracer,
+			},
+			expectErr: core.ErrInsufficientFundsForTransfer,
+			expect:    nil,
+		},
+		// Successful simple contract call
+		//
+		// // SPDX-License-Identifier: GPL-3.0
+		//
+		//  pragma solidity >=0.7.0 <0.8.0;
+		//
+		//  /**
+		//   * @title Storage
+		//   * @dev Store & retrieve value in a variable
+		//   */
+		//  contract Storage {
+		//      uint256 public number;
+		//      constructor() {
+		//          number = block.number;
+		//      }
+		//  }
+		{
+			blockNumber: rpc.PendingBlockNumber,
+			call: ethapi.CallArgs{
+				From: &randomAccounts[0].addr,
+				To:   &randomAccounts[2].addr,
+				Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number()
+			},
+			config: &TraceCallConfig{
+				Tracer: &tracer,
+				StateOverrides: ðapi.StateOverride{
+					randomAccounts[2].addr: ethapi.OverrideAccount{
+						Code:      newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")),
+						StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}),
+					},
+				},
+			},
+			expectErr: nil,
+			expect: &callTrace{
+				Type:    "CALL",
+				From:    randomAccounts[0].addr,
+				To:      randomAccounts[2].addr,
+				Input:   hexutil.Bytes(common.Hex2Bytes("8381f58a")),
+				Output:  hexutil.Bytes(common.BigToHash(big.NewInt(123)).Bytes()),
+				Gas:     newRPCUint64(24978936),
+				GasUsed: newRPCUint64(2283),
+				Value:   (*hexutil.Big)(big.NewInt(0)),
+			},
+		},
+	}
+	for _, testspec := range testSuite {
+		result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
+		if testspec.expectErr != nil {
+			if err == nil {
+				t.Errorf("Expect error %v, get nothing", testspec.expectErr)
+				continue
+			}
+			if !errors.Is(err, testspec.expectErr) {
+				t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("Expect no error, get %v", err)
+				continue
+			}
+			ret := new(callTrace)
+			if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
+				t.Fatalf("failed to unmarshal trace result: %v", err)
+			}
+			if !jsonEqual(ret, testspec.expect) {
+				// uncomment this for easier debugging
+				//have, _ := json.MarshalIndent(ret, "", " ")
+				//want, _ := json.MarshalIndent(testspec.expect, "", " ")
+				//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
+				t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, testspec.expect)
+			}
+		}
+	}
+}
+
+func TestTraceTransaction(t *testing.T) {
+	t.Parallel()
+
+	// Initialize test accounts
+	accounts := newAccounts(2)
+	genesis := &core.Genesis{Alloc: core.GenesisAlloc{
+		accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[1].addr: {Balance: big.NewInt(params.Ether)},
+	}}
+	target := common.Hash{}
+	signer := types.HomesteadSigner{}
+	api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
+		// Transfer from account[0] to account[1]
+		//    value: 1000 wei
+		//    fee:   0 wei
+		tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key)
+		b.AddTx(tx)
+		target = tx.Hash()
+	}))
+	result, err := api.TraceTransaction(context.Background(), target, nil)
+	if err != nil {
+		t.Errorf("Failed to trace transaction %v", err)
+	}
+	if !reflect.DeepEqual(result, ðapi.ExecutionResult{
+		Gas:         params.TxGas,
+		Failed:      false,
+		ReturnValue: "",
+		StructLogs:  []ethapi.StructLogRes{},
+	}) {
+		t.Error("Transaction tracing result is different")
+	}
+}
+
+func TestTraceBlock(t *testing.T) {
+	t.Parallel()
+
+	// Initialize test accounts
+	accounts := newAccounts(3)
+	genesis := &core.Genesis{Alloc: core.GenesisAlloc{
+		accounts[0].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[1].addr: {Balance: big.NewInt(params.Ether)},
+		accounts[2].addr: {Balance: big.NewInt(params.Ether)},
+	}}
+	genBlocks := 10
+	signer := types.HomesteadSigner{}
+	api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
+		// Transfer from account[0] to account[1]
+		//    value: 1000 wei
+		//    fee:   0 wei
+		tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key)
+		b.AddTx(tx)
+	}))
+
+	var testSuite = []struct {
+		blockNumber rpc.BlockNumber
+		config      *TraceConfig
+		expect      interface{}
+		expectErr   error
+	}{
+		// Trace genesis block, expect error
+		{
+			blockNumber: rpc.BlockNumber(0),
+			config:      nil,
+			expect:      nil,
+			expectErr:   errors.New("genesis is not traceable"),
+		},
+		// Trace head block
+		{
+			blockNumber: rpc.BlockNumber(genBlocks),
+			config:      nil,
+			expectErr:   nil,
+			expect: []*txTraceResult{
+				{
+					Result: ðapi.ExecutionResult{
+						Gas:         params.TxGas,
+						Failed:      false,
+						ReturnValue: "",
+						StructLogs:  []ethapi.StructLogRes{},
+					},
+				},
+			},
+		},
+		// Trace non-existent block
+		{
+			blockNumber: rpc.BlockNumber(genBlocks + 1),
+			config:      nil,
+			expectErr:   fmt.Errorf("block #%d not found", genBlocks+1),
+			expect:      nil,
+		},
+		// Trace latest block
+		{
+			blockNumber: rpc.LatestBlockNumber,
+			config:      nil,
+			expectErr:   nil,
+			expect: []*txTraceResult{
+				{
+					Result: ðapi.ExecutionResult{
+						Gas:         params.TxGas,
+						Failed:      false,
+						ReturnValue: "",
+						StructLogs:  []ethapi.StructLogRes{},
+					},
+				},
+			},
+		},
+		// Trace pending block
+		{
+			blockNumber: rpc.PendingBlockNumber,
+			config:      nil,
+			expectErr:   nil,
+			expect: []*txTraceResult{
+				{
+					Result: ðapi.ExecutionResult{
+						Gas:         params.TxGas,
+						Failed:      false,
+						ReturnValue: "",
+						StructLogs:  []ethapi.StructLogRes{},
+					},
+				},
+			},
+		},
+	}
+	for _, testspec := range testSuite {
+		result, err := api.TraceBlockByNumber(context.Background(), testspec.blockNumber, testspec.config)
+		if testspec.expectErr != nil {
+			if err == nil {
+				t.Errorf("Expect error %v, get nothing", testspec.expectErr)
+				continue
+			}
+			if !reflect.DeepEqual(err, testspec.expectErr) {
+				t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("Expect no error, get %v", err)
+				continue
+			}
+			if !reflect.DeepEqual(result, testspec.expect) {
+				t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result)
+			}
+		}
+	}
+}
+
+type Account struct {
+	key  *ecdsa.PrivateKey
+	addr common.Address
+}
+
+type Accounts []Account
+
+func (a Accounts) Len() int           { return len(a) }
+func (a Accounts) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 }
+
+func newAccounts(n int) (accounts Accounts) {
+	for i := 0; i < n; i++ {
+		key, _ := crypto.GenerateKey()
+		addr := crypto.PubkeyToAddress(key.PublicKey)
+		accounts = append(accounts, Account{key: key, addr: addr})
+	}
+	sort.Sort(accounts)
+	return accounts
+}
+
+func newRPCBalance(balance *big.Int) **hexutil.Big {
+	rpcBalance := (*hexutil.Big)(balance)
+	return &rpcBalance
+}
+
+func newRPCUint64(number uint64) *hexutil.Uint64 {
+	rpcUint64 := hexutil.Uint64(number)
+	return &rpcUint64
+}
+
+func newRPCBytes(bytes []byte) *hexutil.Bytes {
+	rpcBytes := hexutil.Bytes(bytes)
+	return &rpcBytes
+}
+
+func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.Hash {
+	if len(keys) != len(vals) {
+		panic("invalid input")
+	}
+	m := make(map[common.Hash]common.Hash)
+	for i := 0; i < len(keys); i++ {
+		m[keys[i]] = vals[i]
+	}
+	return &m
+}
diff --git a/eth/tracers/internal/tracers/assets.go b/eth/tracers/internal/tracers/assets.go
index c2da1ed1f8..7f45ab286e 100644
--- a/eth/tracers/internal/tracers/assets.go
+++ b/eth/tracers/internal/tracers/assets.go
@@ -2,13 +2,13 @@
 // sources:
 // 4byte_tracer.js (2.933kB)
 // bigram_tracer.js (1.712kB)
-// call_tracer.js (8.704kB)
+// call_tracer.js (8.956kB)
 // evmdis_tracer.js (4.195kB)
 // noop_tracer.js (1.271kB)
 // opcount_tracer.js (1.372kB)
-// prestate_tracer.js (4.234kB)
+// prestate_tracer.js (4.287kB)
 // trigram_tracer.js (1.788kB)
-// unigram_tracer.js (1.51kB)
+// unigram_tracer.js (1.469kB)
 
 package tracers
 
@@ -28,7 +28,7 @@ import (
 func bindataRead(data []byte, name string) ([]byte, error) {
 	gz, err := gzip.NewReader(bytes.NewBuffer(data))
 	if err != nil {
-		return nil, fmt.Errorf("read %q: %v", name, err)
+		return nil, fmt.Errorf("read %q: %w", name, err)
 	}
 
 	var buf bytes.Buffer
@@ -36,7 +36,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
 	clErr := gz.Close()
 
 	if err != nil {
-		return nil, fmt.Errorf("read %q: %v", name, err)
+		return nil, fmt.Errorf("read %q: %w", name, err)
 	}
 	if clErr != nil {
 		return nil, err
@@ -117,7 +117,7 @@ func bigram_tracerJs() (*asset, error) {
 	return a, nil
 }
 
-var _call_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x59\xdf\x73\xdb\x36\xf2\x7f\x96\xfe\x8a\x4d\x1e\x6a\x69\xa2\x48\x4a\xd2\x6f\xbf\x33\x76\xd5\x1b\x9d\xa3\xa4\x9e\x71\xe3\x8c\xad\x34\x93\xc9\xe4\x01\x22\x97\x12\x6a\x08\x60\x01\xd0\x32\x2f\xf5\xff\x7e\xb3\x0b\x90\x22\x25\xd9\xf1\xf5\x6e\x6e\x7a\x6f\x24\xb1\xbb\x58\xec\x7e\xf6\x17\x38\x1a\xc1\xa9\xc9\x4b\x2b\x97\x2b\x0f\x2f\xc7\x2f\xfe\x1f\xe6\x2b\x84\xa5\x79\x8e\x7e\x85\x16\x8b\x35\x4c\x0b\xbf\x32\xd6\x75\x47\x23\x98\xaf\xa4\x83\x4c\x2a\x04\xe9\x20\x17\xd6\x83\xc9\xc0\xef\xd0\x2b\xb9\xb0\xc2\x96\xc3\xee\x68\x14\x78\x0e\x2e\x93\x84\xcc\x22\x82\x33\x99\xdf\x08\x8b\xc7\x50\x9a\x02\x12\xa1\xc1\x62\x2a\x9d\xb7\x72\x51\x78\x04\xe9\x41\xe8\x74\x64\x2c\xac\x4d\x2a\xb3\x92\x44\x4a\x0f\x85\x4e\xd1\xf2\xd6\x1e\xed\xda\x55\x7a\xbc\x7d\xf7\x01\xce\xd1\x39\xb4\xf0\x16\x35\x5a\xa1\xe0\x7d\xb1\x50\x32\x81\x73\x99\xa0\x76\x08\xc2\x41\x4e\x5f\xdc\x0a\x53\x58\xb0\x38\x62\x7c\x43\xaa\x5c\x45\x55\xe0\x8d\x29\x74\x2a\xbc\x34\x7a\x00\x28\x49\x73\xb8\x41\xeb\xa4\xd1\xf0\xaa\xda\x2a\x0a\x1c\x80\xb1\x24\xa4\x27\x3c\x1d\xc0\x82\xc9\x89\xaf\x0f\x42\x97\xa0\x84\xdf\xb2\x3e\xc2\x20\xdb\x73\xa7\x20\x35\x6f\xb3\x32\x39\x82\x5f\x09\x4f\xa7\xde\x48\xa5\x60\x81\x50\x38\xcc\x0a\x35\x20\x69\x8b\xc2\xc3\xc7\xb3\xf9\xcf\x17\x1f\xe6\x30\x7d\xf7\x09\x3e\x4e\x2f\x2f\xa7\xef\xe6\x9f\x4e\x60\x23\xfd\xca\x14\x1e\xf0\x06\x83\x28\xb9\xce\x95\xc4\x14\x36\xc2\x5a\xa1\x7d\x09\x26\x23\x09\xbf\xcc\x2e\x4f\x7f\x9e\xbe\x9b\x4f\xff\x7e\x76\x7e\x36\xff\x04\xc6\xc2\x9b\xb3\xf9\xbb\xd9\xd5\x15\xbc\xb9\xb8\x84\x29\xbc\x9f\x5e\xce\xcf\x4e\x3f\x9c\x4f\x2f\xe1\xfd\x87\xcb\xf7\x17\x57\xb3\x21\x5c\x21\x69\x85\xc4\xff\x6d\x9b\x67\xec\x3d\x8b\x90\xa2\x17\x52\xb9\xca\x12\x9f\x4c\x01\x6e\x65\x0a\x95\xc2\x4a\xdc\x20\x58\x4c\x50\xde\x60\x0a\x02\x12\x93\x97\x8f\x76\x2a\xc9\x12\xca\xe8\x25\x9f\xf9\x5e\x40\xc2\x59\x06\xda\xf8\x01\x38\x44\xf8\x71\xe5\x7d\x7e\x3c\x1a\x6d\x36\x9b\xe1\x52\x17\x43\x63\x97\x23\x15\xc4\xb9\xd1\x4f\xc3\x2e\xc9\x4c\x84\x52\x73\x2b\x12\xb4\xe4\x1c\x01\x59\x41\xe6\x57\x66\xa3\xc1\x5b\xa1\x9d\x48\xc8\xd5\xf4\x9c\x30\x18\x85\x07\xbc\xa5\x37\xef\x08\xb4\x60\x31\x37\x96\x9e\x95\xaa\x70\x26\xb5\x47\xab\x85\x62\xd9\x0e\xd6\x22\x45\x58\x94\x20\x9a\x02\x07\xcd\xc3\x10\x8c\x82\xbb\x41\xea\xcc\xd8\x35\xc3\x72\xd8\xfd\xda\xed\x44\x0d\x9d\x17\xc9\x35\x29\x48\xf2\x93\xc2\x5a\xd4\x9e\x4c\x59\x58\x27\x6f\x90\x49\x20\xd0\x44\x7b\xce\x7e\xfd\x05\xf0\x16\x93\x22\x48\xea\xd4\x42\x8e\xe1\xf3\xd7\xbb\x2f\x83\x2e\x8b\x4e\xd1\x25\xa8\x53\x4c\xf9\x7c\xd7\x0e\x36\x2b\xb6\x28\x6c\xf0\xe8\x06\xe1\xb7\xc2\xf9\x06\x4d\x66\xcd\x1a\x84\x06\x53\x10\xe2\x9b\xd6\x91\xda\x1b\x16\x28\xe8\x59\xa3\x65\x8d\x86\xdd\x4e\xcd\x7c\x0c\x99\x50\x0e\xe3\xbe\xce\x63\x4e\xa7\x91\xfa\xc6\x5c\x93\x64\x63\x09\xc2\xb6\x04\x93\x27\x26\x8d\xc1\x40\xe7\xa8\x8f\x81\x6e\xd8\xed\x10\xdf\x31\x64\x85\xe6\x6d\x7b\xca\x2c\x07\x90\x2e\xfa\xf0\xb5\xdb\x21\xb1\xa7\x22\xf7\x85\x45\xb6\x27\x5a\x6b\xac\x03\xb9\x5e\x63\x2a\x85\x47\x55\x76\x3b\x9d\x1b\x61\xc3\x02\x4c\x40\x99\xe5\x70\x89\x7e\x46\xaf\xbd\xfe\x49\xb7\xd3\x91\x19\xf4\xc2\xea\x93\xc9\x84\xb3\x4f\x26\x35\xa6\x41\x7c\xc7\xaf\xa4\x1b\x66\xa2\x50\xbe\xde\x97\x98\x3a\x16\x7d\x61\x35\x3d\xde\x05\x2d\x3e\x22\x18\xad\x4a\x48\x28\xcb\x88\x05\x85\xa7\x2b\x9d\xc7\x75\x3c\x9c\x1b\x40\x26\x1c\x99\x50\x66\xb0\x41\xc8\x2d\x3e\x4f\x56\x48\xbe\xd3\x09\x46\x2d\x5d\xe9\xd8\xa9\x13\xa0\xdd\x86\x26\x1f\x7a\xf3\xae\x58\x2f\xd0\xf6\xfa\xf0\x1d\x8c\x6f\xb3\x71\x1f\x26\x13\x7e\xa8\x74\x8f\x3c\x51\x5f\x92\x62\xf2\x78\x50\xe6\xbf\xf2\x56\xea\x65\x38\x6b\xd4\xf5\x2c\x03\x01\x1a\x37\x90\x18\xcd\xa0\x26\xaf\x2c\x50\xea\x25\x24\x16\x85\xc7\x74\x00\x22\x4d\xc1\x9b\x80\xbc\x1a\x67\xed\x2d\xe1\xbb\xef\xa0\x47\x9b\x4d\xe0\xe8\xf4\x72\x36\x9d\xcf\x8e\xe0\x8f\x3f\x20\x7c\x79\x1a\xbe\xbc\x7c\xda\x6f\x68\x26\xf5\x45\x96\x45\xe5\x58\xe0\x30\x47\xbc\xee\xbd\xe8\x0f\x6f\x84\x2a\xf0\x22\x0b\x6a\x46\xda\x99\x4e\x61\x12\x79\x9e\xed\xf2\xbc\x6c\xf1\x10\xd3\x68\x04\x53\xe7\x70\xbd\x50\xb8\x1f\x90\x31\x62\x39\x78\x9d\xa7\x8c\x45\xe8\x4b\xcc\x3a\x57\x48\xa8\xaa\x76\x8d\xe6\x67\x8d\x3b\xbe\xcc\xf1\x18\x00\xc0\xe4\x03\xfe\x40\xb1\xc0\x1f\xbc\xf9\x19\x6f\xd9\x47\x95\x09\x09\x55\xd3\x34\xb5\xe8\x5c\xaf\xdf\x0f\xe4\x52\xe7\x85\x3f\x6e\x91\xaf\x71\x6d\x6c\x39\x74\x94\x90\x7a\x7c\xb4\x41\x38\x69\xc5\xb3\x14\xee\x4c\x13\x4f\x44\xea\x5b\xe1\x7a\xdb\xa5\x53\xe3\xfc\x71\xb5\x44\x2f\xd5\x1a\xdb\x82\xd8\x8e\xc6\xb7\x47\xfb\xd6\x1a\xf7\xb7\x48\x78\xf1\x43\x9f\x58\xee\x4e\x6a\x7c\xd7\x69\x62\x98\x17\x6e\xd5\x63\x38\x6d\x57\xb7\xa9\x60\x02\xde\x16\x78\x10\xfe\x0c\xa9\x7d\x38\x39\x54\x19\xe5\x12\x6f\x8b\x84\x61\xb5\x14\x9c\x69\x38\xd2\x05\x65\x5e\x57\x2c\xd8\xe6\xde\x98\x7d\x74\x45\x70\x5d\xcd\xce\xdf\xbc\x9e\x5d\xcd\x2f\x3f\x9c\xce\x8f\x1a\x70\x52\x98\x79\x52\xaa\x7d\x06\x85\x7a\xe9\x57\xac\x3f\x89\x6b\xaf\x7e\x26\x9e\xe7\x2f\xbe\x84\x2f\x30\x39\x10\xf2\x9d\x87\x39\xe0\xf3\x17\x96\x7d\xb7\x6f\xbe\x36\x69\x30\xe6\xd7\x00\x22\x93\xdf\x35\x13\xc7\x81\x58\x5c\xa3\x5f\x99\x94\x93\x63\x22\x42\x7e\xad\xac\x98\x1a\x8d\xff\x7a\x44\x4e\xcf\xcf\x1b\xf1\xc8\xef\xa7\x17\xaf\x9b\x31\x7a\xf4\x7a\x76\x3e\x7b\x3b\x9d\xcf\x76\x69\xaf\xe6\xd3\xf9\xd9\x29\x7f\xad\xc2\x77\x34\x82\xab\x6b\x99\x73\x96\xe5\xdc\x65\xd6\x39\xb7\x8b\xb5\xbe\x6e\x00\x7e\x65\xa8\x11\xb3\xb1\x88\x64\x42\x27\x55\x72\x77\x95\xd3\xbc\x21\x97\x99\x2a\x56\xf6\x53\x41\x13\xa8\xfd\xda\x8d\xd2\xbd\xb7\x18\x37\x4d\x7b\xde\x54\x7a\x6d\x0d\x1a\x3c\xc2\x09\x90\x93\x4c\xef\xf1\x87\x84\xbf\xc1\x18\x8e\xe1\x45\xcc\x24\x0f\xa4\xaa\x97\xf0\x8c\xc4\xff\x89\x84\xf5\xea\x00\xe7\x5f\x33\x6d\x79\xc3\xc4\x15\xb9\x37\xff\xfd\x74\x66\x0a\x7f\x91\x65\xc7\xb0\x6b\xc4\xef\xf7\x8c\x58\xd3\x9f\xa3\xde\xa7\xff\xbf\x3d\xfa\x6d\xea\x23\x54\x99\x1c\x9e\xec\x41\x24\x24\x9e\x27\x3b\x71\x10\x8d\xcb\x2d\x0e\x4b\x83\xc9\x3d\xc9\xf6\x65\x1b\xc3\xf7\x65\x8b\x7f\x2b\xd9\x1e\x6c\xd5\xa8\x21\x6b\x37\x63\x03\xb0\xe8\xad\xc4\x1b\x1a\xb7\x8e\x1c\x8b\xa4\xa6\xd5\x6c\x84\x4e\x70\x08\x1f\x31\x48\xd4\x88\x9c\x5c\x62\x93\x4b\x3d\x0a\xf7\x7d\xd4\xa8\xc6\x71\x85\x21\x26\xb8\x17\xb5\x08\x6b\x51\xd2\xb8\x92\x15\xfa\xba\x84\xa5\x70\x90\x96\x5a\xac\x65\xe2\x82\x3c\x6e\x70\x2d\x2e\x85\x65\xb1\x16\x7f\x2f\xd0\xd1\xec\x43\x40\x16\x89\x2f\x84\x52\x25\x2c\x25\x0d\x30\xc4\xdd\x7b\xf9\x6a\x3c\x06\xe7\x65\x8e\x3a\x1d\xc0\x0f\xaf\x46\x3f\x7c\x0f\xb6\x50\xd8\x1f\x76\x1b\x69\xbc\x3e\x6a\xf4\x06\x2d\x44\xf4\xbc\xc6\xdc\xaf\x7a\x7d\xf8\xe9\x9e\x7a\x70\x4f\x72\x3f\x48\x0b\xcf\xe1\xc5\x97\x21\xe9\x35\x69\xe1\x36\x78\x12\x50\x39\x8c\xd2\x68\xe8\xbb\x78\x7d\xd1\xbb\x16\x56\x28\xb1\xc0\xfe\x31\x0f\x81\x6c\xab\x8d\x88\x53\x00\x39\x05\x72\x25\xa4\x06\x91\x24\xa6\xd0\x9e\x0c\x5f\x35\xf4\xaa\xa4\xfc\x7e\xe4\x2b\x79\x3c\x2f\x89\x24\x41\xe7\xaa\x74\xcf\x5e\x23\x75\xc4\x9a\xb8\x41\x6a\x27\x53\x6c\x78\x85\xb2\x83\xe1\xd4\x1c\x29\x68\x9c\xac\x04\xae\x8d\xa3\x4d\x16\x08\x1b\x4b\xc3\x87\x93\x3a\xe1\xe9\x3b\x45\xb2\xb6\x03\xa3\x41\x80\x32\x3c\xf2\x73\x8c\x83\xb0\x4b\x37\x0c\xf9\x9e\xb6\xa5\x9c\xa3\xcd\x66\xd8\x06\x72\x13\xaa\xdc\xe6\xef\xb4\x03\x1a\xf0\x56\x3a\xcf\x5d\x25\x69\x29\x1d\x04\x24\x4b\xbd\x1c\x40\x6e\x72\xce\xd3\xdf\x2a\x67\x31\x59\x5f\xce\x7e\x9d\x5d\xd6\xc5\xff\xf1\x4e\xac\xfa\xfe\xa7\xf5\x58\x04\x96\x66\x0e\x8f\xe9\xd3\x03\x8d\xfc\x01\x40\x4d\xee\x01\x14\xc9\xdf\xd6\xc6\xf7\x8d\xe3\x28\xe1\xfc\xd6\x31\x4b\x0c\x33\x4d\x53\x01\x57\x28\xef\x76\x72\xf7\x6e\x72\x30\x79\x55\x21\x48\x29\x4e\x3b\x94\xd8\x77\xbb\xed\xd6\xc2\xb6\xe9\xde\xe2\xf3\xac\x61\xe3\x0d\xb7\x5c\x81\xa8\x91\x1a\x78\xbd\xea\xdd\x44\xa8\x06\xac\xbb\x29\x3c\xc1\x81\xea\xf7\x36\xf9\x2d\x85\xfb\xe0\xd8\xeb\x31\xfd\x2d\xe4\xf2\x4c\xfb\x5e\xb5\x78\xa6\xe1\x39\x54\x2f\x94\xd4\xe1\x79\x2b\x8a\x0e\x64\xc7\x4e\x8a\x0a\x3d\xc2\x56\xc4\x09\xec\x7c\x22\x41\xc1\x1c\x6c\x34\x8b\x7e\xbf\x38\x8f\xa3\x34\x32\xd8\x13\x8b\x7e\x88\xbf\x17\x42\xb9\xde\xb8\x6e\x16\xc2\x09\xbc\xe1\xf2\x36\xa9\x0b\x5c\x55\x01\x89\xa7\xd5\x7e\x44\x81\x81\x2d\x5a\xa3\x62\x4b\x17\xa1\x6a\xa5\xf8\xa0\x84\x28\x22\xa6\x8d\xda\x97\x11\x98\x87\xfa\xcf\x4e\x93\x00\x9e\xd6\x0d\x41\x26\xa4\x2a\x2c\x3e\x3d\x81\x03\x69\xc7\x15\x36\x13\x09\xfb\xd2\x21\xf0\xc4\xea\xc0\x99\x35\xae\xcc\x26\x28\x70\x28\x79\xed\x83\xa3\xc6\xc1\x4e\xf9\xe0\xab\x17\xe1\xa0\x70\x62\x89\x0d\x70\xd4\x06\xaf\x1c\x75\x70\x8c\xfe\xd3\xd0\x79\x56\xbf\x3e\x02\x45\x77\xff\x19\x78\xec\xf8\x79\xaf\xcf\xa9\x88\xb8\xdb\x69\xbc\x54\xca\x86\x66\xe4\xaf\xe5\xf8\x47\x47\xd8\x2e\x6d\x38\x5a\x9b\x38\x1c\x70\xdb\xd7\x7c\xdb\xfd\xf5\xea\x7d\x9e\xbf\xaf\x65\x22\x8c\xea\xdf\x30\xf1\x5b\x9c\x72\x97\x43\x6f\xb9\xc5\x1b\x69\x0a\x2a\x60\xf8\xbf\x34\x12\xd6\x2d\xdf\x5d\xb7\x73\x17\xef\xc6\xd8\x6f\xcd\xcb\xb1\xcd\x2a\xde\xed\x86\x6e\xa9\x51\x3e\x0c\xd7\xd6\x78\x65\x96\x85\x5b\xd7\x0e\xf3\x3f\x70\x49\x16\x03\xdd\x9b\x9c\xda\x81\x58\x9d\x94\x45\x91\x96\x75\x41\x1c\x84\x46\x04\x56\x42\xa7\x71\x18\x11\x69\x2a\x49\x1e\x83\x90\x34\x14\x4b\x21\x75\xf7\xa0\x19\xbf\x59\x85\x0f\x21\x63\xaf\xb7\x6d\x16\xd2\x38\x44\xd2\xc4\xc7\x1a\x77\x1f\x51\x30\x77\x82\x68\xf7\xbe\x2f\x5e\x19\x1a\xed\x8a\x35\x77\xc2\x20\x6e\x84\x54\x82\xa6\x2f\xee\xb0\x74\x0a\x89\x42\xa1\xc3\x2d\x3f\x66\xde\xdc\xa0\x75\xdd\x47\x80\xfc\xcf\x60\x7c\x27\x2b\x56\xaf\xd1\x1c\x8f\x8f\xd9\xc7\x46\x6c\x38\xfe\x1b\x25\xbc\x8f\xf0\x6a\x98\x37\x44\x96\xf4\xfc\x03\x08\xb5\xef\x3e\x2e\xa4\xb8\x67\x22\x9a\x9f\x60\xdc\xe8\xcb\xff\x2a\x41\xb6\x0f\xb1\xf3\xba\x3f\x8b\x87\xf7\xc6\x0c\x40\xa1\xe0\x29\xa9\xfa\x3d\x53\xf5\xa3\x0f\x0d\x6d\x55\xf4\x86\x8e\x6e\x2f\x7c\xf9\x5e\x6b\x85\xd5\x0d\x48\x68\xed\x17\x88\x1a\xa4\x47\x2b\x68\x1e\x22\x74\xc5\x3f\x0a\xa4\xa5\x63\x71\xec\x17\x49\x41\x17\x05\xc7\xeb\x7d\x2a\xcc\x52\x2f\x87\xdd\x4e\xf8\xde\x88\xf7\xc4\xdf\x6e\xe3\x3d\x54\x40\xe6\x8c\x77\x02\xf5\x95\x40\xe2\x6f\xb9\x5b\xe4\xb1\x79\xe7\x5e\x80\xd6\xe8\x53\x98\xa9\x77\x6e\x01\x98\x31\xde\x04\xec\x5e\x36\xd2\x1a\x7f\x6b\x01\x9c\x49\x97\xc2\x05\x31\x3b\x21\xe1\x6f\xf7\x23\xa2\x62\xa0\x60\x38\x3e\xcc\x40\x4b\x07\x98\x76\x6e\x26\x88\x98\x3f\x85\xd5\x50\xcf\x8f\x9b\xab\xe1\x53\x3c\xa8\x5c\x37\x6c\x23\xd7\x6c\x9b\xbb\x93\xc3\x49\x6e\x5c\xe1\xf1\x70\x32\x23\x9b\xd7\x80\xbd\x87\xb5\x39\x6b\xec\x93\x3c\x94\x2a\x59\x7a\x95\xd9\xee\x61\x65\xe9\x8d\x96\xc3\xdf\x3e\x5e\x64\x4d\xdc\x54\xb1\x45\xd3\x12\xc2\xb7\x8d\x7b\xcb\x87\x26\x2d\x1a\x54\x22\x61\xd5\x5c\x4d\x26\x4f\xc7\xb7\xf5\xcf\x81\x98\xab\x5a\x34\x95\x12\x21\x32\xc2\x79\x39\x2a\xe4\x3f\x30\x6e\xdb\x8c\xc1\x6a\x09\x2c\x86\x9f\x18\xdc\xcd\x52\x08\x9a\x05\x37\x10\x85\xa3\x51\x74\x1b\x5b\x29\x3a\x69\x31\x85\x4c\xa2\x4a\xc1\xa4\x68\x79\xd0\xfd\xcd\x19\x1d\x7e\x57\xa1\x95\x24\x31\xfc\x96\x0b\x7f\xc8\xf9\x67\xa1\x96\x09\xfa\x12\x32\x14\xfc\xdf\xc9\x1b\xc8\x85\x73\xb0\x46\x41\xa3\x6d\x56\x28\x55\x82\xb1\x29\x92\xf0\x7a\xd6\xa3\xb0\x36\x50\x38\xb4\x0e\x36\x2b\x13\x4b\x2d\xb7\x78\x39\x75\xab\xd2\x0f\xe2\x75\x8e\x74\xb9\x12\x25\x48\x4f\x65\x3d\x1e\xaa\x19\xe9\xf5\xcf\x1e\xfe\x63\x64\xc8\xc0\xfb\x61\x5e\x4d\x85\xed\x38\xe7\xcf\xf4\xd6\x8e\xf0\x38\x14\xb5\x63\x7b\x7b\xd1\xd5\x0e\xe4\xaa\xf4\xb4\xa3\xb5\x59\xc8\xda\x21\xc9\x2b\xfc\xd6\x0e\xc6\x46\xab\xcd\x0b\x8c\xa0\x9a\x81\xdf\x76\xc2\x93\xb5\x8c\xf1\x19\x7e\x6d\xd6\xe4\xfc\x36\x88\x80\x21\x2f\xf6\xc8\x38\xd7\x58\x52\x36\x0f\x36\x6a\x94\xa6\xf0\xe1\xf3\x35\x96\x5f\x0e\x57\xa2\x08\xc7\x06\x5d\x5d\x7a\xaa\xb0\x08\x6b\x0f\x24\x83\x5a\x0b\x39\x19\x9f\x80\xfc\xb1\xc9\x50\x55\x4f\x90\xcf\x9e\x55\x7b\x36\xd7\x3f\xcb\x2f\x55\x84\xd7\x88\xdf\x59\xef\xb7\x34\x8a\x31\x12\x68\x28\x28\xba\x77\xdd\x7f\x06\x00\x00\xff\xff\x5a\x43\x33\xde\x00\x22\x00\x00")
+var _call_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x5a\xdf\x6f\x1b\x37\xf2\x7f\x96\xfe\x8a\x89\x1f\x6a\x09\x51\x24\x39\xe9\xb7\x5f\xc0\xae\x7a\x50\x1d\x25\x35\xe0\xc6\x81\xad\x34\x08\x82\x3c\x50\xbb\xb3\x12\x6b\x8a\xdc\x92\x5c\xc9\xba\xd6\xff\xfb\x61\x86\xdc\xd5\xae\x24\x3b\xbe\x5e\x71\xe8\xbd\x69\x97\x33\xc3\xe1\xcc\x67\x7e\x71\x35\x18\xc0\xb9\xc9\x37\x56\xce\x17\x1e\x5e\x0e\x4f\xfe\x1f\xa6\x0b\x84\xb9\x79\x81\x7e\x81\x16\x8b\x25\x8c\x0b\xbf\x30\xd6\xb5\x07\x03\x98\x2e\xa4\x83\x4c\x2a\x04\xe9\x20\x17\xd6\x83\xc9\xc0\xef\xd0\x2b\x39\xb3\xc2\x6e\xfa\xed\xc1\x20\xf0\x1c\x5c\x26\x09\x99\x45\x04\x67\x32\xbf\x16\x16\x4f\x61\x63\x0a\x48\x84\x06\x8b\xa9\x74\xde\xca\x59\xe1\x11\xa4\x07\xa1\xd3\x81\xb1\xb0\x34\xa9\xcc\x36\x24\x52\x7a\x28\x74\x8a\x96\xb7\xf6\x68\x97\xae\xd4\xe3\xed\xbb\x0f\x70\x89\xce\xa1\x85\xb7\xa8\xd1\x0a\x05\xef\x8b\x99\x92\x09\x5c\xca\x04\xb5\x43\x10\x0e\x72\x7a\xe3\x16\x98\xc2\x8c\xc5\x11\xe3\x1b\x52\xe5\x26\xaa\x02\x6f\x4c\xa1\x53\xe1\xa5\xd1\x3d\x40\x49\x9a\xc3\x0a\xad\x93\x46\xc3\xab\x72\xab\x28\xb0\x07\xc6\x92\x90\x8e\xf0\x74\x00\x0b\x26\x27\xbe\x2e\x08\xbd\x01\x25\xfc\x96\xf5\x09\x06\xd9\x9e\x3b\x05\xa9\x79\x9b\x85\xc9\x11\xfc\x42\x78\x3a\xf5\x5a\x2a\x05\x33\x84\xc2\x61\x56\xa8\x1e\x49\x9b\x15\x1e\x3e\x5e\x4c\x7f\xba\xfa\x30\x85\xf1\xbb\x4f\xf0\x71\x7c\x7d\x3d\x7e\x37\xfd\x74\x06\x6b\xe9\x17\xa6\xf0\x80\x2b\x0c\xa2\xe4\x32\x57\x12\x53\x58\x0b\x6b\x85\xf6\x1b\x30\x19\x49\xf8\x79\x72\x7d\xfe\xd3\xf8\xdd\x74\xfc\xe3\xc5\xe5\xc5\xf4\x13\x18\x0b\x6f\x2e\xa6\xef\x26\x37\x37\xf0\xe6\xea\x1a\xc6\xf0\x7e\x7c\x3d\xbd\x38\xff\x70\x39\xbe\x86\xf7\x1f\xae\xdf\x5f\xdd\x4c\xfa\x70\x83\xa4\x15\x12\xff\xd7\x6d\x9e\xb1\xf7\x2c\x42\x8a\x5e\x48\xe5\x4a\x4b\x7c\x32\x05\xb8\x85\x29\x54\x0a\x0b\xb1\x42\xb0\x98\xa0\x5c\x61\x0a\x02\x12\x93\x6f\x9e\xec\x54\x92\x25\x94\xd1\x73\x3e\xf3\x83\x80\x84\x8b\x0c\xb4\xf1\x3d\x70\x88\xf0\xfd\xc2\xfb\xfc\x74\x30\x58\xaf\xd7\xfd\xb9\x2e\xfa\xc6\xce\x07\x2a\x88\x73\x83\x1f\xfa\x6d\x92\x99\x08\xa5\xa6\x56\x24\x68\xc9\x39\x02\xb2\x82\xcc\xaf\xcc\x5a\x83\xb7\x42\x3b\x91\x90\xab\xe9\x77\xc2\x60\x14\x1e\xf0\x8e\x9e\xbc\x23\xd0\x82\xc5\xdc\x58\xfa\xad\x54\x89\x33\xa9\x3d\x5a\x2d\x14\xcb\x76\xb0\x14\x29\xc2\x6c\x03\xa2\x2e\xb0\x57\x3f\x0c\xc1\x28\xb8\x1b\xa4\xce\x8c\x5d\x32\x2c\xfb\xed\xdf\xdb\xad\xa8\xa1\xf3\x22\xb9\x25\x05\x49\x7e\x52\x58\x8b\xda\x93\x29\x0b\xeb\xe4\x0a\x99\x04\x02\x4d\xb4\xe7\xe4\x97\x9f\x01\xef\x30\x29\x82\xa4\x56\x25\xe4\x14\x3e\xff\x7e\xff\xa5\xd7\x66\xd1\x29\xba\x04\x75\x8a\x29\x9f\xef\xd6\xc1\x7a\xc1\x16\x85\x35\x1e\xaf\x10\x7e\x2d\x9c\xaf\xd1\x64\xd6\x2c\x41\x68\x30\x05\x21\xbe\x6e\x1d\xa9\xbd\x61\x81\x82\x7e\x6b\xb4\xac\x51\xbf\xdd\xaa\x98\x4f\x21\x13\xca\x61\xdc\xd7\x79\xcc\xe9\x34\x52\xaf\xcc\x2d\x49\x36\x96\x20\x6c\x37\x60\xf2\xc4\xa4\x31\x18\xe8\x1c\xd5\x31\xd0\xf5\xdb\x2d\xe2\x3b\x85\xac\xd0\xbc\x6d\x47\x99\x79\x0f\xd2\x59\x17\x7e\x6f\xb7\x48\xec\xb9\xc8\x7d\x61\x91\xed\x89\xd6\x1a\xeb\x40\x2e\x97\x98\x4a\xe1\x51\x6d\xda\xad\xd6\x4a\xd8\xb0\x00\x23\x50\x66\xde\x9f\xa3\x9f\xd0\x63\xa7\x7b\xd6\x6e\xb5\x64\x06\x9d\xb0\xfa\x6c\x34\xe2\xec\x93\x49\x8d\x69\x10\xdf\xf2\x0b\xe9\xfa\x99\x28\x94\xaf\xf6\x25\xa6\x96\x45\x5f\x58\x4d\x3f\xef\x83\x16\x1f\x11\x8c\x56\x1b\x48\x28\xcb\x88\x19\x85\xa7\xdb\x38\x8f\xcb\x78\x38\xd7\x83\x4c\x38\x32\xa1\xcc\x60\x8d\x90\x5b\x7c\x91\x2c\x90\x7c\xa7\x13\x8c\x5a\xba\x8d\x63\xa7\x8e\x80\x76\xeb\x9b\xbc\xef\xcd\xbb\x62\x39\x43\xdb\xe9\xc2\x37\x30\xbc\xcb\x86\x5d\x18\x8d\xf8\x47\xa9\x7b\xe4\x89\xfa\x92\x14\x93\xc7\x83\x32\xff\x8d\xb7\x52\xcf\xc3\x59\xa3\xae\x17\x19\x08\xd0\xb8\x86\xc4\x68\x06\x35\x79\x65\x86\x52\xcf\x21\xb1\x28\x3c\xa6\x3d\x10\x69\x0a\xde\x04\xe4\x55\x38\x6b\x6e\x09\xdf\x7c\x03\x1d\xda\x6c\x04\xc7\xe7\xd7\x93\xf1\x74\x72\x0c\x7f\xfc\x01\xe1\xcd\x51\x78\xf3\xf2\xa8\x5b\xd3\x4c\xea\xab\x2c\x8b\xca\xb1\xc0\x7e\x8e\x78\xdb\x39\xe9\xf6\x57\x42\x15\x78\x95\x05\x35\x23\xed\x44\xa7\x30\x8a\x3c\xcf\x77\x79\x5e\x36\x78\x88\x69\x30\x80\xb1\x73\xb8\x9c\x29\xdc\x0f\xc8\x18\xb1\x1c\xbc\xce\x53\xc6\x22\xf4\x25\x66\x99\x2b\x24\x54\x95\xbb\x46\xf3\xb3\xc6\x2d\xbf\xc9\xf1\x14\x00\xc0\xe4\x3d\x7e\x41\xb1\xc0\x2f\xbc\xf9\x09\xef\xd8\x47\xa5\x09\x09\x55\xe3\x34\xb5\xe8\x5c\xa7\xdb\x0d\xe4\x52\xe7\x85\x3f\x6d\x90\x2f\x71\x69\xec\xa6\xef\x28\x21\x75\xf8\x68\xbd\x70\xd2\x92\x67\x2e\xdc\x85\x26\x9e\x88\xd4\xb7\xc2\x75\xb6\x4b\xe7\xc6\xf9\xd3\x72\x89\x1e\xca\x35\xb6\x05\xb1\x1d\x0f\xef\x8e\xf7\xad\x35\xec\x6e\x91\x70\xf2\x5d\x97\x58\xee\xcf\x2a\x7c\x57\x69\xa2\x9f\x17\x6e\xd1\x61\x38\x6d\x57\xb7\xa9\x60\x04\xde\x16\x78\x10\xfe\x0c\xa9\x7d\x38\x39\x54\x19\xe5\x12\x6f\x8b\x84\x61\x35\x17\x9c\x69\x38\xd2\x05\x65\x5e\x57\xcc\xd8\xe6\xde\x98\x7d\x74\x45\x70\xdd\x4c\x2e\xdf\xbc\x9e\xdc\x4c\xaf\x3f\x9c\x4f\x8f\x6b\x70\x52\x98\x79\x52\xaa\x79\x06\x85\x7a\xee\x17\xac\x3f\x89\x6b\xae\x7e\x26\x9e\x17\x27\x5f\xc2\x1b\x18\x1d\x08\xf9\xd6\xe3\x1c\xf0\xf9\x0b\xcb\xbe\xdf\x37\x5f\x93\x34\x18\xf3\xaf\x41\x92\x37\x4c\x5c\x92\x7b\x53\x12\x3c\xee\xe7\xbf\x18\x54\xe9\x8c\x28\x7e\x14\x4a\xe8\x04\x1f\xd1\x79\x1f\x6b\xf5\xa4\x79\x20\x0f\x2d\xd1\x2f\x4c\xca\x85\x21\x11\xa1\xb6\x94\x08\x4a\x8d\xc6\x7f\x3f\x1b\x8d\x2f\x2f\x6b\xb9\x88\x9f\xcf\xaf\x5e\xd7\xf3\xd3\xf1\xeb\xc9\xe5\xe4\xed\x78\x3a\xd9\xa5\xbd\x99\x8e\xa7\x17\xe7\xfc\xb6\x4c\x5d\x83\x01\xdc\xdc\xca\x9c\x2b\x0c\xe7\x6d\xb3\xcc\xb9\x55\xae\xf4\x75\x3d\xf0\x0b\x43\x4d\xa8\x8d\x05\x34\x13\x3a\x29\x0b\x9b\x2b\x01\xeb\x0d\xc1\xf5\x21\xe7\x9d\xec\x38\xaf\x82\xb0\x74\xef\x2d\xc6\x4d\xd3\x8e\x37\xa5\x5e\x5b\x83\x06\x34\x72\xf2\xe7\x04\xdb\x79\xfa\x21\xe1\x1f\x30\x84\x53\x38\x89\x59\xf4\x91\x34\xfd\x12\x9e\x93\xf8\x3f\x91\xac\x5f\x1d\xe0\xfc\x7b\xa6\xec\xbd\x40\xfb\xef\xa7\x72\x53\xf8\xab\x2c\x3b\x85\x5d\x23\x7e\xbb\x67\xc4\x8a\xfe\x12\xf5\x3e\xfd\xff\xed\xd1\x6f\xd3\x3e\xa1\xca\xe4\xf0\x6c\x0f\x22\x21\xe9\x3e\xdb\x89\x83\x68\x5c\x6e\xef\x58\x1a\x8c\x1e\x28\x34\x2f\x9b\x18\x7e\x28\x53\xfe\x47\x85\xe6\x60\x9b\x4a\xcd\x68\xb3\x11\xed\x81\x45\x6f\x25\xae\x68\xd4\x3c\x76\x2c\x92\x1a\x76\xb3\xa6\xf4\xd5\x87\x8f\x18\x24\x6a\x44\x4e\x2e\xb1\xc1\xa7\xfe\x8c\x7b\x5e\x6a\xd2\xe3\xa8\xc6\x10\x13\xdc\x87\x5b\x84\xa5\xd8\xd0\xa8\x96\x15\xfa\x76\x03\x73\xe1\x20\xdd\x68\xb1\x94\x89\x0b\xf2\xb8\xb9\xb7\x38\x17\x96\xc5\x5a\xfc\xad\x40\x47\x73\x1f\x01\x59\x24\xbe\x10\x4a\x6d\x60\x2e\x69\x78\x23\xee\xce\xcb\x57\xc3\x21\x38\x2f\x73\xd4\x69\x0f\xbe\x7b\x35\xf8\xee\x5b\xb0\x85\xc2\x6e\xbf\x5d\x2b\x61\xd5\x51\xa3\x37\x68\x21\xa2\xe7\x35\xe6\x7e\xd1\xe9\xc2\x0f\x0f\xd4\xc2\x07\x0a\xdb\x41\x5a\x78\x01\x27\x5f\xfa\xa4\xd7\xa8\x81\xdb\xe0\x49\x40\xe5\x30\x4a\xa3\x81\xf7\xea\xf5\x55\xe7\x56\x58\xa1\xc4\x0c\xbb\xa7\x3c\x00\xb3\xad\xd6\x22\x4e\x40\xe4\x14\xc8\x95\x90\x1a\x44\x92\x98\x42\x7b\x32\x7c\x39\xcc\xa8\x0d\xe5\xf7\x63\x5f\xca\xe3\x59\x51\x24\x09\x3a\x57\xa6\x7b\xf6\x1a\xa9\x23\x96\xc4\x0d\x52\x3b\x99\x62\xcd\x2b\x94\x1d\x0c\xa7\xe6\x48\x41\xa3\x74\x29\x70\x69\x1c\x6d\x32\x43\x58\x5b\x1a\xbc\x9c\xd4\x09\xdf\x3c\xa4\x48\xd6\x76\x60\x34\x08\x50\x86\xaf\x3b\x38\xc6\x41\xd8\xb9\xeb\x87\x7c\x4f\xdb\x52\xce\xd1\x66\xdd\x6f\x02\xb9\x0e\x55\x1e\x71\x76\x5a\x21\x0d\x78\x27\x9d\xe7\x8e\x9a\xb4\x94\x0e\x02\x92\xa5\x9e\xf7\x20\x37\x39\xe7\xe9\xaf\x95\xb3\x98\xac\xaf\x27\xbf\x4c\xae\xab\xc6\xe7\xe9\x4e\x2c\x67\x9e\xa3\x6a\x24\x04\x4b\xf3\x96\xc7\xf4\xe8\xc0\x10\x73\x00\x50\xa3\x07\x00\x45\xf2\xb7\xb5\xf1\x7d\xed\x38\x4a\x38\xbf\x75\xcc\x1c\xc3\x3c\x57\x57\xc0\x15\xca\xbb\x9d\xdc\xbd\x9b\x1c\x4c\x5e\x56\x08\x52\x8a\xd3\x0e\x25\xf6\xdd\x49\xa3\xb1\xb0\x1d\x38\xb6\xf8\xbc\xa8\xd9\x78\xcd\xed\x66\x20\xaa\xa5\x06\x5e\x2f\xfb\x56\x11\xaa\x01\xeb\x6e\x0a\x4f\x70\xa0\xfa\xbd\x4d\x7e\x73\xe1\x3e\x38\xf6\x7a\x4c\x7f\x33\x39\xbf\xd0\xbe\x53\x2e\x5e\x68\x78\x01\xe5\x03\x25\x75\x78\xd1\x88\xa2\x03\xd9\xb1\x95\xa2\x42\x8f\xb0\x15\x71\x06\x3b\xaf\x48\x50\x30\x07\x1b\xcd\xa2\xdf\x2f\xce\xc3\x28\x8d\x0c\xf6\xcc\xa2\xef\xe3\x6f\x85\x50\xae\x33\xac\x9a\x85\x70\x02\x6f\xb8\xbc\x8d\xf6\x3a\x49\xe2\x69\xf6\x8e\x67\x35\xb6\x68\x8d\x92\x2d\x74\x82\xe7\x26\xc5\x47\x25\x44\x11\x31\x6d\x54\xbe\x8c\xc0\x3c\xd4\x7b\xb7\xea\x04\x70\x54\x35\x04\x99\x90\xaa\xb0\x78\x74\x06\x07\xd2\x8e\x2b\x6c\x26\x12\xf6\xa5\x43\xe0\x69\xdd\x81\x33\x4b\x5c\x98\x75\x50\xe0\x50\xf2\xda\x07\x47\x85\x83\x9d\xf2\xc1\xd7\x4e\xc2\x41\xe1\xc4\x1c\x6b\xe0\xa8\x0c\x5e\x3a\xea\xe0\x15\xc2\x9f\x86\xce\xf3\xea\xf1\x09\x28\xba\xff\x6b\xe0\xb1\xe3\xe7\xbd\x3e\xa7\x24\xe2\x6e\xa7\xf6\x50\x2a\x1b\x9a\x91\xbf\x97\xe3\x9f\x1c\x61\xbb\xb4\xe1\x68\x4d\xe2\x70\xc0\x6d\x5f\xf3\x75\xf7\x57\xab\x0f\x79\xfe\xa1\x96\x89\x30\xaa\x7f\xc5\xc4\x6f\x71\xca\x5d\x0e\x3d\xe5\x16\x57\xd2\x14\x54\xc0\xf0\x7f\x69\x1c\xae\x5a\xbe\xfb\x76\xeb\x3e\xde\x0b\xb2\xdf\xea\x17\x83\xeb\x45\xbc\xd7\x0e\xdd\x52\xad\x7c\x18\xae\xad\xf1\xba\x30\x0b\x37\xce\x2d\xe6\x7f\xe4\x82\x30\x06\xba\x37\x39\xb5\x03\xb1\x3a\x29\x8b\x22\xdd\x54\x05\xb1\x17\x1a\x11\x58\x08\x9d\xc6\x61\x44\xa4\xa9\x24\x79\x0c\x42\xd2\x50\xcc\x85\xd4\xed\x83\x66\xfc\x6a\x15\x3e\x84\x8c\xbd\xde\xb6\x5e\x48\xe3\x10\x49\x13\x1f\x6b\xdc\x7e\x42\xc1\xdc\x09\xa2\xdd\xbb\xce\x78\x5d\x6a\xb4\x2b\x96\xdc\x09\x83\x58\x09\xa9\x04\x4d\x5f\xdc\x61\xe9\x14\x12\x85\x42\x87\x2f\x1c\x98\x79\xb3\x42\xeb\xda\x4f\x00\xf9\x9f\xc1\xf8\x4e\x56\x2c\x1f\xa3\x39\x9e\x1e\xb3\x4f\x8d\xd8\x70\xfc\x37\x4a\x78\x1f\xe1\x55\x33\x6f\x88\x2c\xe9\xf9\xe3\x17\x6a\xdf\x7e\x5a\x48\x71\xcf\x44\x34\x3f\xc0\xb0\xd6\x97\xff\x5d\x82\x6c\x1f\x62\x97\x55\x7f\x16\x0f\xef\x8d\xe9\x81\x42\xc1\x53\x52\xf9\x69\xaa\xec\x47\x1f\x1b\xda\xca\xe8\x0d\x1d\xdd\x5e\xf8\xf2\x9d\xde\x02\xcb\x1b\x90\xd0\xda\xcf\x10\x35\x48\x8f\x56\xd0\x3c\x44\xe8\x8a\x5f\x53\x48\x4b\xc7\xe2\xd8\x2f\x92\x82\x2e\x0a\x8e\x9f\x36\xa8\x30\x4b\x3d\xef\xb7\x5b\xe1\x7d\x2d\xde\x13\x7f\xb7\x8d\xf7\x50\x01\x99\x33\xde\x09\x54\x57\x02\x89\xbf\xe3\x6e\x91\xc7\xe6\x9d\x7b\x01\x5a\xa3\x57\x61\xa6\xde\xb9\x05\x60\xc6\x78\x13\xb0\x7b\x27\x46\x6b\xfc\xae\x01\x70\x26\x9d\x0b\x17\xc4\xec\x84\x84\xbf\xdb\x8f\x88\x92\x81\x82\xe1\xf4\x30\x03\x2d\x1d\x60\xda\xb9\x99\x20\x62\x7e\x15\x56\x43\x3d\x3f\xad\xaf\x86\x57\xf1\xa0\x72\x59\xb3\x8d\x5c\xb2\x6d\xee\xcf\x0e\x27\xb9\x61\x89\xc7\xc3\xc9\x8c\x6c\x5e\x01\xf6\x01\xd6\xfa\xac\xb1\x4f\xf2\x58\xaa\x64\xe9\x65\x66\x7b\x80\x95\xa5\xd7\x5a\x0e\x7f\xf7\x74\x91\x15\x71\x5d\xc5\x06\x4d\x43\x08\xdf\x36\xee\x2d\x1f\x9a\xb4\x68\x50\x89\x84\x65\x73\x35\x1a\x1d\x0d\xef\xaa\x0f\x23\x31\x57\x35\x68\x4a\x25\x42\x64\x84\xf3\x72\x54\xc8\x7f\x62\xdc\xb6\x1e\x83\xe5\x12\x58\x0c\x1f\x70\xb8\x9b\xa5\x10\x34\x33\x6e\x20\x0a\x47\xa3\xe8\x36\xb6\x52\x74\xd2\x62\x0a\x99\x44\x95\x82\x49\xd1\xf2\xa0\xfb\xab\x33\x3a\x7c\xaa\x43\x2b\x49\x62\xf8\x24\x19\xfe\x1d\xc0\x1f\x4a\xb5\x4c\xd0\x6f\x20\x43\xc1\xdf\xdc\xbc\x81\x5c\x38\x07\x4b\x14\x34\xda\x66\x85\x52\x1b\x30\x36\x45\x12\x5e\xcd\x7a\x14\xd6\x06\x0a\x87\xd6\xc1\x7a\x61\x62\xa9\xe5\x16\x2f\xa7\x6e\x55\xfa\x5e\xbc\xce\x91\x2e\x57\x62\x03\xd2\x53\x59\x8f\x87\xaa\x47\x7a\xf5\xa1\x8b\xbf\x96\x19\x32\xf0\x7e\x98\x97\x53\x61\x33\xce\xf9\x35\x3d\x35\x23\x3c\x0e\x45\xcd\xd8\xde\x5e\x74\x35\x03\xb9\x2c\x3d\xcd\x68\xad\x17\xb2\x66\x48\xf2\x0a\x3f\x35\x83\xb1\xd6\x6a\xf3\x02\x23\xa8\x62\xe0\xa7\x9d\xf0\x64\x2d\x63\x7c\x86\xcf\xba\x15\x39\x3f\xf5\x22\x60\xc8\x8b\x1d\x32\xce\x2d\x6e\x28\x9b\x07\x1b\xd5\x4a\x53\x78\xf1\xf9\x16\x37\x5f\x0e\x57\xa2\x08\xc7\x1a\x5d\x55\x7a\xca\xb0\x08\x6b\x8f\x24\x83\x4a\x0b\x39\x1a\x9e\x81\xfc\xbe\xce\x50\x56\x4f\x90\xcf\x9f\x97\x7b\xd6\xd7\x3f\xcb\x2f\x65\x84\x57\x88\xdf\x59\xef\x36\x34\x8a\x31\x12\x68\x28\x28\xda\xf7\xed\x7f\x05\x00\x00\xff\xff\xfb\x65\x93\x4f\xfc\x22\x00\x00")
 
 func call_tracerJsBytes() ([]byte, error) {
 	return bindataRead(
@@ -133,7 +133,7 @@ func call_tracerJs() (*asset, error) {
 	}
 
 	info := bindataFileInfo{name: "call_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
-	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x4d, 0x39, 0xde, 0xc6, 0x79, 0xff, 0xe3, 0x5d, 0x47, 0xed, 0xbd, 0xf4, 0x21, 0xe8, 0xc9, 0x4, 0xe0, 0xe0, 0xe4, 0x76, 0x88, 0x25, 0x7f, 0x4f, 0x30, 0xfe, 0x30, 0x1f, 0x8c, 0x4d, 0x76, 0x3d}}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x46, 0x79, 0xb6, 0xbc, 0xd2, 0xc, 0x25, 0xb1, 0x22, 0x56, 0xef, 0x77, 0xb9, 0x5e, 0x2e, 0xf4, 0xda, 0xb2, 0x2f, 0x53, 0xa4, 0xff, 0xc8, 0xac, 0xbb, 0x75, 0x22, 0x46, 0x59, 0xe3, 0x1d, 0x7d}}
 	return a, nil
 }
 
@@ -197,7 +197,7 @@ func opcount_tracerJs() (*asset, error) {
 	return a, nil
 }
 
-var _prestate_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xdd\x6f\xdb\x38\x12\x7f\x96\xfe\x8a\x41\x5f\x6c\xa3\xae\xdc\x64\x81\x3d\xc0\xb9\x1c\xa0\xba\x6e\x1b\x20\x9b\x04\xb6\x7b\xb9\xdc\x62\x1f\x28\x72\x24\x73\x4d\x93\x02\x49\xd9\xf1\x15\xf9\xdf\x0f\x43\x7d\xf8\xa3\x49\xd3\xdd\x37\x9b\x1c\xfe\xe6\xfb\x37\xa3\xd1\x08\x26\xa6\xdc\x59\x59\x2c\x3d\x9c\xbf\x3f\xfb\x07\x2c\x96\x08\x85\x79\x87\x7e\x89\x16\xab\x35\xa4\x95\x5f\x1a\xeb\xe2\xd1\x08\x16\x4b\xe9\x20\x97\x0a\x41\x3a\x28\x99\xf5\x60\x72\xf0\x27\xf2\x4a\x66\x96\xd9\x5d\x12\x8f\x46\xf5\x9b\x67\xaf\x09\x21\xb7\x88\xe0\x4c\xee\xb7\xcc\xe2\x18\x76\xa6\x02\xce\x34\x58\x14\xd2\x79\x2b\xb3\xca\x23\x48\x0f\x4c\x8b\x91\xb1\xb0\x36\x42\xe6\x3b\x82\x94\x1e\x2a\x2d\xd0\x06\xd5\x1e\xed\xda\xb5\x76\x7c\xbe\xf9\x0a\xd7\xe8\x1c\x5a\xf8\x8c\x1a\x2d\x53\x70\x57\x65\x4a\x72\xb8\x96\x1c\xb5\x43\x60\x0e\x4a\x3a\x71\x4b\x14\x90\x05\x38\x7a\xf8\x89\x4c\x99\x37\xa6\xc0\x27\x53\x69\xc1\xbc\x34\x7a\x08\x28\xc9\x72\xd8\xa0\x75\xd2\x68\xf8\xa5\x55\xd5\x00\x0e\xc1\x58\x02\xe9\x33\x4f\x0e\x58\x30\x25\xbd\x1b\x00\xd3\x3b\x50\xcc\xef\x9f\xfe\x44\x40\xf6\x7e\x0b\x90\x3a\xa8\x59\x9a\x12\xc1\x2f\x99\x27\xaf\xb7\x52\x29\xc8\x10\x2a\x87\x79\xa5\x86\x84\x96\x55\x1e\xee\xaf\x16\x5f\x6e\xbf\x2e\x20\xbd\x79\x80\xfb\x74\x36\x4b\x6f\x16\x0f\x17\xb0\x95\x7e\x69\x2a\x0f\xb8\xc1\x1a\x4a\xae\x4b\x25\x51\xc0\x96\x59\xcb\xb4\xdf\x81\xc9\x09\xe1\xb7\xe9\x6c\xf2\x25\xbd\x59\xa4\x1f\xae\xae\xaf\x16\x0f\x60\x2c\x7c\xba\x5a\xdc\x4c\xe7\x73\xf8\x74\x3b\x83\x14\xee\xd2\xd9\xe2\x6a\xf2\xf5\x3a\x9d\xc1\xdd\xd7\xd9\xdd\xed\x7c\x9a\xc0\x1c\xc9\x2a\xa4\xf7\xaf\xc7\x3c\x0f\xd9\xb3\x08\x02\x3d\x93\xca\xb5\x91\x78\x30\x15\xb8\xa5\xa9\x94\x80\x25\xdb\x20\x58\xe4\x28\x37\x28\x80\x01\x37\xe5\xee\xa7\x93\x4a\x58\x4c\x19\x5d\x04\x9f\x5f\x2c\x48\xb8\xca\x41\x1b\x3f\x04\x87\x08\xff\x5c\x7a\x5f\x8e\x47\xa3\xed\x76\x9b\x14\xba\x4a\x8c\x2d\x46\xaa\x86\x73\xa3\x7f\x25\x31\x61\x96\x16\x9d\x67\x1e\x17\x96\x71\xb4\x60\x2a\x5f\x56\xde\x81\xab\xf2\x5c\x72\x89\xda\x83\xd4\xb9\xb1\xeb\x50\x29\xe0\x0d\x70\x8b\xcc\x23\x30\x50\x86\x33\x05\xf8\x88\xbc\x0a\x77\x75\xa4\x43\xb9\x5a\xa6\x1d\xe3\xe1\x34\xb7\x66\x4d\xbe\x56\xce\xd3\x0f\xe7\x70\x9d\x29\x14\x50\xa0\x46\x27\x1d\x64\xca\xf0\x55\x12\x7f\x8b\xa3\x03\x63\xa8\x4e\x82\x87\x8d\x50\xa8\x8d\x2d\xf6\x2c\x42\x56\x49\x25\xa4\x2e\x92\x38\x6a\xa5\xc7\xa0\x2b\xa5\x86\x71\x80\x50\xc6\xac\xaa\x32\xe5\xdc\x54\xc1\xf6\x3f\x91\xfb\x1a\xcc\x95\xc8\x65\x4e\xc5\xc1\xba\x5b\x6f\xc2\x55\xa7\xd7\x64\x24\x9f\xc4\xd1\x11\xcc\x18\xf2\x4a\x07\x77\xfa\x4c\x08\x3b\x04\x91\x0d\xbe\xc5\x51\xb4\x61\x96\xb0\xe0\x12\xbc\xf9\x82\x8f\xe1\x72\x70\x11\x47\x91\xcc\xa1\xef\x97\xd2\x25\x2d\xf0\xef\x8c\xf3\x3f\xe0\xf2\xf2\x32\x34\x75\x2e\x35\x8a\x01\x10\x44\xf4\x9c\x58\x7d\x13\x65\x4c\x31\xcd\x71\x0c\xbd\xf7\x8f\x3d\x78\x0b\x22\x4b\x0a\xf4\x1f\xea\xd3\x5a\x59\xe2\xcd\xdc\x5b\xa9\x8b\xfe\xd9\xaf\x83\x61\x78\xa5\x4d\x78\x03\x8d\xf8\x8d\xe9\x84\xeb\x7b\x6e\x44\xb8\x6e\x6c\xae\xa5\x26\x46\x34\x42\x8d\x94\xf3\xc6\xb2\x02\xc7\xf0\xed\x89\xfe\x3f\x91\x57\x4f\x71\xf4\x74\x14\xe5\x79\x2d\xf4\x42\x94\x1b\x08\x40\xed\x6d\x57\xe7\x85\xa4\x4e\x3d\x4c\x40\xc0\xfb\x51\x12\xe6\xad\x29\x27\x49\x58\xe1\xee\xf5\x4c\xd0\x85\x14\x8f\xdd\xc5\x0a\x77\x83\x8b\xf8\xc5\x14\x25\x8d\xd1\xbf\x4b\xf1\xf8\xb3\xf9\x3a\x79\x73\x14\xd7\x39\x49\xed\xed\x1d\x0c\x4e\xe2\x68\xd1\x55\xca\x53\xb9\x4b\xbd\x31\x2b\x22\xae\x25\xc5\x47\xa9\x10\x12\x53\x52\xb6\x5c\xcd\x1c\x19\xa2\x06\xe9\xd1\x32\xa2\x4e\xb3\x41\x4b\x53\x03\x2c\xfa\xca\x6a\xd7\x85\x31\x97\x9a\xa9\x16\xb8\x89\xba\xb7\x8c\xd7\x3d\x53\x9f\x1f\xc4\x92\xfb\xc7\x10\xc5\xe0\xdd\x68\x04\xa9\x07\x72\x11\x4a\x23\xb5\x1f\xc2\x16\x41\x23\x0a\x6a\x7c\x81\xa2\xe2\x3e\xe0\xf5\x36\x4c\x55\xd8\xab\x9b\x9b\x28\x32\x3c\x35\x15\x4d\x82\x83\xe6\x1f\x06\x03\xd7\x66\x13\x46\x5c\xc6\xf8\x0a\x9a\x86\x33\x56\x16\x52\xc7\x4d\x38\x8f\x9a\x8d\x2c\x4a\x08\x38\x98\x15\x72\x45\x49\xa4\x93\x0f\x4c\xc1\x25\x64\xb2\xb8\xd2\xfe\x24\x79\x75\xd0\xdb\xa7\x83\x3f\x92\xa6\x79\x12\x47\x84\xd7\x3f\x1f\x0c\xe1\xec\xd7\xae\x22\xbc\x21\x28\x78\x1d\xcc\x9b\x97\xa1\xe2\xd3\x62\x78\xfe\x59\x50\x43\x1d\xfc\x36\x68\x4d\x5c\x95\x51\x3a\x6a\x3f\x43\x1c\x8f\xbb\xf8\xe2\x07\xb8\xc7\xbe\xb5\xb8\x4d\x68\x12\x26\xc4\xcb\xa0\x75\x8a\x3e\x22\xb7\xb8\x26\x56\xa7\x2c\x70\xa6\x14\xda\x9e\x83\xc0\x19\xc3\xa6\x9c\x42\xbe\x70\x5d\xfa\x5d\xcb\xf5\x9e\xd9\x02\xbd\x7b\xdd\xb0\x80\xf3\xee\x5d\x4b\x81\x21\x14\xbb\x12\xe1\xf2\x12\x7a\x93\xd9\x34\x5d\x4c\x7b\x4d\x1b\x8d\x46\x70\x8f\x61\x13\xca\x94\xcc\x84\xda\x81\x40\x85\x1e\x6b\xbb\x8c\x0e\x21\xea\x28\x61\x48\x2b\x0d\x2d\x1b\xf8\x28\x9d\x97\xba\x80\x9a\x29\xb6\x34\x57\x1b\xb8\xd0\x23\x9c\x55\x8e\xaa\xf5\x64\x08\x79\x43\x1b\x85\x45\xe2\x15\xe2\xff\xd0\x6e\x4c\xc9\x6e\x03\xc9\xa5\x75\x1e\x4a\xc5\x38\x26\x84\xd7\x19\xf3\x72\x7e\x9b\x4e\x26\xd5\xb3\xd0\x82\x01\x68\x3f\xe0\x98\xa2\x01\x49\xea\x1d\xf4\x5b\x8c\x41\x1c\x45\xb6\x95\x3e\xc0\xbe\xd8\x53\x82\xf3\x58\x1e\x12\x02\x2d\x16\xb8\x41\xa2\xd0\xc0\x06\xf5\x30\x24\x5d\xff\xfe\xad\x99\xbe\xe8\x92\x38\xa2\x77\x07\x7d\xad\x4c\x71\xdc\xd7\xa2\x0e\x0b\xaf\xac\xa5\xfc\x77\x14\x9c\x53\x8f\xff\x59\x39\x4f\x31\xb5\x14\x9e\x86\x2d\x9e\x23\xc9\x40\x89\x34\x6d\x07\xdf\x93\x21\xcd\xad\x30\x27\x48\x5d\x33\xa5\xea\x6d\xae\x34\x1e\xb5\x97\x4c\xa9\x1d\xe5\x61\x6b\x69\x8d\xa1\xc5\x65\x08\x4e\x92\x54\x60\x9c\x20\x2a\x35\x57\x95\xa8\xcb\x20\xd4\x71\x83\xe7\x82\xcd\xc7\xfb\xcf\x1a\x9d\x63\x05\x26\x54\x49\xb9\x7c\x6c\x36\x48\x0d\xbd\x9a\xe4\xfa\x83\x5e\xd2\x19\x79\x4c\x31\xca\x14\x49\x5b\x64\x44\xd3\xa9\x10\x16\x9d\xeb\x0f\x1a\xce\xe9\x32\x7b\xbf\x44\x4d\xc1\x07\x8d\x5b\xe8\x56\x13\xc6\x39\xad\x6a\x62\x08\x4c\x08\xa2\xb6\x93\x35\x22\x8e\x22\xb7\x95\x9e\x2f\x21\x68\x32\xe5\xbe\x17\x07\x4d\xfd\x73\xe6\x10\xde\x4c\xff\xb3\x98\xdc\x7e\x9c\x4e\x6e\xef\x1e\xde\x8c\xe1\xe8\x6c\x7e\xf5\xdf\x69\x77\xf6\x21\xbd\x4e\x6f\x26\xd3\x37\xe3\x30\x9b\x9f\x71\xc8\x9b\xd6\x05\x52\xe8\x3c\xe3\xab\xa4\x44\x5c\xf5\xdf\x1f\xf3\xc0\xde\xc1\x28\xca\x2c\xb2\xd5\xc5\xde\x98\xba\x41\x1b\x1d\x2d\xe5\xc2\x25\xbc\x18\xac\x8b\x97\xad\x99\x34\xf2\xfd\x96\xc8\xf7\xab\x48\xa0\x8a\xd7\xed\x38\xff\xcb\x86\x84\xde\x61\x7c\x35\x06\xc7\x14\x6d\xc0\xf2\x7f\xf4\xe5\x92\xe7\x0e\xfd\x10\x50\x0b\xb3\x25\xe6\xeb\x50\xeb\x9b\x06\xf7\x20\x64\x67\x83\x9a\x41\x6f\xf3\xfe\xa0\x13\x26\xb0\xef\x45\xcf\x9f\x13\x45\x2d\xe0\xb2\x45\x7f\x1b\x5e\xbe\x1e\xa8\xf3\x26\x52\x27\x0a\x7e\x39\xd9\xf0\xc2\xfd\x1a\xd7\xc6\xee\x9a\x71\x74\xe0\xdf\x8f\xa3\x9a\x5e\x5f\x77\xf5\x44\x7f\xa8\xc8\xba\x83\x8f\xd3\xeb\xe9\xe7\x74\x31\x3d\x92\x9a\x2f\xd2\xc5\xd5\xa4\x3e\xfa\xcb\x85\x77\xf6\xd3\x85\xd7\x9b\xcf\x17\xb7\xb3\x69\x6f\xdc\xfc\xbb\xbe\x4d\x3f\xf6\xbe\x53\xd8\x6c\x81\x3f\x6a\x5d\x6f\xee\x8d\x15\x7f\xa7\x03\x0e\x36\xb2\x9c\x3d\xb7\x90\x05\x6a\xe7\xbe\x3a\xf9\xe0\x01\xa6\x5b\x56\xce\xeb\x8f\xbe\x28\xbc\x7f\x96\x87\x9f\xe2\xa7\xf8\xff\x01\x00\x00\xff\xff\xb1\x28\x85\x2a\x8a\x10\x00\x00")
+var _prestate_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xdd\x6f\xdb\x38\x12\x7f\xb6\xfe\x8a\x41\x5f\x6c\x5d\x5d\xb9\xcd\x02\x7b\x80\x73\x39\x40\x75\xdd\x36\x40\x36\x09\x6c\xe7\x72\xb9\xc5\x3e\x50\xe4\x48\xe6\x9a\x26\x05\x92\xb2\xe3\x2b\xf2\xbf\x1f\x86\xfa\xf0\x47\x93\xa6\x7b\x6f\x16\x39\xfc\xcd\xf7\x6f\xc6\xa3\x11\x4c\x4c\xb9\xb3\xb2\x58\x7a\x38\x7b\xff\xe1\xef\xb0\x58\x22\x14\xe6\x1d\xfa\x25\x5a\xac\xd6\x90\x56\x7e\x69\xac\x8b\x46\x23\x58\x2c\xa5\x83\x5c\x2a\x04\xe9\xa0\x64\xd6\x83\xc9\xc1\x9f\xc8\x2b\x99\x59\x66\x77\x49\x34\x1a\xd5\x6f\x9e\xbd\x26\x84\xdc\x22\x82\x33\xb9\xdf\x32\x8b\x63\xd8\x99\x0a\x38\xd3\x60\x51\x48\xe7\xad\xcc\x2a\x8f\x20\x3d\x30\x2d\x46\xc6\xc2\xda\x08\x99\xef\x08\x52\x7a\xa8\xb4\x40\x1b\x54\x7b\xb4\x6b\xd7\xda\xf1\xe5\xfa\x0e\xae\xd0\x39\xb4\xf0\x05\x35\x5a\xa6\xe0\xb6\xca\x94\xe4\x70\x25\x39\x6a\x87\xc0\x1c\x94\x74\xe2\x96\x28\x20\x0b\x70\xf4\xf0\x33\x99\x32\x6f\x4c\x81\xcf\xa6\xd2\x82\x79\x69\xf4\x10\x50\x92\xe5\xb0\x41\xeb\xa4\xd1\xf0\x4b\xab\xaa\x01\x1c\x82\xb1\x04\x32\x60\x9e\x1c\xb0\x60\x4a\x7a\x17\x03\xd3\x3b\x50\xcc\xef\x9f\xfe\x44\x40\xf6\x7e\x0b\x90\x3a\xa8\x59\x9a\x12\xc1\x2f\x99\x27\xaf\xb7\x52\x29\xc8\x10\x2a\x87\x79\xa5\x86\x84\x96\x55\x1e\xee\x2f\x17\x5f\x6f\xee\x16\x90\x5e\x3f\xc0\x7d\x3a\x9b\xa5\xd7\x8b\x87\x73\xd8\x4a\xbf\x34\x95\x07\xdc\x60\x0d\x25\xd7\xa5\x92\x28\x60\xcb\xac\x65\xda\xef\xc0\xe4\x84\xf0\xdb\x74\x36\xf9\x9a\x5e\x2f\xd2\x8f\x97\x57\x97\x8b\x07\x30\x16\x3e\x5f\x2e\xae\xa7\xf3\x39\x7c\xbe\x99\x41\x0a\xb7\xe9\x6c\x71\x39\xb9\xbb\x4a\x67\x70\x7b\x37\xbb\xbd\x99\x4f\x13\x98\x23\x59\x85\xf4\xfe\xf5\x98\xe7\x21\x7b\x16\x41\xa0\x67\x52\xb9\x36\x12\x0f\xa6\x02\xb7\x34\x95\x12\xb0\x64\x1b\x04\x8b\x1c\xe5\x06\x05\x30\xe0\xa6\xdc\xfd\x74\x52\x09\x8b\x29\xa3\x8b\xe0\xf3\x8b\x05\x09\x97\x39\x68\xe3\x87\xe0\x10\xe1\x1f\x4b\xef\xcb\xf1\x68\xb4\xdd\x6e\x93\x42\x57\x89\xb1\xc5\x48\xd5\x70\x6e\xf4\xcf\x24\x22\xcc\xd2\xa2\xf3\xcc\xe3\xc2\x32\x8e\x16\x4c\xe5\xcb\xca\x3b\x70\x55\x9e\x4b\x2e\x51\x7b\x90\x3a\x37\x76\x1d\x2a\x05\xbc\x01\x6e\x91\x79\x04\x06\xca\x70\xa6\x00\x1f\x91\x57\xe1\xae\x8e\x74\x28\x57\xcb\xb4\x63\x3c\x9c\xe6\xd6\xac\xc9\xd7\xca\x79\xfa\xe1\x1c\xae\x33\x85\x02\x0a\xd4\xe8\xa4\x83\x4c\x19\xbe\x4a\xa2\x6f\x51\xef\xc0\x18\xaa\x93\xe0\x61\x23\x14\x6a\x63\x8b\x7d\x8b\x90\x55\x52\x09\xa9\x8b\x24\xea\xb5\xd2\x63\xd0\x95\x52\xc3\x28\x40\x28\x63\x56\x55\x99\x72\x6e\xaa\x60\xfb\x9f\xc8\x7d\x0d\xe6\x4a\xe4\x32\xa7\xe2\x60\xdd\xad\x37\xe1\xaa\xd3\x6b\x32\x92\x4f\xa2\xde\x11\xcc\x18\xf2\x4a\x07\x77\x06\x4c\x08\x3b\x04\x91\xc5\xdf\xa2\x5e\x6f\xc3\x2c\x61\xc1\x05\x78\xf3\x15\x1f\xc3\x65\x7c\x1e\xf5\x7a\x32\x87\x81\x5f\x4a\x97\xb4\xc0\xbf\x33\xce\xff\x80\x8b\x8b\x8b\xd0\xd4\xb9\xd4\x28\x62\x20\x88\xde\x73\x62\xf5\x4d\x2f\x63\x8a\x69\x8e\x63\xe8\xbf\x7f\xec\xc3\x5b\x10\x59\x52\xa0\xff\x58\x9f\xd6\xca\x12\x6f\xe6\xde\x4a\x5d\x0c\x3e\xfc\x1a\x0f\xc3\x2b\x6d\xc2\x1b\x68\xc4\xaf\x4d\x27\x5c\xdf\x73\x23\xc2\x75\x63\x73\x2d\x35\x31\xa2\x11\x6a\xa4\x9c\x37\x96\x15\x38\x86\x6f\x4f\xf4\xfd\x44\x5e\x3d\x45\xbd\xa7\xa3\x28\xcf\x6b\xa1\x17\xa2\xdc\x40\x00\x6a\x6f\xbb\x3a\x2f\x24\x75\xea\x61\x02\x02\xde\x8f\x92\x30\x6f\x4d\x39\x49\xc2\x0a\x77\xaf\x67\x82\x2e\xa4\x78\xec\x2e\x56\xb8\x8b\xcf\xa3\x17\x53\x94\x34\x46\xff\x2e\xc5\xe3\xcf\xe6\xeb\xe4\xcd\x51\x5c\xe7\x24\xb5\xb7\x37\x8e\x4f\xe2\x68\xd1\x55\xca\x53\xb9\x4b\xbd\x31\x2b\x22\xae\x25\xc5\x47\xa9\x10\x12\x53\x52\xb6\x5c\xcd\x1c\x19\xa2\x06\xe9\xd1\x32\xa2\x4e\xb3\x41\x4b\x53\x03\x2c\xfa\xca\x6a\xd7\x85\x31\x97\x9a\xa9\x16\xb8\x89\xba\xb7\x8c\xd7\x3d\x53\x9f\x1f\xc4\x92\xfb\xc7\x10\xc5\xe0\xdd\x68\x04\xa9\x07\x72\x11\x4a\x23\xb5\x1f\xc2\x16\x41\x23\x0a\x6a\x7c\x81\xa2\xe2\x3e\xe0\xf5\x37\x4c\x55\xd8\xaf\x9b\x9b\x28\x32\x3c\x35\x15\x4d\x82\x83\xe6\x1f\x06\x03\xd7\x66\x13\x46\x5c\xc6\xf8\x0a\x9a\x86\x33\x56\x16\x52\x47\x4d\x38\x8f\x9a\x8d\x2c\x4a\x08\x38\x98\x15\x72\x45\x49\xa4\x93\x8f\x4c\xc1\x05\x64\xb2\xb8\xd4\xfe\x24\x79\x75\xd0\xdb\xa7\xf1\x1f\x49\xd3\x3c\x89\x23\xc2\x1b\x9c\xc5\x43\xf8\xf0\x6b\x57\x11\xde\x10\x14\xbc\x0e\xe6\xcd\xcb\x50\xd1\x69\x31\x3c\xff\x2c\xa8\xa1\x0e\x7e\x1b\xb4\x26\xae\xca\x28\x1d\xb5\x9f\x21\x8e\xc7\x5d\x7c\xfe\x03\xdc\x63\xdf\x5a\xdc\x26\x34\x09\x13\xe2\x10\x94\x3e\xc3\x77\xc1\xdc\x9d\x43\x01\x6f\x81\xbe\xa4\x26\x55\x4e\xf2\x2f\xcc\xc5\xf0\x37\x68\x24\x6e\xad\xe4\xdf\x59\x52\xe7\xf5\x13\x72\x8b\x6b\x1a\x05\x94\x3a\xce\x94\x42\xdb\x77\x10\x88\x66\xd8\xd4\x60\x48\x32\xae\x4b\xbf\x6b\x07\x84\x67\xb6\x40\xef\x5e\xf7\x26\xe0\xbc\x7b\xd7\xf2\x66\x88\xdf\xae\x44\xb8\xb8\x80\xfe\x64\x36\x4d\x17\xd3\x7e\xd3\x7b\xa3\x11\xdc\x63\x58\x9f\x32\x25\x33\xa1\x76\x20\x50\xa1\xc7\xda\x2e\xa3\x43\x5c\x3b\x1e\x19\xd2\x1e\x44\x1b\x0a\x3e\x4a\xe7\xa5\x2e\xa0\xa6\x97\x2d\x0d\xe3\x06\x2e\x34\x16\x67\x15\x85\xe7\x74\x72\x79\x43\x6b\x88\x45\x22\x23\x1a\x1a\xa1\x47\x99\x92\xdd\xda\x92\x4b\xeb\x3c\x94\x8a\x71\x4c\x08\xaf\x33\xe6\xe5\xa2\x68\xda\x9f\x54\xcf\x42\xdf\x06\xa0\xfd\x54\x64\x8a\xa6\x2a\xa9\x77\x30\x68\x31\xe2\xa8\xd7\xb3\xad\xf4\x01\xf6\xf9\x9e\x47\x9c\xc7\xf2\x90\x45\x68\x1b\xc1\x0d\x12\xef\x06\x0a\xa9\x27\x28\xe9\xfa\xd7\x6f\xcd\xc8\x46\x97\x44\x3d\x7a\x77\x40\x06\xca\x14\xc7\x64\x20\xea\xb0\xf0\xca\x5a\xca\x7f\xc7\xdb\x39\x11\xc3\x9f\x95\xf3\x14\x53\x4b\xe1\x69\x28\xe6\x39\x66\x0d\x3c\x4a\x23\x3a\xfe\x9e\x41\x69\xd8\x85\xe1\x42\xea\x9a\xd1\x56\xaf\x80\xa5\xf1\xa8\xbd\x64\x4a\xed\x28\x0f\x5b\x4b\xbb\x0f\x6d\x3b\x43\x70\x92\xa4\x02\x4d\x05\x51\xa9\xb9\xaa\x44\x5d\x06\xa1\xf8\x1b\x3c\x17\x6c\x3e\x5e\x9a\xd6\xe8\x1c\x2b\x30\xa1\x4a\xca\xe5\x63\xb3\x76\x6a\xe8\xd7\xcc\x38\x88\xfb\x49\x67\xe4\x31\x2f\x29\x53\x24\x6d\x91\x11\xb7\xa7\x42\x58\x74\x6e\x10\x37\x44\xd5\x65\xf6\x7e\x89\x9a\x82\x0f\x1a\xb7\xd0\xed\x33\x8c\x73\xda\xef\xc4\x10\x98\x10\xc4\x87\x27\xbb\x47\xd4\xeb\xb9\xad\xf4\x7c\x09\x41\x93\x29\xf7\xbd\x18\x37\xf5\xcf\x99\x43\x78\x33\xfd\xf7\x62\x72\xf3\x69\x3a\xb9\xb9\x7d\x78\x33\x86\xa3\xb3\xf9\xe5\x7f\xa6\xdd\xd9\xc7\xf4\x2a\xbd\x9e\x4c\xdf\x8c\xc3\x40\x7f\xc6\x21\x6f\x5a\x17\x48\xa1\xf3\x8c\xaf\x92\x12\x71\x35\x78\x7f\xcc\x03\x7b\x07\x7b\xbd\xcc\x22\x5b\x9d\xef\x8d\xa9\x1b\xb4\xd1\xd1\xf2\x34\x5c\xc0\x8b\xc1\x3a\x7f\xd9\x9a\x49\x23\x3f\x68\xd9\x7f\xbf\xbf\x04\xaa\x78\xdd\x8e\xb3\xbf\x6c\x48\xe8\x1d\xc6\x57\x63\x70\x4c\xd1\xda\x2c\xff\x4b\x7f\x77\xf2\xdc\xa1\x1f\x02\x6a\x61\xb6\xc4\x7c\x1d\x6a\x7d\xd3\xe0\x1e\x84\xec\x43\x5c\xd3\xee\x4d\x3e\x88\x3b\x61\x02\xfb\x5e\xf4\xec\x39\x51\xd4\x02\x2e\x5a\xf4\xb7\xe1\xe5\xeb\x81\x3a\x6b\x22\x75\xa2\xe0\x97\x93\xb5\x30\xdc\xaf\x71\x6d\xec\xae\x99\x61\x07\xfe\xfd\x38\xaa\xe9\xd5\x55\x57\x4f\xf4\x41\x45\xd6\x1d\x7c\x9a\x5e\x4d\xbf\xa4\x8b\xe9\x91\xd4\x7c\x91\x2e\x2e\x27\xf5\xd1\x5f\x2e\xbc\x0f\x3f\x5d\x78\xfd\xf9\x7c\x71\x33\x9b\xf6\xc7\xcd\xd7\xd5\x4d\xfa\xa9\xff\x9d\xc2\x66\x75\xfc\x51\xeb\x7a\x73\x6f\xac\xf8\x7f\x3a\xe0\x60\x8d\xcb\xd9\x73\x5b\x5c\xa0\x76\xee\xab\x93\x7f\x49\xc0\x74\xcb\xca\x79\xfd\x4f\xb1\x17\xde\x3f\xcb\xc3\x4f\xd1\x53\xf4\xbf\x00\x00\x00\xff\xff\x3a\xb7\x37\x41\xbf\x10\x00\x00")
 
 func prestate_tracerJsBytes() ([]byte, error) {
 	return bindataRead(
@@ -213,7 +213,7 @@ func prestate_tracerJs() (*asset, error) {
 	}
 
 	info := bindataFileInfo{name: "prestate_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
-	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe9, 0x79, 0x70, 0x4f, 0xc5, 0x78, 0x57, 0x63, 0x6f, 0x5, 0x31, 0xce, 0x3e, 0x5d, 0xbd, 0x71, 0x4, 0x46, 0x78, 0xcd, 0x1d, 0xcd, 0xb9, 0xd8, 0x10, 0xff, 0xe6, 0xc5, 0x59, 0xb9, 0x25, 0x6e}}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd4, 0x9, 0xf9, 0x44, 0x13, 0x31, 0x89, 0xf7, 0x35, 0x9a, 0xc6, 0xf0, 0x86, 0x9d, 0xb2, 0xe3, 0x57, 0xe2, 0xc0, 0xde, 0xc9, 0x3a, 0x4c, 0x4a, 0x94, 0x90, 0xa5, 0x92, 0x2f, 0xbf, 0xc0, 0xb8}}
 	return a, nil
 }
 
@@ -237,7 +237,7 @@ func trigram_tracerJs() (*asset, error) {
 	return a, nil
 }
 
-var _unigram_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x54\x4d\x6f\xdb\x46\x10\xbd\xeb\x57\xbc\xa3\x8c\xa8\xa4\xd3\x5e\x0a\xa5\x09\xc0\x1a\x76\x22\xc0\x91\x0d\x89\x6e\x60\x14\x3d\x2c\xc9\x21\xb9\xe8\x6a\x87\xd8\x9d\x95\x42\x04\xfa\xef\xc5\x92\xa2\xe5\x1a\x6e\x13\x9e\x04\xcd\xbc\x8f\x79\x33\x64\x9a\xe2\x8a\xbb\xde\xe9\xa6\x15\xfc\x7c\xf9\xf6\x57\xe4\x2d\xa1\xe1\x9f\x48\x5a\x72\x14\x76\xc8\x82\xb4\xec\xfc\x2c\x4d\x91\xb7\xda\xa3\xd6\x86\xa0\x3d\x3a\xe5\x04\x5c\x43\x5e\xf4\x1b\x5d\x38\xe5\xfa\x64\x96\xa6\x23\xe6\xd5\x72\x64\xa8\x1d\x11\x3c\xd7\x72\x50\x8e\x96\xe8\x39\xa0\x54\x16\x8e\x2a\xed\xc5\xe9\x22\x08\x41\x0b\x94\xad\x52\x76\xd8\x71\xa5\xeb\x3e\x52\x6a\x41\xb0\x15\xb9\x41\x5a\xc8\xed\xfc\xe4\xe3\xe3\xfa\x01\xb7\xe4\x3d\x39\x7c\x24\x4b\x4e\x19\xdc\x87\xc2\xe8\x12\xb7\xba\x24\xeb\x09\xca\xa3\x8b\xff\xf8\x96\x2a\x14\x03\x5d\x04\xde\x44\x2b\xdb\x93\x15\xdc\x70\xb0\x95\x12\xcd\x76\x01\xd2\xd1\x39\xf6\xe4\xbc\x66\x8b\x5f\x26\xa9\x13\xe1\x02\xec\x22\xc9\x5c\x49\x1c\xc0\x81\xbb\x88\xbb\x80\xb2\x3d\x8c\x92\x33\xf4\x07\x02\x39\xcf\x5d\x41\xdb\x41\xa6\xe5\x8e\x20\xad\x92\x38\xf5\x41\x1b\x83\x82\x10\x3c\xd5\xc1\x2c\x22\x5b\x11\x04\x5f\x56\xf9\xa7\xbb\x87\x1c\xd9\xfa\x11\x5f\xb2\xcd\x26\x5b\xe7\x8f\xef\x70\xd0\xd2\x72\x10\xd0\x9e\x46\x2a\xbd\xeb\x8c\xa6\x0a\x07\xe5\x9c\xb2\xd2\x83\xeb\xc8\xf0\xf9\x7a\x73\xf5\x29\x5b\xe7\xd9\xef\xab\xdb\x55\xfe\x08\x76\xb8\x59\xe5\xeb\xeb\xed\x16\x37\x77\x1b\x64\xb8\xcf\x36\xf9\xea\xea\xe1\x36\xdb\xe0\xfe\x61\x73\x7f\xb7\xbd\x4e\xb0\xa5\xe8\x8a\x22\xfe\xfb\x99\xd7\xc3\xf6\x1c\xa1\x22\x51\xda\xf8\x29\x89\x47\x0e\xf0\x2d\x07\x53\xa1\x55\x7b\x82\xa3\x92\xf4\x9e\x2a\x28\x94\xdc\xf5\x3f\xbc\xd4\xc8\xa5\x0c\xdb\x66\x98\xf9\x3f\x0f\x12\xab\x1a\x96\x65\x01\x4f\x84\xdf\x5a\x91\x6e\x99\xa6\x87\xc3\x21\x69\x6c\x48\xd8\x35\xa9\x19\xe9\x7c\xfa\x21\x99\xcd\xbe\xcd\x00\x20\x4d\xd1\x6a\x2f\x71\x39\x91\x76\xa7\xba\xe8\x8a\xbb\x92\x2b\xf2\x10\x46\xc9\xc1\x0a\x39\x3f\x74\xc7\xd6\x25\xbe\x1d\x17\x13\xd6\x72\xe7\xc7\x16\x0f\x1b\x76\x05\xb9\x11\x3e\xb6\xc7\xea\x12\x97\x4f\xdd\x5e\xa8\x8b\x4a\xda\xee\xf9\x6f\xaa\x86\xdc\x68\x4f\xae\x3f\x09\x8e\x77\x10\x7d\xfc\xf1\x19\xf4\x95\xca\x20\xe4\x93\x01\x1d\xa1\x4b\xd4\xc1\x96\xf1\xfa\xe6\x86\x9b\x05\xaa\xe2\x02\xe3\x14\xf1\xd9\xab\x78\x9b\x78\x0f\xc3\x4d\xc2\x5d\x22\xbc\x15\xa7\x6d\x33\xbf\x78\xf7\xd4\xa3\x6b\xcc\xa5\xd5\x3e\x89\x83\xfc\xc9\xdd\x5f\x17\x67\x7c\x7c\xfe\x55\x7b\xf3\xe6\x0c\x3c\x3e\xfd\x22\xe3\x09\xff\x83\xc2\x7b\xbc\x7d\x0d\x37\x34\xc5\x40\x26\xda\x73\x88\xb5\x0a\x46\x9e\xe7\x72\x68\x4f\x17\xad\x4a\x09\xca\x9c\xa2\x88\x6f\x27\xd7\x50\x76\x4a\xab\x1e\x6f\x2d\xb2\x0c\x14\xaf\xe6\x73\x5c\xcc\x26\x1d\x47\xfe\x35\x21\x65\xcc\x20\x36\x2d\x7d\x38\xd5\x82\xc8\x42\x0b\x39\x15\xdf\x55\xde\x93\x8b\x9f\x29\x38\x92\xe0\xac\x9f\x18\x23\xac\xd6\x56\x99\x89\xfb\x74\xd1\xe2\x54\xa9\x6d\x33\x7a\x1b\x4b\xcf\xcc\x95\xf2\xf5\xf9\xe2\x74\x3d\x7f\x0a\x07\x1f\x70\xf9\x62\x27\xa3\xe4\x39\xe4\x97\xe1\x1e\x17\xb3\xe3\xec\x9f\x00\x00\x00\xff\xff\x8d\xba\x8d\xa8\xe6\x05\x00\x00")
+var _unigram_tracerJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x94\x41\x6f\xdb\xc6\x13\xc5\xef\xfa\x14\xef\x68\x23\xfa\x8b\xc9\xbf\x97\x42\x69\x0a\xb0\x86\x9d\x08\x70\x64\x43\xa2\x1b\x18\x45\x0f\x4b\x72\x48\x2e\xba\xda\x21\x76\x67\xa5\x08\x81\xbf\x7b\x31\xa4\x68\xb9\x85\xdb\x86\x27\x41\x3b\xef\x37\x6f\xde\x0e\x99\x65\xb8\xe2\xfe\x18\x6c\xdb\x09\xfe\xff\xf6\xdd\x8f\x28\x3a\x42\xcb\xff\x23\xe9\x28\x50\xda\x21\x4f\xd2\x71\x88\xb3\x2c\x43\xd1\xd9\x88\xc6\x3a\x82\x8d\xe8\x4d\x10\x70\x03\xf9\x5b\xbd\xb3\x65\x30\xe1\xb8\x98\x65\xd9\xa8\x79\xf5\x58\x09\x4d\x20\x42\xe4\x46\x0e\x26\xd0\x12\x47\x4e\xa8\x8c\x47\xa0\xda\x46\x09\xb6\x4c\x42\xb0\x02\xe3\xeb\x8c\x03\x76\x5c\xdb\xe6\xa8\x48\x2b\x48\xbe\xa6\x30\xb4\x16\x0a\xbb\x38\xf9\xf8\xb8\x7e\xc0\x2d\xc5\x48\x01\x1f\xc9\x53\x30\x0e\xf7\xa9\x74\xb6\xc2\xad\xad\xc8\x47\x82\x89\xe8\xf5\x9f\xd8\x51\x8d\x72\xc0\xa9\xf0\x46\xad\x6c\x4f\x56\x70\xc3\xc9\xd7\x46\x2c\xfb\x39\xc8\xaa\x73\xec\x29\x44\xcb\x1e\x3f\x4c\xad\x4e\xc0\x39\x38\x28\xe4\xc2\x88\x0e\x10\xc0\xbd\xea\x2e\x61\xfc\x11\xce\xc8\x59\xfa\x1d\x81\x9c\xe7\xae\x61\xfd\xd0\xa6\xe3\x9e\x20\x9d\x11\x9d\xfa\x60\x9d\x43\x49\x48\x91\x9a\xe4\xe6\x4a\x2b\x93\xe0\xcb\xaa\xf8\x74\xf7\x50\x20\x5f\x3f\xe2\x4b\xbe\xd9\xe4\xeb\xe2\xf1\x3d\x0e\x56\x3a\x4e\x02\xda\xd3\x88\xb2\xbb\xde\x59\xaa\x71\x30\x21\x18\x2f\x47\x70\xa3\x84\xcf\xd7\x9b\xab\x4f\xf9\xba\xc8\x7f\x59\xdd\xae\x8a\x47\x70\xc0\xcd\xaa\x58\x5f\x6f\xb7\xb8\xb9\xdb\x20\xc7\x7d\xbe\x29\x56\x57\x0f\xb7\xf9\x06\xf7\x0f\x9b\xfb\xbb\xed\xf5\x02\x5b\x52\x57\xa4\xfa\xff\xce\xbc\x19\x6e\x2f\x10\x6a\x12\x63\x5d\x9c\x92\x78\xe4\x84\xd8\x71\x72\x35\x3a\xb3\x27\x04\xaa\xc8\xee\xa9\x86\x41\xc5\xfd\xf1\xbb\x2f\x55\x59\xc6\xb1\x6f\x87\x99\xff\x71\x21\xb1\x6a\xe0\x59\xe6\x88\x44\xf8\xa9\x13\xe9\x97\x59\x76\x38\x1c\x16\xad\x4f\x0b\x0e\x6d\xe6\x46\x5c\xcc\x7e\x5e\xcc\x66\xdf\x66\x00\x90\x65\xe8\x6c\x14\xbd\x1c\xc5\xee\x4c\xaf\xae\xb8\xaf\xb8\xa6\x08\x61\x54\x9c\xbc\x50\x88\x43\xb5\x96\x2e\xf1\xed\x69\x3e\x69\x3d\xf7\x71\x2c\x89\xf0\x69\x57\x52\x18\xe5\x63\xb9\x9e\x2e\xf1\xf6\xb9\x3a\x0a\xf5\xda\xc9\xfa\x3d\xff\x41\xf5\x90\x1b\xed\x29\x1c\x4f\x0d\xc7\x3d\x50\x1f\xbf\x7e\x06\x7d\xa5\x2a\x09\xc5\xc5\xa0\x56\xe9\x12\x4d\xf2\x95\x6e\xdf\x85\xe3\x76\x8e\xba\xbc\xc4\x38\x85\x3e\x7b\xa3\xbb\x89\x0f\x70\xdc\x2e\xb8\x5f\x08\x6f\x25\x58\xdf\x5e\x5c\xbe\x7f\xae\xb1\x0d\x2e\xa4\xb3\x71\xa1\x83\xfc\xc6\xfd\xef\x97\x67\xbd\x3e\x7f\x39\x7b\xf3\xe6\x2c\x7c\x7a\xfe\x45\x2e\x12\xfe\x45\x85\x0f\x78\xf7\x9a\x6e\x28\xd2\x40\x26\xec\x39\xc4\xc6\x24\x27\x2f\x73\x39\x74\xa7\x8d\x36\x95\x24\xe3\x4e\x51\xe8\xdb\xc9\x0d\x8c\x9f\xd2\x6a\xc6\x5d\x53\xca\x80\x78\x35\x9f\xa7\xf9\x6c\xea\x13\x28\xbe\xd6\xc8\x38\x37\x34\x9b\x2e\x7d\x58\xd5\x92\xc8\xc3\x0a\x05\xa3\xef\x2a\xef\x29\xe8\x67\x0a\x81\x24\x05\x1f\x27\xa2\xca\x1a\xeb\x8d\x9b\xd8\xa7\x8d\x96\x60\x2a\xeb\xdb\xd1\xdb\x78\xf4\xc2\x5c\x25\x5f\x5f\x5e\xdc\xc8\x3c\xa7\xf8\x1c\xcf\xd3\xec\xcf\x00\x00\x00\xff\xff\xf1\x91\x30\xae\xbd\x05\x00\x00")
 
 func unigram_tracerJsBytes() ([]byte, error) {
 	return bindataRead(
@@ -253,7 +253,7 @@ func unigram_tracerJs() (*asset, error) {
 	}
 
 	info := bindataFileInfo{name: "unigram_tracer.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
-	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x2f, 0x36, 0x14, 0xc2, 0xf6, 0xc3, 0x80, 0x2b, 0x4a, 0x11, 0x7d, 0xd5, 0x3e, 0xef, 0x23, 0xb5, 0xd6, 0xe6, 0xe6, 0x5, 0x41, 0xf6, 0x14, 0x7a, 0x39, 0xf7, 0xf8, 0xac, 0x89, 0x8e, 0x43, 0xe6}}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc, 0xe6, 0x5c, 0x88, 0x18, 0xa7, 0x85, 0x61, 0x18, 0xc6, 0xec, 0x17, 0xfc, 0xdf, 0x9d, 0xc0, 0x1b, 0x49, 0xf8, 0x8d, 0xf1, 0xeb, 0x35, 0xf3, 0xd, 0x3e, 0xf6, 0xa3, 0xac, 0x8c, 0xba, 0x74}}
 	return a, nil
 }
 
@@ -348,25 +348,20 @@ func AssetNames() []string {
 
 // _bindata is a table, holding each asset generator, mapped to its name.
 var _bindata = map[string]func() (*asset, error){
-	"4byte_tracer.js": _4byte_tracerJs,
-
-	"bigram_tracer.js": bigram_tracerJs,
-
-	"call_tracer.js": call_tracerJs,
-
-	"evmdis_tracer.js": evmdis_tracerJs,
-
-	"noop_tracer.js": noop_tracerJs,
-
-	"opcount_tracer.js": opcount_tracerJs,
-
+	"4byte_tracer.js":    _4byte_tracerJs,
+	"bigram_tracer.js":   bigram_tracerJs,
+	"call_tracer.js":     call_tracerJs,
+	"evmdis_tracer.js":   evmdis_tracerJs,
+	"noop_tracer.js":     noop_tracerJs,
+	"opcount_tracer.js":  opcount_tracerJs,
 	"prestate_tracer.js": prestate_tracerJs,
-
-	"trigram_tracer.js": trigram_tracerJs,
-
-	"unigram_tracer.js": unigram_tracerJs,
+	"trigram_tracer.js":  trigram_tracerJs,
+	"unigram_tracer.js":  unigram_tracerJs,
 }
 
+// AssetDebug is true if the assets were built with the debug flag enabled.
+const AssetDebug = false
+
 // AssetDir returns the file names below a certain
 // directory embedded in the file by go-bindata.
 // For example if you run go-bindata on data/... and data contains the
diff --git a/eth/tracers/internal/tracers/call_tracer.js b/eth/tracers/internal/tracers/call_tracer.js
index 352c309b49..3ca7377738 100644
--- a/eth/tracers/internal/tracers/call_tracer.js
+++ b/eth/tracers/internal/tracers/call_tracer.js
@@ -61,7 +61,14 @@
 			if (this.callstack[left-1].calls === undefined) {
 				this.callstack[left-1].calls = [];
 			}
-			this.callstack[left-1].calls.push({type: op});
+			this.callstack[left-1].calls.push({
+				type:    op,
+				from:    toHex(log.contract.getAddress()),
+				to:      toHex(toAddress(log.stack.peek(0).toString(16))),
+				gasIn:   log.getGas(),
+				gasCost: log.getCost(),
+				value:   '0x' + db.getBalance(log.contract.getAddress()).toString(16)
+			});
 			return
 		}
 		// If a new method invocation is being done, add to the call stack
diff --git a/eth/tracers/internal/tracers/prestate_tracer.js b/eth/tracers/internal/tracers/prestate_tracer.js
index e0a22bf157..084c04ec46 100644
--- a/eth/tracers/internal/tracers/prestate_tracer.js
+++ b/eth/tracers/internal/tracers/prestate_tracer.js
@@ -55,7 +55,7 @@
 		var toBal   = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16);
 
 		this.prestate[toHex(ctx.to)].balance   = '0x'+toBal.subtract(ctx.value).toString(16);
-		this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).toString(16);
+		this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).add((ctx.gasUsed + ctx.intrinsicGas) * ctx.gasPrice).toString(16);
 
 		// Decrement the caller's nonce, and remove empty create targets
 		this.prestate[toHex(ctx.from)].nonce--;
diff --git a/eth/tracers/internal/tracers/unigram_tracer.js b/eth/tracers/internal/tracers/unigram_tracer.js
index 000fb13b1e..51107d8f3d 100644
--- a/eth/tracers/internal/tracers/unigram_tracer.js
+++ b/eth/tracers/internal/tracers/unigram_tracer.js
@@ -36,8 +36,6 @@
     // result is invoked when all the opcodes have been iterated over and returns
     // the final result of the tracing.
     result: function(ctx) {
-        if(this.nops > 0){
-            return this.hist;
-        }
+        return this.hist;
     },
 }
diff --git a/eth/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json b/eth/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json
index d1c8ac913c..72152e27e7 100644
--- a/eth/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json
+++ b/eth/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json
@@ -65,7 +65,7 @@
         "value": "0x0"
       }
     ],
-    "error": "evm: invalid jump destination",
+    "error": "invalid jump destination",
     "from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8",
     "gas": "0x435c8",
     "gasUsed": "0x435c8",
diff --git a/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json b/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json
index edd80e5b84..7627c8c23d 100644
--- a/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json
+++ b/eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json
@@ -59,7 +59,7 @@
   "result": {
     "calls": [
       {
-        "error": "invalid opcode 0xfe",
+        "error": "invalid opcode: opcode 0xfe not defined",
         "from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
         "gas": "0x75fe3",
         "gasUsed": "0x75fe3",
diff --git a/eth/tracers/testdata/call_tracer_throw.json b/eth/tracers/testdata/call_tracer_throw.json
index d66f1ffa61..09cf449776 100644
--- a/eth/tracers/testdata/call_tracer_throw.json
+++ b/eth/tracers/testdata/call_tracer_throw.json
@@ -50,7 +50,7 @@
   },
   "input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750",
   "result": {
-    "error": "evm: invalid jump destination",
+    "error": "invalid jump destination",
     "from": "0x70c9217d814985faef62b124420f8dfbddd96433",
     "gas": "0x37b38",
     "gasUsed": "0x37b38",
diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go
index a74ed51e1a..ba65925373 100644
--- a/eth/tracers/tracer.go
+++ b/eth/tracers/tracer.go
@@ -27,10 +27,11 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/log"
-	duktape "gopkg.in/olebedev/go-duktape.v3"
+	"gopkg.in/olebedev/go-duktape.v3"
 )
 
 // bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js.
@@ -162,7 +163,7 @@ func (sw *stackWrapper) peek(idx int) *big.Int {
 		log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
 		return new(big.Int)
 	}
-	return sw.stack.Data()[len(sw.stack.Data())-idx-1]
+	return sw.stack.Back(idx).ToBig()
 }
 
 // pushObject assembles a JSVM object wrapping a swappable stack and pushes it
@@ -286,8 +287,6 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) {
 // Tracer provides an implementation of Tracer that evaluates a Javascript
 // function for each VM execution step.
 type Tracer struct {
-	inited bool // Flag whether the context was already inited from the EVM
-
 	vm *duktape.Context // Javascript VM instance
 
 	tracerObject int // Stack index of the tracer JavaScript object
@@ -316,7 +315,7 @@ type Tracer struct {
 // New instantiates a new tracer instance. code specifies a Javascript snippet,
 // which must evaluate to an expression returning an object with 'step', 'fault'
 // and 'result' functions.
-func New(code string) (*Tracer, error) {
+func New(code string, txCtx vm.TxContext) (*Tracer, error) {
 	// Resolve any tracers by name and assemble the tracer object
 	if tracer, ok := tracer(code); ok {
 		code = tracer
@@ -335,6 +334,8 @@ func New(code string) (*Tracer, error) {
 		depthValue:      new(uint),
 		refundValue:     new(uint),
 	}
+	tracer.ctx["gasPrice"] = txCtx.GasPrice
+
 	// Set up builtins for this environment
 	tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
 		ctx.PushString(hexutil.Encode(popSlice(ctx)))
@@ -526,7 +527,7 @@ func wrapError(context string, err error) error {
 }
 
 // CaptureStart implements the Tracer interface to initialize the tracing operation.
-func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
+func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
 	jst.ctx["type"] = "CALL"
 	if create {
 		jst.ctx["type"] = "CREATE"
@@ -537,73 +538,74 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b
 	jst.ctx["gas"] = gas
 	jst.ctx["value"] = value
 
-	return nil
+	// Initialize the context
+	jst.ctx["block"] = env.Context.BlockNumber.Uint64()
+	jst.dbWrapper.db = env.StateDB
+	// Compute intrinsic gas
+	isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
+	isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
+	intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul)
+	if err != nil {
+		return
+	}
+	jst.ctx["intrinsicGas"] = intrinsicGas
 }
 
 // CaptureState implements the Tracer interface to trace a single step of VM execution.
-func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
-	if jst.err == nil {
-		// Initialize the context if it wasn't done yet
-		if !jst.inited {
-			jst.ctx["block"] = env.BlockNumber.Uint64()
-			jst.inited = true
-		}
-		// If tracing was interrupted, set the error and stop
-		if atomic.LoadUint32(&jst.interrupt) > 0 {
-			jst.err = jst.reason
-			return nil
-		}
-		jst.opWrapper.op = op
-		jst.stackWrapper.stack = stack
-		jst.memoryWrapper.memory = memory
-		jst.contractWrapper.contract = contract
-		jst.dbWrapper.db = env.StateDB
-
-		*jst.pcValue = uint(pc)
-		*jst.gasValue = uint(gas)
-		*jst.costValue = uint(cost)
-		*jst.depthValue = uint(depth)
-		*jst.refundValue = uint(env.StateDB.GetRefund())
-
-		jst.errorValue = nil
-		if err != nil {
-			jst.errorValue = new(string)
-			*jst.errorValue = err.Error()
-		}
-		_, err := jst.call("step", "log", "db")
-		if err != nil {
-			jst.err = wrapError("step", err)
-		}
+func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+	if jst.err != nil {
+		return
+	}
+	// If tracing was interrupted, set the error and stop
+	if atomic.LoadUint32(&jst.interrupt) > 0 {
+		jst.err = jst.reason
+		return
+	}
+	jst.opWrapper.op = op
+	jst.stackWrapper.stack = scope.Stack
+	jst.memoryWrapper.memory = scope.Memory
+	jst.contractWrapper.contract = scope.Contract
+
+	*jst.pcValue = uint(pc)
+	*jst.gasValue = uint(gas)
+	*jst.costValue = uint(cost)
+	*jst.depthValue = uint(depth)
+	*jst.refundValue = uint(env.StateDB.GetRefund())
+
+	jst.errorValue = nil
+	if err != nil {
+		jst.errorValue = new(string)
+		*jst.errorValue = err.Error()
+	}
+
+	if _, err := jst.call("step", "log", "db"); err != nil {
+		jst.err = wrapError("step", err)
 	}
-	return nil
 }
 
 // CaptureFault implements the Tracer interface to trace an execution fault
-// while running an opcode.
-func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
-	if jst.err == nil {
-		// Apart from the error, everything matches the previous invocation
-		jst.errorValue = new(string)
-		*jst.errorValue = err.Error()
+func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+	if jst.err != nil {
+		return
+	}
+	// Apart from the error, everything matches the previous invocation
+	jst.errorValue = new(string)
+	*jst.errorValue = err.Error()
 
-		_, err := jst.call("fault", "log", "db")
-		if err != nil {
-			jst.err = wrapError("fault", err)
-		}
+	if _, err := jst.call("fault", "log", "db"); err != nil {
+		jst.err = wrapError("fault", err)
 	}
-	return nil
 }
 
 // CaptureEnd is called after the call finishes to finalize the tracing.
-func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
+func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
 	jst.ctx["output"] = output
-	jst.ctx["gasUsed"] = gasUsed
 	jst.ctx["time"] = t.String()
+	jst.ctx["gasUsed"] = gasUsed
 
 	if err != nil {
 		jst.ctx["error"] = err.Error()
 	}
-	return nil
 }
 
 // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go
index 0f52370675..7cda2e5330 100644
--- a/eth/tracers/tracer_test.go
+++ b/eth/tracers/tracer_test.go
@@ -17,7 +17,6 @@
 package tracers
 
 import (
-	"bytes"
 	"encoding/json"
 	"errors"
 	"math/big"
@@ -48,96 +47,80 @@ type dummyStatedb struct {
 	state.StateDB
 }
 
-func (*dummyStatedb) GetRefund() uint64 { return 1337 }
+func (*dummyStatedb) GetRefund() uint64                       { return 1337 }
+func (*dummyStatedb) GetBalance(addr common.Address) *big.Int { return new(big.Int) }
 
-func runTrace(tracer *Tracer) (json.RawMessage, error) {
-	env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
-
-	contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000)
-	contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
-
-	_, err := env.Interpreter().Run(contract, []byte{}, false)
-	if err != nil {
-		return nil, err
-	}
-	return tracer.GetResult()
-}
-
-// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access
-func TestRegressionPanicSlice(t *testing.T) {
-	tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if _, err = runTrace(tracer); err != nil {
-		t.Fatal(err)
-	}
-}
-
-// TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks
-func TestRegressionPanicPeek(t *testing.T) {
-	tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if _, err = runTrace(tracer); err != nil {
-		t.Fatal(err)
-	}
-}
-
-// TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint
-func TestRegressionPanicGetUint(t *testing.T) {
-	tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if _, err = runTrace(tracer); err != nil {
-		t.Fatal(err)
-	}
+type vmContext struct {
+	blockCtx vm.BlockContext
+	txCtx    vm.TxContext
 }
 
-func TestTracing(t *testing.T) {
-	tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	ret, err := runTrace(tracer)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !bytes.Equal(ret, []byte("3")) {
-		t.Errorf("Expected return value to be 3, got %s", string(ret))
-	}
+func testCtx() *vmContext {
+	return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
 }
 
-func TestStack(t *testing.T) {
-	tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}")
-	if err != nil {
-		t.Fatal(err)
-	}
+func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
+	env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
+	var (
+		startGas uint64 = 10000
+		value           = big.NewInt(0)
+	)
+	contract := vm.NewContract(account{}, account{}, value, startGas)
+	contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
 
-	ret, err := runTrace(tracer)
+	tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
+	ret, err := env.Interpreter().Run(contract, []byte{}, false)
+	tracer.CaptureEnd(ret, startGas-contract.Gas, 1, err)
 	if err != nil {
-		t.Fatal(err)
-	}
-	if !bytes.Equal(ret, []byte("[0,1,2]")) {
-		t.Errorf("Expected return value to be [0,1,2], got %s", string(ret))
+		return nil, err
 	}
+	return tracer.GetResult()
 }
 
-func TestOpcodes(t *testing.T) {
-	tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	ret, err := runTrace(tracer)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !bytes.Equal(ret, []byte("[\"PUSH1\",\"PUSH1\",\"STOP\"]")) {
-		t.Errorf("Expected return value to be [\"PUSH1\",\"PUSH1\",\"STOP\"], got %s", string(ret))
+func TestTracer(t *testing.T) {
+	execTracer := func(code string) []byte {
+		t.Helper()
+		ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
+		tracer, err := New(code, ctx.txCtx)
+		if err != nil {
+			t.Fatal(err)
+		}
+		ret, err := runTrace(tracer, ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return ret
+	}
+	for i, tt := range []struct {
+		code string
+		want string
+	}{
+		{ // tests that we don't panic on bad arguments to memory access
+			code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
+			want: `[{},{},{}]`,
+		}, { // tests that we don't panic on bad arguments to stack peeks
+			code: "{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}",
+			want: `["0","0","0"]`,
+		}, { //  tests that we don't panic on bad arguments to memory getUint
+			code: "{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}",
+			want: `["0","0","0"]`,
+		}, { // tests some general counting
+			code: "{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}",
+			want: `3`,
+		}, { // tests that depth is reported correctly
+			code: "{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}",
+			want: `[0,1,2]`,
+		}, { // tests to-string of opcodes
+			code: "{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}",
+			want: `["PUSH1","PUSH1","STOP"]`,
+		}, { // tests intrinsic gas
+			code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
+			want: `"100000.6.21000"`,
+		},
+	} {
+		if have := execTracer(tt.code); tt.want != string(have) {
+			t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code)
+		}
 	}
 }
 
@@ -145,7 +128,8 @@ func TestHalt(t *testing.T) {
 	t.Skip("duktape doesn't support abortion")
 
 	timeout := errors.New("stahp")
-	tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}")
+	vmctx := testCtx()
+	tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", vmctx.txCtx)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -155,26 +139,67 @@ func TestHalt(t *testing.T) {
 		tracer.Stop(timeout)
 	}()
 
-	if _, err = runTrace(tracer); err.Error() != "stahp    in server-side tracer function 'step'" {
+	if _, err = runTrace(tracer, vmctx); err.Error() != "stahp    in server-side tracer function 'step'" {
 		t.Errorf("Expected timeout error, got %v", err)
 	}
 }
 
 func TestHaltBetweenSteps(t *testing.T) {
-	tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}")
+	vmctx := testCtx()
+	tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", vmctx.txCtx)
 	if err != nil {
 		t.Fatal(err)
 	}
+	env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
+	scope := &vm.ScopeContext{
+		Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
+	}
 
-	env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
-	contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0)
-
-	tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, contract, 0, nil)
+	tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
 	timeout := errors.New("stahp")
 	tracer.Stop(timeout)
-	tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, contract, 0, nil)
+	tracer.CaptureState(env, 0, 0, 0, 0, scope, nil, 0, nil)
 
 	if _, err := tracer.GetResult(); err.Error() != timeout.Error() {
 		t.Errorf("Expected timeout error, got %v", err)
 	}
 }
+
+// TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb
+// in 'result'
+func TestNoStepExec(t *testing.T) {
+	runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
+		env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
+		startGas := uint64(10000)
+		contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas)
+		tracer.CaptureStart(env, contract.Caller(), contract.Address(), false, []byte{}, startGas, big.NewInt(0))
+		tracer.CaptureEnd(nil, startGas-contract.Gas, 1, nil)
+		return tracer.GetResult()
+	}
+	execTracer := func(code string) []byte {
+		t.Helper()
+		ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
+		tracer, err := New(code, ctx.txCtx)
+		if err != nil {
+			t.Fatal(err)
+		}
+		ret, err := runEmptyTrace(tracer, ctx)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return ret
+	}
+	for i, tt := range []struct {
+		code string
+		want string
+	}{
+		{ // tests that we don't panic on accessing the db methods
+			code: "{depths: [], step: function() {}, fault: function() {},  result: function(ctx, db){ return db.getBalance(ctx.to)} }",
+			want: `"0"`,
+		},
+	} {
+		if have := execTracer(tt.code); tt.want != string(have) {
+			t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code)
+		}
+	}
+}
diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go
index 65ecd674b8..9dc4c69631 100644
--- a/eth/tracers/tracers_test.go
+++ b/eth/tracers/tracers_test.go
@@ -143,16 +143,18 @@ func TestPrestateTracerCreate2(t *testing.T) {
 	    result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7
 	*/
 	origin, _ := signer.Sender(tx)
-	context := vm.Context{
+	txContext := vm.TxContext{
+		Origin:   origin,
+		GasPrice: big.NewInt(1),
+	}
+	context := vm.BlockContext{
 		CanTransfer: core.CanTransfer,
 		Transfer:    core.Transfer,
-		Origin:      origin,
 		Coinbase:    common.Address{},
 		BlockNumber: new(big.Int).SetUint64(8000000),
 		Time:        new(big.Int).SetUint64(5),
 		Difficulty:  big.NewInt(0x30000),
 		GasLimit:    uint64(6000000),
-		GasPrice:    big.NewInt(1),
 	}
 	alloc := core.GenesisAlloc{}
 
@@ -168,21 +170,21 @@ func TestPrestateTracerCreate2(t *testing.T) {
 		Code:    []byte{},
 		Balance: big.NewInt(500000000000000),
 	}
-	statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
+	_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
 
 	// Create the tracer, the EVM environment and run it
-	tracer, err := New("prestateTracer")
+	tracer, err := New("prestateTracer", txContext)
 	if err != nil {
 		t.Fatalf("failed to create call tracer: %v", err)
 	}
-	evm := vm.NewEVM(context, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
+	evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Debug: true, Tracer: tracer})
 
 	msg, err := tx.AsMessage(signer)
 	if err != nil {
 		t.Fatalf("failed to prepare transaction for tracing: %v", err)
 	}
 	st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
-	if _, _, _, err = st.TransitionDb(); err != nil {
+	if _, err = st.TransitionDb(); err != nil {
 		t.Fatalf("failed to execute transaction: %v", err)
 	}
 	// Retrieve the trace result and compare against the etalon
@@ -230,33 +232,34 @@ func TestCallTracer(t *testing.T) {
 			}
 			signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number)))
 			origin, _ := signer.Sender(tx)
-
-			context := vm.Context{
+			txContext := vm.TxContext{
+				Origin:   origin,
+				GasPrice: tx.GasPrice(),
+			}
+			context := vm.BlockContext{
 				CanTransfer: core.CanTransfer,
 				Transfer:    core.Transfer,
-				Origin:      origin,
 				Coinbase:    test.Context.Miner,
 				BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)),
 				Time:        new(big.Int).SetUint64(uint64(test.Context.Time)),
 				Difficulty:  (*big.Int)(test.Context.Difficulty),
 				GasLimit:    uint64(test.Context.GasLimit),
-				GasPrice:    tx.GasPrice(),
 			}
-			statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
+			_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
 
 			// Create the tracer, the EVM environment and run it
-			tracer, err := New("callTracer")
+			tracer, err := New("callTracer", txContext)
 			if err != nil {
 				t.Fatalf("failed to create call tracer: %v", err)
 			}
-			evm := vm.NewEVM(context, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
+			evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
 
 			msg, err := tx.AsMessage(signer)
 			if err != nil {
 				t.Fatalf("failed to prepare transaction for tracing: %v", err)
 			}
 			st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
-			if _, _, _, err = st.TransitionDb(); err != nil {
+			if _, err = st.TransitionDb(); err != nil {
 				t.Fatalf("failed to execute transaction: %v", err)
 			}
 			// Retrieve the trace result and compare against the etalon
diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go
index 6b7cccbed5..81fbe5b407 100644
--- a/ethclient/ethclient.go
+++ b/ethclient/ethclient.go
@@ -28,7 +28,6 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
@@ -88,6 +87,13 @@ func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Bl
 	return ec.getBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), true)
 }
 
+// BlockNumber returns the most recent block number
+func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) {
+	var result hexutil.Uint64
+	err := ec.c.CallContext(ctx, &result, "eth_blockNumber")
+	return uint64(result), err
+}
+
 type rpcBlock struct {
 	Hash         common.Hash      `json:"hash"`
 	Transactions []rpcTransaction `json:"transactions"`
@@ -320,6 +326,10 @@ func toBlockNumArg(number *big.Int) string {
 	if number == nil {
 		return "latest"
 	}
+	pending := big.NewInt(-1)
+	if number.Cmp(pending) == 0 {
+		return "pending"
+	}
 	return hexutil.EncodeBig(number)
 }
 
@@ -548,7 +558,7 @@ func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64
 // If the transaction was a contract creation use the TransactionReceipt method to get the
 // contract address after the transaction has been mined.
 func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
-	data, err := rlp.EncodeToBytes(tx)
+	data, err := tx.MarshalBinary()
 	if err != nil {
 		return err
 	}
diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go
index 3576d4870e..9fa5bf87a4 100644
--- a/ethclient/ethclient_test.go
+++ b/ethclient/ethclient_test.go
@@ -17,6 +17,7 @@
 package ethclient
 
 import (
+	"bytes"
 	"context"
 	"errors"
 	"fmt"
@@ -33,8 +34,10 @@ import (
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rpc"
 )
 
 // Verify that Client implements the ethereum interfaces.
@@ -97,6 +100,22 @@ func TestToFilterArg(t *testing.T) {
 			},
 			nil,
 		},
+		{
+			"with negative fromBlock and negative toBlock",
+			ethereum.FilterQuery{
+				Addresses: addresses,
+				FromBlock: big.NewInt(-1),
+				ToBlock:   big.NewInt(-1),
+				Topics:    [][]common.Hash{},
+			},
+			map[string]interface{}{
+				"address":   addresses,
+				"fromBlock": "pending",
+				"toBlock":   "pending",
+				"topics":    [][]common.Hash{},
+			},
+			nil,
+		},
 		{
 			"with blockhash",
 			ethereum.FilterQuery{
@@ -171,17 +190,18 @@ var (
 func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
 	// Generate test chain.
 	genesis, blocks := generateTestChain()
-
-	// Start Ethereum service.
-	var ethservice *eth.Ethereum
+	// Create node
 	n, err := node.New(&node.Config{})
-	n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-		config := ð.Config{Genesis: genesis}
-		config.Ethash.PowMode = ethash.ModeFake
-		ethservice, err = eth.New(ctx, config)
-		return ethservice, err
-	})
-
+	if err != nil {
+		t.Fatalf("can't create new node: %v", err)
+	}
+	// Create Ethereum Service
+	config := ðconfig.Config{Genesis: genesis}
+	config.Ethash.PowMode = ethash.ModeFake
+	ethservice, err := eth.New(n, config)
+	if err != nil {
+		t.Fatalf("can't create new ethereum service: %v", err)
+	}
 	// Import the test chain.
 	if err := n.Start(); err != nil {
 		t.Fatalf("can't start test node: %v", err)
@@ -212,12 +232,48 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
 	return genesis, blocks
 }
 
-func TestHeader(t *testing.T) {
+func TestEthClient(t *testing.T) {
 	backend, chain := newTestBackend(t)
 	client, _ := backend.Attach()
-	defer backend.Stop()
+	defer backend.Close()
 	defer client.Close()
 
+	tests := map[string]struct {
+		test func(t *testing.T)
+	}{
+		"TestHeader": {
+			func(t *testing.T) { testHeader(t, chain, client) },
+		},
+		"TestBalanceAt": {
+			func(t *testing.T) { testBalanceAt(t, client) },
+		},
+		"TestTxInBlockInterrupted": {
+			func(t *testing.T) { testTransactionInBlockInterrupted(t, client) },
+		},
+		"TestChainID": {
+			func(t *testing.T) { testChainID(t, client) },
+		},
+		"TestGetBlock": {
+			func(t *testing.T) { testGetBlock(t, client) },
+		},
+		"TestStatusFunctions": {
+			func(t *testing.T) { testStatusFunctions(t, client) },
+		},
+		"TestCallContract": {
+			func(t *testing.T) { testCallContract(t, client) },
+		},
+		"TestAtFunctions": {
+			func(t *testing.T) { testAtFunctions(t, client) },
+		},
+	}
+
+	t.Parallel()
+	for name, tt := range tests {
+		t.Run(name, tt.test)
+	}
+}
+
+func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) {
 	tests := map[string]struct {
 		block   *big.Int
 		want    *types.Header
@@ -232,8 +288,9 @@ func TestHeader(t *testing.T) {
 			want:  chain[1].Header(),
 		},
 		"future_block": {
-			block: big.NewInt(1000000000),
-			want:  nil,
+			block:   big.NewInt(1000000000),
+			want:    nil,
+			wantErr: ethereum.NotFound,
 		},
 	}
 	for name, tt := range tests {
@@ -243,10 +300,10 @@ func TestHeader(t *testing.T) {
 			defer cancel()
 
 			got, err := ec.HeaderByNumber(ctx, tt.block)
-			if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) {
+			if !errors.Is(err, tt.wantErr) {
 				t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr)
 			}
-			if got != nil && got.Number.Sign() == 0 {
+			if got != nil && got.Number != nil && got.Number.Sign() == 0 {
 				got.Number = big.NewInt(0) // hack to make DeepEqual work
 			}
 			if !reflect.DeepEqual(got, tt.want) {
@@ -256,12 +313,7 @@ func TestHeader(t *testing.T) {
 	}
 }
 
-func TestBalanceAt(t *testing.T) {
-	backend, _ := newTestBackend(t)
-	client, _ := backend.Attach()
-	defer backend.Stop()
-	defer client.Close()
-
+func testBalanceAt(t *testing.T, client *rpc.Client) {
 	tests := map[string]struct {
 		account common.Address
 		block   *big.Int
@@ -302,31 +354,32 @@ func TestBalanceAt(t *testing.T) {
 	}
 }
 
-func TestTransactionInBlockInterrupted(t *testing.T) {
-	backend, _ := newTestBackend(t)
-	client, _ := backend.Attach()
-	defer backend.Stop()
-	defer client.Close()
-
+func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) {
 	ec := NewClient(client)
+
+	// Get current block by number
+	block, err := ec.BlockByNumber(context.Background(), nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	// Test tx in block interupted
 	ctx, cancel := context.WithCancel(context.Background())
 	cancel()
-	tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1)
+	tx, err := ec.TransactionInBlock(ctx, block.Hash(), 1)
 	if tx != nil {
 		t.Fatal("transaction should be nil")
 	}
-	if err == nil {
-		t.Fatal("error should not be nil")
+	if err == nil || err == ethereum.NotFound {
+		t.Fatal("error should not be nil/notfound")
+	}
+	// Test tx in block not found
+	if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != ethereum.NotFound {
+		t.Fatal("error should be ethereum.NotFound")
 	}
 }
 
-func TestChainID(t *testing.T) {
-	backend, _ := newTestBackend(t)
-	client, _ := backend.Attach()
-	defer backend.Stop()
-	defer client.Close()
+func testChainID(t *testing.T, client *rpc.Client) {
 	ec := NewClient(client)
-
 	id, err := ec.ChainID(context.Background())
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
@@ -335,3 +388,188 @@ func TestChainID(t *testing.T) {
 		t.Fatalf("ChainID returned wrong number: %+v", id)
 	}
 }
+
+func testGetBlock(t *testing.T, client *rpc.Client) {
+	ec := NewClient(client)
+	// Get current block number
+	blockNumber, err := ec.BlockNumber(context.Background())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if blockNumber != 1 {
+		t.Fatalf("BlockNumber returned wrong number: %d", blockNumber)
+	}
+	// Get current block by number
+	block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if block.NumberU64() != blockNumber {
+		t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64())
+	}
+	// Get current block by hash
+	blockH, err := ec.BlockByHash(context.Background(), block.Hash())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if block.Hash() != blockH.Hash() {
+		t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex())
+	}
+	// Get header by number
+	header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if block.Header().Hash() != header.Hash() {
+		t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex())
+	}
+	// Get header by hash
+	headerH, err := ec.HeaderByHash(context.Background(), block.Hash())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if block.Header().Hash() != headerH.Hash() {
+		t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex())
+	}
+}
+
+func testStatusFunctions(t *testing.T, client *rpc.Client) {
+	ec := NewClient(client)
+
+	// Sync progress
+	progress, err := ec.SyncProgress(context.Background())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if progress != nil {
+		t.Fatalf("unexpected progress: %v", progress)
+	}
+	// NetworkID
+	networkID, err := ec.NetworkID(context.Background())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if networkID.Cmp(big.NewInt(0)) != 0 {
+		t.Fatalf("unexpected networkID: %v", networkID)
+	}
+	// SuggestGasPrice (should suggest 1 Gwei)
+	gasPrice, err := ec.SuggestGasPrice(context.Background())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if gasPrice.Cmp(big.NewInt(1000000000)) != 0 {
+		t.Fatalf("unexpected gas price: %v", gasPrice)
+	}
+}
+
+func testCallContract(t *testing.T, client *rpc.Client) {
+	ec := NewClient(client)
+
+	// EstimateGas
+	msg := ethereum.CallMsg{
+		From:     testAddr,
+		To:       &common.Address{},
+		Gas:      21000,
+		GasPrice: big.NewInt(1),
+		Value:    big.NewInt(1),
+	}
+	gas, err := ec.EstimateGas(context.Background(), msg)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if gas != 21000 {
+		t.Fatalf("unexpected gas price: %v", gas)
+	}
+	// CallContract
+	if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	// PendingCallCOntract
+	if _, err := ec.PendingCallContract(context.Background(), msg); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+}
+
+func testAtFunctions(t *testing.T, client *rpc.Client) {
+	ec := NewClient(client)
+	// send a transaction for some interesting pending status
+	sendTransaction(ec)
+	time.Sleep(100 * time.Millisecond)
+	// Check pending transaction count
+	pending, err := ec.PendingTransactionCount(context.Background())
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if pending != 1 {
+		t.Fatalf("unexpected pending, wanted 1 got: %v", pending)
+	}
+	// Query balance
+	balance, err := ec.BalanceAt(context.Background(), testAddr, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if balance.Cmp(penBalance) == 0 {
+		t.Fatalf("unexpected balance: %v %v", balance, penBalance)
+	}
+	// NonceAt
+	nonce, err := ec.NonceAt(context.Background(), testAddr, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	penNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if penNonce != nonce+1 {
+		t.Fatalf("unexpected nonce: %v %v", nonce, penNonce)
+	}
+	// StorageAt
+	storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{})
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if !bytes.Equal(storage, penStorage) {
+		t.Fatalf("unexpected storage: %v %v", storage, penStorage)
+	}
+	// CodeAt
+	code, err := ec.CodeAt(context.Background(), testAddr, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	penCode, err := ec.PendingCodeAt(context.Background(), testAddr)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if !bytes.Equal(code, penCode) {
+		t.Fatalf("unexpected code: %v %v", code, penCode)
+	}
+}
+
+func sendTransaction(ec *Client) error {
+	// Retrieve chainID
+	chainID, err := ec.ChainID(context.Background())
+	if err != nil {
+		return err
+	}
+	// Create transaction
+	tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
+	signer := types.LatestSignerForChainID(chainID)
+	signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
+	if err != nil {
+		return err
+	}
+	signedTx, err := tx.WithSignature(signer, signature)
+	if err != nil {
+		return err
+	}
+	// Send transaction
+	return ec.SendTransaction(context.Background(), signedTx)
+}
diff --git a/ethclient/signer.go b/ethclient/signer.go
index 74a93f1e2f..9de020b352 100644
--- a/ethclient/signer.go
+++ b/ethclient/signer.go
@@ -51,6 +51,9 @@ func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error)
 	return s.addr, nil
 }
 
+func (s *senderFromServer) ChainID() *big.Int {
+	panic("can't sign with senderFromServer")
+}
 func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash {
 	panic("can't sign with senderFromServer")
 }
diff --git a/ethdb/batch.go b/ethdb/batch.go
index e261415bff..1353693318 100644
--- a/ethdb/batch.go
+++ b/ethdb/batch.go
@@ -44,3 +44,28 @@ type Batcher interface {
 	// until a final write is called.
 	NewBatch() Batch
 }
+
+// HookedBatch wraps an arbitrary batch where each operation may be hooked into
+// to monitor from black box code.
+type HookedBatch struct {
+	Batch
+
+	OnPut    func(key []byte, value []byte) // Callback if a key is inserted
+	OnDelete func(key []byte)               // Callback if a key is deleted
+}
+
+// Put inserts the given value into the key-value data store.
+func (b HookedBatch) Put(key []byte, value []byte) error {
+	if b.OnPut != nil {
+		b.OnPut(key, value)
+	}
+	return b.Batch.Put(key, value)
+}
+
+// Delete removes the key from the key-value data store.
+func (b HookedBatch) Delete(key []byte) error {
+	if b.OnDelete != nil {
+		b.OnDelete(key)
+	}
+	return b.Batch.Delete(key)
+}
diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go
index 7ff52e59f6..5d19cc3577 100644
--- a/ethdb/leveldb/leveldb.go
+++ b/ethdb/leveldb/leveldb.go
@@ -83,25 +83,40 @@ type Database struct {
 
 // New returns a wrapped LevelDB object. The namespace is the prefix that the
 // metrics reporting should use for surfacing internal stats.
-func New(file string, cache int, handles int, namespace string) (*Database, error) {
-	// Ensure we have some minimal caching and file guarantees
-	if cache < minCache {
-		cache = minCache
-	}
-	if handles < minHandles {
-		handles = minHandles
-	}
+func New(file string, cache int, handles int, namespace string, readonly bool) (*Database, error) {
+	return NewCustom(file, namespace, func(options *opt.Options) {
+		// Ensure we have some minimal caching and file guarantees
+		if cache < minCache {
+			cache = minCache
+		}
+		if handles < minHandles {
+			handles = minHandles
+		}
+		// Set default options
+		options.OpenFilesCacheCapacity = handles
+		options.BlockCacheCapacity = cache / 2 * opt.MiB
+		options.WriteBuffer = cache / 4 * opt.MiB // Two of these are used internally
+		if readonly {
+			options.ReadOnly = true
+		}
+	})
+}
+
+// NewCustom returns a wrapped LevelDB object. The namespace is the prefix that the
+// metrics reporting should use for surfacing internal stats.
+// The customize function allows the caller to modify the leveldb options.
+func NewCustom(file string, namespace string, customize func(options *opt.Options)) (*Database, error) {
+	options := configureOptions(customize)
 	logger := log.New("database", file)
-	logger.Info("Allocated cache and file handles", "cache", common.StorageSize(cache*1024*1024), "handles", handles)
+	usedCache := options.GetBlockCacheCapacity() + options.GetWriteBuffer()*2
+	logCtx := []interface{}{"cache", common.StorageSize(usedCache), "handles", options.GetOpenFilesCacheCapacity()}
+	if options.ReadOnly {
+		logCtx = append(logCtx, "readonly", "true")
+	}
+	logger.Info("Allocated cache and file handles", logCtx...)
 
 	// Open the db and recover any potential corruptions
-	db, err := leveldb.OpenFile(file, &opt.Options{
-		OpenFilesCacheCapacity: handles,
-		BlockCacheCapacity:     cache / 2 * opt.MiB,
-		WriteBuffer:            cache / 4 * opt.MiB, // Two of these are used internally
-		Filter:                 filter.NewBloomFilter(10),
-		DisableSeeksCompaction: true,
-	})
+	db, err := leveldb.OpenFile(file, options)
 	if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
 		db, err = leveldb.RecoverFile(file, nil)
 	}
@@ -133,6 +148,20 @@ func New(file string, cache int, handles int, namespace string) (*Database, erro
 	return ldb, nil
 }
 
+// configureOptions sets some default options, then runs the provided setter.
+func configureOptions(customizeFn func(*opt.Options)) *opt.Options {
+	// Set default options
+	options := &opt.Options{
+		Filter:                 filter.NewBloomFilter(10),
+		DisableSeeksCompaction: true,
+	}
+	// Allow caller to make custom modifications to the options
+	if customizeFn != nil {
+		customizeFn(options)
+	}
+	return options
+}
+
 // Close stops the metrics collection, flushes any pending data to disk and closes
 // all io accesses to the underlying key-value store.
 func (db *Database) Close() error {
@@ -248,6 +277,9 @@ func (db *Database) meter(refresh time.Duration) {
 		merr error
 	)
 
+	timer := time.NewTimer(refresh)
+	defer timer.Stop()
+
 	// Iterate ad infinitum and collect the stats
 	for i := 1; errc == nil && merr == nil; i++ {
 		// Retrieve the database stats
@@ -399,7 +431,8 @@ func (db *Database) meter(refresh time.Duration) {
 		select {
 		case errc = <-db.quitChan:
 			// Quit requesting, stop hammering the database
-		case <-time.After(refresh):
+		case <-timer.C:
+			timer.Reset(refresh)
 			// Timeout, gather a new set of stats
 		}
 	}
@@ -428,7 +461,7 @@ func (b *batch) Put(key, value []byte) error {
 // Delete inserts the a key removal into the batch for later committing.
 func (b *batch) Delete(key []byte) error {
 	b.b.Delete(key)
-	b.size++
+	b.size += len(key)
 	return nil
 }
 
diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go
index 4c5e1a84de..fedc9e326c 100644
--- a/ethdb/memorydb/memorydb.go
+++ b/ethdb/memorydb/memorydb.go
@@ -211,7 +211,7 @@ func (b *batch) Put(key, value []byte) error {
 // Delete inserts the a key removal into the batch for later committing.
 func (b *batch) Delete(key []byte) error {
 	b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true})
-	b.size += 1
+	b.size += len(key)
 	return nil
 }
 
diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go
index defca45c89..c7acb9481c 100644
--- a/ethstats/ethstats.go
+++ b/ethstats/ethstats.go
@@ -28,6 +28,7 @@ import (
 	"runtime"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -35,10 +36,13 @@ import (
 	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/downloader"
+	ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/les"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/miner"
+	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/rpc"
 	"github.com/gorilla/websocket"
@@ -56,23 +60,33 @@ const (
 	chainHeadChanSize = 10
 )
 
-type txPool interface {
-	// SubscribeNewTxsEvent should return an event subscription of
-	// NewTxsEvent and send events to the given channel.
-	SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
+// backend encompasses the bare-minimum functionality needed for ethstats reporting
+type backend interface {
+	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
+	SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
+	CurrentHeader() *types.Header
+	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
+	GetTd(ctx context.Context, hash common.Hash) *big.Int
+	Stats() (pending int, queued int)
+	Downloader() *downloader.Downloader
 }
 
-type blockChain interface {
-	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
+// fullNodeBackend encompasses the functionality necessary for a full node
+// reporting to ethstats
+type fullNodeBackend interface {
+	backend
+	Miner() *miner.Miner
+	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
+	CurrentBlock() *types.Block
+	SuggestPrice(ctx context.Context) (*big.Int, error)
 }
 
 // Service implements an Ethereum netstats reporting daemon that pushes local
 // chain statistics up to a monitoring server.
 type Service struct {
-	server *p2p.Server        // Peer-to-peer server to retrieve networking infos
-	eth    *eth.Ethereum      // Full Ethereum service if monitoring a full node
-	les    *les.LightEthereum // Light Ethereum service if monitoring a light node
-	engine consensus.Engine   // Consensus engine to retrieve variadic block fields
+	server  *p2p.Server // Peer-to-peer server to retrieve networking infos
+	backend backend
+	engine  consensus.Engine // Consensus engine to retrieve variadic block fields
 
 	node string // Name of the node to display on the monitoring page
 	pass string // Password to authorize access to the monitoring page
@@ -80,81 +94,104 @@ type Service struct {
 
 	pongCh chan struct{} // Pong notifications are fed into this channel
 	histCh chan []uint64 // History request block numbers are fed into this channel
+
+	headSub event.Subscription
+	txSub   event.Subscription
+}
+
+// connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
+// websocket.
+//
+// From Gorilla websocket docs:
+//   Connections support one concurrent reader and one concurrent writer.
+//   Applications are responsible for ensuring that no more than one goroutine calls the write methods
+//     - NextWriter, SetWriteDeadline, WriteMessage, WriteJSON, EnableWriteCompression, SetCompressionLevel
+//   concurrently and that no more than one goroutine calls the read methods
+//     - NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler
+//   concurrently.
+//   The Close and WriteControl methods can be called concurrently with all other methods.
+type connWrapper struct {
+	conn *websocket.Conn
+
+	rlock sync.Mutex
+	wlock sync.Mutex
+}
+
+func newConnectionWrapper(conn *websocket.Conn) *connWrapper {
+	return &connWrapper{conn: conn}
+}
+
+// WriteJSON wraps corresponding method on the websocket but is safe for concurrent calling
+func (w *connWrapper) WriteJSON(v interface{}) error {
+	w.wlock.Lock()
+	defer w.wlock.Unlock()
+
+	return w.conn.WriteJSON(v)
+}
+
+// ReadJSON wraps corresponding method on the websocket but is safe for concurrent calling
+func (w *connWrapper) ReadJSON(v interface{}) error {
+	w.rlock.Lock()
+	defer w.rlock.Unlock()
+
+	return w.conn.ReadJSON(v)
+}
+
+// Close wraps corresponding method on the websocket but is safe for concurrent calling
+func (w *connWrapper) Close() error {
+	// The Close and WriteControl methods can be called concurrently with all other methods,
+	// so the mutex is not used here
+	return w.conn.Close()
 }
 
 // New returns a monitoring service ready for stats reporting.
-func New(url string, ethServ *eth.Ethereum, lesServ *les.LightEthereum) (*Service, error) {
+func New(node *node.Node, backend backend, engine consensus.Engine, url string) error {
 	// Parse the netstats connection url
 	re := regexp.MustCompile("([^:@]*)(:([^@]*))?@(.+)")
 	parts := re.FindStringSubmatch(url)
 	if len(parts) != 5 {
-		return nil, fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
+		return fmt.Errorf("invalid netstats url: \"%s\", should be nodename:secret@host:port", url)
+	}
+	ethstats := &Service{
+		backend: backend,
+		engine:  engine,
+		server:  node.Server(),
+		node:    parts[1],
+		pass:    parts[3],
+		host:    parts[4],
+		pongCh:  make(chan struct{}),
+		histCh:  make(chan []uint64, 1),
 	}
-	// Assemble and return the stats service
-	var engine consensus.Engine
-	if ethServ != nil {
-		engine = ethServ.Engine()
-	} else {
-		engine = lesServ.Engine()
-	}
-	return &Service{
-		eth:    ethServ,
-		les:    lesServ,
-		engine: engine,
-		node:   parts[1],
-		pass:   parts[3],
-		host:   parts[4],
-		pongCh: make(chan struct{}),
-		histCh: make(chan []uint64, 1),
-	}, nil
-}
-
-// Protocols implements node.Service, returning the P2P network protocols used
-// by the stats service (nil as it doesn't use the devp2p overlay network).
-func (s *Service) Protocols() []p2p.Protocol { return nil }
 
-// APIs implements node.Service, returning the RPC API endpoints provided by the
-// stats service (nil as it doesn't provide any user callable APIs).
-func (s *Service) APIs() []rpc.API { return nil }
+	node.RegisterLifecycle(ethstats)
+	return nil
+}
 
-// Start implements node.Service, starting up the monitoring and reporting daemon.
-func (s *Service) Start(server *p2p.Server) error {
-	s.server = server
-	go s.loop()
+// Start implements node.Lifecycle, starting up the monitoring and reporting daemon.
+func (s *Service) Start() error {
+	// Subscribe to chain events to execute updates on
+	chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
+	s.headSub = s.backend.SubscribeChainHeadEvent(chainHeadCh)
+	txEventCh := make(chan core.NewTxsEvent, txChanSize)
+	s.txSub = s.backend.SubscribeNewTxsEvent(txEventCh)
+	go s.loop(chainHeadCh, txEventCh)
 
 	log.Info("Stats daemon started")
 	return nil
 }
 
-// Stop implements node.Service, terminating the monitoring and reporting daemon.
+// Stop implements node.Lifecycle, terminating the monitoring and reporting daemon.
 func (s *Service) Stop() error {
+	s.headSub.Unsubscribe()
+	s.txSub.Unsubscribe()
 	log.Info("Stats daemon stopped")
 	return nil
 }
 
 // loop keeps trying to connect to the netstats server, reporting chain events
 // until termination.
-func (s *Service) loop() {
-	// Subscribe to chain events to execute updates on
-	var blockchain blockChain
-	var txpool txPool
-	if s.eth != nil {
-		blockchain = s.eth.BlockChain()
-		txpool = s.eth.TxPool()
-	} else {
-		blockchain = s.les.BlockChain()
-		txpool = s.les.TxPool()
-	}
-
-	chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize)
-	headSub := blockchain.SubscribeChainHeadEvent(chainHeadCh)
-	defer headSub.Unsubscribe()
-
-	txEventCh := make(chan core.NewTxsEvent, txChanSize)
-	txSub := txpool.SubscribeNewTxsEvent(txEventCh)
-	defer txSub.Unsubscribe()
-
-	// Start a goroutine that exhausts the subsciptions to avoid events piling up
+func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core.NewTxsEvent) {
+	// Start a goroutine that exhausts the subscriptions to avoid events piling up
 	var (
 		quitCh = make(chan struct{})
 		headCh = make(chan *types.Block, 1)
@@ -186,91 +223,107 @@ func (s *Service) loop() {
 				}
 
 			// node stopped
-			case <-txSub.Err():
+			case <-s.txSub.Err():
 				break HandleLoop
-			case <-headSub.Err():
+			case <-s.headSub.Err():
 				break HandleLoop
 			}
 		}
 		close(quitCh)
 	}()
+
+	// Resolve the URL, defaulting to TLS, but falling back to none too
+	path := fmt.Sprintf("%s/api", s.host)
+	urls := []string{path}
+
+	// url.Parse and url.IsAbs is unsuitable (https://github.com/golang/go/issues/19779)
+	if !strings.Contains(path, "://") {
+		urls = []string{"wss://" + path, "ws://" + path}
+	}
+
+	errTimer := time.NewTimer(0)
+	defer errTimer.Stop()
 	// Loop reporting until termination
 	for {
-		// Resolve the URL, defaulting to TLS, but falling back to none too
-		path := fmt.Sprintf("%s/api", s.host)
-		urls := []string{path}
-
-		// url.Parse and url.IsAbs is unsuitable (https://github.com/golang/go/issues/19779)
-		if !strings.Contains(path, "://") {
-			urls = []string{"wss://" + path, "ws://" + path}
-		}
-		// Establish a websocket connection to the server on any supported URL
-		var (
-			conn *websocket.Conn
-			err  error
-		)
-		dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second}
-		header := make(http.Header)
-		header.Set("origin", "http://localhost")
-		for _, url := range urls {
-			conn, _, err = dialer.Dial(url, header)
-			if err == nil {
-				break
+		select {
+		case <-quitCh:
+			return
+		case <-errTimer.C:
+			// Establish a websocket connection to the server on any supported URL
+			var (
+				conn *connWrapper
+				err  error
+			)
+			dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second}
+			header := make(http.Header)
+			header.Set("origin", "http://localhost")
+			for _, url := range urls {
+				c, _, e := dialer.Dial(url, header)
+				err = e
+				if err == nil {
+					conn = newConnectionWrapper(c)
+					break
+				}
 			}
-		}
-		if err != nil {
-			log.Warn("Stats server unreachable", "err", err)
-			time.Sleep(10 * time.Second)
-			continue
-		}
-		// Authenticate the client with the server
-		if err = s.login(conn); err != nil {
-			log.Warn("Stats login failed", "err", err)
-			conn.Close()
-			time.Sleep(10 * time.Second)
-			continue
-		}
-		go s.readLoop(conn)
-
-		// Send the initial stats so our node looks decent from the get go
-		if err = s.report(conn); err != nil {
-			log.Warn("Initial stats report failed", "err", err)
-			conn.Close()
-			continue
-		}
-		// Keep sending status updates until the connection breaks
-		fullReport := time.NewTicker(15 * time.Second)
-		defer fullReport.Stop()
+			if err != nil {
+				log.Warn("Stats server unreachable", "err", err)
+				errTimer.Reset(10 * time.Second)
+				continue
+			}
+			// Authenticate the client with the server
+			if err = s.login(conn); err != nil {
+				log.Warn("Stats login failed", "err", err)
+				conn.Close()
+				errTimer.Reset(10 * time.Second)
+				continue
+			}
+			go s.readLoop(conn)
 
-		for err == nil {
-			select {
-			case <-quitCh:
+			// Send the initial stats so our node looks decent from the get go
+			if err = s.report(conn); err != nil {
+				log.Warn("Initial stats report failed", "err", err)
 				conn.Close()
-				return
+				errTimer.Reset(0)
+				continue
+			}
+			// Keep sending status updates until the connection breaks
+			fullReport := time.NewTicker(15 * time.Second)
 
-			case <-fullReport.C:
-				if err = s.report(conn); err != nil {
-					log.Warn("Full stats report failed", "err", err)
-				}
-			case list := <-s.histCh:
-				if err = s.reportHistory(conn, list); err != nil {
-					log.Warn("Requested history report failed", "err", err)
-				}
-			case head := <-headCh:
-				if err = s.reportBlock(conn, head); err != nil {
-					log.Warn("Block stats report failed", "err", err)
-				}
-				if err = s.reportPending(conn); err != nil {
-					log.Warn("Post-block transaction stats report failed", "err", err)
-				}
-			case <-txCh:
-				if err = s.reportPending(conn); err != nil {
-					log.Warn("Transaction stats report failed", "err", err)
+			for err == nil {
+				select {
+				case <-quitCh:
+					fullReport.Stop()
+					// Make sure the connection is closed
+					conn.Close()
+					return
+
+				case <-fullReport.C:
+					if err = s.report(conn); err != nil {
+						log.Warn("Full stats report failed", "err", err)
+					}
+				case list := <-s.histCh:
+					if err = s.reportHistory(conn, list); err != nil {
+						log.Warn("Requested history report failed", "err", err)
+					}
+				case head := <-headCh:
+					if err = s.reportBlock(conn, head); err != nil {
+						log.Warn("Block stats report failed", "err", err)
+					}
+					if err = s.reportPending(conn); err != nil {
+						log.Warn("Post-block transaction stats report failed", "err", err)
+					}
+				case <-txCh:
+					if err = s.reportPending(conn); err != nil {
+						log.Warn("Transaction stats report failed", "err", err)
+					}
 				}
 			}
+			fullReport.Stop()
+
+			// Close the current connection and establish a new one
+			conn.Close()
+			errTimer.Reset(0)
 		}
-		// Make sure the connection is closed
-		conn.Close()
 	}
 }
 
@@ -278,14 +331,29 @@ func (s *Service) loop() {
 // from the network socket. If any of them match an active request, it forwards
 // it, if they themselves are requests it initiates a reply, and lastly it drops
 // unknown packets.
-func (s *Service) readLoop(conn *websocket.Conn) {
+func (s *Service) readLoop(conn *connWrapper) {
 	// If the read loop exists, close the connection
 	defer conn.Close()
 
 	for {
 		// Retrieve the next generic network packet and bail out on error
+		var blob json.RawMessage
+		if err := conn.ReadJSON(&blob); err != nil {
+			log.Warn("Failed to retrieve stats server message", "err", err)
+			return
+		}
+		// If the network packet is a system ping, respond to it directly
+		var ping string
+		if err := json.Unmarshal(blob, &ping); err == nil && strings.HasPrefix(ping, "primus::ping::") {
+			if err := conn.WriteJSON(strings.Replace(ping, "ping", "pong", -1)); err != nil {
+				log.Warn("Failed to respond to system ping message", "err", err)
+				return
+			}
+			continue
+		}
+		// Not a system ping, try to decode an actual state message
 		var msg map[string][]interface{}
-		if err := conn.ReadJSON(&msg); err != nil {
+		if err := json.Unmarshal(blob, &msg); err != nil {
 			log.Warn("Failed to decode stats server message", "err", err)
 			return
 		}
@@ -317,8 +385,11 @@ func (s *Service) readLoop(conn *websocket.Conn) {
 			request, ok := msg["emit"][1].(map[string]interface{})
 			if !ok {
 				log.Warn("Invalid stats history request", "msg", msg["emit"][1])
-				s.histCh <- nil
-				continue // Ethstats sometime sends invalid history requests, ignore those
+				select {
+				case s.histCh <- nil: // Treat it as an no indexes request
+				default:
+				}
+				continue
 			}
 			list, ok := request["list"].([]interface{})
 			if !ok {
@@ -346,7 +417,7 @@ func (s *Service) readLoop(conn *websocket.Conn) {
 	}
 }
 
-// nodeInfo is the collection of metainformation about a node that is displayed
+// nodeInfo is the collection of meta information about a node that is displayed
 // on the monitoring page.
 type nodeInfo struct {
 	Name     string `json:"name"`
@@ -369,17 +440,19 @@ type authMsg struct {
 }
 
 // login tries to authorize the client at the remote server.
-func (s *Service) login(conn *websocket.Conn) error {
+func (s *Service) login(conn *connWrapper) error {
 	// Construct and send the login authentication
 	infos := s.server.NodeInfo()
 
-	var network, protocol string
+	var protocols []string
+	for _, proto := range s.server.Protocols {
+		protocols = append(protocols, fmt.Sprintf("%s/%d", proto.Name, proto.Version))
+	}
+	var network string
 	if info := infos.Protocols["eth"]; info != nil {
-		network = fmt.Sprintf("%d", info.(*eth.NodeInfo).Network)
-		protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0])
+		network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network)
 	} else {
 		network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network)
-		protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0])
 	}
 	auth := &authMsg{
 		ID: s.node,
@@ -388,7 +461,7 @@ func (s *Service) login(conn *websocket.Conn) error {
 			Node:     infos.Name,
 			Port:     infos.Ports.Listener,
 			Network:  network,
-			Protocol: protocol,
+			Protocol: strings.Join(protocols, ", "),
 			API:      "No",
 			Os:       runtime.GOOS,
 			OsVer:    runtime.GOARCH,
@@ -414,7 +487,7 @@ func (s *Service) login(conn *websocket.Conn) error {
 // report collects all possible data to report and send it to the stats server.
 // This should only be used on reconnects or rarely to avoid overloading the
 // server. Use the individual methods for reporting subscribed events.
-func (s *Service) report(conn *websocket.Conn) error {
+func (s *Service) report(conn *connWrapper) error {
 	if err := s.reportLatency(conn); err != nil {
 		return err
 	}
@@ -432,7 +505,7 @@ func (s *Service) report(conn *websocket.Conn) error {
 
 // reportLatency sends a ping request to the server, measures the RTT time and
 // finally sends a latency update.
-func (s *Service) reportLatency(conn *websocket.Conn) error {
+func (s *Service) reportLatency(conn *connWrapper) error {
 	// Send the current time to the ethstats server
 	start := time.Now()
 
@@ -501,7 +574,7 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
 }
 
 // reportBlock retrieves the current chain head and reports it to the stats server.
-func (s *Service) reportBlock(conn *websocket.Conn, block *types.Block) error {
+func (s *Service) reportBlock(conn *connWrapper, block *types.Block) error {
 	// Gather the block details from the header or block chain
 	details := s.assembleBlockStats(block)
 
@@ -528,13 +601,15 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
 		txs    []txStats
 		uncles []*types.Header
 	)
-	if s.eth != nil {
-		// Full nodes have all needed information available
+
+	// check if backend is a full node
+	fullBackend, ok := s.backend.(fullNodeBackend)
+	if ok {
 		if block == nil {
-			block = s.eth.BlockChain().CurrentBlock()
+			block = fullBackend.CurrentBlock()
 		}
 		header = block.Header()
-		td = s.eth.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
+		td = fullBackend.GetTd(context.Background(), header.Hash())
 
 		txs = make([]txStats, len(block.Transactions()))
 		for i, tx := range block.Transactions() {
@@ -546,11 +621,12 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
 		if block != nil {
 			header = block.Header()
 		} else {
-			header = s.les.BlockChain().CurrentHeader()
+			header = s.backend.CurrentHeader()
 		}
-		td = s.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
+		td = s.backend.GetTd(context.Background(), header.Hash())
 		txs = []txStats{}
 	}
+
 	// Assemble and return the block stats
 	author, _ := s.engine.Author(header)
 
@@ -573,7 +649,7 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats {
 
 // reportHistory retrieves the most recent batch of blocks and reports it to the
 // stats server.
-func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
+func (s *Service) reportHistory(conn *connWrapper, list []uint64) error {
 	// Figure out the indexes that need reporting
 	indexes := make([]uint64, 0, historyUpdateRange)
 	if len(list) > 0 {
@@ -581,12 +657,7 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
 		indexes = append(indexes, list...)
 	} else {
 		// No indexes requested, send back the top ones
-		var head int64
-		if s.eth != nil {
-			head = s.eth.BlockChain().CurrentHeader().Number.Int64()
-		} else {
-			head = s.les.BlockChain().CurrentHeader().Number.Int64()
-		}
+		head := s.backend.CurrentHeader().Number.Int64()
 		start := head - historyUpdateRange + 1
 		if start < 0 {
 			start = 0
@@ -598,12 +669,13 @@ func (s *Service) reportHistory(conn *websocket.Conn, list []uint64) error {
 	// Gather the batch of blocks to report
 	history := make([]*blockStats, len(indexes))
 	for i, number := range indexes {
+		fullBackend, ok := s.backend.(fullNodeBackend)
 		// Retrieve the next block if it's known to us
 		var block *types.Block
-		if s.eth != nil {
-			block = s.eth.BlockChain().GetBlockByNumber(number)
+		if ok {
+			block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(number)) // TODO ignore error here ?
 		} else {
-			if header := s.les.BlockChain().GetHeaderByNumber(number); header != nil {
+			if header, _ := s.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(number)); header != nil {
 				block = types.NewBlockWithHeader(header)
 			}
 		}
@@ -639,14 +711,9 @@ type pendStats struct {
 
 // reportPending retrieves the current number of pending transactions and reports
 // it to the stats server.
-func (s *Service) reportPending(conn *websocket.Conn) error {
+func (s *Service) reportPending(conn *connWrapper) error {
 	// Retrieve the pending count from the local blockchain
-	var pending int
-	if s.eth != nil {
-		pending, _ = s.eth.TxPool().Stats()
-	} else {
-		pending = s.les.TxPool().Stats()
-	}
+	pending, _ := s.backend.Stats()
 	// Assemble the transaction stats and send it to the server
 	log.Trace("Sending pending transactions to ethstats", "count", pending)
 
@@ -673,9 +740,9 @@ type nodeStats struct {
 	Uptime   int  `json:"uptime"`
 }
 
-// reportPending retrieves various stats about the node at the networking and
+// reportStats retrieves various stats about the node at the networking and
 // mining layer and reports it to the stats server.
-func (s *Service) reportStats(conn *websocket.Conn) error {
+func (s *Service) reportStats(conn *connWrapper) error {
 	// Gather the syncing and mining infos from the local miner instance
 	var (
 		mining   bool
@@ -683,18 +750,20 @@ func (s *Service) reportStats(conn *websocket.Conn) error {
 		syncing  bool
 		gasprice int
 	)
-	if s.eth != nil {
-		mining = s.eth.Miner().Mining()
-		hashrate = int(s.eth.Miner().HashRate())
+	// check if backend is a full node
+	fullBackend, ok := s.backend.(fullNodeBackend)
+	if ok {
+		mining = fullBackend.Miner().Mining()
+		hashrate = int(fullBackend.Miner().Hashrate())
 
-		sync := s.eth.Downloader().Progress()
-		syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
+		sync := fullBackend.Downloader().Progress()
+		syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
 
-		price, _ := s.eth.APIBackend.SuggestPrice(context.Background())
+		price, _ := fullBackend.SuggestPrice(context.Background())
 		gasprice = int(price.Uint64())
 	} else {
-		sync := s.les.Downloader().Progress()
-		syncing = s.les.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock
+		sync := s.backend.Downloader().Progress()
+		syncing = s.backend.CurrentHeader().Number.Uint64() >= sync.HighestBlock
 	}
 	// Assemble the node stats and send it to the server
 	log.Trace("Sending node details to ethstats")
diff --git a/event/event.go b/event/event.go
index 4232787314..ce1b03d523 100644
--- a/event/event.go
+++ b/event/event.go
@@ -104,6 +104,7 @@ func (mux *TypeMux) Post(ev interface{}) error {
 // Stop blocks until all current deliveries have finished.
 func (mux *TypeMux) Stop() {
 	mux.mutex.Lock()
+	defer mux.mutex.Unlock()
 	for _, subs := range mux.subm {
 		for _, sub := range subs {
 			sub.closewait()
@@ -111,11 +112,11 @@ func (mux *TypeMux) Stop() {
 	}
 	mux.subm = nil
 	mux.stopped = true
-	mux.mutex.Unlock()
 }
 
 func (mux *TypeMux) del(s *TypeMuxSubscription) {
 	mux.mutex.Lock()
+	defer mux.mutex.Unlock()
 	for typ, subs := range mux.subm {
 		if pos := find(subs, s); pos >= 0 {
 			if len(subs) == 1 {
@@ -125,7 +126,6 @@ func (mux *TypeMux) del(s *TypeMuxSubscription) {
 			}
 		}
 	}
-	s.mux.mutex.Unlock()
 }
 
 func find(slice []*TypeMuxSubscription, item *TypeMuxSubscription) int {
@@ -196,9 +196,9 @@ func (s *TypeMuxSubscription) closewait() {
 	s.closed = true
 
 	s.postMu.Lock()
+	defer s.postMu.Unlock()
 	close(s.postC)
 	s.postC = nil
-	s.postMu.Unlock()
 }
 
 func (s *TypeMuxSubscription) deliver(event *TypeMuxEvent) {
diff --git a/event/event_test.go b/event/event_test.go
index cc9fa5d7c8..bdad11f13d 100644
--- a/event/event_test.go
+++ b/event/event_test.go
@@ -29,7 +29,7 @@ func TestSubCloseUnsub(t *testing.T) {
 	// the point of this test is **not** to panic
 	var mux TypeMux
 	mux.Stop()
-	sub := mux.Subscribe(int(0))
+	sub := mux.Subscribe(0)
 	sub.Unsubscribe()
 }
 
diff --git a/event/feed_test.go b/event/feed_test.go
index 93badaaee6..cdf29fdd73 100644
--- a/event/feed_test.go
+++ b/event/feed_test.go
@@ -27,8 +27,8 @@ import (
 func TestFeedPanics(t *testing.T) {
 	{
 		var f Feed
-		f.Send(int(2))
-		want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(int(0))}
+		f.Send(2)
+		want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
 		if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil {
 			t.Error(err)
 		}
@@ -37,14 +37,14 @@ func TestFeedPanics(t *testing.T) {
 		var f Feed
 		ch := make(chan int)
 		f.Subscribe(ch)
-		want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(int(0))}
+		want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
 		if err := checkPanic(want, func() { f.Send(uint64(2)) }); err != nil {
 			t.Error(err)
 		}
 	}
 	{
 		var f Feed
-		f.Send(int(2))
+		f.Send(2)
 		want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))}
 		if err := checkPanic(want, func() { f.Subscribe(make(chan uint64)) }); err != nil {
 			t.Error(err)
@@ -58,7 +58,7 @@ func TestFeedPanics(t *testing.T) {
 	}
 	{
 		var f Feed
-		if err := checkPanic(errBadChannel, func() { f.Subscribe(int(0)) }); err != nil {
+		if err := checkPanic(errBadChannel, func() { f.Subscribe(0) }); err != nil {
 			t.Error(err)
 		}
 	}
diff --git a/event/subscription.go b/event/subscription.go
index c80d171f3a..6c62874719 100644
--- a/event/subscription.go
+++ b/event/subscription.go
@@ -95,6 +95,26 @@ func (s *funcSub) Err() <-chan error {
 // Resubscribe applies backoff between calls to fn. The time between calls is adapted
 // based on the error rate, but will never exceed backoffMax.
 func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription {
+	return ResubscribeErr(backoffMax, func(ctx context.Context, _ error) (Subscription, error) {
+		return fn(ctx)
+	})
+}
+
+// A ResubscribeFunc attempts to establish a subscription.
+type ResubscribeFunc func(context.Context) (Subscription, error)
+
+// ResubscribeErr calls fn repeatedly to keep a subscription established. When the
+// subscription is established, ResubscribeErr waits for it to fail and calls fn again. This
+// process repeats until Unsubscribe is called or the active subscription ends
+// successfully.
+//
+// The difference between Resubscribe and ResubscribeErr is that with ResubscribeErr,
+// the error of the failing subscription is available to the callback for logging
+// purposes.
+//
+// ResubscribeErr applies backoff between calls to fn. The time between calls is adapted
+// based on the error rate, but will never exceed backoffMax.
+func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscription {
 	s := &resubscribeSub{
 		waitTime:   backoffMax / 10,
 		backoffMax: backoffMax,
@@ -106,15 +126,18 @@ func Resubscribe(backoffMax time.Duration, fn ResubscribeFunc) Subscription {
 	return s
 }
 
-// A ResubscribeFunc attempts to establish a subscription.
-type ResubscribeFunc func(context.Context) (Subscription, error)
+// A ResubscribeErrFunc attempts to establish a subscription.
+// For every call but the first, the second argument to this function is
+// the error that occurred with the previous subscription.
+type ResubscribeErrFunc func(context.Context, error) (Subscription, error)
 
 type resubscribeSub struct {
-	fn                   ResubscribeFunc
+	fn                   ResubscribeErrFunc
 	err                  chan error
 	unsub                chan struct{}
 	unsubOnce            sync.Once
 	lastTry              mclock.AbsTime
+	lastSubErr           error
 	waitTime, backoffMax time.Duration
 }
 
@@ -149,7 +172,7 @@ func (s *resubscribeSub) subscribe() Subscription {
 		s.lastTry = mclock.Now()
 		ctx, cancel := context.WithCancel(context.Background())
 		go func() {
-			rsub, err := s.fn(ctx)
+			rsub, err := s.fn(ctx, s.lastSubErr)
 			sub = rsub
 			subscribed <- err
 		}()
@@ -178,6 +201,7 @@ func (s *resubscribeSub) waitForError(sub Subscription) bool {
 	defer sub.Unsubscribe()
 	select {
 	case err := <-sub.Err():
+		s.lastSubErr = err
 		return err == nil
 	case <-s.unsub:
 		return true
diff --git a/event/subscription_test.go b/event/subscription_test.go
index c48be3aa30..ba081705c4 100644
--- a/event/subscription_test.go
+++ b/event/subscription_test.go
@@ -19,6 +19,8 @@ package event
 import (
 	"context"
 	"errors"
+	"fmt"
+	"reflect"
 	"testing"
 	"time"
 )
@@ -118,3 +120,37 @@ func TestResubscribeAbort(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+func TestResubscribeWithErrorHandler(t *testing.T) {
+	t.Parallel()
+
+	var i int
+	nfails := 6
+	subErrs := make([]string, 0)
+	sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) {
+		i++
+		var lastErrVal string
+		if lastErr != nil {
+			lastErrVal = lastErr.Error()
+		}
+		subErrs = append(subErrs, lastErrVal)
+		sub := NewSubscription(func(unsubscribed <-chan struct{}) error {
+			if i < nfails {
+				return fmt.Errorf("err-%v", i)
+			} else {
+				return nil
+			}
+		})
+		return sub, nil
+	})
+
+	<-sub.Err()
+	if i != nfails {
+		t.Fatalf("resubscribe function called %d times, want %d times", i, nfails)
+	}
+
+	expectedSubErrs := []string{"", "err-1", "err-2", "err-3", "err-4", "err-5"}
+	if !reflect.DeepEqual(subErrs, expectedSubErrs) {
+		t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs)
+	}
+}
diff --git a/fuzzbuzz.yaml b/fuzzbuzz.yaml
deleted file mode 100644
index 2a4f0c296f..0000000000
--- a/fuzzbuzz.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-# bmt  keystore  rlp  trie  whisperv6
-
-base: ubuntu:16.04
-targets:
-  - name: rlp
-    language: go
-    version: "1.13"
-    corpus: ./fuzzers/rlp/corpus
-    harness:
-      function: Fuzz
-      package: github.com/ethereum/go-ethereum/tests/fuzzers/rlp
-      checkout: github.com/ethereum/go-ethereum/
-  - name: keystore
-    language: go
-    version: "1.13"
-    corpus: ./fuzzers/keystore/corpus
-    harness:
-      function: Fuzz
-      package: github.com/ethereum/go-ethereum/tests/fuzzers/keystore
-      checkout: github.com/ethereum/go-ethereum/
-  - name: trie
-    language: go
-    version: "1.13"
-    corpus: ./fuzzers/trie/corpus
-    harness:
-      function: Fuzz
-      package: github.com/ethereum/go-ethereum/tests/fuzzers/trie
-      checkout: github.com/ethereum/go-ethereum/
-  - name: txfetcher
-    language: go
-    version: "1.13"
-    corpus: ./fuzzers/txfetcher/corpus
-    harness:
-      function: Fuzz
-      package: github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher
-      checkout: github.com/ethereum/go-ethereum/
-  - name: whisperv6
-    language: go
-    version: "1.13"
-    corpus: ./fuzzers/whisperv6/corpus
-    harness:
-      function: Fuzz
-      package: github.com/ethereum/go-ethereum/tests/fuzzers/whisperv6
-      checkout: github.com/ethereum/go-ethereum/
diff --git a/go.mod b/go.mod
index f183a7034a..524078e203 100644
--- a/go.mod
+++ b/go.mod
@@ -7,79 +7,77 @@ require (
 	github.com/Azure/azure-storage-blob-go v0.7.0
 	github.com/Azure/go-autorest/autorest/adal v0.8.0 // indirect
 	github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
-	github.com/VictoriaMetrics/fastcache v1.5.3
+	github.com/VictoriaMetrics/fastcache v1.5.7
 	github.com/VividCortex/gohistogram v1.0.0 // indirect
-	github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847
-	github.com/aws/aws-sdk-go v1.25.48
+	github.com/aws/aws-sdk-go-v2 v1.4.0
+	github.com/aws/aws-sdk-go-v2/config v1.1.7
+	github.com/aws/aws-sdk-go-v2/credentials v1.1.7
+	github.com/aws/aws-sdk-go-v2/service/route53 v1.5.0
 	github.com/btcsuite/btcd v0.20.1-beta
 	github.com/cespare/cp v0.1.0
-	github.com/cespare/xxhash/v2 v2.1.1 // indirect
-	github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9
-	github.com/cosmos/cosmos-sdk v0.25.0 // indirect
+	github.com/cloudflare/cloudflare-go v0.14.0
+	github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
 	github.com/davecgh/go-spew v1.1.1
 	github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
 	github.com/dlclark/regexp2 v1.2.0 // indirect
 	github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
-	github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87
-	github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c
-	github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa
+	github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498
+	github.com/edsrzf/mmap-go v1.0.0
 	github.com/etcd-io/bbolt v1.3.3 // indirect
-	github.com/fatih/color v1.3.0
-	github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc
+	github.com/fatih/color v1.7.0
+	github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
 	github.com/fortytw2/leaktest v1.3.0 // indirect
 	github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
 	github.com/go-ole/go-ole v1.2.1 // indirect
 	github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect
 	github.com/go-stack/stack v1.8.0
-	github.com/golang/protobuf v1.3.2
-	github.com/golang/snappy v0.0.1
-	github.com/google/go-cmp v0.4.0 // indirect
-	github.com/google/gofuzz v1.1.0 // indirect
-	github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989
-	github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277
-	github.com/hashicorp/golang-lru v0.5.3
-	github.com/huin/goupnp v1.0.0
-	github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883
+	github.com/golang/protobuf v1.4.3
+	github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3
+	github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa
+	github.com/google/uuid v1.1.5
+	github.com/gorilla/websocket v1.4.2
+	github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29
+	github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
+	github.com/holiman/bloomfilter/v2 v2.0.3
+	github.com/holiman/uint256 v1.1.1
+	github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88
+	github.com/influxdata/influxdb v1.8.3
 	github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458
+	github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e
 	github.com/jmhodges/levigo v1.0.0 // indirect
 	github.com/julienschmidt/httprouter v1.2.0
 	github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356
 	github.com/kr/pretty v0.2.0 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
-	github.com/mattn/go-colorable v0.1.4
-	github.com/mattn/go-isatty v0.0.10
+	github.com/magiconair/properties v1.8.5 // indirect
+	github.com/mattn/go-colorable v0.1.0
+	github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035
 	github.com/naoina/go-stringutil v0.1.0 // indirect
 	github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
-	github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c
-	github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222
+	github.com/olekukonko/tablewriter v0.0.5
 	github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
 	github.com/prometheus/tsdb v0.7.1
 	github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect
 	github.com/rjeczalik/notify v0.9.1
-	github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d
-	github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00
-	github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 // indirect
-	github.com/spf13/cobra v0.0.6 // indirect
-	github.com/spf13/viper v1.6.2 // indirect
+	github.com/rs/cors v1.7.0
+	github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
 	github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4
-	github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570
-	github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
-	github.com/stretchr/testify v1.4.0
-	github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
+	github.com/stretchr/testify v1.7.0
+	github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954
 	github.com/tendermint/go-amino v0.14.1
 	github.com/tendermint/iavl v0.12.0
 	github.com/tendermint/tendermint v0.31.11
+	github.com/tklauser/go-sysconf v0.3.5 // indirect
 	github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
-	github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208
-	golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
-	golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect
-	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
-	golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
-	golang.org/x/text v0.3.2
-	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
+	go.etcd.io/bbolt v1.3.5 // indirect
+	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
+	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
+	golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988
+	golang.org/x/text v0.3.4
+	golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
 	google.golang.org/grpc v1.27.1 // indirect
 	gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
-	gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9
+	gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6
 	gopkg.in/urfave/cli.v1 v1.20.0
 	gotest.tools v2.2.0+incompatible // indirect
 )
diff --git a/go.sum b/go.sum
index 0d02ee41d9..c22848f2f8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,24 @@
 cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
 github.com/Azure/azure-pipeline-go v0.2.2 h1:6oiIS9yaG6XCCzhgAgKFfIWyo4LLCiDhZot6ltoThhY=
 github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc=
@@ -23,76 +42,75 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
-github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
-github.com/VictoriaMetrics/fastcache v1.5.3 h1:2odJnXLbFZcoV9KYtQ+7TH1UOq3dn3AssMgieaezkR4=
-github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE=
+github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
+github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
 github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
 github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
-github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
 github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
+github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
 github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
-github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A=
-github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk=
-github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
+github.com/aws/aws-sdk-go-v2 v1.4.0 h1:Ryh4fNebT9SwLyCKPSk83dyEZj+KB6KzDyb1gXii7EI=
+github.com/aws/aws-sdk-go-v2 v1.4.0/go.mod h1:tI4KhsR5VkzlUa2DZAdwx7wCAYGwkZZ1H31PYrBFx1w=
+github.com/aws/aws-sdk-go-v2/config v1.1.7 h1:I9AsaodDiw1WbUBn8b4Ktvr2ltPBe7QfLq4UUZY4GsY=
+github.com/aws/aws-sdk-go-v2/config v1.1.7/go.mod h1:6GFyKv06rDCBCIOmWe9vLi7ofCkE7y8aqI6a3tFWNQ0=
+github.com/aws/aws-sdk-go-v2/credentials v1.1.7 h1:hpg13GGT/j7Zv+xkJ+0p2Pj1NvnRSNIX1XI4WI02kq8=
+github.com/aws/aws-sdk-go-v2/credentials v1.1.7/go.mod h1:xYCvIyeVCRC9DmG3Zv/pxlZEEIBYf4fY/jSUVSrr58M=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.7 h1:WdPPgbL9Kl4UrEdiy6IdQioPuXSBg8HJcU9eihiCWOE=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.7/go.mod h1:51hY5nMAiL2EF8ny/pFovWYoKZTcEfOw0WWKcq2E9AQ=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.7 h1:/XUEk2eY/QGzCHSX5LH4vp+UMKTSVVCroUfgkzPGIzE=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.7/go.mod h1:Fl3kxN4ucBIPuAHKHRn+wTGylFzMCEjRfmfHqZuEh8o=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.5.0 h1:pElmeJjj+4a/eyOiY7Fhj9cBTuvQP16FrRHcY4YBmVE=
+github.com/aws/aws-sdk-go-v2/service/route53 v1.5.0/go.mod h1:rc8BFfeKArrCF8I1wmYOF3Bhm+DW/LWVOZEpSG12v2o=
+github.com/aws/aws-sdk-go-v2/service/sso v1.1.6 h1:oS6Jdb6ZwD7U1OqJYqyGbgcU0VJ15E3XbiIIp0clge8=
+github.com/aws/aws-sdk-go-v2/service/sso v1.1.6/go.mod h1:EO4s+JzAllrWKgNjS/Q4mj43vGimSvhWCB6BLognegc=
+github.com/aws/aws-sdk-go-v2/service/sts v1.3.1 h1:tAn9Kvjfq2F2rCeTbMOLtDBQVjLLSIvm21BTUFoswss=
+github.com/aws/aws-sdk-go-v2/service/sts v1.3.1/go.mod h1:QPpnumNNgybBiz3HXGgDRf/QypAMOOsh4+JdOhG5mLU=
+github.com/aws/smithy-go v1.4.0 h1:3rsQpgRe+OoQgJhEwGNpIkosl0fJLdmQqF4gSFRjg+4=
+github.com/aws/smithy-go v1.4.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
+github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
 github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
-github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
 github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
 github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
-github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
 github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
-github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
 github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
-github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0=
 github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
-github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
-github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
 github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
+github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
 github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.0.1-0.20190104013014-3767db7a7e18/go.mod h1:HD5P3vAIAh+Y2GAxg0PrPN1P8WkepXGpjbUPDHJqqKM=
 github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 h1:J82+/8rub3qSy0HxEnoYD8cs+HDlHWYrqYXe2Vqxluk=
-github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U=
-github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cosmos/cosmos-sdk v0.25.0 h1:0Wb0/xObOBNwwfJI9LVRBGXwbk76zlCEwK6PUrx5afM=
-github.com/cosmos/cosmos-sdk v0.25.0/go.mod h1:JrX/JpJunJQXBI5PEX2zELHMFzQr/159jDjIhesOh2c=
+github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu2VvzMZlOFA=
+github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304=
+github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ=
+github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f h1:C43yEtQ6NIf4ftFXD/V55gnGFgPbMQobd//YlnLjUJ8=
+github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
 github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -101,36 +119,37 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X
 github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
+github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
 github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
 github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M=
 github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87 h1:OMbqMXf9OAXzH1dDH82mQMrddBE8LIIwDtxeK4wE1/A=
-github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
-github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM=
-github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY=
-github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=
+github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498 h1:Y9vTBSsV4hSwPSj4bacAU/eSnV3dAxVpepaghAdhGoQ=
+github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
+github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
-github.com/fatih/color v1.3.0 h1:YehCCcyeQ6Km0D6+IapqPinWBK6y+0eB5umvZXK9WPs=
-github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c=
-github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
+github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -140,342 +159,485 @@ github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
 github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
 github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug=
 github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
+github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3 h1:ur2rms48b3Ep1dxh7aUV2FZEQ8jEVO2F6ILKx8ofkAg=
+github.com/golang/snappy v0.0.3-0.20201103224600-674baa8c7fc3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
-github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64=
+github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
+github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989 h1:giknQ4mEuDFmmHSrGcbargOuLHQGtywqo4mheITex54=
-github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277 h1:E0whKxgp2ojts0FDgUA8dl62bmH0LxKanMoBr6MDTDM=
-github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
-github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29 h1:sezaKhEfPFg8W0Enm61B9Gs911H8iesGY5R8NDPtd1M=
+github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
+github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
+github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
+github.com/holiman/uint256 v1.1.1 h1:4JywC80b+/hSfljFlEBLHrrh+CIONLDz9NuFl0af4Mw=
+github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
-github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
-github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150 h1:vlNjIqmUZ9CMAWsbURYl3a6wZbw7q5RHVvlXTNS/Bs8=
+github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88 h1:bcAj8KroPf552TScjFPIakjH2/tdIrIH8F+cc4v4SRo=
+github.com/huin/goupnp v1.0.1-0.20210310174557-0ca763054c88/go.mod h1:nNs7wvRfN1eKaMknBydLNQU6146XQim8t4h+q90biWo=
 github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883 h1:FSeK4fZCo8u40n2JMnyAsd6x7+SbvoOMHvQOU/n10P4=
-github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
+github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY=
+github.com/influxdata/influxdb v1.8.3 h1:WEypI1BQFTT4teLM+1qkEcvUi0dAvopAI/ir0vAiBg8=
+github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI=
+github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk=
+github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE=
+github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8=
+github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE=
+github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0=
+github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po=
 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA=
 github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
-github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0=
+github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U=
+github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
 github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
-github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
 github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
-github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
 github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw=
 github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
-github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE=
 github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
+github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
-github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
+github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
+github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o=
+github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
 github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d h1:oNAwILwmgWKFpuU+dXvI6dl9jG2mAWAZLX3r9s0PPiw=
 github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
-github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 h1:USWjF42jDCSEeikX/G1g40ZWnsPXN5WkZ4jMHZWyBK4=
+github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
-github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
 github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0=
 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
-github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
-github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c h1:1RHs3tNxjXGHeul8z2t6H2N2TlAqpKe5yryJztRx4Jk=
-github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
-github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222 h1:goeTyGkArOZIVOMA0dQbyuPWGNQJZGPwPu/QS9GlpnA=
-github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
-github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=
+github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
+github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
 github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
 github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
-github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d h1:ouzpe+YhpIfnjR40gSkJHWsvXmB6TiPKqMtMpfyU9DE=
-github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00 h1:8DPul/X0IT/1TNMIxoKLwdemEOBBHDC/K4EB16Cw5WE=
-github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
-github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521 h1:3hxavr+IHMsQBrYUPQM5v0CgENFktkkbg1sfpgM3h20=
-github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ=
-github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
+github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
+github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9 h1:5Cp3cVwpQP4aCQ6jx6dNLP3IarbYiuStmIzYu+BjQwY=
-github.com/spaolacci/murmur3 v1.0.1-0.20190317074736-539464a789e9/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
-github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
-github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
-github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
-github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
 github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg=
 github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
-github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE=
-github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw=
-github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM=
-github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
-github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
+github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
 github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk=
 github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso=
 github.com/tendermint/iavl v0.12.0 h1:xcaFAr+ycqCj7WN1RzL2EfcBioRDOHcU1oWcg83K028=
 github.com/tendermint/iavl v0.12.0/go.mod h1:EoKMMv++tDOL5qKKVnoIqtVPshRrEPeJ0WsgDOLAauM=
 github.com/tendermint/tendermint v0.31.11 h1:TIs//4WfEAG4TOZc2eUfJPI3T8KrywXQCCPnGAaM1Wo=
 github.com/tendermint/tendermint v0.31.11/go.mod h1:ymcPyWblXCplCPQjbOYbrF1fWnpslATMVqiGgWbZrlc=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
+github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
+github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
+github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
-github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
-github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk=
-github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
+github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
+github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
+go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
-golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 h1:EjgCl+fVlIaPJSori0ikSz3uV0DOHKWOJFpv1sAAhBM=
+golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
+golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
+gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
+gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
+gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f h1:2wh8dWY8959cBGQvk1RD+/eQBgRYYDaZ+hT0/zsARoA=
+google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
-gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 h1:ITeyKbRetrVzqR3U1eY+ywgp7IBspGd1U/bkwd1gWu4=
-gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
-gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0=
+gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
 gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/graphql/graphql.go b/graphql/graphql.go
index 52db342634..53ae23e488 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -20,6 +20,8 @@ package graphql
 import (
 	"context"
 	"errors"
+	"fmt"
+	"strconv"
 	"time"
 
 	"github.com/ethereum/go-ethereum"
@@ -31,7 +33,6 @@ import (
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/eth/filters"
 	"github.com/ethereum/go-ethereum/internal/ethapi"
-	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
@@ -39,6 +40,37 @@ var (
 	errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash")
 )
 
+type Long int64
+
+// ImplementsGraphQLType returns true if Long implements the provided GraphQL type.
+func (b Long) ImplementsGraphQLType(name string) bool { return name == "Long" }
+
+// UnmarshalGraphQL unmarshals the provided GraphQL query data.
+func (b *Long) UnmarshalGraphQL(input interface{}) error {
+	var err error
+	switch input := input.(type) {
+	case string:
+		// uncomment to support hex values
+		//if strings.HasPrefix(input, "0x") {
+		//	// apply leniency and support hex representations of longs.
+		//	value, err := hexutil.DecodeUint64(input)
+		//	*b = Long(value)
+		//	return err
+		//} else {
+		value, err := strconv.ParseInt(input, 10, 64)
+		*b = Long(value)
+		return err
+		//}
+	case int32:
+		*b = Long(input)
+	case int64:
+		*b = Long(input)
+	default:
+		err = fmt.Errorf("unexpected type %T for Long", input)
+	}
+	return err
+}
+
 // Account represents an Ethereum account at a particular block.
 type Account struct {
 	backend       ethapi.Backend
@@ -77,7 +109,7 @@ func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) {
 	if err != nil {
 		return hexutil.Bytes{}, err
 	}
-	return hexutil.Bytes(state.GetCode(a.address)), nil
+	return state.GetCode(a.address), nil
 }
 
 func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) (common.Hash, error) {
@@ -116,7 +148,21 @@ func (l *Log) Topics(ctx context.Context) []common.Hash {
 }
 
 func (l *Log) Data(ctx context.Context) hexutil.Bytes {
-	return hexutil.Bytes(l.log.Data)
+	return l.log.Data
+}
+
+// AccessTuple represents EIP-2930
+type AccessTuple struct {
+	address     common.Address
+	storageKeys *[]common.Hash
+}
+
+func (at *AccessTuple) Address(ctx context.Context) common.Address {
+	return at.address
+}
+
+func (at *AccessTuple) StorageKeys(ctx context.Context) *[]common.Hash {
+	return at.storageKeys
 }
 
 // Transaction represents an Ethereum transaction.
@@ -157,7 +203,7 @@ func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) {
 	if err != nil || tx == nil {
 		return hexutil.Bytes{}, err
 	}
-	return hexutil.Bytes(tx.Data()), nil
+	return tx.Data(), nil
 }
 
 func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) {
@@ -213,12 +259,8 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account,
 	if err != nil || tx == nil {
 		return nil, err
 	}
-	var signer types.Signer = types.HomesteadSigner{}
-	if tx.Protected() {
-		signer = types.NewEIP155Signer(tx.ChainId())
-	}
+	signer := types.LatestSigner(t.backend.ChainConfig())
 	from, _ := types.Sender(signer, tx)
-
 	return &Account{
 		backend:       t.backend,
 		address:       from,
@@ -259,30 +301,30 @@ func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) {
 	return receipts[t.index], nil
 }
 
-func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) {
+func (t *Transaction) Status(ctx context.Context) (*Long, error) {
 	receipt, err := t.getReceipt(ctx)
 	if err != nil || receipt == nil {
 		return nil, err
 	}
-	ret := hexutil.Uint64(receipt.Status)
+	ret := Long(receipt.Status)
 	return &ret, nil
 }
 
-func (t *Transaction) GasUsed(ctx context.Context) (*hexutil.Uint64, error) {
+func (t *Transaction) GasUsed(ctx context.Context) (*Long, error) {
 	receipt, err := t.getReceipt(ctx)
 	if err != nil || receipt == nil {
 		return nil, err
 	}
-	ret := hexutil.Uint64(receipt.GasUsed)
+	ret := Long(receipt.GasUsed)
 	return &ret, nil
 }
 
-func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, error) {
+func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*Long, error) {
 	receipt, err := t.getReceipt(ctx)
 	if err != nil || receipt == nil {
 		return nil, err
 	}
-	ret := hexutil.Uint64(receipt.CumulativeGasUsed)
+	ret := Long(receipt.CumulativeGasUsed)
 	return &ret, nil
 }
 
@@ -314,6 +356,31 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
 	return &ret, nil
 }
 
+func (t *Transaction) Type(ctx context.Context) (*int32, error) {
+	tx, err := t.resolve(ctx)
+	if err != nil {
+		return nil, err
+	}
+	txType := int32(tx.Type())
+	return &txType, nil
+}
+
+func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) {
+	tx, err := t.resolve(ctx)
+	if err != nil || tx == nil {
+		return nil, err
+	}
+	accessList := tx.AccessList()
+	ret := make([]*AccessTuple, 0, len(accessList))
+	for _, al := range accessList {
+		ret = append(ret, &AccessTuple{
+			address:     al.Address,
+			storageKeys: &al.StorageKeys,
+		})
+	}
+	return &ret, nil
+}
+
 func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) {
 	tx, err := t.resolve(ctx)
 	if err != nil || tx == nil {
@@ -410,18 +477,18 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) {
 		if err != nil {
 			return nil, err
 		}
-		b.receipts = []*types.Receipt(receipts)
+		b.receipts = receipts
 	}
 	return b.receipts, nil
 }
 
-func (b *Block) Number(ctx context.Context) (hexutil.Uint64, error) {
+func (b *Block) Number(ctx context.Context) (Long, error) {
 	header, err := b.resolveHeader(ctx)
 	if err != nil {
 		return 0, err
 	}
 
-	return hexutil.Uint64(header.Number.Uint64()), nil
+	return Long(header.Number.Uint64()), nil
 }
 
 func (b *Block) Hash(ctx context.Context) (common.Hash, error) {
@@ -435,20 +502,20 @@ func (b *Block) Hash(ctx context.Context) (common.Hash, error) {
 	return b.hash, nil
 }
 
-func (b *Block) GasLimit(ctx context.Context) (hexutil.Uint64, error) {
+func (b *Block) GasLimit(ctx context.Context) (Long, error) {
 	header, err := b.resolveHeader(ctx)
 	if err != nil {
 		return 0, err
 	}
-	return hexutil.Uint64(header.GasLimit), nil
+	return Long(header.GasLimit), nil
 }
 
-func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) {
+func (b *Block) GasUsed(ctx context.Context) (Long, error) {
 	header, err := b.resolveHeader(ctx)
 	if err != nil {
 		return 0, err
 	}
-	return hexutil.Uint64(header.GasUsed), nil
+	return Long(header.GasUsed), nil
 }
 
 func (b *Block) Parent(ctx context.Context) (*Block, error) {
@@ -490,7 +557,7 @@ func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) {
 	if err != nil {
 		return hexutil.Bytes{}, err
 	}
-	return hexutil.Bytes(header.Nonce[:]), nil
+	return header.Nonce[:], nil
 }
 
 func (b *Block) MixHash(ctx context.Context) (common.Hash, error) {
@@ -564,7 +631,7 @@ func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) {
 	if err != nil {
 		return hexutil.Bytes{}, err
 	}
-	return hexutil.Bytes(header.Extra), nil
+	return header.Extra, nil
 }
 
 func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) {
@@ -572,7 +639,7 @@ func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) {
 	if err != nil {
 		return hexutil.Bytes{}, err
 	}
-	return hexutil.Bytes(header.Bloom.Bytes()), nil
+	return header.Bloom.Bytes(), nil
 }
 
 func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) {
@@ -584,7 +651,7 @@ func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) {
 		}
 		h = header.Hash()
 	}
-	return hexutil.Big(*b.backend.GetTd(h)), nil
+	return hexutil.Big(*b.backend.GetTd(ctx, h)), nil
 }
 
 // BlockNumberArgs encapsulates arguments to accessors that specify a block number.
@@ -777,20 +844,20 @@ type CallData struct {
 
 // CallResult encapsulates the result of an invocation of the `call` accessor.
 type CallResult struct {
-	data    hexutil.Bytes  // The return data from the call
-	gasUsed hexutil.Uint64 // The amount of gas used
-	status  hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success.
+	data    hexutil.Bytes // The return data from the call
+	gasUsed Long          // The amount of gas used
+	status  Long          // The return status of the call - 0 for failure or 1 for success.
 }
 
 func (c *CallResult) Data() hexutil.Bytes {
 	return c.data
 }
 
-func (c *CallResult) GasUsed() hexutil.Uint64 {
+func (c *CallResult) GasUsed() Long {
 	return c.gasUsed
 }
 
-func (c *CallResult) Status() hexutil.Uint64 {
+func (c *CallResult) Status() Long {
 	return c.status
 }
 
@@ -803,29 +870,33 @@ func (b *Block) Call(ctx context.Context, args struct {
 			return nil, err
 		}
 	}
-	result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
-	status := hexutil.Uint64(1)
-	if failed {
+	result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
+	if err != nil {
+		return nil, err
+	}
+	status := Long(1)
+	if result.Failed() {
 		status = 0
 	}
+
 	return &CallResult{
-		data:    hexutil.Bytes(result),
-		gasUsed: hexutil.Uint64(gas),
+		data:    result.ReturnData,
+		gasUsed: Long(result.UsedGas),
 		status:  status,
-	}, err
+	}, nil
 }
 
 func (b *Block) EstimateGas(ctx context.Context, args struct {
 	Data ethapi.CallArgs
-}) (hexutil.Uint64, error) {
+}) (Long, error) {
 	if b.numberOrHash == nil {
 		_, err := b.resolveHeader(ctx)
 		if err != nil {
-			return hexutil.Uint64(0), err
+			return 0, err
 		}
 	}
 	gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap())
-	return gas, err
+	return Long(gas), err
 }
 
 type Pending struct {
@@ -869,23 +940,28 @@ func (p *Pending) Call(ctx context.Context, args struct {
 	Data ethapi.CallArgs
 }) (*CallResult, error) {
 	pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
-	result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
-	status := hexutil.Uint64(1)
-	if failed {
+	result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
+	if err != nil {
+		return nil, err
+	}
+	status := Long(1)
+	if result.Failed() {
 		status = 0
 	}
+
 	return &CallResult{
-		data:    hexutil.Bytes(result),
-		gasUsed: hexutil.Uint64(gas),
+		data:    result.ReturnData,
+		gasUsed: Long(result.UsedGas),
 		status:  status,
-	}, err
+	}, nil
 }
 
 func (p *Pending) EstimateGas(ctx context.Context, args struct {
 	Data ethapi.CallArgs
-}) (hexutil.Uint64, error) {
+}) (Long, error) {
 	pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
-	return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap())
+	gas, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap())
+	return Long(gas), err
 }
 
 // Resolver is the top-level object in the GraphQL hierarchy.
@@ -894,12 +970,15 @@ type Resolver struct {
 }
 
 func (r *Resolver) Block(ctx context.Context, args struct {
-	Number *hexutil.Uint64
+	Number *Long
 	Hash   *common.Hash
 }) (*Block, error) {
 	var block *Block
 	if args.Number != nil {
-		number := rpc.BlockNumber(uint64(*args.Number))
+		if *args.Number < 0 {
+			return nil, nil
+		}
+		number := rpc.BlockNumber(*args.Number)
 		numberOrHash := rpc.BlockNumberOrHashWithNumber(number)
 		block = &Block{
 			backend:      r.backend,
@@ -931,10 +1010,10 @@ func (r *Resolver) Block(ctx context.Context, args struct {
 }
 
 func (r *Resolver) Blocks(ctx context.Context, args struct {
-	From hexutil.Uint64
-	To   *hexutil.Uint64
+	From *Long
+	To   *Long
 }) ([]*Block, error) {
-	from := rpc.BlockNumber(args.From)
+	from := rpc.BlockNumber(*args.From)
 
 	var to rpc.BlockNumber
 	if args.To != nil {
@@ -977,7 +1056,7 @@ func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Has
 
 func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hexutil.Bytes }) (common.Hash, error) {
 	tx := new(types.Transaction)
-	if err := rlp.DecodeBytes(args.Data, tx); err != nil {
+	if err := tx.UnmarshalBinary(args.Data); err != nil {
 		return common.Hash{}, err
 	}
 	hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx)
@@ -1032,8 +1111,8 @@ func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) {
 	return hexutil.Big(*price), err
 }
 
-func (r *Resolver) ProtocolVersion(ctx context.Context) (int32, error) {
-	return int32(r.backend.ProtocolVersion()), nil
+func (r *Resolver) ChainID(ctx context.Context) (hexutil.Big, error) {
+	return hexutil.Big(*r.backend.ChainConfig().ChainID), nil
 }
 
 // SyncState represents the synchronisation status returned from the `syncing` accessor.
diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go
index 40b13187f4..2404ff45fa 100644
--- a/graphql/graphql_test.go
+++ b/graphql/graphql_test.go
@@ -17,12 +17,340 @@
 package graphql
 
 import (
+	"fmt"
+	"io/ioutil"
+	"math/big"
+	"net/http"
+	"strings"
 	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
+	"github.com/ethereum/go-ethereum/node"
+	"github.com/ethereum/go-ethereum/params"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestBuildSchema(t *testing.T) {
+	ddir, err := ioutil.TempDir("", "graphql-buildschema")
+	if err != nil {
+		t.Fatalf("failed to create temporary datadir: %v", err)
+	}
+	// Copy config
+	conf := node.DefaultConfig
+	conf.DataDir = ddir
+	stack, err := node.New(&conf)
+	if err != nil {
+		t.Fatalf("could not create new node: %v", err)
+	}
 	// Make sure the schema can be parsed and matched up to the object model.
-	if _, err := newHandler(nil); err != nil {
+	if err := newHandler(stack, nil, []string{}, []string{}); err != nil {
 		t.Errorf("Could not construct GraphQL handler: %v", err)
 	}
 }
+
+// Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint
+func TestGraphQLBlockSerialization(t *testing.T) {
+	stack := createNode(t, true, false)
+	defer stack.Close()
+	// start node
+	if err := stack.Start(); err != nil {
+		t.Fatalf("could not start node: %v", err)
+	}
+
+	for i, tt := range []struct {
+		body string
+		want string
+		code int
+	}{
+		{ // Should return latest block
+			body: `{"query": "{block{number}}","variables": null}`,
+			want: `{"data":{"block":{"number":10}}}`,
+			code: 200,
+		},
+		{ // Should return info about latest block
+			body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":{"number":10,"gasUsed":0,"gasLimit":11500000}}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:-1){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":null}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:-500){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":null}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:\"-33\"){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":null}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:\"1337\"){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"data":{"block":null}}`,
+			code: 200,
+		},
+		{
+			body: `{"query": "{block(number:\"0xbad\"){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0xbad\": invalid syntax"}],"data":{}}`,
+			code: 400,
+		},
+		{ // hex strings are currently not supported. If that's added to the spec, this test will need to change
+			body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`,
+			code: 400,
+		},
+		{
+			body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`,
+			want: `{"errors":[{"message":"strconv.ParseInt: parsing \"a\": invalid syntax"}],"data":{}}`,
+			code: 400,
+		},
+		{
+			body: `{"query": "{bleh{number}}","variables": null}"`,
+			want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`,
+			code: 400,
+		},
+		// should return `estimateGas` as decimal
+		{
+			body: `{"query": "{block{ estimateGas(data:{}) }}"}`,
+			want: `{"data":{"block":{"estimateGas":53000}}}`,
+			code: 200,
+		},
+		// should return `status` as decimal
+		{
+			body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`,
+			want: `{"data":{"block":{"number":10,"call":{"data":"0x","status":1}}}}`,
+			code: 200,
+		},
+	} {
+		resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body))
+		if err != nil {
+			t.Fatalf("could not post: %v", err)
+		}
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			t.Fatalf("could not read from response body: %v", err)
+		}
+		if have := string(bodyBytes); have != tt.want {
+			t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want)
+		}
+		if tt.code != resp.StatusCode {
+			t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code)
+		}
+	}
+}
+
+func TestGraphQLBlockSerializationEIP2718(t *testing.T) {
+	stack := createNode(t, true, true)
+	defer stack.Close()
+	// start node
+	if err := stack.Start(); err != nil {
+		t.Fatalf("could not start node: %v", err)
+	}
+
+	for i, tt := range []struct {
+		body string
+		want string
+		code int
+	}{
+		{
+			body: `{"query": "{block {number transactions { from { address } to { address } value hash type accessList { address storageKeys } index}}}"}`,
+			want: `{"data":{"block":{"number":1,"transactions":[{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x64","hash":"0x4f7b8d718145233dcf7f29e34a969c63dd4de8715c054ea2af022b66c4f4633e","type":0,"accessList":[],"index":0},{"from":{"address":"0x71562b71999873db5b286df957af199ec94617f7"},"to":{"address":"0x0000000000000000000000000000000000000dad"},"value":"0x32","hash":"0x9c6c2c045b618fe87add0e49ba3ca00659076ecae00fd51de3ba5d4ccf9dbf40","type":1,"accessList":[{"address":"0x0000000000000000000000000000000000000dad","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000"]}],"index":1}]}}}`,
+			code: 200,
+		},
+	} {
+		resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body))
+		if err != nil {
+			t.Fatalf("could not post: %v", err)
+		}
+		bodyBytes, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			t.Fatalf("could not read from response body: %v", err)
+		}
+		if have := string(bodyBytes); have != tt.want {
+			t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want)
+		}
+		if tt.code != resp.StatusCode {
+			t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code)
+		}
+	}
+}
+
+// Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint
+func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
+	stack := createNode(t, false, false)
+	defer stack.Close()
+	if err := stack.Start(); err != nil {
+		t.Fatalf("could not start node: %v", err)
+	}
+	body := strings.NewReader(`{"query": "{block{number}}","variables": null}`)
+	resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", body)
+	if err != nil {
+		t.Fatalf("could not post: %v", err)
+	}
+	// make sure the request is not handled successfully
+	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+}
+
+func createNode(t *testing.T, gqlEnabled bool, txEnabled bool) *node.Node {
+	stack, err := node.New(&node.Config{
+		HTTPHost: "127.0.0.1",
+		HTTPPort: 0,
+		WSHost:   "127.0.0.1",
+		WSPort:   0,
+	})
+	if err != nil {
+		t.Fatalf("could not create node: %v", err)
+	}
+	if !gqlEnabled {
+		return stack
+	}
+	if !txEnabled {
+		createGQLService(t, stack)
+	} else {
+		createGQLServiceWithTransactions(t, stack)
+	}
+	return stack
+}
+
+func createGQLService(t *testing.T, stack *node.Node) {
+	// create backend
+	ethConf := ðconfig.Config{
+		Genesis: &core.Genesis{
+			Config:     params.AllEthashProtocolChanges,
+			GasLimit:   11500000,
+			Difficulty: big.NewInt(1048576),
+		},
+		Ethash: ethash.Config{
+			PowMode: ethash.ModeFake,
+		},
+		NetworkId:               1337,
+		TrieCleanCache:          5,
+		TrieCleanCacheJournal:   "triecache",
+		TrieCleanCacheRejournal: 60 * time.Minute,
+		TrieDirtyCache:          5,
+		TrieTimeout:             60 * time.Minute,
+		SnapshotCache:           5,
+	}
+	ethBackend, err := eth.New(stack, ethConf)
+	if err != nil {
+		t.Fatalf("could not create eth backend: %v", err)
+	}
+	// Create some blocks and import them
+	chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(),
+		ethash.NewFaker(), ethBackend.ChainDb(), 10, func(i int, gen *core.BlockGen) {})
+	_, err = ethBackend.BlockChain().InsertChain(chain)
+	if err != nil {
+		t.Fatalf("could not create import blocks: %v", err)
+	}
+	// create gql service
+	err = New(stack, ethBackend.APIBackend, []string{}, []string{})
+	if err != nil {
+		t.Fatalf("could not create graphql service: %v", err)
+	}
+}
+
+func createGQLServiceWithTransactions(t *testing.T, stack *node.Node) {
+	// create backend
+	key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+	address := crypto.PubkeyToAddress(key.PublicKey)
+	funds := big.NewInt(1000000000)
+	dad := common.HexToAddress("0x0000000000000000000000000000000000000dad")
+
+	ethConf := ðconfig.Config{
+		Genesis: &core.Genesis{
+			Config:     params.AllEthashProtocolChanges,
+			GasLimit:   11500000,
+			Difficulty: big.NewInt(1048576),
+			Alloc: core.GenesisAlloc{
+				address: {Balance: funds},
+				// The address 0xdad sloads 0x00 and 0x01
+				dad: {
+					Code: []byte{
+						byte(vm.PC),
+						byte(vm.PC),
+						byte(vm.SLOAD),
+						byte(vm.SLOAD),
+					},
+					Nonce:   0,
+					Balance: big.NewInt(0),
+				},
+			},
+		},
+		Ethash: ethash.Config{
+			PowMode: ethash.ModeFake,
+		},
+		NetworkId:               1337,
+		TrieCleanCache:          5,
+		TrieCleanCacheJournal:   "triecache",
+		TrieCleanCacheRejournal: 60 * time.Minute,
+		TrieDirtyCache:          5,
+		TrieTimeout:             60 * time.Minute,
+		SnapshotCache:           5,
+	}
+
+	ethBackend, err := eth.New(stack, ethConf)
+	if err != nil {
+		t.Fatalf("could not create eth backend: %v", err)
+	}
+	signer := types.LatestSigner(ethConf.Genesis.Config)
+
+	legacyTx, _ := types.SignNewTx(key, signer, &types.LegacyTx{
+		Nonce:    uint64(0),
+		To:       &dad,
+		Value:    big.NewInt(100),
+		Gas:      50000,
+		GasPrice: big.NewInt(1),
+	})
+	envelopTx, _ := types.SignNewTx(key, signer, &types.AccessListTx{
+		ChainID:  ethConf.Genesis.Config.ChainID,
+		Nonce:    uint64(1),
+		To:       &dad,
+		Gas:      30000,
+		GasPrice: big.NewInt(1),
+		Value:    big.NewInt(50),
+		AccessList: types.AccessList{{
+			Address:     dad,
+			StorageKeys: []common.Hash{{0}},
+		}},
+	})
+
+	// Create some blocks and import them
+	chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(),
+		ethash.NewFaker(), ethBackend.ChainDb(), 1, func(i int, b *core.BlockGen) {
+			b.SetCoinbase(common.Address{1})
+			b.AddTx(legacyTx)
+			b.AddTx(envelopTx)
+		})
+
+	_, err = ethBackend.BlockChain().InsertChain(chain)
+	if err != nil {
+		t.Fatalf("could not create import blocks: %v", err)
+	}
+	// create gql service
+	err = New(stack, ethBackend.APIBackend, []string{}, []string{})
+	if err != nil {
+		t.Fatalf("could not create graphql service: %v", err)
+	}
+}
diff --git a/graphql/schema.go b/graphql/schema.go
index 5dec10db20..d792bb9153 100644
--- a/graphql/schema.go
+++ b/graphql/schema.go
@@ -69,6 +69,12 @@ const schema string = `
         transaction: Transaction!
     }
 
+    #EIP-2718 
+    type AccessTuple{
+        address: Address!
+        storageKeys : [Bytes32!]
+    }
+
     # Transaction is an Ethereum transaction.
     type Transaction {
         # Hash is the hash of this transaction.
@@ -118,6 +124,9 @@ const schema string = `
         r: BigInt!
         s: BigInt!
         v: BigInt!
+        #Envelope transaction support
+        type: Int
+        accessList: [AccessTuple!]
     }
 
     # BlockFilterCriteria encapsulates log filter criteria for a filter applied
@@ -300,7 +309,7 @@ const schema string = `
         block(number: Long, hash: Bytes32): Block
         # Blocks returns all the blocks between two numbers, inclusive. If
         # to is not supplied, it defaults to the most recent known block.
-        blocks(from: Long!, to: Long): [Block!]!
+        blocks(from: Long, to: Long): [Block!]!
         # Pending returns the current pending state.
         pending: Pending!
         # Transaction returns a transaction specified by its hash.
@@ -310,10 +319,10 @@ const schema string = `
         # GasPrice returns the node's estimate of a gas price sufficient to
         # ensure a transaction is mined in a timely fashion.
         gasPrice: BigInt!
-        # ProtocolVersion returns the current wire protocol version number.
-        protocolVersion: Int!
         # Syncing returns information on the current synchronisation state.
         syncing: SyncState
+        # ChainID returns the current chain ID for transaction replay protection.
+        chainID: BigInt!
     }
 
     type Mutation {
diff --git a/graphql/service.go b/graphql/service.go
index a206053024..bcb0a4990d 100644
--- a/graphql/service.go
+++ b/graphql/service.go
@@ -17,99 +17,68 @@
 package graphql
 
 import (
-	"fmt"
-	"net"
+	"encoding/json"
 	"net/http"
 
 	"github.com/ethereum/go-ethereum/internal/ethapi"
-	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/node"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rpc"
 	"github.com/graph-gophers/graphql-go"
-	"github.com/graph-gophers/graphql-go/relay"
 )
 
-// Service encapsulates a GraphQL service.
-type Service struct {
-	endpoint string           // The host:port endpoint for this service.
-	cors     []string         // Allowed CORS domains
-	vhosts   []string         // Recognised vhosts
-	timeouts rpc.HTTPTimeouts // Timeout settings for HTTP requests.
-	backend  ethapi.Backend   // The backend that queries will operate on.
-	handler  http.Handler     // The `http.Handler` used to answer queries.
-	listener net.Listener     // The listening socket.
+type handler struct {
+	Schema *graphql.Schema
 }
 
-// New constructs a new GraphQL service instance.
-func New(backend ethapi.Backend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) {
-	return &Service{
-		endpoint: endpoint,
-		cors:     cors,
-		vhosts:   vhosts,
-		timeouts: timeouts,
-		backend:  backend,
-	}, nil
-}
-
-// Protocols returns the list of protocols exported by this service.
-func (s *Service) Protocols() []p2p.Protocol { return nil }
-
-// APIs returns the list of APIs exported by this service.
-func (s *Service) APIs() []rpc.API { return nil }
+func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	var params struct {
+		Query         string                 `json:"query"`
+		OperationName string                 `json:"operationName"`
+		Variables     map[string]interface{} `json:"variables"`
+	}
+	if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
 
-// Start is called after all services have been constructed and the networking
-// layer was also initialized to spawn any goroutines required by the service.
-func (s *Service) Start(server *p2p.Server) error {
-	var err error
-	s.handler, err = newHandler(s.backend)
+	response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables)
+	responseJSON, err := json.Marshal(response)
 	if err != nil {
-		return err
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
 	}
-	if s.listener, err = net.Listen("tcp", s.endpoint); err != nil {
-		return err
+	if len(response.Errors) > 0 {
+		w.WriteHeader(http.StatusBadRequest)
 	}
-	// create handler stack and wrap the graphql handler
-	handler := node.NewHTTPHandlerStack(s.handler, s.cors, s.vhosts)
-	// make sure timeout values are meaningful
-	node.CheckTimeouts(&s.timeouts)
-	// create http server
-	httpSrv := &http.Server{
-		Handler:      handler,
-		ReadTimeout:  s.timeouts.ReadTimeout,
-		WriteTimeout: s.timeouts.WriteTimeout,
-		IdleTimeout:  s.timeouts.IdleTimeout,
+
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(responseJSON)
+
+}
+
+// New constructs a new GraphQL service instance.
+func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error {
+	if backend == nil {
+		panic("missing backend")
 	}
-	go httpSrv.Serve(s.listener)
-	log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.endpoint))
-	return nil
+	// check if http server with given endpoint exists and enable graphQL on it
+	return newHandler(stack, backend, cors, vhosts)
 }
 
 // newHandler returns a new `http.Handler` that will answer GraphQL queries.
 // It additionally exports an interactive query browser on the / endpoint.
-func newHandler(backend ethapi.Backend) (http.Handler, error) {
+func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error {
 	q := Resolver{backend}
 
 	s, err := graphql.ParseSchema(schema, &q)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	h := &relay.Handler{Schema: s}
+	h := handler{Schema: s}
+	handler := node.NewHTTPHandlerStack(h, cors, vhosts)
 
-	mux := http.NewServeMux()
-	mux.Handle("/", GraphiQL{})
-	mux.Handle("/graphql", h)
-	mux.Handle("/graphql/", h)
-	return mux, nil
-}
+	stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{})
+	stack.RegisterHandler("GraphQL", "/graphql", handler)
+	stack.RegisterHandler("GraphQL", "/graphql/", handler)
 
-// Stop terminates all goroutines belonging to the service, blocking until they
-// are all terminated.
-func (s *Service) Stop() error {
-	if s.listener != nil {
-		s.listener.Close()
-		s.listener = nil
-		log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%s", s.endpoint))
-	}
 	return nil
 }
diff --git a/interfaces.go b/interfaces.go
index 1ff31f96b6..afcdc17e58 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -119,6 +119,8 @@ type CallMsg struct {
 	GasPrice *big.Int        // wei <-> gas exchange ratio
 	Value    *big.Int        // amount of wei sent along with the call
 	Data     []byte          // input data, usually an ABI-encoded contract method invocation
+
+	AccessList types.AccessList // EIP-2930 access list.
 }
 
 // A ContractCaller provides contract calls, essentially transactions that are executed by
diff --git a/internal/build/archive.go b/internal/build/archive.go
index a00258d999..8b3ac23d1d 100644
--- a/internal/build/archive.go
+++ b/internal/build/archive.go
@@ -184,24 +184,35 @@ func (a *TarballArchive) Close() error {
 	return a.file.Close()
 }
 
-func ExtractTarballArchive(archive string, dest string) error {
-	// We're only interested in gzipped archives, wrap the reader now
+// ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory.
+func ExtractArchive(archive string, dest string) error {
 	ar, err := os.Open(archive)
 	if err != nil {
 		return err
 	}
 	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)
 	if err != nil {
 		return err
 	}
 	defer gzr.Close()
 
-	// Iterate over all the files in the tarball
 	tr := tar.NewReader(gzr)
 	for {
-		// Fetch the next tarball header and abort if needed
+		// Move to the next file header.
 		header, err := tr.Next()
 		if err != nil {
 			if err == io.EOF {
@@ -209,22 +220,69 @@ func ExtractTarballArchive(archive string, dest string) error {
 			}
 			return err
 		}
-		// Figure out the target and create it
-		target := filepath.Join(dest, header.Name)
-
-		switch header.Typeflag {
-		case tar.TypeReg:
-			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))
+		// We only care about regular files, directory modes
+		// and special file types are not supported.
+		if header.Typeflag == tar.TypeReg {
+			armode := header.FileInfo().Mode()
+			err := extractFile(header.Name, armode, tr, dest)
 			if err != nil {
-				return err
-			}
-			if _, err := io.Copy(file, tr); err != nil {
-				return err
+				return fmt.Errorf("extract %s: %v", header.Name, 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()
+}
diff --git a/internal/build/azure.go b/internal/build/azure.go
index 7862842650..9c9cc2dcc5 100644
--- a/internal/build/azure.go
+++ b/internal/build/azure.go
@@ -26,7 +26,7 @@ import (
 )
 
 // AzureBlobstoreConfig is an authentication and configuration struct containing
-// the data needed by the Azure SDK to interact with a speicifc container in the
+// the data needed by the Azure SDK to interact with a specific container in the
 // blobstore.
 type AzureBlobstoreConfig struct {
 	Account   string // Account name to authorize API requests with
@@ -71,26 +71,35 @@ func AzureBlobstoreUpload(path string, name string, config AzureBlobstoreConfig)
 
 // AzureBlobstoreList lists all the files contained within an azure blobstore.
 func AzureBlobstoreList(config AzureBlobstoreConfig) ([]azblob.BlobItem, error) {
-	credential, err := azblob.NewSharedKeyCredential(config.Account, config.Token)
-	if err != nil {
-		return nil, err
+	credential := azblob.NewAnonymousCredential()
+	if len(config.Token) > 0 {
+		c, err := azblob.NewSharedKeyCredential(config.Account, config.Token)
+		if err != nil {
+			return nil, err
+		}
+		credential = c
 	}
-
 	pipeline := azblob.NewPipeline(credential, azblob.PipelineOptions{})
 
 	u, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net", config.Account))
 	service := azblob.NewServiceURL(*u, pipeline)
 
+	var allBlobs []azblob.BlobItem
 	// List all the blobs from the container and return them
 	container := service.NewContainerURL(config.Container)
+	nextMarker := azblob.Marker{}
+	for nextMarker.NotDone() {
+		res, err := container.ListBlobsFlatSegment(context.Background(), nextMarker, azblob.ListBlobsSegmentOptions{
+			MaxResults: 5000, // The server only gives max 5K items
+		})
+		if err != nil {
+			return nil, err
+		}
+		allBlobs = append(allBlobs, res.Segment.BlobItems...)
+		nextMarker = res.NextMarker
 
-	res, err := container.ListBlobsFlatSegment(context.Background(), azblob.Marker{}, azblob.ListBlobsSegmentOptions{
-		MaxResults: 1024 * 1024 * 1024, // Yes, fetch all of them
-	})
-	if err != nil {
-		return nil, err
 	}
-	return res.Segment.BlobItems, nil
+	return allBlobs, nil
 }
 
 // AzureBlobstoreDelete iterates over a list of files to delete and removes them
@@ -121,6 +130,7 @@ func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []azblob.BlobItem)
 		if _, err := blockblob.Delete(context.Background(), azblob.DeleteSnapshotsOptionInclude, azblob.BlobAccessConditions{}); err != nil {
 			return err
 		}
+		fmt.Printf("deleted  %s (%s)\n", blob.Name, blob.Properties.LastModified)
 	}
 	return nil
 }
diff --git a/internal/build/env.go b/internal/build/env.go
index a3017182e3..d70c0d50a4 100644
--- a/internal/build/env.go
+++ b/internal/build/env.go
@@ -38,6 +38,7 @@ var (
 
 // Environment contains metadata provided by the build environment.
 type Environment struct {
+	CI                        bool
 	Name                      string // name of the environment
 	Repo                      string // name of GitHub repo
 	Commit, Date, Branch, Tag string // Git info
@@ -61,6 +62,7 @@ func Env() Environment {
 			commit = os.Getenv("TRAVIS_COMMIT")
 		}
 		return Environment{
+			CI:            true,
 			Name:          "travis",
 			Repo:          os.Getenv("TRAVIS_REPO_SLUG"),
 			Commit:        commit,
@@ -77,6 +79,7 @@ func Env() Environment {
 			commit = os.Getenv("APPVEYOR_REPO_COMMIT")
 		}
 		return Environment{
+			CI:            true,
 			Name:          "appveyor",
 			Repo:          os.Getenv("APPVEYOR_REPO_NAME"),
 			Commit:        commit,
diff --git a/internal/build/gotool.go b/internal/build/gotool.go
new file mode 100644
index 0000000000..e644b5f695
--- /dev/null
+++ b/internal/build/gotool.go
@@ -0,0 +1,149 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package build
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+type GoToolchain struct {
+	Root string // GOROOT
+
+	// Cross-compilation variables. These are set when running the go tool.
+	GOARCH string
+	GOOS   string
+	CC     string
+}
+
+// Go creates an invocation of the go command.
+func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd {
+	tool := g.goTool(command, args...)
+
+	// Configure environment for cross build.
+	if g.GOARCH != "" && g.GOARCH != runtime.GOARCH {
+		tool.Env = append(tool.Env, "CGO_ENABLED=1")
+		tool.Env = append(tool.Env, "GOARCH="+g.GOARCH)
+	}
+	if g.GOOS != "" && g.GOOS != runtime.GOOS {
+		tool.Env = append(tool.Env, "GOOS="+g.GOOS)
+	}
+	// Configure C compiler.
+	if g.CC != "" {
+		tool.Env = append(tool.Env, "CC="+g.CC)
+	} else if os.Getenv("CC") != "" {
+		tool.Env = append(tool.Env, "CC="+os.Getenv("CC"))
+	}
+	return tool
+}
+
+// Install creates an invocation of 'go install'. The command is configured to output
+// executables to the given 'gobin' directory.
+//
+// This can be used to install auxiliary build tools without modifying the local go.mod and
+// go.sum files. To install tools which are not required by go.mod, ensure that all module
+// paths in 'args' contain a module version suffix (e.g. "...@latest").
+func (g *GoToolchain) Install(gobin string, args ...string) *exec.Cmd {
+	if !filepath.IsAbs(gobin) {
+		panic("GOBIN must be an absolute path")
+	}
+	tool := g.goTool("install")
+	tool.Env = append(tool.Env, "GOBIN="+gobin)
+	tool.Args = append(tool.Args, "-mod=readonly")
+	tool.Args = append(tool.Args, args...)
+
+	// Ensure GOPATH is set because go install seems to absolutely require it. This uses
+	// 'go env' because it resolves the default value when GOPATH is not set in the
+	// environment. Ignore errors running go env and leave any complaining about GOPATH to
+	// the install command.
+	pathTool := g.goTool("env", "GOPATH")
+	output, _ := pathTool.Output()
+	tool.Env = append(tool.Env, "GOPATH="+string(output))
+	return tool
+}
+
+func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd {
+	if g.Root == "" {
+		g.Root = runtime.GOROOT()
+	}
+	tool := exec.Command(filepath.Join(g.Root, "bin", "go"), command)
+	tool.Args = append(tool.Args, args...)
+	tool.Env = append(tool.Env, "GOROOT="+g.Root)
+
+	// Forward environment variables to the tool, but skip compiler target settings.
+	// TODO: what about GOARM?
+	skip := map[string]struct{}{"GOROOT": {}, "GOARCH": {}, "GOOS": {}, "GOBIN": {}, "CC": {}}
+	for _, e := range os.Environ() {
+		if i := strings.IndexByte(e, '='); i >= 0 {
+			if _, ok := skip[e[:i]]; ok {
+				continue
+			}
+		}
+		tool.Env = append(tool.Env, e)
+	}
+	return tool
+}
+
+// DownloadGo downloads the Go binary distribution and unpacks it into a temporary
+// directory. It returns the GOROOT of the unpacked toolchain.
+func DownloadGo(csdb *ChecksumDB, version string) string {
+	// Shortcut: if the Go version that runs this script matches the
+	// requested version exactly, there is no need to download anything.
+	activeGo := strings.TrimPrefix(runtime.Version(), "go")
+	if activeGo == version {
+		log.Printf("-dlgo version matches active Go version %s, skipping download.", activeGo)
+		return runtime.GOROOT()
+	}
+
+	ucache, err := os.UserCacheDir()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// For Arm architecture, GOARCH includes ISA version.
+	os := runtime.GOOS
+	arch := runtime.GOARCH
+	if arch == "arm" {
+		arch = "armv6l"
+	}
+	file := fmt.Sprintf("go%s.%s-%s", version, os, arch)
+	if os == "windows" {
+		file += ".zip"
+	} else {
+		file += ".tar.gz"
+	}
+	url := "https://golang.org/dl/" + file
+	dst := filepath.Join(ucache, file)
+	if err := csdb.DownloadFile(url, dst); err != nil {
+		log.Fatal(err)
+	}
+
+	godir := filepath.Join(ucache, fmt.Sprintf("geth-go-%s-%s-%s", version, os, arch))
+	if err := ExtractArchive(dst, godir); err != nil {
+		log.Fatal(err)
+	}
+	goroot, err := filepath.Abs(filepath.Join(godir, "go"))
+	if err != nil {
+		log.Fatal(err)
+	}
+	return goroot
+}
diff --git a/internal/build/util.go b/internal/build/util.go
index fc559760b2..2bdced82ee 100644
--- a/internal/build/util.go
+++ b/internal/build/util.go
@@ -20,6 +20,8 @@ import (
 	"bytes"
 	"flag"
 	"fmt"
+	"go/parser"
+	"go/token"
 	"io"
 	"io/ioutil"
 	"log"
@@ -27,7 +29,6 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
-	"runtime"
 	"strings"
 	"text/template"
 )
@@ -109,19 +110,6 @@ func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x
 	}
 }
 
-// GoTool returns the command that runs a go tool. This uses go from GOROOT instead of PATH
-// so that go commands executed by build use the same version of Go as the 'host' that runs
-// build code. e.g.
-//
-//     /usr/lib/go-1.12.1/bin/go run build/ci.go ...
-//
-// runs using go 1.12.1 and invokes go 1.12.1 tools from the same GOROOT. This is also important
-// because runtime.Version checks on the host should match the tools that are run.
-func GoTool(tool string, args ...string) *exec.Cmd {
-	args = append([]string{tool}, args...)
-	return exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
-}
-
 // UploadSFTP uploads files to a remote host using the sftp command line tool.
 // The destination host may be specified either as [user@]host[: or as a URI in
 // the form sftp://[user@]host[:port].
@@ -152,3 +140,28 @@ func UploadSFTP(identityFile, host, dir string, files []string) error {
 	stdin.Close()
 	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
+}
diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go
index d4ccc58329..82ad9c15b6 100644
--- a/internal/cmdtest/test_cmd.go
+++ b/internal/cmdtest/test_cmd.go
@@ -27,6 +27,7 @@ import (
 	"regexp"
 	"strings"
 	"sync"
+	"sync/atomic"
 	"syscall"
 	"testing"
 	"text/template"
@@ -55,10 +56,13 @@ type TestCmd struct {
 	Err error
 }
 
+var id int32
+
 // Run exec's the current binary using name as argv[0] which will trigger the
 // reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go)
 func (tt *TestCmd) Run(name string, args ...string) {
-	tt.stderr = &testlogger{t: tt.T}
+	id := atomic.AddInt32(&id, 1)
+	tt.stderr = &testlogger{t: tt.T, name: fmt.Sprintf("%d", id)}
 	tt.cmd = &exec.Cmd{
 		Path:   reexec.Self(),
 		Args:   append([]string{name}, args...),
@@ -77,7 +81,7 @@ func (tt *TestCmd) Run(name string, args ...string) {
 	}
 }
 
-// InputLine writes the given text to the childs stdin.
+// InputLine writes the given text to the child's stdin.
 // This method can also be called from an expect template, e.g.:
 //
 //     geth.expect(`Passphrase: {{.InputLine "password"}}`)
@@ -238,16 +242,17 @@ func (tt *TestCmd) withKillTimeout(fn func()) {
 // testlogger logs all written lines via t.Log and also
 // collects them for later inspection.
 type testlogger struct {
-	t   *testing.T
-	mu  sync.Mutex
-	buf bytes.Buffer
+	t    *testing.T
+	mu   sync.Mutex
+	buf  bytes.Buffer
+	name string
 }
 
 func (tl *testlogger) Write(b []byte) (n int, err error) {
 	lines := bytes.Split(b, []byte("\n"))
 	for _, line := range lines {
 		if len(line) > 0 {
-			tl.t.Logf("(stderr) %s", line)
+			tl.t.Logf("(stderr:%v) %s", tl.name, line)
 		}
 	}
 	tl.mu.Lock()
diff --git a/internal/debug/api.go b/internal/debug/api.go
index 86a4218f6a..efd8626776 100644
--- a/internal/debug/api.go
+++ b/internal/debug/api.go
@@ -196,7 +196,7 @@ func (*HandlerT) Stacks() string {
 	return buf.String()
 }
 
-// FreeOSMemory returns unused memory to the OS.
+// FreeOSMemory forces a garbage collection.
 func (*HandlerT) FreeOSMemory() {
 	debug.FreeOSMemory()
 }
diff --git a/internal/debug/flags.go b/internal/debug/flags.go
index 3c85749402..126ee09a7c 100644
--- a/internal/debug/flags.go
+++ b/internal/debug/flags.go
@@ -28,7 +28,7 @@ import (
 	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/metrics/exp"
 	"github.com/fjl/memsize/memsizeui"
-	colorable "github.com/mattn/go-colorable"
+	"github.com/mattn/go-colorable"
 	"github.com/mattn/go-isatty"
 	"gopkg.in/urfave/cli.v1"
 )
@@ -46,13 +46,17 @@ var (
 		Usage: "Per-module verbosity: comma-separated list of = (e.g. eth/*=5,p2p=4)",
 		Value: "",
 	}
+	logjsonFlag = cli.BoolFlag{
+		Name:  "log.json",
+		Usage: "Format logs with JSON",
+	}
 	backtraceAtFlag = cli.StringFlag{
-		Name:  "backtrace",
+		Name:  "log.backtrace",
 		Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")",
 		Value: "",
 	}
 	debugFlag = cli.BoolFlag{
-		Name:  "debug",
+		Name:  "log.debug",
 		Usage: "Prepends log messages with call-site location (file and line number)",
 	}
 	pprofFlag = cli.BoolFlag{
@@ -60,74 +64,172 @@ var (
 		Usage: "Enable the pprof HTTP server",
 	}
 	pprofPortFlag = cli.IntFlag{
-		Name:  "pprofport",
+		Name:  "pprof.port",
 		Usage: "pprof HTTP server listening port",
 		Value: 6060,
 	}
 	pprofAddrFlag = cli.StringFlag{
-		Name:  "pprofaddr",
+		Name:  "pprof.addr",
 		Usage: "pprof HTTP server listening interface",
 		Value: "127.0.0.1",
 	}
 	memprofilerateFlag = cli.IntFlag{
-		Name:  "memprofilerate",
+		Name:  "pprof.memprofilerate",
 		Usage: "Turn on memory profiling with the given rate",
 		Value: runtime.MemProfileRate,
 	}
 	blockprofilerateFlag = cli.IntFlag{
-		Name:  "blockprofilerate",
+		Name:  "pprof.blockprofilerate",
 		Usage: "Turn on block profiling with the given rate",
 	}
 	cpuprofileFlag = cli.StringFlag{
-		Name:  "cpuprofile",
+		Name:  "pprof.cpuprofile",
 		Usage: "Write CPU profile to the given file",
 	}
 	traceFlag = cli.StringFlag{
 		Name:  "trace",
 		Usage: "Write execution trace to the given file",
 	}
+	// (Deprecated April 2020)
+	legacyPprofPortFlag = cli.IntFlag{
+		Name:  "pprofport",
+		Usage: "pprof HTTP server listening port (deprecated, use --pprof.port)",
+		Value: 6060,
+	}
+	legacyPprofAddrFlag = cli.StringFlag{
+		Name:  "pprofaddr",
+		Usage: "pprof HTTP server listening interface (deprecated, use --pprof.addr)",
+		Value: "127.0.0.1",
+	}
+	legacyMemprofilerateFlag = cli.IntFlag{
+		Name:  "memprofilerate",
+		Usage: "Turn on memory profiling with the given rate (deprecated, use --pprof.memprofilerate)",
+		Value: runtime.MemProfileRate,
+	}
+	legacyBlockprofilerateFlag = cli.IntFlag{
+		Name:  "blockprofilerate",
+		Usage: "Turn on block profiling with the given rate (deprecated, use --pprof.blockprofilerate)",
+	}
+	legacyCpuprofileFlag = cli.StringFlag{
+		Name:  "cpuprofile",
+		Usage: "Write CPU profile to the given file (deprecated, use --pprof.cpuprofile)",
+	}
+	legacyBacktraceAtFlag = cli.StringFlag{
+		Name:  "backtrace",
+		Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\") (deprecated, use --log.backtrace)",
+		Value: "",
+	}
+	legacyDebugFlag = cli.BoolFlag{
+		Name:  "debug",
+		Usage: "Prepends log messages with call-site location (file and line number) (deprecated, use --log.debug)",
+	}
 )
 
 // Flags holds all command-line flags required for debugging.
 var Flags = []cli.Flag{
-	verbosityFlag, vmoduleFlag, backtraceAtFlag, debugFlag,
-	pprofFlag, pprofAddrFlag, pprofPortFlag,
-	memprofilerateFlag, blockprofilerateFlag, cpuprofileFlag, traceFlag,
+	verbosityFlag,
+	vmoduleFlag,
+	logjsonFlag,
+	backtraceAtFlag,
+	debugFlag,
+	pprofFlag,
+	pprofAddrFlag,
+	pprofPortFlag,
+	memprofilerateFlag,
+	blockprofilerateFlag,
+	cpuprofileFlag,
+	traceFlag,
 }
 
-var (
-	ostream log.Handler
-	glogger *log.GlogHandler
-)
+// This is the list of deprecated debugging flags.
+var DeprecatedFlags = []cli.Flag{
+	legacyPprofPortFlag,
+	legacyPprofAddrFlag,
+	legacyMemprofilerateFlag,
+	legacyBlockprofilerateFlag,
+	legacyCpuprofileFlag,
+	legacyBacktraceAtFlag,
+	legacyDebugFlag,
+}
+
+var glogger *log.GlogHandler
 
 func init() {
-	usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
-	output := io.Writer(os.Stderr)
-	if usecolor {
-		output = colorable.NewColorableStderr()
-	}
-	ostream = log.StreamHandler(output, log.TerminalFormat(usecolor))
-	glogger = log.NewGlogHandler(ostream)
+	glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
+	glogger.Verbosity(log.LvlInfo)
+	log.Root().SetHandler(glogger)
 }
 
 // Setup initializes profiling and logging based on the CLI flags.
 // It should be called as early as possible in the program.
 func Setup(ctx *cli.Context) error {
+	var ostream log.Handler
+	output := io.Writer(os.Stderr)
+	if ctx.GlobalBool(logjsonFlag.Name) {
+		ostream = log.StreamHandler(output, log.JSONFormat())
+	} else {
+		usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
+		if usecolor {
+			output = colorable.NewColorableStderr()
+		}
+		ostream = log.StreamHandler(output, log.TerminalFormat(usecolor))
+	}
+	glogger.SetHandler(ostream)
+
 	// logging
-	log.PrintOrigins(ctx.GlobalBool(debugFlag.Name))
-	glogger.Verbosity(log.Lvl(ctx.GlobalInt(verbosityFlag.Name)))
-	glogger.Vmodule(ctx.GlobalString(vmoduleFlag.Name))
-	glogger.BacktraceAt(ctx.GlobalString(backtraceAtFlag.Name))
+	verbosity := ctx.GlobalInt(verbosityFlag.Name)
+	glogger.Verbosity(log.Lvl(verbosity))
+	vmodule := ctx.GlobalString(vmoduleFlag.Name)
+	glogger.Vmodule(vmodule)
+
+	debug := ctx.GlobalBool(debugFlag.Name)
+	if ctx.GlobalIsSet(legacyDebugFlag.Name) {
+		debug = ctx.GlobalBool(legacyDebugFlag.Name)
+		log.Warn("The flag --debug is deprecated and will be removed in the future, please use --log.debug")
+	}
+	if ctx.GlobalIsSet(debugFlag.Name) {
+		debug = ctx.GlobalBool(debugFlag.Name)
+	}
+	log.PrintOrigins(debug)
+
+	backtrace := ctx.GlobalString(backtraceAtFlag.Name)
+	if b := ctx.GlobalString(legacyBacktraceAtFlag.Name); b != "" {
+		backtrace = b
+		log.Warn("The flag --backtrace is deprecated and will be removed in the future, please use --log.backtrace")
+	}
+	if b := ctx.GlobalString(backtraceAtFlag.Name); b != "" {
+		backtrace = b
+	}
+	glogger.BacktraceAt(backtrace)
+
 	log.Root().SetHandler(glogger)
 
 	// profiling, tracing
-	runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name)
-	Handler.SetBlockProfileRate(ctx.GlobalInt(blockprofilerateFlag.Name))
+	runtime.MemProfileRate = memprofilerateFlag.Value
+	if ctx.GlobalIsSet(legacyMemprofilerateFlag.Name) {
+		runtime.MemProfileRate = ctx.GlobalInt(legacyMemprofilerateFlag.Name)
+		log.Warn("The flag --memprofilerate is deprecated and will be removed in the future, please use --pprof.memprofilerate")
+	}
+	if ctx.GlobalIsSet(memprofilerateFlag.Name) {
+		runtime.MemProfileRate = ctx.GlobalInt(memprofilerateFlag.Name)
+	}
+
+	blockProfileRate := blockprofilerateFlag.Value
+	if ctx.GlobalIsSet(legacyBlockprofilerateFlag.Name) {
+		blockProfileRate = ctx.GlobalInt(legacyBlockprofilerateFlag.Name)
+		log.Warn("The flag --blockprofilerate is deprecated and will be removed in the future, please use --pprof.blockprofilerate")
+	}
+	if ctx.GlobalIsSet(blockprofilerateFlag.Name) {
+		blockProfileRate = ctx.GlobalInt(blockprofilerateFlag.Name)
+	}
+	Handler.SetBlockProfileRate(blockProfileRate)
+
 	if traceFile := ctx.GlobalString(traceFlag.Name); traceFile != "" {
 		if err := Handler.StartGoTrace(traceFile); err != nil {
 			return err
 		}
 	}
+
 	if cpuFile := ctx.GlobalString(cpuprofileFlag.Name); cpuFile != "" {
 		if err := Handler.StartCPUProfile(cpuFile); err != nil {
 			return err
@@ -136,16 +238,24 @@ func Setup(ctx *cli.Context) error {
 
 	// pprof server
 	if ctx.GlobalBool(pprofFlag.Name) {
-		address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name))
-		StartPProf(address)
+		listenHost := ctx.GlobalString(pprofAddrFlag.Name)
+
+		port := ctx.GlobalInt(pprofPortFlag.Name)
+
+		address := fmt.Sprintf("%s:%d", listenHost, port)
+		// This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name.
+		// It cannot be imported because it will cause a cyclical dependency.
+		StartPProf(address, !ctx.GlobalIsSet("metrics.addr"))
 	}
 	return nil
 }
 
-func StartPProf(address string) {
+func StartPProf(address string, withMetrics bool) {
 	// Hook go-metrics into expvar on any /debug/metrics request, load all vars
 	// from the registry into expvar, and execute regular expvar handler.
-	exp.Exp(metrics.DefaultRegistry)
+	if withMetrics {
+		exp.Exp(metrics.DefaultRegistry)
+	}
 	http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize))
 	log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address))
 	go func() {
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 4cbe64bcf0..56c8eb206e 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -25,8 +25,11 @@ import (
 	"strings"
 	"time"
 
+	"github.com/ethereum/go-ethereum/core/rawdb"
+
 	"github.com/davecgh/go-spew/spew"
 	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/accounts/abi"
 	"github.com/ethereum/go-ethereum/accounts/keystore"
 	"github.com/ethereum/go-ethereum/accounts/scwallet"
 	"github.com/ethereum/go-ethereum/common"
@@ -35,7 +38,7 @@ import (
 	"github.com/ethereum/go-ethereum/consensus/clique"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -66,11 +69,6 @@ func (s *PublicEthereumAPI) GasPrice(ctx context.Context) (*hexutil.Big, error)
 	return (*hexutil.Big)(price), err
 }
 
-// ProtocolVersion returns the current Ethereum protocol version this node supports
-func (s *PublicEthereumAPI) ProtocolVersion() hexutil.Uint {
-	return hexutil.Uint(s.b.ProtocolVersion())
-}
-
 // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
 // yet received the latest block headers from its pears. In case it is synchronizing:
 // - startingBlock: block number this node started to synchronise from
@@ -278,7 +276,11 @@ func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (a
 
 // NewAccount will create a new account and returns the address for the new account.
 func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
-	acc, err := fetchKeystore(s.am).NewAccount(password)
+	ks, err := fetchKeystore(s.am)
+	if err != nil {
+		return common.Address{}, err
+	}
+	acc, err := ks.NewAccount(password)
 	if err == nil {
 		log.Info("Your new key was generated", "address", acc.Address)
 		log.Warn("Please backup your key file!", "path", acc.URL.Path)
@@ -288,9 +290,12 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error)
 	return common.Address{}, err
 }
 
-// fetchKeystore retrives the encrypted keystore from the account manager.
-func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
-	return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+// fetchKeystore retrieves the encrypted keystore from the account manager.
+func fetchKeystore(am *accounts.Manager) (*keystore.KeyStore, error) {
+	if ks := am.Backends(keystore.KeyStoreType); len(ks) > 0 {
+		return ks[0].(*keystore.KeyStore), nil
+	}
+	return nil, errors.New("local keystore not used")
 }
 
 // ImportRawKey stores the given hex encoded ECDSA key into the key directory,
@@ -300,7 +305,11 @@ func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (commo
 	if err != nil {
 		return common.Address{}, err
 	}
-	acc, err := fetchKeystore(s.am).ImportECDSA(key, password)
+	ks, err := fetchKeystore(s.am)
+	if err != nil {
+		return common.Address{}, err
+	}
+	acc, err := ks.ImportECDSA(key, password)
 	return acc.Address, err
 }
 
@@ -324,7 +333,11 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre
 	} else {
 		d = time.Duration(*duration) * time.Second
 	}
-	err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)
+	ks, err := fetchKeystore(s.am)
+	if err != nil {
+		return false, err
+	}
+	err = ks.TimedUnlock(accounts.Account{Address: addr}, password, d)
 	if err != nil {
 		log.Warn("Failed account unlock attempt", "address", addr, "err", err)
 	}
@@ -333,7 +346,10 @@ func (s *PrivateAccountAPI) UnlockAccount(ctx context.Context, addr common.Addre
 
 // LockAccount will lock the account associated with the given address when it's unlocked.
 func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
-	return fetchKeystore(s.am).Lock(addr) == nil
+	if ks, err := fetchKeystore(s.am); err == nil {
+		return ks.Lock(addr) == nil
+	}
+	return false
 }
 
 // signTransaction sets defaults and signs the given transaction
@@ -357,7 +373,7 @@ func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args *SendTxArg
 }
 
 // SendTransaction will create a transaction from the given arguments and
-// tries to sign it with the key associated with args.To. If the given passwd isn't
+// tries to sign it with the key associated with args.From. If the given passwd isn't
 // able to decrypt the key it fails.
 func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
 	if args.Nonce == nil {
@@ -375,7 +391,7 @@ func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs
 }
 
 // SignTransaction will create a transaction from the given arguments and
-// tries to sign it with the key associated with args.To. If the given passwd isn't
+// tries to sign it with the key associated with args.From. If the given passwd isn't
 // able to decrypt the key it fails. The transaction is returned in RLP-form, not broadcast
 // to other nodes
 func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs, passwd string) (*SignTransactionResult, error) {
@@ -390,12 +406,16 @@ func (s *PrivateAccountAPI) SignTransaction(ctx context.Context, args SendTxArgs
 	if args.Nonce == nil {
 		return nil, fmt.Errorf("nonce not specified")
 	}
+	// Before actually sign the transaction, ensure the transaction fee is reasonable.
+	if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil {
+		return nil, err
+	}
 	signed, err := s.signTransaction(ctx, &args, passwd)
 	if err != nil {
 		log.Warn("Failed transaction sign attempt", "from", args.From, "to", args.To, "value", args.Value.ToInt(), "err", err)
 		return nil, err
 	}
-	data, err := rlp.EncodeToBytes(signed)
+	data, err := signed.MarshalBinary()
 	if err != nil {
 		return nil, err
 	}
@@ -514,9 +534,13 @@ func NewPublicBlockChainAPI(b Backend) *PublicBlockChainAPI {
 	return &PublicBlockChainAPI{b}
 }
 
-// ChainId returns the chainID value for transaction replay protection.
-func (s *PublicBlockChainAPI) ChainId() *hexutil.Big {
-	return (*hexutil.Big)(s.b.ChainConfig().ChainID)
+// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
+func (api *PublicBlockChainAPI) ChainId() (*hexutil.Big, error) {
+	// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
+	if config := api.b.ChainConfig(); config.IsEIP155(api.b.CurrentBlock().Number()) {
+		return (*hexutil.Big)(config.ChainID), nil
+	}
+	return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block")
 }
 
 // BlockNumber returns the block number of the chain head.
@@ -579,7 +603,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
 			if storageError != nil {
 				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 {
 			storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}}
 		}
@@ -593,7 +617,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
 
 	return &AccountResult{
 		Address:      address,
-		AccountProof: common.ToHexArray(accountProof),
+		AccountProof: toHexSlice(accountProof),
 		Balance:      (*hexutil.Big)(state.GetBalance(address)),
 		CodeHash:     codeHash,
 		Nonce:        hexutil.Uint64(state.GetNonce(address)),
@@ -608,7 +632,7 @@ func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Addre
 func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) {
 	header, err := s.b.HeaderByNumber(ctx, number)
 	if header != nil && err == nil {
-		response := s.rpcMarshalHeader(header)
+		response := s.rpcMarshalHeader(ctx, header)
 		if number == rpc.PendingBlockNumber {
 			// Pending header need to nil out a few fields
 			for _, field := range []string{"hash", "nonce", "miner"} {
@@ -624,7 +648,7 @@ func (s *PublicBlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.
 func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) map[string]interface{} {
 	header, _ := s.b.HeaderByHash(ctx, hash)
 	if header != nil {
-		return s.rpcMarshalHeader(header)
+		return s.rpcMarshalHeader(ctx, header)
 	}
 	return nil
 }
@@ -637,7 +661,7 @@ func (s *PublicBlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.H
 func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
 	block, err := s.b.BlockByNumber(ctx, number)
 	if block != nil && err == nil {
-		response, err := s.rpcMarshalBlock(block, true, fullTx)
+		response, err := s.rpcMarshalBlock(ctx, block, true, fullTx)
 		if err == nil && number == rpc.PendingBlockNumber {
 			// Pending blocks need to nil out a few fields
 			for _, field := range []string{"hash", "nonce", "miner"} {
@@ -654,7 +678,7 @@ func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.B
 func (s *PublicBlockChainAPI) GetBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (map[string]interface{}, error) {
 	block, err := s.b.BlockByHash(ctx, hash)
 	if block != nil {
-		return s.rpcMarshalBlock(block, true, fullTx)
+		return s.rpcMarshalBlock(ctx, block, true, fullTx)
 	}
 	return nil, err
 }
@@ -677,7 +701,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(ctx context.Context,
 			return nil, nil
 		}
 		block = types.NewBlockWithHeader(uncles[index])
-		return s.rpcMarshalBlock(block, false, false)
+		return s.rpcMarshalBlock(ctx, block, false, false)
 	}
 	return nil, err
 }
@@ -693,7 +717,7 @@ func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(ctx context.Context, b
 			return nil, nil
 		}
 		block = types.NewBlockWithHeader(uncles[index])
-		return s.rpcMarshalBlock(block, false, false)
+		return s.rpcMarshalBlock(ctx, block, false, false)
 	}
 	return nil, err
 }
@@ -740,16 +764,17 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A
 
 // CallArgs represents the arguments for a call.
 type CallArgs struct {
-	From     *common.Address `json:"from"`
-	To       *common.Address `json:"to"`
-	Gas      *hexutil.Uint64 `json:"gas"`
-	GasPrice *hexutil.Big    `json:"gasPrice"`
-	Value    *hexutil.Big    `json:"value"`
-	Data     *hexutil.Bytes  `json:"data"`
+	From       *common.Address   `json:"from"`
+	To         *common.Address   `json:"to"`
+	Gas        *hexutil.Uint64   `json:"gas"`
+	GasPrice   *hexutil.Big      `json:"gasPrice"`
+	Value      *hexutil.Big      `json:"value"`
+	Data       *hexutil.Bytes    `json:"data"`
+	AccessList *types.AccessList `json:"accessList"`
 }
 
 // ToMessage converts CallArgs to the Message type used by the core evm
-func (args *CallArgs) ToMessage(globalGasCap *big.Int) types.Message {
+func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message {
 	// Set sender address or use zero address if none specified.
 	var addr common.Address
 	if args.From != nil {
@@ -757,40 +782,45 @@ func (args *CallArgs) ToMessage(globalGasCap *big.Int) types.Message {
 	}
 
 	// Set default gas & gas price if none were set
-	gas := uint64(math.MaxUint64 / 2)
+	gas := globalGasCap
+	if gas == 0 {
+		gas = uint64(math.MaxUint64 / 2)
+	}
 	if args.Gas != nil {
 		gas = uint64(*args.Gas)
 	}
-	if globalGasCap != nil && globalGasCap.Uint64() < gas {
-		log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
-		gas = globalGasCap.Uint64()
+	if globalGasCap != 0 && globalGasCap < gas {
+		log.Debug("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
+		gas = globalGasCap
 	}
 	gasPrice := new(big.Int)
 	if args.GasPrice != nil {
 		gasPrice = args.GasPrice.ToInt()
 	}
-
 	value := new(big.Int)
 	if args.Value != nil {
 		value = args.Value.ToInt()
 	}
-
 	var data []byte
 	if args.Data != nil {
-		data = []byte(*args.Data)
+		data = *args.Data
+	}
+	var accessList types.AccessList
+	if args.AccessList != nil {
+		accessList = *args.AccessList
 	}
 
-	msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false)
+	msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, accessList, false)
 	return msg
 }
 
-// account indicates the overriding fields of account during the execution of
-// a message call.
+// OverrideAccount indicates the overriding fields of account during the execution
+// of a message call.
 // Note, state and stateDiff can't be specified at the same time. If state is
 // set, message execution will only use the data in the given state. Otherwise
 // if statDiff is set, all diff will be applied first and then execute the call
 // message.
-type account struct {
+type OverrideAccount struct {
 	Nonce     *hexutil.Uint64              `json:"nonce"`
 	Code      *hexutil.Bytes               `json:"code"`
 	Balance   **hexutil.Big                `json:"balance"`
@@ -798,16 +828,15 @@ type account struct {
 	StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
 }
 
-func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
-	defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
+// StateOverride is the collection of overridden accounts.
+type StateOverride map[common.Address]OverrideAccount
 
-	state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
-	if state == nil || err != nil {
-		return nil, 0, false, err
+// Apply overrides the fields of specified accounts into the given state.
+func (diff *StateOverride) Apply(state *state.StateDB) error {
+	if diff == nil {
+		return nil
 	}
-
-	// Override the fields of specified contracts before execution.
-	for addr, account := range overrides {
+	for addr, account := range *diff {
 		// Override account nonce.
 		if account.Nonce != nil {
 			state.SetNonce(addr, uint64(*account.Nonce))
@@ -821,7 +850,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
 			state.SetBalance(addr, (*big.Int)(*account.Balance))
 		}
 		if account.State != nil && account.StateDiff != nil {
-			return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
+			return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
 		}
 		// Replace entire state if caller requires.
 		if account.State != nil {
@@ -834,7 +863,19 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
 			}
 		}
 	}
+	return nil
+}
+
+func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
+	defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
 
+	state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+	if state == nil || err != nil {
+		return nil, err
+	}
+	if err := overrides.Apply(state); err != nil {
+		return nil, err
+	}
 	// Setup context so it may be cancelled the call has completed
 	// or, in case of unmetered gas, setup a context with a timeout.
 	var cancel context.CancelFunc
@@ -849,9 +890,9 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
 
 	// Get a new instance of the EVM.
 	msg := args.ToMessage(globalGasCap)
-	evm, vmError, err := b.GetEVM(ctx, msg, state, header)
+	evm, vmError, err := b.GetEVM(ctx, msg, state, header, nil)
 	if err != nil {
-		return nil, 0, false, err
+		return nil, err
 	}
 	// Wait for the context to be done and cancel the evm. Even if the
 	// EVM has finished, cancelling may be done (repeatedly)
@@ -860,18 +901,51 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
 		evm.Cancel()
 	}()
 
-	// Setup the gas pool (also for unmetered requests)
-	// and apply the message.
+	// Execute the message.
 	gp := new(core.GasPool).AddGas(math.MaxUint64)
-	res, gas, failed, err := core.ApplyMessage(evm, msg, gp)
+	result, err := core.ApplyMessage(evm, msg, gp)
 	if err := vmError(); err != nil {
-		return nil, 0, false, err
+		return nil, err
 	}
+
 	// If the timer caused an abort, return an appropriate error message
 	if evm.Cancelled() {
-		return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout)
+		return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
+	}
+	if err != nil {
+		return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
+	}
+	return result, nil
+}
+
+func newRevertError(result *core.ExecutionResult) *revertError {
+	reason, errUnpack := abi.UnpackRevert(result.Revert())
+	err := errors.New("execution reverted")
+	if errUnpack == nil {
+		err = fmt.Errorf("execution reverted: %v", reason)
 	}
-	return res, gas, failed, err
+	return &revertError{
+		error:  err,
+		reason: hexutil.Encode(result.Revert()),
+	}
+}
+
+// revertError is an API error that encompassas an EVM revertal with JSON error
+// code and a binary data blob.
+type revertError struct {
+	error
+	reason string // revert reason hex encoded
+}
+
+// ErrorCode returns the JSON error code for a revertal.
+// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
+func (e *revertError) ErrorCode() int {
+	return 3
+}
+
+// ErrorData returns the hex encoded revert reason.
+func (e *revertError) ErrorData() interface{} {
+	return e.reason
 }
 
 // Call executes the given transaction on the state for the given block number.
@@ -880,16 +954,19 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
 //
 // Note, this function doesn't make and changes in the state/blockchain and is
 // useful to execute and retrieve values.
-func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
-	var accounts map[common.Address]account
-	if overrides != nil {
-		accounts = *overrides
+func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
+	result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
+	if err != nil {
+		return nil, err
+	}
+	// If the result contains a revert reason, try to unpack and return it.
+	if len(result.Revert()) > 0 {
+		return nil, newRevertError(result)
 	}
-	result, _, _, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
-	return (hexutil.Bytes)(result), err
+	return result.Return(), result.Err
 }
 
-func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
+func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
 	// Binary search the gas requirement, as it may be higher than the amount used
 	var (
 		lo  uint64 = params.TxGas - 1
@@ -898,7 +975,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
 	)
 	// Use zero address if sender unspecified.
 	if args.From == nil {
-		return 0, fmt.Errorf("missing from address")
+		args.From = new(common.Address)
 	}
 	// Determine the highest gas limit can be used during the estimation.
 	if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
@@ -942,26 +1019,37 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
 		}
 	}
 	// Recap the highest gas allowance with specified gascap.
-	if gasCap != nil && hi > gasCap.Uint64() {
-		log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
-		hi = gasCap.Uint64()
+	if gasCap != 0 && hi > gasCap {
+		log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
+		hi = gasCap
 	}
 	cap = hi
 
 	// Create a helper to check if a gas allowance results in an executable transaction
-	executable := func(gas uint64) bool {
+	executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
 		args.Gas = (*hexutil.Uint64)(&gas)
 
-		_, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
-		if err != nil || failed {
-			return false
+		result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
+		if err != nil {
+			if errors.Is(err, core.ErrIntrinsicGas) {
+				return true, nil, nil // Special case, raise gas limit
+			}
+			return true, nil, err // Bail out
 		}
-		return true
+		return result.Failed(), result, nil
 	}
 	// Execute the binary search and hone in on an executable gas limit
 	for lo+1 < hi {
 		mid := (hi + lo) / 2
-		if !executable(mid) {
+		failed, _, err := executable(mid)
+
+		// If the error is not nil(consensus error), it means the provided message
+		// call or transaction will never be accepted no matter how much gas it is
+		// assigned. Return the error directly, don't struggle any more.
+		if err != nil {
+			return 0, err
+		}
+		if failed {
 			lo = mid
 		} else {
 			hi = mid
@@ -969,8 +1057,19 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
 	}
 	// Reject the transaction as invalid if it still fails at the highest allowance
 	if hi == cap {
-		if !executable(hi) {
-			return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap)
+		failed, result, err := executable(hi)
+		if err != nil {
+			return 0, err
+		}
+		if failed {
+			if result != nil && result.Err != vm.ErrOutOfGas {
+				if len(result.Revert()) > 0 {
+					return 0, newRevertError(result)
+				}
+				return 0, result.Err
+			}
+			// Otherwise, the specified gas cap is too low
+			return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
 		}
 	}
 	return hexutil.Uint64(hi), nil
@@ -978,9 +1077,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
 
 // EstimateGas returns an estimate of the amount of gas needed to execute the
 // given transaction against the current pending block.
-func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
-	blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
-	return DoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap())
+func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) {
+	bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+	if blockNrOrHash != nil {
+		bNrOrHash = *blockNrOrHash
+	}
+	return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap())
 }
 
 // ExecutionResult groups all structured logs emitted by the EVM
@@ -1105,54 +1207,64 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]i
 
 // rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires
 // a `PublicBlockchainAPI`.
-func (s *PublicBlockChainAPI) rpcMarshalHeader(header *types.Header) map[string]interface{} {
+func (s *PublicBlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} {
 	fields := RPCMarshalHeader(header)
-	fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(header.Hash()))
+	fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, header.Hash()))
 	return fields
 }
 
 // rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires
 // a `PublicBlockchainAPI`.
-func (s *PublicBlockChainAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
+func (s *PublicBlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
 	fields, err := RPCMarshalBlock(b, inclTx, fullTx)
 	if err != nil {
 		return nil, err
 	}
 	if inclTx {
-		fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash()))
+		fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash()))
 	}
 	return fields, err
 }
 
 // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
 type RPCTransaction struct {
-	BlockHash        *common.Hash    `json:"blockHash"`
-	BlockNumber      *hexutil.Big    `json:"blockNumber"`
-	From             common.Address  `json:"from"`
-	Gas              hexutil.Uint64  `json:"gas"`
-	GasPrice         *hexutil.Big    `json:"gasPrice"`
-	Hash             common.Hash     `json:"hash"`
-	Input            hexutil.Bytes   `json:"input"`
-	Nonce            hexutil.Uint64  `json:"nonce"`
-	To               *common.Address `json:"to"`
-	TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
-	Value            *hexutil.Big    `json:"value"`
-	V                *hexutil.Big    `json:"v"`
-	R                *hexutil.Big    `json:"r"`
-	S                *hexutil.Big    `json:"s"`
+	BlockHash        *common.Hash      `json:"blockHash"`
+	BlockNumber      *hexutil.Big      `json:"blockNumber"`
+	From             common.Address    `json:"from"`
+	Gas              hexutil.Uint64    `json:"gas"`
+	GasPrice         *hexutil.Big      `json:"gasPrice"`
+	Hash             common.Hash       `json:"hash"`
+	Input            hexutil.Bytes     `json:"input"`
+	Nonce            hexutil.Uint64    `json:"nonce"`
+	To               *common.Address   `json:"to"`
+	TransactionIndex *hexutil.Uint64   `json:"transactionIndex"`
+	Value            *hexutil.Big      `json:"value"`
+	Type             hexutil.Uint64    `json:"type"`
+	Accesses         *types.AccessList `json:"accessList,omitempty"`
+	ChainID          *hexutil.Big      `json:"chainId,omitempty"`
+	V                *hexutil.Big      `json:"v"`
+	R                *hexutil.Big      `json:"r"`
+	S                *hexutil.Big      `json:"s"`
 }
 
 // newRPCTransaction returns a transaction that will serialize to the RPC
 // representation, with the given location metadata set (if available).
 func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
-	var signer types.Signer = types.FrontierSigner{}
+	// Determine the signer. For replay-protected transactions, use the most permissive
+	// signer, because we assume that signers are backwards-compatible with old
+	// transactions. For non-protected transactions, the homestead signer signer is used
+	// because the return value of ChainId is zero for those transactions.
+	var signer types.Signer
 	if tx.Protected() {
-		signer = types.NewEIP155Signer(tx.ChainId())
+		signer = types.LatestSignerForChainID(tx.ChainId())
+	} else {
+		signer = types.HomesteadSigner{}
 	}
+
 	from, _ := types.Sender(signer, tx)
 	v, r, s := tx.RawSignatureValues()
-
 	result := &RPCTransaction{
+		Type:     hexutil.Uint64(tx.Type()),
 		From:     from,
 		Gas:      hexutil.Uint64(tx.Gas()),
 		GasPrice: (*hexutil.Big)(tx.GasPrice()),
@@ -1170,6 +1282,11 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
 		result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
 		result.TransactionIndex = (*hexutil.Uint64)(&index)
 	}
+	if tx.Type() == types.AccessListTxType {
+		al := tx.AccessList()
+		result.Accesses = &al
+		result.ChainID = (*hexutil.Big)(tx.ChainId())
+	}
 	return result
 }
 
@@ -1204,7 +1321,7 @@ func newRPCRawTransactionFromBlockIndex(b *types.Block, index uint64) hexutil.By
 	if index >= uint64(len(txs)) {
 		return nil
 	}
-	blob, _ := rlp.EncodeToBytes(txs[index])
+	blob, _ := txs[index].MarshalBinary()
 	return blob
 }
 
@@ -1218,15 +1335,119 @@ func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransa
 	return nil
 }
 
+// accessListResult returns an optional accesslist
+// Its the result of the `debug_createAccessList` RPC call.
+// It contains an error if the transaction itself failed.
+type accessListResult struct {
+	Accesslist *types.AccessList `json:"accessList"`
+	Error      string            `json:"error,omitempty"`
+	GasUsed    hexutil.Uint64    `json:"gasUsed"`
+}
+
+// CreateAccessList creates a EIP-2930 type AccessList for the given transaction.
+// Reexec and BlockNrOrHash can be specified to create the accessList on top of a certain state.
+func (s *PublicBlockChainAPI) CreateAccessList(ctx context.Context, args SendTxArgs, blockNrOrHash *rpc.BlockNumberOrHash) (*accessListResult, error) {
+	bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+	if blockNrOrHash != nil {
+		bNrOrHash = *blockNrOrHash
+	}
+	acl, gasUsed, vmerr, err := AccessList(ctx, s.b, bNrOrHash, args)
+	if err != nil {
+		return nil, err
+	}
+	result := &accessListResult{Accesslist: &acl, GasUsed: hexutil.Uint64(gasUsed)}
+	if vmerr != nil {
+		result.Error = vmerr.Error()
+	}
+	return result, nil
+}
+
+// AccessList creates an access list for the given transaction.
+// If the accesslist creation fails an error is returned.
+// If the transaction itself fails, an vmErr is returned.
+func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args SendTxArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
+	// Retrieve the execution context
+	db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
+	if db == nil || err != nil {
+		return nil, 0, nil, err
+	}
+	// If the gas amount is not set, extract this as it will depend on access
+	// lists and we'll need to reestimate every time
+	nogas := args.Gas == nil
+
+	// Ensure any missing fields are filled, extract the recipient and input data
+	if err := args.setDefaults(ctx, b); err != nil {
+		return nil, 0, nil, err
+	}
+	var to common.Address
+	if args.To != nil {
+		to = *args.To
+	} else {
+		to = crypto.CreateAddress(args.From, uint64(*args.Nonce))
+	}
+	var input []byte
+	if args.Input != nil {
+		input = *args.Input
+	} else if args.Data != nil {
+		input = *args.Data
+	}
+	// Retrieve the precompiles since they don't need to be added to the access list
+	precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number))
+
+	// Create an initial tracer
+	prevTracer := vm.NewAccessListTracer(nil, args.From, to, precompiles)
+	if args.AccessList != nil {
+		prevTracer = vm.NewAccessListTracer(*args.AccessList, args.From, to, precompiles)
+	}
+	for {
+		// Retrieve the current access list to expand
+		accessList := prevTracer.AccessList()
+		log.Trace("Creating access list", "input", accessList)
+
+		// If no gas amount was specified, each unique access list needs it's own
+		// gas calculation. This is quite expensive, but we need to be accurate
+		// and it's convered by the sender only anyway.
+		if nogas {
+			args.Gas = nil
+			if err := args.setDefaults(ctx, b); err != nil {
+				return nil, 0, nil, err // shouldn't happen, just in case
+			}
+		}
+		// Copy the original db so we don't modify it
+		statedb := db.Copy()
+		msg := types.NewMessage(args.From, args.To, uint64(*args.Nonce), args.Value.ToInt(), uint64(*args.Gas), args.GasPrice.ToInt(), input, accessList, false)
+
+		// Apply the transaction with the access list tracer
+		tracer := vm.NewAccessListTracer(accessList, args.From, to, precompiles)
+		config := vm.Config{Tracer: tracer, Debug: true}
+		vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config)
+		if err != nil {
+			return nil, 0, nil, err
+		}
+		res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
+		if err != nil {
+			return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
+		}
+		if tracer.Equal(prevTracer) {
+			return accessList, res.UsedGas, res.Err, nil
+		}
+		prevTracer = tracer
+	}
+}
+
 // PublicTransactionPoolAPI exposes methods for the RPC interface
 type PublicTransactionPoolAPI struct {
 	b         Backend
 	nonceLock *AddrLocker
+	signer    types.Signer
 }
 
 // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool.
 func NewPublicTransactionPoolAPI(b Backend, nonceLock *AddrLocker) *PublicTransactionPoolAPI {
-	return &PublicTransactionPoolAPI{b, nonceLock}
+	// The signer used by the API should always be the 'latest' known one because we expect
+	// signers to be backwards-compatible with old transactions.
+	signer := types.LatestSigner(b.ChainConfig())
+	return &PublicTransactionPoolAPI{b, nonceLock, signer}
 }
 
 // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number.
@@ -1339,7 +1560,7 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context,
 		}
 	}
 	// Serialize to RLP and return
-	return rlp.EncodeToBytes(tx)
+	return tx.MarshalBinary()
 }
 
 // GetTransactionReceipt returns the transaction receipt for the given transaction hash.
@@ -1471,7 +1692,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionDataAndReceipt(ctx context.Cont
 		fields["contractAddress"] = receipt.ContractAddress
 	}
 	result := map[string]interface{}{
-		"txData": txData,
+		"txData":  txData,
 		"receipt": fields,
 	}
 	return result, nil
@@ -1479,8 +1700,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionDataAndReceipt(ctx context.Cont
 
 // GetTransactionReceipt returns the transaction receipt for the given transaction hash.
 func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
-	tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash)
-	if tx == nil {
+	tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
+	if err != nil {
 		return nil, nil
 	}
 	receipts, err := s.b.GetReceipts(ctx, blockHash)
@@ -1492,10 +1713,9 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
 	}
 	receipt := receipts[index]
 
-	var signer types.Signer = types.FrontierSigner{}
-	if tx.Protected() {
-		signer = types.NewEIP155Signer(tx.ChainId())
-	}
+	// Derive the sender.
+	bigblock := new(big.Int).SetUint64(blockNumber)
+	signer := types.MakeSigner(s.b.ChainConfig(), bigblock)
 	from, _ := types.Sender(signer, tx)
 
 	fields := map[string]interface{}{
@@ -1510,6 +1730,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha
 		"contractAddress":   nil,
 		"logs":              receipt.Logs,
 		"logsBloom":         receipt.Bloom,
+		"type":              hexutil.Uint(tx.Type()),
 	}
 
 	// Assign receipt status or post state.
@@ -1553,9 +1774,13 @@ type SendTxArgs struct {
 	// newer name and should be preferred by clients.
 	Data  *hexutil.Bytes `json:"data"`
 	Input *hexutil.Bytes `json:"input"`
+
+	// For non-legacy transactions
+	AccessList *types.AccessList `json:"accessList,omitempty"`
+	ChainID    *hexutil.Big      `json:"chainId,omitempty"`
 }
 
-// setDefaults is a helper function that fills in default values for unspecified tx fields.
+// setDefaults fills in default values for unspecified tx fields.
 func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
 	if args.GasPrice == nil {
 		price, err := b.SuggestPrice(ctx)
@@ -1598,11 +1823,12 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
 			input = args.Data
 		}
 		callArgs := CallArgs{
-			From:     &args.From, // From shouldn't be nil
-			To:       args.To,
-			GasPrice: args.GasPrice,
-			Value:    args.Value,
-			Data:     input,
+			From:       &args.From, // From shouldn't be nil
+			To:         args.To,
+			GasPrice:   args.GasPrice,
+			Value:      args.Value,
+			Data:       input,
+			AccessList: args.AccessList,
 		}
 		pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
 		estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
@@ -1612,9 +1838,15 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
 		args.Gas = &estimated
 		log.Trace("Estimate gas usage automatically", "gas", args.Gas)
 	}
+	if args.ChainID == nil {
+		id := (*hexutil.Big)(b.ChainConfig().ChainID)
+		args.ChainID = id
+	}
 	return nil
 }
 
+// toTransaction converts the arguments to a transaction.
+// This assumes that setDefaults has been called.
 func (args *SendTxArgs) toTransaction() *types.Transaction {
 	var input []byte
 	if args.Input != nil {
@@ -1622,27 +1854,57 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
 	} else if args.Data != nil {
 		input = *args.Data
 	}
-	if args.To == nil {
-		return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
+	var data types.TxData
+	if args.AccessList == nil {
+		data = &types.LegacyTx{
+			To:       args.To,
+			Nonce:    uint64(*args.Nonce),
+			Gas:      uint64(*args.Gas),
+			GasPrice: (*big.Int)(args.GasPrice),
+			Value:    (*big.Int)(args.Value),
+			Data:     input,
+		}
+	} else {
+		data = &types.AccessListTx{
+			To:         args.To,
+			ChainID:    (*big.Int)(args.ChainID),
+			Nonce:      uint64(*args.Nonce),
+			Gas:        uint64(*args.Gas),
+			GasPrice:   (*big.Int)(args.GasPrice),
+			Value:      (*big.Int)(args.Value),
+			Data:       input,
+			AccessList: *args.AccessList,
+		}
 	}
-	return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
+	return types.NewTx(data)
 }
 
 // SubmitTransaction is a helper function that submits tx to txPool and logs a message.
 func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
+	// If the transaction fee cap is already specified, ensure the
+	// fee of the given transaction is _reasonable_.
+	if err := checkTxFee(tx.GasPrice(), tx.Gas(), b.RPCTxFeeCap()); err != nil {
+		return common.Hash{}, err
+	}
+	if !b.UnprotectedAllowed() && !tx.Protected() {
+		// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
+		return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
+	}
 	if err := b.SendTx(ctx, tx); err != nil {
 		return common.Hash{}, err
 	}
+	// Print a log with full tx details for manual investigations and interventions
+	signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
+	from, err := types.Sender(signer, tx)
+	if err != nil {
+		return common.Hash{}, err
+	}
+
 	if tx.To() == nil {
-		signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
-		from, err := types.Sender(signer, tx)
-		if err != nil {
-			return common.Hash{}, err
-		}
 		addr := crypto.CreateAddress(from, tx.Nonce())
-		log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())
+		log.Info("Submitted contract creation", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "contract", addr.Hex(), "value", tx.Value())
 	} else {
-		log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())
+		log.Info("Submitted transaction", "hash", tx.Hash().Hex(), "from", from, "nonce", tx.Nonce(), "recipient", tx.To(), "value", tx.Value())
 	}
 	return tx.Hash(), nil
 }
@@ -1688,7 +1950,7 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen
 	}
 	// Assemble the transaction and obtain rlp
 	tx := args.toTransaction()
-	data, err := rlp.EncodeToBytes(tx)
+	data, err := tx.MarshalBinary()
 	if err != nil {
 		return nil, err
 	}
@@ -1697,9 +1959,9 @@ func (s *PublicTransactionPoolAPI) FillTransaction(ctx context.Context, args Sen
 
 // SendRawTransaction will add the signed transaction to the transaction pool.
 // The sender is responsible for signing the transaction and using the correct nonce.
-func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
+func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
 	tx := new(types.Transaction)
-	if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
+	if err := tx.UnmarshalBinary(input); err != nil {
 		return common.Hash{}, err
 	}
 	return SubmitTransaction(ctx, s.b, tx)
@@ -1752,11 +2014,15 @@ func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args Sen
 	if err := args.setDefaults(ctx, s.b); err != nil {
 		return nil, err
 	}
+	// Before actually sign the transaction, ensure the transaction fee is reasonable.
+	if err := checkTxFee(args.GasPrice.ToInt(), uint64(*args.Gas), s.b.RPCTxFeeCap()); err != nil {
+		return nil, err
+	}
 	tx, err := s.sign(args.From, args.toTransaction())
 	if err != nil {
 		return nil, err
 	}
-	data, err := rlp.EncodeToBytes(tx)
+	data, err := tx.MarshalBinary()
 	if err != nil {
 		return nil, err
 	}
@@ -1778,11 +2044,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
 	}
 	transactions := make([]*RPCTransaction, 0, len(pending))
 	for _, tx := range pending {
-		var signer types.Signer = types.HomesteadSigner{}
-		if tx.Protected() {
-			signer = types.NewEIP155Signer(tx.ChainId())
-		}
-		from, _ := types.Sender(signer, tx)
+		from, _ := types.Sender(s.signer, tx)
 		if _, exists := accounts[from]; exists {
 			transactions = append(transactions, newRPCPendingTransaction(tx))
 		}
@@ -1800,19 +2062,28 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
 		return common.Hash{}, err
 	}
 	matchTx := sendArgs.toTransaction()
+
+	// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
+	var price = matchTx.GasPrice()
+	if gasPrice != nil {
+		price = gasPrice.ToInt()
+	}
+	var gas = matchTx.Gas()
+	if gasLimit != nil {
+		gas = uint64(*gasLimit)
+	}
+	if err := checkTxFee(price, gas, s.b.RPCTxFeeCap()); err != nil {
+		return common.Hash{}, err
+	}
+	// Iterate the pending list for replacement
 	pending, err := s.b.GetPoolTransactions()
 	if err != nil {
 		return common.Hash{}, err
 	}
-
 	for _, p := range pending {
-		var signer types.Signer = types.HomesteadSigner{}
-		if p.Protected() {
-			signer = types.NewEIP155Signer(p.ChainId())
-		}
-		wantSigHash := signer.Hash(matchTx)
-
-		if pFrom, err := types.Sender(signer, p); err == nil && pFrom == sendArgs.From && signer.Hash(p) == wantSigHash {
+		wantSigHash := s.signer.Hash(matchTx)
+		pFrom, err := types.Sender(s.signer, p)
+		if err == nil && pFrom == sendArgs.From && s.signer.Hash(p) == wantSigHash {
 			// Match. Re-sign and send the transaction.
 			if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
 				sendArgs.GasPrice = gasPrice
@@ -1830,7 +2101,6 @@ func (s *PublicTransactionPoolAPI) Resend(ctx context.Context, sendArgs SendTxAr
 			return signedTx.Hash(), nil
 		}
 	}
-
 	return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
 }
 
@@ -1981,3 +2251,27 @@ func (s *PublicNetAPI) PeerCount() hexutil.Uint {
 func (s *PublicNetAPI) Version() string {
 	return fmt.Sprintf("%d", s.networkVersion)
 }
+
+// checkTxFee is an internal function used to check whether the fee of
+// the given transaction is _reasonable_(under the cap).
+func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
+	// Short circuit if there is no cap for transaction fee at all.
+	if cap == 0 {
+		return nil
+	}
+	feeEth := new(big.Float).Quo(new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas))), new(big.Float).SetInt(big.NewInt(params.Ether)))
+	feeFloat, _ := feeEth.Float64()
+	if feeFloat > cap {
+		return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
+	}
+	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
+}
diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go
index 245091df35..07e76583f3 100644
--- a/internal/ethapi/backend.go
+++ b/internal/ethapi/backend.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/accounts"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/bloombits"
 	"github.com/ethereum/go-ethereum/core/state"
@@ -40,26 +41,29 @@ import (
 type Backend interface {
 	// General Ethereum API
 	Downloader() *downloader.Downloader
-	ProtocolVersion() int
 	SuggestPrice(ctx context.Context) (*big.Int, error)
 	ChainDb() ethdb.Database
 	AccountManager() *accounts.Manager
 	ExtRPCEnabled() bool
-	RPCGasCap() *big.Int // global gas cap for eth_call over rpc: DoS protection
+	RPCGasCap() uint64        // global gas cap for eth_call over rpc: DoS protection
+	RPCTxFeeCap() float64     // global tx fee cap for all transaction related APIs
+	UnprotectedAllowed() bool // allows only for EIP155 transactions.
 
 	// Blockchain API
 	SetHead(number uint64)
 	HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
 	HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
 	HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
+	CurrentHeader() *types.Header
+	CurrentBlock() *types.Block
 	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
 	BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
 	BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
 	StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)
 	StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
 	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
-	GetTd(hash common.Hash) *big.Int
-	GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error)
+	GetTd(ctx context.Context, hash common.Hash) *big.Int
+	GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error)
 	SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
 	SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
 	SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
@@ -83,7 +87,7 @@ type Backend interface {
 	SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
 
 	ChainConfig() *params.ChainConfig
-	CurrentBlock() *types.Block
+	Engine() consensus.Engine
 }
 
 func GetAPIs(apiBackend Backend) []rpc.API {
diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go
new file mode 100644
index 0000000000..43bbcf0201
--- /dev/null
+++ b/internal/flags/helpers.go
@@ -0,0 +1,152 @@
+// 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 .
+
+package flags
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/ethereum/go-ethereum/params"
+	"gopkg.in/urfave/cli.v1"
+)
+
+var (
+	CommandHelpTemplate = `{{.cmd.Name}}{{if .cmd.Subcommands}} command{{end}}{{if .cmd.Flags}} [command options]{{end}} {{.cmd.ArgsUsage}}
+{{if .cmd.Description}}{{.cmd.Description}}
+{{end}}{{if .cmd.Subcommands}}
+SUBCOMMANDS:
+  {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+  {{end}}{{end}}{{if .categorizedFlags}}
+{{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS:
+{{range $categorized.Flags}}{{"\t"}}{{.}}
+{{end}}
+{{end}}{{end}}`
+
+	OriginCommandHelpTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} {{.ArgsUsage}}
+{{if .Description}}{{.Description}}
+{{end}}{{if .Subcommands}}
+SUBCOMMANDS:
+  {{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
+  {{end}}{{end}}{{if .Flags}}
+OPTIONS:
+{{range $.Flags}}   {{.}}
+{{end}}
+{{end}}`
+
+	// AppHelpTemplate is the test template for the default, global app help topic.
+	AppHelpTemplate = `NAME:
+   {{.App.Name}} - {{.App.Usage}}
+
+   Copyright 2013-2021 The go-ethereum Authors
+
+USAGE:
+   {{.App.HelpName}} [options]{{if .App.Commands}} [command] [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
+   {{if .App.Version}}
+VERSION:
+   {{.App.Version}}
+   {{end}}{{if len .App.Authors}}
+AUTHOR(S):
+   {{range .App.Authors}}{{ . }}{{end}}
+   {{end}}{{if .App.Commands}}
+COMMANDS:
+   {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
+   {{end}}{{end}}{{if .FlagGroups}}
+{{range .FlagGroups}}{{.Name}} OPTIONS:
+  {{range .Flags}}{{.}}
+  {{end}}
+{{end}}{{end}}{{if .App.Copyright }}
+COPYRIGHT:
+   {{.App.Copyright}}
+   {{end}}
+`
+	// ClefAppHelpTemplate is the template for the default, global app help topic.
+	ClefAppHelpTemplate = `NAME:
+   {{.App.Name}} - {{.App.Usage}}
+
+   Copyright 2013-2021 The go-ethereum Authors
+
+USAGE:
+   {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
+   {{if .App.Version}}
+COMMANDS:
+   {{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
+   {{end}}{{end}}{{if .FlagGroups}}
+{{range .FlagGroups}}{{.Name}} OPTIONS:
+  {{range .Flags}}{{.}}
+  {{end}}
+{{end}}{{end}}{{if .App.Copyright }}
+COPYRIGHT:
+   {{.App.Copyright}}
+   {{end}}
+`
+)
+
+// HelpData is a one shot struct to pass to the usage template
+type HelpData struct {
+	App        interface{}
+	FlagGroups []FlagGroup
+}
+
+// FlagGroup is a collection of flags belonging to a single topic.
+type FlagGroup struct {
+	Name  string
+	Flags []cli.Flag
+}
+
+// byCategory sorts an array of FlagGroup by Name in the order
+// defined in AppHelpFlagGroups.
+type ByCategory []FlagGroup
+
+func (a ByCategory) Len() int      { return len(a) }
+func (a ByCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByCategory) Less(i, j int) bool {
+	iCat, jCat := a[i].Name, a[j].Name
+	iIdx, jIdx := len(a), len(a) // ensure non categorized flags come last
+
+	for i, group := range a {
+		if iCat == group.Name {
+			iIdx = i
+		}
+		if jCat == group.Name {
+			jIdx = i
+		}
+	}
+
+	return iIdx < jIdx
+}
+
+func FlagCategory(flag cli.Flag, flagGroups []FlagGroup) string {
+	for _, category := range flagGroups {
+		for _, flg := range category.Flags {
+			if flg.GetName() == flag.GetName() {
+				return category.Name
+			}
+		}
+	}
+	return "MISC"
+}
+
+// NewApp creates an app with sane defaults.
+func NewApp(gitCommit, gitDate, usage string) *cli.App {
+	app := cli.NewApp()
+	app.Name = filepath.Base(os.Args[0])
+	app.Author = ""
+	app.Email = ""
+	app.Version = params.VersionWithCommit(gitCommit, gitDate)
+	app.Usage = usage
+	return app
+}
diff --git a/internal/guide/guide_test.go b/internal/guide/guide_test.go
index 9c7ad16d18..abc48e0e4b 100644
--- a/internal/guide/guide_test.go
+++ b/internal/guide/guide_test.go
@@ -31,6 +31,7 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/accounts/keystore"
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
 )
 
@@ -75,7 +76,8 @@ func TestAccountManagement(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Failed to create signer account: %v", err)
 	}
-	tx, chain := new(types.Transaction), big.NewInt(1)
+	tx := types.NewTransaction(0, common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil)
+	chain := big.NewInt(1)
 
 	// Sign a transaction with a single authorization
 	if _, err := ks.SignTxWithPassphrase(signer, "Signer password", tx, chain); err != nil {
diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js
index e42cc2963d..b68f155371 100644
--- a/internal/jsre/deps/web3.js
+++ b/internal/jsre/deps/web3.js
@@ -3056,7 +3056,7 @@ ContractFactory.prototype.at = function (address, callback) {
     var contract = new Contract(this.eth, this.abi, address);
 
     // this functions are not part of prototype,
-    // because we dont want to spoil the interface
+    // because we don't want to spoil the interface
     addFunctionsToContract(contract);
     addEventsToContract(contract);
 
@@ -13675,7 +13675,7 @@ module.exports = BigNumber; // jshint ignore:line
 },{}],"web3":[function(require,module,exports){
 var Web3 = require('./lib/web3');
 
-// dont override global variable
+// don't override global variable
 if (typeof window !== 'undefined' && typeof window.Web3 === 'undefined') {
     window.Web3 = Web3;
 }
diff --git a/internal/utesting/utesting.go b/internal/utesting/utesting.go
new file mode 100644
index 0000000000..ef05a90e4c
--- /dev/null
+++ b/internal/utesting/utesting.go
@@ -0,0 +1,339 @@
+// 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 .
+
+// Package utesting provides a standalone replacement for package testing.
+//
+// This package exists because package testing cannot easily be embedded into a
+// standalone go program. It provides an API that mirrors the standard library
+// testing API.
+package utesting
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"regexp"
+	"runtime"
+	"sync"
+	"time"
+)
+
+// Test represents a single test.
+type Test struct {
+	Name string
+	Fn   func(*T)
+}
+
+// Result is the result of a test execution.
+type Result struct {
+	Name     string
+	Failed   bool
+	Output   string
+	Duration time.Duration
+}
+
+// MatchTests returns the tests whose name matches a regular expression.
+func MatchTests(tests []Test, expr string) []Test {
+	var results []Test
+	re, err := regexp.Compile(expr)
+	if err != nil {
+		return nil
+	}
+	for _, test := range tests {
+		if re.MatchString(test.Name) {
+			results = append(results, test)
+		}
+	}
+	return 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.
+func RunTests(tests []Test, report io.Writer) []Result {
+	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 {
+		buffer := new(bytes.Buffer)
+		logOutput := io.MultiWriter(buffer, output)
+
+		output.testStart(test.Name)
+		start := time.Now()
+		results[i].Name = test.Name
+		results[i].Failed = runTest(test, logOutput)
+		results[i].Duration = time.Since(start)
+		results[i].Output = buffer.String()
+		output.testResult(results[i])
+	}
+	return results
+}
+
+// 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)
+	if r.Failed {
+		fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd)
+	} else {
+		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
+	}
+}
+
+// CountFailures returns the number of failed tests in the result slice.
+func CountFailures(rr []Result) int {
+	count := 0
+	for _, r := range rr {
+		if r.Failed {
+			count++
+		}
+	}
+	return count
+}
+
+// Run executes a single test.
+func Run(test Test) (bool, string) {
+	output := new(bytes.Buffer)
+	failed := runTest(test, output)
+	return failed, output.String()
+}
+
+func runTest(test Test, output io.Writer) bool {
+	t := &T{output: output}
+	done := make(chan struct{})
+	go func() {
+		defer close(done)
+		defer func() {
+			if err := recover(); err != nil {
+				buf := make([]byte, 4096)
+				i := runtime.Stack(buf, false)
+				t.Logf("panic: %v\n\n%s", err, buf[:i])
+				t.Fail()
+			}
+		}()
+		test.Fn(t)
+	}()
+	<-done
+	return t.failed
+}
+
+// T is the value given to the test function. The test can signal failures
+// and log output by calling methods on this object.
+type T struct {
+	mu     sync.Mutex
+	failed bool
+	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
+// runtime.Goexit (which then runs all deferred calls in the current goroutine).
+func (t *T) FailNow() {
+	t.Fail()
+	runtime.Goexit()
+}
+
+// Fail marks the test as having failed but continues execution.
+func (t *T) Fail() {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.failed = true
+}
+
+// Failed reports whether the test has failed.
+func (t *T) Failed() bool {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	return t.failed
+}
+
+// Log formats its arguments using default formatting, analogous to Println, and records
+// the text in the error log.
+func (t *T) Log(vs ...interface{}) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	fmt.Fprintln(t.output, vs...)
+}
+
+// Logf formats its arguments according to the format, analogous to Printf, and records
+// the text in the error log. A final newline is added if not provided.
+func (t *T) Logf(format string, vs ...interface{}) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	if len(format) == 0 || format[len(format)-1] != '\n' {
+		format += "\n"
+	}
+	fmt.Fprintf(t.output, format, vs...)
+}
+
+// Error is equivalent to Log followed by Fail.
+func (t *T) Error(vs ...interface{}) {
+	t.Log(vs...)
+	t.Fail()
+}
+
+// Errorf is equivalent to Logf followed by Fail.
+func (t *T) Errorf(format string, vs ...interface{}) {
+	t.Logf(format, vs...)
+	t.Fail()
+}
+
+// Fatal is equivalent to Log followed by FailNow.
+func (t *T) Fatal(vs ...interface{}) {
+	t.Log(vs...)
+	t.FailNow()
+}
+
+// Fatalf is equivalent to Logf followed by FailNow.
+func (t *T) Fatalf(format string, vs ...interface{}) {
+	t.Logf(format, vs...)
+	t.FailNow()
+}
diff --git a/internal/utesting/utesting_test.go b/internal/utesting/utesting_test.go
new file mode 100644
index 0000000000..31c7911c52
--- /dev/null
+++ b/internal/utesting/utesting_test.go
@@ -0,0 +1,139 @@
+// 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 .
+
+package utesting
+
+import (
+	"bytes"
+	"regexp"
+	"strings"
+	"testing"
+)
+
+func TestTest(t *testing.T) {
+	tests := []Test{
+		{
+			Name: "successful test",
+			Fn:   func(t *T) {},
+		},
+		{
+			Name: "failing test",
+			Fn: func(t *T) {
+				t.Log("output")
+				t.Error("failed")
+			},
+		},
+		{
+			Name: "panicking test",
+			Fn: func(t *T) {
+				panic("oh no")
+			},
+		},
+	}
+	results := RunTests(tests, nil)
+
+	if results[0].Failed || results[0].Output != "" {
+		t.Fatalf("wrong result for successful test: %#v", results[0])
+	}
+	if !results[1].Failed || results[1].Output != "output\nfailed\n" {
+		t.Fatalf("wrong result for failing test: %#v", results[1])
+	}
+	if !results[2].Failed || !strings.HasPrefix(results[2].Output, "panic: oh no\n") {
+		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())
+	}
+}
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index c978667715..1934412c90 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -33,7 +33,7 @@ var Modules = map[string]string{
 	"swarmfs":    SwarmfsJs,
 	"txpool":     TxpoolJs,
 	"les":        LESJs,
-	"lespay":     LESPayJs,
+	"vflux":      VfluxJs,
 }
 
 const ChequebookJs = `
@@ -138,8 +138,8 @@ web3._extend({
 			params: 3,
 		}),
 		new web3._extend.Method({
-			name: 'submitHashRate',
-			call: 'ethash_submitHashRate',
+			name: 'submitHashrate',
+			call: 'ethash_submitHashrate',
 			params: 2,
 		}),
 	]
@@ -186,12 +186,24 @@ web3._extend({
 			call: 'admin_sleepBlocks',
 			params: 2
 		}),
+		new web3._extend.Method({
+			name: 'startHTTP',
+			call: 'admin_startHTTP',
+			params: 5,
+			inputFormatter: [null, null, null, null, null]
+		}),
+		new web3._extend.Method({
+			name: 'stopHTTP',
+			call: 'admin_stopHTTP'
+		}),
+		// This method is deprecated.
 		new web3._extend.Method({
 			name: 'startRPC',
 			call: 'admin_startRPC',
-			params: 4,
-			inputFormatter: [null, null, null, null]
+			params: 5,
+			inputFormatter: [null, null, null, null, null]
 		}),
+		// This method is deprecated.
 		new web3._extend.Method({
 			name: 'stopRPC',
 			call: 'admin_stopRPC'
@@ -231,12 +243,14 @@ web3._extend({
 		new web3._extend.Method({
 			name: 'accountRange',
 			call: 'debug_accountRange',
-			params: 2
+			params: 6,
+			inputFormatter: [web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null, null, null, null],
 		}),
 		new web3._extend.Method({
 			name: 'printBlock',
 			call: 'debug_printBlock',
-			params: 1
+			params: 1,
+			outputFormatter: console.log
 		}),
 		new web3._extend.Method({
 			name: 'getBlockRlp',
@@ -247,7 +261,7 @@ web3._extend({
 			name: 'testSignCliqueBlock',
 			call: 'debug_testSignCliqueBlock',
 			params: 2,
-			inputFormatters: [web3._extend.formatters.inputAddressFormatter, null],
+			inputFormatter: [web3._extend.formatters.inputAddressFormatter, null],
 		}),
 		new web3._extend.Method({
 			name: 'setHead',
@@ -262,7 +276,8 @@ web3._extend({
 		new web3._extend.Method({
 			name: 'dumpBlock',
 			call: 'debug_dumpBlock',
-			params: 1
+			params: 1,
+			inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter]
 		}),
 		new web3._extend.Method({
 			name: 'chaindbProperty',
@@ -414,7 +429,7 @@ web3._extend({
 			name: 'traceBlockByNumber',
 			call: 'debug_traceBlockByNumber',
 			params: 2,
-			inputFormatter: [null, null]
+			inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, null]
 		}),
 		new web3._extend.Method({
 			name: 'traceBlockByHash',
@@ -428,6 +443,12 @@ web3._extend({
 			params: 2,
 			inputFormatter: [null, null]
 		}),
+		new web3._extend.Method({
+			name: 'traceCall',
+			call: 'debug_traceCall',
+			params: 3,
+			inputFormatter: [null, null, null]
+		}),
 		new web3._extend.Method({
 			name: 'preimage',
 			call: 'debug_preimage',
@@ -515,7 +536,8 @@ web3._extend({
 		new web3._extend.Method({
 			name: 'getHeaderByNumber',
 			call: 'eth_getHeaderByNumber',
-			params: 1
+			params: 1,
+			inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter]
 		}),
 		new web3._extend.Method({
 			name: 'getHeaderByHash',
@@ -525,12 +547,14 @@ web3._extend({
 		new web3._extend.Method({
 			name: 'getBlockByNumber',
 			call: 'eth_getBlockByNumber',
-			params: 2
+			params: 2,
+			inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, function (val) { return !!val; }]
 		}),
 		new web3._extend.Method({
 			name: 'getBlockByHash',
 			call: 'eth_getBlockByHash',
-			params: 2
+			params: 2,
+			inputFormatter: [null, function (val) { return !!val; }]
 		}),
 		new web3._extend.Method({
 			name: 'getRawTransaction',
@@ -551,6 +575,12 @@ web3._extend({
 			params: 3,
 			inputFormatter: [web3._extend.formatters.inputAddressFormatter, null, web3._extend.formatters.inputBlockNumberFormatter]
 		}),
+		new web3._extend.Method({
+			name: 'createAccessList',
+			call: 'eth_createAccessList',
+			params: 2,
+			inputFormatter: [null, web3._extend.formatters.inputBlockNumberFormatter],
+		}),
 	],
 	properties: [
 		new web3._extend.Property({
@@ -844,7 +874,7 @@ web3._extend({
 		new web3._extend.Method({
 			name: 'addBalance',
 			call: 'les_addBalance',
-			params: 3
+			params: 2
 		}),
 	],
 	properties:
@@ -865,24 +895,24 @@ web3._extend({
 });
 `
 
-const LESPayJs = `
+const VfluxJs = `
 web3._extend({
-	property: 'lespay',
+	property: 'vflux',
 	methods:
 	[
 		new web3._extend.Method({
 			name: 'distribution',
-			call: 'lespay_distribution',
+			call: 'vflux_distribution',
 			params: 2
 		}),
 		new web3._extend.Method({
 			name: 'timeout',
-			call: 'lespay_timeout',
+			call: 'vflux_timeout',
 			params: 2
 		}),
 		new web3._extend.Method({
 			name: 'value',
-			call: 'lespay_value',
+			call: 'vflux_value',
 			params: 2
 		}),
 	],
@@ -890,7 +920,7 @@ web3._extend({
 	[
 		new web3._extend.Property({
 			name: 'requestStats',
-			getter: 'lespay_requestStats'
+			getter: 'vflux_requestStats'
 		}),
 	]
 });
diff --git a/les/api.go b/les/api.go
index f9b8c34458..782bb31ef2 100644
--- a/les/api.go
+++ b/les/api.go
@@ -19,11 +19,11 @@ package les
 import (
 	"errors"
 	"fmt"
-	"math"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/mclock"
+	vfs "github.com/ethereum/go-ethereum/les/vflux/server"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
@@ -31,24 +31,32 @@ var (
 	errNoCheckpoint         = errors.New("no local checkpoint provided")
 	errNotActivated         = errors.New("checkpoint registrar is not activated")
 	errUnknownBenchmarkType = errors.New("unknown benchmark type")
-	errBalanceOverflow      = errors.New("balance overflow")
-	errNoPriority           = errors.New("priority too low to raise capacity")
 )
 
-const maxBalance = math.MaxInt64
-
 // PrivateLightServerAPI provides an API to access the LES light server.
 type PrivateLightServerAPI struct {
 	server                               *LesServer
-	defaultPosFactors, defaultNegFactors priceFactors
+	defaultPosFactors, defaultNegFactors vfs.PriceFactors
 }
 
 // NewPrivateLightServerAPI creates a new LES light server API.
 func NewPrivateLightServerAPI(server *LesServer) *PrivateLightServerAPI {
 	return &PrivateLightServerAPI{
 		server:            server,
-		defaultPosFactors: server.clientPool.defaultPosFactors,
-		defaultNegFactors: server.clientPool.defaultNegFactors,
+		defaultPosFactors: defaultPosFactors,
+		defaultNegFactors: defaultNegFactors,
+	}
+}
+
+// parseNode parses either an enode address a raw hex node id
+func parseNode(node string) (enode.ID, error) {
+	if id, err := enode.ParseID(node); err == nil {
+		return id, nil
+	}
+	if node, err := enode.Parse(enode.ValidSchemes, node); err == nil {
+		return node.ID(), nil
+	} else {
+		return enode.ID{}, err
 	}
 }
 
@@ -57,18 +65,34 @@ func (api *PrivateLightServerAPI) ServerInfo() map[string]interface{} {
 	res := make(map[string]interface{})
 	res["minimumCapacity"] = api.server.minCapacity
 	res["maximumCapacity"] = api.server.maxCapacity
-	res["freeClientCapacity"] = api.server.freeCapacity
-	res["totalCapacity"], res["totalConnectedCapacity"], res["priorityConnectedCapacity"] = api.server.clientPool.capacityInfo()
+	_, res["totalCapacity"] = api.server.clientPool.Limits()
+	_, res["totalConnectedCapacity"] = api.server.clientPool.Active()
+	res["priorityConnectedCapacity"] = 0 //TODO connect when token sale module is added
 	return res
 }
 
 // ClientInfo returns information about clients listed in the ids list or matching the given tags
-func (api *PrivateLightServerAPI) ClientInfo(ids []enode.ID) map[enode.ID]map[string]interface{} {
+func (api *PrivateLightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]interface{} {
+	var ids []enode.ID
+	for _, node := range nodes {
+		if id, err := parseNode(node); err == nil {
+			ids = append(ids, id)
+		}
+	}
+
 	res := make(map[enode.ID]map[string]interface{})
-	api.server.clientPool.forClients(ids, func(client *clientInfo, id enode.ID) error {
-		res[id] = api.clientInfo(client, id)
-		return nil
-	})
+	if len(ids) == 0 {
+		ids = api.server.peers.ids()
+	}
+	for _, id := range ids {
+		if peer := api.server.peers.peer(id); peer != nil {
+			res[id] = api.clientInfo(peer, peer.balance)
+		} else {
+			api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) {
+				res[id] = api.clientInfo(nil, balance)
+			})
+		}
+	}
 	return res
 }
 
@@ -80,48 +104,44 @@ func (api *PrivateLightServerAPI) ClientInfo(ids []enode.ID) map[enode.ID]map[st
 // assigned to it.
 func (api *PrivateLightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} {
 	res := make(map[enode.ID]map[string]interface{})
-	ids := api.server.clientPool.ndb.getPosBalanceIDs(start, stop, maxCount+1)
+	ids := api.server.clientPool.GetPosBalanceIDs(start, stop, maxCount+1)
 	if len(ids) > maxCount {
 		res[ids[maxCount]] = make(map[string]interface{})
 		ids = ids[:maxCount]
 	}
-	if len(ids) != 0 {
-		api.server.clientPool.forClients(ids, func(client *clientInfo, id enode.ID) error {
-			res[id] = api.clientInfo(client, id)
-			return nil
-		})
+	for _, id := range ids {
+		if peer := api.server.peers.peer(id); peer != nil {
+			res[id] = api.clientInfo(peer, peer.balance)
+		} else {
+			api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) {
+				res[id] = api.clientInfo(nil, balance)
+			})
+		}
 	}
 	return res
 }
 
 // clientInfo creates a client info data structure
-func (api *PrivateLightServerAPI) clientInfo(c *clientInfo, id enode.ID) map[string]interface{} {
+func (api *PrivateLightServerAPI) clientInfo(peer *clientPeer, balance vfs.ReadOnlyBalance) map[string]interface{} {
 	info := make(map[string]interface{})
-	if c != nil {
-		now := mclock.Now()
-		info["isConnected"] = true
-		info["connectionTime"] = float64(now-c.connectedAt) / float64(time.Second)
-		info["capacity"] = c.capacity
-		pb, nb := c.balanceTracker.getBalance(now)
-		info["pricing/balance"], info["pricing/negBalance"] = pb, nb
-		info["pricing/balanceMeta"] = c.balanceMetaInfo
-		info["priority"] = pb != 0
-	} else {
-		info["isConnected"] = false
-		pb := api.server.clientPool.ndb.getOrNewPB(id)
-		info["pricing/balance"], info["pricing/balanceMeta"] = pb.value, pb.meta
-		info["priority"] = pb.value != 0
+	pb, nb := balance.GetBalance()
+	info["isConnected"] = peer != nil
+	info["pricing/balance"] = pb
+	info["priority"] = pb != 0
+	//		cb := api.server.clientPool.ndb.getCurrencyBalance(id)
+	//		info["pricing/currency"] = cb.amount
+	if peer != nil {
+		info["connectionTime"] = float64(mclock.Now()-peer.connectedAt) / float64(time.Second)
+		info["capacity"] = peer.getCapacity()
+		info["pricing/negBalance"] = nb
 	}
 	return info
 }
 
 // setParams either sets the given parameters for a single connected client (if specified)
 // or the default parameters applicable to clients connected in the future
-func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientInfo, posFactors, negFactors *priceFactors) (updateFactors bool, err error) {
+func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, client *clientPeer, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) {
 	defParams := client == nil
-	if !defParams {
-		posFactors, negFactors = &client.posFactors, &client.negFactors
-	}
 	for name, value := range params {
 		errValue := func() error {
 			return fmt.Errorf("invalid value for parameter '%s'", name)
@@ -137,22 +157,21 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien
 
 		switch {
 		case name == "pricing/timeFactor":
-			setFactor(&posFactors.timeFactor)
+			setFactor(&posFactors.TimeFactor)
 		case name == "pricing/capacityFactor":
-			setFactor(&posFactors.capacityFactor)
+			setFactor(&posFactors.CapacityFactor)
 		case name == "pricing/requestCostFactor":
-			setFactor(&posFactors.requestFactor)
+			setFactor(&posFactors.RequestFactor)
 		case name == "pricing/negative/timeFactor":
-			setFactor(&negFactors.timeFactor)
+			setFactor(&negFactors.TimeFactor)
 		case name == "pricing/negative/capacityFactor":
-			setFactor(&negFactors.capacityFactor)
+			setFactor(&negFactors.CapacityFactor)
 		case name == "pricing/negative/requestCostFactor":
-			setFactor(&negFactors.requestFactor)
+			setFactor(&negFactors.RequestFactor)
 		case !defParams && name == "capacity":
 			if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity {
-				err = api.server.clientPool.setCapacity(client, uint64(capacity))
-				// Don't have to call factor update explicitly. It's already done
-				// in setCapacity function.
+				_, err = api.server.clientPool.SetCapacity(client.Node(), uint64(capacity), 0, false)
+				// time factor recalculation is performed automatically by the balance tracker
 			} else {
 				err = errValue()
 			}
@@ -170,38 +189,65 @@ func (api *PrivateLightServerAPI) setParams(params map[string]interface{}, clien
 	return
 }
 
-// AddBalance updates the balance of a client (either overwrites it or adds to it).
-// It also updates the balance meta info string.
-func (api *PrivateLightServerAPI) AddBalance(id enode.ID, value int64, meta string) ([2]uint64, error) {
-	oldBalance, newBalance, err := api.server.clientPool.addBalance(id, value, meta)
-	return [2]uint64{oldBalance, newBalance}, err
-}
-
 // SetClientParams sets client parameters for all clients listed in the ids list
 // or all connected clients if the list is empty
-func (api *PrivateLightServerAPI) SetClientParams(ids []enode.ID, params map[string]interface{}) error {
-	return api.server.clientPool.forClients(ids, func(client *clientInfo, id enode.ID) error {
-		if client != nil {
-			update, err := api.setParams(params, client, nil, nil)
+func (api *PrivateLightServerAPI) SetClientParams(nodes []string, params map[string]interface{}) error {
+	var err error
+	for _, node := range nodes {
+		var id enode.ID
+		if id, err = parseNode(node); err != nil {
+			return err
+		}
+		if peer := api.server.peers.peer(id); peer != nil {
+			posFactors, negFactors := peer.balance.GetPriceFactors()
+			update, e := api.setParams(params, peer, &posFactors, &negFactors)
 			if update {
-				client.updatePriceFactors()
+				peer.balance.SetPriceFactors(posFactors, negFactors)
+			}
+			if e != nil {
+				err = e
 			}
-			return err
 		} else {
-			return fmt.Errorf("client %064x is not connected", id[:])
+			err = fmt.Errorf("client %064x is not connected", id)
 		}
-	})
+	}
+	return err
 }
 
 // SetDefaultParams sets the default parameters applicable to clients connected in the future
 func (api *PrivateLightServerAPI) SetDefaultParams(params map[string]interface{}) error {
 	update, err := api.setParams(params, nil, &api.defaultPosFactors, &api.defaultNegFactors)
 	if update {
-		api.server.clientPool.setDefaultFactors(api.defaultPosFactors, api.defaultNegFactors)
+		api.server.clientPool.SetDefaultFactors(api.defaultPosFactors, api.defaultNegFactors)
 	}
 	return err
 }
 
+// SetConnectedBias set the connection bias, which is applied to already connected clients
+// So that already connected client won't be kicked out very soon and we can ensure all
+// connected clients can have enough time to request or sync some data.
+// When the input parameter `bias` < 0 (illegal), return error.
+func (api *PrivateLightServerAPI) SetConnectedBias(bias time.Duration) error {
+	if bias < time.Duration(0) {
+		return fmt.Errorf("bias illegal: %v less than 0", bias)
+	}
+	api.server.clientPool.SetConnectedBias(bias)
+	return nil
+}
+
+// AddBalance adds the given amount to the balance of a client if possible and returns
+// the balance before and after the operation
+func (api *PrivateLightServerAPI) AddBalance(node string, amount int64) (balance [2]uint64, err error) {
+	var id enode.ID
+	if id, err = parseNode(node); err != nil {
+		return
+	}
+	api.server.clientPool.BalanceOperation(id, "", func(nb vfs.AtomicBalanceOperator) {
+		balance[0], balance[1], err = nb.AddBalance(amount)
+	})
+	return
+}
+
 // Benchmark runs a request performance benchmark with a given set of measurement setups
 // in multiple passes specified by passCount. The measurement time for each setup in each
 // pass is specified in milliseconds by length.
@@ -291,14 +337,20 @@ func NewPrivateDebugAPI(server *LesServer) *PrivateDebugAPI {
 }
 
 // FreezeClient forces a temporary client freeze which normally happens when the server is overloaded
-func (api *PrivateDebugAPI) FreezeClient(id enode.ID) error {
-	return api.server.clientPool.forClients([]enode.ID{id}, func(c *clientInfo, id enode.ID) error {
-		if c == nil {
-			return fmt.Errorf("client %064x is not connected", id[:])
-		}
-		c.peer.freezeClient()
+func (api *PrivateDebugAPI) FreezeClient(node string) error {
+	var (
+		id  enode.ID
+		err error
+	)
+	if id, err = parseNode(node); err != nil {
+		return err
+	}
+	if peer := api.server.peers.peer(id); peer != nil {
+		peer.freeze()
 		return nil
-	})
+	} else {
+		return fmt.Errorf("client %064x is not connected", id[:])
+	}
 }
 
 // PrivateLightAPI provides an API to access the LES light server or light client.
diff --git a/les/api_backend.go b/les/api_backend.go
index 756beaf6a7..60c64a8bdf 100644
--- a/les/api_backend.go
+++ b/les/api_backend.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/accounts"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/bloombits"
 	"github.com/ethereum/go-ethereum/core/rawdb"
@@ -39,9 +40,10 @@ import (
 )
 
 type LesApiBackend struct {
-	extRPCEnabled bool
-	eth           *LightEthereum
-	gpo           *gasprice.Oracle
+	extRPCEnabled       bool
+	allowUnprotectedTxs bool
+	eth                 *LightEthereum
+	gpo                 *gasprice.Oracle
 }
 
 func (b *LesApiBackend) ChainConfig() *params.ChainConfig {
@@ -162,13 +164,20 @@ func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*typ
 	return nil, nil
 }
 
-func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int {
-	return b.eth.blockchain.GetTdByHash(hash)
+func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
+	if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil {
+		return b.eth.blockchain.GetTdOdr(ctx, hash, *number)
+	}
+	return nil
 }
 
-func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) {
-	context := core.NewEVMContext(msg, header, b.eth.blockchain, nil)
-	return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil
+func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
+	if vmConfig == nil {
+		vmConfig = new(vm.Config)
+	}
+	txContext := core.NewEVMTxContext(msg)
+	context := core.NewEVMBlockContext(header, b.eth.blockchain, nil)
+	return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error, nil
 }
 
 func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
@@ -258,10 +267,18 @@ func (b *LesApiBackend) ExtRPCEnabled() bool {
 	return b.extRPCEnabled
 }
 
-func (b *LesApiBackend) RPCGasCap() *big.Int {
+func (b *LesApiBackend) UnprotectedAllowed() bool {
+	return b.allowUnprotectedTxs
+}
+
+func (b *LesApiBackend) RPCGasCap() uint64 {
 	return b.eth.config.RPCGasCap
 }
 
+func (b *LesApiBackend) RPCTxFeeCap() float64 {
+	return b.eth.config.RPCTxFeeCap
+}
+
 func (b *LesApiBackend) BloomStatus() (uint64, uint64) {
 	if b.eth.bloomIndexer == nil {
 		return 0, 0
@@ -275,3 +292,19 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
 		go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
 	}
 }
+
+func (b *LesApiBackend) Engine() consensus.Engine {
+	return b.eth.engine
+}
+
+func (b *LesApiBackend) CurrentHeader() *types.Header {
+	return b.eth.blockchain.CurrentHeader()
+}
+
+func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) {
+	return b.eth.stateAtBlock(ctx, block, reexec)
+}
+
+func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
+	return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
+}
diff --git a/les/api_test.go b/les/api_test.go
index 06a519b62b..f7017c5d98 100644
--- a/les/api_test.go
+++ b/les/api_test.go
@@ -33,6 +33,7 @@ import (
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/les/flowcontrol"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/node"
@@ -55,7 +56,7 @@ func TestMain(m *testing.M) {
 	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
 	// register the Delivery service which will run as a devp2p
 	// protocol when using the exec adapter
-	adapters.RegisterServices(services)
+	adapters.RegisterLifecycles(services)
 	os.Exit(m.Run())
 }
 
@@ -107,7 +108,7 @@ func testCapacityAPI(t *testing.T, clientCount int) {
 			t.Fatalf("Failed to obtain rpc client: %v", err)
 		}
 		headNum, headHash := getHead(ctx, t, serverRpcClient)
-		minCap, freeCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient)
+		minCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient)
 		testCap := totalCap * 3 / 4
 		t.Logf("Server testCap: %d  minCap: %d  head number: %d  head hash: %064x\n", testCap, minCap, headNum, headHash)
 		reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1)))
@@ -202,7 +203,7 @@ func testCapacityAPI(t *testing.T, clientCount int) {
 
 		weights := make([]float64, len(clients))
 		for c := 0; c < 5; c++ {
-			setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), freeCap)
+			setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), minCap)
 			freeIdx = rand.Intn(len(clients))
 			var sum float64
 			for i := range clients {
@@ -214,7 +215,7 @@ func testCapacityAPI(t *testing.T, clientCount int) {
 				sum += weights[i]
 			}
 			for i, client := range clients {
-				weights[i] *= float64(testCap-freeCap-100) / sum
+				weights[i] *= float64(testCap-minCap-100) / sum
 				capacity := uint64(weights[i])
 				if i != freeIdx && capacity < getCapacity(ctx, t, serverRpcClient, client.ID()) {
 					setCapacity(ctx, t, serverRpcClient, client.ID(), capacity)
@@ -227,7 +228,7 @@ func testCapacityAPI(t *testing.T, clientCount int) {
 					setCapacity(ctx, t, serverRpcClient, client.ID(), capacity)
 				}
 			}
-			weights[freeIdx] = float64(freeCap)
+			weights[freeIdx] = float64(minCap)
 			for i := range clients {
 				weights[i] /= float64(testCap)
 			}
@@ -247,7 +248,7 @@ func testCapacityAPI(t *testing.T, clientCount int) {
 				default:
 				}
 
-				_, _, totalCap = getCapacityInfo(ctx, t, serverRpcClient)
+				_, totalCap = getCapacityInfo(ctx, t, serverRpcClient)
 				if totalCap < testCap {
 					t.Log("Total capacity underrun")
 					close(stop)
@@ -370,7 +371,7 @@ func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID
 	return uint64(vv)
 }
 
-func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, freeCap, totalCap uint64) {
+func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, totalCap uint64) {
 	var res map[string]interface{}
 	if err := server.CallContext(ctx, &res, "les_serverInfo"); err != nil {
 		t.Fatalf("Failed to query server info: %v", err)
@@ -387,12 +388,11 @@ func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (min
 		return uint64(vv)
 	}
 	minCap = decode("minimumCapacity")
-	freeCap = decode("freeClientCapacity")
 	totalCap = decode("totalCapacity")
 	return
 }
 
-var services = adapters.Services{
+var services = adapters.LifecycleConstructors{
 	"lesclient": newLesClientService,
 	"lesserver": newLesServerService,
 }
@@ -414,7 +414,7 @@ func NewNetwork() (*simulations.Network, func(), error) {
 	return net, teardown, nil
 }
 
-func NewAdapter(adapterType string, services adapters.Services) (adapter adapters.NodeAdapter, teardown func(), err error) {
+func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) {
 	teardown = func() {}
 	switch adapterType {
 	case "sim":
@@ -454,7 +454,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []
 
 	for i := range clients {
 		clientconf := adapters.RandomNodeConfig()
-		clientconf.Services = []string{"lesclient"}
+		clientconf.Lifecycles = []string{"lesclient"}
 		if len(clientDir) == clientCount {
 			clientconf.DataDir = clientDir[i]
 		}
@@ -467,7 +467,7 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []
 
 	for i := range servers {
 		serverconf := adapters.RandomNodeConfig()
-		serverconf.Services = []string{"lesserver"}
+		serverconf.Lifecycles = []string{"lesserver"}
 		if len(serverDir) == serverCount {
 			serverconf.DataDir = serverDir[i]
 		}
@@ -492,26 +492,25 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []
 	return test(ctx, net, servers, clients)
 }
 
-func newLesClientService(ctx *adapters.ServiceContext) (node.Service, error) {
-	config := eth.DefaultConfig
+func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
+	config := ethconfig.Defaults
 	config.SyncMode = downloader.LightSync
 	config.Ethash.PowMode = ethash.ModeFake
-	return New(ctx.NodeContext, &config)
+	return New(stack, &config)
 }
 
-func newLesServerService(ctx *adapters.ServiceContext) (node.Service, error) {
-	config := eth.DefaultConfig
+func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
+	config := ethconfig.Defaults
 	config.SyncMode = downloader.FullSync
 	config.LightServ = testServerCapacity
 	config.LightPeers = testMaxClients
-	ethereum, err := eth.New(ctx.NodeContext, &config)
+	ethereum, err := eth.New(stack, &config)
 	if err != nil {
 		return nil, err
 	}
-	server, err := NewLesServer(ethereum, &config)
+	_, err = NewLesServer(stack, ethereum, &config)
 	if err != nil {
 		return nil, err
 	}
-	ethereum.AddLesServer(server)
 	return ethereum, nil
 }
diff --git a/les/balance.go b/les/balance.go
deleted file mode 100644
index 51cef15c80..0000000000
--- a/les/balance.go
+++ /dev/null
@@ -1,389 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package les
-
-import (
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common/mclock"
-)
-
-const (
-	balanceCallbackQueue = iota
-	balanceCallbackZero
-	balanceCallbackCount
-)
-
-// balanceTracker keeps track of the positive and negative balances of a connected
-// client and calculates actual and projected future priority values required by
-// prque.LazyQueue.
-type balanceTracker struct {
-	lock                             sync.Mutex
-	clock                            mclock.Clock
-	stopped                          bool
-	capacity                         uint64
-	balance                          balance
-	timeFactor, requestFactor        float64
-	negTimeFactor, negRequestFactor  float64
-	sumReqCost                       uint64
-	lastUpdate, nextUpdate, initTime mclock.AbsTime
-	updateEvent                      mclock.Timer
-	// since only a limited and fixed number of callbacks are needed, they are
-	// stored in a fixed size array ordered by priority threshold.
-	callbacks [balanceCallbackCount]balanceCallback
-	// callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active)
-	callbackIndex [balanceCallbackCount]int
-	callbackCount int // number of active callbacks
-}
-
-// balance represents a pair of positive and negative balances
-type balance struct {
-	pos, neg uint64
-}
-
-// balanceCallback represents a single callback that is activated when client priority
-// reaches the given threshold
-type balanceCallback struct {
-	id        int
-	threshold int64
-	callback  func()
-}
-
-// init initializes balanceTracker
-func (bt *balanceTracker) init(clock mclock.Clock, capacity uint64) {
-	bt.clock = clock
-	bt.initTime, bt.lastUpdate = clock.Now(), clock.Now() // Init timestamps
-	for i := range bt.callbackIndex {
-		bt.callbackIndex[i] = -1
-	}
-	bt.capacity = capacity
-}
-
-// stop shuts down the balance tracker
-func (bt *balanceTracker) stop(now mclock.AbsTime) {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	bt.stopped = true
-	bt.addBalance(now)
-	bt.negTimeFactor = 0
-	bt.negRequestFactor = 0
-	bt.timeFactor = 0
-	bt.requestFactor = 0
-	if bt.updateEvent != nil {
-		bt.updateEvent.Stop()
-		bt.updateEvent = nil
-	}
-}
-
-// balanceToPriority converts a balance to a priority value. Higher priority means
-// first to disconnect. Positive balance translates to negative priority. If positive
-// balance is zero then negative balance translates to a positive priority.
-func (bt *balanceTracker) balanceToPriority(b balance) int64 {
-	if b.pos > 0 {
-		return ^int64(b.pos / bt.capacity)
-	}
-	return int64(b.neg)
-}
-
-// reducedBalance estimates the reduced balance at a given time in the fututre based
-// on the current balance, the time factor and an estimated average request cost per time ratio
-func (bt *balanceTracker) reducedBalance(at mclock.AbsTime, avgReqCost float64) balance {
-	dt := float64(at - bt.lastUpdate)
-	b := bt.balance
-	if b.pos != 0 {
-		factor := bt.timeFactor + bt.requestFactor*avgReqCost
-		diff := uint64(dt * factor)
-		if diff <= b.pos {
-			b.pos -= diff
-			dt = 0
-		} else {
-			dt -= float64(b.pos) / factor
-			b.pos = 0
-		}
-	}
-	if dt != 0 {
-		factor := bt.negTimeFactor + bt.negRequestFactor*avgReqCost
-		b.neg += uint64(dt * factor)
-	}
-	return b
-}
-
-// timeUntil calculates the remaining time needed to reach a given priority level
-// assuming that no requests are processed until then. If the given level is never
-// reached then (0, false) is returned.
-// Note: the function assumes that the balance has been recently updated and
-// calculates the time starting from the last update.
-func (bt *balanceTracker) timeUntil(priority int64) (time.Duration, bool) {
-	var dt float64
-	if bt.balance.pos != 0 {
-		if bt.timeFactor < 1e-100 {
-			return 0, false
-		}
-		if priority < 0 {
-			newBalance := uint64(^priority) * bt.capacity
-			if newBalance > bt.balance.pos {
-				return 0, false
-			}
-			dt = float64(bt.balance.pos-newBalance) / bt.timeFactor
-			return time.Duration(dt), true
-		} else {
-			dt = float64(bt.balance.pos) / bt.timeFactor
-		}
-	} else {
-		if priority < 0 {
-			return 0, false
-		}
-	}
-	// if we have a positive balance then dt equals the time needed to get it to zero
-	if uint64(priority) > bt.balance.neg {
-		if bt.negTimeFactor < 1e-100 {
-			return 0, false
-		}
-		dt += float64(uint64(priority)-bt.balance.neg) / bt.negTimeFactor
-	}
-	return time.Duration(dt), true
-}
-
-// setCapacity updates the capacity value used for priority calculation
-func (bt *balanceTracker) setCapacity(capacity uint64) {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	bt.capacity = capacity
-}
-
-// getPriority returns the actual priority based on the current balance
-func (bt *balanceTracker) getPriority(now mclock.AbsTime) int64 {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	bt.addBalance(now)
-	return bt.balanceToPriority(bt.balance)
-}
-
-// estimatedPriority gives an upper estimate for the priority at a given time in the future.
-// If addReqCost is true then an average request cost per time is assumed that is twice the
-// average cost per time in the current session. If false, zero request cost is assumed.
-func (bt *balanceTracker) estimatedPriority(at mclock.AbsTime, addReqCost bool) int64 {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	var avgReqCost float64
-	if addReqCost {
-		dt := time.Duration(bt.lastUpdate - bt.initTime)
-		if dt > time.Second {
-			avgReqCost = float64(bt.sumReqCost) * 2 / float64(dt)
-		}
-	}
-	return bt.balanceToPriority(bt.reducedBalance(at, avgReqCost))
-}
-
-// addBalance updates balance based on the time factor
-func (bt *balanceTracker) addBalance(now mclock.AbsTime) {
-	if now > bt.lastUpdate {
-		bt.balance = bt.reducedBalance(now, 0)
-		bt.lastUpdate = now
-	}
-}
-
-// checkCallbacks checks whether the threshold of any of the active callbacks
-// have been reached and calls them if necessary. It also sets up or updates
-// a scheduled event to ensure that is will be called again just after the next
-// threshold has been reached.
-// Note: checkCallbacks assumes that the balance has been recently updated.
-func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) {
-	if bt.callbackCount == 0 {
-		return
-	}
-	pri := bt.balanceToPriority(bt.balance)
-	for bt.callbackCount != 0 && bt.callbacks[bt.callbackCount-1].threshold <= pri {
-		bt.callbackCount--
-		bt.callbackIndex[bt.callbacks[bt.callbackCount].id] = -1
-		go bt.callbacks[bt.callbackCount].callback()
-	}
-	if bt.callbackCount != 0 {
-		d, ok := bt.timeUntil(bt.callbacks[bt.callbackCount-1].threshold)
-		if !ok {
-			bt.nextUpdate = 0
-			bt.updateAfter(0)
-			return
-		}
-		if bt.nextUpdate == 0 || bt.nextUpdate > now+mclock.AbsTime(d) {
-			if d > time.Second {
-				// Note: if the scheduled update is not in the very near future then we
-				// schedule the update a bit earlier. This way we do need to update a few
-				// extra times but don't need to reschedule every time a processed request
-				// brings the expected firing time a little bit closer.
-				d = ((d - time.Second) * 7 / 8) + time.Second
-			}
-			bt.nextUpdate = now + mclock.AbsTime(d)
-			bt.updateAfter(d)
-		}
-	} else {
-		bt.nextUpdate = 0
-		bt.updateAfter(0)
-	}
-}
-
-// updateAfter schedules a balance update and callback check in the future
-func (bt *balanceTracker) updateAfter(dt time.Duration) {
-	if bt.updateEvent == nil || bt.updateEvent.Stop() {
-		if dt == 0 {
-			bt.updateEvent = nil
-		} else {
-			bt.updateEvent = bt.clock.AfterFunc(dt, func() {
-				bt.lock.Lock()
-				defer bt.lock.Unlock()
-
-				if bt.callbackCount != 0 {
-					now := bt.clock.Now()
-					bt.addBalance(now)
-					bt.checkCallbacks(now)
-				}
-			})
-		}
-	}
-}
-
-// requestCost should be called after serving a request for the given peer
-func (bt *balanceTracker) requestCost(cost uint64) {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	if bt.stopped {
-		return
-	}
-	now := bt.clock.Now()
-	bt.addBalance(now)
-	fcost := float64(cost)
-
-	if bt.balance.pos != 0 {
-		if bt.requestFactor != 0 {
-			c := uint64(fcost * bt.requestFactor)
-			if bt.balance.pos >= c {
-				bt.balance.pos -= c
-				fcost = 0
-			} else {
-				fcost *= 1 - float64(bt.balance.pos)/float64(c)
-				bt.balance.pos = 0
-			}
-			bt.checkCallbacks(now)
-		} else {
-			fcost = 0
-		}
-	}
-	if fcost > 0 {
-		if bt.negRequestFactor != 0 {
-			bt.balance.neg += uint64(fcost * bt.negRequestFactor)
-			bt.checkCallbacks(now)
-		}
-	}
-	bt.sumReqCost += cost
-}
-
-// getBalance returns the current positive and negative balance
-func (bt *balanceTracker) getBalance(now mclock.AbsTime) (uint64, uint64) {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	bt.addBalance(now)
-	return bt.balance.pos, bt.balance.neg
-}
-
-// setBalance sets the positive and negative balance to the given values
-func (bt *balanceTracker) setBalance(pos, neg uint64) error {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	now := bt.clock.Now()
-	bt.addBalance(now)
-	bt.balance.pos = pos
-	bt.balance.neg = neg
-	bt.checkCallbacks(now)
-	return nil
-}
-
-// setFactors sets the price factors. timeFactor is the price of a nanosecond of
-// connection while requestFactor is the price of a "realCost" unit.
-func (bt *balanceTracker) setFactors(neg bool, timeFactor, requestFactor float64) {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	if bt.stopped {
-		return
-	}
-	now := bt.clock.Now()
-	bt.addBalance(now)
-	if neg {
-		bt.negTimeFactor = timeFactor
-		bt.negRequestFactor = requestFactor
-	} else {
-		bt.timeFactor = timeFactor
-		bt.requestFactor = requestFactor
-	}
-	bt.checkCallbacks(now)
-}
-
-// setCallback sets up a one-time callback to be called when priority reaches
-// the threshold. If it has already reached the threshold the callback is called
-// immediately.
-func (bt *balanceTracker) addCallback(id int, threshold int64, callback func()) {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	bt.removeCb(id)
-	idx := 0
-	for idx < bt.callbackCount && threshold < bt.callbacks[idx].threshold {
-		idx++
-	}
-	for i := bt.callbackCount - 1; i >= idx; i-- {
-		bt.callbackIndex[bt.callbacks[i].id]++
-		bt.callbacks[i+1] = bt.callbacks[i]
-	}
-	bt.callbackCount++
-	bt.callbackIndex[id] = idx
-	bt.callbacks[idx] = balanceCallback{id, threshold, callback}
-	now := bt.clock.Now()
-	bt.addBalance(now)
-	bt.checkCallbacks(now)
-}
-
-// removeCallback removes the given callback and returns true if it was active
-func (bt *balanceTracker) removeCallback(id int) bool {
-	bt.lock.Lock()
-	defer bt.lock.Unlock()
-
-	return bt.removeCb(id)
-}
-
-// removeCb removes the given callback and returns true if it was active
-// Note: should be called while bt.lock is held
-func (bt *balanceTracker) removeCb(id int) bool {
-	idx := bt.callbackIndex[id]
-	if idx == -1 {
-		return false
-	}
-	bt.callbackIndex[id] = -1
-	for i := idx; i < bt.callbackCount-1; i++ {
-		bt.callbackIndex[bt.callbacks[i+1].id]--
-		bt.callbacks[i] = bt.callbacks[i+1]
-	}
-	bt.callbackCount--
-	return true
-}
diff --git a/les/balance_test.go b/les/balance_test.go
deleted file mode 100644
index b571c2cc5c..0000000000
--- a/les/balance_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package les
-
-import (
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common/mclock"
-)
-
-func TestSetBalance(t *testing.T) {
-	var clock = &mclock.Simulated{}
-	var inputs = []struct {
-		pos uint64
-		neg uint64
-	}{
-		{1000, 0},
-		{0, 1000},
-		{1000, 1000},
-	}
-
-	tracker := balanceTracker{}
-	tracker.init(clock, 1000)
-	defer tracker.stop(clock.Now())
-
-	for _, i := range inputs {
-		tracker.setBalance(i.pos, i.neg)
-		pos, neg := tracker.getBalance(clock.Now())
-		if pos != i.pos {
-			t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos)
-		}
-		if neg != i.neg {
-			t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg)
-		}
-	}
-}
-
-func TestBalanceTimeCost(t *testing.T) {
-	var (
-		clock   = &mclock.Simulated{}
-		tracker = balanceTracker{}
-	)
-	tracker.init(clock, 1000)
-	defer tracker.stop(clock.Now())
-	tracker.setFactors(false, 1, 1)
-	tracker.setFactors(true, 1, 1)
-
-	tracker.setBalance(uint64(time.Minute), 0) // 1 minute time allowance
-
-	var inputs = []struct {
-		runTime time.Duration
-		expPos  uint64
-		expNeg  uint64
-	}{
-		{time.Second, uint64(time.Second * 59), 0},
-		{0, uint64(time.Second * 59), 0},
-		{time.Second * 59, 0, 0},
-		{time.Second, 0, uint64(time.Second)},
-	}
-	for _, i := range inputs {
-		clock.Run(i.runTime)
-		if pos, _ := tracker.getBalance(clock.Now()); pos != i.expPos {
-			t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
-		}
-		if _, neg := tracker.getBalance(clock.Now()); neg != i.expNeg {
-			t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
-		}
-	}
-
-	tracker.setBalance(uint64(time.Minute), 0) // Refill 1 minute time allowance
-	for _, i := range inputs {
-		clock.Run(i.runTime)
-		if pos, _ := tracker.getBalance(clock.Now()); pos != i.expPos {
-			t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
-		}
-		if _, neg := tracker.getBalance(clock.Now()); neg != i.expNeg {
-			t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
-		}
-	}
-}
-
-func TestBalanceReqCost(t *testing.T) {
-	var (
-		clock   = &mclock.Simulated{}
-		tracker = balanceTracker{}
-	)
-	tracker.init(clock, 1000)
-	defer tracker.stop(clock.Now())
-	tracker.setFactors(false, 1, 1)
-	tracker.setFactors(true, 1, 1)
-
-	tracker.setBalance(uint64(time.Minute), 0) // 1 minute time serving time allowance
-	var inputs = []struct {
-		reqCost uint64
-		expPos  uint64
-		expNeg  uint64
-	}{
-		{uint64(time.Second), uint64(time.Second * 59), 0},
-		{0, uint64(time.Second * 59), 0},
-		{uint64(time.Second * 59), 0, 0},
-		{uint64(time.Second), 0, uint64(time.Second)},
-	}
-	for _, i := range inputs {
-		tracker.requestCost(i.reqCost)
-		if pos, _ := tracker.getBalance(clock.Now()); pos != i.expPos {
-			t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
-		}
-		if _, neg := tracker.getBalance(clock.Now()); neg != i.expNeg {
-			t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
-		}
-	}
-}
-
-func TestBalanceToPriority(t *testing.T) {
-	var (
-		clock   = &mclock.Simulated{}
-		tracker = balanceTracker{}
-	)
-	tracker.init(clock, 1000) // cap = 1000
-	defer tracker.stop(clock.Now())
-	tracker.setFactors(false, 1, 1)
-	tracker.setFactors(true, 1, 1)
-
-	var inputs = []struct {
-		pos      uint64
-		neg      uint64
-		priority int64
-	}{
-		{1000, 0, ^int64(1)},
-		{2000, 0, ^int64(2)}, // Higher balance, lower priority value
-		{0, 0, 0},
-		{0, 1000, 1000},
-	}
-	for _, i := range inputs {
-		tracker.setBalance(i.pos, i.neg)
-		priority := tracker.getPriority(clock.Now())
-		if priority != i.priority {
-			t.Fatalf("Priority mismatch, want %v, got %v", i.priority, priority)
-		}
-	}
-}
-
-func TestEstimatedPriority(t *testing.T) {
-	var (
-		clock   = &mclock.Simulated{}
-		tracker = balanceTracker{}
-	)
-	tracker.init(clock, 1000000000) // cap = 1000,000,000
-	defer tracker.stop(clock.Now())
-	tracker.setFactors(false, 1, 1)
-	tracker.setFactors(true, 1, 1)
-
-	tracker.setBalance(uint64(time.Minute), 0)
-	var inputs = []struct {
-		runTime    time.Duration // time cost
-		futureTime time.Duration // diff of future time
-		reqCost    uint64        // single request cost
-		priority   int64         // expected estimated priority
-	}{
-		{time.Second, time.Second, 0, ^int64(58)},
-		{0, time.Second, 0, ^int64(58)},
-
-		// 2 seconds time cost, 1 second estimated time cost, 10^9 request cost,
-		// 10^9 estimated request cost per second.
-		{time.Second, time.Second, 1000000000, ^int64(55)},
-
-		// 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost,
-		// 4*10^9 estimated request cost.
-		{time.Second, 3 * time.Second, 1000000000, ^int64(48)},
-
-		// All positive balance is used up
-		{time.Second * 55, 0, 0, 0},
-
-		// 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec.
-		{0, time.Minute, 0, int64(time.Minute) + int64(time.Second)*120/29},
-	}
-	for _, i := range inputs {
-		clock.Run(i.runTime)
-		tracker.requestCost(i.reqCost)
-		priority := tracker.estimatedPriority(clock.Now()+mclock.AbsTime(i.futureTime), true)
-		if priority != i.priority {
-			t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority)
-		}
-	}
-}
-
-func TestCallbackChecking(t *testing.T) {
-	var (
-		clock   = &mclock.Simulated{}
-		tracker = balanceTracker{}
-	)
-	tracker.init(clock, 1000000) // cap = 1000,000
-	defer tracker.stop(clock.Now())
-	tracker.setFactors(false, 1, 1)
-	tracker.setFactors(true, 1, 1)
-
-	var inputs = []struct {
-		priority int64
-		expDiff  time.Duration
-	}{
-		{^int64(500), time.Millisecond * 500},
-		{0, time.Second},
-		{int64(time.Second), 2 * time.Second},
-	}
-	tracker.setBalance(uint64(time.Second), 0)
-	for _, i := range inputs {
-		diff, _ := tracker.timeUntil(i.priority)
-		if diff != i.expDiff {
-			t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff)
-		}
-	}
-}
-
-func TestCallback(t *testing.T) {
-	var (
-		clock   = &mclock.Simulated{}
-		tracker = balanceTracker{}
-	)
-	tracker.init(clock, 1000) // cap = 1000
-	defer tracker.stop(clock.Now())
-	tracker.setFactors(false, 1, 1)
-	tracker.setFactors(true, 1, 1)
-
-	callCh := make(chan struct{}, 1)
-	tracker.setBalance(uint64(time.Minute), 0)
-	tracker.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} })
-
-	clock.Run(time.Minute)
-	select {
-	case <-callCh:
-	case <-time.NewTimer(time.Second).C:
-		t.Fatalf("Callback hasn't been called yet")
-	}
-
-	tracker.setBalance(uint64(time.Minute), 0)
-	tracker.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} })
-	tracker.removeCallback(balanceCallbackZero)
-
-	clock.Run(time.Minute)
-	select {
-	case <-callCh:
-		t.Fatalf("Callback shouldn't be called")
-	case <-time.NewTimer(time.Millisecond * 100).C:
-	}
-}
diff --git a/les/benchmark.go b/les/benchmark.go
index a146de2fed..757822a6b3 100644
--- a/les/benchmark.go
+++ b/les/benchmark.go
@@ -75,9 +75,8 @@ func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error {
 func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error {
 	if b.byHash {
 		return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse)
-	} else {
-		return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
 	}
+	return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse)
 }
 
 // benchmarkBodiesOrReceipts implements requestBenchmark
@@ -98,9 +97,8 @@ func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error {
 func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error {
 	if b.receipts {
 		return peer.requestReceipts(0, []common.Hash{b.hashes[index]})
-	} else {
-		return peer.requestBodies(0, []common.Hash{b.hashes[index]})
 	}
+	return peer.requestBodies(0, []common.Hash{b.hashes[index]})
 }
 
 // benchmarkProofsOrCode implements requestBenchmark
@@ -119,9 +117,8 @@ func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error {
 	rand.Read(key)
 	if b.code {
 		return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}})
-	} else {
-		return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}})
 	}
+	return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}})
 }
 
 // benchmarkHelperTrie implements requestBenchmark
@@ -159,7 +156,7 @@ func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error {
 		for i := range reqs {
 			key := make([]byte, 8)
 			binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum))))
-			reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: auxHeader}
+			reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: htAuxHeader}
 		}
 	}
 
@@ -174,7 +171,7 @@ type benchmarkTxSend struct {
 func (b *benchmarkTxSend) init(h *serverHandler, count int) error {
 	key, _ := crypto.GenerateKey()
 	addr := crypto.PubkeyToAddress(key.PublicKey)
-	signer := types.NewEIP155Signer(big.NewInt(18))
+	signer := types.LatestSigner(h.server.chainConfig)
 	b.txs = make(types.Transactions, count)
 
 	for i := range b.txs {
diff --git a/les/checkpointoracle/oracle.go b/les/checkpointoracle/oracle.go
index c3983e1a95..003eb17df7 100644
--- a/les/checkpointoracle/oracle.go
+++ b/les/checkpointoracle/oracle.go
@@ -21,7 +21,9 @@ package checkpointoracle
 
 import (
 	"encoding/binary"
+	"sync"
 	"sync/atomic"
+	"time"
 
 	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
@@ -40,20 +42,15 @@ type CheckpointOracle struct {
 
 	running  int32                                 // Flag whether the contract backend is set or not
 	getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint
+
+	checkMu              sync.Mutex                // Mutex to sync access to the fields below
+	lastCheckTime        time.Time                 // Time we last checked the checkpoint
+	lastCheckPoint       *params.TrustedCheckpoint // The last stable checkpoint
+	lastCheckPointHeight uint64                    // The height of last stable checkpoint
 }
 
 // New creates a checkpoint oracle handler with given configs and callback.
 func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle {
-	if config == nil {
-		log.Info("Checkpoint registrar is not enabled")
-		return nil
-	}
-	if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
-		log.Warn("Invalid checkpoint registrar config")
-		return nil
-	}
-	log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold)
-
 	return &CheckpointOracle{
 		config:   config,
 		getLocal: getLocal,
@@ -88,10 +85,19 @@ func (oracle *CheckpointOracle) Contract() *checkpointoracle.CheckpointOracle {
 // StableCheckpoint returns the stable checkpoint which was generated by local
 // indexers and announced by trusted signers.
 func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, uint64) {
+	oracle.checkMu.Lock()
+	defer oracle.checkMu.Unlock()
+	if time.Since(oracle.lastCheckTime) < 1*time.Minute {
+		return oracle.lastCheckPoint, oracle.lastCheckPointHeight
+	}
+	// Look it up properly
 	// Retrieve the latest checkpoint from the contract, abort if empty
 	latest, hash, height, err := oracle.contract.Contract().GetLatestCheckpoint(nil)
+	oracle.lastCheckTime = time.Now()
 	if err != nil || (latest == 0 && hash == [32]byte{}) {
-		return nil, 0
+		oracle.lastCheckPointHeight = 0
+		oracle.lastCheckPoint = nil
+		return oracle.lastCheckPoint, oracle.lastCheckPointHeight
 	}
 	local := oracle.getLocal(latest)
 
@@ -103,7 +109,9 @@ func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, u
 	//
 	// In both cases, no stable checkpoint will be returned.
 	if local.HashEqual(hash) {
-		return &local, height.Uint64()
+		oracle.lastCheckPointHeight = height.Uint64()
+		oracle.lastCheckPoint = &local
+		return oracle.lastCheckPoint, oracle.lastCheckPointHeight
 	}
 	return nil, 0
 }
diff --git a/les/client.go b/les/client.go
index 091f89380f..465e2c3723 100644
--- a/les/client.go
+++ b/les/client.go
@@ -22,7 +22,6 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/accounts"
-	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/mclock"
@@ -31,36 +30,39 @@ import (
 	"github.com/ethereum/go-ethereum/core/bloombits"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/eth/filters"
 	"github.com/ethereum/go-ethereum/eth/gasprice"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/internal/ethapi"
-	"github.com/ethereum/go-ethereum/les/checkpointoracle"
-	lpc "github.com/ethereum/go-ethereum/les/lespay/client"
+	"github.com/ethereum/go-ethereum/les/vflux"
+	vfc "github.com/ethereum/go-ethereum/les/vflux/client"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
 type LightEthereum struct {
 	lesCommons
 
-	peers        *serverPeerSet
-	reqDist      *requestDistributor
-	retriever    *retrieveManager
-	odr          *LesOdr
-	relay        *lesTxRelay
-	handler      *clientHandler
-	txPool       *light.TxPool
-	blockchain   *light.LightChain
-	serverPool   *serverPool
-	valueTracker *lpc.ValueTracker
+	peers              *serverPeerSet
+	reqDist            *requestDistributor
+	retriever          *retrieveManager
+	odr                *LesOdr
+	relay              *lesTxRelay
+	handler            *clientHandler
+	txPool             *light.TxPool
+	blockchain         *light.LightChain
+	serverPool         *vfc.ServerPool
+	serverPoolIterator enode.Iterator
+	pruner             *pruner
 
 	bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
 	bloomIndexer  *core.ChainIndexer             // Bloom indexer operating during block imports
@@ -70,19 +72,23 @@ type LightEthereum struct {
 	engine         consensus.Engine
 	accountManager *accounts.Manager
 	netRPCService  *ethapi.PublicNetAPI
+
+	p2pServer  *p2p.Server
+	p2pConfig  *p2p.Config
+	udpEnabled bool
 }
 
-func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
-	chainDb, err := ctx.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/")
+// New creates an instance of the light client.
+func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
+	chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
 	if err != nil {
 		return nil, err
 	}
-	lespayDb, err := ctx.OpenDatabase("lespay", 0, 0, "eth/db/lespay")
+	lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/", false)
 	if err != nil {
 		return nil, err
 	}
-	chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis,
-		config.OverrideIstanbul, config.OverrideMuirGlacier)
+	chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideBerlin)
 	if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat {
 		return nil, genesisErr
 	}
@@ -96,25 +102,34 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
 			chainConfig: chainConfig,
 			iConfig:     light.DefaultClientIndexerConfig,
 			chainDb:     chainDb,
+			lesDb:       lesDb,
 			closeCh:     make(chan struct{}),
 		},
 		peers:          peers,
-		eventMux:       ctx.EventMux,
+		eventMux:       stack.EventMux(),
 		reqDist:        newRequestDistributor(peers, &mclock.System{}),
-		accountManager: ctx.AccountManager,
-		engine:         eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, false, chainDb, nil, genesisHash),
+		accountManager: stack.AccountManager(),
+		engine:         ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb, nil, genesisHash),
 		bloomRequests:  make(chan chan *bloombits.Retrieval),
-		bloomIndexer:   eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
-		serverPool:     newServerPool(chainDb, config.UltraLightServers),
-		valueTracker:   lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)),
+		bloomIndexer:   core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
+		p2pServer:      stack.Server(),
+		p2pConfig:      &stack.Config().P2P,
+		udpEnabled:     stack.Config().P2P.DiscoveryV5,
+	}
+
+	var prenegQuery vfc.QueryFunc
+	if leth.udpEnabled {
+		prenegQuery = leth.prenegQuery
 	}
-	peers.subscribe((*vtSubscription)(leth.valueTracker))
-	leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
+	leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, config.UltraLightServers, requestList)
+	leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter)
+
+	leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout)
 	leth.relay = newLesTxRelay(peers, leth.retriever)
 
-	leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever)
-	leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations)
-	leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency)
+	leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever)
+	leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune)
+	leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune)
 	leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
 
 	checkpoint := config.Checkpoint
@@ -130,22 +145,16 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
 	leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
 
 	// Set up checkpoint oracle.
-	oracle := config.CheckpointOracle
-	if oracle == nil {
-		oracle = params.CheckpointOracles[genesisHash]
-	}
-	leth.oracle = checkpointoracle.New(oracle, leth.localCheckpoint)
+	leth.oracle = leth.setupOracle(stack, genesisHash, config)
 
 	// Note: AddChildIndexer starts the update process for the child
 	leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
 	leth.chtIndexer.Start(leth.blockchain)
 	leth.bloomIndexer.Start(leth.blockchain)
 
-	leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth)
-	if leth.handler.ulc != nil {
-		log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction)
-		leth.blockchain.DisableCheckFreq()
-	}
+	// Start a light chain pruner to delete useless historical data.
+	leth.pruner = newPruner(chainDb, leth.chtIndexer, leth.bloomTrieIndexer)
+
 	// Rewind the chain in case of an incompatible config upgrade.
 	if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
 		log.Warn("Rewinding chain to upgrade configuration", "err", compat)
@@ -153,31 +162,102 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
 		rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
 	}
 
-	leth.ApiBackend = &LesApiBackend{ctx.ExtRPCEnabled(), leth, nil}
+	leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil}
 	gpoParams := config.GPO
 	if gpoParams.Default == nil {
 		gpoParams.Default = config.Miner.GasPrice
 	}
 	leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
 
+	leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth)
+	if leth.handler.ulc != nil {
+		log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction)
+		leth.blockchain.DisableCheckFreq()
+	}
+
+	leth.netRPCService = ethapi.NewPublicNetAPI(leth.p2pServer, leth.config.NetworkId)
+
+	// Register the backend on the node
+	stack.RegisterAPIs(leth.APIs())
+	stack.RegisterProtocols(leth.Protocols())
+	stack.RegisterLifecycle(leth)
+
+	// Check for unclean shutdown
+	if uncleanShutdowns, discards, err := rawdb.PushUncleanShutdownMarker(chainDb); err != nil {
+		log.Error("Could not update unclean-shutdown-marker list", "error", err)
+	} else {
+		if discards > 0 {
+			log.Warn("Old unclean shutdowns found", "count", discards)
+		}
+		for _, tstamp := range uncleanShutdowns {
+			t := time.Unix(int64(tstamp), 0)
+			log.Warn("Unclean shutdown detected", "booted", t,
+				"age", common.PrettyAge(t))
+		}
+	}
 	return leth, nil
 }
 
-// vtSubscription implements serverPeerSubscriber
-type vtSubscription lpc.ValueTracker
+// VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses
+func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies {
+	if !s.udpEnabled {
+		return nil
+	}
+	reqsEnc, _ := rlp.EncodeToBytes(&reqs)
+	repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc)
+	var replies vflux.Replies
+	if len(repliesEnc) == 0 || rlp.DecodeBytes(repliesEnc, &replies) != nil {
+		return nil
+	}
+	return replies
+}
+
+// vfxVersion returns the version number of the "les" service subdomain of the vflux UDP
+// service, as advertised in the ENR record
+func (s *LightEthereum) vfxVersion(n *enode.Node) uint {
+	if n.Seq() == 0 {
+		var err error
+		if !s.udpEnabled {
+			return 0
+		}
+		if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 {
+			s.serverPool.Persist(n)
+		} else {
+			return 0
+		}
+	}
 
-// registerPeer implements serverPeerSubscriber
-func (v *vtSubscription) registerPeer(p *serverPeer) {
-	vt := (*lpc.ValueTracker)(v)
-	p.setValueTracker(vt, vt.Register(p.ID()))
-	p.updateVtParams()
+	var les []rlp.RawValue
+	if err := n.Load(enr.WithEntry("les", &les)); err != nil || len(les) < 1 {
+		return 0
+	}
+	var version uint
+	rlp.DecodeBytes(les[0], &version) // Ignore additional fields (for forward compatibility).
+	return version
 }
 
-// unregisterPeer implements serverPeerSubscriber
-func (v *vtSubscription) unregisterPeer(p *serverPeer) {
-	vt := (*lpc.ValueTracker)(v)
-	vt.Unregister(p.ID())
-	p.setValueTracker(nil, nil)
+// prenegQuery sends a capacity query to the given server node to determine whether
+// a connection slot is immediately available
+func (s *LightEthereum) prenegQuery(n *enode.Node) int {
+	if s.vfxVersion(n) < 1 {
+		// UDP query not supported, always try TCP connection
+		return 1
+	}
+
+	var requests vflux.Requests
+	requests.Add("les", vflux.CapacityQueryName, vflux.CapacityQueryReq{
+		Bias:      180,
+		AddTokens: []vflux.IntOrInf{{}},
+	})
+	replies := s.VfluxRequest(n, requests)
+	var cqr vflux.CapacityQueryReply
+	if replies.Get(0, &cqr) != nil || len(cqr) != 1 { // Note: Get returns an error if replies is nil
+		return -1
+	}
+	if cqr[0] > 0 {
+		return 1
+	}
+	return 0
 }
 
 type LightDummyAPI struct{}
@@ -221,7 +301,7 @@ func (s *LightEthereum) APIs() []rpc.API {
 		}, {
 			Namespace: "eth",
 			Version:   "1.0",
-			Service:   filters.NewPublicFilterAPI(s.ApiBackend, true, s.config.RangeLimit),
+			Service:   filters.NewPublicFilterAPI(s.ApiBackend, true, 5*time.Minute, s.config.RangeLimit),
 			Public:    true,
 		}, {
 			Namespace: "net",
@@ -234,9 +314,9 @@ func (s *LightEthereum) APIs() []rpc.API {
 			Service:   NewPrivateLightAPI(&s.lesCommons),
 			Public:    false,
 		}, {
-			Namespace: "lespay",
+			Namespace: "vflux",
 			Version:   "1.0",
-			Service:   lpc.NewPrivateClientAPI(s.valueTracker),
+			Service:   s.serverPool.API(),
 			Public:    false,
 		},
 	}...)
@@ -253,38 +333,44 @@ func (s *LightEthereum) LesVersion() int                    { return int(ClientP
 func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader }
 func (s *LightEthereum) EventMux() *event.TypeMux           { return s.eventMux }
 
-// Protocols implements node.Service, returning all the currently configured
-// network protocols to start.
+// Protocols returns all the currently configured network protocols to start.
 func (s *LightEthereum) Protocols() []p2p.Protocol {
 	return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
-		if p := s.peers.peer(peerIdToString(id)); p != nil {
+		if p := s.peers.peer(id.String()); p != nil {
 			return p.Info()
 		}
 		return nil
-	})
+	}, s.serverPoolIterator)
 }
 
-// Start implements node.Service, starting all internal goroutines needed by the
+// Start implements node.Lifecycle, starting all internal goroutines needed by the
 // light ethereum protocol implementation.
-func (s *LightEthereum) Start(srvr *p2p.Server) error {
+func (s *LightEthereum) Start() error {
 	log.Warn("Light client mode is an experimental feature")
 
+	if s.udpEnabled && s.p2pServer.DiscV5 == nil {
+		s.udpEnabled = false
+		log.Error("Discovery v5 is not initialized")
+	}
+	discovery, err := s.setupDiscovery()
+	if err != nil {
+		return err
+	}
+	s.serverPool.AddSource(discovery)
+	s.serverPool.Start()
 	// Start bloom request workers.
 	s.wg.Add(bloomServiceThreads)
 	s.startBloomHandlers(params.BloomBitsBlocksClient)
+	s.handler.start()
 
-	s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.config.NetworkId)
-
-	// clients are searching for the first advertised protocol in the list
-	protocolVersion := AdvertiseProtocolVersions[0]
-	s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash(), protocolVersion))
 	return nil
 }
 
-// Stop implements node.Service, terminating all internal goroutines used by the
+// Stop implements node.Lifecycle, terminating all internal goroutines used by the
 // Ethereum protocol.
 func (s *LightEthereum) Stop() error {
 	close(s.closeCh)
+	s.serverPool.Stop()
 	s.peers.close()
 	s.reqDist.close()
 	s.odr.Stop()
@@ -295,19 +381,12 @@ func (s *LightEthereum) Stop() error {
 	s.handler.stop()
 	s.txPool.Stop()
 	s.engine.Close()
+	s.pruner.close()
 	s.eventMux.Stop()
-	s.serverPool.stop()
-	s.valueTracker.Stop()
+	rawdb.PopUncleanShutdownMarker(s.chainDb)
 	s.chainDb.Close()
+	s.lesDb.Close()
 	s.wg.Wait()
 	log.Info("Light ethereum stopped")
 	return nil
 }
-
-// SetClient sets the rpc client and binds the registrar contract.
-func (s *LightEthereum) SetContractBackend(backend bind.ContractBackend) {
-	if s.oracle == nil {
-		return
-	}
-	s.oracle.Start(backend)
-}
diff --git a/les/client_handler.go b/les/client_handler.go
index 6367fdb6be..73149975c3 100644
--- a/les/client_handler.go
+++ b/les/client_handler.go
@@ -17,6 +17,7 @@
 package les
 
 import (
+	"context"
 	"math/big"
 	"sync"
 	"sync/atomic"
@@ -24,8 +25,10 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/core/forkid"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/protocols/eth"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p"
@@ -36,18 +39,23 @@ import (
 // responses.
 type clientHandler struct {
 	ulc        *ulc
+	forkFilter forkid.Filter
 	checkpoint *params.TrustedCheckpoint
 	fetcher    *lightFetcher
 	downloader *downloader.Downloader
 	backend    *LightEthereum
 
-	closeCh  chan struct{}
-	wg       sync.WaitGroup // WaitGroup used to track all connected peers.
-	syncDone func()         // Test hooks when syncing is done.
+	closeCh chan struct{}
+	wg      sync.WaitGroup // WaitGroup used to track all connected peers.
+
+	// Hooks used in the testing
+	syncStart func(header *types.Header) // Hook called when the syncing is started
+	syncEnd   func(header *types.Header) // Hook called when the syncing is done
 }
 
 func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler {
 	handler := &clientHandler{
+		forkFilter: forkid.NewFilter(backend.blockchain),
 		checkpoint: checkpoint,
 		backend:    backend,
 		closeCh:    make(chan struct{}),
@@ -64,16 +72,20 @@ func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.T
 	if checkpoint != nil {
 		height = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1
 	}
-	handler.fetcher = newLightFetcher(handler)
+	handler.fetcher = newLightFetcher(backend.blockchain, backend.engine, backend.peers, handler.ulc, backend.chainDb, backend.reqDist, handler.synchronise)
 	handler.downloader = downloader.New(height, backend.chainDb, nil, backend.eventMux, nil, backend.blockchain, handler.removePeer)
 	handler.backend.peers.subscribe((*downloaderPeerNotify)(handler))
 	return handler
 }
 
+func (h *clientHandler) start() {
+	h.fetcher.start()
+}
+
 func (h *clientHandler) stop() {
 	close(h.closeCh)
 	h.downloader.Terminate()
-	h.fetcher.close()
+	h.fetcher.stop()
 	h.wg.Wait()
 }
 
@@ -85,14 +97,9 @@ func (h *clientHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter)
 	}
 	peer := newServerPeer(int(version), h.backend.config.NetworkId, trusted, p, newMeteredMsgWriter(rw, int(version)))
 	defer peer.close()
-	peer.poolEntry = h.backend.serverPool.connect(peer, peer.Node())
-	if peer.poolEntry == nil {
-		return p2p.DiscRequested
-	}
 	h.wg.Add(1)
 	defer h.wg.Done()
 	err := h.handle(peer)
-	h.backend.serverPool.disconnect(peer.poolEntry)
 	return err
 }
 
@@ -103,21 +110,30 @@ func (h *clientHandler) handle(p *serverPeer) error {
 	p.Log().Debug("Light Ethereum peer connected", "name", p.Name())
 
 	// Execute the LES handshake
-	var (
-		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 {
+	forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64())
+	if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil {
 		p.Log().Debug("Light Ethereum handshake failed", "err", err)
 		return err
 	}
+	// Register peer with the server pool
+	if h.backend.serverPool != nil {
+		if nvt, err := h.backend.serverPool.RegisterNode(p.Node()); err == nil {
+			p.setValueTracker(nvt)
+			p.updateVtParams()
+			defer func() {
+				p.setValueTracker(nil)
+				h.backend.serverPool.UnregisterNode(p.Node())
+			}()
+		} else {
+			return err
+		}
+	}
 	// Register the peer locally
 	if err := h.backend.peers.register(p); err != nil {
 		p.Log().Error("Light Ethereum peer registration failed", "err", err)
 		return err
 	}
+
 	serverConnectionGauge.Update(int64(h.backend.peers.len()))
 
 	connectedAt := mclock.Now()
@@ -126,13 +142,8 @@ func (h *clientHandler) handle(p *serverPeer) error {
 		connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
 		serverConnectionGauge.Update(int64(h.backend.peers.len()))
 	}()
-
 	h.fetcher.announce(p, &announceData{Hash: p.headInfo.Hash, Number: p.headInfo.Number, Td: p.headInfo.Td})
 
-	// pool entry can be nil during the unit test.
-	if p.poolEntry != nil {
-		h.backend.serverPool.registered(p.poolEntry)
-	}
 	// Mark the peer starts to be served.
 	atomic.StoreUint32(&p.serving, 1)
 	defer atomic.StoreUint32(&p.serving, 0)
@@ -165,8 +176,8 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 	var deliverMsg *Msg
 
 	// Handle the message depending on its contents
-	switch msg.Code {
-	case AnnounceMsg:
+	switch {
+	case msg.Code == AnnounceMsg:
 		p.Log().Trace("Received announce message")
 		var req announceData
 		if err := msg.Decode(&req); err != nil {
@@ -194,9 +205,12 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 				p.Log().Trace("Valid announcement signature")
 			}
 			p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
+
+			// Update peer head information first and then notify the announcement
+			p.updateHead(req.Hash, req.Number, req.Td)
 			h.fetcher.announce(p, &req)
 		}
-	case BlockHeadersMsg:
+	case msg.Code == BlockHeadersMsg:
 		p.Log().Trace("Received block header response message")
 		var resp struct {
 			ReqID, BV uint64
@@ -205,16 +219,30 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 		if err := msg.Decode(&resp); err != nil {
 			return errResp(ErrDecode, "msg %v: %v", msg, err)
 		}
+		headers := resp.Headers
 		p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
 		p.answeredRequest(resp.ReqID)
-		if h.fetcher.requestedID(resp.ReqID) {
-			h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
+
+		// Filter out the explicitly requested header by the retriever
+		if h.backend.retriever.requested(resp.ReqID) {
+			deliverMsg = &Msg{
+				MsgType: MsgBlockHeaders,
+				ReqID:   resp.ReqID,
+				Obj:     resp.Headers,
+			}
 		} else {
-			if err := h.downloader.DeliverHeaders(p.id, resp.Headers); err != nil {
-				log.Debug("Failed to deliver headers", "err", err)
+			// Filter out any explicitly requested headers, deliver the rest to the downloader
+			filter := len(headers) == 1
+			if filter {
+				headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
+			}
+			if len(headers) != 0 || !filter {
+				if err := h.downloader.DeliverHeaders(p.id, headers); err != nil {
+					log.Debug("Failed to deliver headers", "err", err)
+				}
 			}
 		}
-	case BlockBodiesMsg:
+	case msg.Code == BlockBodiesMsg:
 		p.Log().Trace("Received block bodies response")
 		var resp struct {
 			ReqID, BV uint64
@@ -230,7 +258,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 			ReqID:   resp.ReqID,
 			Obj:     resp.Data,
 		}
-	case CodeMsg:
+	case msg.Code == CodeMsg:
 		p.Log().Trace("Received code response")
 		var resp struct {
 			ReqID, BV uint64
@@ -246,7 +274,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 			ReqID:   resp.ReqID,
 			Obj:     resp.Data,
 		}
-	case ReceiptsMsg:
+	case msg.Code == ReceiptsMsg:
 		p.Log().Trace("Received receipts response")
 		var resp struct {
 			ReqID, BV uint64
@@ -262,7 +290,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 			ReqID:   resp.ReqID,
 			Obj:     resp.Receipts,
 		}
-	case ProofsV2Msg:
+	case msg.Code == ProofsV2Msg:
 		p.Log().Trace("Received les/2 proofs response")
 		var resp struct {
 			ReqID, BV uint64
@@ -278,7 +306,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 			ReqID:   resp.ReqID,
 			Obj:     resp.Data,
 		}
-	case HelperTrieProofsMsg:
+	case msg.Code == HelperTrieProofsMsg:
 		p.Log().Trace("Received helper trie proof response")
 		var resp struct {
 			ReqID, BV uint64
@@ -294,7 +322,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 			ReqID:   resp.ReqID,
 			Obj:     resp.Data,
 		}
-	case TxStatusMsg:
+	case msg.Code == TxStatusMsg:
 		p.Log().Trace("Received tx status response")
 		var resp struct {
 			ReqID, BV uint64
@@ -310,11 +338,11 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 			ReqID:   resp.ReqID,
 			Obj:     resp.Status,
 		}
-	case StopMsg:
+	case msg.Code == StopMsg && p.version >= lpv3:
 		p.freeze()
 		h.backend.retriever.frozen(p)
 		p.Log().Debug("Service stopped")
-	case ResumeMsg:
+	case msg.Code == ResumeMsg && p.version >= lpv3:
 		var bv uint64
 		if err := msg.Decode(&bv); err != nil {
 			return errResp(ErrDecode, "msg %v: %v", msg, err)
@@ -329,8 +357,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
 	// Deliver the received response to retriever.
 	if deliverMsg != nil {
 		if err := h.backend.retriever.deliver(p, deliverMsg); err != nil {
-			p.errCount++
-			if p.errCount > maxResponseErrors {
+			if val := p.errCount.Add(1, mclock.Now()); val > maxResponseErrors {
 				return err
 			}
 		}
@@ -399,6 +426,42 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip
 	return nil
 }
 
+// RetrieveSingleHeaderByNumber requests a single header by the specified block
+// number. This function will wait the response until it's timeout or delivered.
+func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, number uint64) (*types.Header, error) {
+	reqID := genReqID()
+	rq := &distReq{
+		getCost: func(dp distPeer) uint64 {
+			peer := dp.(*serverPeer)
+			return peer.getRequestCost(GetBlockHeadersMsg, 1)
+		},
+		canSend: func(dp distPeer) bool {
+			return dp.(*serverPeer) == pc.peer
+		},
+		request: func(dp distPeer) func() {
+			peer := dp.(*serverPeer)
+			cost := peer.getRequestCost(GetBlockHeadersMsg, 1)
+			peer.fcServer.QueuedRequest(reqID, cost)
+			return func() { peer.requestHeadersByNumber(reqID, number, 1, 0, false) }
+		},
+	}
+	var header *types.Header
+	if err := pc.handler.backend.retriever.retrieve(context, reqID, rq, func(peer distPeer, msg *Msg) error {
+		if msg.MsgType != MsgBlockHeaders {
+			return errInvalidMessageType
+		}
+		headers := msg.Obj.([]*types.Header)
+		if len(headers) != 1 {
+			return errInvalidEntryCount
+		}
+		header = headers[0]
+		return nil
+	}, nil); err != nil {
+		return nil, err
+	}
+	return header, nil
+}
+
 // downloaderPeerNotify implements peerSetNotify
 type downloaderPeerNotify clientHandler
 
@@ -408,7 +471,7 @@ func (d *downloaderPeerNotify) registerPeer(p *serverPeer) {
 		handler: h,
 		peer:    p,
 	}
-	h.downloader.RegisterLightPeer(p.id, ethVersion, pc)
+	h.downloader.RegisterLightPeer(p.id, eth.ETH65, pc)
 }
 
 func (d *downloaderPeerNotify) unregisterPeer(p *serverPeer) {
diff --git a/les/clientpool.go b/les/clientpool.go
deleted file mode 100644
index c862649501..0000000000
--- a/les/clientpool.go
+++ /dev/null
@@ -1,872 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package les
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"io"
-	"math"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/common/prque"
-	"github.com/ethereum/go-ethereum/ethdb"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/rlp"
-	lru "github.com/hashicorp/golang-lru"
-)
-
-const (
-	negBalanceExpTC              = time.Hour        // time constant for exponentially reducing negative balance
-	fixedPointMultiplier         = 0x1000000        // constant to convert logarithms to fixed point format
-	lazyQueueRefresh             = time.Second * 10 // refresh period of the connected queue
-	persistCumulativeTimeRefresh = time.Minute * 5  // refresh period of the cumulative running time persistence
-	posBalanceCacheLimit         = 8192             // the maximum number of cached items in positive balance queue
-	negBalanceCacheLimit         = 8192             // the maximum number of cached items in negative balance queue
-
-	// connectedBias is applied to already connected clients So that
-	// already connected client won't be kicked out very soon and we
-	// can ensure all connected clients can have enough time to request
-	// or sync some data.
-	//
-	// todo(rjl493456442) make it configurable. It can be the option of
-	// free trial time!
-	connectedBias = time.Minute * 3
-)
-
-// clientPool implements a client database that assigns a priority to each client
-// based on a positive and negative balance. Positive balance is externally assigned
-// to prioritized clients and is decreased with connection time and processed
-// requests (unless the price factors are zero). If the positive balance is zero
-// then negative balance is accumulated.
-//
-// Balance tracking and priority calculation for connected clients is done by
-// balanceTracker. connectedQueue ensures that clients with the lowest positive or
-// highest negative balance get evicted when the total capacity allowance is full
-// and new clients with a better balance want to connect.
-//
-// Already connected nodes receive a small bias in their favor in order to avoid
-// accepting and instantly kicking out clients. In theory, we try to ensure that
-// each client can have several minutes of connection time.
-//
-// Balances of disconnected clients are stored in nodeDB including positive balance
-// and negative banalce. Negative balance is transformed into a logarithmic form
-// with a constantly shifting linear offset in order to implement an exponential
-// decrease. Besides nodeDB will have a background thread to check the negative
-// balance of disconnected client. If the balance is low enough, then the record
-// will be dropped.
-type clientPool struct {
-	ndb        *nodeDB
-	lock       sync.Mutex
-	clock      mclock.Clock
-	stopCh     chan struct{}
-	closed     bool
-	removePeer func(enode.ID)
-
-	connectedMap   map[enode.ID]*clientInfo
-	connectedQueue *prque.LazyQueue
-
-	defaultPosFactors, defaultNegFactors priceFactors
-
-	connLimit         int            // The maximum number of connections that clientpool can support
-	capLimit          uint64         // The maximum cumulative capacity that clientpool can support
-	connectedCap      uint64         // The sum of the capacity of the current clientpool connected
-	priorityConnected uint64         // The sum of the capacity of currently connected priority clients
-	freeClientCap     uint64         // The capacity value of each free client
-	startTime         mclock.AbsTime // The timestamp at which the clientpool started running
-	cumulativeTime    int64          // The cumulative running time of clientpool at the start point.
-	disableBias       bool           // Disable connection bias(used in testing)
-}
-
-// clientPoolPeer represents a client peer in the pool.
-// Positive balances are assigned to node key while negative balances are assigned
-// to freeClientId. Currently network IP address without port is used because
-// clients have a limited access to IP addresses while new node keys can be easily
-// generated so it would be useless to assign a negative value to them.
-type clientPoolPeer interface {
-	ID() enode.ID
-	freeClientId() string
-	updateCapacity(uint64)
-	freezeClient()
-}
-
-// clientInfo represents a connected client
-type clientInfo struct {
-	address                string
-	id                     enode.ID
-	connectedAt            mclock.AbsTime
-	capacity               uint64
-	priority               bool
-	pool                   *clientPool
-	peer                   clientPoolPeer
-	queueIndex             int // position in connectedQueue
-	balanceTracker         balanceTracker
-	posFactors, negFactors priceFactors
-	balanceMetaInfo        string
-}
-
-// connSetIndex callback updates clientInfo item index in connectedQueue
-func connSetIndex(a interface{}, index int) {
-	a.(*clientInfo).queueIndex = index
-}
-
-// connPriority callback returns actual priority of clientInfo item in connectedQueue
-func connPriority(a interface{}, now mclock.AbsTime) int64 {
-	c := a.(*clientInfo)
-	return c.balanceTracker.getPriority(now)
-}
-
-// connMaxPriority callback returns estimated maximum priority of clientInfo item in connectedQueue
-func connMaxPriority(a interface{}, until mclock.AbsTime) int64 {
-	c := a.(*clientInfo)
-	pri := c.balanceTracker.estimatedPriority(until, true)
-	c.balanceTracker.addCallback(balanceCallbackQueue, pri+1, func() {
-		c.pool.lock.Lock()
-		if c.queueIndex != -1 {
-			c.pool.connectedQueue.Update(c.queueIndex)
-		}
-		c.pool.lock.Unlock()
-	})
-	return pri
-}
-
-// priceFactors determine the pricing policy (may apply either to positive or
-// negative balances which may have different factors).
-// - timeFactor is cost unit per nanosecond of connection time
-// - capacityFactor is cost unit per nanosecond of connection time per 1000000 capacity
-// - requestFactor is cost unit per request "realCost" unit
-type priceFactors struct {
-	timeFactor, capacityFactor, requestFactor float64
-}
-
-// newClientPool creates a new client pool
-func newClientPool(db ethdb.Database, freeClientCap uint64, clock mclock.Clock, removePeer func(enode.ID)) *clientPool {
-	ndb := newNodeDB(db, clock)
-	pool := &clientPool{
-		ndb:            ndb,
-		clock:          clock,
-		connectedMap:   make(map[enode.ID]*clientInfo),
-		connectedQueue: prque.NewLazyQueue(connSetIndex, connPriority, connMaxPriority, clock, lazyQueueRefresh),
-		freeClientCap:  freeClientCap,
-		removePeer:     removePeer,
-		startTime:      clock.Now(),
-		cumulativeTime: ndb.getCumulativeTime(),
-		stopCh:         make(chan struct{}),
-	}
-	// If the negative balance of free client is even lower than 1,
-	// delete this entry.
-	ndb.nbEvictCallBack = func(now mclock.AbsTime, b negBalance) bool {
-		balance := math.Exp(float64(b.logValue-pool.logOffset(now)) / fixedPointMultiplier)
-		return balance <= 1
-	}
-	go func() {
-		for {
-			select {
-			case <-clock.After(lazyQueueRefresh):
-				pool.lock.Lock()
-				pool.connectedQueue.Refresh()
-				pool.lock.Unlock()
-			case <-clock.After(persistCumulativeTimeRefresh):
-				pool.ndb.setCumulativeTime(pool.logOffset(clock.Now()))
-			case <-pool.stopCh:
-				return
-			}
-		}
-	}()
-	return pool
-}
-
-// stop shuts the client pool down
-func (f *clientPool) stop() {
-	close(f.stopCh)
-	f.lock.Lock()
-	f.closed = true
-	f.lock.Unlock()
-	f.ndb.setCumulativeTime(f.logOffset(f.clock.Now()))
-	f.ndb.close()
-}
-
-// connect should be called after a successful handshake. If the connection was
-// rejected, there is no need to call disconnect.
-func (f *clientPool) connect(peer clientPoolPeer, capacity uint64) bool {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	// Short circuit if clientPool is already closed.
-	if f.closed {
-		return false
-	}
-	// Dedup connected peers.
-	id, freeID := peer.ID(), peer.freeClientId()
-	if _, ok := f.connectedMap[id]; ok {
-		clientRejectedMeter.Mark(1)
-		log.Debug("Client already connected", "address", freeID, "id", peerIdToString(id))
-		return false
-	}
-	// Create a clientInfo but do not add it yet
-	var (
-		posBalance uint64
-		negBalance uint64
-		now        = f.clock.Now()
-	)
-	pb := f.ndb.getOrNewPB(id)
-	posBalance = pb.value
-
-	nb := f.ndb.getOrNewNB(freeID)
-	if nb.logValue != 0 {
-		negBalance = uint64(math.Exp(float64(nb.logValue-f.logOffset(now))/fixedPointMultiplier) * float64(time.Second))
-	}
-	e := &clientInfo{
-		pool:            f,
-		peer:            peer,
-		address:         freeID,
-		queueIndex:      -1,
-		id:              id,
-		connectedAt:     now,
-		priority:        posBalance != 0,
-		posFactors:      f.defaultPosFactors,
-		negFactors:      f.defaultNegFactors,
-		balanceMetaInfo: pb.meta,
-	}
-	// If the client is a free client, assign with a low free capacity,
-	// Otherwise assign with the given value(priority client)
-	if !e.priority || capacity == 0 {
-		capacity = f.freeClientCap
-	}
-	e.capacity = capacity
-
-	// Starts a balance tracker
-	e.balanceTracker.init(f.clock, capacity)
-	e.balanceTracker.setBalance(posBalance, negBalance)
-	e.updatePriceFactors()
-
-	// If the number of clients already connected in the clientpool exceeds its
-	// capacity, evict some clients with lowest priority.
-	//
-	// If the priority of the newly added client is lower than the priority of
-	// all connected clients, the client is rejected.
-	newCapacity := f.connectedCap + capacity
-	newCount := f.connectedQueue.Size() + 1
-	if newCapacity > f.capLimit || newCount > f.connLimit {
-		var (
-			kickList     []*clientInfo
-			kickPriority int64
-		)
-		f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool {
-			c := data.(*clientInfo)
-			kickList = append(kickList, c)
-			kickPriority = priority
-			newCapacity -= c.capacity
-			newCount--
-			return newCapacity > f.capLimit || newCount > f.connLimit
-		})
-		bias := connectedBias
-		if f.disableBias {
-			bias = 0
-		}
-		if newCapacity > f.capLimit || newCount > f.connLimit || (e.balanceTracker.estimatedPriority(now+mclock.AbsTime(bias), false)-kickPriority) > 0 {
-			for _, c := range kickList {
-				f.connectedQueue.Push(c)
-			}
-			clientRejectedMeter.Mark(1)
-			log.Debug("Client rejected", "address", freeID, "id", peerIdToString(id))
-			return false
-		}
-		// accept new client, drop old ones
-		for _, c := range kickList {
-			f.dropClient(c, now, true)
-		}
-	}
-
-	// Register new client to connection queue.
-	f.connectedMap[id] = e
-	f.connectedQueue.Push(e)
-	f.connectedCap += e.capacity
-
-	// If the current client is a paid client, monitor the status of client,
-	// downgrade it to normal client if positive balance is used up.
-	if e.priority {
-		f.priorityConnected += capacity
-		e.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) })
-	}
-	// If the capacity of client is not the default value(free capacity), notify
-	// it to update capacity.
-	if e.capacity != f.freeClientCap {
-		e.peer.updateCapacity(e.capacity)
-	}
-	totalConnectedGauge.Update(int64(f.connectedCap))
-	clientConnectedMeter.Mark(1)
-	log.Debug("Client accepted", "address", freeID)
-	return true
-}
-
-// disconnect should be called when a connection is terminated. If the disconnection
-// was initiated by the pool itself using disconnectFn then calling disconnect is
-// not necessary but permitted.
-func (f *clientPool) disconnect(p clientPoolPeer) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	// Short circuit if client pool is already closed.
-	if f.closed {
-		return
-	}
-	// Short circuit if the peer hasn't been registered.
-	e := f.connectedMap[p.ID()]
-	if e == nil {
-		log.Debug("Client not connected", "address", p.freeClientId(), "id", peerIdToString(p.ID()))
-		return
-	}
-	f.dropClient(e, f.clock.Now(), false)
-}
-
-// forClients iterates through a list of clients, calling the callback for each one.
-// If a client is not connected then clientInfo is nil. If the specified list is empty
-// then the callback is called for all connected clients.
-func (f *clientPool) forClients(ids []enode.ID, callback func(*clientInfo, enode.ID) error) error {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	if len(ids) > 0 {
-		for _, id := range ids {
-			if err := callback(f.connectedMap[id], id); err != nil {
-				return err
-			}
-		}
-	} else {
-		for _, c := range f.connectedMap {
-			if err := callback(c, c.id); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-// setDefaultFactors sets the default price factors applied to subsequently connected clients
-func (f *clientPool) setDefaultFactors(posFactors, negFactors priceFactors) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	f.defaultPosFactors = posFactors
-	f.defaultNegFactors = negFactors
-}
-
-// dropClient removes a client from the connected queue and finalizes its balance.
-// If kick is true then it also initiates the disconnection.
-func (f *clientPool) dropClient(e *clientInfo, now mclock.AbsTime, kick bool) {
-	if _, ok := f.connectedMap[e.id]; !ok {
-		return
-	}
-	f.finalizeBalance(e, now)
-	f.connectedQueue.Remove(e.queueIndex)
-	delete(f.connectedMap, e.id)
-	f.connectedCap -= e.capacity
-	if e.priority {
-		f.priorityConnected -= e.capacity
-	}
-	totalConnectedGauge.Update(int64(f.connectedCap))
-	if kick {
-		clientKickedMeter.Mark(1)
-		log.Debug("Client kicked out", "address", e.address)
-		f.removePeer(e.id)
-	} else {
-		clientDisconnectedMeter.Mark(1)
-		log.Debug("Client disconnected", "address", e.address)
-	}
-}
-
-// capacityInfo returns the total capacity allowance, the total capacity of connected
-// clients and the total capacity of connected and prioritized clients
-func (f *clientPool) capacityInfo() (uint64, uint64, uint64) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	return f.capLimit, f.connectedCap, f.priorityConnected
-}
-
-// finalizeBalance stops the balance tracker, retrieves the final balances and
-// stores them in posBalanceQueue and negBalanceQueue
-func (f *clientPool) finalizeBalance(c *clientInfo, now mclock.AbsTime) {
-	c.balanceTracker.stop(now)
-	pos, neg := c.balanceTracker.getBalance(now)
-
-	pb, nb := f.ndb.getOrNewPB(c.id), f.ndb.getOrNewNB(c.address)
-	pb.value = pos
-	f.ndb.setPB(c.id, pb)
-
-	neg /= uint64(time.Second) // Convert the expanse to second level.
-	if neg > 1 {
-		nb.logValue = int64(math.Log(float64(neg))*fixedPointMultiplier) + f.logOffset(now)
-		f.ndb.setNB(c.address, nb)
-	} else {
-		f.ndb.delNB(c.address) // Negative balance is small enough, drop it directly.
-	}
-}
-
-// balanceExhausted callback is called by balanceTracker when positive balance is exhausted.
-// It revokes priority status and also reduces the client capacity if necessary.
-func (f *clientPool) balanceExhausted(id enode.ID) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	c := f.connectedMap[id]
-	if c == nil || !c.priority {
-		return
-	}
-	if c.priority {
-		f.priorityConnected -= c.capacity
-	}
-	c.priority = false
-	if c.capacity != f.freeClientCap {
-		f.connectedCap += f.freeClientCap - c.capacity
-		totalConnectedGauge.Update(int64(f.connectedCap))
-		c.capacity = f.freeClientCap
-		c.balanceTracker.setCapacity(c.capacity)
-		c.peer.updateCapacity(c.capacity)
-	}
-	pb := f.ndb.getOrNewPB(id)
-	pb.value = 0
-	f.ndb.setPB(id, pb)
-}
-
-// setConnLimit sets the maximum number and total capacity of connected clients,
-// dropping some of them if necessary.
-func (f *clientPool) setLimits(totalConn int, totalCap uint64) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	f.connLimit = totalConn
-	f.capLimit = totalCap
-	if f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit {
-		f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool {
-			f.dropClient(data.(*clientInfo), mclock.Now(), true)
-			return f.connectedCap > f.capLimit || f.connectedQueue.Size() > f.connLimit
-		})
-	}
-}
-
-// setCapacity sets the assigned capacity of a connected client
-func (f *clientPool) setCapacity(c *clientInfo, capacity uint64) error {
-	if f.connectedMap[c.id] != c {
-		return fmt.Errorf("client %064x is not connected", c.id[:])
-	}
-	if c.capacity == capacity {
-		return nil
-	}
-	if !c.priority {
-		return errNoPriority
-	}
-	oldCapacity := c.capacity
-	c.capacity = capacity
-	f.connectedCap += capacity - oldCapacity
-	c.balanceTracker.setCapacity(capacity)
-	f.connectedQueue.Update(c.queueIndex)
-	if f.connectedCap > f.capLimit {
-		var kickList []*clientInfo
-		kick := true
-		f.connectedQueue.MultiPop(func(data interface{}, priority int64) bool {
-			client := data.(*clientInfo)
-			kickList = append(kickList, client)
-			f.connectedCap -= client.capacity
-			if client == c {
-				kick = false
-			}
-			return kick && (f.connectedCap > f.capLimit)
-		})
-		if kick {
-			now := mclock.Now()
-			for _, c := range kickList {
-				f.dropClient(c, now, true)
-			}
-		} else {
-			c.capacity = oldCapacity
-			c.balanceTracker.setCapacity(oldCapacity)
-			for _, c := range kickList {
-				f.connectedCap += c.capacity
-				f.connectedQueue.Push(c)
-			}
-			return errNoPriority
-		}
-	}
-	totalConnectedGauge.Update(int64(f.connectedCap))
-	f.priorityConnected += capacity - oldCapacity
-	c.updatePriceFactors()
-	c.peer.updateCapacity(c.capacity)
-	return nil
-}
-
-// requestCost feeds request cost after serving a request from the given peer.
-func (f *clientPool) requestCost(p *clientPeer, cost uint64) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	info, exist := f.connectedMap[p.ID()]
-	if !exist || f.closed {
-		return
-	}
-	info.balanceTracker.requestCost(cost)
-}
-
-// logOffset calculates the time-dependent offset for the logarithmic
-// representation of negative balance
-//
-// From another point of view, the result returned by the function represents
-// the total time that the clientpool is cumulatively running(total_hours/multiplier).
-func (f *clientPool) logOffset(now mclock.AbsTime) int64 {
-	// Note: fixedPointMultiplier acts as a multiplier here; the reason for dividing the divisor
-	// is to avoid int64 overflow. We assume that int64(negBalanceExpTC) >> fixedPointMultiplier.
-	cumulativeTime := int64((time.Duration(now - f.startTime)) / (negBalanceExpTC / fixedPointMultiplier))
-	return f.cumulativeTime + cumulativeTime
-}
-
-// setClientPriceFactors sets the pricing factors for an individual connected client
-func (c *clientInfo) updatePriceFactors() {
-	c.balanceTracker.setFactors(true, c.negFactors.timeFactor+float64(c.capacity)*c.negFactors.capacityFactor/1000000, c.negFactors.requestFactor)
-	c.balanceTracker.setFactors(false, c.posFactors.timeFactor+float64(c.capacity)*c.posFactors.capacityFactor/1000000, c.posFactors.requestFactor)
-}
-
-// getPosBalance retrieves a single positive balance entry from cache or the database
-func (f *clientPool) getPosBalance(id enode.ID) posBalance {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	return f.ndb.getOrNewPB(id)
-}
-
-// addBalance updates the balance of a client (either overwrites it or adds to it).
-// It also updates the balance meta info string.
-func (f *clientPool) addBalance(id enode.ID, amount int64, meta string) (uint64, uint64, error) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	pb := f.ndb.getOrNewPB(id)
-	var negBalance uint64
-	c := f.connectedMap[id]
-	if c != nil {
-		pb.value, negBalance = c.balanceTracker.getBalance(f.clock.Now())
-	}
-	oldBalance := pb.value
-	if amount > 0 {
-		if amount > maxBalance || pb.value > maxBalance-uint64(amount) {
-			return oldBalance, oldBalance, errBalanceOverflow
-		}
-		pb.value += uint64(amount)
-	} else {
-		if uint64(-amount) > pb.value {
-			pb.value = 0
-		} else {
-			pb.value -= uint64(-amount)
-		}
-	}
-	pb.meta = meta
-	f.ndb.setPB(id, pb)
-	if c != nil {
-		c.balanceTracker.setBalance(pb.value, negBalance)
-		if !c.priority && pb.value > 0 {
-			// The capacity should be adjusted based on the requirement,
-			// but we have no idea about the new capacity, need a second
-			// call to udpate it.
-			c.priority = true
-			f.priorityConnected += c.capacity
-			c.balanceTracker.addCallback(balanceCallbackZero, 0, func() { f.balanceExhausted(id) })
-		}
-		// if balance is set to zero then reverting to non-priority status
-		// is handled by the balanceExhausted callback
-		c.balanceMetaInfo = meta
-	}
-	return oldBalance, pb.value, nil
-}
-
-// posBalance represents a recently accessed positive balance entry
-type posBalance struct {
-	value uint64
-	meta  string
-}
-
-// EncodeRLP implements rlp.Encoder
-func (e *posBalance) EncodeRLP(w io.Writer) error {
-	return rlp.Encode(w, []interface{}{e.value, e.meta})
-}
-
-// DecodeRLP implements rlp.Decoder
-func (e *posBalance) DecodeRLP(s *rlp.Stream) error {
-	var entry struct {
-		Value uint64
-		Meta  string
-	}
-	if err := s.Decode(&entry); err != nil {
-		return err
-	}
-	e.value = entry.Value
-	e.meta = entry.Meta
-	return nil
-}
-
-// negBalance represents a negative balance entry of a disconnected client
-type negBalance struct{ logValue int64 }
-
-// EncodeRLP implements rlp.Encoder
-func (e *negBalance) EncodeRLP(w io.Writer) error {
-	return rlp.Encode(w, []interface{}{uint64(e.logValue)})
-}
-
-// DecodeRLP implements rlp.Decoder
-func (e *negBalance) DecodeRLP(s *rlp.Stream) error {
-	var entry struct {
-		LogValue uint64
-	}
-	if err := s.Decode(&entry); err != nil {
-		return err
-	}
-	e.logValue = int64(entry.LogValue)
-	return nil
-}
-
-const (
-	// nodeDBVersion is the version identifier of the node data in db
-	//
-	// Changelog:
-	// * Replace `lastTotal` with `meta` in positive balance: version 0=>1
-	nodeDBVersion = 1
-
-	// dbCleanupCycle is the cycle of db for useless data cleanup
-	dbCleanupCycle = time.Hour
-)
-
-var (
-	positiveBalancePrefix    = []byte("pb:")             // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance
-	negativeBalancePrefix    = []byte("nb:")             // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance
-	cumulativeRunningTimeKey = []byte("cumulativeTime:") // dbVersion(uint16 big endian) + cumulativeRunningTimeKey -> cumulativeTime
-)
-
-type nodeDB struct {
-	db              ethdb.Database
-	pcache          *lru.Cache
-	ncache          *lru.Cache
-	auxbuf          []byte                                // 37-byte auxiliary buffer for key encoding
-	verbuf          [2]byte                               // 2-byte auxiliary buffer for db version
-	nbEvictCallBack func(mclock.AbsTime, negBalance) bool // Callback to determine whether the negative balance can be evicted.
-	clock           mclock.Clock
-	closeCh         chan struct{}
-	cleanupHook     func() // Test hook used for testing
-}
-
-func newNodeDB(db ethdb.Database, clock mclock.Clock) *nodeDB {
-	pcache, _ := lru.New(posBalanceCacheLimit)
-	ncache, _ := lru.New(negBalanceCacheLimit)
-	ndb := &nodeDB{
-		db:      db,
-		pcache:  pcache,
-		ncache:  ncache,
-		auxbuf:  make([]byte, 37),
-		clock:   clock,
-		closeCh: make(chan struct{}),
-	}
-	binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion))
-	go ndb.expirer()
-	return ndb
-}
-
-func (db *nodeDB) close() {
-	close(db.closeCh)
-}
-
-func (db *nodeDB) getPrefix(neg bool) []byte {
-	prefix := positiveBalancePrefix
-	if neg {
-		prefix = negativeBalancePrefix
-	}
-	return append(db.verbuf[:], prefix...)
-}
-
-func (db *nodeDB) key(id []byte, neg bool) []byte {
-	prefix := positiveBalancePrefix
-	if neg {
-		prefix = negativeBalancePrefix
-	}
-	if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) {
-		db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...)
-	}
-	copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:])
-	copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix)
-	copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id)
-	return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)]
-}
-
-func (db *nodeDB) getCumulativeTime() int64 {
-	blob, err := db.db.Get(append(cumulativeRunningTimeKey, db.verbuf[:]...))
-	if err != nil || len(blob) == 0 {
-		return 0
-	}
-	return int64(binary.BigEndian.Uint64(blob))
-}
-
-func (db *nodeDB) setCumulativeTime(v int64) {
-	binary.BigEndian.PutUint64(db.auxbuf[:8], uint64(v))
-	db.db.Put(append(cumulativeRunningTimeKey, db.verbuf[:]...), db.auxbuf[:8])
-}
-
-func (db *nodeDB) getOrNewPB(id enode.ID) posBalance {
-	key := db.key(id.Bytes(), false)
-	item, exist := db.pcache.Get(string(key))
-	if exist {
-		return item.(posBalance)
-	}
-	var balance posBalance
-	if enc, err := db.db.Get(key); err == nil {
-		if err := rlp.DecodeBytes(enc, &balance); err != nil {
-			log.Error("Failed to decode positive balance", "err", err)
-		}
-	}
-	db.pcache.Add(string(key), balance)
-	return balance
-}
-
-func (db *nodeDB) setPB(id enode.ID, b posBalance) {
-	if b.value == 0 && len(b.meta) == 0 {
-		db.delPB(id)
-		return
-	}
-	key := db.key(id.Bytes(), false)
-	enc, err := rlp.EncodeToBytes(&(b))
-	if err != nil {
-		log.Error("Failed to encode positive balance", "err", err)
-		return
-	}
-	db.db.Put(key, enc)
-	db.pcache.Add(string(key), b)
-}
-
-func (db *nodeDB) delPB(id enode.ID) {
-	key := db.key(id.Bytes(), false)
-	db.db.Delete(key)
-	db.pcache.Remove(string(key))
-}
-
-// getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts
-// with a positive balance
-func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) {
-	if maxCount <= 0 {
-		return
-	}
-	prefix := db.getPrefix(false)
-	it := db.db.NewIterator(prefix, start.Bytes())
-	defer it.Release()
-	for i := len(stop[:]) - 1; i >= 0; i-- {
-		stop[i]--
-		if stop[i] != 255 {
-			break
-		}
-	}
-	stopKey := db.key(stop.Bytes(), false)
-	keyLen := len(stopKey)
-
-	for it.Next() {
-		var id enode.ID
-		if len(it.Key()) != keyLen || bytes.Compare(it.Key(), stopKey) == 1 {
-			return
-		}
-		copy(id[:], it.Key()[keyLen-len(id):])
-		result = append(result, id)
-		if len(result) == maxCount {
-			return
-		}
-	}
-	return
-}
-
-func (db *nodeDB) getOrNewNB(id string) negBalance {
-	key := db.key([]byte(id), true)
-	item, exist := db.ncache.Get(string(key))
-	if exist {
-		return item.(negBalance)
-	}
-	var balance negBalance
-	if enc, err := db.db.Get(key); err == nil {
-		if err := rlp.DecodeBytes(enc, &balance); err != nil {
-			log.Error("Failed to decode negative balance", "err", err)
-		}
-	}
-	db.ncache.Add(string(key), balance)
-	return balance
-}
-
-func (db *nodeDB) setNB(id string, b negBalance) {
-	key := db.key([]byte(id), true)
-	enc, err := rlp.EncodeToBytes(&(b))
-	if err != nil {
-		log.Error("Failed to encode negative balance", "err", err)
-		return
-	}
-	db.db.Put(key, enc)
-	db.ncache.Add(string(key), b)
-}
-
-func (db *nodeDB) delNB(id string) {
-	key := db.key([]byte(id), true)
-	db.db.Delete(key)
-	db.ncache.Remove(string(key))
-}
-
-func (db *nodeDB) expirer() {
-	for {
-		select {
-		case <-db.clock.After(dbCleanupCycle):
-			db.expireNodes()
-		case <-db.closeCh:
-			return
-		}
-	}
-}
-
-// expireNodes iterates the whole node db and checks whether the negative balance
-// entry can deleted.
-//
-// The rationale behind this is: server doesn't need to keep the negative balance
-// records if they are low enough.
-func (db *nodeDB) expireNodes() {
-	var (
-		visited int
-		deleted int
-		start   = time.Now()
-		prefix  = db.getPrefix(true)
-	)
-	iter := db.db.NewIterator(prefix, nil)
-	for iter.Next() {
-		visited += 1
-		var balance negBalance
-		if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil {
-			log.Error("Failed to decode negative balance", "err", err)
-			continue
-		}
-		if db.nbEvictCallBack != nil && db.nbEvictCallBack(db.clock.Now(), balance) {
-			deleted += 1
-			db.db.Delete(iter.Key())
-		}
-	}
-	// Invoke testing hook if it's not nil.
-	if db.cleanupHook != nil {
-		db.cleanupHook()
-	}
-	log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start)))
-}
diff --git a/les/clientpool_test.go b/les/clientpool_test.go
deleted file mode 100644
index 6308113fe7..0000000000
--- a/les/clientpool_test.go
+++ /dev/null
@@ -1,543 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package les
-
-import (
-	"bytes"
-	"fmt"
-	"math"
-	"math/rand"
-	"reflect"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-func TestClientPoolL10C100Free(t *testing.T) {
-	testClientPool(t, 10, 100, 0, true)
-}
-
-func TestClientPoolL40C200Free(t *testing.T) {
-	testClientPool(t, 40, 200, 0, true)
-}
-
-func TestClientPoolL100C300Free(t *testing.T) {
-	testClientPool(t, 100, 300, 0, true)
-}
-
-func TestClientPoolL10C100P4(t *testing.T) {
-	testClientPool(t, 10, 100, 4, false)
-}
-
-func TestClientPoolL40C200P30(t *testing.T) {
-	testClientPool(t, 40, 200, 30, false)
-}
-
-func TestClientPoolL100C300P20(t *testing.T) {
-	testClientPool(t, 100, 300, 20, false)
-}
-
-const testClientPoolTicks = 100000
-
-type poolTestPeer int
-
-func (i poolTestPeer) ID() enode.ID {
-	return enode.ID{byte(i % 256), byte(i >> 8)}
-}
-
-func (i poolTestPeer) freeClientId() string {
-	return fmt.Sprintf("addr #%d", i)
-}
-
-func (i poolTestPeer) updateCapacity(uint64) {}
-
-type poolTestPeerWithCap struct {
-	poolTestPeer
-
-	cap uint64
-}
-
-func (i *poolTestPeerWithCap) updateCapacity(cap uint64) { i.cap = cap }
-
-func (i poolTestPeer) freezeClient() {}
-
-func testClientPool(t *testing.T, connLimit, clientCount, paidCount int, randomDisconnect bool) {
-	rand.Seed(time.Now().UnixNano())
-	var (
-		clock     mclock.Simulated
-		db        = rawdb.NewMemoryDatabase()
-		connected = make([]bool, clientCount)
-		connTicks = make([]int, clientCount)
-		disconnCh = make(chan int, clientCount)
-		disconnFn = func(id enode.ID) {
-			disconnCh <- int(id[0]) + int(id[1])<<8
-		}
-		pool = newClientPool(db, 1, &clock, disconnFn)
-	)
-	pool.disableBias = true
-	pool.setLimits(connLimit, uint64(connLimit))
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	// pool should accept new peers up to its connected limit
-	for i := 0; i < connLimit; i++ {
-		if pool.connect(poolTestPeer(i), 0) {
-			connected[i] = true
-		} else {
-			t.Fatalf("Test peer #%d rejected", i)
-		}
-	}
-	// randomly connect and disconnect peers, expect to have a similar total connection time at the end
-	for tickCounter := 0; tickCounter < testClientPoolTicks; tickCounter++ {
-		clock.Run(1 * time.Second)
-
-		if tickCounter == testClientPoolTicks/4 {
-			// give a positive balance to some of the peers
-			amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period
-			for i := 0; i < paidCount; i++ {
-				pool.addBalance(poolTestPeer(i).ID(), amount, "")
-			}
-		}
-
-		i := rand.Intn(clientCount)
-		if connected[i] {
-			if randomDisconnect {
-				pool.disconnect(poolTestPeer(i))
-				connected[i] = false
-				connTicks[i] += tickCounter
-			}
-		} else {
-			if pool.connect(poolTestPeer(i), 0) {
-				connected[i] = true
-				connTicks[i] -= tickCounter
-			}
-		}
-	pollDisconnects:
-		for {
-			select {
-			case i := <-disconnCh:
-				pool.disconnect(poolTestPeer(i))
-				if connected[i] {
-					connTicks[i] += tickCounter
-					connected[i] = false
-				}
-			default:
-				break pollDisconnects
-			}
-		}
-	}
-
-	expTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2*(connLimit-paidCount)/(clientCount-paidCount)
-	expMin := expTicks - expTicks/5
-	expMax := expTicks + expTicks/5
-	paidTicks := testClientPoolTicks/2*connLimit/clientCount + testClientPoolTicks/2
-	paidMin := paidTicks - paidTicks/5
-	paidMax := paidTicks + paidTicks/5
-
-	// check if the total connected time of peers are all in the expected range
-	for i, c := range connected {
-		if c {
-			connTicks[i] += testClientPoolTicks
-		}
-		min, max := expMin, expMax
-		if i < paidCount {
-			// expect a higher amount for clients with a positive balance
-			min, max = paidMin, paidMax
-		}
-		if connTicks[i] < min || connTicks[i] > max {
-			t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max)
-		}
-	}
-	pool.stop()
-}
-
-func TestConnectPaidClient(t *testing.T) {
-	var (
-		clock mclock.Simulated
-		db    = rawdb.NewMemoryDatabase()
-	)
-	pool := newClientPool(db, 1, &clock, nil)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10))
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	// Add balance for an external client and mark it as paid client
-	pool.addBalance(poolTestPeer(0).ID(), 1000, "")
-
-	if !pool.connect(poolTestPeer(0), 10) {
-		t.Fatalf("Failed to connect paid client")
-	}
-}
-
-func TestConnectPaidClientToSmallPool(t *testing.T) {
-	var (
-		clock mclock.Simulated
-		db    = rawdb.NewMemoryDatabase()
-	)
-	pool := newClientPool(db, 1, &clock, nil)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	// Add balance for an external client and mark it as paid client
-	pool.addBalance(poolTestPeer(0).ID(), 1000, "")
-
-	// Connect a fat paid client to pool, should reject it.
-	if pool.connect(poolTestPeer(0), 100) {
-		t.Fatalf("Connected fat paid client, should reject it")
-	}
-}
-
-func TestConnectPaidClientToFullPool(t *testing.T) {
-	var (
-		clock mclock.Simulated
-		db    = rawdb.NewMemoryDatabase()
-	)
-	removeFn := func(enode.ID) {} // Noop
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	for i := 0; i < 10; i++ {
-		pool.addBalance(poolTestPeer(i).ID(), 1000000000, "")
-		pool.connect(poolTestPeer(i), 1)
-	}
-	pool.addBalance(poolTestPeer(11).ID(), 1000, "") // Add low balance to new paid client
-	if pool.connect(poolTestPeer(11), 1) {
-		t.Fatalf("Low balance paid client should be rejected")
-	}
-	clock.Run(time.Second)
-	pool.addBalance(poolTestPeer(12).ID(), 1000000000*60*3, "") // Add high balance to new paid client
-	if !pool.connect(poolTestPeer(12), 1) {
-		t.Fatalf("High balance paid client should be accpected")
-	}
-}
-
-func TestPaidClientKickedOut(t *testing.T) {
-	var (
-		clock    mclock.Simulated
-		db       = rawdb.NewMemoryDatabase()
-		kickedCh = make(chan int, 1)
-	)
-	removeFn := func(id enode.ID) { kickedCh <- int(id[0]) }
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	for i := 0; i < 10; i++ {
-		pool.addBalance(poolTestPeer(i).ID(), 1000000000, "") // 1 second allowance
-		pool.connect(poolTestPeer(i), 1)
-		clock.Run(time.Millisecond)
-	}
-	clock.Run(time.Second)
-	clock.Run(connectedBias)
-	if !pool.connect(poolTestPeer(11), 0) {
-		t.Fatalf("Free client should be accectped")
-	}
-	select {
-	case id := <-kickedCh:
-		if id != 0 {
-			t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id)
-		}
-	case <-time.NewTimer(time.Second).C:
-		t.Fatalf("timeout")
-	}
-}
-
-func TestConnectFreeClient(t *testing.T) {
-	var (
-		clock mclock.Simulated
-		db    = rawdb.NewMemoryDatabase()
-	)
-	pool := newClientPool(db, 1, &clock, nil)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10))
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-	if !pool.connect(poolTestPeer(0), 10) {
-		t.Fatalf("Failed to connect free client")
-	}
-}
-
-func TestConnectFreeClientToFullPool(t *testing.T) {
-	var (
-		clock mclock.Simulated
-		db    = rawdb.NewMemoryDatabase()
-	)
-	removeFn := func(enode.ID) {} // Noop
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	for i := 0; i < 10; i++ {
-		pool.connect(poolTestPeer(i), 1)
-	}
-	if pool.connect(poolTestPeer(11), 1) {
-		t.Fatalf("New free client should be rejected")
-	}
-	clock.Run(time.Minute)
-	if pool.connect(poolTestPeer(12), 1) {
-		t.Fatalf("New free client should be rejected")
-	}
-	clock.Run(time.Millisecond)
-	clock.Run(4 * time.Minute)
-	if !pool.connect(poolTestPeer(13), 1) {
-		t.Fatalf("Old client connects more than 5min should be kicked")
-	}
-}
-
-func TestFreeClientKickedOut(t *testing.T) {
-	var (
-		clock  mclock.Simulated
-		db     = rawdb.NewMemoryDatabase()
-		kicked = make(chan int, 10)
-	)
-	removeFn := func(id enode.ID) { kicked <- int(id[0]) }
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	for i := 0; i < 10; i++ {
-		pool.connect(poolTestPeer(i), 1)
-		clock.Run(time.Millisecond)
-	}
-	if pool.connect(poolTestPeer(10), 1) {
-		t.Fatalf("New free client should be rejected")
-	}
-	clock.Run(5 * time.Minute)
-	for i := 0; i < 10; i++ {
-		pool.connect(poolTestPeer(i+10), 1)
-	}
-	for i := 0; i < 10; i++ {
-		select {
-		case id := <-kicked:
-			if id >= 10 {
-				t.Fatalf("Old client should be kicked, now got: %d", id)
-			}
-		case <-time.NewTimer(time.Second).C:
-			t.Fatalf("timeout")
-		}
-	}
-}
-
-func TestPositiveBalanceCalculation(t *testing.T) {
-	var (
-		clock  mclock.Simulated
-		db     = rawdb.NewMemoryDatabase()
-		kicked = make(chan int, 10)
-	)
-	removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	pool.addBalance(poolTestPeer(0).ID(), int64(time.Minute*3), "")
-	pool.connect(poolTestPeer(0), 10)
-	clock.Run(time.Minute)
-
-	pool.disconnect(poolTestPeer(0))
-	pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID())
-	if pb.value != uint64(time.Minute*2) {
-		t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb.value)
-	}
-}
-
-func TestDowngradePriorityClient(t *testing.T) {
-	var (
-		clock  mclock.Simulated
-		db     = rawdb.NewMemoryDatabase()
-		kicked = make(chan int, 10)
-	)
-	removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	p := &poolTestPeerWithCap{
-		poolTestPeer: poolTestPeer(0),
-	}
-	pool.addBalance(p.ID(), int64(time.Minute), "")
-	pool.connect(p, 10)
-	if p.cap != 10 {
-		t.Fatalf("The capcacity of priority peer hasn't been updated, got: %d", p.cap)
-	}
-
-	clock.Run(time.Minute)             // All positive balance should be used up.
-	time.Sleep(300 * time.Millisecond) // Ensure the callback is called
-	if p.cap != 1 {
-		t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap)
-	}
-	pb := pool.ndb.getOrNewPB(poolTestPeer(0).ID())
-	if pb.value != 0 {
-		t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb.value)
-	}
-
-	pool.addBalance(poolTestPeer(0).ID(), int64(time.Minute), "")
-	pb = pool.ndb.getOrNewPB(poolTestPeer(0).ID())
-	if pb.value != uint64(time.Minute) {
-		t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb.value)
-	}
-}
-
-func TestNegativeBalanceCalculation(t *testing.T) {
-	var (
-		clock  mclock.Simulated
-		db     = rawdb.NewMemoryDatabase()
-		kicked = make(chan int, 10)
-	)
-	removeFn := func(id enode.ID) { kicked <- int(id[0]) } // Noop
-	pool := newClientPool(db, 1, &clock, removeFn)
-	defer pool.stop()
-	pool.setLimits(10, uint64(10)) // Total capacity limit is 10
-	pool.setDefaultFactors(priceFactors{1, 0, 1}, priceFactors{1, 0, 1})
-
-	for i := 0; i < 10; i++ {
-		pool.connect(poolTestPeer(i), 1)
-	}
-	clock.Run(time.Second)
-
-	for i := 0; i < 10; i++ {
-		pool.disconnect(poolTestPeer(i))
-		nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId())
-		if nb.logValue != 0 {
-			t.Fatalf("Short connection shouldn't be recorded")
-		}
-	}
-
-	for i := 0; i < 10; i++ {
-		pool.connect(poolTestPeer(i), 1)
-	}
-	clock.Run(time.Minute)
-	for i := 0; i < 10; i++ {
-		pool.disconnect(poolTestPeer(i))
-		nb := pool.ndb.getOrNewNB(poolTestPeer(i).freeClientId())
-		nb.logValue -= pool.logOffset(clock.Now())
-		nb.logValue /= fixedPointMultiplier
-		if nb.logValue != int64(math.Log(float64(time.Minute/time.Second))) {
-			t.Fatalf("Negative balance mismatch, want %v, got %v", int64(math.Log(float64(time.Minute/time.Second))), nb.logValue)
-		}
-	}
-}
-
-func TestNodeDB(t *testing.T) {
-	ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{})
-	defer ndb.close()
-
-	if !bytes.Equal(ndb.verbuf[:], []byte{0x00, nodeDBVersion}) {
-		t.Fatalf("version buffer mismatch, want %v, got %v", []byte{0x00, nodeDBVersion}, ndb.verbuf)
-	}
-	var cases = []struct {
-		id       enode.ID
-		ip       string
-		balance  interface{}
-		positive bool
-	}{
-		{enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 100}, true},
-		{enode.ID{0x00, 0x01, 0x02}, "", posBalance{value: 200}, true},
-		{enode.ID{}, "127.0.0.1", negBalance{logValue: 10}, false},
-		{enode.ID{}, "127.0.0.1", negBalance{logValue: 20}, false},
-	}
-	for _, c := range cases {
-		if c.positive {
-			ndb.setPB(c.id, c.balance.(posBalance))
-			if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, c.balance.(posBalance)) {
-				t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance.(posBalance), pb)
-			}
-		} else {
-			ndb.setNB(c.ip, c.balance.(negBalance))
-			if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, c.balance.(negBalance)) {
-				t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance.(negBalance), nb)
-			}
-		}
-	}
-	for _, c := range cases {
-		if c.positive {
-			ndb.delPB(c.id)
-			if pb := ndb.getOrNewPB(c.id); !reflect.DeepEqual(pb, posBalance{}) {
-				t.Fatalf("Positive balance mismatch, want %v, got %v", posBalance{}, pb)
-			}
-		} else {
-			ndb.delNB(c.ip)
-			if nb := ndb.getOrNewNB(c.ip); !reflect.DeepEqual(nb, negBalance{}) {
-				t.Fatalf("Negative balance mismatch, want %v, got %v", negBalance{}, nb)
-			}
-		}
-	}
-	ndb.setCumulativeTime(100)
-	if ndb.getCumulativeTime() != 100 {
-		t.Fatalf("Cumulative time mismatch, want %v, got %v", 100, ndb.getCumulativeTime())
-	}
-}
-
-func TestNodeDBExpiration(t *testing.T) {
-	var (
-		iterated int
-		done     = make(chan struct{}, 1)
-	)
-	callback := func(now mclock.AbsTime, b negBalance) bool {
-		iterated += 1
-		return true
-	}
-	clock := &mclock.Simulated{}
-	ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock)
-	defer ndb.close()
-	ndb.nbEvictCallBack = callback
-	ndb.cleanupHook = func() { done <- struct{}{} }
-
-	var cases = []struct {
-		ip      string
-		balance negBalance
-	}{
-		{"127.0.0.1", negBalance{logValue: 1}},
-		{"127.0.0.2", negBalance{logValue: 1}},
-		{"127.0.0.3", negBalance{logValue: 1}},
-		{"127.0.0.4", negBalance{logValue: 1}},
-	}
-	for _, c := range cases {
-		ndb.setNB(c.ip, c.balance)
-	}
-	clock.WaitForTimers(1)
-	clock.Run(time.Hour + time.Minute)
-	select {
-	case <-done:
-	case <-time.NewTimer(time.Second).C:
-		t.Fatalf("timeout")
-	}
-	if iterated != 4 {
-		t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated)
-	}
-	clock.WaitForTimers(1)
-	for _, c := range cases {
-		ndb.setNB(c.ip, c.balance)
-	}
-	clock.Run(time.Hour + time.Minute)
-	select {
-	case <-done:
-	case <-time.NewTimer(time.Second).C:
-		t.Fatalf("timeout")
-	}
-	if iterated != 8 {
-		t.Fatalf("Failed to evict useless negative balances, want %v, got %d", 4, iterated)
-	}
-}
diff --git a/les/commons.go b/les/commons.go
index 29b5b7660e..d090fc21fc 100644
--- a/les/commons.go
+++ b/les/commons.go
@@ -25,12 +25,14 @@ import (
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
+	"github.com/ethereum/go-ethereum/ethclient"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/les/checkpointoracle"
 	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/discv5"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/params"
 )
@@ -39,17 +41,6 @@ func errResp(code errCode, format string, v ...interface{}) error {
 	return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
 }
 
-func lesTopic(genesisHash common.Hash, protocolVersion uint) discv5.Topic {
-	var name string
-	switch protocolVersion {
-	case lpv2:
-		name = "LES2"
-	default:
-		panic(nil)
-	}
-	return discv5.Topic(name + "@" + common.Bytes2Hex(genesisHash.Bytes()[0:8]))
-}
-
 type chainReader interface {
 	CurrentHeader() *types.Header
 }
@@ -57,10 +48,10 @@ type chainReader interface {
 // lesCommons contains fields needed by both server and client.
 type lesCommons struct {
 	genesis                      common.Hash
-	config                       *eth.Config
+	config                       *ethconfig.Config
 	chainConfig                  *params.ChainConfig
 	iConfig                      *light.IndexerConfig
-	chainDb                      ethdb.Database
+	chainDb, lesDb               ethdb.Database
 	chainReader                  chainReader
 	chtIndexer, bloomTrieIndexer *core.ChainIndexer
 	oracle                       *checkpointoracle.CheckpointOracle
@@ -81,7 +72,7 @@ type NodeInfo struct {
 }
 
 // makeProtocols creates protocol descriptors for the given LES versions.
-func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error, peerInfo func(id enode.ID) interface{}) []p2p.Protocol {
+func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error, peerInfo func(id enode.ID) interface{}, dialCandidates enode.Iterator) []p2p.Protocol {
 	protos := make([]p2p.Protocol, len(versions))
 	for i, version := range versions {
 		version := version
@@ -93,7 +84,8 @@ func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p
 			Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
 				return runPeer(version, peer, rw)
 			},
-			PeerInfo: peerInfo,
+			PeerInfo:       peerInfo,
+			DialCandidates: dialCandidates,
 		}
 	}
 	return protos
@@ -144,3 +136,26 @@ func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint {
 		BloomRoot:    light.GetBloomTrieRoot(c.chainDb, index, sectionHead),
 	}
 }
+
+// setupOracle sets up the checkpoint oracle contract client.
+func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *ethconfig.Config) *checkpointoracle.CheckpointOracle {
+	config := ethconfig.CheckpointOracle
+	if config == nil {
+		// Try loading default config.
+		config = params.CheckpointOracles[genesis]
+	}
+	if config == nil {
+		log.Info("Checkpoint oracle is not enabled")
+		return nil
+	}
+	if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
+		log.Warn("Invalid checkpoint oracle config")
+		return nil
+	}
+	oracle := checkpointoracle.New(config, c.localCheckpoint)
+	rpcClient, _ := node.Attach()
+	client := ethclient.NewClient(rpcClient)
+	oracle.Start(client)
+	log.Info("Configured checkpoint oracle", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold)
+	return oracle
+}
diff --git a/les/costtracker.go b/les/costtracker.go
index 0558779bc5..43e32a5b2d 100644
--- a/les/costtracker.go
+++ b/les/costtracker.go
@@ -24,7 +24,7 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/les/flowcontrol"
 	"github.com/ethereum/go-ethereum/log"
@@ -137,7 +137,7 @@ type costTracker struct {
 
 // newCostTracker creates a cost tracker and loads the cost factor statistics from the database.
 // It also returns the minimum capacity that can be assigned to any peer.
-func newCostTracker(db ethdb.Database, config *eth.Config) (*costTracker, uint64) {
+func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, uint64) {
 	utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100
 	ct := &costTracker{
 		db:         db,
diff --git a/les/distributor.go b/les/distributor.go
index 97d2ccdfea..31150e4d73 100644
--- a/les/distributor.go
+++ b/les/distributor.go
@@ -180,12 +180,11 @@ func (d *requestDistributor) loop() {
 type selectPeerItem struct {
 	peer   distPeer
 	req    *distReq
-	weight int64
+	weight uint64
 }
 
-// Weight implements wrsItem interface
-func (sp selectPeerItem) Weight() int64 {
-	return sp.weight
+func selectPeerWeight(i interface{}) uint64 {
+	return i.(selectPeerItem).weight
 }
 
 // nextRequest returns the next possible request from any peer, along with the
@@ -220,9 +219,9 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) {
 				wait, bufRemain := peer.waitBefore(cost)
 				if wait == 0 {
 					if sel == nil {
-						sel = utils.NewWeightedRandomSelect()
+						sel = utils.NewWeightedRandomSelect(selectPeerWeight)
 					}
-					sel.Update(selectPeerItem{peer: peer, req: req, weight: int64(bufRemain*1000000) + 1})
+					sel.Update(selectPeerItem{peer: peer, req: req, weight: uint64(bufRemain*1000000) + 1})
 				} else {
 					if bestWait == 0 || wait < bestWait {
 						bestWait = wait
diff --git a/les/enr_entry.go b/les/enr_entry.go
index c2a92dd999..307313fb10 100644
--- a/les/enr_entry.go
+++ b/les/enr_entry.go
@@ -17,16 +17,56 @@
 package les
 
 import (
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/p2p/dnsdisc"
+	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
 // lesEntry is the "les" ENR entry. This is set for LES servers only.
 type lesEntry struct {
 	// Ignore additional fields (for forward compatibility).
-	Rest []rlp.RawValue `rlp:"tail"`
+	VfxVersion uint
+	Rest       []rlp.RawValue `rlp:"tail"`
 }
 
-// ENRKey implements enr.Entry.
-func (e lesEntry) ENRKey() string {
-	return "les"
+func (lesEntry) ENRKey() string { return "les" }
+
+// ethEntry is the "eth" ENR entry. This is redeclared here to avoid depending on package eth.
+type ethEntry struct {
+	ForkID forkid.ID
+	Tail   []rlp.RawValue `rlp:"tail"`
+}
+
+func (ethEntry) ENRKey() string { return "eth" }
+
+// setupDiscovery creates the node discovery source for the eth protocol.
+func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) {
+	it := enode.NewFairMix(0)
+
+	// Enable DNS discovery.
+	if len(eth.config.EthDiscoveryURLs) != 0 {
+		client := dnsdisc.NewClient(dnsdisc.Config{})
+		dns, err := client.NewIterator(eth.config.EthDiscoveryURLs...)
+		if err != nil {
+			return nil, err
+		}
+		it.AddSource(dns)
+	}
+
+	// Enable DHT.
+	if eth.udpEnabled {
+		it.AddSource(eth.p2pServer.DiscV5.RandomNodes())
+	}
+
+	forkFilter := forkid.NewFilter(eth.blockchain)
+	iterator := enode.Filter(it, func(n *enode.Node) bool { return nodeIsServer(forkFilter, n) })
+	return iterator, nil
+}
+
+// nodeIsServer checks whether n is an LES server node.
+func nodeIsServer(forkFilter forkid.Filter, n *enode.Node) bool {
+	var les lesEntry
+	var eth ethEntry
+	return n.Load(&les) == nil && n.Load(ð) == nil && forkFilter(eth.ForkID) == nil
 }
diff --git a/les/fetcher.go b/les/fetcher.go
index 7fa81cbcbb..fc4c5e386a 100644
--- a/les/fetcher.go
+++ b/les/fetcher.go
@@ -18,874 +18,547 @@ package les
 
 import (
 	"math/big"
+	"math/rand"
 	"sync"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/eth/fetcher"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
 const (
-	blockDelayTimeout    = time.Second * 10 // timeout for a peer to announce a head that has already been confirmed by others
-	maxNodeCount         = 20               // maximum number of fetcherTreeNode entries remembered for each peer
-	serverStateAvailable = 100              // number of recent blocks where state availability is assumed
+	blockDelayTimeout    = 10 * time.Second       // Timeout for retrieving the headers from the peer
+	gatherSlack          = 100 * time.Millisecond // Interval used to collate almost-expired requests
+	cachedAnnosThreshold = 64                     // The maximum queued announcements
 )
 
-// lightFetcher implements retrieval of newly announced headers. It also provides a peerHasBlock function for the
-// ODR system to ensure that we only request data related to a certain block from peers who have already processed
-// and announced that block.
-type lightFetcher struct {
-	handler *clientHandler
-	chain   *light.LightChain
-
-	lock            sync.Mutex // lock protects access to the fetcher's internal state variables except sent requests
-	maxConfirmedTd  *big.Int
-	peers           map[*serverPeer]*fetcherPeerInfo
-	lastUpdateStats *updateStatsEntry
-	syncing         bool
-	syncDone        chan *serverPeer
-
-	reqMu             sync.RWMutex // reqMu protects access to sent header fetch requests
-	requested         map[uint64]fetchRequest
-	deliverChn        chan fetchResponse
-	timeoutChn        chan uint64
-	requestTriggered  bool
-	requestTrigger    chan struct{}
-	lastTrustedHeader *types.Header
-
-	closeCh chan struct{}
-	wg      sync.WaitGroup
-}
-
-// fetcherPeerInfo holds fetcher-specific information about each active peer
-type fetcherPeerInfo struct {
-	root, lastAnnounced *fetcherTreeNode
-	nodeCnt             int
-	confirmedTd         *big.Int
-	bestConfirmed       *fetcherTreeNode
-	nodeByHash          map[common.Hash]*fetcherTreeNode
-	firstUpdateStats    *updateStatsEntry
-}
-
-// fetcherTreeNode is a node of a tree that holds information about blocks recently
-// announced and confirmed by a certain peer. Each new announce message from a peer
-// adds nodes to the tree, based on the previous announced head and the reorg depth.
-// There are three possible states for a tree node:
-// - announced: not downloaded (known) yet, but we know its head, number and td
-// - intermediate: not known, hash and td are empty, they are filled out when it becomes known
-// - known: both announced by this peer and downloaded (from any peer).
-// This structure makes it possible to always know which peer has a certain block,
-// which is necessary for selecting a suitable peer for ODR requests and also for
-// canonizing new heads. It also helps to always download the minimum necessary
-// amount of headers with a single request.
-type fetcherTreeNode struct {
-	hash             common.Hash
-	number           uint64
-	td               *big.Int
-	known, requested bool
-	parent           *fetcherTreeNode
-	children         []*fetcherTreeNode
+// announce represents an new block announcement from the les server.
+type announce struct {
+	data   *announceData
+	trust  bool
+	peerid enode.ID
 }
 
-// fetchRequest represents a header download request
-type fetchRequest struct {
-	hash    common.Hash
-	amount  uint64
-	peer    *serverPeer
-	sent    mclock.AbsTime
-	timeout bool
+// request represents a record when the header request is sent.
+type request struct {
+	reqid  uint64
+	peerid enode.ID
+	sendAt time.Time
+	hash   common.Hash
 }
 
-// fetchResponse represents a header download response
-type fetchResponse struct {
-	reqID   uint64
+// response represents a response packet from network as well as a channel
+// to return all un-requested data.
+type response struct {
+	reqid   uint64
 	headers []*types.Header
-	peer    *serverPeer
-}
-
-// newLightFetcher creates a new light fetcher
-func newLightFetcher(h *clientHandler) *lightFetcher {
-	f := &lightFetcher{
-		handler:        h,
-		chain:          h.backend.blockchain,
-		peers:          make(map[*serverPeer]*fetcherPeerInfo),
-		deliverChn:     make(chan fetchResponse, 100),
-		requested:      make(map[uint64]fetchRequest),
-		timeoutChn:     make(chan uint64),
-		requestTrigger: make(chan struct{}, 1),
-		syncDone:       make(chan *serverPeer),
-		closeCh:        make(chan struct{}),
-		maxConfirmedTd: big.NewInt(0),
-	}
-	h.backend.peers.subscribe(f)
-
-	f.wg.Add(1)
-	go f.syncLoop()
-	return f
-}
-
-func (f *lightFetcher) close() {
-	close(f.closeCh)
-	f.wg.Wait()
-}
-
-// syncLoop is the main event loop of the light fetcher
-func (f *lightFetcher) syncLoop() {
-	defer f.wg.Done()
-	for {
-		select {
-		case <-f.closeCh:
-			return
-		// request loop keeps running until no further requests are necessary or possible
-		case <-f.requestTrigger:
-			f.lock.Lock()
-			var (
-				rq      *distReq
-				reqID   uint64
-				syncing bool
-			)
-			if !f.syncing {
-				rq, reqID, syncing = f.nextRequest()
-			}
-			f.requestTriggered = rq != nil
-			f.lock.Unlock()
-
-			if rq != nil {
-				if _, ok := <-f.handler.backend.reqDist.queue(rq); ok {
-					if syncing {
-						f.lock.Lock()
-						f.syncing = true
-						f.lock.Unlock()
-					} else {
-						go func() {
-							time.Sleep(softRequestTimeout)
-							f.reqMu.Lock()
-							req, ok := f.requested[reqID]
-							if ok {
-								req.timeout = true
-								f.requested[reqID] = req
-							}
-							f.reqMu.Unlock()
-							// keep starting new requests while possible
-							f.requestTrigger <- struct{}{}
-						}()
-					}
-				} else {
-					f.requestTrigger <- struct{}{}
-				}
-			}
-		case reqID := <-f.timeoutChn:
-			f.reqMu.Lock()
-			req, ok := f.requested[reqID]
-			if ok {
-				delete(f.requested, reqID)
-			}
-			f.reqMu.Unlock()
-			if ok {
-				f.handler.backend.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), true)
-				req.peer.Log().Debug("Fetching data timed out hard")
-				go f.handler.removePeer(req.peer.id)
-			}
-		case resp := <-f.deliverChn:
-			f.reqMu.Lock()
-			req, ok := f.requested[resp.reqID]
-			if ok && req.peer != resp.peer {
-				ok = false
-			}
-			if ok {
-				delete(f.requested, resp.reqID)
-			}
-			f.reqMu.Unlock()
-			if ok {
-				f.handler.backend.serverPool.adjustResponseTime(req.peer.poolEntry, time.Duration(mclock.Now()-req.sent), req.timeout)
-			}
-			f.lock.Lock()
-			if !ok || !(f.syncing || f.processResponse(req, resp)) {
-				resp.peer.Log().Debug("Failed processing response")
-				go f.handler.removePeer(resp.peer.id)
-			}
-			f.lock.Unlock()
-		case p := <-f.syncDone:
-			f.lock.Lock()
-			p.Log().Debug("Done synchronising with peer")
-			f.checkSyncedHeaders(p)
-			f.syncing = false
-			f.lock.Unlock()
-			f.requestTrigger <- struct{}{} // f.requestTriggered is always true here
-		}
-	}
+	peerid  enode.ID
+	remain  chan []*types.Header
 }
 
-// registerPeer adds a new peer to the fetcher's peer set
-func (f *lightFetcher) registerPeer(p *serverPeer) {
-	p.lock.Lock()
-	p.hasBlock = func(hash common.Hash, number uint64, hasState bool) bool {
-		return f.peerHasBlock(p, hash, number, hasState)
-	}
-	p.lock.Unlock()
+// fetcherPeer holds the fetcher-specific information for each active peer
+type fetcherPeer struct {
+	latest *announceData // The latest announcement sent from the peer
 
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	f.peers[p] = &fetcherPeerInfo{nodeByHash: make(map[common.Hash]*fetcherTreeNode)}
+	// These following two fields can track the latest announces
+	// from the peer with limited size for caching. We hold the
+	// assumption that all enqueued announces are td-monotonic.
+	announces     map[common.Hash]*announce // Announcement map
+	announcesList []common.Hash             // FIFO announces list
 }
 
-// unregisterPeer removes a new peer from the fetcher's peer set
-func (f *lightFetcher) unregisterPeer(p *serverPeer) {
-	p.lock.Lock()
-	p.hasBlock = nil
-	p.lock.Unlock()
-
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	// check for potential timed out block delay statistics
-	f.checkUpdateStats(p, nil)
-	delete(f.peers, p)
-}
-
-// announce processes a new announcement message received from a peer, adding new
-// nodes to the peer's block tree and removing old nodes if necessary
-func (f *lightFetcher) announce(p *serverPeer, head *announceData) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	p.Log().Debug("Received new announcement", "number", head.Number, "hash", head.Hash, "reorg", head.ReorgDepth)
-
-	fp := f.peers[p]
-	if fp == nil {
-		p.Log().Debug("Announcement from unknown peer")
+// addAnno enqueues an new trusted announcement. If the queued announces overflow,
+// evict from the oldest.
+func (fp *fetcherPeer) addAnno(anno *announce) {
+	// Short circuit if the anno already exists. In normal case it should
+	// never happen since only monotonic anno is accepted. But the adversary
+	// may feed us fake announces with higher td but same hash. In this case,
+	// ignore the anno anyway.
+	hash := anno.data.Hash
+	if _, exist := fp.announces[hash]; exist {
 		return
 	}
+	fp.announces[hash] = anno
+	fp.announcesList = append(fp.announcesList, hash)
 
-	if fp.lastAnnounced != nil && head.Td.Cmp(fp.lastAnnounced.td) <= 0 {
-		// announced tds should be strictly monotonic
-		p.Log().Debug("Received non-monotonic td", "current", head.Td, "previous", fp.lastAnnounced.td)
-		go f.handler.removePeer(p.id)
-		return
-	}
-
-	n := fp.lastAnnounced
-	for i := uint64(0); i < head.ReorgDepth; i++ {
-		if n == nil {
-			break
+	// Evict oldest if the announces are oversized.
+	if len(fp.announcesList)-cachedAnnosThreshold > 0 {
+		for i := 0; i < len(fp.announcesList)-cachedAnnosThreshold; i++ {
+			delete(fp.announces, fp.announcesList[i])
 		}
-		n = n.parent
+		copy(fp.announcesList, fp.announcesList[len(fp.announcesList)-cachedAnnosThreshold:])
+		fp.announcesList = fp.announcesList[:cachedAnnosThreshold]
 	}
-	// n is now the reorg common ancestor, add a new branch of nodes
-	if n != nil && (head.Number >= n.number+maxNodeCount || head.Number <= n.number) {
-		// if announced head block height is lower or same as n or too far from it to add
-		// intermediate nodes then discard previous announcement info and trigger a resync
-		n = nil
-		fp.nodeCnt = 0
-		fp.nodeByHash = make(map[common.Hash]*fetcherTreeNode)
-	}
-	// check if the node count is too high to add new nodes, discard oldest ones if necessary
-	if n != nil {
-		// n is now the reorg common ancestor, add a new branch of nodes
-		// check if the node count is too high to add new nodes
-		locked := false
-		for uint64(fp.nodeCnt)+head.Number-n.number > maxNodeCount && fp.root != nil {
-			if !locked {
-				f.chain.LockChain()
-				defer f.chain.UnlockChain()
-				locked = true
-			}
-			// if one of root's children is canonical, keep it, delete other branches and root itself
-			var newRoot *fetcherTreeNode
-			for i, nn := range fp.root.children {
-				if rawdb.ReadCanonicalHash(f.handler.backend.chainDb, nn.number) == nn.hash {
-					fp.root.children = append(fp.root.children[:i], fp.root.children[i+1:]...)
-					nn.parent = nil
-					newRoot = nn
-					break
-				}
-			}
-			fp.deleteNode(fp.root)
-			if n == fp.root {
-				n = newRoot
-			}
-			fp.root = newRoot
-			if newRoot == nil || !f.checkKnownNode(p, newRoot) {
-				fp.bestConfirmed = nil
-				fp.confirmedTd = nil
-			}
+}
 
-			if n == nil {
-				break
-			}
-		}
-		if n != nil {
-			for n.number < head.Number {
-				nn := &fetcherTreeNode{number: n.number + 1, parent: n}
-				n.children = append(n.children, nn)
-				n = nn
-				fp.nodeCnt++
-			}
-			n.hash = head.Hash
-			n.td = head.Td
-			fp.nodeByHash[n.hash] = n
+// forwardAnno removes all announces from the map with a number lower than
+// the provided threshold.
+func (fp *fetcherPeer) forwardAnno(td *big.Int) []*announce {
+	var (
+		cutset  int
+		evicted []*announce
+	)
+	for ; cutset < len(fp.announcesList); cutset++ {
+		anno := fp.announces[fp.announcesList[cutset]]
+		if anno == nil {
+			continue // In theory it should never ever happen
 		}
-	}
-
-	if n == nil {
-		// could not find reorg common ancestor or had to delete entire tree, a new root and a resync is needed
-		if fp.root != nil {
-			fp.deleteNode(fp.root)
+		if anno.data.Td.Cmp(td) > 0 {
+			break
 		}
-		n = &fetcherTreeNode{hash: head.Hash, number: head.Number, td: head.Td}
-		fp.root = n
-		fp.nodeCnt++
-		fp.nodeByHash[n.hash] = n
-		fp.bestConfirmed = nil
-		fp.confirmedTd = nil
+		evicted = append(evicted, anno)
+		delete(fp.announces, anno.data.Hash)
 	}
-
-	f.checkKnownNode(p, n)
-	p.lock.Lock()
-	p.headInfo = blockInfo{Number: head.Number, Hash: head.Hash, Td: head.Td}
-	fp.lastAnnounced = n
-	p.lock.Unlock()
-	f.checkUpdateStats(p, nil)
-	if !f.requestTriggered {
-		f.requestTriggered = true
-		f.requestTrigger <- struct{}{}
+	if cutset > 0 {
+		copy(fp.announcesList, fp.announcesList[cutset:])
+		fp.announcesList = fp.announcesList[:len(fp.announcesList)-cutset]
 	}
+	return evicted
 }
 
-// peerHasBlock returns true if we can assume the peer knows the given block
-// based on its announcements
-func (f *lightFetcher) peerHasBlock(p *serverPeer, hash common.Hash, number uint64, hasState bool) bool {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-
-	fp := f.peers[p]
-	if fp == nil || fp.root == nil {
-		return false
-	}
+// lightFetcher implements retrieval of newly announced headers. It reuses
+// the eth.BlockFetcher as the underlying fetcher but adding more additional
+// rules: e.g. evict "timeout" peers.
+type lightFetcher struct {
+	// Various handlers
+	ulc     *ulc
+	chaindb ethdb.Database
+	reqDist *requestDistributor
+	peerset *serverPeerSet        // The global peerset of light client which shared by all components
+	chain   *light.LightChain     // The local light chain which maintains the canonical header chain.
+	fetcher *fetcher.BlockFetcher // The underlying fetcher which takes care block header retrieval.
+
+	// Peerset maintained by fetcher
+	plock sync.RWMutex
+	peers map[enode.ID]*fetcherPeer
+
+	// Various channels
+	announceCh chan *announce
+	requestCh  chan *request
+	deliverCh  chan *response
+	syncDone   chan *types.Header
 
-	if hasState {
-		if fp.lastAnnounced == nil || fp.lastAnnounced.number > number+serverStateAvailable {
-			return false
-		}
-	}
+	closeCh chan struct{}
+	wg      sync.WaitGroup
 
-	if f.syncing {
-		// always return true when syncing
-		// false positives are acceptable, a more sophisticated condition can be implemented later
-		return true
-	}
+	// Callback
+	synchronise func(peer *serverPeer)
 
-	if number >= fp.root.number {
-		// it is recent enough that if it is known, is should be in the peer's block tree
-		return fp.nodeByHash[hash] != nil
-	}
-	f.chain.LockChain()
-	defer f.chain.UnlockChain()
-	// if it's older than the peer's block tree root but it's in the same canonical chain
-	// as the root, we can still be sure the peer knows it
-	//
-	// when syncing, just check if it is part of the known chain, there is nothing better we
-	// can do since we do not know the most recent block hash yet
-	return rawdb.ReadCanonicalHash(f.handler.backend.chainDb, fp.root.number) == fp.root.hash && rawdb.ReadCanonicalHash(f.handler.backend.chainDb, number) == hash
+	// Test fields or hooks
+	noAnnounce  bool
+	newHeadHook func(*types.Header)
+	newAnnounce func(*serverPeer, *announceData)
 }
 
-// requestAmount calculates the amount of headers to be downloaded starting
-// from a certain head backwards
-func (f *lightFetcher) requestAmount(p *serverPeer, n *fetcherTreeNode) uint64 {
-	amount := uint64(0)
-	nn := n
-	for nn != nil && !f.checkKnownNode(p, nn) {
-		nn = nn.parent
-		amount++
-	}
-	if nn == nil {
-		amount = n.number
+// newLightFetcher creates a light fetcher instance.
+func newLightFetcher(chain *light.LightChain, engine consensus.Engine, peers *serverPeerSet, ulc *ulc, chaindb ethdb.Database, reqDist *requestDistributor, syncFn func(p *serverPeer)) *lightFetcher {
+	// Construct the fetcher by offering all necessary APIs
+	validator := func(header *types.Header) error {
+		// Disable seal verification explicitly if we are running in ulc mode.
+		return engine.VerifyHeader(chain, header, ulc == nil)
+	}
+	heighter := func() uint64 { return chain.CurrentHeader().Number.Uint64() }
+	dropper := func(id string) { peers.unregister(id) }
+	inserter := func(headers []*types.Header) (int, error) {
+		// Disable PoW checking explicitly if we are running in ulc mode.
+		checkFreq := 1
+		if ulc != nil {
+			checkFreq = 0
+		}
+		return chain.InsertHeaderChain(headers, checkFreq)
 	}
-	return amount
+	f := &lightFetcher{
+		ulc:         ulc,
+		peerset:     peers,
+		chaindb:     chaindb,
+		chain:       chain,
+		reqDist:     reqDist,
+		fetcher:     fetcher.NewBlockFetcher(true, chain.GetHeaderByHash, nil, validator, nil, heighter, inserter, nil, dropper),
+		peers:       make(map[enode.ID]*fetcherPeer),
+		synchronise: syncFn,
+		announceCh:  make(chan *announce),
+		requestCh:   make(chan *request),
+		deliverCh:   make(chan *response),
+		syncDone:    make(chan *types.Header),
+		closeCh:     make(chan struct{}),
+	}
+	peers.subscribe(f)
+	return f
 }
 
-// requestedID tells if a certain reqID has been requested by the fetcher
-func (f *lightFetcher) requestedID(reqID uint64) bool {
-	f.reqMu.RLock()
-	_, ok := f.requested[reqID]
-	f.reqMu.RUnlock()
-	return ok
+func (f *lightFetcher) start() {
+	f.wg.Add(1)
+	f.fetcher.Start()
+	go f.mainloop()
 }
 
-// nextRequest selects the peer and announced head to be requested next, amount
-// to be downloaded starting from the head backwards is also returned
-func (f *lightFetcher) nextRequest() (*distReq, uint64, bool) {
-	var (
-		bestHash    common.Hash
-		bestAmount  uint64
-		bestTd      *big.Int
-		bestSyncing bool
-	)
-	bestHash, bestAmount, bestTd, bestSyncing = f.findBestRequest()
-
-	if bestTd == f.maxConfirmedTd {
-		return nil, 0, false
-	}
-
-	var rq *distReq
-	reqID := genReqID()
-	if bestSyncing {
-		rq = f.newFetcherDistReqForSync(bestHash)
-	} else {
-		rq = f.newFetcherDistReq(bestHash, reqID, bestAmount)
-	}
-	return rq, reqID, bestSyncing
+func (f *lightFetcher) stop() {
+	close(f.closeCh)
+	f.fetcher.Stop()
+	f.wg.Wait()
 }
 
-// findBestRequest finds the best head to request that has been announced by but not yet requested from a known peer.
-// It also returns the announced Td (which should be verified after fetching the head),
-// the necessary amount to request and whether a downloader sync is necessary instead of a normal header request.
-func (f *lightFetcher) findBestRequest() (bestHash common.Hash, bestAmount uint64, bestTd *big.Int, bestSyncing bool) {
-	bestTd = f.maxConfirmedTd
-	bestSyncing = false
+// registerPeer adds an new peer to the fetcher's peer set
+func (f *lightFetcher) registerPeer(p *serverPeer) {
+	f.plock.Lock()
+	defer f.plock.Unlock()
 
-	for p, fp := range f.peers {
-		for hash, n := range fp.nodeByHash {
-			if f.checkKnownNode(p, n) || n.requested {
-				continue
-			}
-			// if ulc mode is disabled, isTrustedHash returns true
-			amount := f.requestAmount(p, n)
-			if (bestTd == nil || n.td.Cmp(bestTd) > 0 || amount < bestAmount) && (f.isTrustedHash(hash) || f.maxConfirmedTd.Int64() == 0) {
-				bestHash = hash
-				bestTd = n.td
-				bestAmount = amount
-				bestSyncing = fp.bestConfirmed == nil || fp.root == nil || !f.checkKnownNode(p, fp.root)
-			}
-		}
-	}
-	return
+	f.peers[p.ID()] = &fetcherPeer{announces: make(map[common.Hash]*announce)}
 }
 
-// isTrustedHash checks if the block can be trusted by the minimum trusted fraction.
-func (f *lightFetcher) isTrustedHash(hash common.Hash) bool {
-	// If ultra light cliet mode is disabled, trust all hashes
-	if f.handler.ulc == nil {
-		return true
-	}
-	// Ultra light enabled, only trust after enough confirmations
-	var agreed int
-	for peer, info := range f.peers {
-		if peer.trusted && info.nodeByHash[hash] != nil {
-			agreed++
-		}
-	}
-	return 100*agreed/len(f.handler.ulc.keys) >= f.handler.ulc.fraction
-}
+// unregisterPeer removes the specified peer from the fetcher's peer set
+func (f *lightFetcher) unregisterPeer(p *serverPeer) {
+	f.plock.Lock()
+	defer f.plock.Unlock()
 
-func (f *lightFetcher) newFetcherDistReqForSync(bestHash common.Hash) *distReq {
-	return &distReq{
-		getCost: func(dp distPeer) uint64 {
-			return 0
-		},
-		canSend: func(dp distPeer) bool {
-			p := dp.(*serverPeer)
-			f.lock.Lock()
-			defer f.lock.Unlock()
-
-			if p.onlyAnnounce {
-				return false
-			}
-			fp := f.peers[p]
-			return fp != nil && fp.nodeByHash[bestHash] != nil
-		},
-		request: func(dp distPeer) func() {
-			if f.handler.ulc != nil {
-				// Keep last trusted header before sync
-				f.setLastTrustedHeader(f.chain.CurrentHeader())
-			}
-			go func() {
-				p := dp.(*serverPeer)
-				p.Log().Debug("Synchronisation started")
-				f.handler.synchronise(p)
-				f.syncDone <- p
-			}()
-			return nil
-		},
-	}
+	delete(f.peers, p.ID())
 }
 
-// newFetcherDistReq creates a new request for the distributor.
-func (f *lightFetcher) newFetcherDistReq(bestHash common.Hash, reqID uint64, bestAmount uint64) *distReq {
-	return &distReq{
-		getCost: func(dp distPeer) uint64 {
-			p := dp.(*serverPeer)
-			return p.getRequestCost(GetBlockHeadersMsg, int(bestAmount))
-		},
-		canSend: func(dp distPeer) bool {
-			p := dp.(*serverPeer)
-			f.lock.Lock()
-			defer f.lock.Unlock()
-
-			if p.onlyAnnounce {
-				return false
-			}
-			fp := f.peers[p]
-			if fp == nil {
-				return false
-			}
-			n := fp.nodeByHash[bestHash]
-			return n != nil && !n.requested
-		},
-		request: func(dp distPeer) func() {
-			p := dp.(*serverPeer)
-			f.lock.Lock()
-			fp := f.peers[p]
-			if fp != nil {
-				n := fp.nodeByHash[bestHash]
-				if n != nil {
-					n.requested = true
-				}
-			}
-			f.lock.Unlock()
-
-			cost := p.getRequestCost(GetBlockHeadersMsg, int(bestAmount))
-			p.fcServer.QueuedRequest(reqID, cost)
-			f.reqMu.Lock()
-			f.requested[reqID] = fetchRequest{hash: bestHash, amount: bestAmount, peer: p, sent: mclock.Now()}
-			f.reqMu.Unlock()
-			go func() {
-				time.Sleep(hardRequestTimeout)
-				f.timeoutChn <- reqID
-			}()
-			return func() { p.requestHeadersByHash(reqID, bestHash, int(bestAmount), 0, true) }
-		},
-	}
-}
+// peer returns the peer from the fetcher peerset.
+func (f *lightFetcher) peer(id enode.ID) *fetcherPeer {
+	f.plock.RLock()
+	defer f.plock.RUnlock()
 
-// deliverHeaders delivers header download request responses for processing
-func (f *lightFetcher) deliverHeaders(peer *serverPeer, reqID uint64, headers []*types.Header) {
-	f.deliverChn <- fetchResponse{reqID: reqID, headers: headers, peer: peer}
+	return f.peers[id]
 }
 
-// processResponse processes header download request responses, returns true if successful
-func (f *lightFetcher) processResponse(req fetchRequest, resp fetchResponse) bool {
-	if uint64(len(resp.headers)) != req.amount || resp.headers[0].Hash() != req.hash {
-		req.peer.Log().Debug("Response content mismatch", "requested", len(resp.headers), "reqfrom", resp.headers[0], "delivered", req.amount, "delfrom", req.hash)
-		return false
-	}
-	headers := make([]*types.Header, req.amount)
-	for i, header := range resp.headers {
-		headers[int(req.amount)-1-i] = header
-	}
+// forEachPeer iterates the fetcher peerset, abort the iteration if the
+// callback returns false.
+func (f *lightFetcher) forEachPeer(check func(id enode.ID, p *fetcherPeer) bool) {
+	f.plock.RLock()
+	defer f.plock.RUnlock()
 
-	if _, err := f.chain.InsertHeaderChain(headers, 1); err != nil {
-		if err == consensus.ErrFutureBlock {
-			return true
-		}
-		log.Debug("Failed to insert header chain", "err", err)
-		return false
-	}
-	tds := make([]*big.Int, len(headers))
-	for i, header := range headers {
-		td := f.chain.GetTd(header.Hash(), header.Number.Uint64())
-		if td == nil {
-			log.Debug("Total difficulty not found for header", "index", i+1, "number", header.Number, "hash", header.Hash())
-			return false
+	for id, peer := range f.peers {
+		if !check(id, peer) {
+			return
 		}
-		tds[i] = td
 	}
-	f.newHeaders(headers, tds)
-	return true
 }
 
-// newHeaders updates the block trees of all active peers according to a newly
-// downloaded and validated batch or headers
-func (f *lightFetcher) newHeaders(headers []*types.Header, tds []*big.Int) {
-	var maxTd *big.Int
+// mainloop is the main event loop of the light fetcher, which is responsible for
+// - announcement maintenance(ulc)
+//   If we are running in ultra light client mode, then all announcements from
+//   the trusted servers are maintained. If the same announcements from trusted
+//   servers reach the threshold, then the relevant header is requested for retrieval.
+//
+// - block header retrieval
+//   Whenever we receive announce with higher td compared with local chain, the
+//   request will be made for header retrieval.
+//
+// - re-sync trigger
+//   If the local chain lags too much, then the fetcher will enter "synnchronise"
+//   mode to retrieve missing headers in batch.
+func (f *lightFetcher) mainloop() {
+	defer f.wg.Done()
 
-	for p, fp := range f.peers {
-		if !f.checkAnnouncedHeaders(fp, headers, tds) {
-			p.Log().Debug("Inconsistent announcement")
-			go f.handler.removePeer(p.id)
-		}
-		if fp.confirmedTd != nil && (maxTd == nil || maxTd.Cmp(fp.confirmedTd) > 0) {
-			maxTd = fp.confirmedTd
-		}
-	}
+	var (
+		syncInterval = uint64(1) // Interval used to trigger a light resync.
+		syncing      bool        // Indicator whether the client is syncing
 
-	if maxTd != nil {
-		f.updateMaxConfirmedTd(maxTd)
-	}
-}
+		ulc          = f.ulc != nil
+		headCh       = make(chan core.ChainHeadEvent, 100)
+		fetching     = make(map[uint64]*request)
+		requestTimer = time.NewTimer(0)
 
-// checkAnnouncedHeaders updates peer's block tree if necessary after validating
-// a batch of headers. It searches for the latest header in the batch that has a
-// matching tree node (if any), and if it has not been marked as known already,
-// sets it and its parents to known (even those which are older than the currently
-// validated ones). Return value shows if all hashes, numbers and Tds matched
-// correctly to the announced values (otherwise the peer should be dropped).
-func (f *lightFetcher) checkAnnouncedHeaders(fp *fetcherPeerInfo, headers []*types.Header, tds []*big.Int) bool {
-	var (
-		n      *fetcherTreeNode
-		header *types.Header
-		td     *big.Int
+		// Local status
+		localHead = f.chain.CurrentHeader()
+		localTd   = f.chain.GetTd(localHead.Hash(), localHead.Number.Uint64())
 	)
+	sub := f.chain.SubscribeChainHeadEvent(headCh)
+	defer sub.Unsubscribe()
+
+	// reset updates the local status with given header.
+	reset := func(header *types.Header) {
+		localHead = header
+		localTd = f.chain.GetTd(header.Hash(), header.Number.Uint64())
+	}
+	// trustedHeader returns an indicator whether the header is regarded as
+	// trusted. If we are running in the ulc mode, only when we receive enough
+	// same announcement from trusted server, the header will be trusted.
+	trustedHeader := func(hash common.Hash, number uint64) (bool, []enode.ID) {
+		var (
+			agreed  []enode.ID
+			trusted bool
+		)
+		f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool {
+			if anno := p.announces[hash]; anno != nil && anno.trust && anno.data.Number == number {
+				agreed = append(agreed, id)
+				if 100*len(agreed)/len(f.ulc.keys) >= f.ulc.fraction {
+					trusted = true
+					return false // abort iteration
+				}
+			}
+			return true
+		})
+		return trusted, agreed
+	}
+	for {
+		select {
+		case anno := <-f.announceCh:
+			peerid, data := anno.peerid, anno.data
+			log.Debug("Received new announce", "peer", peerid, "number", data.Number, "hash", data.Hash, "reorg", data.ReorgDepth)
 
-	for i := len(headers) - 1; ; i-- {
-		if i < 0 {
-			if n == nil {
-				// no more headers and nothing to match
-				return true
+			peer := f.peer(peerid)
+			if peer == nil {
+				log.Debug("Receive announce from unknown peer", "peer", peerid)
+				continue
 			}
-			// we ran out of recently delivered headers but have not reached a node known by this peer yet, continue matching
-			hash, number := header.ParentHash, header.Number.Uint64()-1
-			td = f.chain.GetTd(hash, number)
-			header = f.chain.GetHeader(hash, number)
-			if header == nil || td == nil {
-				log.Error("Missing parent of validated header", "hash", hash, "number", number)
-				return false
+			// Announced tds should be strictly monotonic, drop the peer if
+			// the announce is out-of-order.
+			if peer.latest != nil && data.Td.Cmp(peer.latest.Td) <= 0 {
+				f.peerset.unregister(peerid.String())
+				log.Debug("Non-monotonic td", "peer", peerid, "current", data.Td, "previous", peer.latest.Td)
+				continue
 			}
-		} else {
-			header = headers[i]
-			td = tds[i]
-		}
-		hash := header.Hash()
-		number := header.Number.Uint64()
-		if n == nil {
-			n = fp.nodeByHash[hash]
-		}
-		if n != nil {
-			if n.td == nil {
-				// node was unannounced
-				if nn := fp.nodeByHash[hash]; nn != nil {
-					// if there was already a node with the same hash, continue there and drop this one
-					nn.children = append(nn.children, n.children...)
-					n.children = nil
-					fp.deleteNode(n)
-					n = nn
-				} else {
-					n.hash = hash
-					n.td = td
-					fp.nodeByHash[hash] = n
+			peer.latest = data
+
+			// Filter out any stale announce, the local chain is ahead of announce
+			if localTd != nil && data.Td.Cmp(localTd) <= 0 {
+				continue
+			}
+			peer.addAnno(anno)
+
+			// If we are not syncing, try to trigger a single retrieval or re-sync
+			if !ulc && !syncing {
+				// Two scenarios lead to re-sync:
+				// - reorg happens
+				// - local chain lags
+				// We can't retrieve the parent of the announce by single retrieval
+				// in both cases, so resync is necessary.
+				if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 {
+					syncing = true
+					go f.startSync(peerid)
+					log.Debug("Trigger light sync", "peer", peerid, "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash)
+					continue
 				}
+				f.fetcher.Notify(peerid.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(peerid), nil)
+				log.Debug("Trigger header retrieval", "peer", peerid, "number", data.Number, "hash", data.Hash)
 			}
-			// check if it matches the header
-			if n.hash != hash || n.number != number || n.td.Cmp(td) != 0 {
-				// peer has previously made an invalid announcement
-				return false
+			// Keep collecting announces from trusted server even we are syncing.
+			if ulc && anno.trust {
+				// Notify underlying fetcher to retrieve header or trigger a resync if
+				// we have receive enough announcements from trusted server.
+				trusted, agreed := trustedHeader(data.Hash, data.Number)
+				if trusted && !syncing {
+					if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 {
+						syncing = true
+						go f.startSync(peerid)
+						log.Debug("Trigger trusted light sync", "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash)
+						continue
+					}
+					p := agreed[rand.Intn(len(agreed))]
+					f.fetcher.Notify(p.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(p), nil)
+					log.Debug("Trigger trusted header retrieval", "number", data.Number, "hash", data.Hash)
+				}
 			}
-			if n.known {
-				// we reached a known node that matched our expectations, return with success
-				return true
+
+		case req := <-f.requestCh:
+			fetching[req.reqid] = req // Tracking all in-flight requests for response latency statistic.
+			if len(fetching) == 1 {
+				f.rescheduleTimer(fetching, requestTimer)
 			}
-			n.known = true
-			if fp.confirmedTd == nil || td.Cmp(fp.confirmedTd) > 0 {
-				fp.confirmedTd = td
-				fp.bestConfirmed = n
+
+		case <-requestTimer.C:
+			for reqid, request := range fetching {
+				if time.Since(request.sendAt) > blockDelayTimeout-gatherSlack {
+					delete(fetching, reqid)
+					f.peerset.unregister(request.peerid.String())
+					log.Debug("Request timeout", "peer", request.peerid, "reqid", reqid)
+				}
 			}
-			n = n.parent
-			if n == nil {
+			f.rescheduleTimer(fetching, requestTimer)
+
+		case resp := <-f.deliverCh:
+			if req := fetching[resp.reqid]; req != nil {
+				delete(fetching, resp.reqid)
+				f.rescheduleTimer(fetching, requestTimer)
+
+				// The underlying fetcher does not check the consistency of request and response.
+				// The adversary can send the fake announces with invalid hash and number but always
+				// delivery some mismatched header. So it can't be punished by the underlying fetcher.
+				// We have to add two more rules here to detect.
+				if len(resp.headers) != 1 {
+					f.peerset.unregister(req.peerid.String())
+					log.Debug("Deliver more than requested", "peer", req.peerid, "reqid", req.reqid)
+					continue
+				}
+				if resp.headers[0].Hash() != req.hash {
+					f.peerset.unregister(req.peerid.String())
+					log.Debug("Deliver invalid header", "peer", req.peerid, "reqid", req.reqid)
+					continue
+				}
+				resp.remain <- f.fetcher.FilterHeaders(resp.peerid.String(), resp.headers, time.Now())
+			} else {
+				// Discard the entire packet no matter it's a timeout response or unexpected one.
+				resp.remain <- resp.headers
+			}
+
+		case ev := <-headCh:
+			// Short circuit if we are still syncing.
+			if syncing {
+				continue
+			}
+			reset(ev.Block.Header())
+
+			// Clean stale announcements from les-servers.
+			var droplist []enode.ID
+			f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool {
+				removed := p.forwardAnno(localTd)
+				for _, anno := range removed {
+					if header := f.chain.GetHeaderByHash(anno.data.Hash); header != nil {
+						if header.Number.Uint64() != anno.data.Number {
+							droplist = append(droplist, id)
+							break
+						}
+						// In theory td should exists.
+						td := f.chain.GetTd(anno.data.Hash, anno.data.Number)
+						if td != nil && td.Cmp(anno.data.Td) != 0 {
+							droplist = append(droplist, id)
+							break
+						}
+					}
+				}
 				return true
+			})
+			for _, id := range droplist {
+				f.peerset.unregister(id.String())
+				log.Debug("Kicked out peer for invalid announcement")
 			}
+			if f.newHeadHook != nil {
+				f.newHeadHook(localHead)
+			}
+
+		case origin := <-f.syncDone:
+			syncing = false // Reset the status
+
+			// Rewind all untrusted headers for ulc mode.
+			if ulc {
+				head := f.chain.CurrentHeader()
+				ancestor := rawdb.FindCommonAncestor(f.chaindb, origin, head)
+				var untrusted []common.Hash
+				for head.Number.Cmp(ancestor.Number) > 0 {
+					hash, number := head.Hash(), head.Number.Uint64()
+					if trusted, _ := trustedHeader(hash, number); trusted {
+						break
+					}
+					untrusted = append(untrusted, hash)
+					head = f.chain.GetHeader(head.ParentHash, number-1)
+				}
+				if len(untrusted) > 0 {
+					for i, j := 0, len(untrusted)-1; i < j; i, j = i+1, j-1 {
+						untrusted[i], untrusted[j] = untrusted[j], untrusted[i]
+					}
+					f.chain.Rollback(untrusted)
+				}
+			}
+			// Reset local status.
+			reset(f.chain.CurrentHeader())
+			if f.newHeadHook != nil {
+				f.newHeadHook(localHead)
+			}
+			log.Debug("light sync finished", "number", localHead.Number, "hash", localHead.Hash())
+
+		case <-f.closeCh:
+			return
 		}
 	}
 }
 
-// checkSyncedHeaders updates peer's block tree after synchronisation by marking
-// downloaded headers as known. If none of the announced headers are found after
-// syncing, the peer is dropped.
-func (f *lightFetcher) checkSyncedHeaders(p *serverPeer) {
-	fp := f.peers[p]
-	if fp == nil {
-		p.Log().Debug("Unknown peer to check sync headers")
-		return
-	}
-	var (
-		node = fp.lastAnnounced
-		td   *big.Int
-	)
-	if f.handler.ulc != nil {
-		// Roll back untrusted blocks
-		h, unapproved := f.lastTrustedTreeNode(p)
-		f.chain.Rollback(unapproved)
-		node = fp.nodeByHash[h.Hash()]
+// announce processes a new announcement message received from a peer.
+func (f *lightFetcher) announce(p *serverPeer, head *announceData) {
+	if f.newAnnounce != nil {
+		f.newAnnounce(p, head)
 	}
-	// Find last valid block
-	for node != nil {
-		if td = f.chain.GetTd(node.hash, node.number); td != nil {
-			break
-		}
-		node = node.parent
+	if f.noAnnounce {
+		return
 	}
-	// Now node is the latest downloaded/approved header after syncing
-	if node == nil {
-		p.Log().Debug("Synchronisation failed")
-		go f.handler.removePeer(p.id)
+	select {
+	case f.announceCh <- &announce{peerid: p.ID(), trust: p.trusted, data: head}:
+	case <-f.closeCh:
 		return
 	}
-	header := f.chain.GetHeader(node.hash, node.number)
-	f.newHeaders([]*types.Header{header}, []*big.Int{td})
 }
 
-// lastTrustedTreeNode return last approved treeNode and a list of unapproved hashes
-func (f *lightFetcher) lastTrustedTreeNode(p *serverPeer) (*types.Header, []common.Hash) {
-	unapprovedHashes := make([]common.Hash, 0)
-	current := f.chain.CurrentHeader()
-
-	if f.lastTrustedHeader == nil {
-		return current, unapprovedHashes
-	}
-
-	canonical := f.chain.CurrentHeader()
-	if canonical.Number.Uint64() > f.lastTrustedHeader.Number.Uint64() {
-		canonical = f.chain.GetHeaderByNumber(f.lastTrustedHeader.Number.Uint64())
-	}
-	commonAncestor := rawdb.FindCommonAncestor(f.handler.backend.chainDb, canonical, f.lastTrustedHeader)
-	if commonAncestor == nil {
-		log.Error("Common ancestor of last trusted header and canonical header is nil", "canonical hash", canonical.Hash(), "trusted hash", f.lastTrustedHeader.Hash())
-		return current, unapprovedHashes
+// trackRequest sends a reqID to main loop for in-flight request tracking.
+func (f *lightFetcher) trackRequest(peerid enode.ID, reqid uint64, hash common.Hash) {
+	select {
+	case f.requestCh <- &request{reqid: reqid, peerid: peerid, sendAt: time.Now(), hash: hash}:
+	case <-f.closeCh:
 	}
+}
 
-	for current.Hash() == commonAncestor.Hash() {
-		if f.isTrustedHash(current.Hash()) {
-			break
+// requestHeaderByHash constructs a header retrieval request and sends it to
+// local request distributor.
+//
+// Note, we rely on the underlying eth/fetcher to retrieve and validate the
+// response, so that we have to obey the rule of eth/fetcher which only accepts
+// the response from given peer.
+func (f *lightFetcher) requestHeaderByHash(peerid enode.ID) func(common.Hash) error {
+	return func(hash common.Hash) error {
+		req := &distReq{
+			getCost: func(dp distPeer) uint64 { return dp.(*serverPeer).getRequestCost(GetBlockHeadersMsg, 1) },
+			canSend: func(dp distPeer) bool { return dp.(*serverPeer).ID() == peerid },
+			request: func(dp distPeer) func() {
+				peer, id := dp.(*serverPeer), genReqID()
+				cost := peer.getRequestCost(GetBlockHeadersMsg, 1)
+				peer.fcServer.QueuedRequest(id, cost)
+
+				return func() {
+					f.trackRequest(peer.ID(), id, hash)
+					peer.requestHeadersByHash(id, hash, 1, 0, false)
+				}
+			},
 		}
-		unapprovedHashes = append(unapprovedHashes, current.Hash())
-		current = f.chain.GetHeader(current.ParentHash, current.Number.Uint64()-1)
+		f.reqDist.queue(req)
+		return nil
 	}
-	return current, unapprovedHashes
 }
 
-func (f *lightFetcher) setLastTrustedHeader(h *types.Header) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	f.lastTrustedHeader = h
-}
-
-// checkKnownNode checks if a block tree node is known (downloaded and validated)
-// If it was not known previously but found in the database, sets its known flag
-func (f *lightFetcher) checkKnownNode(p *serverPeer, n *fetcherTreeNode) bool {
-	if n.known {
-		return true
-	}
-	td := f.chain.GetTd(n.hash, n.number)
-	if td == nil {
-		return false
-	}
-	header := f.chain.GetHeader(n.hash, n.number)
-	// check the availability of both header and td because reads are not protected by chain db mutex
-	// Note: returning false is always safe here
-	if header == nil {
-		return false
-	}
+// requestResync invokes synchronisation callback to start syncing.
+func (f *lightFetcher) startSync(id enode.ID) {
+	defer func(header *types.Header) {
+		f.syncDone <- header
+	}(f.chain.CurrentHeader())
 
-	fp := f.peers[p]
-	if fp == nil {
-		p.Log().Debug("Unknown peer to check known nodes")
-		return false
-	}
-	if !f.checkAnnouncedHeaders(fp, []*types.Header{header}, []*big.Int{td}) {
-		p.Log().Debug("Inconsistent announcement")
-		go f.handler.removePeer(p.id)
-	}
-	if fp.confirmedTd != nil {
-		f.updateMaxConfirmedTd(fp.confirmedTd)
-	}
-	return n.known
-}
-
-// deleteNode deletes a node and its child subtrees from a peer's block tree
-func (fp *fetcherPeerInfo) deleteNode(n *fetcherTreeNode) {
-	if n.parent != nil {
-		for i, nn := range n.parent.children {
-			if nn == n {
-				n.parent.children = append(n.parent.children[:i], n.parent.children[i+1:]...)
-				break
-			}
-		}
-	}
-	for {
-		if n.td != nil {
-			delete(fp.nodeByHash, n.hash)
-		}
-		fp.nodeCnt--
-		if len(n.children) == 0 {
-			return
-		}
-		for i, nn := range n.children {
-			if i == 0 {
-				n = nn
-			} else {
-				fp.deleteNode(nn)
-			}
-		}
+	peer := f.peerset.peer(id.String())
+	if peer == nil || peer.onlyAnnounce {
+		return
 	}
+	f.synchronise(peer)
 }
 
-// updateStatsEntry items form a linked list that is expanded with a new item every time a new head with a higher Td
-// than the previous one has been downloaded and validated. The list contains a series of maximum confirmed Td values
-// and the time these values have been confirmed, both increasing monotonically. A maximum confirmed Td is calculated
-// both globally for all peers and also for each individual peer (meaning that the given peer has announced the head
-// and it has also been downloaded from any peer, either before or after the given announcement).
-// The linked list has a global tail where new confirmed Td entries are added and a separate head for each peer,
-// pointing to the next Td entry that is higher than the peer's max confirmed Td (nil if it has already confirmed
-// the current global head).
-type updateStatsEntry struct {
-	time mclock.AbsTime
-	td   *big.Int
-	next *updateStatsEntry
-}
-
-// updateMaxConfirmedTd updates the block delay statistics of active peers. Whenever a new highest Td is confirmed,
-// adds it to the end of a linked list together with the time it has been confirmed. Then checks which peers have
-// already confirmed a head with the same or higher Td (which counts as zero block delay) and updates their statistics.
-// Those who have not confirmed such a head by now will be updated by a subsequent checkUpdateStats call with a
-// positive block delay value.
-func (f *lightFetcher) updateMaxConfirmedTd(td *big.Int) {
-	if f.maxConfirmedTd == nil || td.Cmp(f.maxConfirmedTd) > 0 {
-		f.maxConfirmedTd = td
-		newEntry := &updateStatsEntry{
-			time: mclock.Now(),
-			td:   td,
-		}
-		if f.lastUpdateStats != nil {
-			f.lastUpdateStats.next = newEntry
-		}
-
-		f.lastUpdateStats = newEntry
-		for p := range f.peers {
-			f.checkUpdateStats(p, newEntry)
-		}
-	}
+// deliverHeaders delivers header download request responses for processing
+func (f *lightFetcher) deliverHeaders(peer *serverPeer, reqid uint64, headers []*types.Header) []*types.Header {
+	remain := make(chan []*types.Header, 1)
+	select {
+	case f.deliverCh <- &response{reqid: reqid, headers: headers, peerid: peer.ID(), remain: remain}:
+	case <-f.closeCh:
+		return nil
+	}
+	return <-remain
 }
 
-// checkUpdateStats checks those peers who have not confirmed a certain highest Td (or a larger one) by the time it
-// has been confirmed by another peer. If they have confirmed such a head by now, their stats are updated with the
-// block delay which is (this peer's confirmation time)-(first confirmation time). After blockDelayTimeout has passed,
-// the stats are updated with blockDelayTimeout value. In either case, the confirmed or timed out updateStatsEntry
-// items are removed from the head of the linked list.
-// If a new entry has been added to the global tail, it is passed as a parameter here even though this function
-// assumes that it has already been added, so that if the peer's list is empty (all heads confirmed, head is nil),
-// it can set the new head to newEntry.
-func (f *lightFetcher) checkUpdateStats(p *serverPeer, newEntry *updateStatsEntry) {
-	now := mclock.Now()
-	fp := f.peers[p]
-	if fp == nil {
-		p.Log().Debug("Unknown peer to check update stats")
+// rescheduleTimer resets the specified timeout timer to the next request timeout.
+func (f *lightFetcher) rescheduleTimer(requests map[uint64]*request, timer *time.Timer) {
+	// Short circuit if no inflight requests
+	if len(requests) == 0 {
+		timer.Stop()
 		return
 	}
-
-	if newEntry != nil && fp.firstUpdateStats == nil {
-		fp.firstUpdateStats = newEntry
-	}
-	for fp.firstUpdateStats != nil && fp.firstUpdateStats.time <= now-mclock.AbsTime(blockDelayTimeout) {
-		f.handler.backend.serverPool.adjustBlockDelay(p.poolEntry, blockDelayTimeout)
-		fp.firstUpdateStats = fp.firstUpdateStats.next
-	}
-	if fp.confirmedTd != nil {
-		for fp.firstUpdateStats != nil && fp.firstUpdateStats.td.Cmp(fp.confirmedTd) <= 0 {
-			f.handler.backend.serverPool.adjustBlockDelay(p.poolEntry, time.Duration(now-fp.firstUpdateStats.time))
-			fp.firstUpdateStats = fp.firstUpdateStats.next
+	// Otherwise find the earliest expiring request
+	earliest := time.Now()
+	for _, req := range requests {
+		if earliest.After(req.sendAt) {
+			earliest = req.sendAt
 		}
 	}
+	timer.Reset(blockDelayTimeout - time.Since(earliest))
 }
diff --git a/les/fetcher_test.go b/les/fetcher_test.go
new file mode 100644
index 0000000000..d3a74d25c2
--- /dev/null
+++ b/les/fetcher_test.go
@@ -0,0 +1,293 @@
+// 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 .
+
+package les
+
+import (
+	"math/big"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+// verifyImportEvent verifies that one single event arrive on an import channel.
+func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
+	if arrive {
+		select {
+		case <-imported:
+		case <-time.After(time.Second):
+			t.Fatalf("import timeout")
+		}
+	} else {
+		select {
+		case <-imported:
+			t.Fatalf("import invoked")
+		case <-time.After(20 * time.Millisecond):
+		}
+	}
+}
+
+// verifyImportDone verifies that no more events are arriving on an import channel.
+func verifyImportDone(t *testing.T, imported chan interface{}) {
+	select {
+	case <-imported:
+		t.Fatalf("extra block imported")
+	case <-time.After(50 * time.Millisecond):
+	}
+}
+
+// verifyChainHeight verifies the chain height is as expected.
+func verifyChainHeight(t *testing.T, fetcher *lightFetcher, height uint64) {
+	local := fetcher.chain.CurrentHeader().Number.Uint64()
+	if local != height {
+		t.Fatalf("chain height mismatch, got %d, want %d", local, height)
+	}
+}
+
+func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements(t, 2) }
+func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) }
+
+func testSequentialAnnouncements(t *testing.T, protocol int) {
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	s, c, teardown := newClientServerEnv(t, netconfig)
+	defer teardown()
+
+	// Create connected peer pair.
+	c.handler.fetcher.noAnnounce = true // Ignore the first announce from peer which can trigger a resync.
+	p1, _, err := newTestPeerPair("peer", protocol, s.handler, c.handler)
+	if err != nil {
+		t.Fatalf("Failed to create peer pair %v", err)
+	}
+	c.handler.fetcher.noAnnounce = false
+
+	importCh := make(chan interface{})
+	c.handler.fetcher.newHeadHook = func(header *types.Header) {
+		importCh <- header
+	}
+	for i := uint64(1); i <= s.backend.Blockchain().CurrentHeader().Number.Uint64(); i++ {
+		header := s.backend.Blockchain().GetHeaderByNumber(i)
+		hash, number := header.Hash(), header.Number.Uint64()
+		td := rawdb.ReadTd(s.db, hash, number)
+
+		announce := announceData{hash, number, td, 0, nil}
+		if p1.cpeer.announceType == announceTypeSigned {
+			announce.sign(s.handler.server.privateKey)
+		}
+		p1.cpeer.sendAnnounce(announce)
+		verifyImportEvent(t, importCh, true)
+	}
+	verifyImportDone(t, importCh)
+	verifyChainHeight(t, c.handler.fetcher, 4)
+}
+
+func TestGappedAnnouncementsLes2(t *testing.T) { testGappedAnnouncements(t, 2) }
+func TestGappedAnnouncementsLes3(t *testing.T) { testGappedAnnouncements(t, 3) }
+
+func testGappedAnnouncements(t *testing.T, protocol int) {
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	s, c, teardown := newClientServerEnv(t, netconfig)
+	defer teardown()
+
+	// Create connected peer pair.
+	c.handler.fetcher.noAnnounce = true // Ignore the first announce from peer which can trigger a resync.
+	peer, _, err := newTestPeerPair("peer", protocol, s.handler, c.handler)
+	if err != nil {
+		t.Fatalf("Failed to create peer pair %v", err)
+	}
+	c.handler.fetcher.noAnnounce = false
+
+	done := make(chan *types.Header, 1)
+	c.handler.fetcher.newHeadHook = func(header *types.Header) { done <- header }
+
+	// Prepare announcement by latest header.
+	latest := s.backend.Blockchain().CurrentHeader()
+	hash, number := latest.Hash(), latest.Number.Uint64()
+	td := rawdb.ReadTd(s.db, hash, number)
+
+	// Sign the announcement if necessary.
+	announce := announceData{hash, number, td, 0, nil}
+	if peer.cpeer.announceType == announceTypeSigned {
+		announce.sign(s.handler.server.privateKey)
+	}
+	peer.cpeer.sendAnnounce(announce)
+
+	<-done // Wait syncing
+	verifyChainHeight(t, c.handler.fetcher, 4)
+
+	// Send a reorged announcement
+	var newAnno = make(chan struct{}, 1)
+	c.handler.fetcher.noAnnounce = true
+	c.handler.fetcher.newAnnounce = func(*serverPeer, *announceData) {
+		newAnno <- struct{}{}
+	}
+	blocks, _ := core.GenerateChain(rawdb.ReadChainConfig(s.db, s.backend.Blockchain().Genesis().Hash()), s.backend.Blockchain().GetBlockByNumber(3),
+		ethash.NewFaker(), s.db, 2, func(i int, gen *core.BlockGen) {
+			gen.OffsetTime(-9) // higher block difficulty
+		})
+	s.backend.Blockchain().InsertChain(blocks)
+	<-newAnno
+	c.handler.fetcher.noAnnounce = false
+	c.handler.fetcher.newAnnounce = nil
+
+	latest = blocks[len(blocks)-1].Header()
+	hash, number = latest.Hash(), latest.Number.Uint64()
+	td = rawdb.ReadTd(s.db, hash, number)
+
+	announce = announceData{hash, number, td, 1, nil}
+	if peer.cpeer.announceType == announceTypeSigned {
+		announce.sign(s.handler.server.privateKey)
+	}
+	peer.cpeer.sendAnnounce(announce)
+
+	<-done // Wait syncing
+	verifyChainHeight(t, c.handler.fetcher, 5)
+}
+
+func TestTrustedAnnouncementsLes2(t *testing.T) { testTrustedAnnouncement(t, 2) }
+func TestTrustedAnnouncementsLes3(t *testing.T) { testTrustedAnnouncement(t, 3) }
+
+func testTrustedAnnouncement(t *testing.T, protocol int) {
+	var (
+		servers   []*testServer
+		teardowns []func()
+		nodes     []*enode.Node
+		ids       []string
+		cpeers    []*clientPeer
+		speers    []*serverPeer
+	)
+	for i := 0; i < 10; i++ {
+		s, n, teardown := newTestServerPeer(t, 10, protocol)
+
+		servers = append(servers, s)
+		nodes = append(nodes, n)
+		teardowns = append(teardowns, teardown)
+
+		// A half of them are trusted servers.
+		if i < 5 {
+			ids = append(ids, n.String())
+		}
+	}
+	netconfig := testnetConfig{
+		protocol:    protocol,
+		nopruning:   true,
+		ulcServers:  ids,
+		ulcFraction: 60,
+	}
+	_, c, teardown := newClientServerEnv(t, netconfig)
+	defer teardown()
+	defer func() {
+		for i := 0; i < len(teardowns); i++ {
+			teardowns[i]()
+		}
+	}()
+
+	c.handler.fetcher.noAnnounce = true // Ignore the first announce from peer which can trigger a resync.
+
+	// Connect all server instances.
+	for i := 0; i < len(servers); i++ {
+		sp, cp, err := connect(servers[i].handler, nodes[i].ID(), c.handler, protocol)
+		if err != nil {
+			t.Fatalf("connect server and client failed, err %s", err)
+		}
+		cpeers = append(cpeers, cp)
+		speers = append(speers, sp)
+	}
+	c.handler.fetcher.noAnnounce = false
+
+	newHead := make(chan *types.Header, 1)
+	c.handler.fetcher.newHeadHook = func(header *types.Header) { newHead <- header }
+
+	check := func(height []uint64, expected uint64, callback func()) {
+		for i := 0; i < len(height); i++ {
+			for j := 0; j < len(servers); j++ {
+				h := servers[j].backend.Blockchain().GetHeaderByNumber(height[i])
+				hash, number := h.Hash(), h.Number.Uint64()
+				td := rawdb.ReadTd(servers[j].db, hash, number)
+
+				// Sign the announcement if necessary.
+				announce := announceData{hash, number, td, 0, nil}
+				p := cpeers[j]
+				if p.announceType == announceTypeSigned {
+					announce.sign(servers[j].handler.server.privateKey)
+				}
+				p.sendAnnounce(announce)
+			}
+		}
+		if callback != nil {
+			callback()
+		}
+		verifyChainHeight(t, c.handler.fetcher, expected)
+	}
+	check([]uint64{1}, 1, func() { <-newHead })   // Sequential announcements
+	check([]uint64{4}, 4, func() { <-newHead })   // ULC-style light syncing, rollback untrusted headers
+	check([]uint64{10}, 10, func() { <-newHead }) // Sync the whole chain.
+}
+
+func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) }
+func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) }
+func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) }
+
+func testInvalidAnnounces(t *testing.T, protocol int) {
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	s, c, teardown := newClientServerEnv(t, netconfig)
+	defer teardown()
+
+	// Create connected peer pair.
+	c.handler.fetcher.noAnnounce = true // Ignore the first announce from peer which can trigger a resync.
+	peer, _, err := newTestPeerPair("peer", lpv3, s.handler, c.handler)
+	if err != nil {
+		t.Fatalf("Failed to create peer pair %v", err)
+	}
+	c.handler.fetcher.noAnnounce = false
+
+	done := make(chan *types.Header, 1)
+	c.handler.fetcher.newHeadHook = func(header *types.Header) { done <- header }
+
+	// Prepare announcement by latest header.
+	headerOne := s.backend.Blockchain().GetHeaderByNumber(1)
+	hash, number := headerOne.Hash(), headerOne.Number.Uint64()
+	td := big.NewInt(200) // bad td
+
+	// Sign the announcement if necessary.
+	announce := announceData{hash, number, td, 0, nil}
+	if peer.cpeer.announceType == announceTypeSigned {
+		announce.sign(s.handler.server.privateKey)
+	}
+	peer.cpeer.sendAnnounce(announce)
+	<-done // Wait syncing
+
+	// Ensure the bad peer is evicited
+	if c.handler.backend.peers.len() != 0 {
+		t.Fatalf("Failed to evict invalid peer")
+	}
+}
diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go
index 490013677c..4f0de82318 100644
--- a/les/flowcontrol/control.go
+++ b/les/flowcontrol/control.go
@@ -19,6 +19,7 @@ package flowcontrol
 
 import (
 	"fmt"
+	"math"
 	"sync"
 	"time"
 
@@ -316,6 +317,9 @@ func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) {
 	node.lock.RLock()
 	defer node.lock.RUnlock()
 
+	if node.params.BufLimit == 0 {
+		return time.Duration(math.MaxInt64), 0
+	}
 	now := node.clock.Now()
 	node.recalcBLE(now)
 	maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst)
diff --git a/les/flowcontrol/manager.go b/les/flowcontrol/manager.go
index d6d0b1adde..c9e681c144 100644
--- a/les/flowcontrol/manager.go
+++ b/les/flowcontrol/manager.go
@@ -108,7 +108,7 @@ type ClientManager struct {
 func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager {
 	cm := &ClientManager{
 		clock:         clock,
-		rcQueue:       prque.New(func(a interface{}, i int) { a.(*ClientNode).queueIndex = i }),
+		rcQueue:       prque.NewWrapAround(func(a interface{}, i int) { a.(*ClientNode).queueIndex = i }),
 		capLastUpdate: clock.Now(),
 		stop:          make(chan chan struct{}),
 	}
diff --git a/les/handler_test.go b/les/handler_test.go
index 1612caf427..d1dbee6bdf 100644
--- a/les/handler_test.go
+++ b/les/handler_test.go
@@ -49,11 +49,19 @@ func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}
 // Tests that block headers can be retrieved from a remote chain based on user queries.
 func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) }
 func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) }
+func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) }
 
 func testGetBlockHeaders(t *testing.T, protocol int) {
-	server, tearDown := newServerEnv(t, downloader.MaxHashFetch+15, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    downloader.MaxHeaderFetch + 15,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
 	bc := server.handler.blockchain
 
 	// Create a "random" unknown hash for testing
@@ -64,27 +72,27 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 	// Create a batch of tests for various scenarios
 	limit := uint64(MaxHeaderFetch)
 	tests := []struct {
-		query  *getBlockHeadersData // The query to execute for header retrieval
+		query  *GetBlockHeadersData // The query to execute for header retrieval
 		expect []common.Hash        // The hashes of the block whose headers are expected
 	}{
 		// A single random block should be retrievable by hash and number too
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
+			&GetBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1},
 			[]common.Hash{bc.GetBlockByNumber(limit / 2).Hash()},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1},
 			[]common.Hash{bc.GetBlockByNumber(limit / 2).Hash()},
 		},
 		// Multiple headers should be retrievable in both directions
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3},
 			[]common.Hash{
 				bc.GetBlockByNumber(limit / 2).Hash(),
 				bc.GetBlockByNumber(limit/2 + 1).Hash(),
 				bc.GetBlockByNumber(limit/2 + 2).Hash(),
 			},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true},
 			[]common.Hash{
 				bc.GetBlockByNumber(limit / 2).Hash(),
 				bc.GetBlockByNumber(limit/2 - 1).Hash(),
@@ -93,14 +101,14 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 		},
 		// Multiple headers with skip lists should be retrievable
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3},
 			[]common.Hash{
 				bc.GetBlockByNumber(limit / 2).Hash(),
 				bc.GetBlockByNumber(limit/2 + 4).Hash(),
 				bc.GetBlockByNumber(limit/2 + 8).Hash(),
 			},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true},
 			[]common.Hash{
 				bc.GetBlockByNumber(limit / 2).Hash(),
 				bc.GetBlockByNumber(limit/2 - 4).Hash(),
@@ -109,26 +117,26 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 		},
 		// The chain endpoints should be retrievable
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1},
 			[]common.Hash{bc.GetBlockByNumber(0).Hash()},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64()}, Amount: 1},
 			[]common.Hash{bc.CurrentBlock().Hash()},
 		},
 		// Ensure protocol limits are honored
 		//{
-		//	&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
+		//	&GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 1}, Amount: limit + 10, Reverse: true},
 		//	[]common.Hash{},
 		//},
 		// Check that requesting more than available is handled gracefully
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 3, Amount: 3},
 			[]common.Hash{
 				bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(),
 				bc.GetBlockByNumber(bc.CurrentBlock().NumberU64()).Hash(),
 			},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true},
 			[]common.Hash{
 				bc.GetBlockByNumber(4).Hash(),
 				bc.GetBlockByNumber(0).Hash(),
@@ -136,13 +144,13 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 		},
 		// Check that requesting more than available is handled gracefully, even if mid skip
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() - 4}, Skip: 2, Amount: 3},
 			[]common.Hash{
 				bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 4).Hash(),
 				bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1).Hash(),
 			},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true},
 			[]common.Hash{
 				bc.GetBlockByNumber(4).Hash(),
 				bc.GetBlockByNumber(1).Hash(),
@@ -150,10 +158,10 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 		},
 		// Check that non existing headers aren't returned
 		{
-			&getBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
+			&GetBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1},
 			[]common.Hash{},
 		}, {
-			&getBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1},
+			&GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().NumberU64() + 1}, Amount: 1},
 			[]common.Hash{},
 		},
 	}
@@ -168,8 +176,8 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 		// Send the hash request and verify the response
 		reqID++
 
-		sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, tt.query)
-		if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil {
+		sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, tt.query)
+		if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil {
 			t.Errorf("test %d: headers mismatch: %v", i, err)
 		}
 	}
@@ -178,11 +186,20 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
 // Tests that block contents can be retrieved from a remote chain based on their hashes.
 func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) }
 func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) }
+func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) }
 
 func testGetBlockBodies(t *testing.T, protocol int) {
-	server, tearDown := newServerEnv(t, downloader.MaxBlockFetch+15, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    downloader.MaxHeaderFetch + 15,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	// Create a batch of tests for various scenarios
@@ -245,8 +262,8 @@ func testGetBlockBodies(t *testing.T, protocol int) {
 		reqID++
 
 		// Send the hash request and verify the response
-		sendRequest(server.peer.app, GetBlockBodiesMsg, reqID, hashes)
-		if err := expectResponse(server.peer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
+		sendRequest(rawPeer.app, GetBlockBodiesMsg, reqID, hashes)
+		if err := expectResponse(rawPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil {
 			t.Errorf("test %d: bodies mismatch: %v", i, err)
 		}
 	}
@@ -255,11 +272,21 @@ func testGetBlockBodies(t *testing.T, protocol int) {
 // Tests that the contract codes can be retrieved based on account addresses.
 func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) }
 func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) }
+func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) }
 
 func testGetCode(t *testing.T, protocol int) {
 	// Assemble the test environment
-	server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
+
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	var codereqs []*CodeReq
@@ -276,8 +303,8 @@ func testGetCode(t *testing.T, protocol int) {
 		}
 	}
 
-	sendRequest(server.peer.app, GetCodeMsg, 42, codereqs)
-	if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
+	sendRequest(rawPeer.app, GetCodeMsg, 42, codereqs)
+	if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil {
 		t.Errorf("codes mismatch: %v", err)
 	}
 }
@@ -285,10 +312,20 @@ func testGetCode(t *testing.T, protocol int) {
 // Tests that the stale contract codes can't be retrieved based on account addresses.
 func TestGetStaleCodeLes2(t *testing.T) { testGetStaleCode(t, 2) }
 func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) }
+func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) }
 
 func testGetStaleCode(t *testing.T, protocol int) {
-	server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    core.TriesInMemory + 4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
+
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	check := func(number uint64, expected [][]byte) {
@@ -296,8 +333,8 @@ func testGetStaleCode(t *testing.T, protocol int) {
 			BHash:  bc.GetHeaderByNumber(number).Hash(),
 			AccKey: crypto.Keccak256(testContractAddr[:]),
 		}
-		sendRequest(server.peer.app, GetCodeMsg, 42, []*CodeReq{req})
-		if err := expectResponse(server.peer.app, CodeMsg, 42, testBufLimit, expected); err != nil {
+		sendRequest(rawPeer.app, GetCodeMsg, 42, []*CodeReq{req})
+		if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil {
 			t.Errorf("codes mismatch: %v", err)
 		}
 	}
@@ -309,12 +346,21 @@ func testGetStaleCode(t *testing.T, protocol int) {
 // Tests that the transaction receipts can be retrieved based on hashes.
 func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) }
 func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) }
+func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) }
 
 func testGetReceipt(t *testing.T, protocol int) {
 	// Assemble the test environment
-	server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	// Collect the hashes to request, and the response to expect
@@ -327,8 +373,8 @@ func testGetReceipt(t *testing.T, protocol int) {
 		receipts = append(receipts, rawdb.ReadRawReceipts(server.db, block.Hash(), block.NumberU64()))
 	}
 	// Send the hash request and verify the response
-	sendRequest(server.peer.app, GetReceiptsMsg, 42, hashes)
-	if err := expectResponse(server.peer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
+	sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes)
+	if err := expectResponse(rawPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil {
 		t.Errorf("receipts mismatch: %v", err)
 	}
 }
@@ -336,12 +382,21 @@ func testGetReceipt(t *testing.T, protocol int) {
 // Tests that trie merkle proofs can be retrieved
 func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) }
 func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) }
+func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) }
 
 func testGetProofs(t *testing.T, protocol int) {
 	// Assemble the test environment
-	server, tearDown := newServerEnv(t, 4, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	var proofreqs []ProofReq
@@ -362,8 +417,8 @@ func testGetProofs(t *testing.T, protocol int) {
 		}
 	}
 	// Send the proof request and verify the response
-	sendRequest(server.peer.app, GetProofsV2Msg, 42, proofreqs)
-	if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil {
+	sendRequest(rawPeer.app, GetProofsV2Msg, 42, proofreqs)
+	if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil {
 		t.Errorf("proofs mismatch: %v", err)
 	}
 }
@@ -371,10 +426,20 @@ func testGetProofs(t *testing.T, protocol int) {
 // Tests that the stale contract codes can't be retrieved based on account addresses.
 func TestGetStaleProofLes2(t *testing.T) { testGetStaleProof(t, 2) }
 func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) }
+func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) }
 
 func testGetStaleProof(t *testing.T, protocol int) {
-	server, tearDown := newServerEnv(t, core.TriesInMemory+4, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		blocks:    core.TriesInMemory + 4,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
+
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	check := func(number uint64, wantOK bool) {
@@ -386,7 +451,7 @@ func testGetStaleProof(t *testing.T, protocol int) {
 			BHash: header.Hash(),
 			Key:   account,
 		}
-		sendRequest(server.peer.app, GetProofsV2Msg, 42, []*ProofReq{req})
+		sendRequest(rawPeer.app, GetProofsV2Msg, 42, []*ProofReq{req})
 
 		var expected []rlp.RawValue
 		if wantOK {
@@ -395,7 +460,7 @@ func testGetStaleProof(t *testing.T, protocol int) {
 			t.Prove(account, 0, proofsV2)
 			expected = proofsV2.NodeList()
 		}
-		if err := expectResponse(server.peer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil {
+		if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil {
 			t.Errorf("codes mismatch: %v", err)
 		}
 	}
@@ -407,22 +472,33 @@ func testGetStaleProof(t *testing.T, protocol int) {
 // Tests that CHT proofs can be correctly retrieved.
 func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) }
 func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) }
+func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) }
 
 func testGetCHTProofs(t *testing.T, protocol int) {
-	config := light.TestServerIndexerConfig
-
-	waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
-		for {
-			cs, _, _ := cIndexer.Sections()
-			if cs >= 1 {
-				break
+	var (
+		config       = light.TestServerIndexerConfig
+		waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
+			for {
+				cs, _, _ := cIndexer.Sections()
+				if cs >= 1 {
+					break
+				}
+				time.Sleep(10 * time.Millisecond)
 			}
-			time.Sleep(10 * time.Millisecond)
 		}
-	}
-	server, tearDown := newServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false, true, 0)
+		netconfig = testnetConfig{
+			blocks:    int(config.ChtSize + config.ChtConfirms),
+			protocol:  protocol,
+			indexFn:   waitIndexers,
+			nopruning: true,
+		}
+	)
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	// Assemble the proofs from the different protocols
@@ -443,34 +519,45 @@ func testGetCHTProofs(t *testing.T, protocol int) {
 		Type:    htCanonical,
 		TrieIdx: 0,
 		Key:     key,
-		AuxReq:  auxHeader,
+		AuxReq:  htAuxHeader,
 	}}
 	// Send the proof request and verify the response
-	sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requestsV2)
-	if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil {
+	sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requestsV2)
+	if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil {
 		t.Errorf("proofs mismatch: %v", err)
 	}
 }
 
 func TestGetBloombitsProofsLes2(t *testing.T) { testGetBloombitsProofs(t, 2) }
 func TestGetBloombitsProofsLes3(t *testing.T) { testGetBloombitsProofs(t, 3) }
+func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) }
 
 // Tests that bloombits proofs can be correctly retrieved.
 func testGetBloombitsProofs(t *testing.T, protocol int) {
-	config := light.TestServerIndexerConfig
-
-	waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
-		for {
-			bts, _, _ := btIndexer.Sections()
-			if bts >= 1 {
-				break
+	var (
+		config       = light.TestServerIndexerConfig
+		waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
+			for {
+				bts, _, _ := btIndexer.Sections()
+				if bts >= 1 {
+					break
+				}
+				time.Sleep(10 * time.Millisecond)
 			}
-			time.Sleep(10 * time.Millisecond)
 		}
-	}
-	server, tearDown := newServerEnv(t, int(config.BloomTrieSize+config.BloomTrieConfirms), protocol, waitIndexers, false, true, 0)
+		netconfig = testnetConfig{
+			blocks:    int(config.BloomTrieSize + config.BloomTrieConfirms),
+			protocol:  protocol,
+			indexFn:   waitIndexers,
+			nopruning: true,
+		}
+	)
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	bc := server.handler.blockchain
 
 	// Request and verify each bit of the bloom bits proofs
@@ -494,19 +581,28 @@ func testGetBloombitsProofs(t *testing.T, protocol int) {
 		trie.Prove(key, 0, &proofs.Proofs)
 
 		// Send the proof request and verify the response
-		sendRequest(server.peer.app, GetHelperTrieProofsMsg, 42, requests)
-		if err := expectResponse(server.peer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil {
+		sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requests)
+		if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil {
 			t.Errorf("bit %d: proofs mismatch: %v", bit, err)
 		}
 	}
 }
 
-func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, 2) }
-func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, 3) }
+func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, lpv2) }
+func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, lpv3) }
+func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, lpv4) }
 
 func testTransactionStatus(t *testing.T, protocol int) {
-	server, tearDown := newServerEnv(t, 0, protocol, nil, false, true, 0)
+	netconfig := testnetConfig{
+		protocol:  protocol,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
+
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
+
 	server.handler.addTxsSync = true
 
 	chain := server.handler.blockchain
@@ -516,11 +612,11 @@ func testTransactionStatus(t *testing.T, protocol int) {
 	test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) {
 		reqID++
 		if send {
-			sendRequest(server.peer.app, SendTxV2Msg, reqID, types.Transactions{tx})
+			sendRequest(rawPeer.app, SendTxV2Msg, reqID, types.Transactions{tx})
 		} else {
-			sendRequest(server.peer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()})
+			sendRequest(rawPeer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()})
 		}
-		if err := expectResponse(server.peer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
+		if err := expectResponse(rawPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
 			t.Errorf("transaction status mismatch")
 		}
 	}
@@ -562,7 +658,7 @@ func testTransactionStatus(t *testing.T, protocol int) {
 		t.Fatalf("pending count mismatch: have %d, want 1", pending)
 	}
 	// Discard new block announcement
-	msg, _ := server.peer.app.ReadMsg()
+	msg, _ := rawPeer.app.ReadMsg()
 	msg.Discard()
 
 	// check if their status is included now
@@ -587,7 +683,7 @@ func testTransactionStatus(t *testing.T, protocol int) {
 		t.Fatalf("pending count mismatch: have %d, want 3", pending)
 	}
 	// Discard new block announcement
-	msg, _ = server.peer.app.ReadMsg()
+	msg, _ = rawPeer.app.ReadMsg()
 	msg.Discard()
 
 	// check if their status is pending again
@@ -595,11 +691,23 @@ func testTransactionStatus(t *testing.T, protocol int) {
 	test(tx2, false, light.TxStatus{Status: core.TxStatusPending})
 }
 
-func TestStopResumeLes3(t *testing.T) {
-	server, tearDown := newServerEnv(t, 0, 3, nil, true, true, testBufLimit/10)
+func TestStopResumeLES3(t *testing.T) { testStopResume(t, lpv3) }
+func TestStopResumeLES4(t *testing.T) { testStopResume(t, lpv4) }
+
+func testStopResume(t *testing.T, protocol int) {
+	netconfig := testnetConfig{
+		protocol:  protocol,
+		simClock:  true,
+		nopruning: true,
+	}
+	server, _, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
 	server.handler.server.costTracker.testing = true
+	server.handler.server.costTracker.testCostList = testCostList(testBufLimit / 10)
+
+	rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol)
+	defer closePeer()
 
 	var (
 		reqID    uint64
@@ -609,14 +717,14 @@ func TestStopResumeLes3(t *testing.T) {
 	header := server.handler.blockchain.CurrentHeader()
 	req := func() {
 		reqID++
-		sendRequest(server.peer.app, GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1})
+		sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1})
 	}
 	for i := 1; i <= 5; i++ {
 		// send requests while we still have enough buffer and expect a response
 		for expBuf >= testCost {
 			req()
 			expBuf -= testCost
-			if err := expectResponse(server.peer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil {
+			if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil {
 				t.Errorf("expected response and failed: %v", err)
 			}
 		}
@@ -626,7 +734,7 @@ func TestStopResumeLes3(t *testing.T) {
 			req()
 			c--
 		}
-		if err := p2p.ExpectMsg(server.peer.app, StopMsg, nil); err != nil {
+		if err := p2p.ExpectMsg(rawPeer.app, StopMsg, nil); err != nil {
 			t.Errorf("expected StopMsg and failed: %v", err)
 		}
 		// wait until the buffer is recharged by half of the limit
@@ -635,7 +743,7 @@ func TestStopResumeLes3(t *testing.T) {
 
 		// expect a ResumeMsg with the partially recharged buffer value
 		expBuf += testBufRecharge * wait
-		if err := p2p.ExpectMsg(server.peer.app, ResumeMsg, expBuf); err != nil {
+		if err := p2p.ExpectMsg(rawPeer.app, ResumeMsg, expBuf); err != nil {
 			t.Errorf("expected ResumeMsg and failed: %v", err)
 		}
 	}
diff --git a/les/metrics.go b/les/metrics.go
index 9ef8c36518..07d3133c95 100644
--- a/les/metrics.go
+++ b/les/metrics.go
@@ -71,11 +71,9 @@ var (
 
 	connectionTimer       = metrics.NewRegisteredTimer("les/connection/duration", nil)
 	serverConnectionGauge = metrics.NewRegisteredGauge("les/connection/server", nil)
-	clientConnectionGauge = metrics.NewRegisteredGauge("les/connection/client", nil)
 
 	totalCapacityGauge   = metrics.NewRegisteredGauge("les/server/totalCapacity", nil)
 	totalRechargeGauge   = metrics.NewRegisteredGauge("les/server/totalRecharge", nil)
-	totalConnectedGauge  = metrics.NewRegisteredGauge("les/server/totalConnected", nil)
 	blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil)
 
 	requestServedMeter               = metrics.NewRegisteredMeter("les/server/req/avgServedTime", nil)
@@ -98,15 +96,18 @@ var (
 	sqServedGauge        = metrics.NewRegisteredGauge("les/server/servingQueue/served", nil)
 	sqQueuedGauge        = metrics.NewRegisteredGauge("les/server/servingQueue/queued", nil)
 
-	clientConnectedMeter    = metrics.NewRegisteredMeter("les/server/clientEvent/connected", nil)
-	clientRejectedMeter     = metrics.NewRegisteredMeter("les/server/clientEvent/rejected", nil)
-	clientKickedMeter       = metrics.NewRegisteredMeter("les/server/clientEvent/kicked", nil)
-	clientDisconnectedMeter = metrics.NewRegisteredMeter("les/server/clientEvent/disconnected", nil)
-	clientFreezeMeter       = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil)
-	clientErrorMeter        = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil)
+	clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil)
+	clientErrorMeter  = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil)
 
 	requestRTT       = metrics.NewRegisteredTimer("les/client/req/rtt", nil)
 	requestSendDelay = metrics.NewRegisteredTimer("les/client/req/sendDelay", nil)
+
+	serverSelectableGauge = metrics.NewRegisteredGauge("les/client/serverPool/selectable", nil)
+	serverDialedMeter     = metrics.NewRegisteredMeter("les/client/serverPool/dialed", nil)
+	serverConnectedGauge  = metrics.NewRegisteredGauge("les/client/serverPool/connected", nil)
+	sessionValueMeter     = metrics.NewRegisteredMeter("les/client/serverPool/sessionValue", nil)
+	totalValueGauge       = metrics.NewRegisteredGauge("les/client/serverPool/totalValue", nil)
+	suggestedTimeoutGauge = metrics.NewRegisteredGauge("les/client/serverPool/timeout", nil)
 )
 
 // meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of
diff --git a/les/odr.go b/les/odr.go
index f8469cc103..d45c6a1a5d 100644
--- a/les/odr.go
+++ b/les/odr.go
@@ -18,13 +18,13 @@ package les
 
 import (
 	"context"
+	"sort"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/light"
-	"github.com/ethereum/go-ethereum/log"
 )
 
 // LesOdr implements light.OdrBackend
@@ -32,14 +32,16 @@ type LesOdr struct {
 	db                                         ethdb.Database
 	indexerConfig                              *light.IndexerConfig
 	chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer
+	peers                                      *serverPeerSet
 	retriever                                  *retrieveManager
 	stop                                       chan struct{}
 }
 
-func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, retriever *retrieveManager) *LesOdr {
+func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr {
 	return &LesOdr{
 		db:            db,
 		indexerConfig: config,
+		peers:         peers,
 		retriever:     retriever,
 		stop:          make(chan struct{}),
 	}
@@ -83,7 +85,8 @@ func (odr *LesOdr) IndexerConfig() *light.IndexerConfig {
 }
 
 const (
-	MsgBlockBodies = iota
+	MsgBlockHeaders = iota
+	MsgBlockBodies
 	MsgCode
 	MsgReceipts
 	MsgProofsV2
@@ -98,7 +101,101 @@ type Msg struct {
 	Obj     interface{}
 }
 
-// Retrieve tries to fetch an object from the LES network.
+// peerByTxHistory is a heap.Interface implementation which can sort
+// the peerset by transaction history.
+type peerByTxHistory []*serverPeer
+
+func (h peerByTxHistory) Len() int { return len(h) }
+func (h peerByTxHistory) Less(i, j int) bool {
+	if h[i].txHistory == txIndexUnlimited {
+		return false
+	}
+	if h[j].txHistory == txIndexUnlimited {
+		return true
+	}
+	return h[i].txHistory < h[j].txHistory
+}
+func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
+
+const (
+	maxTxStatusRetry      = 3 // The maximum retrys will be made for tx status request.
+	maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to.
+)
+
+// RetrieveTxStatus retrieves the transaction status from the LES network.
+// There is no guarantee in the LES protocol that the mined transaction will
+// be retrieved back for sure because of different reasons(the transaction
+// is unindexed, the malicous server doesn't reply it deliberately, etc).
+// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number
+// of retries, thus giving a weak guarantee.
+func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error {
+	// Sort according to the transaction history supported by the peer and
+	// select the peers with longest history.
+	var (
+		retries int
+		peers   []*serverPeer
+		missing = len(req.Hashes)
+		result  = make([]light.TxStatus, len(req.Hashes))
+		canSend = make(map[string]bool)
+	)
+	for _, peer := range odr.peers.allPeers() {
+		if peer.txHistory == txIndexDisabled {
+			continue
+		}
+		peers = append(peers, peer)
+	}
+	sort.Sort(sort.Reverse(peerByTxHistory(peers)))
+	for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ {
+		canSend[peers[i].id] = true
+	}
+	// Send out the request and assemble the result.
+	for {
+		if retries >= maxTxStatusRetry || len(canSend) == 0 {
+			break
+		}
+		var (
+			// Deep copy the request, so that the partial result won't be mixed.
+			req     = &TxStatusRequest{Hashes: req.Hashes}
+			id      = genReqID()
+			distreq = &distReq{
+				getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) },
+				canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] },
+				request: func(dp distPeer) func() {
+					p := dp.(*serverPeer)
+					p.fcServer.QueuedRequest(id, req.GetCost(p))
+					delete(canSend, p.id)
+					return func() { req.Request(id, p) }
+				},
+			}
+		)
+		if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil {
+			return err
+		}
+		// Collect the response and assemble them to the final result.
+		// All the response is not verifiable, so always pick the first
+		// one we get.
+		for index, status := range req.Status {
+			if result[index].Status != core.TxStatusUnknown {
+				continue
+			}
+			if status.Status == core.TxStatusUnknown {
+				continue
+			}
+			result[index], missing = status, missing-1
+		}
+		// Abort the procedure if all the status are retrieved
+		if missing == 0 {
+			break
+		}
+		retries += 1
+	}
+	req.Status = result
+	return nil
+}
+
+// Retrieve tries to fetch an object from the LES network. It's a common API
+// for most of the LES requests except for the TxStatusRequest which needs
+// the additional retry mechanism.
 // If the network retrieval was successful, it stores the object in local db.
 func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
 	lreq := LesRequest(req)
@@ -122,13 +219,17 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro
 			return func() { lreq.Request(reqID, p) }
 		},
 	}
-	sent := mclock.Now()
-	if err = odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err == nil {
-		// retrieved from network, store in db
-		req.StoreResult(odr.db)
+
+	defer func(sent mclock.AbsTime) {
+		if err != nil {
+			return
+		}
 		requestRTT.Update(time.Duration(mclock.Now() - sent))
-	} else {
-		log.Debug("Failed to retrieve data from network", "err", err)
+	}(mclock.Now())
+
+	if err := odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err != nil {
+		return err
 	}
-	return
+	req.StoreResult(odr.db)
+	return nil
 }
diff --git a/les/odr_requests.go b/les/odr_requests.go
index 146da2213c..d548fb1ee0 100644
--- a/les/odr_requests.go
+++ b/les/odr_requests.go
@@ -110,14 +110,16 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error {
 	body := bodies[0]
 
 	// Retrieve our stored header and validate block content against it
-	header := rawdb.ReadHeader(db, r.Hash, r.Number)
-	if header == nil {
+	if r.Header == nil {
+		r.Header = rawdb.ReadHeader(db, r.Hash, r.Number)
+	}
+	if r.Header == nil {
 		return errHeaderUnavailable
 	}
-	if header.TxHash != types.DeriveSha(types.Transactions(body.Transactions)) {
+	if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)) {
 		return errTxHashMismatch
 	}
-	if header.UncleHash != types.CalcUncleHash(body.Uncles) {
+	if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) {
 		return errUncleHashMismatch
 	}
 	// Validations passed, encode and store RLP
@@ -172,7 +174,7 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error {
 	if r.Header == nil {
 		return errHeaderUnavailable
 	}
-	if r.Header.ReceiptHash != types.DeriveSha(receipt) {
+	if r.Header.ReceiptHash != types.DeriveSha(receipt, trie.NewStackTrie(nil)) {
 		return errReceiptHashMismatch
 	}
 	// Validations passed, store and return
@@ -224,7 +226,7 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
 	// Verify the proof and store if checks out
 	nodeSet := proofs.NodeSet()
 	reads := &readTraceDB{db: nodeSet}
-	if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil {
+	if _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil {
 		return fmt.Errorf("merkle proof verification failed: %v", err)
 	}
 	// check if all nodes have been read by VerifyProof
@@ -293,10 +295,9 @@ const (
 	htCanonical = iota // Canonical hash trie
 	htBloomBits        // BloomBits trie
 
-	// applicable for all helper trie requests
-	auxRoot = 1
-	// applicable for htCanonical
-	auxHeader = 2
+	// helper trie auxiliary types
+	// htAuxNone = 1 ; deprecated number, used in les2/3 previously.
+	htAuxHeader = 2 // applicable for htCanonical, requests for relevant headers
 )
 
 type HelperTrieReq struct {
@@ -325,11 +326,7 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool {
 	peer.lock.RLock()
 	defer peer.lock.RUnlock()
 
-	if r.Untrusted {
-		return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId
-	} else {
-		return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize
-	}
+	return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize
 }
 
 // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
@@ -341,7 +338,7 @@ func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error {
 		Type:    htCanonical,
 		TrieIdx: r.ChtNum,
 		Key:     encNum[:],
-		AuxReq:  auxHeader,
+		AuxReq:  htAuxHeader,
 	}
 	return peer.requestHelperTrieProofs(reqID, []HelperTrieReq{req})
 }
@@ -368,39 +365,34 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
 	if err := rlp.DecodeBytes(headerEnc, header); err != nil {
 		return errHeaderUnavailable
 	}
-
 	// Verify the CHT
-	// Note: For untrusted CHT request, there is no proof response but
-	// header data.
-	var node light.ChtNode
-	if !r.Untrusted {
-		var encNumber [8]byte
-		binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
-
-		reads := &readTraceDB{db: nodeSet}
-		value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
-		if err != nil {
-			return fmt.Errorf("merkle proof verification failed: %v", err)
-		}
-		if len(reads.reads) != nodeSet.KeyCount() {
-			return errUselessNodes
-		}
+	var (
+		node      light.ChtNode
+		encNumber [8]byte
+	)
+	binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
 
-		if err := rlp.DecodeBytes(value, &node); err != nil {
-			return err
-		}
-		if node.Hash != header.Hash() {
-			return errCHTHashMismatch
-		}
-		if r.BlockNum != header.Number.Uint64() {
-			return errCHTNumberMismatch
-		}
+	reads := &readTraceDB{db: nodeSet}
+	value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
+	if err != nil {
+		return fmt.Errorf("merkle proof verification failed: %v", err)
+	}
+	if len(reads.reads) != nodeSet.KeyCount() {
+		return errUselessNodes
+	}
+	if err := rlp.DecodeBytes(value, &node); err != nil {
+		return err
+	}
+	if node.Hash != header.Hash() {
+		return errCHTHashMismatch
+	}
+	if r.BlockNum != header.Number.Uint64() {
+		return errCHTNumberMismatch
 	}
 	// Verifications passed, store and return
 	r.Header = header
 	r.Proof = nodeSet
-	r.Td = node.Td // For untrusted request, td here is nil, todo improve the les/2 protocol
-
+	r.Td = node.Td
 	return nil
 }
 
@@ -470,7 +462,7 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error {
 
 	for i, idx := range r.SectionIndexList {
 		binary.BigEndian.PutUint64(encNumber[2:], idx)
-		value, _, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads)
+		value, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads)
 		if err != nil {
 			return err
 		}
@@ -495,7 +487,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 {
 
 // CanSend tells if a certain peer is suitable for serving the given request
 func (r *TxStatusRequest) CanSend(peer *serverPeer) bool {
-	return peer.version >= lpv2
+	return peer.txHistory != txIndexDisabled
 }
 
 // Request sends an ODR request to the LES network (implementation of LesOdrRequest)
@@ -504,13 +496,12 @@ func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error {
 	return peer.requestTxStatus(reqID, r.Hashes)
 }
 
-// Valid processes an ODR request reply message from the LES network
+// Validate processes an ODR request reply message from the LES network
 // returns true and stores results in memory if the message was a valid reply
 // to the request (implementation of LesOdrRequest)
 func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error {
 	log.Debug("Validating transaction status", "count", len(r.Hashes))
 
-	// Ensure we have a correct message with a single block body
 	if msg.MsgType != MsgTxStatus {
 		return errInvalidMessageType
 	}
diff --git a/les/odr_test.go b/les/odr_test.go
index 5c97686068..0c75014d49 100644
--- a/les/odr_test.go
+++ b/les/odr_test.go
@@ -19,7 +19,10 @@ package les
 import (
 	"bytes"
 	"context"
+	"crypto/rand"
+	"fmt"
 	"math/big"
+	"reflect"
 	"testing"
 	"time"
 
@@ -40,6 +43,7 @@ type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.Chain
 
 func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) }
 func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) }
+func TestOdrGetBlockLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetBlock) }
 
 func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
 	var block *types.Block
@@ -57,6 +61,7 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainCon
 
 func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) }
 func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) }
+func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipts) }
 
 func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
 	var receipts types.Receipts
@@ -78,6 +83,7 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.Chain
 
 func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) }
 func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) }
+func TestOdrAccountsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrAccounts) }
 
 func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
 	dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678")
@@ -107,6 +113,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon
 
 func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) }
 func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) }
+func TestOdrContractCallLes4(t *testing.T) { testOdr(t, 4, 2, true, odrContractCall) }
 
 type callmsg struct {
 	types.Message
@@ -128,27 +135,29 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
 				from := statedb.GetOrNewStateObject(bankAddr)
 				from.SetBalance(math.MaxBig256)
 
-				msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)}
+				msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)}
 
-				context := core.NewEVMContext(msg, header, bc, nil)
-				vmenv := vm.NewEVM(context, statedb, config, vm.Config{})
+				context := core.NewEVMBlockContext(header, bc, nil)
+				txContext := core.NewEVMTxContext(msg)
+				vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{})
 
 				//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
 				gp := new(core.GasPool).AddGas(math.MaxUint64)
-				ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp)
-				res = append(res, ret...)
+				result, _ := core.ApplyMessage(vmenv, msg, gp)
+				res = append(res, result.Return()...)
 			}
 		} else {
 			header := lc.GetHeaderByHash(bhash)
 			state := light.NewState(ctx, header, lc.Odr())
 			state.SetBalance(bankAddr, math.MaxBig256)
-			msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, false)}
-			context := core.NewEVMContext(msg, header, lc, nil)
-			vmenv := vm.NewEVM(context, state, config, vm.Config{})
+			msg := callmsg{types.NewMessage(bankAddr, &testContractAddr, 0, new(big.Int), 100000, new(big.Int), data, nil, false)}
+			context := core.NewEVMBlockContext(header, lc, nil)
+			txContext := core.NewEVMTxContext(msg)
+			vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{})
 			gp := new(core.GasPool).AddGas(math.MaxUint64)
-			ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp)
+			result, _ := core.ApplyMessage(vmenv, msg, gp)
 			if state.Error() == nil {
-				res = append(res, ret...)
+				res = append(res, result.Return()...)
 			}
 		}
 	}
@@ -157,6 +166,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
 
 func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) }
 func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) }
+func TestOdrTxStatusLes4(t *testing.T) { testOdr(t, 4, 1, false, odrTxStatus) }
 
 func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte {
 	var txs types.Transactions
@@ -183,7 +193,13 @@ func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainCon
 // testOdr tests odr requests whose validation guaranteed by block headers.
 func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) {
 	// Assemble the test environment
-	server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true)
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		connect:   true,
+		nopruning: true,
+	}
+	server, client, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
 	// Ensure the client has synced all necessary data.
@@ -222,13 +238,13 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od
 
 	// expect retrievals to fail (except genesis block) without a les peer
 	client.handler.backend.peers.lock.Lock()
-	client.peer.speer.hasBlock = func(common.Hash, uint64, bool) bool { return false }
+	client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return false }
 	client.handler.backend.peers.lock.Unlock()
 	test(expFail)
 
 	// expect all retrievals to pass
 	client.handler.backend.peers.lock.Lock()
-	client.peer.speer.hasBlock = func(common.Hash, uint64, bool) bool { return true }
+	client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return true }
 	client.handler.backend.peers.lock.Unlock()
 	test(5)
 
@@ -239,3 +255,184 @@ func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn od
 		test(5)
 	}
 }
+
+func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) }
+
+func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) {
+	var (
+		blocks    = 8
+		netconfig = testnetConfig{
+			blocks:    blocks,
+			protocol:  protocol,
+			nopruning: true,
+		}
+	)
+	server, client, tearDown := newClientServerEnv(t, netconfig)
+	defer tearDown()
+
+	// Iterate the chain, create the tx indexes locally
+	var (
+		testHash   common.Hash
+		testStatus light.TxStatus
+
+		txs          = make(map[common.Hash]*types.Transaction) // Transaction objects set
+		blockNumbers = make(map[common.Hash]uint64)             // Transaction hash to block number mappings
+		blockHashes  = make(map[common.Hash]common.Hash)        // Transaction hash to block hash mappings
+		intraIndex   = make(map[common.Hash]uint64)             // Transaction intra-index in block
+	)
+	for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().NumberU64(); number++ {
+		block := server.backend.Blockchain().GetBlockByNumber(number)
+		if block == nil {
+			t.Fatalf("Failed to retrieve block %d", number)
+		}
+		for index, tx := range block.Transactions() {
+			txs[tx.Hash()] = tx
+			blockNumbers[tx.Hash()] = number
+			blockHashes[tx.Hash()] = block.Hash()
+			intraIndex[tx.Hash()] = uint64(index)
+
+			if testHash == (common.Hash{}) {
+				testHash = tx.Hash()
+				testStatus = light.TxStatus{
+					Status: core.TxStatusIncluded,
+					Lookup: &rawdb.LegacyTxLookupEntry{
+						BlockHash:  block.Hash(),
+						BlockIndex: block.NumberU64(),
+						Index:      uint64(index),
+					},
+				}
+			}
+		}
+	}
+	// serveMsg processes incoming GetTxStatusMsg and sends the response back.
+	serveMsg := func(peer *testPeer, txLookup uint64) error {
+		msg, err := peer.app.ReadMsg()
+		if err != nil {
+			return err
+		}
+		if msg.Code != GetTxStatusMsg {
+			return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg)
+		}
+		var r GetTxStatusPacket
+		if err := msg.Decode(&r); err != nil {
+			return err
+		}
+		stats := make([]light.TxStatus, len(r.Hashes))
+		for i, hash := range r.Hashes {
+			number, exist := blockNumbers[hash]
+			if !exist {
+				continue // Filter out unknown transactions
+			}
+			min := uint64(blocks) - txLookup
+			if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) {
+				continue // Filter out unindexed transactions
+			}
+			stats[i].Status = core.TxStatusIncluded
+			stats[i].Lookup = &rawdb.LegacyTxLookupEntry{
+				BlockHash:  blockHashes[hash],
+				BlockIndex: number,
+				Index:      intraIndex[hash],
+			}
+		}
+		data, _ := rlp.EncodeToBytes(stats)
+		reply := &reply{peer.app, TxStatusMsg, r.ReqID, data}
+		reply.send(testBufLimit)
+		return nil
+	}
+
+	var testspecs = []struct {
+		peers     int
+		txLookups []uint64
+		txs       []common.Hash
+		results   []light.TxStatus
+	}{
+		// Retrieve mined transaction from the empty peerset
+		{
+			peers:     0,
+			txLookups: []uint64{},
+			txs:       []common.Hash{testHash},
+			results:   []light.TxStatus{{}},
+		},
+		// Retrieve unknown transaction from the full peers
+		{
+			peers:     3,
+			txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
+			txs:       []common.Hash{randomHash()},
+			results:   []light.TxStatus{{}},
+		},
+		// Retrieve mined transaction from the full peers
+		{
+			peers:     3,
+			txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
+			txs:       []common.Hash{testHash},
+			results:   []light.TxStatus{testStatus},
+		},
+		// Retrieve mixed transactions from the full peers
+		{
+			peers:     3,
+			txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited},
+			txs:       []common.Hash{randomHash(), testHash},
+			results:   []light.TxStatus{{}, testStatus},
+		},
+		// Retrieve mixed transactions from unindexed peer(but the target is still available)
+		{
+			peers:     3,
+			txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2},
+			txs:       []common.Hash{randomHash(), testHash},
+			results:   []light.TxStatus{{}, testStatus},
+		},
+		// Retrieve mixed transactions from unindexed peer(but the target is not available)
+		{
+			peers:     3,
+			txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2},
+			txs:       []common.Hash{randomHash(), testHash},
+			results:   []light.TxStatus{{}, {}},
+		},
+	}
+	for _, testspec := range testspecs {
+		// Create a bunch of server peers with different tx history
+		var (
+			serverPeers []*testPeer
+			closeFns    []func()
+		)
+		for i := 0; i < testspec.peers; i++ {
+			peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i])
+			serverPeers = append(serverPeers, peer)
+			closeFns = append(closeFns, closePeer)
+
+			// Create a one-time routine for serving message
+			go func(i int, peer *testPeer) {
+				serveMsg(peer, testspec.txLookups[i])
+			}(i, peer)
+		}
+
+		// Send out the GetTxStatus requests, compare the result with
+		// expected value.
+		r := &light.TxStatusRequest{Hashes: testspec.txs}
+		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+		defer cancel()
+
+		err := client.handler.backend.odr.RetrieveTxStatus(ctx, r)
+		if err != nil {
+			t.Errorf("Failed to retrieve tx status %v", err)
+		} else {
+			if !reflect.DeepEqual(testspec.results, r.Status) {
+				t.Errorf("Result mismatch, diff")
+			}
+		}
+
+		// Close all connected peers and start the next round
+		for _, closeFn := range closeFns {
+			closeFn()
+		}
+	}
+}
+
+// randomHash generates a random blob of data and returns it as a hash.
+func randomHash() common.Hash {
+	var hash common.Hash
+	if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
+		panic(err)
+	}
+	return hash
+}
diff --git a/les/peer.go b/les/peer.go
index d14a5e0d1e..25a3bb1556 100644
--- a/les/peer.go
+++ b/les/peer.go
@@ -17,6 +17,7 @@
 package les
 
 import (
+	"crypto/ecdsa"
 	"errors"
 	"fmt"
 	"math/big"
@@ -29,11 +30,12 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/forkid"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/les/flowcontrol"
-	lpc "github.com/ethereum/go-ethereum/les/lespay/client"
 	"github.com/ethereum/go-ethereum/les/utils"
+	vfc "github.com/ethereum/go-ethereum/les/vflux/client"
+	vfs "github.com/ethereum/go-ethereum/les/vflux/server"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/enode"
@@ -65,9 +67,6 @@ const (
 
 	// handshakeTimeout is the timeout LES handshake will be treated as failed.
 	handshakeTimeout = 5 * time.Second
-
-	// retrySendCachePeriod is the time interval a caching retry is performed.
-	retrySendCachePeriod = time.Millisecond * 100
 )
 
 const (
@@ -118,11 +117,6 @@ func (m keyValueMap) get(key string, val interface{}) error {
 	return rlp.DecodeBytes(enc, val)
 }
 
-// peerIdToString converts enode.ID to a string form
-func peerIdToString(id enode.ID) string {
-	return fmt.Sprintf("%x", id.Bytes())
-}
-
 // peerCommons contains fields needed by both server peer and client peer.
 type peerCommons struct {
 	*p2p.Peer
@@ -134,7 +128,7 @@ type peerCommons struct {
 	frozen       uint32    // Flag whether the peer is frozen.
 	announceType uint64    // New block announcement type.
 	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.
 	sendQueue *utils.ExecQueue
@@ -153,7 +147,7 @@ func (p *peerCommons) isFrozen() bool {
 	return atomic.LoadUint32(&p.frozen) != 0
 }
 
-// canQueue returns an indicator whether the peer can queue a operation.
+// canQueue returns an indicator whether the peer can queue an operation.
 func (p *peerCommons) canQueue() bool {
 	return p.sendQueue.CanQueue() && !p.isFrozen()
 }
@@ -164,32 +158,22 @@ func (p *peerCommons) queueSend(f func()) bool {
 	return p.sendQueue.Queue(f)
 }
 
-// mustQueueSend starts a for loop and retry the caching if failed.
-// If the stopCh is closed, then it returns.
-func (p *peerCommons) mustQueueSend(f func()) {
-	for {
-		// Check whether the stopCh is closed.
-		select {
-		case <-p.closeCh:
-			return
-		default:
-		}
-		// If the function is successfully cached, return.
-		if p.canQueue() && p.queueSend(f) {
-			return
-		}
-		time.Sleep(retrySendCachePeriod)
-	}
-}
-
 // String implements fmt.Stringer.
 func (p *peerCommons) String() string {
 	return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version))
 }
 
+// PeerInfo represents a short summary of the `eth` sub-protocol metadata known
+// about a connected peer.
+type PeerInfo struct {
+	Version    int      `json:"version"`    // Ethereum protocol version negotiated
+	Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain
+	Head       string   `json:"head"`       // SHA3 hash of the peer's best owned block
+}
+
 // Info gathers and returns a collection of metadata known about a peer.
-func (p *peerCommons) Info() *eth.PeerInfo {
-	return ð.PeerInfo{
+func (p *peerCommons) Info() *PeerInfo {
+	return &PeerInfo{
 		Version:    p.version,
 		Difficulty: p.Td(),
 		Head:       fmt.Sprintf("%x", p.Head()),
@@ -272,7 +256,7 @@ func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList,
 // network IDs, difficulties, head and genesis blocks. Besides the basic handshake
 // fields, server and client can exchange and resolve some specified fields through
 // two callback functions.
-func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error {
+func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error {
 	p.lock.Lock()
 	defer p.lock.Unlock()
 
@@ -281,11 +265,19 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g
 	// Add some basic handshake fields
 	send = send.add("protocolVersion", uint64(p.version))
 	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("headHash", head)
 	send = send.add("headNum", headNum)
 	send = send.add("genesisHash", genesis)
 
+	// If the protocol version is beyond les4, then pass the forkID
+	// as well. Check http://eips.ethereum.org/EIPS/eip-2124 for more
+	// spec detail.
+	if p.version >= lpv4 {
+		send = send.add("forkID", forkID)
+	}
 	// Add client-specified or server-specified fields
 	if sendCallback != nil {
 		sendCallback(&send)
@@ -299,24 +291,14 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g
 	if size > allowedUpdateBytes {
 		return errResp(ErrRequestRejected, "")
 	}
-	var rGenesis, rHash common.Hash
-	var rVersion, rNetwork, rNum uint64
-	var rTd *big.Int
+	var rGenesis common.Hash
+	var rVersion, rNetwork uint64
 	if err := recv.get("protocolVersion", &rVersion); err != nil {
 		return err
 	}
 	if err := recv.get("networkId", &rNetwork); err != nil {
 		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 {
 		return err
 	}
@@ -329,7 +311,16 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g
 	if int(rVersion) != p.version {
 		return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version)
 	}
-	p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd}
+	// Check forkID if the protocol version is beyond the les4
+	if p.version >= lpv4 {
+		var forkID forkid.ID
+		if err := recv.get("forkID", &forkID); err != nil {
+			return err
+		}
+		if err := forkFilter(forkID); err != nil {
+			return errResp(ErrForkIDRejected, "%v", err)
+		}
+	}
 	if recvCallback != nil {
 		return recvCallback(recv)
 	}
@@ -352,25 +343,24 @@ type serverPeer struct {
 	onlyAnnounce            bool   // The flag whether the server sends announcement only.
 	chainSince, chainRecent uint64 // The range of chain server peer can serve.
 	stateSince, stateRecent uint64 // The range of state server peer can serve.
+	txHistory               uint64 // The length of available tx history, 0 means all, 1 means disabled
 
 	// Advertised checkpoint fields
 	checkpointNumber uint64                   // The block height which the checkpoint is registered.
 	checkpoint       params.TrustedCheckpoint // The advertised checkpoint sent by server.
 
-	poolEntry        *poolEntry              // Statistic for server peer.
 	fcServer         *flowcontrol.ServerNode // Client side mirror token bucket.
 	vtLock           sync.Mutex
-	valueTracker     *lpc.ValueTracker
-	nodeValueTracker *lpc.NodeValueTracker
+	nodeValueTracker *vfc.NodeValueTracker
 	sentReqs         map[uint64]sentReqEntry
 
 	// Statistics
-	errCount    int // Counter the invalid responses server has replied
+	errCount    utils.LinearExpiredValue // Counter the invalid responses server has replied
 	updateCount uint64
 	updateTime  mclock.AbsTime
 
-	// Callbacks
-	hasBlock func(common.Hash, uint64, bool) bool // Used to determine whether the server has the specified block.
+	// Test callback hooks
+	hasBlockHook func(common.Hash, uint64, bool) bool // Used to determine whether the server has the specified block.
 }
 
 func newServerPeer(version int, network uint64, trusted bool, p *p2p.Peer, rw p2p.MsgReadWriter) *serverPeer {
@@ -378,13 +368,14 @@ func newServerPeer(version int, network uint64, trusted bool, p *p2p.Peer, rw p2
 		peerCommons: peerCommons{
 			Peer:      p,
 			rw:        rw,
-			id:        peerIdToString(p.ID()),
+			id:        p.ID().String(),
 			version:   version,
 			network:   network,
 			sendQueue: utils.NewExecQueue(100),
 			closeCh:   make(chan struct{}),
 		},
-		trusted: trusted,
+		trusted:  trusted,
+		errCount: utils.LinearExpiredValue{Rate: mclock.AbsTime(time.Hour)},
 	}
 }
 
@@ -442,14 +433,14 @@ func (p *serverPeer) sendRequest(msgcode, reqID uint64, data interface{}, amount
 // specified header query, based on the hash of an origin block.
 func (p *serverPeer) requestHeadersByHash(reqID uint64, origin common.Hash, amount int, skip int, reverse bool) error {
 	p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse)
-	return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount)
+	return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount)
 }
 
 // requestHeadersByNumber fetches a batch of blocks' headers corresponding to the
 // specified header query, based on the number of an origin block.
 func (p *serverPeer) requestHeadersByNumber(reqID, origin uint64, amount int, skip int, reverse bool) error {
 	p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse)
-	return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount)
+	return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount)
 }
 
 // requestBodies fetches a batch of blocks' bodies corresponding to the hashes
@@ -490,7 +481,7 @@ func (p *serverPeer) requestTxStatus(reqID uint64, txHashes []common.Hash) error
 	return p.sendRequest(GetTxStatusMsg, reqID, txHashes, len(txHashes))
 }
 
-// SendTxStatus creates a reply with a batch of transactions to be added to the remote transaction pool.
+// sendTxs creates a reply with a batch of transactions to be added to the remote transaction pool.
 func (p *serverPeer) sendTxs(reqID uint64, amount int, txs rlp.RawValue) error {
 	p.Log().Debug("Sending batch of transactions", "amount", amount, "size", len(txs))
 	sizeFactor := (len(txs) + txSizeCostLimit/2) / txSizeCostLimit
@@ -546,7 +537,11 @@ func (p *serverPeer) getTxRelayCost(amount, size int) uint64 {
 // HasBlock checks if the peer has a given block
 func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bool {
 	p.lock.RLock()
+	defer p.lock.RUnlock()
 
+	if p.hasBlockHook != nil {
+		return p.hasBlockHook(hash, number, hasState)
+	}
 	head := p.headInfo.Number
 	var since, recent uint64
 	if hasState {
@@ -556,10 +551,7 @@ func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bo
 		since = p.chainSince
 		recent = p.chainRecent
 	}
-	hasBlock := p.hasBlock
-	p.lock.RUnlock()
-
-	return head >= number && number >= since && (recent == 0 || number+recent+4 > head) && hasBlock != nil && hasBlock(hash, number, hasState)
+	return head >= number && number >= since && (recent == 0 || number+recent+4 > head)
 }
 
 // updateFlowControl updates the flow control parameters belonging to the server
@@ -584,10 +576,21 @@ func (p *serverPeer) updateFlowControl(update keyValueMap) {
 	}
 }
 
+// updateHead updates the head information based on the announcement from
+// the peer.
+func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	p.headInfo = blockInfo{Hash: hash, Number: number, Td: td}
+}
+
 // Handshake executes the les protocol handshake, negotiating version number,
-// network IDs, difficulties, head and genesis blocks.
-func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, server *LesServer) error {
-	return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) {
+// network IDs and genesis blocks.
+func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter forkid.Filter) error {
+	// 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, forkid, forkFilter, func(lists *keyValueList) {
 		// Add some client-specific handshake fields
 		//
 		// Enable signed announcement randomly even the server is not trusted.
@@ -597,6 +600,21 @@ func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
 		}
 		*lists = (*lists).add("announceType", p.announceType)
 	}, 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 {
 			p.onlyAnnounce = true
 		}
@@ -612,6 +630,18 @@ func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
 		if recv.get("txRelay", nil) != nil {
 			p.onlyAnnounce = true
 		}
+		if p.version >= lpv4 {
+			var recentTx uint
+			if err := recv.get("recentTxLookup", &recentTx); err != nil {
+				return err
+			}
+			p.txHistory = uint64(recentTx)
+		} else {
+			// The weak assumption is held here that legacy les server(les2,3)
+			// has unlimited transaction history. The les serving in these legacy
+			// versions is disabled if the transaction is unindexed.
+			p.txHistory = txIndexUnlimited
+		}
 		if p.onlyAnnounce && !p.trusted {
 			return errResp(ErrUselessPeer, "peer cannot serve requests")
 		}
@@ -647,9 +677,8 @@ func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
 
 // setValueTracker sets the value tracker references for connected servers. Note that the
 // references should be removed upon disconnection by setValueTracker(nil, nil).
-func (p *serverPeer) setValueTracker(vt *lpc.ValueTracker, nvt *lpc.NodeValueTracker) {
+func (p *serverPeer) setValueTracker(nvt *vfc.NodeValueTracker) {
 	p.vtLock.Lock()
-	p.valueTracker = vt
 	p.nodeValueTracker = nvt
 	if nvt != nil {
 		p.sentReqs = make(map[uint64]sentReqEntry)
@@ -676,7 +705,7 @@ func (p *serverPeer) updateVtParams() {
 			}
 		}
 	}
-	p.valueTracker.UpdateCosts(p.nodeValueTracker, reqCosts)
+	p.nodeValueTracker.UpdateCosts(reqCosts)
 }
 
 // sentReqEntry remembers sent requests and their sending times
@@ -703,27 +732,26 @@ func (p *serverPeer) answeredRequest(id uint64) {
 	}
 	e, ok := p.sentReqs[id]
 	delete(p.sentReqs, id)
-	vt := p.valueTracker
 	nvt := p.nodeValueTracker
 	p.vtLock.Unlock()
 	if !ok {
 		return
 	}
 	var (
-		vtReqs   [2]lpc.ServedRequest
+		vtReqs   [2]vfc.ServedRequest
 		reqCount int
 	)
 	m := requestMapping[e.reqType]
 	if m.rest == -1 || e.amount <= 1 {
 		reqCount = 1
-		vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount}
+		vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount}
 	} else {
 		reqCount = 2
-		vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: 1}
-		vtReqs[1] = lpc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1}
+		vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: 1}
+		vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1}
 	}
 	dt := time.Duration(mclock.Now() - e.at)
-	vt.Served(nvt, vtReqs[:reqCount], dt)
+	nvt.Served(vtReqs[:reqCount], dt)
 }
 
 // clientPeer represents each node to which the les server is connected.
@@ -734,11 +762,24 @@ type clientPeer struct {
 	// responseLock ensures that responses are queued in the same order as
 	// RequestProcessed is called
 	responseLock  sync.Mutex
-	server        bool
-	invalidCount  uint32 // Counter the invalid request the client peer has made.
 	responseCount uint64 // Counter to generate an unique id for request processing.
-	errCh         chan error
-	fcClient      *flowcontrol.ClientNode // Server side mirror token bucket.
+
+	balance vfs.ConnectedBalance
+
+	// invalidLock is used for protecting invalidCount.
+	invalidLock  sync.RWMutex
+	invalidCount utils.LinearExpiredValue // Counter the invalid request the client peer has made.
+
+	capacity uint64
+	// lastAnnounce is the last broadcast created by the server; may be newer than the last head
+	// sent to the specific client (stored in headInfo) if capacity is zero. In this case the
+	// latest head is sent when the client gains non-zero capacity.
+	lastAnnounce announceData
+
+	connectedAt mclock.AbsTime
+	server      bool
+	errCh       chan error
+	fcClient    *flowcontrol.ClientNode // Server side mirror token bucket.
 }
 
 func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *clientPeer {
@@ -746,19 +787,20 @@ func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWrite
 		peerCommons: peerCommons{
 			Peer:      p,
 			rw:        rw,
-			id:        peerIdToString(p.ID()),
+			id:        p.ID().String(),
 			version:   version,
 			network:   network,
 			sendQueue: utils.NewExecQueue(100),
 			closeCh:   make(chan struct{}),
 		},
-		errCh: make(chan error, 1),
+		invalidCount: utils.LinearExpiredValue{Rate: mclock.AbsTime(time.Hour)},
+		errCh:        make(chan error, 1),
 	}
 }
 
-// freeClientId returns a string identifier for the peer. Multiple peers with
+// FreeClientId returns a string identifier for the peer. Multiple peers with
 // the same identifier can not be connected in free mode simultaneously.
-func (p *clientPeer) freeClientId() string {
+func (p *clientPeer) FreeClientId() string {
 	if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok {
 		if addr.IP.IsLoopback() {
 			// using peer id instead of loopback ip address allows multiple free
@@ -888,18 +930,69 @@ func (p *clientPeer) sendAnnounce(request announceData) error {
 	return p2p.Send(p.rw, AnnounceMsg, request)
 }
 
-// updateCapacity updates the request serving capacity assigned to a given client
-// and also sends an announcement about the updated flow control parameters
-func (p *clientPeer) updateCapacity(cap uint64) {
+// InactiveAllowance implements vfs.clientPeer
+func (p *clientPeer) InactiveAllowance() time.Duration {
+	return 0 // will return more than zero for les/5 clients
+}
+
+// getCapacity returns the current capacity of the peer
+func (p *clientPeer) getCapacity() uint64 {
+	p.lock.RLock()
+	defer p.lock.RUnlock()
+
+	return p.capacity
+}
+
+// UpdateCapacity updates the request serving capacity assigned to a given client
+// and also sends an announcement about the updated flow control parameters.
+// Note: UpdateCapacity implements vfs.clientPeer and should not block. The requested
+// parameter is true if the callback was initiated by ClientPool.SetCapacity on the given peer.
+func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) {
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	if newCap != p.fcParams.MinRecharge {
+		p.fcParams = flowcontrol.ServerParams{MinRecharge: newCap, BufLimit: newCap * bufLimitRatio}
+		p.fcClient.UpdateParams(p.fcParams)
+		var kvList keyValueList
+		kvList = kvList.add("flowControl/MRR", newCap)
+		kvList = kvList.add("flowControl/BL", newCap*bufLimitRatio)
+		p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) })
+	}
+
+	if p.capacity == 0 && newCap != 0 {
+		p.sendLastAnnounce()
+	}
+	p.capacity = newCap
+}
+
+// announceOrStore sends the given head announcement to the client if the client is
+// active (capacity != 0) and the same announcement hasn't been sent before. If the
+// client is inactive the announcement is stored and sent later if the client is
+// activated again.
+func (p *clientPeer) announceOrStore(announce announceData) {
 	p.lock.Lock()
 	defer p.lock.Unlock()
 
-	p.fcParams = flowcontrol.ServerParams{MinRecharge: cap, BufLimit: cap * bufLimitRatio}
-	p.fcClient.UpdateParams(p.fcParams)
-	var kvList keyValueList
-	kvList = kvList.add("flowControl/MRR", cap)
-	kvList = kvList.add("flowControl/BL", cap*bufLimitRatio)
-	p.mustQueueSend(func() { p.sendAnnounce(announceData{Update: kvList}) })
+	p.lastAnnounce = announce
+	if p.capacity != 0 {
+		p.sendLastAnnounce()
+	}
+}
+
+// announce sends the given head announcement to the client if it hasn't been sent before
+func (p *clientPeer) sendLastAnnounce() {
+	if p.lastAnnounce.Td == nil {
+		return
+	}
+	if p.headInfo.Td == nil || p.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 {
+		if !p.queueSend(func() { p.sendAnnounce(p.lastAnnounce) }) {
+			p.Log().Debug("Dropped announcement because queue is full", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash)
+		} else {
+			p.Log().Debug("Sent announcement", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash)
+		}
+		p.headInfo = blockInfo{Hash: p.lastAnnounce.Hash, Number: p.lastAnnounce.Number, Td: p.lastAnnounce.Td}
+	}
 }
 
 // freezeClient temporarily puts the client in a frozen state which means all
@@ -938,8 +1031,22 @@ func (p *clientPeer) freezeClient() {
 
 // Handshake executes the les protocol handshake, negotiating version number,
 // 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 {
-	return p.handshake(td, head, headNum, genesis, func(lists *keyValueList) {
+func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error {
+	recentTx := server.handler.blockchain.TxLookupLimit()
+	if recentTx != txIndexUnlimited {
+		if recentTx < blockSafetyMargin {
+			recentTx = txIndexDisabled
+		} else {
+			recentTx -= blockSafetyMargin - txIndexRecentOffset
+		}
+	}
+	if server.config.UltraLightOnlyAnnounce {
+		recentTx = txIndexDisabled
+	}
+	// 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, forkID, forkFilter, func(lists *keyValueList) {
 		// Add some information which services server can offer.
 		if !server.config.UltraLightOnlyAnnounce {
 			*lists = (*lists).add("serveHeaders", nil)
@@ -948,13 +1055,16 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
 
 			// If local ethereum node is running in archive mode, advertise ourselves we have
 			// all version state data. Otherwise only recent state is available.
-			stateRecent := uint64(core.TriesInMemory - 4)
+			stateRecent := uint64(core.TriesInMemory - blockSafetyMargin)
 			if server.archiveMode {
 				stateRecent = 0
 			}
 			*lists = (*lists).add("serveRecentState", stateRecent)
 			*lists = (*lists).add("txRelay", nil)
 		}
+		if p.version >= lpv4 {
+			*lists = (*lists).add("recentTxLookup", recentTx)
+		}
 		*lists = (*lists).add("flowControl/BL", server.defParams.BufLimit)
 		*lists = (*lists).add("flowControl/MRR", server.defParams.MinRecharge)
 
@@ -986,12 +1096,28 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
 				// set default announceType on server side
 				p.announceType = announceTypeSimple
 			}
-			p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
 		}
 		return nil
 	})
 }
 
+func (p *clientPeer) bumpInvalid() {
+	p.invalidLock.Lock()
+	p.invalidCount.Add(1, mclock.Now())
+	p.invalidLock.Unlock()
+}
+
+func (p *clientPeer) getInvalid() uint64 {
+	p.invalidLock.RLock()
+	defer p.invalidLock.RUnlock()
+	return p.invalidCount.Value(mclock.Now())
+}
+
+// Disconnect implements vfs.clientPeer
+func (p *clientPeer) Disconnect() {
+	p.Peer.Disconnect(p2p.DiscRequested)
+}
+
 // serverPeerSubscriber is an interface to notify services about added or
 // removed server peers
 type serverPeerSubscriber interface {
@@ -999,33 +1125,26 @@ type serverPeerSubscriber interface {
 	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
+// serverPeerSet represents the set of active server peers currently
 // participating in the Light Ethereum sub-protocol.
-type clientPeerSet struct {
-	peers map[string]*clientPeer
+type serverPeerSet struct {
+	peers map[string]*serverPeer
 	// subscribers is a batch of subscribers and peerset will notify
-	// these subscribers when the peerset changes(new client peer is
+	// these subscribers when the peerset changes(new server peer is
 	// added or removed)
-	subscribers []clientPeerSubscriber
+	subscribers []serverPeerSubscriber
 	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)}
+// newServerPeerSet creates a new peer set to track the active server peers.
+func newServerPeerSet() *serverPeerSet {
+	return &serverPeerSet{peers: make(map[string]*serverPeer)}
 }
 
 // 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) {
+func (ps *serverPeerSet) subscribe(sub serverPeerSubscriber) {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
@@ -1036,7 +1155,7 @@ func (ps *clientPeerSet) subscribe(sub clientPeerSubscriber) {
 }
 
 // unSubscribe removes the specified service from the subscriber pool.
-func (ps *clientPeerSet) unSubscribe(sub clientPeerSubscriber) {
+func (ps *serverPeerSet) unSubscribe(sub serverPeerSubscriber) {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
@@ -1048,9 +1167,9 @@ func (ps *clientPeerSet) unSubscribe(sub clientPeerSubscriber) {
 	}
 }
 
-// register adds a new peer into the peer set, or returns an error if the
+// register adds a new server peer into the set, or returns an error if the
 // peer is already known.
-func (ps *clientPeerSet) register(peer *clientPeer) error {
+func (ps *serverPeerSet) register(peer *serverPeer) error {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
@@ -1067,10 +1186,10 @@ func (ps *clientPeerSet) register(peer *clientPeer) error {
 	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 {
+// unregister removes a remote peer from the active set, disabling any further
+// actions to/from that particular entity. It also initiates disconnection at
+// the networking layer.
+func (ps *serverPeerSet) unregister(id string) error {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
@@ -1087,7 +1206,7 @@ func (ps *clientPeerSet) unregister(id string) error {
 }
 
 // ids returns a list of all registered peer IDs
-func (ps *clientPeerSet) ids() []string {
+func (ps *serverPeerSet) ids() []string {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
@@ -1099,7 +1218,7 @@ func (ps *clientPeerSet) ids() []string {
 }
 
 // peer retrieves the registered peer with the given id.
-func (ps *clientPeerSet) peer(id string) *clientPeer {
+func (ps *serverPeerSet) peer(id string) *serverPeer {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
@@ -1107,19 +1226,38 @@ func (ps *clientPeerSet) peer(id string) *clientPeer {
 }
 
 // len returns if the current number of peers in the set.
-func (ps *clientPeerSet) len() int {
+func (ps *serverPeerSet) 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 {
+// bestPeer retrieves the known peer with the currently highest total difficulty.
+// If the peerset is "client peer set", then nothing meaningful will return. The
+// reason is client peer never send back their latest status to server.
+func (ps *serverPeerSet) bestPeer() *serverPeer {
+	ps.lock.RLock()
+	defer ps.lock.RUnlock()
+
+	var (
+		bestPeer *serverPeer
+		bestTd   *big.Int
+	)
+	for _, p := range ps.peers {
+		if td := p.Td(); bestTd == nil || td.Cmp(bestTd) > 0 {
+			bestPeer, bestTd = p, td
+		}
+	}
+	return bestPeer
+}
+
+// allServerPeers returns all server peers in a list.
+func (ps *serverPeerSet) allPeers() []*serverPeer {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
-	list := make([]*clientPeer, 0, len(ps.peers))
+	list := make([]*serverPeer, 0, len(ps.peers))
 	for _, p := range ps.peers {
 		list = append(list, p)
 	}
@@ -1128,7 +1266,7 @@ func (ps *clientPeerSet) allPeers() []*clientPeer {
 
 // close disconnects all peers. No new peers can be registered
 // after close has returned.
-func (ps *clientPeerSet) close() {
+func (ps *serverPeerSet) close() {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
@@ -1138,71 +1276,43 @@ func (ps *clientPeerSet) close() {
 	ps.closed = true
 }
 
-// serverPeerSet represents the set of active server peers currently
+// clientPeerSet represents the set of active client peers currently
 // participating in the Light Ethereum sub-protocol.
-type serverPeerSet struct {
-	peers map[string]*serverPeer
-	// subscribers is a batch of subscribers and peerset will notify
-	// these subscribers when the peerset changes(new server peer is
-	// added or removed)
-	subscribers []serverPeerSubscriber
-	closed      bool
-	lock        sync.RWMutex
-}
+type clientPeerSet struct {
+	peers  map[enode.ID]*clientPeer
+	lock   sync.RWMutex
+	closed bool
 
-// newServerPeerSet creates a new peer set to track the active server peers.
-func newServerPeerSet() *serverPeerSet {
-	return &serverPeerSet{peers: make(map[string]*serverPeer)}
+	privateKey                   *ecdsa.PrivateKey
+	lastAnnounce, signedAnnounce announceData
 }
 
-// subscribe adds a service to be notified about added or removed
-// peers and also register all active peers into the given service.
-func (ps *serverPeerSet) subscribe(sub serverPeerSubscriber) {
-	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 *serverPeerSet) unSubscribe(sub serverPeerSubscriber) {
-	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
-		}
-	}
+// newClientPeerSet creates a new peer set to track the client peers.
+func newClientPeerSet() *clientPeerSet {
+	return &clientPeerSet{peers: make(map[enode.ID]*clientPeer)}
 }
 
-// register adds a new server peer into the set, or returns an error if the
+// register adds a new peer into the peer set, or returns an error if the
 // peer is already known.
-func (ps *serverPeerSet) register(peer *serverPeer) error {
+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 {
+	if _, exist := ps.peers[peer.ID()]; exist {
 		return errAlreadyRegistered
 	}
-	ps.peers[peer.id] = peer
-	for _, sub := range ps.subscribers {
-		sub.registerPeer(peer)
-	}
+	ps.peers[peer.ID()] = peer
+	ps.announceOrStore(peer)
 	return nil
 }
 
-// unregister removes a remote peer from the active set, disabling any further
-// actions to/from that particular entity. It also initiates disconnection at
-// the networking layer.
-func (ps *serverPeerSet) unregister(id string) error {
+// 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 enode.ID) error {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
@@ -1211,19 +1321,16 @@ func (ps *serverPeerSet) unregister(id string) error {
 		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 *serverPeerSet) ids() []string {
+func (ps *clientPeerSet) ids() []enode.ID {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
-	var ids []string
+	var ids []enode.ID
 	for id := range ps.peers {
 		ids = append(ids, id)
 	}
@@ -1231,7 +1338,7 @@ func (ps *serverPeerSet) ids() []string {
 }
 
 // peer retrieves the registered peer with the given id.
-func (ps *serverPeerSet) peer(id string) *serverPeer {
+func (ps *clientPeerSet) peer(id enode.ID) *clientPeer {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
@@ -1239,52 +1346,110 @@ func (ps *serverPeerSet) peer(id string) *serverPeer {
 }
 
 // len returns if the current number of peers in the set.
-func (ps *serverPeerSet) len() int {
+func (ps *clientPeerSet) len() int {
 	ps.lock.RLock()
 	defer ps.lock.RUnlock()
 
 	return len(ps.peers)
 }
 
-// bestPeer retrieves the known peer with the currently highest total difficulty.
-// If the peerset is "client peer set", then nothing meaningful will return. The
-// reason is client peer never send back their latest status to server.
-func (ps *serverPeerSet) bestPeer() *serverPeer {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
+// setSignerKey sets the signer key for signed announcements. Should be called before
+// starting the protocol handler.
+func (ps *clientPeerSet) setSignerKey(privateKey *ecdsa.PrivateKey) {
+	ps.privateKey = privateKey
+}
 
-	var (
-		bestPeer *serverPeer
-		bestTd   *big.Int
-	)
-	for _, p := range ps.peers {
-		if td := p.Td(); bestTd == nil || td.Cmp(bestTd) > 0 {
-			bestPeer, bestTd = p, td
-		}
+// broadcast sends the given announcements to all active peers
+func (ps *clientPeerSet) broadcast(announce announceData) {
+	ps.lock.Lock()
+	defer ps.lock.Unlock()
+
+	ps.lastAnnounce = announce
+	for _, peer := range ps.peers {
+		ps.announceOrStore(peer)
 	}
-	return bestPeer
 }
 
-// allServerPeers returns all server peers in a list.
-func (ps *serverPeerSet) allPeers() []*serverPeer {
-	ps.lock.RLock()
-	defer ps.lock.RUnlock()
-
-	list := make([]*serverPeer, 0, len(ps.peers))
-	for _, p := range ps.peers {
-		list = append(list, p)
+// announceOrStore sends the requested type of announcement to the given peer or stores
+// it for later if the peer is inactive (capacity == 0).
+func (ps *clientPeerSet) announceOrStore(p *clientPeer) {
+	if ps.lastAnnounce.Td == nil {
+		return
+	}
+	switch p.announceType {
+	case announceTypeSimple:
+		p.announceOrStore(ps.lastAnnounce)
+	case announceTypeSigned:
+		if ps.signedAnnounce.Hash != ps.lastAnnounce.Hash {
+			ps.signedAnnounce = ps.lastAnnounce
+			ps.signedAnnounce.sign(ps.privateKey)
+		}
+		p.announceOrStore(ps.signedAnnounce)
 	}
-	return list
 }
 
 // close disconnects all peers. No new peers can be registered
 // after close has returned.
-func (ps *serverPeerSet) close() {
+func (ps *clientPeerSet) close() {
 	ps.lock.Lock()
 	defer ps.lock.Unlock()
 
 	for _, p := range ps.peers {
-		p.Disconnect(p2p.DiscQuitting)
+		p.Peer.Disconnect(p2p.DiscQuitting)
 	}
 	ps.closed = true
 }
+
+// serverSet is a special set which contains all connected les servers.
+// Les servers will also be discovered by discovery protocol because they
+// also run the LES protocol. We can't drop them although they are useless
+// for us(server) but for other protocols(e.g. ETH) upon the devp2p they
+// may be useful.
+type serverSet struct {
+	lock   sync.Mutex
+	set    map[string]*clientPeer
+	closed bool
+}
+
+func newServerSet() *serverSet {
+	return &serverSet{set: make(map[string]*clientPeer)}
+}
+
+func (s *serverSet) register(peer *clientPeer) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	if s.closed {
+		return errClosed
+	}
+	if _, exist := s.set[peer.id]; exist {
+		return errAlreadyRegistered
+	}
+	s.set[peer.id] = peer
+	return nil
+}
+
+func (s *serverSet) unregister(peer *clientPeer) error {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	if s.closed {
+		return errClosed
+	}
+	if _, exist := s.set[peer.id]; !exist {
+		return errNotRegistered
+	}
+	delete(s.set, peer.id)
+	peer.Peer.Disconnect(p2p.DiscQuitting)
+	return nil
+}
+
+func (s *serverSet) close() {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	for _, p := range s.set {
+		p.Peer.Disconnect(p2p.DiscQuitting)
+	}
+	s.closed = true
+}
diff --git a/les/peer_test.go b/les/peer_test.go
index 8309e3557e..d6551ce6b6 100644
--- a/les/peer_test.go
+++ b/les/peer_test.go
@@ -18,6 +18,7 @@ package les
 
 import (
 	"crypto/rand"
+	"errors"
 	"math/big"
 	"reflect"
 	"sort"
@@ -25,8 +26,13 @@ import (
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/forkid"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/params"
 )
 
 type testServerPeerSub struct {
@@ -90,6 +96,14 @@ func TestPeerSubscription(t *testing.T) {
 	checkPeers(sub.unregCh)
 }
 
+type fakeChain struct{}
+
+func (f *fakeChain) Config() *params.ChainConfig { return params.MainnetChainConfig }
+func (f *fakeChain) Genesis() *types.Block {
+	return core.DefaultGenesisBlock().ToBlock(rawdb.NewMemoryDatabase())
+}
+func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} }
+
 func TestHandshake(t *testing.T) {
 	// Create a message pipe to communicate through
 	app, net := p2p.MsgPipe()
@@ -109,22 +123,28 @@ func TestHandshake(t *testing.T) {
 		head    = common.HexToHash("deadbeef")
 		headNum = uint64(10)
 		genesis = common.HexToHash("cafebabe")
+
+		chain1, chain2   = &fakeChain{}, &fakeChain{}
+		forkID1          = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64())
+		forkID2          = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64())
+		filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2)
 	)
+
 	go func() {
-		errCh1 <- peer1.handshake(td, head, headNum, genesis, func(list *keyValueList) {
+		errCh1 <- peer1.handshake(td, head, headNum, genesis, forkID1, filter1, func(list *keyValueList) {
 			var announceType uint64 = announceTypeSigned
 			*list = (*list).add("announceType", announceType)
 		}, nil)
 	}()
 	go func() {
-		errCh2 <- peer2.handshake(td, head, headNum, genesis, nil, func(recv keyValueMap) error {
+		errCh2 <- peer2.handshake(td, head, headNum, genesis, forkID2, filter2, nil, func(recv keyValueMap) error {
 			var reqType uint64
 			err := recv.get("announceType", &reqType)
 			if err != nil {
-				t.Fatal(err)
+				return err
 			}
 			if reqType != announceTypeSigned {
-				t.Fatal("Expected announceTypeSigned")
+				return errors.New("Expected announceTypeSigned")
 			}
 			return nil
 		})
diff --git a/les/protocol.go b/les/protocol.go
index f8ad94a7b5..07a4452f40 100644
--- a/les/protocol.go
+++ b/les/protocol.go
@@ -24,8 +24,9 @@ import (
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
-	lpc "github.com/ethereum/go-ethereum/les/lespay/client"
+	vfc "github.com/ethereum/go-ethereum/les/vflux/client"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/rlp"
 )
@@ -34,21 +35,27 @@ import (
 const (
 	lpv2 = 2
 	lpv3 = 3
+	lpv4 = 4
 )
 
 // Supported versions of the les protocol (first is primary)
 var (
-	ClientProtocolVersions    = []uint{lpv2, lpv3}
-	ServerProtocolVersions    = []uint{lpv2, lpv3}
+	ClientProtocolVersions    = []uint{lpv2, lpv3, lpv4}
+	ServerProtocolVersions    = []uint{lpv2, lpv3, lpv4}
 	AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list
 )
 
 // Number of implemented message corresponding to different protocol versions.
-var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24}
+var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24}
 
 const (
 	NetworkId          = 1
 	ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
+	blockSafetyMargin  = 4                // safety margin applied to block ranges specified relative to head block
+
+	txIndexUnlimited    = 0 // this value in the "recentTxLookup" handshake field means the entire tx index history is served
+	txIndexDisabled     = 1 // this value means tx index is not served at all
+	txIndexRecentOffset = 1 // txIndexRecentOffset + N in the handshake field means then tx index of the last N blocks is supported
 )
 
 // les protocol message codes
@@ -77,13 +84,69 @@ const (
 	ResumeMsg = 0x17
 )
 
+// GetBlockHeadersData represents a block header query (the request ID is not included)
+type GetBlockHeadersData struct {
+	Origin  hashOrNumber // Block from which to retrieve headers
+	Amount  uint64       // Maximum number of headers to retrieve
+	Skip    uint64       // Blocks to skip between consecutive headers
+	Reverse bool         // Query direction (false = rising towards latest, true = falling towards genesis)
+}
+
+// GetBlockHeadersPacket represents a block header request
+type GetBlockHeadersPacket struct {
+	ReqID uint64
+	Query GetBlockHeadersData
+}
+
+// GetBlockBodiesPacket represents a block body request
+type GetBlockBodiesPacket struct {
+	ReqID  uint64
+	Hashes []common.Hash
+}
+
+// GetCodePacket represents a contract code request
+type GetCodePacket struct {
+	ReqID uint64
+	Reqs  []CodeReq
+}
+
+// GetReceiptsPacket represents a block receipts request
+type GetReceiptsPacket struct {
+	ReqID  uint64
+	Hashes []common.Hash
+}
+
+// GetProofsPacket represents a proof request
+type GetProofsPacket struct {
+	ReqID uint64
+	Reqs  []ProofReq
+}
+
+// GetHelperTrieProofsPacket represents a helper trie proof request
+type GetHelperTrieProofsPacket struct {
+	ReqID uint64
+	Reqs  []HelperTrieReq
+}
+
+// SendTxPacket represents a transaction propagation request
+type SendTxPacket struct {
+	ReqID uint64
+	Txs   []*types.Transaction
+}
+
+// GetTxStatusPacket represents a transaction status query
+type GetTxStatusPacket struct {
+	ReqID  uint64
+	Hashes []common.Hash
+}
+
 type requestInfo struct {
 	name                          string
 	maxCount                      uint64
 	refBasketFirst, refBasketRest float64
 }
 
-// reqMapping maps an LES request to one or two lespay service vector entries.
+// reqMapping maps an LES request to one or two vflux service vector entries.
 // If rest != -1 and the request type is used with amounts larger than one then the
 // first one of the multi-request is mapped to first while the rest is mapped to rest.
 type reqMapping struct {
@@ -92,7 +155,7 @@ type reqMapping struct {
 
 var (
 	// requests describes the available LES request types and their initializing amounts
-	// in the lespay/client.ValueTracker reference basket. Initial values are estimates
+	// in the vfc.ValueTracker reference basket. Initial values are estimates
 	// based on the same values as the server's default cost estimates (reqAvgTimeCost).
 	requests = map[uint64]requestInfo{
 		GetBlockHeadersMsg:     {"GetBlockHeaders", MaxHeaderFetch, 10, 1000},
@@ -104,25 +167,25 @@ var (
 		SendTxV2Msg:            {"SendTxV2", MaxTxSend, 1, 0},
 		GetTxStatusMsg:         {"GetTxStatus", MaxTxStatus, 10, 0},
 	}
-	requestList    []lpc.RequestInfo
+	requestList    []vfc.RequestInfo
 	requestMapping map[uint32]reqMapping
 )
 
-// init creates a request list and mapping between protocol message codes and lespay
+// init creates a request list and mapping between protocol message codes and vflux
 // service vector indices.
 func init() {
 	requestMapping = make(map[uint32]reqMapping)
 	for code, req := range requests {
 		cost := reqAvgTimeCost[code]
 		rm := reqMapping{len(requestList), -1}
-		requestList = append(requestList, lpc.RequestInfo{
+		requestList = append(requestList, vfc.RequestInfo{
 			Name:       req.name + ".first",
 			InitAmount: req.refBasketFirst,
 			InitValue:  float64(cost.baseCost + cost.reqCost),
 		})
 		if req.refBasketRest != 0 {
 			rm.rest = len(requestList)
-			requestList = append(requestList, lpc.RequestInfo{
+			requestList = append(requestList, vfc.RequestInfo{
 				Name:       req.name + ".rest",
 				InitAmount: req.refBasketRest,
 				InitValue:  float64(cost.reqCost),
@@ -130,7 +193,6 @@ func init() {
 		}
 		requestMapping[uint32(code)] = rm
 	}
-
 }
 
 type errCode int
@@ -151,6 +213,7 @@ const (
 	ErrInvalidResponse
 	ErrTooManyTimeouts
 	ErrMissingKey
+	ErrForkIDRejected
 )
 
 func (e errCode) String() string {
@@ -173,12 +236,7 @@ var errorToString = map[int]string{
 	ErrInvalidResponse:         "Invalid response",
 	ErrTooManyTimeouts:         "Too many request timeouts",
 	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
+	ErrForkIDRejected:          "ForkID rejected",
 }
 
 // announceData is the network packet for the block announcements.
@@ -200,7 +258,7 @@ func (a *announceData) sanityCheck() error {
 
 // sign adds a signature to the block announcement by the given privKey
 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)
 	a.Update = a.Update.add("sign", sig)
 }
@@ -211,7 +269,7 @@ func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error {
 	if err := update.get("sign", &sig); err != nil {
 		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)
 	if err != nil {
 		return err
@@ -228,14 +286,6 @@ type blockInfo struct {
 	Td     *big.Int    // Total difficulty of one particular block being announced
 }
 
-// getBlockHeadersData represents a block header query.
-type getBlockHeadersData struct {
-	Origin  hashOrNumber // Block from which to retrieve headers
-	Amount  uint64       // Maximum number of headers to retrieve
-	Skip    uint64       // Blocks to skip between consecutive headers
-	Reverse bool         // Query direction (false = rising towards latest, true = falling towards genesis)
-}
-
 // hashOrNumber is a combined field for specifying an origin block.
 type hashOrNumber struct {
 	Hash   common.Hash // Block hash from which to retrieve headers (excludes Number)
diff --git a/les/pruner.go b/les/pruner.go
new file mode 100644
index 0000000000..622e648688
--- /dev/null
+++ b/les/pruner.go
@@ -0,0 +1,98 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package les
+
+import (
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+)
+
+// pruner is responsible for pruning historical light chain data.
+type pruner struct {
+	db       ethdb.Database
+	indexers []*core.ChainIndexer
+	closeCh  chan struct{}
+	wg       sync.WaitGroup
+}
+
+// newPruner returns a light chain pruner instance.
+func newPruner(db ethdb.Database, indexers ...*core.ChainIndexer) *pruner {
+	pruner := &pruner{
+		db:       db,
+		indexers: indexers,
+		closeCh:  make(chan struct{}),
+	}
+	pruner.wg.Add(1)
+	go pruner.loop()
+	return pruner
+}
+
+// close notifies all background goroutines belonging to pruner to exit.
+func (p *pruner) close() {
+	close(p.closeCh)
+	p.wg.Wait()
+}
+
+// loop periodically queries the status of chain indexers and prunes useless
+// historical chain data. Notably, whenever Geth restarts, it will iterate
+// all historical sections even they don't exist at all(below checkpoint) so
+// that light client can prune cached chain data that was ODRed after pruning
+// that section.
+func (p *pruner) loop() {
+	defer p.wg.Done()
+
+	// cleanTicker is the ticker used to trigger a history clean 2 times a day.
+	var cleanTicker = time.NewTicker(12 * time.Hour)
+
+	// pruning finds the sections that have been processed by all indexers
+	// and deletes all historical chain data.
+	// Note, if some indexers don't support pruning(e.g. eth.BloomIndexer),
+	// pruning operations can be silently ignored.
+	pruning := func() {
+		min := uint64(math.MaxUint64)
+		for _, indexer := range p.indexers {
+			sections, _, _ := indexer.Sections()
+			if sections < min {
+				min = sections
+			}
+		}
+		// Always keep the latest section data in database.
+		if min < 2 || len(p.indexers) == 0 {
+			return
+		}
+		for _, indexer := range p.indexers {
+			if err := indexer.Prune(min - 2); err != nil {
+				log.Debug("Failed to prune historical data", "err", err)
+				return
+			}
+		}
+		p.db.Compact(nil, nil) // Compact entire database, ensure all removed data are deleted.
+	}
+	for {
+		pruning()
+		select {
+		case <-cleanTicker.C:
+		case <-p.closeCh:
+			return
+		}
+	}
+}
diff --git a/les/pruner_test.go b/les/pruner_test.go
new file mode 100644
index 0000000000..c6f198c088
--- /dev/null
+++ b/les/pruner_test.go
@@ -0,0 +1,204 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package les
+
+import (
+	"bytes"
+	"context"
+	"encoding/binary"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/light"
+)
+
+func TestLightPruner(t *testing.T) {
+	var (
+		waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
+			for {
+				cs, _, _ := cIndexer.Sections()
+				bts, _, _ := btIndexer.Sections()
+				if cs >= 3 && bts >= 3 {
+					break
+				}
+				time.Sleep(10 * time.Millisecond)
+			}
+		}
+		config    = light.TestClientIndexerConfig
+		netconfig = testnetConfig{
+			blocks:   int(3*config.ChtSize + config.ChtConfirms),
+			protocol: 3,
+			indexFn:  waitIndexers,
+			connect:  true,
+		}
+	)
+	server, client, tearDown := newClientServerEnv(t, netconfig)
+	defer tearDown()
+
+	// checkDB iterates the chain with given prefix, resolves the block number
+	// with given callback and ensures this entry should exist or not.
+	checkDB := func(from, to uint64, prefix []byte, resolve func(key, value []byte) *uint64, exist bool) bool {
+		it := client.db.NewIterator(prefix, nil)
+		defer it.Release()
+
+		var next = from
+		for it.Next() {
+			number := resolve(it.Key(), it.Value())
+			if number == nil || *number < from {
+				continue
+			} else if *number > to {
+				return true
+			}
+			if exist {
+				if *number != next {
+					return false
+				}
+				next++
+			} else {
+				return false
+			}
+		}
+		return true
+	}
+	// checkPruned checks and ensures the stale chain data has been pruned.
+	checkPruned := func(from, to uint64) {
+		// Iterate canonical hash
+		if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 {
+			if len(key) == 1+8+1 && bytes.Equal(key[9:10], []byte("n")) {
+				n := binary.BigEndian.Uint64(key[1:9])
+				return &n
+			}
+			return nil
+		}, false) {
+			t.Fatalf("canonical hash mappings are not properly pruned")
+		}
+		// Iterate header
+		if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 {
+			if len(key) == 1+8+32 {
+				n := binary.BigEndian.Uint64(key[1:9])
+				return &n
+			}
+			return nil
+		}, false) {
+			t.Fatalf("headers are not properly pruned")
+		}
+		// Iterate body
+		if !checkDB(from, to, []byte("b"), func(key, value []byte) *uint64 {
+			if len(key) == 1+8+32 {
+				n := binary.BigEndian.Uint64(key[1:9])
+				return &n
+			}
+			return nil
+		}, false) {
+			t.Fatalf("block bodies are not properly pruned")
+		}
+		// Iterate receipts
+		if !checkDB(from, to, []byte("r"), func(key, value []byte) *uint64 {
+			if len(key) == 1+8+32 {
+				n := binary.BigEndian.Uint64(key[1:9])
+				return &n
+			}
+			return nil
+		}, false) {
+			t.Fatalf("receipts are not properly pruned")
+		}
+		// Iterate td
+		if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 {
+			if len(key) == 1+8+32+1 && bytes.Equal(key[41:42], []byte("t")) {
+				n := binary.BigEndian.Uint64(key[1:9])
+				return &n
+			}
+			return nil
+		}, false) {
+			t.Fatalf("tds are not properly pruned")
+		}
+	}
+	// Start light pruner.
+	time.Sleep(1500 * time.Millisecond) // Ensure light client has finished the syncing and indexing
+	newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer)
+
+	time.Sleep(1500 * time.Millisecond) // Ensure pruner have enough time to prune data.
+	checkPruned(1, config.ChtSize-1)
+
+	// Ensure all APIs still work after pruning.
+	var cases = []struct {
+		from, to   uint64
+		methodName string
+		method     func(uint64) bool
+	}{
+		{
+			1, 10, "GetHeaderByNumber",
+			func(n uint64) bool {
+				_, err := light.GetHeaderByNumber(context.Background(), client.handler.backend.odr, n)
+				return err == nil
+			},
+		},
+		{
+			11, 20, "GetCanonicalHash",
+			func(n uint64) bool {
+				_, err := light.GetCanonicalHash(context.Background(), client.handler.backend.odr, n)
+				return err == nil
+			},
+		},
+		{
+			21, 30, "GetTd",
+			func(n uint64) bool {
+				_, err := light.GetTd(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
+				return err == nil
+			},
+		},
+		{
+			31, 40, "GetBodyRLP",
+			func(n uint64) bool {
+				_, err := light.GetBodyRLP(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
+				return err == nil
+			},
+		},
+		{
+			41, 50, "GetBlock",
+			func(n uint64) bool {
+				_, err := light.GetBlock(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
+				return err == nil
+			},
+		},
+		{
+			51, 60, "GetBlockReceipts",
+			func(n uint64) bool {
+				_, err := light.GetBlockReceipts(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n)
+				return err == nil
+			},
+		},
+	}
+	for _, c := range cases {
+		for i := c.from; i <= c.to; i++ {
+			if !c.method(i) {
+				t.Fatalf("rpc method %s failed, number %d", c.methodName, i)
+			}
+		}
+	}
+	// Check GetBloombits
+	_, err := light.GetBloomBits(context.Background(), client.handler.backend.odr, 0, []uint64{0})
+	if err != nil {
+		t.Fatalf("Failed to retrieve bloombits of pruned section: %v", err)
+	}
+
+	// Ensure the ODR cached data can be cleaned by pruner.
+	newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer)
+	time.Sleep(50 * time.Millisecond) // Ensure pruner have enough time to prune data.
+	checkPruned(1, config.ChtSize-1)  // Ensure all cached data(by odr) is cleaned.
+}
diff --git a/les/request_test.go b/les/request_test.go
index e20b06fda5..c65405e375 100644
--- a/les/request_test.go
+++ b/les/request_test.go
@@ -38,6 +38,7 @@ type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) ligh
 
 func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) }
 func TestBlockAccessLes3(t *testing.T) { testAccess(t, 3, tfBlockAccess) }
+func TestBlockAccessLes4(t *testing.T) { testAccess(t, 4, tfBlockAccess) }
 
 func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
 	return &light.BlockRequest{Hash: bhash, Number: number}
@@ -45,6 +46,7 @@ func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.Od
 
 func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) }
 func TestReceiptsAccessLes3(t *testing.T) { testAccess(t, 3, tfReceiptsAccess) }
+func TestReceiptsAccessLes4(t *testing.T) { testAccess(t, 4, tfReceiptsAccess) }
 
 func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
 	return &light.ReceiptsRequest{Hash: bhash, Number: number}
@@ -52,6 +54,7 @@ func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light
 
 func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) }
 func TestTrieEntryAccessLes3(t *testing.T) { testAccess(t, 3, tfTrieEntryAccess) }
+func TestTrieEntryAccessLes4(t *testing.T) { testAccess(t, 4, tfTrieEntryAccess) }
 
 func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest {
 	if number := rawdb.ReadHeaderNumber(db, bhash); number != nil {
@@ -62,6 +65,7 @@ func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) ligh
 
 func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) }
 func TestCodeAccessLes3(t *testing.T) { testAccess(t, 3, tfCodeAccess) }
+func TestCodeAccessLes4(t *testing.T) { testAccess(t, 4, tfCodeAccess) }
 
 func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest {
 	number := rawdb.ReadHeaderNumber(db, bhash)
@@ -79,7 +83,14 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq
 
 func testAccess(t *testing.T, protocol int, fn accessTestFn) {
 	// Assemble the test environment
-	server, client, tearDown := newClientServerEnv(t, 4, protocol, nil, nil, 0, false, true)
+	netconfig := testnetConfig{
+		blocks:    4,
+		protocol:  protocol,
+		indexFn:   nil,
+		connect:   true,
+		nopruning: true,
+	}
+	server, client, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
 	// Ensure the client has synced all necessary data.
diff --git a/les/retrieve.go b/les/retrieve.go
index 5fa68b7456..3174d49878 100644
--- a/les/retrieve.go
+++ b/les/retrieve.go
@@ -24,22 +24,20 @@ import (
 	"sync"
 	"time"
 
-	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/light"
 )
 
 var (
 	retryQueue         = time.Millisecond * 100
-	softRequestTimeout = time.Millisecond * 500
 	hardRequestTimeout = time.Second * 10
 )
 
 // retrieveManager is a layer on top of requestDistributor which takes care of
 // matching replies by request ID and handles timeouts and resends if necessary.
 type retrieveManager struct {
-	dist       *requestDistributor
-	peers      *serverPeerSet
-	serverPool peerSelector
+	dist               *requestDistributor
+	peers              *serverPeerSet
+	softRequestTimeout func() time.Duration
 
 	lock     sync.RWMutex
 	sentReqs map[uint64]*sentReq
@@ -48,11 +46,6 @@ type retrieveManager struct {
 // validatorFunc is a function that processes a reply message
 type validatorFunc func(distPeer, *Msg) error
 
-// peerSelector receives feedback info about response times and timeouts
-type peerSelector interface {
-	adjustResponseTime(*poolEntry, time.Duration, bool)
-}
-
 // sentReq represents a request sent and tracked by retrieveManager
 type sentReq struct {
 	rm       *retrieveManager
@@ -99,12 +92,12 @@ const (
 )
 
 // newRetrieveManager creates the retrieve manager
-func newRetrieveManager(peers *serverPeerSet, dist *requestDistributor, serverPool peerSelector) *retrieveManager {
+func newRetrieveManager(peers *serverPeerSet, dist *requestDistributor, srto func() time.Duration) *retrieveManager {
 	return &retrieveManager{
-		peers:      peers,
-		dist:       dist,
-		serverPool: serverPool,
-		sentReqs:   make(map[uint64]*sentReq),
+		peers:              peers,
+		dist:               dist,
+		sentReqs:           make(map[uint64]*sentReq),
+		softRequestTimeout: srto,
 	}
 }
 
@@ -162,6 +155,15 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc
 	return r
 }
 
+// requested reports whether the request with given reqid is sent by the retriever.
+func (rm *retrieveManager) requested(reqId uint64) bool {
+	rm.lock.RLock()
+	defer rm.lock.RUnlock()
+
+	_, ok := rm.sentReqs[reqId]
+	return ok
+}
+
 // deliver is called by the LES protocol manager to deliver reply messages to waiting requests
 func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error {
 	rm.lock.RLock()
@@ -325,8 +327,7 @@ func (r *sentReq) tryRequest() {
 		return
 	}
 
-	reqSent := mclock.Now()
-	srto, hrto := false, false
+	hrto := false
 
 	r.lock.RLock()
 	s, ok := r.sentTo[p]
@@ -336,22 +337,13 @@ func (r *sentReq) tryRequest() {
 	}
 
 	defer func() {
-		// send feedback to server pool and remove peer if hard timeout happened
 		pp, ok := p.(*serverPeer)
-		if ok && r.rm.serverPool != nil {
-			respTime := time.Duration(mclock.Now() - reqSent)
-			r.rm.serverPool.adjustResponseTime(pp.poolEntry, respTime, srto)
-		}
-		if hrto {
+		if hrto && ok {
 			pp.Log().Debug("Request timed out hard")
 			if r.rm.peers != nil {
 				r.rm.peers.unregister(pp.id)
 			}
 		}
-
-		r.lock.Lock()
-		delete(r.sentTo, p)
-		r.lock.Unlock()
 	}()
 
 	select {
@@ -363,8 +355,7 @@ func (r *sentReq) tryRequest() {
 		}
 		r.eventsCh <- reqPeerEvent{event, p}
 		return
-	case <-time.After(softRequestTimeout):
-		srto = true
+	case <-time.After(r.rm.softRequestTimeout()):
 		r.eventsCh <- reqPeerEvent{rpSoftTimeout, p}
 	}
 
diff --git a/les/server.go b/les/server.go
index f72f31321a..c135e65f2d 100644
--- a/les/server.go
+++ b/les/server.go
@@ -20,29 +20,46 @@ import (
 	"crypto/ecdsa"
 	"time"
 
-	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/eth"
-	"github.com/ethereum/go-ethereum/les/checkpointoracle"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/les/flowcontrol"
+	vfs "github.com/ethereum/go-ethereum/les/vflux/server"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/discv5"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
+var (
+	defaultPosFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}
+	defaultNegFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1}
+)
+
+const defaultConnectedBias = time.Minute * 3
+
+type ethBackend interface {
+	ArchiveMode() bool
+	BlockChain() *core.BlockChain
+	BloomIndexer() *core.ChainIndexer
+	ChainDb() ethdb.Database
+	Synced() bool
+	TxPool() *core.TxPool
+}
+
 type LesServer struct {
 	lesCommons
 
 	archiveMode bool // Flag whether the ethereum node runs in archive mode.
-	peers       *clientPeerSet
 	handler     *serverHandler
-	lesTopics   []discv5.Topic
+	peers       *clientPeerSet
+	serverset   *serverSet
+	vfluxServer *vfs.Server
 	privateKey  *ecdsa.PrivateKey
 
 	// Flow control and capacity management
@@ -50,18 +67,19 @@ type LesServer struct {
 	costTracker  *costTracker
 	defParams    flowcontrol.ServerParams
 	servingQueue *servingQueue
-	clientPool   *clientPool
+	clientPool   *vfs.ClientPool
+
+	minCapacity, maxCapacity uint64
+	threadsIdle              int // Request serving threads count when system is idle.
+	threadsBusy              int // Request serving threads count when system is busy(block insertion).
 
-	minCapacity, maxCapacity, freeCapacity uint64
-	threadsIdle                            int // Request serving threads count when system is idle.
-	threadsBusy                            int // Request serving threads count when system is busy(block insertion).
+	p2pSrv *p2p.Server
 }
 
-func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
-	// Collect les protocol version information supported by local node.
-	lesTopics := make([]discv5.Topic, len(AdvertiseProtocolVersions))
-	for i, pv := range AdvertiseProtocolVersions {
-		lesTopics[i] = lesTopic(e.BlockChain().Genesis().Hash(), pv)
+func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) {
+	lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/lesserver/", false)
+	if err != nil {
+		return nil, err
 	}
 	// Calculate the number of threads used to service the light client
 	// requests based on the user-specified value.
@@ -76,34 +94,37 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
 			chainConfig:      e.BlockChain().Config(),
 			iConfig:          light.DefaultServerIndexerConfig,
 			chainDb:          e.ChainDb(),
+			lesDb:            lesDb,
 			chainReader:      e.BlockChain(),
-			chtIndexer:       light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations),
-			bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency),
+			chtIndexer:       light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true),
+			bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true),
 			closeCh:          make(chan struct{}),
 		},
 		archiveMode:  e.ArchiveMode(),
 		peers:        newClientPeerSet(),
-		lesTopics:    lesTopics,
+		serverset:    newServerSet(),
+		vfluxServer:  vfs.NewServer(time.Millisecond * 10),
 		fcManager:    flowcontrol.NewClientManager(nil, &mclock.System{}),
 		servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100),
 		threadsBusy:  config.LightServ/100 + 1,
 		threadsIdle:  threads,
+		p2pSrv:       node.Server(),
+	}
+	issync := e.Synced
+	if config.LightNoSyncServe {
+		issync = func() bool { return true }
 	}
-	srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), e.Synced)
+	srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync)
 	srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
-	srv.freeCapacity = srv.minCapacity
+	srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config)
 
-	// Set up checkpoint oracle.
-	oracle := config.CheckpointOracle
-	if oracle == nil {
-		oracle = params.CheckpointOracles[e.BlockChain().Genesis().Hash()]
-	}
-	srv.oracle = checkpointoracle.New(oracle, srv.localCheckpoint)
+	// Initialize the bloom trie indexer.
+	e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer)
 
 	// Initialize server capacity management fields.
 	srv.defParams = flowcontrol.ServerParams{
-		BufLimit:    srv.freeCapacity * bufLimitRatio,
-		MinRecharge: srv.freeCapacity,
+		BufLimit:    srv.minCapacity * bufLimitRatio,
+		MinRecharge: srv.minCapacity,
 	}
 	// LES flow control tries to more or less guarantee the possibility for the
 	// clients to send a certain amount of requests at any time and get a quick
@@ -111,13 +132,15 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
 	// to send requests most of the time. Our goal is to serve as many clients as
 	// possible while the actually used server capacity does not exceed the limits
 	totalRecharge := srv.costTracker.totalRecharge()
-	srv.maxCapacity = srv.freeCapacity * uint64(srv.config.LightPeers)
+	srv.maxCapacity = srv.minCapacity * uint64(srv.config.LightPeers)
 	if totalRecharge > srv.maxCapacity {
 		srv.maxCapacity = totalRecharge
 	}
-	srv.fcManager.SetCapacityLimits(srv.freeCapacity, srv.maxCapacity, srv.freeCapacity*2)
-	srv.clientPool = newClientPool(srv.chainDb, srv.freeCapacity, mclock.System{}, func(id enode.ID) { go srv.peers.unregister(peerIdToString(id)) })
-	srv.clientPool.setDefaultFactors(priceFactors{0, 1, 1}, priceFactors{0, 1, 1})
+	srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2)
+	srv.clientPool = vfs.NewClientPool(lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, issync)
+	srv.clientPool.Start()
+	srv.clientPool.SetDefaultFactors(defaultPosFactors, defaultNegFactors)
+	srv.vfluxServer.Register(srv.clientPool, "les", "Ethereum light client service")
 
 	checkpoint := srv.latestLocalCheckpoint()
 	if !checkpoint.Empty() {
@@ -125,6 +148,10 @@ func NewLesServer(e *eth.Ethereum, config *eth.Config) (*LesServer, error) {
 			"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
 	}
 	srv.chtIndexer.Start(e.BlockChain())
+
+	node.RegisterProtocols(srv.Protocols())
+	node.RegisterAPIs(srv.APIs())
+	node.RegisterLifecycle(srv)
 	return srv, nil
 }
 
@@ -153,72 +180,61 @@ func (s *LesServer) APIs() []rpc.API {
 
 func (s *LesServer) Protocols() []p2p.Protocol {
 	ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} {
-		if p := s.peers.peer(peerIdToString(id)); p != nil {
+		if p := s.peers.peer(id); p != nil {
 			return p.Info()
 		}
 		return nil
-	})
+	}, nil)
 	// Add "les" ENR entries.
 	for i := range ps {
-		ps[i].Attributes = []enr.Entry{&lesEntry{}}
+		ps[i].Attributes = []enr.Entry{&lesEntry{
+			VfxVersion: 1,
+		}}
 	}
 	return ps
 }
 
 // Start starts the LES server
-func (s *LesServer) Start(srvr *p2p.Server) {
-	s.privateKey = srvr.PrivateKey
+func (s *LesServer) Start() error {
+	s.privateKey = s.p2pSrv.PrivateKey
+	s.peers.setSignerKey(s.privateKey)
 	s.handler.start()
-
 	s.wg.Add(1)
 	go s.capacityManagement()
-
-	if srvr.DiscV5 != nil {
-		for _, topic := range s.lesTopics {
-			topic := topic
-			go func() {
-				logger := log.New("topic", topic)
-				logger.Info("Starting topic registration")
-				defer logger.Info("Terminated topic registration")
-
-				srvr.DiscV5.RegisterTopic(topic, s.closeCh)
-			}()
-		}
+	if s.p2pSrv.DiscV5 != nil {
+		s.p2pSrv.DiscV5.RegisterTalkHandler("vfx", s.vfluxServer.ServeEncoded)
 	}
+	return nil
 }
 
 // Stop stops the LES service
-func (s *LesServer) Stop() {
+func (s *LesServer) Stop() error {
 	close(s.closeCh)
 
-	// 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.clientPool.Stop()
+	if s.serverset != nil {
+		s.serverset.close()
+	}
 	s.peers.close()
-
 	s.fcManager.Stop()
 	s.costTracker.stop()
 	s.handler.stop()
-	s.clientPool.stop() // client pool should be closed after handler.
 	s.servingQueue.stop()
+	if s.vfluxServer != nil {
+		s.vfluxServer.Stop()
+	}
 
 	// Note, bloom trie indexer is closed by parent bloombits indexer.
-	s.chtIndexer.Close()
+	if s.chtIndexer != nil {
+		s.chtIndexer.Close()
+	}
+	if s.lesDb != nil {
+		s.lesDb.Close()
+	}
 	s.wg.Wait()
 	log.Info("Les server stopped")
-}
 
-func (s *LesServer) SetBloomBitsIndexer(bloomIndexer *core.ChainIndexer) {
-	bloomIndexer.AddChildIndexer(s.bloomTrieIndexer)
-}
-
-// SetClient sets the rpc client and starts running checkpoint contract if it is not yet watched.
-func (s *LesServer) SetContractBackend(backend bind.ContractBackend) {
-	if s.oracle == nil {
-		return
-	}
-	s.oracle.Start(backend)
+	return nil
 }
 
 // capacityManagement starts an event handler loop that updates the recharge curve of
@@ -236,7 +252,7 @@ func (s *LesServer) capacityManagement() {
 
 	totalCapacityCh := make(chan uint64, 100)
 	totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh)
-	s.clientPool.setLimits(s.config.LightPeers, totalCapacity)
+	s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity)
 
 	var (
 		busy         bool
@@ -268,12 +284,12 @@ func (s *LesServer) capacityManagement() {
 			updateRecharge()
 		case totalCapacity = <-totalCapacityCh:
 			totalCapacityGauge.Update(int64(totalCapacity))
-			newFreePeers := totalCapacity / s.freeCapacity
+			newFreePeers := totalCapacity / s.minCapacity
 			if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) {
 				log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers)
 			}
 			freePeers = newFreePeers
-			s.clientPool.setLimits(s.config.LightPeers, totalCapacity)
+			s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity)
 		case <-s.closeCh:
 			return
 		}
diff --git a/les/server_handler.go b/les/server_handler.go
index f27bb5ea62..80fcf1c44e 100644
--- a/les/server_handler.go
+++ b/les/server_handler.go
@@ -17,8 +17,6 @@
 package les
 
 import (
-	"encoding/binary"
-	"encoding/json"
 	"errors"
 	"sync"
 	"sync/atomic"
@@ -27,10 +25,12 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/forkid"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/les/flowcontrol"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
@@ -42,7 +42,6 @@ import (
 const (
 	softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data.
 	estHeaderRlpSize  = 500             // Approximate size of an RLP encoded block header
-	ethVersion        = 63              // equivalent eth version for the downloader
 
 	MaxHeaderFetch           = 192 // Amount of block headers to be fetched per retrieval request
 	MaxBodyFetch             = 32  // Amount of block bodies to be fetched per retrieval request
@@ -56,12 +55,12 @@ const (
 
 var (
 	errTooManyInvalidRequest = errors.New("too many invalid requests made")
-	errFullClientPool        = errors.New("client pool is full")
 )
 
 // serverHandler is responsible for serving light client and process
 // all incoming light requests.
 type serverHandler struct {
+	forkFilter forkid.Filter
 	blockchain *core.BlockChain
 	chainDb    ethdb.Database
 	txpool     *core.TxPool
@@ -77,6 +76,7 @@ type serverHandler struct {
 
 func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *core.TxPool, synced func() bool) *serverHandler {
 	handler := &serverHandler{
+		forkFilter: forkid.NewFilter(blockchain),
 		server:     server,
 		blockchain: blockchain,
 		chainDb:    chainDb,
@@ -90,7 +90,7 @@ func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb et
 // start starts the server handler.
 func (h *serverHandler) start() {
 	h.wg.Add(1)
-	go h.broadcastHeaders()
+	go h.broadcastLoop()
 }
 
 // stop stops the server handler.
@@ -117,47 +117,53 @@ func (h *serverHandler) handle(p *clientPeer) error {
 		hash   = head.Hash()
 		number = head.Number.Uint64()
 		td     = h.blockchain.GetTd(hash, number)
+		forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), h.blockchain.CurrentBlock().NumberU64())
 	)
-	if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), h.server); err != nil {
+	if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil {
 		p.Log().Debug("Light Ethereum handshake failed", "err", err)
 		return err
 	}
+	// Connected to another server, no messages expected, just wait for disconnection
 	if p.server {
-		// connected to another server, no messages expected, just wait for disconnection
+		if err := h.server.serverset.register(p); err != nil {
+			return err
+		}
 		_, err := p.rw.ReadMsg()
+		h.server.serverset.unregister(p)
 		return err
 	}
-	// Reject light clients if server is not synced.
+	// Setup flow control mechanism for the peer
+	p.fcClient = flowcontrol.NewClientNode(h.server.fcManager, p.fcParams)
+	defer p.fcClient.Disconnect()
+
+	// 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() {
 		p.Log().Debug("Light server not synced, rejecting peer")
 		return p2p.DiscRequested
 	}
-	defer p.fcClient.Disconnect()
 
-	// Disconnect the inbound peer if it's rejected by clientPool
-	if !h.server.clientPool.connect(p, 0) {
-		p.Log().Debug("Light Ethereum peer registration failed", "err", errFullClientPool)
-		return errFullClientPool
-	}
-	// Register the peer locally
+	// Register the peer into the peerset and clientpool
 	if err := h.server.peers.register(p); err != nil {
-		h.server.clientPool.disconnect(p)
-		p.Log().Error("Light Ethereum peer registration failed", "err", err)
 		return err
 	}
-	clientConnectionGauge.Update(int64(h.server.peers.len()))
+	if p.balance = h.server.clientPool.Register(p); p.balance == nil {
+		h.server.peers.unregister(p.ID())
+		p.Log().Debug("Client pool already closed")
+		return p2p.DiscRequested
+	}
+	p.connectedAt = mclock.Now()
 
 	var wg sync.WaitGroup // Wait group used to track all in-flight task routines.
-
-	connectedAt := mclock.Now()
 	defer func() {
 		wg.Wait() // Ensure all background task routines have exited.
-		h.server.peers.unregister(p.id)
-		h.server.clientPool.disconnect(p)
-		clientConnectionGauge.Update(int64(h.server.peers.len()))
-		connectionTimer.Update(time.Duration(mclock.Now() - connectedAt))
+		h.server.clientPool.Unregister(p)
+		h.server.peers.unregister(p.ID())
+		p.balance = nil
+		connectionTimer.Update(time.Duration(mclock.Now() - p.connectedAt))
 	}()
-	// Mark the peer starts to be served.
+
+	// Mark the peer as being served.
 	atomic.StoreUint32(&p.serving, 1)
 	defer atomic.StoreUint32(&p.serving, 0)
 
@@ -176,6 +182,90 @@ func (h *serverHandler) handle(p *clientPeer) error {
 	}
 }
 
+// beforeHandle will do a series of prechecks before handling message.
+func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, reqCnt uint64, maxCount uint64) (*servingTask, uint64) {
+	// Ensure that the request sent by client peer is valid
+	inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0)
+	if reqCnt == 0 || reqCnt > maxCount {
+		p.fcClient.OneTimeCost(inSizeCost)
+		return nil, 0
+	}
+	// Ensure that the client peer complies with the flow control
+	// rules agreed by both sides.
+	if p.isFrozen() {
+		p.fcClient.OneTimeCost(inSizeCost)
+		return nil, 0
+	}
+	maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt)
+	accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost)
+	if !accepted {
+		p.freeze()
+		p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
+		p.fcClient.OneTimeCost(inSizeCost)
+		return nil, 0
+	}
+	// Create a multi-stage task, estimate the time it takes for the task to
+	// execute, and cache it in the request service queue.
+	factor := h.server.costTracker.globalFactor()
+	if factor < 0.001 {
+		factor = 1
+		p.Log().Error("Invalid global cost factor", "factor", factor)
+	}
+	maxTime := uint64(float64(maxCost) / factor)
+	task := h.server.servingQueue.newTask(p, maxTime, priority)
+	if !task.start() {
+		p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost)
+		return nil, 0
+	}
+	return task, maxCost
+}
+
+// Afterhandle will perform a series of operations after message handling,
+// such as updating flow control data, sending reply, etc.
+func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, maxCost uint64, reqCnt uint64, task *servingTask, reply *reply) {
+	if reply != nil {
+		task.done()
+	}
+	p.responseLock.Lock()
+	defer p.responseLock.Unlock()
+
+	// Short circuit if the client is already frozen.
+	if p.isFrozen() {
+		realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0)
+		p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
+		return
+	}
+	// Positive correction buffer value with real cost.
+	var replySize uint32
+	if reply != nil {
+		replySize = reply.size()
+	}
+	var realCost uint64
+	if h.server.costTracker.testing {
+		realCost = maxCost // Assign a fake cost for testing purpose
+	} else {
+		realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize)
+		if realCost > maxCost {
+			realCost = maxCost
+		}
+	}
+	bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
+	if reply != nil {
+		// Feed cost tracker request serving statistic.
+		h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost)
+		// Reduce priority "balance" for the specific peer.
+		p.balance.RequestServed(realCost)
+		p.queueSend(func() {
+			if err := reply.send(bv); err != nil {
+				select {
+				case p.errCh <- err:
+				default:
+				}
+			}
+		})
+	}
+}
+
 // handleMsg is invoked whenever an inbound message is received from a remote
 // peer. The remote connection is torn down upon returning any error.
 func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
@@ -193,655 +283,83 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
 	}
 	defer msg.Discard()
 
-	var (
-		maxCost uint64
-		task    *servingTask
-	)
-	p.responseCount++
-	responseCount := p.responseCount
-	// accept returns an indicator whether the request can be served.
-	// If so, deduct the max cost from the flow control buffer.
-	accept := func(reqID, reqCnt, maxCnt uint64) bool {
-		// Short circuit if the peer is already frozen or the request is invalid.
-		inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0)
-		if p.isFrozen() || reqCnt == 0 || reqCnt > maxCnt {
-			p.fcClient.OneTimeCost(inSizeCost)
-			return false
-		}
-		// Prepaid max cost units before request been serving.
-		maxCost = p.fcCosts.getMaxCost(msg.Code, reqCnt)
-		accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost)
-		if !accepted {
-			p.freeze()
-			p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
-			p.fcClient.OneTimeCost(inSizeCost)
-			return false
-		}
-		// Create a multi-stage task, estimate the time it takes for the task to
-		// execute, and cache it in the request service queue.
-		factor := h.server.costTracker.globalFactor()
-		if factor < 0.001 {
-			factor = 1
-			p.Log().Error("Invalid global cost factor", "factor", factor)
-		}
-		maxTime := uint64(float64(maxCost) / factor)
-		task = h.server.servingQueue.newTask(p, maxTime, priority)
-		if task.start() {
-			return true
-		}
-		p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost)
-		return false
-	}
-	// sendResponse sends back the response and updates the flow control statistic.
-	sendResponse := func(reqID, amount uint64, reply *reply, servingTime uint64) {
-		p.responseLock.Lock()
-		defer p.responseLock.Unlock()
-
-		// Short circuit if the client is already frozen.
-		if p.isFrozen() {
-			realCost := h.server.costTracker.realCost(servingTime, msg.Size, 0)
-			p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
-			return
-		}
-		// Positive correction buffer value with real cost.
-		var replySize uint32
-		if reply != nil {
-			replySize = reply.size()
-		}
-		var realCost uint64
-		if h.server.costTracker.testing {
-			realCost = maxCost // Assign a fake cost for testing purpose
-		} else {
-			realCost = h.server.costTracker.realCost(servingTime, msg.Size, replySize)
-		}
-		bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
-		if amount != 0 {
-			// Feed cost tracker request serving statistic.
-			h.server.costTracker.updateStats(msg.Code, amount, servingTime, realCost)
-			// Reduce priority "balance" for the specific peer.
-			h.server.clientPool.requestCost(p, realCost)
-		}
-		if reply != nil {
-			p.mustQueueSend(func() {
-				if err := reply.send(bv); err != nil {
-					select {
-					case p.errCh <- err:
-					default:
-					}
-				}
-			})
-		}
+	// Lookup the request handler table, ensure it's supported
+	// message type by the protocol.
+	req, ok := Les3[msg.Code]
+	if !ok {
+		p.Log().Trace("Received invalid message", "code", msg.Code)
+		clientErrorMeter.Mark(1)
+		return errResp(ErrInvalidMsgCode, "%v", msg.Code)
 	}
-	switch msg.Code {
-	case GetBlockHeadersMsg:
-		p.Log().Trace("Received block header request")
-		if metrics.EnabledExpensive {
-			miscInHeaderPacketsMeter.Mark(1)
-			miscInHeaderTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID uint64
-			Query getBlockHeadersData
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "%v: %v", msg, err)
-		}
-		query := req.Query
-		if accept(req.ReqID, query.Amount, MaxHeaderFetch) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				hashMode := query.Origin.Hash != (common.Hash{})
-				first := true
-				maxNonCanonical := uint64(100)
-
-				// Gather headers until the fetch or network limits is reached
-				var (
-					bytes   common.StorageSize
-					headers []*types.Header
-					unknown bool
-				)
-				for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit {
-					if !first && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					// Retrieve the next header satisfying the query
-					var origin *types.Header
-					if hashMode {
-						if first {
-							origin = h.blockchain.GetHeaderByHash(query.Origin.Hash)
-							if origin != nil {
-								query.Origin.Number = origin.Number.Uint64()
-							}
-						} else {
-							origin = h.blockchain.GetHeader(query.Origin.Hash, query.Origin.Number)
-						}
-					} else {
-						origin = h.blockchain.GetHeaderByNumber(query.Origin.Number)
-					}
-					if origin == nil {
-						atomic.AddUint32(&p.invalidCount, 1)
-						break
-					}
-					headers = append(headers, origin)
-					bytes += estHeaderRlpSize
-
-					// Advance to the next header of the query
-					switch {
-					case hashMode && query.Reverse:
-						// Hash based traversal towards the genesis block
-						ancestor := query.Skip + 1
-						if ancestor == 0 {
-							unknown = true
-						} else {
-							query.Origin.Hash, query.Origin.Number = h.blockchain.GetAncestor(query.Origin.Hash, query.Origin.Number, ancestor, &maxNonCanonical)
-							unknown = query.Origin.Hash == common.Hash{}
-						}
-					case hashMode && !query.Reverse:
-						// Hash based traversal towards the leaf block
-						var (
-							current = origin.Number.Uint64()
-							next    = current + query.Skip + 1
-						)
-						if next <= current {
-							infos, _ := json.MarshalIndent(p.Peer.Info(), "", "  ")
-							p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", query.Skip, "next", next, "attacker", infos)
-							unknown = true
-						} else {
-							if header := h.blockchain.GetHeaderByNumber(next); header != nil {
-								nextHash := header.Hash()
-								expOldHash, _ := h.blockchain.GetAncestor(nextHash, next, query.Skip+1, &maxNonCanonical)
-								if expOldHash == query.Origin.Hash {
-									query.Origin.Hash, query.Origin.Number = nextHash, next
-								} else {
-									unknown = true
-								}
-							} else {
-								unknown = true
-							}
-						}
-					case query.Reverse:
-						// Number based traversal towards the genesis block
-						if query.Origin.Number >= query.Skip+1 {
-							query.Origin.Number -= query.Skip + 1
-						} else {
-							unknown = true
-						}
-
-					case !query.Reverse:
-						// Number based traversal towards the leaf block
-						query.Origin.Number += query.Skip + 1
-					}
-					first = false
-				}
-				reply := p.replyBlockHeaders(req.ReqID, headers)
-				sendResponse(req.ReqID, query.Amount, p.replyBlockHeaders(req.ReqID, headers), task.done())
-				if metrics.EnabledExpensive {
-					miscOutHeaderPacketsMeter.Mark(1)
-					miscOutHeaderTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeHeaderTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
-
-	case GetBlockBodiesMsg:
-		p.Log().Trace("Received block bodies request")
-		if metrics.EnabledExpensive {
-			miscInBodyPacketsMeter.Mark(1)
-			miscInBodyTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID  uint64
-			Hashes []common.Hash
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		var (
-			bytes  int
-			bodies []rlp.RawValue
-		)
-		reqCnt := len(req.Hashes)
-		if accept(req.ReqID, uint64(reqCnt), MaxBodyFetch) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				for i, hash := range req.Hashes {
-					if i != 0 && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					if bytes >= softResponseLimit {
-						break
-					}
-					body := h.blockchain.GetBodyRLP(hash)
-					if body == nil {
-						atomic.AddUint32(&p.invalidCount, 1)
-						continue
-					}
-					bodies = append(bodies, body)
-					bytes += len(body)
-				}
-				reply := p.replyBlockBodiesRLP(req.ReqID, bodies)
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutBodyPacketsMeter.Mark(1)
-					miscOutBodyTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeBodyTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
-
-	case GetCodeMsg:
-		p.Log().Trace("Received code request")
-		if metrics.EnabledExpensive {
-			miscInCodePacketsMeter.Mark(1)
-			miscInCodeTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID uint64
-			Reqs  []CodeReq
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		var (
-			bytes int
-			data  [][]byte
-		)
-		reqCnt := len(req.Reqs)
-		if accept(req.ReqID, uint64(reqCnt), MaxCodeFetch) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				for i, request := range req.Reqs {
-					if i != 0 && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					// Look up the root hash belonging to the request
-					header := h.blockchain.GetHeaderByHash(request.BHash)
-					if header == nil {
-						p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash)
-						atomic.AddUint32(&p.invalidCount, 1)
-						continue
-					}
-					// Refuse to search stale state data in the database since looking for
-					// a non-exist key is kind of expensive.
-					local := h.blockchain.CurrentHeader().Number.Uint64()
-					if !h.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local {
-						p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local)
-						atomic.AddUint32(&p.invalidCount, 1)
-						continue
-					}
-					triedb := h.blockchain.StateCache().TrieDB()
-
-					account, err := h.getAccount(triedb, header.Root, common.BytesToHash(request.AccKey))
-					if err != nil {
-						p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
-						atomic.AddUint32(&p.invalidCount, 1)
-						continue
-					}
-					code, err := triedb.Node(common.BytesToHash(account.CodeHash))
-					if err != nil {
-						p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err)
-						continue
-					}
-					// Accumulate the code and abort if enough data was retrieved
-					data = append(data, code)
-					if bytes += len(code); bytes >= softResponseLimit {
-						break
-					}
-				}
-				reply := p.replyCode(req.ReqID, data)
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutCodePacketsMeter.Mark(1)
-					miscOutCodeTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeCodeTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
+	p.Log().Trace("Received " + req.Name)
 
-	case GetReceiptsMsg:
-		p.Log().Trace("Received receipts request")
-		if metrics.EnabledExpensive {
-			miscInReceiptPacketsMeter.Mark(1)
-			miscInReceiptTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID  uint64
-			Hashes []common.Hash
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		var (
-			bytes    int
-			receipts []rlp.RawValue
-		)
-		reqCnt := len(req.Hashes)
-		if accept(req.ReqID, uint64(reqCnt), MaxReceiptFetch) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				for i, hash := range req.Hashes {
-					if i != 0 && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					if bytes >= softResponseLimit {
-						break
-					}
-					// Retrieve the requested block's receipts, skipping if unknown to us
-					results := h.blockchain.GetReceiptsByHash(hash)
-					if results == nil {
-						if header := h.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
-							atomic.AddUint32(&p.invalidCount, 1)
-							continue
-						}
-					}
-					// If known, encode and queue for response packet
-					if encoded, err := rlp.EncodeToBytes(results); err != nil {
-						log.Error("Failed to encode receipt", "err", err)
-					} else {
-						receipts = append(receipts, encoded)
-						bytes += len(encoded)
-					}
-				}
-				reply := p.replyReceiptsRLP(req.ReqID, receipts)
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutReceiptPacketsMeter.Mark(1)
-					miscOutReceiptTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeReceiptTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
-
-	case GetProofsV2Msg:
-		p.Log().Trace("Received les/2 proofs request")
-		if metrics.EnabledExpensive {
-			miscInTrieProofPacketsMeter.Mark(1)
-			miscInTrieProofTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID uint64
-			Reqs  []ProofReq
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// Gather state data until the fetch or network limits is reached
-		var (
-			lastBHash common.Hash
-			root      common.Hash
-			header    *types.Header
-		)
-		reqCnt := len(req.Reqs)
-		if accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				nodes := light.NewNodeSet()
-
-				for i, request := range req.Reqs {
-					if i != 0 && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					// Look up the root hash belonging to the request
-					if request.BHash != lastBHash {
-						root, lastBHash = common.Hash{}, request.BHash
-
-						if header = h.blockchain.GetHeaderByHash(request.BHash); header == nil {
-							p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash)
-							atomic.AddUint32(&p.invalidCount, 1)
-							continue
-						}
-						// Refuse to search stale state data in the database since looking for
-						// a non-exist key is kind of expensive.
-						local := h.blockchain.CurrentHeader().Number.Uint64()
-						if !h.server.archiveMode && header.Number.Uint64()+core.TriesInMemory <= local {
-							p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local)
-							atomic.AddUint32(&p.invalidCount, 1)
-							continue
-						}
-						root = header.Root
-					}
-					// If a header lookup failed (non existent), ignore subsequent requests for the same header
-					if root == (common.Hash{}) {
-						atomic.AddUint32(&p.invalidCount, 1)
-						continue
-					}
-					// Open the account or storage trie for the request
-					statedb := h.blockchain.StateCache()
-
-					var trie state.Trie
-					switch len(request.AccKey) {
-					case 0:
-						// No account key specified, open an account trie
-						trie, err = statedb.OpenTrie(root)
-						if trie == nil || err != nil {
-							p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
-							continue
-						}
-					default:
-						// Account key specified, open a storage trie
-						account, err := h.getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey))
-						if err != nil {
-							p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
-							atomic.AddUint32(&p.invalidCount, 1)
-							continue
-						}
-						trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root)
-						if trie == nil || err != nil {
-							p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err)
-							continue
-						}
-					}
-					// Prove the user's request from the account or stroage trie
-					if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil {
-						p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
-						continue
-					}
-					if nodes.DataSize() >= softResponseLimit {
-						break
-					}
-				}
-				reply := p.replyProofsV2(req.ReqID, nodes.NodeList())
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutTrieProofPacketsMeter.Mark(1)
-					miscOutTrieProofTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeTrieProofTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
+	// Decode the p2p message, resolve the concrete handler for it.
+	serve, reqID, reqCnt, err := req.Handle(msg)
+	if err != nil {
+		clientErrorMeter.Mark(1)
+		return errResp(ErrDecode, "%v: %v", msg, err)
+	}
+	if metrics.EnabledExpensive {
+		req.InPacketsMeter.Mark(1)
+		req.InTrafficMeter.Mark(int64(msg.Size))
+	}
+	p.responseCount++
+	responseCount := p.responseCount
 
-	case GetHelperTrieProofsMsg:
-		p.Log().Trace("Received helper trie proof request")
-		if metrics.EnabledExpensive {
-			miscInHelperTriePacketsMeter.Mark(1)
-			miscInHelperTrieTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID uint64
-			Reqs  []HelperTrieReq
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		// Gather state data until the fetch or network limits is reached
-		var (
-			auxBytes int
-			auxData  [][]byte
-		)
-		reqCnt := len(req.Reqs)
-		if accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				var (
-					lastIdx  uint64
-					lastType uint
-					root     common.Hash
-					auxTrie  *trie.Trie
-				)
-				nodes := light.NewNodeSet()
-				for i, request := range req.Reqs {
-					if i != 0 && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx {
-						auxTrie, lastType, lastIdx = nil, request.Type, request.TrieIdx
-
-						var prefix string
-						if root, prefix = h.getHelperTrie(request.Type, request.TrieIdx); root != (common.Hash{}) {
-							auxTrie, _ = trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix)))
-						}
-					}
-					if request.AuxReq == auxRoot {
-						var data []byte
-						if root != (common.Hash{}) {
-							data = root[:]
-						}
-						auxData = append(auxData, data)
-						auxBytes += len(data)
-					} else {
-						if auxTrie != nil {
-							auxTrie.Prove(request.Key, request.FromLevel, nodes)
-						}
-						if request.AuxReq != 0 {
-							data := h.getAuxiliaryHeaders(request)
-							auxData = append(auxData, data)
-							auxBytes += len(data)
-						}
-					}
-					if nodes.DataSize()+auxBytes >= softResponseLimit {
-						break
-					}
-				}
-				reply := p.replyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData})
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutHelperTriePacketsMeter.Mark(1)
-					miscOutHelperTrieTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeHelperTrieTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
+	// First check this client message complies all rules before
+	// handling it and return a processor if all checks are passed.
+	task, maxCost := h.beforeHandle(p, reqID, responseCount, msg, reqCnt, req.MaxCount)
+	if task == nil {
+		return nil
+	}
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
 
-	case SendTxV2Msg:
-		p.Log().Trace("Received new transactions")
-		if metrics.EnabledExpensive {
-			miscInTxsPacketsMeter.Mark(1)
-			miscInTxsTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID uint64
-			Txs   []*types.Transaction
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		reqCnt := len(req.Txs)
-		if accept(req.ReqID, uint64(reqCnt), MaxTxSend) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				stats := make([]light.TxStatus, len(req.Txs))
-				for i, tx := range req.Txs {
-					if i != 0 && !task.waitOrStop() {
-						return
-					}
-					hash := tx.Hash()
-					stats[i] = h.txStatus(hash)
-					if stats[i].Status == core.TxStatusUnknown {
-						addFn := h.txpool.AddRemotes
-						// Add txs synchronously for testing purpose
-						if h.addTxsSync {
-							addFn = h.txpool.AddRemotesSync
-						}
-						if errs := addFn([]*types.Transaction{tx}); errs[0] != nil {
-							stats[i].Error = errs[0].Error()
-							continue
-						}
-						stats[i] = h.txStatus(hash)
-					}
-				}
-				reply := p.replyTxStatus(req.ReqID, stats)
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutTxsPacketsMeter.Mark(1)
-					miscOutTxsTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeTxTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
-		}
+		reply := serve(h, p, task.waitOrStop)
+		h.afterHandle(p, reqID, responseCount, msg, maxCost, reqCnt, task, reply)
 
-	case GetTxStatusMsg:
-		p.Log().Trace("Received transaction status query request")
 		if metrics.EnabledExpensive {
-			miscInTxStatusPacketsMeter.Mark(1)
-			miscInTxStatusTrafficMeter.Mark(int64(msg.Size))
-		}
-		var req struct {
-			ReqID  uint64
-			Hashes []common.Hash
-		}
-		if err := msg.Decode(&req); err != nil {
-			clientErrorMeter.Mark(1)
-			return errResp(ErrDecode, "msg %v: %v", msg, err)
-		}
-		reqCnt := len(req.Hashes)
-		if accept(req.ReqID, uint64(reqCnt), MaxTxStatus) {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				stats := make([]light.TxStatus, len(req.Hashes))
-				for i, hash := range req.Hashes {
-					if i != 0 && !task.waitOrStop() {
-						sendResponse(req.ReqID, 0, nil, task.servingTime)
-						return
-					}
-					stats[i] = h.txStatus(hash)
-				}
-				reply := p.replyTxStatus(req.ReqID, stats)
-				sendResponse(req.ReqID, uint64(reqCnt), reply, task.done())
-				if metrics.EnabledExpensive {
-					miscOutTxStatusPacketsMeter.Mark(1)
-					miscOutTxStatusTrafficMeter.Mark(int64(reply.size()))
-					miscServingTimeTxStatusTimer.Update(time.Duration(task.servingTime))
-				}
-			}()
+			size := uint32(0)
+			if reply != nil {
+				size = reply.size()
+			}
+			req.OutPacketsMeter.Mark(1)
+			req.OutTrafficMeter.Mark(int64(size))
+			req.ServingTimeMeter.Update(time.Duration(task.servingTime))
 		}
-
-	default:
-		p.Log().Trace("Received invalid message", "code", msg.Code)
-		clientErrorMeter.Mark(1)
-		return errResp(ErrInvalidMsgCode, "%v", msg.Code)
-	}
-	// If the client has made too much invalid request(e.g. request a non-exist data),
+	}()
+	// If the client has made too much invalid request(e.g. request a non-existent data),
 	// reject them to prevent SPAM attack.
-	if atomic.LoadUint32(&p.invalidCount) > maxRequestErrors {
+	if p.getInvalid() > maxRequestErrors {
 		clientErrorMeter.Mark(1)
 		return errTooManyInvalidRequest
 	}
 	return nil
 }
 
+// BlockChain implements serverBackend
+func (h *serverHandler) BlockChain() *core.BlockChain {
+	return h.blockchain
+}
+
+// TxPool implements serverBackend
+func (h *serverHandler) TxPool() *core.TxPool {
+	return h.txpool
+}
+
+// ArchiveMode implements serverBackend
+func (h *serverHandler) ArchiveMode() bool {
+	return h.server.archiveMode
+}
+
+// AddTxsSync implements serverBackend
+func (h *serverHandler) AddTxsSync() bool {
+	return h.addTxsSync
+}
+
 // getAccount retrieves an account from the state based on root.
-func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) {
+func getAccount(triedb *trie.Database, root, hash common.Hash) (state.Account, error) {
 	trie, err := trie.New(root, triedb)
 	if err != nil {
 		return state.Account{}, err
@@ -858,50 +376,31 @@ func (h *serverHandler) getAccount(triedb *trie.Database, root, hash common.Hash
 }
 
 // getHelperTrie returns the post-processed trie root for the given trie ID and section index
-func (h *serverHandler) getHelperTrie(typ uint, index uint64) (common.Hash, string) {
+func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie {
+	var (
+		root   common.Hash
+		prefix string
+	)
 	switch typ {
 	case htCanonical:
 		sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1)
-		return light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix
+		root, prefix = light.GetChtRoot(h.chainDb, index, sectionHead), light.ChtTablePrefix
 	case htBloomBits:
 		sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1)
-		return light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix
+		root, prefix = light.GetBloomTrieRoot(h.chainDb, index, sectionHead), light.BloomTrieTablePrefix
 	}
-	return common.Hash{}, ""
-}
-
-// getAuxiliaryHeaders returns requested auxiliary headers for the CHT request.
-func (h *serverHandler) getAuxiliaryHeaders(req HelperTrieReq) []byte {
-	if req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8 {
-		blockNum := binary.BigEndian.Uint64(req.Key)
-		hash := rawdb.ReadCanonicalHash(h.chainDb, blockNum)
-		return rawdb.ReadHeaderRLP(h.chainDb, hash, blockNum)
+	if root == (common.Hash{}) {
+		return nil
 	}
-	return nil
+	trie, _ := trie.New(root, trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix)))
+	return trie
 }
 
-// txStatus returns the status of a specified transaction.
-func (h *serverHandler) txStatus(hash common.Hash) light.TxStatus {
-	var stat light.TxStatus
-	// Looking the transaction in txpool first.
-	stat.Status = h.txpool.Status([]common.Hash{hash})[0]
-
-	// If the transaction is unknown to the pool, try looking it up locally.
-	if stat.Status == core.TxStatusUnknown {
-		lookup := h.blockchain.GetTransactionLookup(hash)
-		if lookup != nil {
-			stat.Status = core.TxStatusIncluded
-			stat.Lookup = lookup
-		}
-	}
-	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
 // only broadcast new announcement if the total difficulty is higher than the
 // last one. Besides server will add the signature if client requires.
-func (h *serverHandler) broadcastHeaders() {
+func (h *serverHandler) broadcastLoop() {
 	defer h.wg.Done()
 
 	headCh := make(chan core.ChainHeadEvent, 10)
@@ -915,10 +414,6 @@ func (h *serverHandler) broadcastHeaders() {
 	for {
 		select {
 		case ev := <-headCh:
-			peers := h.server.peers.allPeers()
-			if len(peers) == 0 {
-				continue
-			}
 			header := ev.Block.Header()
 			hash, number := header.Hash(), header.Number.Uint64()
 			td := h.blockchain.GetTd(hash, number)
@@ -930,31 +425,8 @@ func (h *serverHandler) broadcastHeaders() {
 				reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(h.chainDb, header, lastHead).Number.Uint64()
 			}
 			lastHead, lastTd = header, td
-
 			log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
-			var (
-				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)
-					}
-				}
-			}
+			h.server.peers.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg})
 		case <-h.closeCh:
 			return
 		}
diff --git a/les/server_requests.go b/les/server_requests.go
new file mode 100644
index 0000000000..bab5f733d5
--- /dev/null
+++ b/les/server_requests.go
@@ -0,0 +1,569 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package les
+
+import (
+	"encoding/binary"
+	"encoding/json"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/light"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+// serverBackend defines the backend functions needed for serving LES requests
+type serverBackend interface {
+	ArchiveMode() bool
+	AddTxsSync() bool
+	BlockChain() *core.BlockChain
+	TxPool() *core.TxPool
+	GetHelperTrie(typ uint, index uint64) *trie.Trie
+}
+
+// Decoder is implemented by the messages passed to the handler functions
+type Decoder interface {
+	Decode(val interface{}) error
+}
+
+// RequestType is a static struct that describes an LES request type and references
+// its handler function.
+type RequestType struct {
+	Name                                                             string
+	MaxCount                                                         uint64
+	InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter
+	ServingTimeMeter                                                 metrics.Timer
+	Handle                                                           func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error)
+}
+
+// serveRequestFn is returned by the request handler functions after decoding the request.
+// This function does the actual request serving using the supplied backend. waitOrStop is
+// called between serving individual request items and may block if the serving process
+// needs to be throttled. If it returns false then the process is terminated.
+// The reply is not sent by this function yet. The flow control feedback value is supplied
+// by the protocol handler when calling the send function of the returned reply struct.
+type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply
+
+// Les3 contains the request types supported by les/2 and les/3
+var Les3 = map[uint64]RequestType{
+	GetBlockHeadersMsg: {
+		Name:             "block header request",
+		MaxCount:         MaxHeaderFetch,
+		InPacketsMeter:   miscInHeaderPacketsMeter,
+		InTrafficMeter:   miscInHeaderTrafficMeter,
+		OutPacketsMeter:  miscOutHeaderPacketsMeter,
+		OutTrafficMeter:  miscOutHeaderTrafficMeter,
+		ServingTimeMeter: miscServingTimeHeaderTimer,
+		Handle:           handleGetBlockHeaders,
+	},
+	GetBlockBodiesMsg: {
+		Name:             "block bodies request",
+		MaxCount:         MaxBodyFetch,
+		InPacketsMeter:   miscInBodyPacketsMeter,
+		InTrafficMeter:   miscInBodyTrafficMeter,
+		OutPacketsMeter:  miscOutBodyPacketsMeter,
+		OutTrafficMeter:  miscOutBodyTrafficMeter,
+		ServingTimeMeter: miscServingTimeBodyTimer,
+		Handle:           handleGetBlockBodies,
+	},
+	GetCodeMsg: {
+		Name:             "code request",
+		MaxCount:         MaxCodeFetch,
+		InPacketsMeter:   miscInCodePacketsMeter,
+		InTrafficMeter:   miscInCodeTrafficMeter,
+		OutPacketsMeter:  miscOutCodePacketsMeter,
+		OutTrafficMeter:  miscOutCodeTrafficMeter,
+		ServingTimeMeter: miscServingTimeCodeTimer,
+		Handle:           handleGetCode,
+	},
+	GetReceiptsMsg: {
+		Name:             "receipts request",
+		MaxCount:         MaxReceiptFetch,
+		InPacketsMeter:   miscInReceiptPacketsMeter,
+		InTrafficMeter:   miscInReceiptTrafficMeter,
+		OutPacketsMeter:  miscOutReceiptPacketsMeter,
+		OutTrafficMeter:  miscOutReceiptTrafficMeter,
+		ServingTimeMeter: miscServingTimeReceiptTimer,
+		Handle:           handleGetReceipts,
+	},
+	GetProofsV2Msg: {
+		Name:             "les/2 proofs request",
+		MaxCount:         MaxProofsFetch,
+		InPacketsMeter:   miscInTrieProofPacketsMeter,
+		InTrafficMeter:   miscInTrieProofTrafficMeter,
+		OutPacketsMeter:  miscOutTrieProofPacketsMeter,
+		OutTrafficMeter:  miscOutTrieProofTrafficMeter,
+		ServingTimeMeter: miscServingTimeTrieProofTimer,
+		Handle:           handleGetProofs,
+	},
+	GetHelperTrieProofsMsg: {
+		Name:             "helper trie proof request",
+		MaxCount:         MaxHelperTrieProofsFetch,
+		InPacketsMeter:   miscInHelperTriePacketsMeter,
+		InTrafficMeter:   miscInHelperTrieTrafficMeter,
+		OutPacketsMeter:  miscOutHelperTriePacketsMeter,
+		OutTrafficMeter:  miscOutHelperTrieTrafficMeter,
+		ServingTimeMeter: miscServingTimeHelperTrieTimer,
+		Handle:           handleGetHelperTrieProofs,
+	},
+	SendTxV2Msg: {
+		Name:             "new transactions",
+		MaxCount:         MaxTxSend,
+		InPacketsMeter:   miscInTxsPacketsMeter,
+		InTrafficMeter:   miscInTxsTrafficMeter,
+		OutPacketsMeter:  miscOutTxsPacketsMeter,
+		OutTrafficMeter:  miscOutTxsTrafficMeter,
+		ServingTimeMeter: miscServingTimeTxTimer,
+		Handle:           handleSendTx,
+	},
+	GetTxStatusMsg: {
+		Name:             "transaction status query request",
+		MaxCount:         MaxTxStatus,
+		InPacketsMeter:   miscInTxStatusPacketsMeter,
+		InTrafficMeter:   miscInTxStatusTrafficMeter,
+		OutPacketsMeter:  miscOutTxStatusPacketsMeter,
+		OutTrafficMeter:  miscOutTxStatusTrafficMeter,
+		ServingTimeMeter: miscServingTimeTxStatusTimer,
+		Handle:           handleGetTxStatus,
+	},
+}
+
+// handleGetBlockHeaders handles a block header request
+func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetBlockHeadersPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		// Gather headers until the fetch or network limits is reached
+		var (
+			bc              = backend.BlockChain()
+			hashMode        = r.Query.Origin.Hash != (common.Hash{})
+			first           = true
+			maxNonCanonical = uint64(100)
+			bytes           common.StorageSize
+			headers         []*types.Header
+			unknown         bool
+		)
+		for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit {
+			if !first && !waitOrStop() {
+				return nil
+			}
+			// Retrieve the next header satisfying the r
+			var origin *types.Header
+			if hashMode {
+				if first {
+					origin = bc.GetHeaderByHash(r.Query.Origin.Hash)
+					if origin != nil {
+						r.Query.Origin.Number = origin.Number.Uint64()
+					}
+				} else {
+					origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number)
+				}
+			} else {
+				origin = bc.GetHeaderByNumber(r.Query.Origin.Number)
+			}
+			if origin == nil {
+				break
+			}
+			headers = append(headers, origin)
+			bytes += estHeaderRlpSize
+
+			// Advance to the next header of the r
+			switch {
+			case hashMode && r.Query.Reverse:
+				// Hash based traversal towards the genesis block
+				ancestor := r.Query.Skip + 1
+				if ancestor == 0 {
+					unknown = true
+				} else {
+					r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical)
+					unknown = r.Query.Origin.Hash == common.Hash{}
+				}
+			case hashMode && !r.Query.Reverse:
+				// Hash based traversal towards the leaf block
+				var (
+					current = origin.Number.Uint64()
+					next    = current + r.Query.Skip + 1
+				)
+				if next <= current {
+					infos, _ := json.Marshal(p.Peer.Info())
+					p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(infos))
+					unknown = true
+				} else {
+					if header := bc.GetHeaderByNumber(next); header != nil {
+						nextHash := header.Hash()
+						expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical)
+						if expOldHash == r.Query.Origin.Hash {
+							r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next
+						} else {
+							unknown = true
+						}
+					} else {
+						unknown = true
+					}
+				}
+			case r.Query.Reverse:
+				// Number based traversal towards the genesis block
+				if r.Query.Origin.Number >= r.Query.Skip+1 {
+					r.Query.Origin.Number -= r.Query.Skip + 1
+				} else {
+					unknown = true
+				}
+
+			case !r.Query.Reverse:
+				// Number based traversal towards the leaf block
+				r.Query.Origin.Number += r.Query.Skip + 1
+			}
+			first = false
+		}
+		return p.replyBlockHeaders(r.ReqID, headers)
+	}, r.ReqID, r.Query.Amount, nil
+}
+
+// handleGetBlockBodies handles a block body request
+func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetBlockBodiesPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		var (
+			bytes  int
+			bodies []rlp.RawValue
+		)
+		bc := backend.BlockChain()
+		for i, hash := range r.Hashes {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			if bytes >= softResponseLimit {
+				break
+			}
+			body := bc.GetBodyRLP(hash)
+			if body == nil {
+				p.bumpInvalid()
+				continue
+			}
+			bodies = append(bodies, body)
+			bytes += len(body)
+		}
+		return p.replyBlockBodiesRLP(r.ReqID, bodies)
+	}, r.ReqID, uint64(len(r.Hashes)), nil
+}
+
+// handleGetCode handles a contract code request
+func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetCodePacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		var (
+			bytes int
+			data  [][]byte
+		)
+		bc := backend.BlockChain()
+		for i, request := range r.Reqs {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			// Look up the root hash belonging to the request
+			header := bc.GetHeaderByHash(request.BHash)
+			if header == nil {
+				p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash)
+				p.bumpInvalid()
+				continue
+			}
+			// Refuse to search stale state data in the database since looking for
+			// a non-exist key is kind of expensive.
+			local := bc.CurrentHeader().Number.Uint64()
+			if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
+				p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local)
+				p.bumpInvalid()
+				continue
+			}
+			triedb := bc.StateCache().TrieDB()
+
+			account, err := getAccount(triedb, header.Root, common.BytesToHash(request.AccKey))
+			if err != nil {
+				p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
+				p.bumpInvalid()
+				continue
+			}
+			code, err := bc.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash))
+			if err != nil {
+				p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err)
+				continue
+			}
+			// Accumulate the code and abort if enough data was retrieved
+			data = append(data, code)
+			if bytes += len(code); bytes >= softResponseLimit {
+				break
+			}
+		}
+		return p.replyCode(r.ReqID, data)
+	}, r.ReqID, uint64(len(r.Reqs)), nil
+}
+
+// handleGetReceipts handles a block receipts request
+func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetReceiptsPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		var (
+			bytes    int
+			receipts []rlp.RawValue
+		)
+		bc := backend.BlockChain()
+		for i, hash := range r.Hashes {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			if bytes >= softResponseLimit {
+				break
+			}
+			// Retrieve the requested block's receipts, skipping if unknown to us
+			results := bc.GetReceiptsByHash(hash)
+			if results == nil {
+				if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
+					p.bumpInvalid()
+					continue
+				}
+			}
+			// If known, encode and queue for response packet
+			if encoded, err := rlp.EncodeToBytes(results); err != nil {
+				log.Error("Failed to encode receipt", "err", err)
+			} else {
+				receipts = append(receipts, encoded)
+				bytes += len(encoded)
+			}
+		}
+		return p.replyReceiptsRLP(r.ReqID, receipts)
+	}, r.ReqID, uint64(len(r.Hashes)), nil
+}
+
+// handleGetProofs handles a proof request
+func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetProofsPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		var (
+			lastBHash common.Hash
+			root      common.Hash
+			header    *types.Header
+			err       error
+		)
+		bc := backend.BlockChain()
+		nodes := light.NewNodeSet()
+
+		for i, request := range r.Reqs {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			// Look up the root hash belonging to the request
+			if request.BHash != lastBHash {
+				root, lastBHash = common.Hash{}, request.BHash
+
+				if header = bc.GetHeaderByHash(request.BHash); header == nil {
+					p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash)
+					p.bumpInvalid()
+					continue
+				}
+				// Refuse to search stale state data in the database since looking for
+				// a non-exist key is kind of expensive.
+				local := bc.CurrentHeader().Number.Uint64()
+				if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local {
+					p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local)
+					p.bumpInvalid()
+					continue
+				}
+				root = header.Root
+			}
+			// If a header lookup failed (non existent), ignore subsequent requests for the same header
+			if root == (common.Hash{}) {
+				p.bumpInvalid()
+				continue
+			}
+			// Open the account or storage trie for the request
+			statedb := bc.StateCache()
+
+			var trie state.Trie
+			switch len(request.AccKey) {
+			case 0:
+				// No account key specified, open an account trie
+				trie, err = statedb.OpenTrie(root)
+				if trie == nil || err != nil {
+					p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err)
+					continue
+				}
+			default:
+				// Account key specified, open a storage trie
+				account, err := getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey))
+				if err != nil {
+					p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err)
+					p.bumpInvalid()
+					continue
+				}
+				trie, err = statedb.OpenStorageTrie(common.BytesToHash(request.AccKey), account.Root)
+				if trie == nil || err != nil {
+					p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err)
+					continue
+				}
+			}
+			// Prove the user's request from the account or stroage trie
+			if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil {
+				p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err)
+				continue
+			}
+			if nodes.DataSize() >= softResponseLimit {
+				break
+			}
+		}
+		return p.replyProofsV2(r.ReqID, nodes.NodeList())
+	}, r.ReqID, uint64(len(r.Reqs)), nil
+}
+
+// handleGetHelperTrieProofs handles a helper trie proof request
+func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetHelperTrieProofsPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		var (
+			lastIdx  uint64
+			lastType uint
+			auxTrie  *trie.Trie
+			auxBytes int
+			auxData  [][]byte
+		)
+		bc := backend.BlockChain()
+		nodes := light.NewNodeSet()
+		for i, request := range r.Reqs {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx {
+				lastType, lastIdx = request.Type, request.TrieIdx
+				auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx)
+			}
+			if auxTrie == nil {
+				return nil
+			}
+			// TODO(rjl493456442) short circuit if the proving is failed.
+			// The original client side code has a dirty hack to retrieve
+			// the headers with no valid proof. Keep the compatibility for
+			// legacy les protocol and drop this hack when the les2/3 are
+			// not supported.
+			err := auxTrie.Prove(request.Key, request.FromLevel, nodes)
+			if p.version >= lpv4 && err != nil {
+				return nil
+			}
+			if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 {
+				header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key))
+				data, err := rlp.EncodeToBytes(header)
+				if err != nil {
+					log.Error("Failed to encode header", "err", err)
+					return nil
+				}
+				auxData = append(auxData, data)
+				auxBytes += len(data)
+			}
+			if nodes.DataSize()+auxBytes >= softResponseLimit {
+				break
+			}
+		}
+		return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData})
+	}, r.ReqID, uint64(len(r.Reqs)), nil
+}
+
+// handleSendTx handles a transaction propagation request
+func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r SendTxPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	amount := uint64(len(r.Txs))
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		stats := make([]light.TxStatus, len(r.Txs))
+		for i, tx := range r.Txs {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			hash := tx.Hash()
+			stats[i] = txStatus(backend, hash)
+			if stats[i].Status == core.TxStatusUnknown {
+				addFn := backend.TxPool().AddRemotes
+				// Add txs synchronously for testing purpose
+				if backend.AddTxsSync() {
+					addFn = backend.TxPool().AddRemotesSync
+				}
+				if errs := addFn([]*types.Transaction{tx}); errs[0] != nil {
+					stats[i].Error = errs[0].Error()
+					continue
+				}
+				stats[i] = txStatus(backend, hash)
+			}
+		}
+		return p.replyTxStatus(r.ReqID, stats)
+	}, r.ReqID, amount, nil
+}
+
+// handleGetTxStatus handles a transaction status query
+func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) {
+	var r GetTxStatusPacket
+	if err := msg.Decode(&r); err != nil {
+		return nil, 0, 0, err
+	}
+	return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply {
+		stats := make([]light.TxStatus, len(r.Hashes))
+		for i, hash := range r.Hashes {
+			if i != 0 && !waitOrStop() {
+				return nil
+			}
+			stats[i] = txStatus(backend, hash)
+		}
+		return p.replyTxStatus(r.ReqID, stats)
+	}, r.ReqID, uint64(len(r.Hashes)), nil
+}
+
+// txStatus returns the status of a specified transaction.
+func txStatus(b serverBackend, hash common.Hash) light.TxStatus {
+	var stat light.TxStatus
+	// Looking the transaction in txpool first.
+	stat.Status = b.TxPool().Status([]common.Hash{hash})[0]
+
+	// If the transaction is unknown to the pool, try looking it up locally.
+	if stat.Status == core.TxStatusUnknown {
+		lookup := b.BlockChain().GetTransactionLookup(hash)
+		if lookup != nil {
+			stat.Status = core.TxStatusIncluded
+			stat.Lookup = lookup
+		}
+	}
+	return stat
+}
diff --git a/les/serverpool.go b/les/serverpool.go
deleted file mode 100644
index d1c53295a7..0000000000
--- a/les/serverpool.go
+++ /dev/null
@@ -1,920 +0,0 @@
-// Copyright 2016 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 .
-
-package les
-
-import (
-	"crypto/ecdsa"
-	"fmt"
-	"io"
-	"math"
-	"math/rand"
-	"net"
-	"strconv"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/ethdb"
-	"github.com/ethereum/go-ethereum/les/utils"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/discv5"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-const (
-	// After a connection has been ended or timed out, there is a waiting period
-	// before it can be selected for connection again.
-	// waiting period = base delay * (1 + random(1))
-	// base delay = shortRetryDelay for the first shortRetryCnt times after a
-	// successful connection, after that longRetryDelay is applied
-	shortRetryCnt   = 5
-	shortRetryDelay = time.Second * 5
-	longRetryDelay  = time.Minute * 10
-	// maxNewEntries is the maximum number of newly discovered (never connected) nodes.
-	// If the limit is reached, the least recently discovered one is thrown out.
-	maxNewEntries = 1000
-	// maxKnownEntries is the maximum number of known (already connected) nodes.
-	// If the limit is reached, the least recently connected one is thrown out.
-	// (not that unlike new entries, known entries are persistent)
-	maxKnownEntries = 1000
-	// target for simultaneously connected servers
-	targetServerCount = 5
-	// target for servers selected from the known table
-	// (we leave room for trying new ones if there is any)
-	targetKnownSelect = 3
-	// after dialTimeout, consider the server unavailable and adjust statistics
-	dialTimeout = time.Second * 30
-	// targetConnTime is the minimum expected connection duration before a server
-	// drops a client without any specific reason
-	targetConnTime = time.Minute * 10
-	// new entry selection weight calculation based on most recent discovery time:
-	// unity until discoverExpireStart, then exponential decay with discoverExpireConst
-	discoverExpireStart = time.Minute * 20
-	discoverExpireConst = time.Minute * 20
-	// known entry selection weight is dropped by a factor of exp(-failDropLn) after
-	// each unsuccessful connection (restored after a successful one)
-	failDropLn = 0.1
-	// known node connection success and quality statistics have a long term average
-	// and a short term value which is adjusted exponentially with a factor of
-	// pstatRecentAdjust with each dial/connection and also returned exponentially
-	// to the average with the time constant pstatReturnToMeanTC
-	pstatReturnToMeanTC = time.Hour
-	// node address selection weight is dropped by a factor of exp(-addrFailDropLn) after
-	// each unsuccessful connection (restored after a successful one)
-	addrFailDropLn = math.Ln2
-	// responseScoreTC and delayScoreTC are exponential decay time constants for
-	// calculating selection chances from response times and block delay times
-	responseScoreTC = time.Millisecond * 100
-	delayScoreTC    = time.Second * 5
-	timeoutPow      = 10
-	// initStatsWeight is used to initialize previously unknown peers with good
-	// statistics to give a chance to prove themselves
-	initStatsWeight = 1
-)
-
-// connReq represents a request for peer connection.
-type connReq struct {
-	p      *serverPeer
-	node   *enode.Node
-	result chan *poolEntry
-}
-
-// disconnReq represents a request for peer disconnection.
-type disconnReq struct {
-	entry   *poolEntry
-	stopped bool
-	done    chan struct{}
-}
-
-// registerReq represents a request for peer registration.
-type registerReq struct {
-	entry *poolEntry
-	done  chan struct{}
-}
-
-// serverPool implements a pool for storing and selecting newly discovered and already
-// known light server nodes. It received discovered nodes, stores statistics about
-// known nodes and takes care of always having enough good quality servers connected.
-type serverPool struct {
-	db     ethdb.Database
-	dbKey  []byte
-	server *p2p.Server
-	connWg sync.WaitGroup
-
-	topic discv5.Topic
-
-	discSetPeriod chan time.Duration
-	discNodes     chan *enode.Node
-	discLookups   chan bool
-
-	trustedNodes         map[enode.ID]*enode.Node
-	entries              map[enode.ID]*poolEntry
-	timeout, enableRetry chan *poolEntry
-	adjustStats          chan poolStatAdjust
-
-	knownQueue, newQueue       poolEntryQueue
-	knownSelect, newSelect     *utils.WeightedRandomSelect
-	knownSelected, newSelected int
-	fastDiscover               bool
-	connCh                     chan *connReq
-	disconnCh                  chan *disconnReq
-	registerCh                 chan *registerReq
-
-	closeCh chan struct{}
-	wg      sync.WaitGroup
-}
-
-// newServerPool creates a new serverPool instance
-func newServerPool(db ethdb.Database, ulcServers []string) *serverPool {
-	pool := &serverPool{
-		db:           db,
-		entries:      make(map[enode.ID]*poolEntry),
-		timeout:      make(chan *poolEntry, 1),
-		adjustStats:  make(chan poolStatAdjust, 100),
-		enableRetry:  make(chan *poolEntry, 1),
-		connCh:       make(chan *connReq),
-		disconnCh:    make(chan *disconnReq),
-		registerCh:   make(chan *registerReq),
-		closeCh:      make(chan struct{}),
-		knownSelect:  utils.NewWeightedRandomSelect(),
-		newSelect:    utils.NewWeightedRandomSelect(),
-		fastDiscover: true,
-		trustedNodes: parseTrustedNodes(ulcServers),
-	}
-
-	pool.knownQueue = newPoolEntryQueue(maxKnownEntries, pool.removeEntry)
-	pool.newQueue = newPoolEntryQueue(maxNewEntries, pool.removeEntry)
-	return pool
-}
-
-func (pool *serverPool) start(server *p2p.Server, topic discv5.Topic) {
-	pool.server = server
-	pool.topic = topic
-	pool.dbKey = append([]byte("serverPool/"), []byte(topic)...)
-	pool.loadNodes()
-	pool.connectToTrustedNodes()
-
-	if pool.server.DiscV5 != nil {
-		pool.discSetPeriod = make(chan time.Duration, 1)
-		pool.discNodes = make(chan *enode.Node, 100)
-		pool.discLookups = make(chan bool, 100)
-		go pool.discoverNodes()
-	}
-	pool.checkDial()
-	pool.wg.Add(1)
-	go pool.eventLoop()
-
-	// Inject the bootstrap nodes as initial dial candiates.
-	pool.wg.Add(1)
-	go func() {
-		defer pool.wg.Done()
-		for _, n := range server.BootstrapNodes {
-			select {
-			case pool.discNodes <- n:
-			case <-pool.closeCh:
-				return
-			}
-		}
-	}()
-}
-
-func (pool *serverPool) stop() {
-	close(pool.closeCh)
-	pool.wg.Wait()
-}
-
-// discoverNodes wraps SearchTopic, converting result nodes to enode.Node.
-func (pool *serverPool) discoverNodes() {
-	ch := make(chan *discv5.Node)
-	go func() {
-		pool.server.DiscV5.SearchTopic(pool.topic, pool.discSetPeriod, ch, pool.discLookups)
-		close(ch)
-	}()
-	for n := range ch {
-		pubkey, err := decodePubkey64(n.ID[:])
-		if err != nil {
-			continue
-		}
-		pool.discNodes <- enode.NewV4(pubkey, n.IP, int(n.TCP), int(n.UDP))
-	}
-}
-
-// connect should be called upon any incoming connection. If the connection has been
-// dialed by the server pool recently, the appropriate pool entry is returned.
-// Otherwise, the connection should be rejected.
-// Note that whenever a connection has been accepted and a pool entry has been returned,
-// disconnect should also always be called.
-func (pool *serverPool) connect(p *serverPeer, node *enode.Node) *poolEntry {
-	log.Debug("Connect new entry", "enode", p.id)
-	req := &connReq{p: p, node: node, result: make(chan *poolEntry, 1)}
-	select {
-	case pool.connCh <- req:
-	case <-pool.closeCh:
-		return nil
-	}
-	return <-req.result
-}
-
-// registered should be called after a successful handshake
-func (pool *serverPool) registered(entry *poolEntry) {
-	log.Debug("Registered new entry", "enode", entry.node.ID())
-	req := ®isterReq{entry: entry, done: make(chan struct{})}
-	select {
-	case pool.registerCh <- req:
-	case <-pool.closeCh:
-		return
-	}
-	<-req.done
-}
-
-// disconnect should be called when ending a connection. Service quality statistics
-// can be updated optionally (not updated if no registration happened, in this case
-// only connection statistics are updated, just like in case of timeout)
-func (pool *serverPool) disconnect(entry *poolEntry) {
-	stopped := false
-	select {
-	case <-pool.closeCh:
-		stopped = true
-	default:
-	}
-	log.Debug("Disconnected old entry", "enode", entry.node.ID())
-	req := &disconnReq{entry: entry, stopped: stopped, done: make(chan struct{})}
-
-	// Block until disconnection request is served.
-	pool.disconnCh <- req
-	<-req.done
-}
-
-const (
-	pseBlockDelay = iota
-	pseResponseTime
-	pseResponseTimeout
-)
-
-// poolStatAdjust records are sent to adjust peer block delay/response time statistics
-type poolStatAdjust struct {
-	adjustType int
-	entry      *poolEntry
-	time       time.Duration
-}
-
-// adjustBlockDelay adjusts the block announce delay statistics of a node
-func (pool *serverPool) adjustBlockDelay(entry *poolEntry, time time.Duration) {
-	if entry == nil {
-		return
-	}
-	pool.adjustStats <- poolStatAdjust{pseBlockDelay, entry, time}
-}
-
-// adjustResponseTime adjusts the request response time statistics of a node
-func (pool *serverPool) adjustResponseTime(entry *poolEntry, time time.Duration, timeout bool) {
-	if entry == nil {
-		return
-	}
-	if timeout {
-		pool.adjustStats <- poolStatAdjust{pseResponseTimeout, entry, time}
-	} else {
-		pool.adjustStats <- poolStatAdjust{pseResponseTime, entry, time}
-	}
-}
-
-// eventLoop handles pool events and mutex locking for all internal functions
-func (pool *serverPool) eventLoop() {
-	defer pool.wg.Done()
-	lookupCnt := 0
-	var convTime mclock.AbsTime
-	if pool.discSetPeriod != nil {
-		pool.discSetPeriod <- time.Millisecond * 100
-	}
-
-	// disconnect updates service quality statistics depending on the connection time
-	// and disconnection initiator.
-	disconnect := func(req *disconnReq, stopped bool) {
-		// Handle peer disconnection requests.
-		entry := req.entry
-		if entry.state == psRegistered {
-			connAdjust := float64(mclock.Now()-entry.regTime) / float64(targetConnTime)
-			if connAdjust > 1 {
-				connAdjust = 1
-			}
-			if stopped {
-				// disconnect requested by ourselves.
-				entry.connectStats.add(1, connAdjust)
-			} else {
-				// disconnect requested by server side.
-				entry.connectStats.add(connAdjust, 1)
-			}
-		}
-		entry.state = psNotConnected
-
-		if entry.knownSelected {
-			pool.knownSelected--
-		} else {
-			pool.newSelected--
-		}
-		pool.setRetryDial(entry)
-		pool.connWg.Done()
-		close(req.done)
-	}
-
-	for {
-		select {
-		case entry := <-pool.timeout:
-			if !entry.removed {
-				pool.checkDialTimeout(entry)
-			}
-
-		case entry := <-pool.enableRetry:
-			if !entry.removed {
-				entry.delayedRetry = false
-				pool.updateCheckDial(entry)
-			}
-
-		case adj := <-pool.adjustStats:
-			switch adj.adjustType {
-			case pseBlockDelay:
-				adj.entry.delayStats.add(float64(adj.time), 1)
-			case pseResponseTime:
-				adj.entry.responseStats.add(float64(adj.time), 1)
-				adj.entry.timeoutStats.add(0, 1)
-			case pseResponseTimeout:
-				adj.entry.timeoutStats.add(1, 1)
-			}
-
-		case node := <-pool.discNodes:
-			if pool.trustedNodes[node.ID()] == nil {
-				entry := pool.findOrNewNode(node)
-				pool.updateCheckDial(entry)
-			}
-
-		case conv := <-pool.discLookups:
-			if conv {
-				if lookupCnt == 0 {
-					convTime = mclock.Now()
-				}
-				lookupCnt++
-				if pool.fastDiscover && (lookupCnt == 50 || time.Duration(mclock.Now()-convTime) > time.Minute) {
-					pool.fastDiscover = false
-					if pool.discSetPeriod != nil {
-						pool.discSetPeriod <- time.Minute
-					}
-				}
-			}
-
-		case req := <-pool.connCh:
-			if pool.trustedNodes[req.p.ID()] != nil {
-				// ignore trusted nodes
-				req.result <- &poolEntry{trusted: true}
-			} else {
-				// Handle peer connection requests.
-				entry := pool.entries[req.p.ID()]
-				if entry == nil {
-					entry = pool.findOrNewNode(req.node)
-				}
-				if entry.state == psConnected || entry.state == psRegistered {
-					req.result <- nil
-					continue
-				}
-				pool.connWg.Add(1)
-				entry.peer = req.p
-				entry.state = psConnected
-				addr := &poolEntryAddress{
-					ip:       req.node.IP(),
-					port:     uint16(req.node.TCP()),
-					lastSeen: mclock.Now(),
-				}
-				entry.lastConnected = addr
-				entry.addr = make(map[string]*poolEntryAddress)
-				entry.addr[addr.strKey()] = addr
-				entry.addrSelect = *utils.NewWeightedRandomSelect()
-				entry.addrSelect.Update(addr)
-				req.result <- entry
-			}
-
-		case req := <-pool.registerCh:
-			if req.entry.trusted {
-				continue
-			}
-			// Handle peer registration requests.
-			entry := req.entry
-			entry.state = psRegistered
-			entry.regTime = mclock.Now()
-			if !entry.known {
-				pool.newQueue.remove(entry)
-				entry.known = true
-			}
-			pool.knownQueue.setLatest(entry)
-			entry.shortRetry = shortRetryCnt
-			close(req.done)
-
-		case req := <-pool.disconnCh:
-			if req.entry.trusted {
-				continue
-			}
-			// Handle peer disconnection requests.
-			disconnect(req, req.stopped)
-
-		case <-pool.closeCh:
-			if pool.discSetPeriod != nil {
-				close(pool.discSetPeriod)
-			}
-
-			// Spawn a goroutine to close the disconnCh after all connections are disconnected.
-			go func() {
-				pool.connWg.Wait()
-				close(pool.disconnCh)
-			}()
-
-			// Handle all remaining disconnection requests before exit.
-			for req := range pool.disconnCh {
-				disconnect(req, true)
-			}
-			pool.saveNodes()
-			return
-		}
-	}
-}
-
-func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry {
-	now := mclock.Now()
-	entry := pool.entries[node.ID()]
-	if entry == nil {
-		log.Debug("Discovered new entry", "id", node.ID())
-		entry = &poolEntry{
-			node:       node,
-			addr:       make(map[string]*poolEntryAddress),
-			addrSelect: *utils.NewWeightedRandomSelect(),
-			shortRetry: shortRetryCnt,
-		}
-		pool.entries[node.ID()] = entry
-		// initialize previously unknown peers with good statistics to give a chance to prove themselves
-		entry.connectStats.add(1, initStatsWeight)
-		entry.delayStats.add(0, initStatsWeight)
-		entry.responseStats.add(0, initStatsWeight)
-		entry.timeoutStats.add(0, initStatsWeight)
-	}
-	entry.lastDiscovered = now
-	addr := &poolEntryAddress{ip: node.IP(), port: uint16(node.TCP())}
-	if a, ok := entry.addr[addr.strKey()]; ok {
-		addr = a
-	} else {
-		entry.addr[addr.strKey()] = addr
-	}
-	addr.lastSeen = now
-	entry.addrSelect.Update(addr)
-	if !entry.known {
-		pool.newQueue.setLatest(entry)
-	}
-	return entry
-}
-
-// loadNodes loads known nodes and their statistics from the database
-func (pool *serverPool) loadNodes() {
-	enc, err := pool.db.Get(pool.dbKey)
-	if err != nil {
-		return
-	}
-	var list []*poolEntry
-	err = rlp.DecodeBytes(enc, &list)
-	if err != nil {
-		log.Debug("Failed to decode node list", "err", err)
-		return
-	}
-	for _, e := range list {
-		log.Debug("Loaded server stats", "id", e.node.ID(), "fails", e.lastConnected.fails,
-			"conn", fmt.Sprintf("%v/%v", e.connectStats.avg, e.connectStats.weight),
-			"delay", fmt.Sprintf("%v/%v", time.Duration(e.delayStats.avg), e.delayStats.weight),
-			"response", fmt.Sprintf("%v/%v", time.Duration(e.responseStats.avg), e.responseStats.weight),
-			"timeout", fmt.Sprintf("%v/%v", e.timeoutStats.avg, e.timeoutStats.weight))
-		pool.entries[e.node.ID()] = e
-		if pool.trustedNodes[e.node.ID()] == nil {
-			pool.knownQueue.setLatest(e)
-			pool.knownSelect.Update((*knownEntry)(e))
-		}
-	}
-}
-
-// connectToTrustedNodes adds trusted server nodes as static trusted peers.
-//
-// Note: trusted nodes are not handled by the server pool logic, they are not
-// added to either the known or new selection pools. They are connected/reconnected
-// by p2p.Server whenever possible.
-func (pool *serverPool) connectToTrustedNodes() {
-	//connect to trusted nodes
-	for _, node := range pool.trustedNodes {
-		pool.server.AddTrustedPeer(node)
-		pool.server.AddPeer(node)
-		log.Debug("Added trusted node", "id", node.ID().String())
-	}
-}
-
-// parseTrustedNodes returns valid and parsed enodes
-func parseTrustedNodes(trustedNodes []string) map[enode.ID]*enode.Node {
-	nodes := make(map[enode.ID]*enode.Node)
-
-	for _, node := range trustedNodes {
-		node, err := enode.Parse(enode.ValidSchemes, node)
-		if err != nil {
-			log.Warn("Trusted node URL invalid", "enode", node, "err", err)
-			continue
-		}
-		nodes[node.ID()] = node
-	}
-	return nodes
-}
-
-// saveNodes saves known nodes and their statistics into the database. Nodes are
-// ordered from least to most recently connected.
-func (pool *serverPool) saveNodes() {
-	list := make([]*poolEntry, len(pool.knownQueue.queue))
-	for i := range list {
-		list[i] = pool.knownQueue.fetchOldest()
-	}
-	enc, err := rlp.EncodeToBytes(list)
-	if err == nil {
-		pool.db.Put(pool.dbKey, enc)
-	}
-}
-
-// removeEntry removes a pool entry when the entry count limit is reached.
-// Note that it is called by the new/known queues from which the entry has already
-// been removed so removing it from the queues is not necessary.
-func (pool *serverPool) removeEntry(entry *poolEntry) {
-	pool.newSelect.Remove((*discoveredEntry)(entry))
-	pool.knownSelect.Remove((*knownEntry)(entry))
-	entry.removed = true
-	delete(pool.entries, entry.node.ID())
-}
-
-// setRetryDial starts the timer which will enable dialing a certain node again
-func (pool *serverPool) setRetryDial(entry *poolEntry) {
-	delay := longRetryDelay
-	if entry.shortRetry > 0 {
-		entry.shortRetry--
-		delay = shortRetryDelay
-	}
-	delay += time.Duration(rand.Int63n(int64(delay) + 1))
-	entry.delayedRetry = true
-	go func() {
-		select {
-		case <-pool.closeCh:
-		case <-time.After(delay):
-			select {
-			case <-pool.closeCh:
-			case pool.enableRetry <- entry:
-			}
-		}
-	}()
-}
-
-// updateCheckDial is called when an entry can potentially be dialed again. It updates
-// its selection weights and checks if new dials can/should be made.
-func (pool *serverPool) updateCheckDial(entry *poolEntry) {
-	pool.newSelect.Update((*discoveredEntry)(entry))
-	pool.knownSelect.Update((*knownEntry)(entry))
-	pool.checkDial()
-}
-
-// checkDial checks if new dials can/should be made. It tries to select servers both
-// based on good statistics and recent discovery.
-func (pool *serverPool) checkDial() {
-	fillWithKnownSelects := !pool.fastDiscover
-	for pool.knownSelected < targetKnownSelect {
-		entry := pool.knownSelect.Choose()
-		if entry == nil {
-			fillWithKnownSelects = false
-			break
-		}
-		pool.dial((*poolEntry)(entry.(*knownEntry)), true)
-	}
-	for pool.knownSelected+pool.newSelected < targetServerCount {
-		entry := pool.newSelect.Choose()
-		if entry == nil {
-			break
-		}
-		pool.dial((*poolEntry)(entry.(*discoveredEntry)), false)
-	}
-	if fillWithKnownSelects {
-		// no more newly discovered nodes to select and since fast discover period
-		// is over, we probably won't find more in the near future so select more
-		// known entries if possible
-		for pool.knownSelected < targetServerCount {
-			entry := pool.knownSelect.Choose()
-			if entry == nil {
-				break
-			}
-			pool.dial((*poolEntry)(entry.(*knownEntry)), true)
-		}
-	}
-}
-
-// dial initiates a new connection
-func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) {
-	if pool.server == nil || entry.state != psNotConnected {
-		return
-	}
-	entry.state = psDialed
-	entry.knownSelected = knownSelected
-	if knownSelected {
-		pool.knownSelected++
-	} else {
-		pool.newSelected++
-	}
-	addr := entry.addrSelect.Choose().(*poolEntryAddress)
-	log.Debug("Dialing new peer", "lesaddr", entry.node.ID().String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected)
-	entry.dialed = addr
-	go func() {
-		pool.server.AddPeer(entry.node)
-		select {
-		case <-pool.closeCh:
-		case <-time.After(dialTimeout):
-			select {
-			case <-pool.closeCh:
-			case pool.timeout <- entry:
-			}
-		}
-	}()
-}
-
-// checkDialTimeout checks if the node is still in dialed state and if so, resets it
-// and adjusts connection statistics accordingly.
-func (pool *serverPool) checkDialTimeout(entry *poolEntry) {
-	if entry.state != psDialed {
-		return
-	}
-	log.Debug("Dial timeout", "lesaddr", entry.node.ID().String()+"@"+entry.dialed.strKey())
-	entry.state = psNotConnected
-	if entry.knownSelected {
-		pool.knownSelected--
-	} else {
-		pool.newSelected--
-	}
-	entry.connectStats.add(0, 1)
-	entry.dialed.fails++
-	pool.setRetryDial(entry)
-}
-
-const (
-	psNotConnected = iota
-	psDialed
-	psConnected
-	psRegistered
-)
-
-// poolEntry represents a server node and stores its current state and statistics.
-type poolEntry struct {
-	peer                  *serverPeer
-	pubkey                [64]byte // secp256k1 key of the node
-	addr                  map[string]*poolEntryAddress
-	node                  *enode.Node
-	lastConnected, dialed *poolEntryAddress
-	addrSelect            utils.WeightedRandomSelect
-
-	lastDiscovered                mclock.AbsTime
-	known, knownSelected, trusted bool
-	connectStats, delayStats      poolStats
-	responseStats, timeoutStats   poolStats
-	state                         int
-	regTime                       mclock.AbsTime
-	queueIdx                      int
-	removed                       bool
-
-	delayedRetry bool
-	shortRetry   int
-}
-
-// poolEntryEnc is the RLP encoding of poolEntry.
-type poolEntryEnc struct {
-	Pubkey                     []byte
-	IP                         net.IP
-	Port                       uint16
-	Fails                      uint
-	CStat, DStat, RStat, TStat poolStats
-}
-
-func (e *poolEntry) EncodeRLP(w io.Writer) error {
-	return rlp.Encode(w, &poolEntryEnc{
-		Pubkey: encodePubkey64(e.node.Pubkey()),
-		IP:     e.lastConnected.ip,
-		Port:   e.lastConnected.port,
-		Fails:  e.lastConnected.fails,
-		CStat:  e.connectStats,
-		DStat:  e.delayStats,
-		RStat:  e.responseStats,
-		TStat:  e.timeoutStats,
-	})
-}
-
-func (e *poolEntry) DecodeRLP(s *rlp.Stream) error {
-	var entry poolEntryEnc
-	if err := s.Decode(&entry); err != nil {
-		return err
-	}
-	pubkey, err := decodePubkey64(entry.Pubkey)
-	if err != nil {
-		return err
-	}
-	addr := &poolEntryAddress{ip: entry.IP, port: entry.Port, fails: entry.Fails, lastSeen: mclock.Now()}
-	e.node = enode.NewV4(pubkey, entry.IP, int(entry.Port), int(entry.Port))
-	e.addr = make(map[string]*poolEntryAddress)
-	e.addr[addr.strKey()] = addr
-	e.addrSelect = *utils.NewWeightedRandomSelect()
-	e.addrSelect.Update(addr)
-	e.lastConnected = addr
-	e.connectStats = entry.CStat
-	e.delayStats = entry.DStat
-	e.responseStats = entry.RStat
-	e.timeoutStats = entry.TStat
-	e.shortRetry = shortRetryCnt
-	e.known = true
-	return nil
-}
-
-func encodePubkey64(pub *ecdsa.PublicKey) []byte {
-	return crypto.FromECDSAPub(pub)[1:]
-}
-
-func decodePubkey64(b []byte) (*ecdsa.PublicKey, error) {
-	return crypto.UnmarshalPubkey(append([]byte{0x04}, b...))
-}
-
-// discoveredEntry implements wrsItem
-type discoveredEntry poolEntry
-
-// Weight calculates random selection weight for newly discovered entries
-func (e *discoveredEntry) Weight() int64 {
-	if e.state != psNotConnected || e.delayedRetry {
-		return 0
-	}
-	t := time.Duration(mclock.Now() - e.lastDiscovered)
-	if t <= discoverExpireStart {
-		return 1000000000
-	}
-	return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst)))
-}
-
-// knownEntry implements wrsItem
-type knownEntry poolEntry
-
-// Weight calculates random selection weight for known entries
-func (e *knownEntry) Weight() int64 {
-	if e.state != psNotConnected || !e.known || e.delayedRetry {
-		return 0
-	}
-	return int64(1000000000 * e.connectStats.recentAvg() * math.Exp(-float64(e.lastConnected.fails)*failDropLn-e.responseStats.recentAvg()/float64(responseScoreTC)-e.delayStats.recentAvg()/float64(delayScoreTC)) * math.Pow(1-e.timeoutStats.recentAvg(), timeoutPow))
-}
-
-// poolEntryAddress is a separate object because currently it is necessary to remember
-// multiple potential network addresses for a pool entry. This will be removed after
-// the final implementation of v5 discovery which will retrieve signed and serial
-// numbered advertisements, making it clear which IP/port is the latest one.
-type poolEntryAddress struct {
-	ip       net.IP
-	port     uint16
-	lastSeen mclock.AbsTime // last time it was discovered, connected or loaded from db
-	fails    uint           // connection failures since last successful connection (persistent)
-}
-
-func (a *poolEntryAddress) Weight() int64 {
-	t := time.Duration(mclock.Now() - a.lastSeen)
-	return int64(1000000*math.Exp(-float64(t)/float64(discoverExpireConst)-float64(a.fails)*addrFailDropLn)) + 1
-}
-
-func (a *poolEntryAddress) strKey() string {
-	return a.ip.String() + ":" + strconv.Itoa(int(a.port))
-}
-
-// poolStats implement statistics for a certain quantity with a long term average
-// and a short term value which is adjusted exponentially with a factor of
-// pstatRecentAdjust with each update and also returned exponentially to the
-// average with the time constant pstatReturnToMeanTC
-type poolStats struct {
-	sum, weight, avg, recent float64
-	lastRecalc               mclock.AbsTime
-}
-
-// init initializes stats with a long term sum/update count pair retrieved from the database
-func (s *poolStats) init(sum, weight float64) {
-	s.sum = sum
-	s.weight = weight
-	var avg float64
-	if weight > 0 {
-		avg = s.sum / weight
-	}
-	s.avg = avg
-	s.recent = avg
-	s.lastRecalc = mclock.Now()
-}
-
-// recalc recalculates recent value return-to-mean and long term average
-func (s *poolStats) recalc() {
-	now := mclock.Now()
-	s.recent = s.avg + (s.recent-s.avg)*math.Exp(-float64(now-s.lastRecalc)/float64(pstatReturnToMeanTC))
-	if s.sum == 0 {
-		s.avg = 0
-	} else {
-		if s.sum > s.weight*1e30 {
-			s.avg = 1e30
-		} else {
-			s.avg = s.sum / s.weight
-		}
-	}
-	s.lastRecalc = now
-}
-
-// add updates the stats with a new value
-func (s *poolStats) add(value, weight float64) {
-	s.weight += weight
-	s.sum += value * weight
-	s.recalc()
-}
-
-// recentAvg returns the short-term adjusted average
-func (s *poolStats) recentAvg() float64 {
-	s.recalc()
-	return s.recent
-}
-
-func (s *poolStats) EncodeRLP(w io.Writer) error {
-	return rlp.Encode(w, []interface{}{math.Float64bits(s.sum), math.Float64bits(s.weight)})
-}
-
-func (s *poolStats) DecodeRLP(st *rlp.Stream) error {
-	var stats struct {
-		SumUint, WeightUint uint64
-	}
-	if err := st.Decode(&stats); err != nil {
-		return err
-	}
-	s.init(math.Float64frombits(stats.SumUint), math.Float64frombits(stats.WeightUint))
-	return nil
-}
-
-// poolEntryQueue keeps track of its least recently accessed entries and removes
-// them when the number of entries reaches the limit
-type poolEntryQueue struct {
-	queue                  map[int]*poolEntry // known nodes indexed by their latest lastConnCnt value
-	newPtr, oldPtr, maxCnt int
-	removeFromPool         func(*poolEntry)
-}
-
-// newPoolEntryQueue returns a new poolEntryQueue
-func newPoolEntryQueue(maxCnt int, removeFromPool func(*poolEntry)) poolEntryQueue {
-	return poolEntryQueue{queue: make(map[int]*poolEntry), maxCnt: maxCnt, removeFromPool: removeFromPool}
-}
-
-// fetchOldest returns and removes the least recently accessed entry
-func (q *poolEntryQueue) fetchOldest() *poolEntry {
-	if len(q.queue) == 0 {
-		return nil
-	}
-	for {
-		if e := q.queue[q.oldPtr]; e != nil {
-			delete(q.queue, q.oldPtr)
-			q.oldPtr++
-			return e
-		}
-		q.oldPtr++
-	}
-}
-
-// remove removes an entry from the queue
-func (q *poolEntryQueue) remove(entry *poolEntry) {
-	if q.queue[entry.queueIdx] == entry {
-		delete(q.queue, entry.queueIdx)
-	}
-}
-
-// setLatest adds or updates a recently accessed entry. It also checks if an old entry
-// needs to be removed and removes it from the parent pool too with a callback function.
-func (q *poolEntryQueue) setLatest(entry *poolEntry) {
-	if q.queue[entry.queueIdx] == entry {
-		delete(q.queue, entry.queueIdx)
-	} else {
-		if len(q.queue) == q.maxCnt {
-			e := q.fetchOldest()
-			q.remove(e)
-			q.removeFromPool(e)
-		}
-	}
-	entry.queueIdx = q.newPtr
-	q.queue[entry.queueIdx] = entry
-	q.newPtr++
-}
diff --git a/les/servingqueue.go b/les/servingqueue.go
index 9db84e6159..16e064cb3f 100644
--- a/les/servingqueue.go
+++ b/les/servingqueue.go
@@ -123,7 +123,7 @@ func (t *servingTask) waitOrStop() bool {
 // newServingQueue returns a new servingQueue
 func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue {
 	sq := &servingQueue{
-		queue:          prque.New(nil),
+		queue:          prque.NewWrapAround(nil),
 		suspendBias:    suspendBias,
 		queueAddCh:     make(chan *servingTask, 100),
 		queueBestCh:    make(chan *servingTask),
@@ -279,7 +279,7 @@ func (sq *servingQueue) updateRecentTime() {
 func (sq *servingQueue) addTask(task *servingTask) {
 	if sq.best == nil {
 		sq.best = task
-	} else if task.priority > sq.best.priority {
+	} else if task.priority-sq.best.priority > 0 {
 		sq.queue.Push(sq.best, sq.best.priority)
 		sq.best = task
 	} else {
diff --git a/les/state_accessor.go b/les/state_accessor.go
new file mode 100644
index 0000000000..af5df36508
--- /dev/null
+++ b/les/state_accessor.go
@@ -0,0 +1,75 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package les
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/light"
+)
+
+// stateAtBlock retrieves the state database associated with a certain block.
+func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, error) {
+	return light.NewState(ctx, block.Header(), leth.odr), nil
+}
+
+// stateAtTransaction returns the execution environment of a certain transaction.
+func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
+	// Short circuit if it's genesis block.
+	if block.NumberU64() == 0 {
+		return nil, vm.BlockContext{}, nil, errors.New("no transaction in genesis")
+	}
+	// Create the parent state database
+	parent, err := leth.blockchain.GetBlock(ctx, block.ParentHash(), block.NumberU64()-1)
+	if err != nil {
+		return nil, vm.BlockContext{}, nil, err
+	}
+	statedb, err := leth.stateAtBlock(ctx, parent, reexec)
+	if err != nil {
+		return nil, vm.BlockContext{}, nil, err
+	}
+	if txIndex == 0 && len(block.Transactions()) == 0 {
+		return nil, vm.BlockContext{}, statedb, nil
+	}
+	// Recompute transactions up to the target index.
+	signer := types.MakeSigner(leth.blockchain.Config(), block.Number())
+	for idx, tx := range block.Transactions() {
+		// Assemble the transaction call message and return if the requested offset
+		msg, _ := tx.AsMessage(signer)
+		txContext := core.NewEVMTxContext(msg)
+		context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil)
+		statedb.Prepare(tx.Hash(), block.Hash(), idx)
+		if idx == txIndex {
+			return msg, context, statedb, nil
+		}
+		// Not yet the searched for transaction, execute on top of the current state
+		vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{})
+		if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
+			return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
+		}
+		// Ensure any modifications are committed to the state
+		// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
+		statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
+	}
+	return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
+}
diff --git a/les/sync.go b/les/sync.go
index d2568d45bc..fa5ef4ff82 100644
--- a/les/sync.go
+++ b/les/sync.go
@@ -26,6 +26,7 @@ import (
 	"github.com/ethereum/go-ethereum/eth/downloader"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/params"
 )
 
 var errInvalidCheckpoint = errors.New("invalid advertised checkpoint")
@@ -56,8 +57,8 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error {
 	defer cancel()
 
 	// Fetch the block header corresponding to the checkpoint registration.
-	cp := peer.checkpoint
-	header, err := light.GetUntrustedHeaderByNumber(ctx, h.backend.odr, peer.checkpointNumber, peer.id)
+	wrapPeer := &peerConnection{handler: h, peer: peer}
+	header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber)
 	if err != nil {
 		return err
 	}
@@ -66,7 +67,7 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error {
 	if err != nil {
 		return err
 	}
-	events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash())
+	events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, peer.checkpoint.SectionIndex, peer.checkpoint.Hash())
 	if len(events) == 0 {
 		return errInvalidCheckpoint
 	}
@@ -98,22 +99,33 @@ func (h *clientHandler) synchronise(peer *serverPeer) {
 	if currentTd != nil && peer.Td().Cmp(currentTd) < 0 {
 		return
 	}
-	// Recap the checkpoint.
-	//
-	// The light client may be connected to several different versions of the server.
-	// (1) Old version server which can not provide stable checkpoint in the handshake packet.
-	//     => Use hardcoded checkpoint or empty checkpoint
-	// (2) New version server but simple checkpoint syncing is not enabled(e.g. mainnet, new testnet or private network)
-	//     => Use hardcoded checkpoint or empty checkpoint
-	// (3) New version server but the provided stable checkpoint is even lower than the hardcoded one.
-	//     => Use hardcoded checkpoint
+	// Recap the checkpoint. The light client may be connected to several different
+	// versions of the server.
+	// (1) Old version server which can not provide stable checkpoint in the
+	//     handshake packet.
+	//     => Use local checkpoint or empty checkpoint
+	// (2) New version server but simple checkpoint syncing is not enabled
+	//     (e.g. mainnet, new testnet or private network)
+	//     => Use local checkpoint or empty checkpoint
+	// (3) New version server but the provided stable checkpoint is even lower
+	//     than the local one.
+	//     => Use local checkpoint
 	// (4) New version server with valid and higher stable checkpoint
 	//     => Use provided checkpoint
-	var checkpoint = &peer.checkpoint
-	var hardcoded bool
+	var (
+		local      bool
+		checkpoint = &peer.checkpoint
+	)
 	if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex {
-		checkpoint = h.checkpoint // Use the hardcoded one.
-		hardcoded = true
+		local, checkpoint = true, h.checkpoint
+	}
+	// Replace the checkpoint with locally configured one If it's required by
+	// users. Nil checkpoint means synchronization from the scratch.
+	if h.backend.config.SyncFromCheckpoint {
+		local, checkpoint = true, h.backend.config.Checkpoint
+		if h.backend.config.Checkpoint == nil {
+			checkpoint = ¶ms.TrustedCheckpoint{}
+		}
 	}
 	// Determine whether we should run checkpoint syncing or normal light syncing.
 	//
@@ -121,7 +133,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) {
 	//
 	// 1. The checkpoint is empty
 	// 2. The latest head block of the local chain is above the checkpoint.
-	// 3. The checkpoint is hardcoded(recap with local hardcoded checkpoint)
+	// 3. The checkpoint is local(replaced with local checkpoint)
 	// 4. For some networks the checkpoint syncing is not activated.
 	mode := checkpointSync
 	switch {
@@ -131,7 +143,7 @@ func (h *clientHandler) synchronise(peer *serverPeer) {
 	case latest.Number.Uint64() >= (checkpoint.SectionIndex+1)*h.backend.iConfig.ChtSize-1:
 		mode = lightSync
 		log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint")
-	case hardcoded:
+	case local:
 		mode = legacyCheckpointSync
 		log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded")
 	case h.backend.oracle == nil || !h.backend.oracle.IsRunning():
@@ -143,12 +155,14 @@ func (h *clientHandler) synchronise(peer *serverPeer) {
 		}
 		log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated")
 	}
+
 	// Notify testing framework if syncing has completed(for testing purpose).
 	defer func() {
-		if h.syncDone != nil {
-			h.syncDone()
+		if h.syncEnd != nil {
+			h.syncEnd(h.backend.blockchain.CurrentHeader())
 		}
 	}()
+
 	start := time.Now()
 	if mode == checkpointSync || mode == legacyCheckpointSync {
 		// Validate the advertised checkpoint
@@ -177,6 +191,10 @@ func (h *clientHandler) synchronise(peer *serverPeer) {
 			return
 		}
 	}
+
+	if h.syncStart != nil {
+		h.syncStart(h.backend.blockchain.CurrentHeader())
+	}
 	// Fetch the remaining block headers based on the current chain header.
 	if err := h.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil {
 		log.Debug("Synchronise failed", "reason", err)
diff --git a/les/sync_test.go b/les/sync_test.go
index c128a8c9f7..d3bb90df02 100644
--- a/les/sync_test.go
+++ b/les/sync_test.go
@@ -24,21 +24,22 @@ import (
 
 	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/params"
 )
 
 // Test light syncing which will download all headers from genesis.
-func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 0) }
+func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 0) }
 
 // Test legacy checkpoint syncing which will download tail headers
 // based on a hardcoded checkpoint.
-func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 1) }
+func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 1) }
 
 // Test checkpoint syncing which will download tail headers based
 // on a verified checkpoint.
-func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 2) }
+func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 2) }
 
 func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
 	config := light.TestServerIndexerConfig
@@ -53,8 +54,14 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
 			time.Sleep(10 * time.Millisecond)
 		}
 	}
-	// Generate 512+4 blocks (totally 1 CHT sections)
-	server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false)
+	// Generate 128+1 blocks (totally 1 CHT section)
+	netconfig := testnetConfig{
+		blocks:    int(config.ChtSize + config.ChtConfirms),
+		protocol:  protocol,
+		indexFn:   waitIndexers,
+		nopruning: true,
+	}
+	server, client, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
 	expected := config.ChtSize + config.ChtConfirms
@@ -77,10 +84,11 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
 			// Register the assembled checkpoint into oracle.
 			header := server.backend.Blockchain().CurrentHeader()
 
-			data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
+			data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
 			sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
 			sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
-			if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
+			auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337))
+			if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
 				t.Error("register checkpoint failed", err)
 			}
 			server.backend.Commit()
@@ -99,8 +107,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
 	}
 
 	done := make(chan error)
-	client.handler.syncDone = func() {
-		header := client.handler.backend.blockchain.CurrentHeader()
+	client.handler.syncEnd = func(header *types.Header) {
 		if header.Number.Uint64() == expected {
 			done <- nil
 		} else {
@@ -127,10 +134,10 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
 	}
 }
 
-func TestMissOracleBackend(t *testing.T)             { testMissOracleBackend(t, true) }
-func TestMissOracleBackendNoCheckpoint(t *testing.T) { testMissOracleBackend(t, false) }
+func TestMissOracleBackendLES3(t *testing.T)             { testMissOracleBackend(t, true, lpv3) }
+func TestMissOracleBackendNoCheckpointLES3(t *testing.T) { testMissOracleBackend(t, false, lpv3) }
 
-func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
+func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol int) {
 	config := light.TestServerIndexerConfig
 
 	waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
@@ -143,8 +150,14 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
 			time.Sleep(10 * time.Millisecond)
 		}
 	}
-	// Generate 512+4 blocks (totally 1 CHT sections)
-	server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), 3, waitIndexers, nil, 0, false, false)
+	// Generate 128+1 blocks (totally 1 CHT section)
+	netconfig := testnetConfig{
+		blocks:    int(config.ChtSize + config.ChtConfirms),
+		protocol:  protocol,
+		indexFn:   waitIndexers,
+		nopruning: true,
+	}
+	server, client, tearDown := newClientServerEnv(t, netconfig)
 	defer tearDown()
 
 	expected := config.ChtSize + config.ChtConfirms
@@ -159,10 +172,11 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
 	// Register the assembled checkpoint into oracle.
 	header := server.backend.Blockchain().CurrentHeader()
 
-	data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
+	data := append([]byte{0x19, 0x00}, append(oracleAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...)
 	sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey)
 	sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
-	if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(bind.NewKeyedTransactor(signerKey), cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
+	auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337))
+	if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil {
 		t.Error("register checkpoint failed", err)
 	}
 	server.backend.Commit()
@@ -196,8 +210,7 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
 	}
 
 	done := make(chan error)
-	client.handler.syncDone = func() {
-		header := client.handler.backend.blockchain.CurrentHeader()
+	client.handler.syncEnd = func(header *types.Header) {
 		if header.Number.Uint64() == expected {
 			done <- nil
 		} else {
@@ -218,3 +231,160 @@ func testMissOracleBackend(t *testing.T, hasCheckpoint bool) {
 		t.Error("checkpoint syncing timeout")
 	}
 }
+
+func TestSyncFromConfiguredCheckpointLES3(t *testing.T) { testSyncFromConfiguredCheckpoint(t, lpv3) }
+
+func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) {
+	config := light.TestServerIndexerConfig
+
+	waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
+		for {
+			cs, _, _ := cIndexer.Sections()
+			bts, _, _ := btIndexer.Sections()
+			if cs >= 2 && bts >= 2 {
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	}
+	// Generate 256+1 blocks (totally 2 CHT sections)
+	netconfig := testnetConfig{
+		blocks:    int(2*config.ChtSize + config.ChtConfirms),
+		protocol:  protocol,
+		indexFn:   waitIndexers,
+		nopruning: true,
+	}
+	server, client, tearDown := newClientServerEnv(t, netconfig)
+	defer tearDown()
+
+	// Configure the local checkpoint(the first section)
+	head := server.handler.blockchain.GetHeaderByNumber(config.ChtSize - 1).Hash()
+	cp := ¶ms.TrustedCheckpoint{
+		SectionIndex: 0,
+		SectionHead:  head,
+		CHTRoot:      light.GetChtRoot(server.db, 0, head),
+		BloomRoot:    light.GetBloomTrieRoot(server.db, 0, head),
+	}
+	client.handler.backend.config.SyncFromCheckpoint = true
+	client.handler.backend.config.Checkpoint = cp
+	client.handler.checkpoint = cp
+	client.handler.backend.blockchain.AddTrustedCheckpoint(cp)
+
+	var (
+		start       = make(chan error, 1)
+		end         = make(chan error, 1)
+		expectStart = config.ChtSize - 1
+		expectEnd   = 2*config.ChtSize + config.ChtConfirms
+	)
+	client.handler.syncStart = func(header *types.Header) {
+		if header.Number.Uint64() == expectStart {
+			start <- nil
+		} else {
+			start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number)
+		}
+	}
+	client.handler.syncEnd = func(header *types.Header) {
+		if header.Number.Uint64() == expectEnd {
+			end <- nil
+		} else {
+			end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number)
+		}
+	}
+	// Create connected peer pair.
+	if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler); err != nil {
+		t.Fatalf("Failed to connect testing peers %v", err)
+	}
+
+	select {
+	case err := <-start:
+		if err != nil {
+			t.Error("sync failed", err)
+		}
+		return
+	case <-time.NewTimer(10 * time.Second).C:
+		t.Error("checkpoint syncing timeout")
+	}
+
+	select {
+	case err := <-end:
+		if err != nil {
+			t.Error("sync failed", err)
+		}
+		return
+	case <-time.NewTimer(10 * time.Second).C:
+		t.Error("checkpoint syncing timeout")
+	}
+}
+
+func TestSyncAll(t *testing.T) { testSyncAll(t, lpv3) }
+
+func testSyncAll(t *testing.T, protocol int) {
+	config := light.TestServerIndexerConfig
+
+	waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
+		for {
+			cs, _, _ := cIndexer.Sections()
+			bts, _, _ := btIndexer.Sections()
+			if cs >= 2 && bts >= 2 {
+				break
+			}
+			time.Sleep(10 * time.Millisecond)
+		}
+	}
+	// Generate 256+1 blocks (totally 2 CHT sections)
+	netconfig := testnetConfig{
+		blocks:    int(2*config.ChtSize + config.ChtConfirms),
+		protocol:  protocol,
+		indexFn:   waitIndexers,
+		nopruning: true,
+	}
+	server, client, tearDown := newClientServerEnv(t, netconfig)
+	defer tearDown()
+
+	client.handler.backend.config.SyncFromCheckpoint = true
+
+	var (
+		start       = make(chan error, 1)
+		end         = make(chan error, 1)
+		expectStart = uint64(0)
+		expectEnd   = 2*config.ChtSize + config.ChtConfirms
+	)
+	client.handler.syncStart = func(header *types.Header) {
+		if header.Number.Uint64() == expectStart {
+			start <- nil
+		} else {
+			start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number)
+		}
+	}
+	client.handler.syncEnd = func(header *types.Header) {
+		if header.Number.Uint64() == expectEnd {
+			end <- nil
+		} else {
+			end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number)
+		}
+	}
+	// Create connected peer pair.
+	if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler); err != nil {
+		t.Fatalf("Failed to connect testing peers %v", err)
+	}
+
+	select {
+	case err := <-start:
+		if err != nil {
+			t.Error("sync failed", err)
+		}
+		return
+	case <-time.NewTimer(10 * time.Second).C:
+		t.Error("checkpoint syncing timeout")
+	}
+
+	select {
+	case err := <-end:
+		if err != nil {
+			t.Error("sync failed", err)
+		}
+		return
+	case <-time.NewTimer(10 * time.Second).C:
+		t.Error("checkpoint syncing timeout")
+	}
+}
diff --git a/les/test_helper.go b/les/test_helper.go
index 1f02d25299..fc85ed957f 100644
--- a/les/test_helper.go
+++ b/les/test_helper.go
@@ -14,8 +14,9 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// This file contains some shares testing functionality, common to  multiple
-// different files and modules being tested.
+// This file contains some shares testing functionality, common to multiple
+// different files and modules being tested. Client based network and Server
+// based network can be created easily with available APIs.
 
 package les
 
@@ -35,14 +36,16 @@ import (
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
 	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/forkid"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/les/checkpointoracle"
 	"github.com/ethereum/go-ethereum/les/flowcontrol"
+	vfs "github.com/ethereum/go-ethereum/les/vflux/server"
 	"github.com/ethereum/go-ethereum/light"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/enode"
@@ -66,10 +69,10 @@ var (
 
 	testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029")
 
-	// Checkpoint registrar relative
-	registrarAddr common.Address
-	signerKey, _  = crypto.GenerateKey()
-	signerAddr    = crypto.PubkeyToAddress(signerKey.PublicKey)
+	// Checkpoint oracle relative fields
+	oracleAddr   common.Address
+	signerKey, _ = crypto.GenerateKey()
+	signerAddr   = crypto.PubkeyToAddress(signerKey.PublicKey)
 )
 
 var (
@@ -110,13 +113,23 @@ func prepare(n int, backend *backends.SimulatedBackend) {
 	for i := 0; i < n; i++ {
 		switch i {
 		case 0:
+			// Builtin-block
+			//    number: 1
+			//    txs:    2
+
 			// deploy checkpoint contract
-			registrarAddr, _, _, _ = contract.DeployCheckpointOracle(bind.NewKeyedTransactor(bankKey), backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1))
+			auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337))
+			oracleAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1))
+
 			// bankUser transfers some ether to user1
 			nonce, _ := backend.PendingNonceAt(ctx, bankAddr)
 			tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey)
 			backend.SendTransaction(ctx, tx)
 		case 1:
+			// Builtin-block
+			//    number: 2
+			//    txs:    4
+
 			bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
 			userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1)
 
@@ -137,6 +150,10 @@ func prepare(n int, backend *backends.SimulatedBackend) {
 			tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(0), testEventEmitterCode), signer, userKey1)
 			backend.SendTransaction(ctx, tx4)
 		case 2:
+			// Builtin-block
+			//    number: 3
+			//    txs:    2
+
 			// bankUser transfer some ether to signer
 			bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
 			tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, nil, nil), signer, bankKey)
@@ -147,6 +164,10 @@ func prepare(n int, backend *backends.SimulatedBackend) {
 			tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, nil, data), signer, bankKey)
 			backend.SendTransaction(ctx, tx2)
 		case 3:
+			// Builtin-block
+			//    number: 4
+			//    txs:    1
+
 			// invoke test contract
 			bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr)
 			data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002")
@@ -158,17 +179,17 @@ func prepare(n int, backend *backends.SimulatedBackend) {
 }
 
 // testIndexers creates a set of indexers with specified params for testing purpose.
-func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig) []*core.ChainIndexer {
+func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig, disablePruning bool) []*core.ChainIndexer {
 	var indexers [3]*core.ChainIndexer
-	indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms)
-	indexers[1] = eth.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms)
-	indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize)
+	indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms, disablePruning)
+	indexers[1] = core.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms)
+	indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize, disablePruning)
 	// make bloomTrieIndexer as a child indexer of bloom indexer.
 	indexers[1].AddChildIndexer(indexers[2])
 	return indexers[:]
 }
 
-func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet, ulcServers []string, ulcFraction int) *clientHandler {
+func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet, ulcServers []string, ulcFraction int) (*clientHandler, func()) {
 	var (
 		evmux  = new(event.TypeMux)
 		engine = ethash.NewFaker()
@@ -202,7 +223,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
 	client := &LightEthereum{
 		lesCommons: lesCommons{
 			genesis:     genesis.Hash(),
-			config:      ð.Config{LightPeers: 100, NetworkId: NetworkId},
+			config:      ðconfig.Config{LightPeers: 100, NetworkId: NetworkId},
 			chainConfig: params.AllEthashProtocolChanges,
 			iConfig:     light.TestClientIndexerConfig,
 			chainDb:     db,
@@ -223,10 +244,13 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
 	if client.oracle != nil {
 		client.oracle.Start(backend)
 	}
-	return client.handler
+	client.handler.start()
+	return client.handler, func() {
+		client.handler.stop()
+	}
 }
 
-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, func()) {
 	var (
 		gspec = core.Genesis{
 			Config:   params.AllEthashProtocolChanges,
@@ -265,7 +289,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
 	server := &LesServer{
 		lesCommons: lesCommons{
 			genesis:     genesis.Hash(),
-			config:      ð.Config{LightPeers: 100, NetworkId: NetworkId},
+			config:      ðconfig.Config{LightPeers: 100, NetworkId: NetworkId},
 			chainConfig: params.AllEthashProtocolChanges,
 			iConfig:     light.TestServerIndexerConfig,
 			chainDb:     db,
@@ -273,7 +297,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
 			oracle:      oracle,
 			closeCh:     make(chan struct{}),
 		},
-		peers:        peers,
+		peers:        newClientPeerSet(),
 		servingQueue: newServingQueue(int64(time.Millisecond*10), 1),
 		defParams: flowcontrol.ServerParams{
 			BufLimit:    testBufLimit,
@@ -281,17 +305,23 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
 		},
 		fcManager: flowcontrol.NewClientManager(nil, clock),
 	}
-	server.costTracker, server.freeCapacity = newCostTracker(db, server.config)
+	server.costTracker, server.minCapacity = newCostTracker(db, server.config)
 	server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism.
-	server.clientPool = newClientPool(db, 1, clock, nil)
-	server.clientPool.setLimits(10000, 10000) // Assign enough capacity for clientpool
+	server.clientPool = vfs.NewClientPool(db, testBufRecharge, defaultConnectedBias, clock, alwaysTrueFn)
+	server.clientPool.Start()
+	server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool
 	server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true })
 	if server.oracle != nil {
 		server.oracle.Start(simulation)
 	}
 	server.servingQueue.setThreads(4)
 	server.handler.start()
-	return server.handler, simulation
+	closer := func() { server.Stop() }
+	return server.handler, simulation, closer
+}
+
+func alwaysTrueFn() bool {
+	return true
 }
 
 // testPeer is a simulated peer to allow testing direct network calls.
@@ -303,44 +333,61 @@ type testPeer struct {
 	app *p2p.MsgPipeRW    // Application layer reader/writer to simulate the local side
 }
 
-// newTestPeer creates a new peer registered at the given protocol manager.
-func newTestPeer(t *testing.T, name string, version int, handler *serverHandler, shake bool, testCost uint64) (*testPeer, <-chan error) {
-	// Create a message pipe to communicate through
-	app, net := p2p.MsgPipe()
-
-	// Generate a random id and create the peer
-	var id enode.ID
-	rand.Read(id[:])
-	peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
+// handshakeWithServer executes the handshake with the remote server peer.
+func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID) {
+	// It only works for the simulated client peer
+	if p.cpeer == nil {
+		t.Fatal("handshake for client peer only")
+	}
+	var sendList keyValueList
+	sendList = sendList.add("protocolVersion", uint64(p.cpeer.version))
+	sendList = sendList.add("networkId", uint64(NetworkId))
+	sendList = sendList.add("headTd", td)
+	sendList = sendList.add("headHash", head)
+	sendList = sendList.add("headNum", headNum)
+	sendList = sendList.add("genesisHash", genesis)
+	if p.cpeer.version >= lpv4 {
+		sendList = sendList.add("forkID", &forkID)
+	}
+	if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil {
+		t.Fatalf("status recv: %v", err)
+	}
+	if err := p2p.Send(p.app, StatusMsg, sendList); err != nil {
+		t.Fatalf("status send: %v", err)
+	}
+}
 
-	// Start the peer on a new thread
-	errCh := make(chan error, 1)
-	go func() {
-		select {
-		case <-handler.closeCh:
-			errCh <- p2p.DiscQuitting
-		case errCh <- handler.handle(peer):
-		}
-	}()
-	tp := &testPeer{
-		app:   app,
-		net:   net,
-		cpeer: peer,
+// handshakeWithClient executes the handshake with the remote client peer.
+func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList, recentTxLookup uint64) {
+	// It only works for the simulated client peer
+	if p.speer == nil {
+		t.Fatal("handshake for server peer only")
 	}
-	// Execute any implicitly requested handshakes and return
-	if shake {
-		// Customize the cost table if required.
-		if testCost != 0 {
-			handler.server.costTracker.testCostList = testCostList(testCost)
-		}
-		var (
-			genesis = handler.blockchain.Genesis()
-			head    = handler.blockchain.CurrentHeader()
-			td      = handler.blockchain.GetTd(head.Hash(), head.Number.Uint64())
-		)
-		tp.handshake(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), testCostList(testCost))
+	var sendList keyValueList
+	sendList = sendList.add("protocolVersion", uint64(p.speer.version))
+	sendList = sendList.add("networkId", uint64(NetworkId))
+	sendList = sendList.add("headTd", td)
+	sendList = sendList.add("headHash", head)
+	sendList = sendList.add("headNum", headNum)
+	sendList = sendList.add("genesisHash", genesis)
+	sendList = sendList.add("serveHeaders", nil)
+	sendList = sendList.add("serveChainSince", uint64(0))
+	sendList = sendList.add("serveStateSince", uint64(0))
+	sendList = sendList.add("serveRecentState", uint64(core.TriesInMemory-4))
+	sendList = sendList.add("txRelay", nil)
+	sendList = sendList.add("flowControl/BL", testBufLimit)
+	sendList = sendList.add("flowControl/MRR", testBufRecharge)
+	sendList = sendList.add("flowControl/MRC", costList)
+	if p.speer.version >= lpv4 {
+		sendList = sendList.add("forkID", &forkID)
+		sendList = sendList.add("recentTxLookup", recentTxLookup)
+	}
+	if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil {
+		t.Fatalf("status recv: %v", err)
+	}
+	if err := p2p.Send(p.app, StatusMsg, sendList); err != nil {
+		t.Fatalf("status send: %v", err)
 	}
-	return tp, errCh
 }
 
 // close terminates the local side of the peer, notifying the remote protocol
@@ -394,42 +441,9 @@ func newTestPeerPair(name string, version int, server *serverHandler, client *cl
 	return &testPeer{cpeer: peer1, net: net, app: app}, &testPeer{speer: peer2, net: app, app: net}, nil
 }
 
-// handshake simulates a trivial handshake that expects the same state from the
-// remote side as we are simulating locally.
-func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, costList RequestCostList) {
-	var expList keyValueList
-	expList = expList.add("protocolVersion", uint64(p.cpeer.version))
-	expList = expList.add("networkId", uint64(NetworkId))
-	expList = expList.add("headTd", td)
-	expList = expList.add("headHash", head)
-	expList = expList.add("headNum", headNum)
-	expList = expList.add("genesisHash", genesis)
-	sendList := make(keyValueList, len(expList))
-	copy(sendList, expList)
-	expList = expList.add("serveHeaders", nil)
-	expList = expList.add("serveChainSince", uint64(0))
-	expList = expList.add("serveStateSince", uint64(0))
-	expList = expList.add("serveRecentState", uint64(core.TriesInMemory-4))
-	expList = expList.add("txRelay", nil)
-	expList = expList.add("flowControl/BL", testBufLimit)
-	expList = expList.add("flowControl/MRR", testBufRecharge)
-	expList = expList.add("flowControl/MRC", costList)
-
-	if err := p2p.ExpectMsg(p.app, StatusMsg, expList); err != nil {
-		t.Fatalf("status recv: %v", err)
-	}
-	if err := p2p.Send(p.app, StatusMsg, sendList); err != nil {
-		t.Fatalf("status send: %v", err)
-	}
-	p.cpeer.fcParams = flowcontrol.ServerParams{
-		BufLimit:    testBufLimit,
-		MinRecharge: testBufRecharge,
-	}
-}
-
 type indexerCallback func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer)
 
-// testClient represents a client for testing with necessary auxiliary fields.
+// testClient represents a client object for testing with necessary auxiliary fields.
 type testClient struct {
 	clock   mclock.Clock
 	db      ethdb.Database
@@ -441,7 +455,58 @@ type testClient struct {
 	bloomTrieIndexer *core.ChainIndexer
 }
 
-// testServer represents a server for testing with necessary auxiliary fields.
+// newRawPeer creates a new server peer connects to the server and do the handshake.
+func (client *testClient) newRawPeer(t *testing.T, name string, version int, recentTxLookup uint64) (*testPeer, func(), <-chan error) {
+	// Create a message pipe to communicate through
+	app, net := p2p.MsgPipe()
+
+	// Generate a random id and create the peer
+	var id enode.ID
+	rand.Read(id[:])
+	peer := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net)
+
+	// Start the peer on a new thread
+	errCh := make(chan error, 1)
+	go func() {
+		select {
+		case <-client.handler.closeCh:
+			errCh <- p2p.DiscQuitting
+		case errCh <- client.handler.handle(peer):
+		}
+	}()
+	tp := &testPeer{
+		app:   app,
+		net:   net,
+		speer: peer,
+	}
+	var (
+		genesis = client.handler.backend.blockchain.Genesis()
+		head    = client.handler.backend.blockchain.CurrentHeader()
+		td      = client.handler.backend.blockchain.GetTd(head.Hash(), head.Number.Uint64())
+	)
+	forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64())
+	tp.handshakeWithClient(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(0), recentTxLookup) // disable flow control by default
+
+	// Ensure the connection is established or exits when any error occurs
+	for {
+		select {
+		case <-errCh:
+			return nil, nil, nil
+		default:
+		}
+		if atomic.LoadUint32(&peer.serving) == 1 {
+			break
+		}
+		time.Sleep(50 * time.Millisecond)
+	}
+	closePeer := func() {
+		tp.speer.close()
+		tp.close()
+	}
+	return tp, closePeer, errCh
+}
+
+// testServer represents a server object for testing with necessary auxiliary fields.
 type testServer struct {
 	clock   mclock.Clock
 	backend *backends.SimulatedBackend
@@ -454,95 +519,115 @@ type testServer struct {
 	bloomTrieIndexer *core.ChainIndexer
 }
 
-func newServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, simClock bool, newPeer bool, testCost uint64) (*testServer, func()) {
-	db := rawdb.NewMemoryDatabase()
-	indexers := testIndexers(db, nil, light.TestServerIndexerConfig)
+// newRawPeer creates a new client peer connects to the server and do the handshake.
+func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*testPeer, func(), <-chan error) {
+	// Create a message pipe to communicate through
+	app, net := p2p.MsgPipe()
 
-	var clock mclock.Clock = &mclock.System{}
-	if simClock {
-		clock = &mclock.Simulated{}
-	}
-	handler, b := newTestServerHandler(blocks, indexers, db, newClientPeerSet(), clock)
+	// Generate a random id and create the peer
+	var id enode.ID
+	rand.Read(id[:])
+	peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net)
 
-	var peer *testPeer
-	if newPeer {
-		peer, _ = newTestPeer(t, "peer", protocol, handler, true, testCost)
+	// Start the peer on a new thread
+	errCh := make(chan error, 1)
+	go func() {
+		select {
+		case <-server.handler.closeCh:
+			errCh <- p2p.DiscQuitting
+		case errCh <- server.handler.handle(peer):
+		}
+	}()
+	tp := &testPeer{
+		app:   app,
+		net:   net,
+		cpeer: peer,
 	}
+	var (
+		genesis = server.handler.blockchain.Genesis()
+		head    = server.handler.blockchain.CurrentHeader()
+		td      = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64())
+	)
+	forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64())
+	tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID)
 
-	cIndexer, bIndexer, btIndexer := indexers[0], indexers[1], indexers[2]
-	cIndexer.Start(handler.blockchain)
-	bIndexer.Start(handler.blockchain)
-
-	// Wait until indexers generate enough index data.
-	if callback != nil {
-		callback(cIndexer, bIndexer, btIndexer)
-	}
-	server := &testServer{
-		clock:            clock,
-		backend:          b,
-		db:               db,
-		peer:             peer,
-		handler:          handler,
-		chtIndexer:       cIndexer,
-		bloomIndexer:     bIndexer,
-		bloomTrieIndexer: btIndexer,
-	}
-	teardown := func() {
-		if newPeer {
-			peer.close()
-			peer.cpeer.close()
-			b.Close()
+	// Ensure the connection is established or exits when any error occurs
+	for {
+		select {
+		case <-errCh:
+			return nil, nil, nil
+		default:
+		}
+		if atomic.LoadUint32(&peer.serving) == 1 {
+			break
 		}
-		cIndexer.Close()
-		bIndexer.Close()
+		time.Sleep(50 * time.Millisecond)
+	}
+	closePeer := func() {
+		tp.cpeer.close()
+		tp.close()
 	}
-	return server, teardown
+	return tp, closePeer, errCh
 }
 
-func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexerCallback, ulcServers []string, ulcFraction int, simClock bool, connect bool) (*testServer, *testClient, func()) {
-	sdb, cdb := rawdb.NewMemoryDatabase(), rawdb.NewMemoryDatabase()
-	speers, cpeers := newServerPeerSet(), newClientPeerSet()
+// testnetConfig wraps all the configurations for testing network.
+type testnetConfig struct {
+	blocks      int
+	protocol    int
+	indexFn     indexerCallback
+	ulcServers  []string
+	ulcFraction int
+	simClock    bool
+	connect     bool
+	nopruning   bool
+}
 
+func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testClient, func()) {
+	var (
+		sdb    = rawdb.NewMemoryDatabase()
+		cdb    = rawdb.NewMemoryDatabase()
+		speers = newServerPeerSet()
+	)
 	var clock mclock.Clock = &mclock.System{}
-	if simClock {
+	if config.simClock {
 		clock = &mclock.Simulated{}
 	}
 	dist := newRequestDistributor(speers, clock)
-	rm := newRetrieveManager(speers, dist, nil)
-	odr := NewLesOdr(cdb, light.TestClientIndexerConfig, rm)
+	rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 })
+	odr := NewLesOdr(cdb, light.TestClientIndexerConfig, speers, rm)
 
-	sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig)
-	cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig)
+	sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true)
+	cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, config.nopruning)
 
 	scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2]
 	ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2]
 	odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer)
 
-	server, b := newTestServerHandler(blocks, sindexers, sdb, cpeers, clock)
-	client := newTestClientHandler(b, odr, cIndexers, cdb, speers, ulcServers, ulcFraction)
+	server, b, serverClose := newTestServerHandler(config.blocks, sindexers, sdb, clock)
+	client, clientClose := newTestClientHandler(b, odr, cIndexers, cdb, speers, config.ulcServers, config.ulcFraction)
 
 	scIndexer.Start(server.blockchain)
 	sbIndexer.Start(server.blockchain)
 	ccIndexer.Start(client.backend.blockchain)
 	cbIndexer.Start(client.backend.blockchain)
 
-	if callback != nil {
-		callback(scIndexer, sbIndexer, sbtIndexer)
+	if config.indexFn != nil {
+		config.indexFn(scIndexer, sbIndexer, sbtIndexer)
 	}
 	var (
 		err          error
 		speer, cpeer *testPeer
 	)
-	if connect {
+	if config.connect {
 		done := make(chan struct{})
-		client.syncDone = func() { close(done) }
-		cpeer, speer, err = newTestPeerPair("peer", protocol, server, client)
+		client.syncEnd = func(_ *types.Header) { close(done) }
+		cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client)
 		if err != nil {
 			t.Fatalf("Failed to connect testing peers %v", err)
 		}
 		select {
 		case <-done:
-		case <-time.After(3 * time.Second):
+		case <-time.After(10 * time.Second):
 			t.Fatal("test peer did not connect and sync within 3s")
 		}
 	}
@@ -566,7 +651,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer
 		bloomTrieIndexer: cbtIndexer,
 	}
 	teardown := func() {
-		if connect {
+		if config.connect {
 			speer.close()
 			cpeer.close()
 			cpeer.cpeer.close()
@@ -576,7 +661,18 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer
 		cbIndexer.Close()
 		scIndexer.Close()
 		sbIndexer.Close()
+		dist.close()
+		serverClose()
 		b.Close()
+		clientClose()
 	}
 	return s, c, teardown
 }
+
+// NewFuzzerPeer creates a client peer for test purposes, and also returns
+// a function to close the peer: this is needed to avoid goroutine leaks in the
+// exec queue.
+func NewFuzzerPeer(version int) (p *clientPeer, closer func()) {
+	p = newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil)
+	return p, func() { p.peerCommons.close() }
+}
diff --git a/les/txrelay.go b/les/txrelay.go
index 4f6c15025e..9d29b2f234 100644
--- a/les/txrelay.go
+++ b/les/txrelay.go
@@ -25,17 +25,12 @@ import (
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
-type ltrInfo struct {
-	tx     *types.Transaction
-	sentTo map[*serverPeer]struct{}
-}
-
 type lesTxRelay struct {
-	txSent       map[common.Hash]*ltrInfo
+	txSent       map[common.Hash]*types.Transaction
 	txPending    map[common.Hash]struct{}
 	peerList     []*serverPeer
 	peerStartPos int
-	lock         sync.RWMutex
+	lock         sync.Mutex
 	stop         chan struct{}
 
 	retriever *retrieveManager
@@ -43,7 +38,7 @@ type lesTxRelay struct {
 
 func newLesTxRelay(ps *serverPeerSet, retriever *retrieveManager) *lesTxRelay {
 	r := &lesTxRelay{
-		txSent:    make(map[common.Hash]*ltrInfo),
+		txSent:    make(map[common.Hash]*types.Transaction),
 		txPending: make(map[common.Hash]struct{}),
 		retriever: retriever,
 		stop:      make(chan struct{}),
@@ -80,8 +75,7 @@ func (ltrx *lesTxRelay) unregisterPeer(p *serverPeer) {
 	}
 }
 
-// send sends a list of transactions to at most a given number of peers at
-// once, never resending any particular transaction to the same peer twice
+// send sends a list of transactions to at most a given number of peers.
 func (ltrx *lesTxRelay) send(txs types.Transactions, count int) {
 	sendTo := make(map[*serverPeer]types.Transactions)
 
@@ -92,26 +86,18 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) {
 
 	for _, tx := range txs {
 		hash := tx.Hash()
-		ltr, ok := ltrx.txSent[hash]
+		_, ok := ltrx.txSent[hash]
 		if !ok {
-			ltr = <rInfo{
-				tx:     tx,
-				sentTo: make(map[*serverPeer]struct{}),
-			}
-			ltrx.txSent[hash] = ltr
+			ltrx.txSent[hash] = tx
 			ltrx.txPending[hash] = struct{}{}
 		}
-
 		if len(ltrx.peerList) > 0 {
 			cnt := count
 			pos := ltrx.peerStartPos
 			for {
 				peer := ltrx.peerList[pos]
-				if _, ok := ltr.sentTo[peer]; !ok {
-					sendTo[peer] = append(sendTo[peer], tx)
-					ltr.sentTo[peer] = struct{}{}
-					cnt--
-				}
+				sendTo[peer] = append(sendTo[peer], tx)
+				cnt--
 				if cnt == 0 {
 					break // sent it to the desired number of peers
 				}
@@ -174,7 +160,7 @@ func (ltrx *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback
 		txs := make(types.Transactions, len(ltrx.txPending))
 		i := 0
 		for hash := range ltrx.txPending {
-			txs[i] = ltrx.txSent[hash].tx
+			txs[i] = ltrx.txSent[hash]
 			i++
 		}
 		ltrx.send(txs, 1)
diff --git a/les/ulc_test.go b/les/ulc_test.go
index 273c63e4bd..d7308fa593 100644
--- a/les/ulc_test.go
+++ b/les/ulc_test.go
@@ -126,7 +126,12 @@ func connect(server *serverHandler, serverId enode.ID, client *clientHandler, pr
 
 // newTestServerPeer creates server peer.
 func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *enode.Node, func()) {
-	s, teardown := newServerEnv(t, blocks, protocol, nil, false, false, 0)
+	netconfig := testnetConfig{
+		blocks:    blocks,
+		protocol:  protocol,
+		nopruning: true,
+	}
+	s, _, teardown := newClientServerEnv(t, netconfig)
 	key, err := crypto.GenerateKey()
 	if err != nil {
 		t.Fatal("generate key err:", err)
@@ -138,6 +143,12 @@ func newTestServerPeer(t *testing.T, blocks int, protocol int) (*testServer, *en
 
 // newTestLightPeer creates node with light sync mode
 func newTestLightPeer(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) {
-	_, c, teardown := newClientServerEnv(t, 0, protocol, nil, ulcServers, ulcFraction, false, false)
+	netconfig := testnetConfig{
+		protocol:    protocol,
+		ulcServers:  ulcServers,
+		ulcFraction: ulcFraction,
+		nopruning:   true,
+	}
+	_, c, teardown := newClientServerEnv(t, netconfig)
 	return c, teardown
 }
diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go
index 85f9b88b7b..3fd52616fa 100644
--- a/les/utils/expiredvalue.go
+++ b/les/utils/expiredvalue.go
@@ -18,6 +18,7 @@ package utils
 
 import (
 	"math"
+	"sync"
 
 	"github.com/ethereum/go-ethereum/common/mclock"
 )
@@ -63,14 +64,7 @@ func ExpFactor(logOffset Fixed64) ExpirationFactor {
 // Value calculates the expired value based on a floating point base and integer
 // power-of-2 exponent. This function should be used by multi-value expired structures.
 func (e ExpirationFactor) Value(base float64, exp uint64) float64 {
-	res := base / e.Factor
-	if exp > e.Exp {
-		res *= float64(uint64(1) << (exp - e.Exp))
-	}
-	if exp < e.Exp {
-		res /= float64(uint64(1) << (e.Exp - exp))
-	}
-	return res
+	return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp)))
 }
 
 // value calculates the value at the given moment.
@@ -92,10 +86,15 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 {
 		e.Exp = integer
 	}
 	if base >= 0 || uint64(-base) <= e.Base {
-		// This is a temporary fix to circumvent a golang
-		// uint conversion issue on arm64, which needs to
-		// be investigated further. FIXME
-		e.Base = uint64(int64(e.Base) + int64(base))
+		// The conversion from negative float64 to
+		// uint64 is undefined in golang, and doesn't
+		// work with ARMv8. More details at:
+		// https://github.com/golang/go/issues/43047
+		if base >= 0 {
+			e.Base += uint64(base)
+		} else {
+			e.Base -= uint64(-base)
+		}
 		return amount
 	}
 	net := int64(-float64(e.Base) / factor)
@@ -131,12 +130,69 @@ func (e *ExpiredValue) SubExp(a ExpiredValue) {
 	}
 }
 
+// IsZero returns true if the value is zero
+func (e *ExpiredValue) IsZero() bool {
+	return e.Base == 0
+}
+
+// LinearExpiredValue is very similar with the expiredValue which the value
+// will continuously expired. But the different part is it's expired linearly.
+type LinearExpiredValue struct {
+	Offset uint64         // The latest time offset
+	Val    uint64         // The remaining value, can never be negative
+	Rate   mclock.AbsTime `rlp:"-"` // Expiration rate(by nanosecond), will ignored by RLP
+}
+
+// value calculates the value at the given moment. This function always has the
+// assumption that the given timestamp shouldn't less than the recorded one.
+func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 {
+	offset := uint64(now / e.Rate)
+	if e.Offset < offset {
+		diff := offset - e.Offset
+		if e.Val >= diff {
+			e.Val -= diff
+		} else {
+			e.Val = 0
+		}
+	}
+	return e.Val
+}
+
+// add adds a signed value at the given moment. This function always has the
+// assumption that the given timestamp shouldn't less than the recorded one.
+func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 {
+	offset := uint64(now / e.Rate)
+	if e.Offset < offset {
+		diff := offset - e.Offset
+		if e.Val >= diff {
+			e.Val -= diff
+		} else {
+			e.Val = 0
+		}
+		e.Offset = offset
+	}
+	if amount < 0 && uint64(-amount) > e.Val {
+		e.Val = 0
+	} else {
+		e.Val = uint64(int64(e.Val) + amount)
+	}
+	return e.Val
+}
+
+// ValueExpirer controls value expiration rate
+type ValueExpirer interface {
+	SetRate(now mclock.AbsTime, rate float64)
+	SetLogOffset(now mclock.AbsTime, logOffset Fixed64)
+	LogOffset(now mclock.AbsTime) Fixed64
+}
+
 // Expirer changes logOffset with a linear rate which can be changed during operation.
 // It is not thread safe, if access by multiple goroutines is needed then it should be
 // encapsulated into a locked structure.
 // Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset
 // is thread safe.
 type Expirer struct {
+	lock       sync.RWMutex
 	logOffset  Fixed64
 	rate       float64
 	lastUpdate mclock.AbsTime
@@ -145,6 +201,9 @@ type Expirer struct {
 // SetRate changes the expiration rate which is the inverse of the time constant in
 // nanoseconds.
 func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) {
+	e.lock.Lock()
+	defer e.lock.Unlock()
+
 	dt := now - e.lastUpdate
 	if dt > 0 {
 		e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate)
@@ -155,12 +214,18 @@ func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) {
 
 // SetLogOffset sets logOffset instantly.
 func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) {
+	e.lock.Lock()
+	defer e.lock.Unlock()
+
 	e.lastUpdate = now
 	e.logOffset = logOffset
 }
 
 // LogOffset returns the current logarithmic offset.
 func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 {
+	e.lock.RLock()
+	defer e.lock.RUnlock()
+
 	dt := now - e.lastUpdate
 	if dt <= 0 {
 		return e.logOffset
diff --git a/les/utils/expiredvalue_test.go b/les/utils/expiredvalue_test.go
index fa22d58274..1c751d8cc6 100644
--- a/les/utils/expiredvalue_test.go
+++ b/les/utils/expiredvalue_test.go
@@ -18,6 +18,8 @@ package utils
 
 import (
 	"testing"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
 )
 
 func TestValueExpiration(t *testing.T) {
@@ -116,3 +118,78 @@ func TestExpiredValueSubtraction(t *testing.T) {
 		}
 	}
 }
+
+func TestLinearExpiredValue(t *testing.T) {
+	var cases = []struct {
+		value  LinearExpiredValue
+		now    mclock.AbsTime
+		expect uint64
+	}{
+		{LinearExpiredValue{
+			Offset: 0,
+			Val:    0,
+			Rate:   mclock.AbsTime(1),
+		}, 0, 0},
+
+		{LinearExpiredValue{
+			Offset: 1,
+			Val:    1,
+			Rate:   mclock.AbsTime(1),
+		}, 0, 1},
+
+		{LinearExpiredValue{
+			Offset: 1,
+			Val:    1,
+			Rate:   mclock.AbsTime(1),
+		}, mclock.AbsTime(2), 0},
+
+		{LinearExpiredValue{
+			Offset: 1,
+			Val:    1,
+			Rate:   mclock.AbsTime(1),
+		}, mclock.AbsTime(3), 0},
+	}
+	for _, c := range cases {
+		if value := c.value.Value(c.now); value != c.expect {
+			t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value)
+		}
+	}
+}
+
+func TestLinearExpiredAddition(t *testing.T) {
+	var cases = []struct {
+		value  LinearExpiredValue
+		amount int64
+		now    mclock.AbsTime
+		expect uint64
+	}{
+		{LinearExpiredValue{
+			Offset: 0,
+			Val:    0,
+			Rate:   mclock.AbsTime(1),
+		}, -1, 0, 0},
+
+		{LinearExpiredValue{
+			Offset: 1,
+			Val:    1,
+			Rate:   mclock.AbsTime(1),
+		}, -1, 0, 0},
+
+		{LinearExpiredValue{
+			Offset: 1,
+			Val:    2,
+			Rate:   mclock.AbsTime(1),
+		}, -1, mclock.AbsTime(2), 0},
+
+		{LinearExpiredValue{
+			Offset: 1,
+			Val:    2,
+			Rate:   mclock.AbsTime(1),
+		}, -2, mclock.AbsTime(2), 0},
+	}
+	for _, c := range cases {
+		if value := c.value.Add(c.amount, c.now); value != c.expect {
+			t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value)
+		}
+	}
+}
diff --git a/les/utils/limiter.go b/les/utils/limiter.go
new file mode 100644
index 0000000000..0cc2d7b262
--- /dev/null
+++ b/les/utils/limiter.go
@@ -0,0 +1,405 @@
+// 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 .
+
+package utils
+
+import (
+	"sort"
+	"sync"
+
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+const maxSelectionWeight = 1000000000 // maximum selection weight of each individual node/address group
+
+// Limiter protects a network request serving mechanism from denial-of-service attacks.
+// It limits the total amount of resources used for serving requests while ensuring that
+// the most valuable connections always have a reasonable chance of being served.
+type Limiter struct {
+	lock sync.Mutex
+	cond *sync.Cond
+	quit bool
+
+	nodes                          map[enode.ID]*nodeQueue
+	addresses                      map[string]*addressGroup
+	addressSelect, valueSelect     *WeightedRandomSelect
+	maxValue                       float64
+	maxCost, sumCost, sumCostLimit uint
+	selectAddressNext              bool
+}
+
+// nodeQueue represents queued requests coming from a single node ID
+type nodeQueue struct {
+	queue                   []request // always nil if penaltyCost != 0
+	id                      enode.ID
+	address                 string
+	value                   float64
+	flatWeight, valueWeight uint64 // current selection weights in the address/value selectors
+	sumCost                 uint   // summed cost of requests queued by the node
+	penaltyCost             uint   // cumulative cost of dropped requests since last processed request
+	groupIndex              int
+}
+
+// addressGroup is a group of node IDs that have sent their last requests from the same
+// network address
+type addressGroup struct {
+	nodes                      []*nodeQueue
+	nodeSelect                 *WeightedRandomSelect
+	sumFlatWeight, groupWeight uint64
+}
+
+// request represents an incoming request scheduled for processing
+type request struct {
+	process chan chan struct{}
+	cost    uint
+}
+
+// flatWeight distributes weights equally between each active network address
+func flatWeight(item interface{}) uint64 { return item.(*nodeQueue).flatWeight }
+
+// add adds the node queue to the address group. It is the caller's responsibility to
+// add the address group to the address map and the address selector if it wasn't
+// there before.
+func (ag *addressGroup) add(nq *nodeQueue) {
+	if nq.groupIndex != -1 {
+		panic("added node queue is already in an address group")
+	}
+	l := len(ag.nodes)
+	nq.groupIndex = l
+	ag.nodes = append(ag.nodes, nq)
+	ag.sumFlatWeight += nq.flatWeight
+	ag.groupWeight = ag.sumFlatWeight / uint64(l+1)
+	ag.nodeSelect.Update(ag.nodes[l])
+}
+
+// update updates the selection weight of the node queue inside the address group.
+// It is the caller's responsibility to update the group's selection weight in the
+// address selector.
+func (ag *addressGroup) update(nq *nodeQueue, weight uint64) {
+	if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq {
+		panic("updated node queue is not in this address group")
+	}
+	ag.sumFlatWeight += weight - nq.flatWeight
+	nq.flatWeight = weight
+	ag.groupWeight = ag.sumFlatWeight / uint64(len(ag.nodes))
+	ag.nodeSelect.Update(nq)
+}
+
+// remove removes the node queue from the address group. It is the caller's responsibility
+// to remove the address group from the address map if it is empty.
+func (ag *addressGroup) remove(nq *nodeQueue) {
+	if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq {
+		panic("removed node queue is not in this address group")
+	}
+
+	l := len(ag.nodes) - 1
+	if nq.groupIndex != l {
+		ag.nodes[nq.groupIndex] = ag.nodes[l]
+		ag.nodes[nq.groupIndex].groupIndex = nq.groupIndex
+	}
+	nq.groupIndex = -1
+	ag.nodes = ag.nodes[:l]
+	ag.sumFlatWeight -= nq.flatWeight
+	if l >= 1 {
+		ag.groupWeight = ag.sumFlatWeight / uint64(l)
+	} else {
+		ag.groupWeight = 0
+	}
+	ag.nodeSelect.Remove(nq)
+}
+
+// choose selects one of the node queues belonging to the address group
+func (ag *addressGroup) choose() *nodeQueue {
+	return ag.nodeSelect.Choose().(*nodeQueue)
+}
+
+// NewLimiter creates a new Limiter
+func NewLimiter(sumCostLimit uint) *Limiter {
+	l := &Limiter{
+		addressSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*addressGroup).groupWeight }),
+		valueSelect:   NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*nodeQueue).valueWeight }),
+		nodes:         make(map[enode.ID]*nodeQueue),
+		addresses:     make(map[string]*addressGroup),
+		sumCostLimit:  sumCostLimit,
+	}
+	l.cond = sync.NewCond(&l.lock)
+	go l.processLoop()
+	return l
+}
+
+// selectionWeights calculates the selection weights of a node for both the address and
+// the value selector. The selection weight depends on the next request cost or the
+// summed cost of recently dropped requests.
+func (l *Limiter) selectionWeights(reqCost uint, value float64) (flatWeight, valueWeight uint64) {
+	if value > l.maxValue {
+		l.maxValue = value
+	}
+	if value > 0 {
+		// normalize value to <= 1
+		value /= l.maxValue
+	}
+	if reqCost > l.maxCost {
+		l.maxCost = reqCost
+	}
+	relCost := float64(reqCost) / float64(l.maxCost)
+	var f float64
+	if relCost <= 0.001 {
+		f = 1
+	} else {
+		f = 0.001 / relCost
+	}
+	f *= maxSelectionWeight
+	flatWeight, valueWeight = uint64(f), uint64(f*value)
+	if flatWeight == 0 {
+		flatWeight = 1
+	}
+	return
+}
+
+// Add adds a new request to the node queue belonging to the given id. Value belongs
+// to the requesting node. A higher value gives the request a higher chance of being
+// served quickly in case of heavy load or a DDoS attack. Cost is a rough estimate
+// of the serving cost of the request. A lower cost also gives the request a
+// better chance.
+func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) chan chan struct{} {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+
+	process := make(chan chan struct{}, 1)
+	if l.quit {
+		close(process)
+		return process
+	}
+	if reqCost == 0 {
+		reqCost = 1
+	}
+	if nq, ok := l.nodes[id]; ok {
+		if nq.queue != nil {
+			nq.queue = append(nq.queue, request{process, reqCost})
+			nq.sumCost += reqCost
+			nq.value = value
+			if address != nq.address {
+				// known id sending request from a new address, move to different address group
+				l.removeFromGroup(nq)
+				l.addToGroup(nq, address)
+			}
+		} else {
+			// already waiting on a penalty, just add to the penalty cost and drop the request
+			nq.penaltyCost += reqCost
+			l.update(nq)
+			close(process)
+			return process
+		}
+	} else {
+		nq := &nodeQueue{
+			queue:      []request{{process, reqCost}},
+			id:         id,
+			value:      value,
+			sumCost:    reqCost,
+			groupIndex: -1,
+		}
+		nq.flatWeight, nq.valueWeight = l.selectionWeights(reqCost, value)
+		if len(l.nodes) == 0 {
+			l.cond.Signal()
+		}
+		l.nodes[id] = nq
+		if nq.valueWeight != 0 {
+			l.valueSelect.Update(nq)
+		}
+		l.addToGroup(nq, address)
+	}
+	l.sumCost += reqCost
+	if l.sumCost > l.sumCostLimit {
+		l.dropRequests()
+	}
+	return process
+}
+
+// update updates the selection weights of the node queue
+func (l *Limiter) update(nq *nodeQueue) {
+	var cost uint
+	if nq.queue != nil {
+		cost = nq.queue[0].cost
+	} else {
+		cost = nq.penaltyCost
+	}
+	flatWeight, valueWeight := l.selectionWeights(cost, nq.value)
+	ag := l.addresses[nq.address]
+	ag.update(nq, flatWeight)
+	l.addressSelect.Update(ag)
+	nq.valueWeight = valueWeight
+	l.valueSelect.Update(nq)
+}
+
+// addToGroup adds the node queue to the given address group. The group is created if
+// it does not exist yet.
+func (l *Limiter) addToGroup(nq *nodeQueue, address string) {
+	nq.address = address
+	ag := l.addresses[address]
+	if ag == nil {
+		ag = &addressGroup{nodeSelect: NewWeightedRandomSelect(flatWeight)}
+		l.addresses[address] = ag
+	}
+	ag.add(nq)
+	l.addressSelect.Update(ag)
+}
+
+// removeFromGroup removes the node queue from its address group
+func (l *Limiter) removeFromGroup(nq *nodeQueue) {
+	ag := l.addresses[nq.address]
+	ag.remove(nq)
+	if len(ag.nodes) == 0 {
+		delete(l.addresses, nq.address)
+	}
+	l.addressSelect.Update(ag)
+}
+
+// remove removes the node queue from its address group, the nodes map and the value
+// selector
+func (l *Limiter) remove(nq *nodeQueue) {
+	l.removeFromGroup(nq)
+	if nq.valueWeight != 0 {
+		l.valueSelect.Remove(nq)
+	}
+	delete(l.nodes, nq.id)
+}
+
+// choose selects the next node queue to process.
+func (l *Limiter) choose() *nodeQueue {
+	if l.valueSelect.IsEmpty() || l.selectAddressNext {
+		if ag, ok := l.addressSelect.Choose().(*addressGroup); ok {
+			l.selectAddressNext = false
+			return ag.choose()
+		}
+	}
+	nq, _ := l.valueSelect.Choose().(*nodeQueue)
+	l.selectAddressNext = true
+	return nq
+}
+
+// processLoop processes requests sequentially
+func (l *Limiter) processLoop() {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+
+	for {
+		if l.quit {
+			for _, nq := range l.nodes {
+				for _, request := range nq.queue {
+					close(request.process)
+				}
+			}
+			return
+		}
+		nq := l.choose()
+		if nq == nil {
+			l.cond.Wait()
+			continue
+		}
+		if nq.queue != nil {
+			request := nq.queue[0]
+			nq.queue = nq.queue[1:]
+			nq.sumCost -= request.cost
+			l.sumCost -= request.cost
+			l.lock.Unlock()
+			ch := make(chan struct{})
+			request.process <- ch
+			<-ch
+			l.lock.Lock()
+			if len(nq.queue) > 0 {
+				l.update(nq)
+			} else {
+				l.remove(nq)
+			}
+		} else {
+			// penalized queue removed, next request will be added to a clean queue
+			l.remove(nq)
+		}
+	}
+}
+
+// Stop stops the processing loop. All queued and future requests are rejected.
+func (l *Limiter) Stop() {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+
+	l.quit = true
+	l.cond.Signal()
+}
+
+type (
+	dropList     []dropListItem
+	dropListItem struct {
+		nq       *nodeQueue
+		priority float64
+	}
+)
+
+func (l dropList) Len() int {
+	return len(l)
+}
+
+func (l dropList) Less(i, j int) bool {
+	return l[i].priority < l[j].priority
+}
+
+func (l dropList) Swap(i, j int) {
+	l[i], l[j] = l[j], l[i]
+}
+
+// dropRequests selects the nodes with the highest queued request cost to selection
+// weight ratio and drops their queued request. The empty node queues stay in the
+// selectors with a low selection weight in order to penalize these nodes.
+func (l *Limiter) dropRequests() {
+	var (
+		sumValue float64
+		list     dropList
+	)
+	for _, nq := range l.nodes {
+		sumValue += nq.value
+	}
+	for _, nq := range l.nodes {
+		if nq.sumCost == 0 {
+			continue
+		}
+		w := 1 / float64(len(l.addresses)*len(l.addresses[nq.address].nodes))
+		if sumValue > 0 {
+			w += nq.value / sumValue
+		}
+		list = append(list, dropListItem{
+			nq:       nq,
+			priority: w / float64(nq.sumCost),
+		})
+	}
+	sort.Sort(list)
+	for _, item := range list {
+		for _, request := range item.nq.queue {
+			close(request.process)
+		}
+		// make the queue penalized; no more requests are accepted until the node is
+		// selected based on the penalty cost which is the cumulative cost of all dropped
+		// requests. This ensures that sending excess requests is always penalized
+		// and incentivizes the sender to stop for a while if no replies are received.
+		item.nq.queue = nil
+		item.nq.penaltyCost = item.nq.sumCost
+		l.sumCost -= item.nq.sumCost // penalty costs are not counted in sumCost
+		item.nq.sumCost = 0
+		l.update(item.nq)
+		if l.sumCost <= l.sumCostLimit/2 {
+			return
+		}
+	}
+}
diff --git a/les/utils/limiter_test.go b/les/utils/limiter_test.go
new file mode 100644
index 0000000000..43af3309ab
--- /dev/null
+++ b/les/utils/limiter_test.go
@@ -0,0 +1,206 @@
+// 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 .
+
+package utils
+
+import (
+	"math/rand"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+const (
+	ltTolerance = 0.03
+	ltRounds    = 7
+)
+
+type (
+	ltNode struct {
+		addr, id         int
+		value, exp       float64
+		cost             uint
+		reqRate          float64
+		reqMax, runCount int
+		lastTotalCost    uint
+
+		served, dropped int
+	}
+
+	ltResult struct {
+		node *ltNode
+		ch   chan struct{}
+	}
+
+	limTest struct {
+		limiter            *Limiter
+		results            chan ltResult
+		runCount           int
+		expCost, totalCost uint
+	}
+)
+
+func (lt *limTest) request(n *ltNode) {
+	var (
+		address string
+		id      enode.ID
+	)
+	if n.addr >= 0 {
+		address = string([]byte{byte(n.addr)})
+	} else {
+		var b [32]byte
+		rand.Read(b[:])
+		address = string(b[:])
+	}
+	if n.id >= 0 {
+		id = enode.ID{byte(n.id)}
+	} else {
+		rand.Read(id[:])
+	}
+	lt.runCount++
+	n.runCount++
+	cch := lt.limiter.Add(id, address, n.value, n.cost)
+	go func() {
+		lt.results <- ltResult{n, <-cch}
+	}()
+}
+
+func (lt *limTest) moreRequests(n *ltNode) {
+	maxStart := int(float64(lt.totalCost-n.lastTotalCost) * n.reqRate)
+	if maxStart != 0 {
+		n.lastTotalCost = lt.totalCost
+	}
+	for n.reqMax > n.runCount && maxStart > 0 {
+		lt.request(n)
+		maxStart--
+	}
+}
+
+func (lt *limTest) process() {
+	res := <-lt.results
+	lt.runCount--
+	res.node.runCount--
+	if res.ch != nil {
+		res.node.served++
+		if res.node.exp != 0 {
+			lt.expCost += res.node.cost
+		}
+		lt.totalCost += res.node.cost
+		close(res.ch)
+	} else {
+		res.node.dropped++
+	}
+}
+
+func TestLimiter(t *testing.T) {
+	limTests := [][]*ltNode{
+		{ // one id from an individual address and two ids from a shared address
+			{addr: 0, id: 0, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.5},
+			{addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
+			{addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
+		},
+		{ // varying request costs
+			{addr: 0, id: 0, value: 0, cost: 10, reqRate: 0.2, reqMax: 1, exp: 0.5},
+			{addr: 1, id: 1, value: 0, cost: 3, reqRate: 0.5, reqMax: 1, exp: 0.25},
+			{addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
+		},
+		{ // different request rate
+			{addr: 0, id: 0, value: 0, cost: 1, reqRate: 2, reqMax: 2, exp: 0.5},
+			{addr: 1, id: 1, value: 0, cost: 1, reqRate: 10, reqMax: 10, exp: 0.25},
+			{addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25},
+		},
+		{ // adding value
+			{addr: 0, id: 0, value: 3, cost: 1, reqRate: 1, reqMax: 1, exp: (0.5 + 0.3) / 2},
+			{addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25 / 2},
+			{addr: 1, id: 2, value: 7, cost: 1, reqRate: 1, reqMax: 1, exp: (0.25 + 0.7) / 2},
+		},
+		{ // DoS attack from a single address with a single id
+			{addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 3, id: 3, value: 0, cost: 1, reqRate: 10, reqMax: 1000000000, exp: 0},
+		},
+		{ // DoS attack from a single address with different ids
+			{addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 3, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0},
+		},
+		{ // DDoS attack from different addresses with a single id
+			{addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: -1, id: 3, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0},
+		},
+		{ // DDoS attack from different addresses with different ids
+			{addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333},
+			{addr: -1, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0},
+		},
+	}
+
+	lt := &limTest{
+		limiter: NewLimiter(100),
+		results: make(chan ltResult),
+	}
+	for _, test := range limTests {
+		lt.expCost, lt.totalCost = 0, 0
+		iterCount := 10000
+		for j := 0; j < ltRounds; j++ {
+			// try to reach expected target range in multiple rounds with increasing iteration counts
+			last := j == ltRounds-1
+			for _, n := range test {
+				lt.request(n)
+			}
+			for i := 0; i < iterCount; i++ {
+				lt.process()
+				for _, n := range test {
+					lt.moreRequests(n)
+				}
+			}
+			for lt.runCount > 0 {
+				lt.process()
+			}
+			if spamRatio := 1 - float64(lt.expCost)/float64(lt.totalCost); spamRatio > 0.5*(1+ltTolerance) {
+				t.Errorf("Spam ratio too high (%f)", spamRatio)
+			}
+			fail, success := false, true
+			for _, n := range test {
+				if n.exp != 0 {
+					if n.dropped > 0 {
+						t.Errorf("Dropped %d requests of non-spam node", n.dropped)
+						fail = true
+					}
+					r := float64(n.served) * float64(n.cost) / float64(lt.expCost)
+					if r < n.exp*(1-ltTolerance) || r > n.exp*(1+ltTolerance) {
+						if last {
+							// print error only if the target is still not reached in the last round
+							t.Errorf("Request ratio (%f) does not match expected value (%f)", r, n.exp)
+						}
+						success = false
+					}
+				}
+			}
+			if fail || success {
+				break
+			}
+			// neither failed nor succeeded; try more iterations to reach probability targets
+			iterCount *= 2
+		}
+	}
+	lt.limiter.Stop()
+}
diff --git a/les/utils/timeutils.go b/les/utils/timeutils.go
new file mode 100644
index 0000000000..62a4285d15
--- /dev/null
+++ b/les/utils/timeutils.go
@@ -0,0 +1,69 @@
+// 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 .
+
+package utils
+
+import (
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+)
+
+type UpdateTimer struct {
+	clock     mclock.Clock
+	lock      sync.Mutex
+	last      mclock.AbsTime
+	threshold time.Duration
+}
+
+func NewUpdateTimer(clock mclock.Clock, threshold time.Duration) *UpdateTimer {
+	// We don't accept the update threshold less than 0.
+	if threshold < 0 {
+		return nil
+	}
+	// Don't panic for lazy users
+	if clock == nil {
+		clock = mclock.System{}
+	}
+	return &UpdateTimer{
+		clock:     clock,
+		last:      clock.Now(),
+		threshold: threshold,
+	}
+}
+
+func (t *UpdateTimer) Update(callback func(diff time.Duration) bool) bool {
+	return t.UpdateAt(t.clock.Now(), callback)
+}
+
+func (t *UpdateTimer) UpdateAt(at mclock.AbsTime, callback func(diff time.Duration) bool) bool {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	diff := time.Duration(at - t.last)
+	if diff < 0 {
+		diff = 0
+	}
+	if diff < t.threshold {
+		return false
+	}
+	if callback(diff) {
+		t.last = at
+		return true
+	}
+	return false
+}
diff --git a/les/utils/timeutils_test.go b/les/utils/timeutils_test.go
new file mode 100644
index 0000000000..9f9e1c2dc9
--- /dev/null
+++ b/les/utils/timeutils_test.go
@@ -0,0 +1,47 @@
+// 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 .
+
+package utils
+
+import (
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+)
+
+func TestUpdateTimer(t *testing.T) {
+	timer := NewUpdateTimer(mclock.System{}, -1)
+	if timer != nil {
+		t.Fatalf("Create update timer with negative threshold")
+	}
+	sim := &mclock.Simulated{}
+	timer = NewUpdateTimer(sim, time.Second)
+	if updated := timer.Update(func(diff time.Duration) bool { return true }); updated {
+		t.Fatalf("Update the clock without reaching the threshold")
+	}
+	sim.Run(time.Second)
+	if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated {
+		t.Fatalf("Doesn't update the clock when reaching the threshold")
+	}
+	if updated := timer.UpdateAt(sim.Now()+mclock.AbsTime(time.Second), func(diff time.Duration) bool { return true }); !updated {
+		t.Fatalf("Doesn't update the clock when reaching the threshold")
+	}
+	timer = NewUpdateTimer(sim, 0)
+	if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated {
+		t.Fatalf("Doesn't update the clock without threshold limitaion")
+	}
+}
diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go
index fbf1f37d62..486b00820a 100644
--- a/les/utils/weighted_select.go
+++ b/les/utils/weighted_select.go
@@ -16,28 +16,55 @@
 
 package utils
 
-import "math/rand"
+import (
+	"math"
+	"math/rand"
 
-// wrsItem interface should be implemented by any entries that are to be selected from
-// a WeightedRandomSelect set. Note that recalculating monotonously decreasing item
-// weights on-demand (without constantly calling Update) is allowed
-type wrsItem interface {
-	Weight() int64
+	"github.com/ethereum/go-ethereum/log"
+)
+
+type (
+	// WeightedRandomSelect is capable of weighted random selection from a set of items
+	WeightedRandomSelect struct {
+		root *wrsNode
+		idx  map[WrsItem]int
+		wfn  WeightFn
+	}
+	WrsItem  interface{}
+	WeightFn func(interface{}) uint64
+)
+
+// NewWeightedRandomSelect returns a new WeightedRandomSelect structure
+func NewWeightedRandomSelect(wfn WeightFn) *WeightedRandomSelect {
+	return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[WrsItem]int), wfn: wfn}
 }
 
-// WeightedRandomSelect is capable of weighted random selection from a set of items
-type WeightedRandomSelect struct {
-	root *wrsNode
-	idx  map[wrsItem]int
+// Update updates an item's weight, adds it if it was non-existent or removes it if
+// the new weight is zero. Note that explicitly updating decreasing weights is not necessary.
+func (w *WeightedRandomSelect) Update(item WrsItem) {
+	w.setWeight(item, w.wfn(item))
 }
 
-// NewWeightedRandomSelect returns a new WeightedRandomSelect structure
-func NewWeightedRandomSelect() *WeightedRandomSelect {
-	return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)}
+// Remove removes an item from the set
+func (w *WeightedRandomSelect) Remove(item WrsItem) {
+	w.setWeight(item, 0)
+}
+
+// IsEmpty returns true if the set is empty
+func (w *WeightedRandomSelect) IsEmpty() bool {
+	return w.root.sumCost == 0
 }
 
 // setWeight sets an item's weight to a specific value (removes it if zero)
-func (w *WeightedRandomSelect) setWeight(item wrsItem, weight int64) {
+func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) {
+	if weight > math.MaxInt64-w.root.sumCost {
+		// old weight is still included in sumCost, remove and check again
+		w.setWeight(item, 0)
+		if weight > math.MaxInt64-w.root.sumCost {
+			log.Error("WeightedRandomSelect overflow", "sumCost", w.root.sumCost, "new weight", weight)
+			weight = math.MaxInt64 - w.root.sumCost
+		}
+	}
 	idx, ok := w.idx[item]
 	if ok {
 		w.root.setWeight(idx, weight)
@@ -48,9 +75,9 @@ func (w *WeightedRandomSelect) setWeight(item wrsItem, weight int64) {
 		if weight != 0 {
 			if w.root.itemCnt == w.root.maxItems {
 				// add a new level
-				newRoot := &wrsNode{sumWeight: w.root.sumWeight, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches}
+				newRoot := &wrsNode{sumCost: w.root.sumCost, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches}
 				newRoot.items[0] = w.root
-				newRoot.weights[0] = w.root.sumWeight
+				newRoot.weights[0] = w.root.sumCost
 				w.root = newRoot
 			}
 			w.idx[item] = w.root.insert(item, weight)
@@ -58,33 +85,22 @@ func (w *WeightedRandomSelect) setWeight(item wrsItem, weight int64) {
 	}
 }
 
-// Update updates an item's weight, adds it if it was non-existent or removes it if
-// the new weight is zero. Note that explicitly updating decreasing weights is not necessary.
-func (w *WeightedRandomSelect) Update(item wrsItem) {
-	w.setWeight(item, item.Weight())
-}
-
-// Remove removes an item from the set
-func (w *WeightedRandomSelect) Remove(item wrsItem) {
-	w.setWeight(item, 0)
-}
-
 // Choose randomly selects an item from the set, with a chance proportional to its
 // current weight. If the weight of the chosen element has been decreased since the
 // last stored value, returns it with a newWeight/oldWeight chance, otherwise just
 // updates its weight and selects another one
-func (w *WeightedRandomSelect) Choose() wrsItem {
+func (w *WeightedRandomSelect) Choose() WrsItem {
 	for {
-		if w.root.sumWeight == 0 {
+		if w.root.sumCost == 0 {
 			return nil
 		}
-		val := rand.Int63n(w.root.sumWeight)
+		val := uint64(rand.Int63n(int64(w.root.sumCost)))
 		choice, lastWeight := w.root.choose(val)
-		weight := choice.Weight()
+		weight := w.wfn(choice)
 		if weight != lastWeight {
 			w.setWeight(choice, weight)
 		}
-		if weight >= lastWeight || rand.Int63n(lastWeight) < weight {
+		if weight >= lastWeight || uint64(rand.Int63n(int64(lastWeight))) < weight {
 			return choice
 		}
 	}
@@ -92,16 +108,16 @@ func (w *WeightedRandomSelect) Choose() wrsItem {
 
 const wrsBranches = 8 // max number of branches in the wrsNode tree
 
-// wrsNode is a node of a tree structure that can store wrsItems or further wrsNodes.
+// wrsNode is a node of a tree structure that can store WrsItems or further wrsNodes.
 type wrsNode struct {
 	items                    [wrsBranches]interface{}
-	weights                  [wrsBranches]int64
-	sumWeight                int64
+	weights                  [wrsBranches]uint64
+	sumCost                  uint64
 	level, itemCnt, maxItems int
 }
 
 // insert recursively inserts a new item to the tree and returns the item index
-func (n *wrsNode) insert(item wrsItem, weight int64) int {
+func (n *wrsNode) insert(item WrsItem, weight uint64) int {
 	branch := 0
 	for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) {
 		branch++
@@ -110,7 +126,7 @@ func (n *wrsNode) insert(item wrsItem, weight int64) int {
 		}
 	}
 	n.itemCnt++
-	n.sumWeight += weight
+	n.sumCost += weight
 	n.weights[branch] += weight
 	if n.level == 0 {
 		n.items[branch] = item
@@ -129,12 +145,12 @@ func (n *wrsNode) insert(item wrsItem, weight int64) int {
 
 // setWeight updates the weight of a certain item (which should exist) and returns
 // the change of the last weight value stored in the tree
-func (n *wrsNode) setWeight(idx int, weight int64) int64 {
+func (n *wrsNode) setWeight(idx int, weight uint64) uint64 {
 	if n.level == 0 {
 		oldWeight := n.weights[idx]
 		n.weights[idx] = weight
 		diff := weight - oldWeight
-		n.sumWeight += diff
+		n.sumCost += diff
 		if weight == 0 {
 			n.items[idx] = nil
 			n.itemCnt--
@@ -145,19 +161,19 @@ func (n *wrsNode) setWeight(idx int, weight int64) int64 {
 	branch := idx / branchItems
 	diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight)
 	n.weights[branch] += diff
-	n.sumWeight += diff
+	n.sumCost += diff
 	if weight == 0 {
 		n.itemCnt--
 	}
 	return diff
 }
 
-// Choose recursively selects an item from the tree and returns it along with its weight
-func (n *wrsNode) choose(val int64) (wrsItem, int64) {
+// choose recursively selects an item from the tree and returns it along with its weight
+func (n *wrsNode) choose(val uint64) (WrsItem, uint64) {
 	for i, w := range n.weights {
 		if val < w {
 			if n.level == 0 {
-				return n.items[i].(wrsItem), n.weights[i]
+				return n.items[i].(WrsItem), n.weights[i]
 			}
 			return n.items[i].(*wrsNode).choose(val)
 		}
diff --git a/les/utils/weighted_select_test.go b/les/utils/weighted_select_test.go
index e1969e1a61..3e1c0ad987 100644
--- a/les/utils/weighted_select_test.go
+++ b/les/utils/weighted_select_test.go
@@ -26,17 +26,18 @@ type testWrsItem struct {
 	widx *int
 }
 
-func (t *testWrsItem) Weight() int64 {
+func testWeight(i interface{}) uint64 {
+	t := i.(*testWrsItem)
 	w := *t.widx
 	if w == -1 || w == t.idx {
-		return int64(t.idx + 1)
+		return uint64(t.idx + 1)
 	}
 	return 0
 }
 
 func TestWeightedRandomSelect(t *testing.T) {
 	testFn := func(cnt int) {
-		s := NewWeightedRandomSelect()
+		s := NewWeightedRandomSelect(testWeight)
 		w := -1
 		list := make([]testWrsItem, cnt)
 		for i := range list {
diff --git a/les/lespay/client/api.go b/les/vflux/client/api.go
similarity index 98%
rename from les/lespay/client/api.go
rename to les/vflux/client/api.go
index 5ad6ffd77e..135273ef96 100644
--- a/les/lespay/client/api.go
+++ b/les/vflux/client/api.go
@@ -24,7 +24,7 @@ import (
 	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
-// PrivateClientAPI implements the lespay client side API
+// PrivateClientAPI implements the vflux client side API
 type PrivateClientAPI struct {
 	vt *ValueTracker
 }
diff --git a/les/vflux/client/fillset.go b/les/vflux/client/fillset.go
new file mode 100644
index 0000000000..0da850bcac
--- /dev/null
+++ b/les/vflux/client/fillset.go
@@ -0,0 +1,107 @@
+// 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 .
+
+package client
+
+import (
+	"sync"
+
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+// FillSet tries to read nodes from an input iterator and add them to a node set by
+// setting the specified node state flag(s) until the size of the set reaches the target.
+// Note that other mechanisms (like other FillSet instances reading from different inputs)
+// can also set the same flag(s) and FillSet will always care about the total number of
+// nodes having those flags.
+type FillSet struct {
+	lock          sync.Mutex
+	cond          *sync.Cond
+	ns            *nodestate.NodeStateMachine
+	input         enode.Iterator
+	closed        bool
+	flags         nodestate.Flags
+	count, target int
+}
+
+// NewFillSet creates a new FillSet
+func NewFillSet(ns *nodestate.NodeStateMachine, input enode.Iterator, flags nodestate.Flags) *FillSet {
+	fs := &FillSet{
+		ns:    ns,
+		input: input,
+		flags: flags,
+	}
+	fs.cond = sync.NewCond(&fs.lock)
+
+	ns.SubscribeState(flags, func(n *enode.Node, oldState, newState nodestate.Flags) {
+		fs.lock.Lock()
+		if oldState.Equals(flags) {
+			fs.count--
+		}
+		if newState.Equals(flags) {
+			fs.count++
+		}
+		if fs.target > fs.count {
+			fs.cond.Signal()
+		}
+		fs.lock.Unlock()
+	})
+
+	go fs.readLoop()
+	return fs
+}
+
+// readLoop keeps reading nodes from the input and setting the specified flags for them
+// whenever the node set size is under the current target
+func (fs *FillSet) readLoop() {
+	for {
+		fs.lock.Lock()
+		for fs.target <= fs.count && !fs.closed {
+			fs.cond.Wait()
+		}
+
+		fs.lock.Unlock()
+		if !fs.input.Next() {
+			return
+		}
+		fs.ns.SetState(fs.input.Node(), fs.flags, nodestate.Flags{}, 0)
+	}
+}
+
+// SetTarget sets the current target for node set size. If the previous target was not
+// reached and FillSet was still waiting for the next node from the input then the next
+// incoming node will be added to the set regardless of the target. This ensures that
+// all nodes coming from the input are eventually added to the set.
+func (fs *FillSet) SetTarget(target int) {
+	fs.lock.Lock()
+	defer fs.lock.Unlock()
+
+	fs.target = target
+	if fs.target > fs.count {
+		fs.cond.Signal()
+	}
+}
+
+// Close shuts FillSet down and closes the input iterator
+func (fs *FillSet) Close() {
+	fs.lock.Lock()
+	defer fs.lock.Unlock()
+
+	fs.closed = true
+	fs.input.Close()
+	fs.cond.Signal()
+}
diff --git a/les/vflux/client/fillset_test.go b/les/vflux/client/fillset_test.go
new file mode 100644
index 0000000000..58240682c6
--- /dev/null
+++ b/les/vflux/client/fillset_test.go
@@ -0,0 +1,113 @@
+// 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 .
+
+package client
+
+import (
+	"math/rand"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+type testIter struct {
+	waitCh chan struct{}
+	nodeCh chan *enode.Node
+	node   *enode.Node
+}
+
+func (i *testIter) Next() bool {
+	i.waitCh <- struct{}{}
+	i.node = <-i.nodeCh
+	return i.node != nil
+}
+
+func (i *testIter) Node() *enode.Node {
+	return i.node
+}
+
+func (i *testIter) Close() {}
+
+func (i *testIter) push() {
+	var id enode.ID
+	rand.Read(id[:])
+	i.nodeCh <- enode.SignNull(new(enr.Record), id)
+}
+
+func (i *testIter) waiting(timeout time.Duration) bool {
+	select {
+	case <-i.waitCh:
+		return true
+	case <-time.After(timeout):
+		return false
+	}
+}
+
+func TestFillSet(t *testing.T) {
+	ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup)
+	iter := &testIter{
+		waitCh: make(chan struct{}),
+		nodeCh: make(chan *enode.Node),
+	}
+	fs := NewFillSet(ns, iter, sfTest1)
+	ns.Start()
+
+	expWaiting := func(i int, push bool) {
+		for ; i > 0; i-- {
+			if !iter.waiting(time.Second * 10) {
+				t.Fatalf("FillSet not waiting for new nodes")
+			}
+			if push {
+				iter.push()
+			}
+		}
+	}
+
+	expNotWaiting := func() {
+		if iter.waiting(time.Millisecond * 100) {
+			t.Fatalf("FillSet unexpectedly waiting for new nodes")
+		}
+	}
+
+	expNotWaiting()
+	fs.SetTarget(3)
+	expWaiting(3, true)
+	expNotWaiting()
+	fs.SetTarget(100)
+	expWaiting(2, true)
+	expWaiting(1, false)
+	// lower the target before the previous one has been filled up
+	fs.SetTarget(0)
+	iter.push()
+	expNotWaiting()
+	fs.SetTarget(10)
+	expWaiting(4, true)
+	expNotWaiting()
+	// remove all previosly set flags
+	ns.ForEach(sfTest1, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
+		ns.SetState(node, nodestate.Flags{}, sfTest1, 0)
+	})
+	// now expect FillSet to fill the set up again with 10 new nodes
+	expWaiting(10, true)
+	expNotWaiting()
+
+	fs.Close()
+	ns.Stop()
+}
diff --git a/les/vflux/client/queueiterator.go b/les/vflux/client/queueiterator.go
new file mode 100644
index 0000000000..ad3f8df5bb
--- /dev/null
+++ b/les/vflux/client/queueiterator.go
@@ -0,0 +1,123 @@
+// 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 .
+
+package client
+
+import (
+	"sync"
+
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+// QueueIterator returns nodes from the specified selectable set in the same order as
+// they entered the set.
+type QueueIterator struct {
+	lock sync.Mutex
+	cond *sync.Cond
+
+	ns           *nodestate.NodeStateMachine
+	queue        []*enode.Node
+	nextNode     *enode.Node
+	waitCallback func(bool)
+	fifo, closed bool
+}
+
+// NewQueueIterator creates a new QueueIterator. Nodes are selectable if they have all the required
+// and none of the disabled flags set. When a node is selected the selectedFlag is set which also
+// disables further selectability until it is removed or times out.
+func NewQueueIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags nodestate.Flags, fifo bool, waitCallback func(bool)) *QueueIterator {
+	qi := &QueueIterator{
+		ns:           ns,
+		fifo:         fifo,
+		waitCallback: waitCallback,
+	}
+	qi.cond = sync.NewCond(&qi.lock)
+
+	ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) {
+		oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags)
+		newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags)
+		if newMatch == oldMatch {
+			return
+		}
+
+		qi.lock.Lock()
+		defer qi.lock.Unlock()
+
+		if newMatch {
+			qi.queue = append(qi.queue, n)
+		} else {
+			id := n.ID()
+			for i, qn := range qi.queue {
+				if qn.ID() == id {
+					copy(qi.queue[i:len(qi.queue)-1], qi.queue[i+1:])
+					qi.queue = qi.queue[:len(qi.queue)-1]
+					break
+				}
+			}
+		}
+		qi.cond.Signal()
+	})
+	return qi
+}
+
+// Next moves to the next selectable node.
+func (qi *QueueIterator) Next() bool {
+	qi.lock.Lock()
+	if !qi.closed && len(qi.queue) == 0 {
+		if qi.waitCallback != nil {
+			qi.waitCallback(true)
+		}
+		for !qi.closed && len(qi.queue) == 0 {
+			qi.cond.Wait()
+		}
+		if qi.waitCallback != nil {
+			qi.waitCallback(false)
+		}
+	}
+	if qi.closed {
+		qi.nextNode = nil
+		qi.lock.Unlock()
+		return false
+	}
+	// Move to the next node in queue.
+	if qi.fifo {
+		qi.nextNode = qi.queue[0]
+		copy(qi.queue[:len(qi.queue)-1], qi.queue[1:])
+		qi.queue = qi.queue[:len(qi.queue)-1]
+	} else {
+		qi.nextNode = qi.queue[len(qi.queue)-1]
+		qi.queue = qi.queue[:len(qi.queue)-1]
+	}
+	qi.lock.Unlock()
+	return true
+}
+
+// Close ends the iterator.
+func (qi *QueueIterator) Close() {
+	qi.lock.Lock()
+	qi.closed = true
+	qi.lock.Unlock()
+	qi.cond.Signal()
+}
+
+// Node returns the current node.
+func (qi *QueueIterator) Node() *enode.Node {
+	qi.lock.Lock()
+	defer qi.lock.Unlock()
+
+	return qi.nextNode
+}
diff --git a/les/vflux/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go
new file mode 100644
index 0000000000..400d978e19
--- /dev/null
+++ b/les/vflux/client/queueiterator_test.go
@@ -0,0 +1,95 @@
+// 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 .
+
+package client
+
+import (
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+func testNode(i int) *enode.Node {
+	return enode.SignNull(new(enr.Record), testNodeID(i))
+}
+
+func TestQueueIteratorFIFO(t *testing.T) {
+	testQueueIterator(t, true)
+}
+
+func TestQueueIteratorLIFO(t *testing.T) {
+	testQueueIterator(t, false)
+}
+
+func testQueueIterator(t *testing.T, fifo bool) {
+	ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup)
+	qi := NewQueueIterator(ns, sfTest2, sfTest3.Or(sfTest4), fifo, nil)
+	ns.Start()
+	for i := 1; i <= iterTestNodeCount; i++ {
+		ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0)
+	}
+	next := func() int {
+		ch := make(chan struct{})
+		go func() {
+			qi.Next()
+			close(ch)
+		}()
+		select {
+		case <-ch:
+		case <-time.After(time.Second * 5):
+			t.Fatalf("Iterator.Next() timeout")
+		}
+		node := qi.Node()
+		ns.SetState(node, sfTest4, nodestate.Flags{}, 0)
+		return testNodeIndex(node.ID())
+	}
+	exp := func(i int) {
+		n := next()
+		if n != i {
+			t.Errorf("Wrong item returned by iterator (expected %d, got %d)", i, n)
+		}
+	}
+	explist := func(list []int) {
+		for i := range list {
+			if fifo {
+				exp(list[i])
+			} else {
+				exp(list[len(list)-1-i])
+			}
+		}
+	}
+
+	ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0)
+	explist([]int{1, 2, 3})
+	ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(5), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(5), sfTest3, nodestate.Flags{}, 0)
+	explist([]int{4, 6})
+	ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0)
+	ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0)
+	ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0)
+	ns.SetState(testNode(2), sfTest3, nodestate.Flags{}, 0)
+	ns.SetState(testNode(2), nodestate.Flags{}, sfTest3, 0)
+	explist([]int{1, 3, 2})
+	ns.Stop()
+}
diff --git a/les/lespay/client/requestbasket.go b/les/vflux/client/requestbasket.go
similarity index 100%
rename from les/lespay/client/requestbasket.go
rename to les/vflux/client/requestbasket.go
diff --git a/les/lespay/client/requestbasket_test.go b/les/vflux/client/requestbasket_test.go
similarity index 100%
rename from les/lespay/client/requestbasket_test.go
rename to les/vflux/client/requestbasket_test.go
diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go
new file mode 100644
index 0000000000..cc0254c127
--- /dev/null
+++ b/les/vflux/client/serverpool.go
@@ -0,0 +1,606 @@
+// 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 .
+
+package client
+
+import (
+	"errors"
+	"math/rand"
+	"reflect"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+const (
+	minTimeout          = time.Millisecond * 500 // minimum request timeout suggested by the server pool
+	timeoutRefresh      = time.Second * 5        // recalculate timeout if older than this
+	dialCost            = 10000                  // cost of a TCP dial (used for known node selection weight calculation)
+	dialWaitStep        = 1.5                    // exponential multiplier of redial wait time when no value was provided by the server
+	queryCost           = 500                    // cost of a UDP pre-negotiation query
+	queryWaitStep       = 1.02                   // exponential multiplier of redial wait time when no value was provided by the server
+	waitThreshold       = time.Hour * 2000       // drop node if waiting time is over the threshold
+	nodeWeightMul       = 1000000                // multiplier constant for node weight calculation
+	nodeWeightThreshold = 100                    // minimum weight for keeping a node in the the known (valuable) set
+	minRedialWait       = 10                     // minimum redial wait time in seconds
+	preNegLimit         = 5                      // maximum number of simultaneous pre-negotiation queries
+	warnQueryFails      = 20                     // number of consecutive UDP query failures before we print a warning
+	maxQueryFails       = 100                    // number of consecutive UDP query failures when then chance of skipping a query reaches 50%
+)
+
+// ServerPool provides a node iterator for dial candidates. The output is a mix of newly discovered
+// nodes, a weighted random selection of known (previously valuable) nodes and trusted/paid nodes.
+type ServerPool struct {
+	clock    mclock.Clock
+	unixTime func() int64
+	db       ethdb.KeyValueStore
+
+	ns                  *nodestate.NodeStateMachine
+	vt                  *ValueTracker
+	mixer               *enode.FairMix
+	mixSources          []enode.Iterator
+	dialIterator        enode.Iterator
+	validSchemes        enr.IdentityScheme
+	trustedURLs         []string
+	fillSet             *FillSet
+	started, queryFails uint32
+
+	timeoutLock      sync.RWMutex
+	timeout          time.Duration
+	timeWeights      ResponseTimeWeights
+	timeoutRefreshed mclock.AbsTime
+
+	suggestedTimeoutGauge, totalValueGauge metrics.Gauge
+	sessionValueMeter                      metrics.Meter
+}
+
+// nodeHistory keeps track of dial costs which determine node weight together with the
+// service value calculated by ValueTracker.
+type nodeHistory struct {
+	dialCost                       utils.ExpiredValue
+	redialWaitStart, redialWaitEnd int64 // unix time (seconds)
+}
+
+type nodeHistoryEnc struct {
+	DialCost                       utils.ExpiredValue
+	RedialWaitStart, RedialWaitEnd uint64
+}
+
+// queryFunc sends a pre-negotiation query and blocks until a response arrives or timeout occurs.
+// It returns 1 if the remote node has confirmed that connection is possible, 0 if not
+// possible and -1 if no response arrived (timeout).
+type QueryFunc func(*enode.Node) int
+
+var (
+	clientSetup       = &nodestate.Setup{Version: 2}
+	sfHasValue        = clientSetup.NewPersistentFlag("hasValue")
+	sfQuery           = clientSetup.NewFlag("query")
+	sfCanDial         = clientSetup.NewFlag("canDial")
+	sfDialing         = clientSetup.NewFlag("dialed")
+	sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout")
+	sfConnected       = clientSetup.NewFlag("connected")
+	sfRedialWait      = clientSetup.NewFlag("redialWait")
+	sfAlwaysConnect   = clientSetup.NewFlag("alwaysConnect")
+	sfDialProcess     = nodestate.MergeFlags(sfQuery, sfCanDial, sfDialing, sfConnected, sfRedialWait)
+
+	sfiNodeHistory = clientSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}),
+		func(field interface{}) ([]byte, error) {
+			if n, ok := field.(nodeHistory); ok {
+				ne := nodeHistoryEnc{
+					DialCost:        n.dialCost,
+					RedialWaitStart: uint64(n.redialWaitStart),
+					RedialWaitEnd:   uint64(n.redialWaitEnd),
+				}
+				enc, err := rlp.EncodeToBytes(&ne)
+				return enc, err
+			}
+			return nil, errors.New("invalid field type")
+		},
+		func(enc []byte) (interface{}, error) {
+			var ne nodeHistoryEnc
+			err := rlp.DecodeBytes(enc, &ne)
+			n := nodeHistory{
+				dialCost:        ne.DialCost,
+				redialWaitStart: int64(ne.RedialWaitStart),
+				redialWaitEnd:   int64(ne.RedialWaitEnd),
+			}
+			return n, err
+		},
+	)
+	sfiNodeWeight     = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0)))
+	sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{}))
+	sfiLocalAddress   = clientSetup.NewPersistentField("localAddress", reflect.TypeOf(&enr.Record{}),
+		func(field interface{}) ([]byte, error) {
+			if enr, ok := field.(*enr.Record); ok {
+				enc, err := rlp.EncodeToBytes(enr)
+				return enc, err
+			}
+			return nil, errors.New("invalid field type")
+		},
+		func(enc []byte) (interface{}, error) {
+			var enr enr.Record
+			if err := rlp.DecodeBytes(enc, &enr); err != nil {
+				return nil, err
+			}
+			return &enr, nil
+		},
+	)
+)
+
+// NewServerPool creates a new server pool
+func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query QueryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) {
+	s := &ServerPool{
+		db:           db,
+		clock:        clock,
+		unixTime:     func() int64 { return time.Now().Unix() },
+		validSchemes: enode.ValidSchemes,
+		trustedURLs:  trustedURLs,
+		vt:           NewValueTracker(db, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)),
+		ns:           nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, clientSetup),
+	}
+	s.recalTimeout()
+	s.mixer = enode.NewFairMix(mixTimeout)
+	knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDialProcess, sfiNodeWeight)
+	alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDialProcess, true, nil)
+	s.mixSources = append(s.mixSources, knownSelector)
+	s.mixSources = append(s.mixSources, alwaysConnect)
+
+	s.dialIterator = s.mixer
+	if query != nil {
+		s.dialIterator = s.addPreNegFilter(s.dialIterator, query)
+	}
+
+	s.ns.SubscribeState(nodestate.MergeFlags(sfWaitDialTimeout, sfConnected), func(n *enode.Node, oldState, newState nodestate.Flags) {
+		if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() {
+			// dial timeout, no connection
+			s.setRedialWait(n, dialCost, dialWaitStep)
+			s.ns.SetStateSub(n, nodestate.Flags{}, sfDialing, 0)
+		}
+	})
+
+	return s, &serverPoolIterator{
+		dialIterator: s.dialIterator,
+		nextFn: func(node *enode.Node) {
+			s.ns.Operation(func() {
+				s.ns.SetStateSub(node, sfDialing, sfCanDial, 0)
+				s.ns.SetStateSub(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10)
+			})
+		},
+		nodeFn: s.DialNode,
+	}
+}
+
+type serverPoolIterator struct {
+	dialIterator enode.Iterator
+	nextFn       func(*enode.Node)
+	nodeFn       func(*enode.Node) *enode.Node
+}
+
+// Next implements enode.Iterator
+func (s *serverPoolIterator) Next() bool {
+	if s.dialIterator.Next() {
+		s.nextFn(s.dialIterator.Node())
+		return true
+	}
+	return false
+}
+
+// Node implements enode.Iterator
+func (s *serverPoolIterator) Node() *enode.Node {
+	return s.nodeFn(s.dialIterator.Node())
+}
+
+// Close implements enode.Iterator
+func (s *serverPoolIterator) Close() {
+	s.dialIterator.Close()
+}
+
+// AddMetrics adds metrics to the server pool. Should be called before Start().
+func (s *ServerPool) AddMetrics(
+	suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge metrics.Gauge,
+	sessionValueMeter, serverDialedMeter metrics.Meter) {
+
+	s.suggestedTimeoutGauge = suggestedTimeoutGauge
+	s.totalValueGauge = totalValueGauge
+	s.sessionValueMeter = sessionValueMeter
+	if serverSelectableGauge != nil {
+		s.ns.AddLogMetrics(sfHasValue, sfDialProcess, "selectable", nil, nil, serverSelectableGauge)
+	}
+	if serverDialedMeter != nil {
+		s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil)
+	}
+	if serverConnectedGauge != nil {
+		s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge)
+	}
+}
+
+// AddSource adds a node discovery source to the server pool (should be called before start)
+func (s *ServerPool) AddSource(source enode.Iterator) {
+	if source != nil {
+		s.mixSources = append(s.mixSources, source)
+	}
+}
+
+// addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query.
+// Nodes that are filtered out and does not appear on the output iterator are put back
+// into redialWait state.
+func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enode.Iterator {
+	s.fillSet = NewFillSet(s.ns, input, sfQuery)
+	s.ns.SubscribeState(sfDialProcess, func(n *enode.Node, oldState, newState nodestate.Flags) {
+		if !newState.Equals(sfQuery) {
+			if newState.HasAll(sfQuery) {
+				// remove query flag if the node is already somewhere in the dial process
+				s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0)
+			}
+			return
+		}
+		fails := atomic.LoadUint32(&s.queryFails)
+		failMax := fails
+		if failMax > maxQueryFails {
+			failMax = maxQueryFails
+		}
+		if rand.Intn(maxQueryFails*2) < int(failMax) {
+			// skip pre-negotiation with increasing chance, max 50%
+			// this ensures that the client can operate even if UDP is not working at all
+			s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10)
+			// set canDial before resetting queried so that FillSet will not read more
+			// candidates unnecessarily
+			s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0)
+			return
+		}
+		go func() {
+			q := query(n)
+			if q == -1 {
+				atomic.AddUint32(&s.queryFails, 1)
+				fails++
+				if fails%warnQueryFails == 0 {
+					// warn if a large number of consecutive queries have failed
+					log.Warn("UDP connection queries failed", "count", fails)
+				}
+			} else {
+				atomic.StoreUint32(&s.queryFails, 0)
+			}
+			s.ns.Operation(func() {
+				// we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait
+				if q == 1 {
+					s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10)
+				} else {
+					s.setRedialWait(n, queryCost, queryWaitStep)
+				}
+				s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0)
+			})
+		}()
+	})
+	return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) {
+		if waiting {
+			s.fillSet.SetTarget(preNegLimit)
+		} else {
+			s.fillSet.SetTarget(0)
+		}
+	})
+}
+
+// start starts the server pool. Note that NodeStateMachine should be started first.
+func (s *ServerPool) Start() {
+	s.ns.Start()
+	for _, iter := range s.mixSources {
+		// add sources to mixer at startup because the mixer instantly tries to read them
+		// which should only happen after NodeStateMachine has been started
+		s.mixer.AddSource(iter)
+	}
+	for _, url := range s.trustedURLs {
+		if node, err := enode.Parse(s.validSchemes, url); err == nil {
+			s.ns.SetState(node, sfAlwaysConnect, nodestate.Flags{}, 0)
+		} else {
+			log.Error("Invalid trusted server URL", "url", url, "error", err)
+		}
+	}
+	unixTime := s.unixTime()
+	s.ns.Operation(func() {
+		s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
+			s.calculateWeight(node)
+			if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime {
+				wait := n.redialWaitEnd - unixTime
+				lastWait := n.redialWaitEnd - n.redialWaitStart
+				if wait > lastWait {
+					// if the time until expiration is larger than the last suggested
+					// waiting time then the system clock was probably adjusted
+					wait = lastWait
+				}
+				s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second)
+			}
+		})
+	})
+	atomic.StoreUint32(&s.started, 1)
+}
+
+// stop stops the server pool
+func (s *ServerPool) Stop() {
+	if s.fillSet != nil {
+		s.fillSet.Close()
+	}
+	s.ns.Operation(func() {
+		s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) {
+			// recalculate weight of connected nodes in order to update hasValue flag if necessary
+			s.calculateWeight(n)
+		})
+	})
+	s.ns.Stop()
+	s.vt.Stop()
+}
+
+// RegisterNode implements serverPeerSubscriber
+func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) {
+	if atomic.LoadUint32(&s.started) == 0 {
+		return nil, errors.New("server pool not started yet")
+	}
+	nvt := s.vt.Register(node.ID())
+	s.ns.Operation(func() {
+		s.ns.SetStateSub(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0)
+		s.ns.SetFieldSub(node, sfiConnectedStats, nvt.RtStats())
+		if node.IP().IsLoopback() {
+			s.ns.SetFieldSub(node, sfiLocalAddress, node.Record())
+		}
+	})
+	return nvt, nil
+}
+
+// UnregisterNode implements serverPeerSubscriber
+func (s *ServerPool) UnregisterNode(node *enode.Node) {
+	s.ns.Operation(func() {
+		s.setRedialWait(node, dialCost, dialWaitStep)
+		s.ns.SetStateSub(node, nodestate.Flags{}, sfConnected, 0)
+		s.ns.SetFieldSub(node, sfiConnectedStats, nil)
+	})
+	s.vt.Unregister(node.ID())
+}
+
+// recalTimeout calculates the current recommended timeout. This value is used by
+// the client as a "soft timeout" value. It also affects the service value calculation
+// of individual nodes.
+func (s *ServerPool) recalTimeout() {
+	// Use cached result if possible, avoid recalculating too frequently.
+	s.timeoutLock.RLock()
+	refreshed := s.timeoutRefreshed
+	s.timeoutLock.RUnlock()
+	now := s.clock.Now()
+	if refreshed != 0 && time.Duration(now-refreshed) < timeoutRefresh {
+		return
+	}
+	// Cached result is stale, recalculate a new one.
+	rts := s.vt.RtStats()
+
+	// Add a fake statistic here. It is an easy way to initialize with some
+	// conservative values when the database is new. As soon as we have a
+	// considerable amount of real stats this small value won't matter.
+	rts.Add(time.Second*2, 10, s.vt.StatsExpFactor())
+
+	// Use either 10% failure rate timeout or twice the median response time
+	// as the recommended timeout.
+	timeout := minTimeout
+	if t := rts.Timeout(0.1); t > timeout {
+		timeout = t
+	}
+	if t := rts.Timeout(0.5) * 2; t > timeout {
+		timeout = t
+	}
+	s.timeoutLock.Lock()
+	if s.timeout != timeout {
+		s.timeout = timeout
+		s.timeWeights = TimeoutWeights(s.timeout)
+
+		if s.suggestedTimeoutGauge != nil {
+			s.suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond))
+		}
+		if s.totalValueGauge != nil {
+			s.totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor())))
+		}
+	}
+	s.timeoutRefreshed = now
+	s.timeoutLock.Unlock()
+}
+
+// GetTimeout returns the recommended request timeout.
+func (s *ServerPool) GetTimeout() time.Duration {
+	s.recalTimeout()
+	s.timeoutLock.RLock()
+	defer s.timeoutLock.RUnlock()
+	return s.timeout
+}
+
+// getTimeoutAndWeight returns the recommended request timeout as well as the
+// response time weight which is necessary to calculate service value.
+func (s *ServerPool) getTimeoutAndWeight() (time.Duration, ResponseTimeWeights) {
+	s.recalTimeout()
+	s.timeoutLock.RLock()
+	defer s.timeoutLock.RUnlock()
+	return s.timeout, s.timeWeights
+}
+
+// addDialCost adds the given amount of dial cost to the node history and returns the current
+// amount of total dial cost
+func (s *ServerPool) addDialCost(n *nodeHistory, amount int64) uint64 {
+	logOffset := s.vt.StatsExpirer().LogOffset(s.clock.Now())
+	if amount > 0 {
+		n.dialCost.Add(amount, logOffset)
+	}
+	totalDialCost := n.dialCost.Value(logOffset)
+	if totalDialCost < dialCost {
+		totalDialCost = dialCost
+	}
+	return totalDialCost
+}
+
+// serviceValue returns the service value accumulated in this session and in total
+func (s *ServerPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) {
+	nvt := s.vt.GetNode(node.ID())
+	if nvt == nil {
+		return 0, 0
+	}
+	currentStats := nvt.RtStats()
+	_, timeWeights := s.getTimeoutAndWeight()
+	expFactor := s.vt.StatsExpFactor()
+
+	totalValue = currentStats.Value(timeWeights, expFactor)
+	if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(ResponseTimeStats); ok {
+		diff := currentStats
+		diff.SubStats(&connStats)
+		sessionValue = diff.Value(timeWeights, expFactor)
+		if s.sessionValueMeter != nil {
+			s.sessionValueMeter.Mark(int64(sessionValue))
+		}
+	}
+	return
+}
+
+// updateWeight calculates the node weight and updates the nodeWeight field and the
+// hasValue flag. It also saves the node state if necessary.
+// Note: this function should run inside a NodeStateMachine operation
+func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) {
+	weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost))
+	if weight >= nodeWeightThreshold {
+		s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0)
+		s.ns.SetFieldSub(node, sfiNodeWeight, weight)
+	} else {
+		s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0)
+		s.ns.SetFieldSub(node, sfiNodeWeight, nil)
+		s.ns.SetFieldSub(node, sfiNodeHistory, nil)
+		s.ns.SetFieldSub(node, sfiLocalAddress, nil)
+	}
+	s.ns.Persist(node) // saved if node history or hasValue changed
+}
+
+// setRedialWait calculates and sets the redialWait timeout based on the service value
+// and dial cost accumulated during the last session/attempt and in total.
+// The waiting time is raised exponentially if no service value has been received in order
+// to prevent dialing an unresponsive node frequently for a very long time just because it
+// was useful in the past. It can still be occasionally dialed though and once it provides
+// a significant amount of service value again its waiting time is quickly reduced or reset
+// to the minimum.
+// Note: node weight is also recalculated and updated by this function.
+// Note 2: this function should run inside a NodeStateMachine operation
+func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) {
+	n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory)
+	sessionValue, totalValue := s.serviceValue(node)
+	totalDialCost := s.addDialCost(&n, addDialCost)
+
+	// if the current dial session has yielded at least the average value/dial cost ratio
+	// then the waiting time should be reset to the minimum. If the session value
+	// is below average but still positive then timeout is limited to the ratio of
+	// average / current service value multiplied by the minimum timeout. If the attempt
+	// was unsuccessful then timeout is raised exponentially without limitation.
+	// Note: dialCost is used in the formula below even if dial was not attempted at all
+	// because the pre-negotiation query did not return a positive result. In this case
+	// the ratio has no meaning anyway and waitFactor is always raised, though in smaller
+	// steps because queries are cheaper and therefore we can allow more failed attempts.
+	unixTime := s.unixTime()
+	plannedTimeout := float64(n.redialWaitEnd - n.redialWaitStart) // last planned redialWait timeout
+	var actualWait float64                                         // actual waiting time elapsed
+	if unixTime > n.redialWaitEnd {
+		// the planned timeout has elapsed
+		actualWait = plannedTimeout
+	} else {
+		// if the node was redialed earlier then we do not raise the planned timeout
+		// exponentially because that could lead to the timeout rising very high in
+		// a short amount of time
+		// Note that in case of an early redial actualWait also includes the dial
+		// timeout or connection time of the last attempt but it still serves its
+		// purpose of preventing the timeout rising quicker than linearly as a function
+		// of total time elapsed without a successful connection.
+		actualWait = float64(unixTime - n.redialWaitStart)
+	}
+	// raise timeout exponentially if the last planned timeout has elapsed
+	// (use at least the last planned timeout otherwise)
+	nextTimeout := actualWait * waitStep
+	if plannedTimeout > nextTimeout {
+		nextTimeout = plannedTimeout
+	}
+	// we reduce the waiting time if the server has provided service value during the
+	// connection (but never under the minimum)
+	a := totalValue * dialCost * float64(minRedialWait)
+	b := float64(totalDialCost) * sessionValue
+	if a < b*nextTimeout {
+		nextTimeout = a / b
+	}
+	if nextTimeout < minRedialWait {
+		nextTimeout = minRedialWait
+	}
+	wait := time.Duration(float64(time.Second) * nextTimeout)
+	if wait < waitThreshold {
+		n.redialWaitStart = unixTime
+		n.redialWaitEnd = unixTime + int64(nextTimeout)
+		s.ns.SetFieldSub(node, sfiNodeHistory, n)
+		s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, wait)
+		s.updateWeight(node, totalValue, totalDialCost)
+	} else {
+		// discard known node statistics if waiting time is very long because the node
+		// hasn't been responsive for a very long time
+		s.ns.SetFieldSub(node, sfiNodeHistory, nil)
+		s.ns.SetFieldSub(node, sfiNodeWeight, nil)
+		s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0)
+	}
+}
+
+// calculateWeight calculates and sets the node weight without altering the node history.
+// This function should be called during startup and shutdown only, otherwise setRedialWait
+// will keep the weights updated as the underlying statistics are adjusted.
+// Note: this function should run inside a NodeStateMachine operation
+func (s *ServerPool) calculateWeight(node *enode.Node) {
+	n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory)
+	_, totalValue := s.serviceValue(node)
+	totalDialCost := s.addDialCost(&n, 0)
+	s.updateWeight(node, totalValue, totalDialCost)
+}
+
+// API returns the vflux client API
+func (s *ServerPool) API() *PrivateClientAPI {
+	return NewPrivateClientAPI(s.vt)
+}
+
+type dummyIdentity enode.ID
+
+func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil }
+func (id dummyIdentity) NodeAddr(r *enr.Record) []byte          { return id[:] }
+
+// DialNode replaces the given enode with a locally generated one containing the ENR
+// stored in the sfiLocalAddress field if present. This workaround ensures that nodes
+// on the local network can be dialed at the local address if a connection has been
+// successfully established previously.
+// Note that NodeStateMachine always remembers the enode with the latest version of
+// the remote signed ENR. ENR filtering should be performed on that version while
+// dialNode should be used for dialing the node over TCP or UDP.
+func (s *ServerPool) DialNode(n *enode.Node) *enode.Node {
+	if enr, ok := s.ns.GetField(n, sfiLocalAddress).(*enr.Record); ok {
+		n, _ := enode.New(dummyIdentity(n.ID()), enr)
+		return n
+	}
+	return n
+}
+
+// Persist immediately stores the state of a node in the node database
+func (s *ServerPool) Persist(n *enode.Node) {
+	s.ns.Persist(n)
+}
diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go
new file mode 100644
index 0000000000..c777d6c16d
--- /dev/null
+++ b/les/vflux/client/serverpool_test.go
@@ -0,0 +1,354 @@
+// 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 .
+
+package client
+
+import (
+	"math/rand"
+	"strconv"
+	"sync/atomic"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+)
+
+const (
+	spTestNodes  = 1000
+	spTestTarget = 5
+	spTestLength = 10000
+	spMinTotal   = 40000
+	spMaxTotal   = 50000
+)
+
+func testNodeID(i int) enode.ID {
+	return enode.ID{42, byte(i % 256), byte(i / 256)}
+}
+
+func testNodeIndex(id enode.ID) int {
+	if id[0] != 42 {
+		return -1
+	}
+	return int(id[1]) + int(id[2])*256
+}
+
+type ServerPoolTest struct {
+	db                   ethdb.KeyValueStore
+	clock                *mclock.Simulated
+	quit                 chan struct{}
+	preNeg, preNegFail   bool
+	vt                   *ValueTracker
+	sp                   *ServerPool
+	spi                  enode.Iterator
+	input                enode.Iterator
+	testNodes            []spTestNode
+	trusted              []string
+	waitCount, waitEnded int32
+
+	cycle, conn, servedConn  int
+	serviceCycles, dialCount int
+	disconnect               map[int][]int
+}
+
+type spTestNode struct {
+	connectCycles, waitCycles int
+	nextConnCycle, totalConn  int
+	connected, service        bool
+	node                      *enode.Node
+}
+
+func newServerPoolTest(preNeg, preNegFail bool) *ServerPoolTest {
+	nodes := make([]*enode.Node, spTestNodes)
+	for i := range nodes {
+		nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i))
+	}
+	return &ServerPoolTest{
+		clock:      &mclock.Simulated{},
+		db:         memorydb.New(),
+		input:      enode.CycleNodes(nodes),
+		testNodes:  make([]spTestNode, spTestNodes),
+		preNeg:     preNeg,
+		preNegFail: preNegFail,
+	}
+}
+
+func (s *ServerPoolTest) beginWait() {
+	// ensure that dialIterator and the maximal number of pre-neg queries are not all stuck in a waiting state
+	for atomic.AddInt32(&s.waitCount, 1) > preNegLimit {
+		atomic.AddInt32(&s.waitCount, -1)
+		s.clock.Run(time.Second)
+	}
+}
+
+func (s *ServerPoolTest) endWait() {
+	atomic.AddInt32(&s.waitCount, -1)
+	atomic.AddInt32(&s.waitEnded, 1)
+}
+
+func (s *ServerPoolTest) addTrusted(i int) {
+	s.trusted = append(s.trusted, enode.SignNull(&enr.Record{}, testNodeID(i)).String())
+}
+
+func (s *ServerPoolTest) start() {
+	var testQuery QueryFunc
+	if s.preNeg {
+		testQuery = func(node *enode.Node) int {
+			idx := testNodeIndex(node.ID())
+			n := &s.testNodes[idx]
+			canConnect := !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle
+			if s.preNegFail {
+				// simulate a scenario where UDP queries never work
+				s.beginWait()
+				s.clock.Sleep(time.Second * 5)
+				s.endWait()
+				return -1
+			}
+			switch idx % 3 {
+			case 0:
+				// pre-neg returns true only if connection is possible
+				if canConnect {
+					return 1
+				}
+				return 0
+			case 1:
+				// pre-neg returns true but connection might still fail
+				return 1
+			case 2:
+				// pre-neg returns true if connection is possible, otherwise timeout (node unresponsive)
+				if canConnect {
+					return 1
+				}
+				s.beginWait()
+				s.clock.Sleep(time.Second * 5)
+				s.endWait()
+				return -1
+			}
+			return -1
+		}
+	}
+
+	requestList := make([]RequestInfo, testReqTypes)
+	for i := range requestList {
+		requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1}
+	}
+
+	s.sp, s.spi = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList)
+	s.sp.AddSource(s.input)
+	s.sp.validSchemes = enode.ValidSchemesForTesting
+	s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) }
+	s.disconnect = make(map[int][]int)
+	s.sp.Start()
+	s.quit = make(chan struct{})
+	go func() {
+		last := int32(-1)
+		for {
+			select {
+			case <-time.After(time.Millisecond * 100):
+				c := atomic.LoadInt32(&s.waitEnded)
+				if c == last {
+					// advance clock if test is stuck (might happen in rare cases)
+					s.clock.Run(time.Second)
+				}
+				last = c
+			case <-s.quit:
+				return
+			}
+		}
+	}()
+}
+
+func (s *ServerPoolTest) stop() {
+	close(s.quit)
+	s.sp.Stop()
+	s.spi.Close()
+	for i := range s.testNodes {
+		n := &s.testNodes[i]
+		if n.connected {
+			n.totalConn += s.cycle
+		}
+		n.connected = false
+		n.node = nil
+		n.nextConnCycle = 0
+	}
+	s.conn, s.servedConn = 0, 0
+}
+
+func (s *ServerPoolTest) run() {
+	for count := spTestLength; count > 0; count-- {
+		if dcList := s.disconnect[s.cycle]; dcList != nil {
+			for _, idx := range dcList {
+				n := &s.testNodes[idx]
+				s.sp.UnregisterNode(n.node)
+				n.totalConn += s.cycle
+				n.connected = false
+				n.node = nil
+				s.conn--
+				if n.service {
+					s.servedConn--
+				}
+				n.nextConnCycle = s.cycle + n.waitCycles
+			}
+			delete(s.disconnect, s.cycle)
+		}
+		if s.conn < spTestTarget {
+			s.dialCount++
+			s.beginWait()
+			s.spi.Next()
+			s.endWait()
+			dial := s.spi.Node()
+			id := dial.ID()
+			idx := testNodeIndex(id)
+			n := &s.testNodes[idx]
+			if !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle {
+				s.conn++
+				if n.service {
+					s.servedConn++
+				}
+				n.totalConn -= s.cycle
+				n.connected = true
+				dc := s.cycle + n.connectCycles
+				s.disconnect[dc] = append(s.disconnect[dc], idx)
+				n.node = dial
+				nv, _ := s.sp.RegisterNode(n.node)
+				if n.service {
+					nv.Served([]ServedRequest{{ReqType: 0, Amount: 100}}, 0)
+				}
+			}
+		}
+		s.serviceCycles += s.servedConn
+		s.clock.Run(time.Second)
+		s.cycle++
+	}
+}
+
+func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) {
+	for ; count > 0; count-- {
+		idx := rand.Intn(spTestNodes)
+		for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected {
+			idx = rand.Intn(spTestNodes)
+		}
+		res = append(res, idx)
+		s.testNodes[idx] = spTestNode{
+			connectCycles: conn,
+			waitCycles:    wait,
+			service:       service,
+		}
+		if trusted {
+			s.addTrusted(idx)
+		}
+	}
+	return
+}
+
+func (s *ServerPoolTest) resetNodes() {
+	for i, n := range s.testNodes {
+		if n.connected {
+			n.totalConn += s.cycle
+			s.sp.UnregisterNode(n.node)
+		}
+		s.testNodes[i] = spTestNode{totalConn: n.totalConn}
+	}
+	s.conn, s.servedConn = 0, 0
+	s.disconnect = make(map[int][]int)
+	s.trusted = nil
+}
+
+func (s *ServerPoolTest) checkNodes(t *testing.T, nodes []int) {
+	var sum int
+	for _, idx := range nodes {
+		n := &s.testNodes[idx]
+		if n.connected {
+			n.totalConn += s.cycle
+		}
+		sum += n.totalConn
+		n.totalConn = 0
+		if n.connected {
+			n.totalConn -= s.cycle
+		}
+	}
+	if sum < spMinTotal || sum > spMaxTotal {
+		t.Errorf("Total connection amount %d outside expected range %d to %d", sum, spMinTotal, spMaxTotal)
+	}
+}
+
+func TestServerPool(t *testing.T)               { testServerPool(t, false, false) }
+func TestServerPoolWithPreNeg(t *testing.T)     { testServerPool(t, true, false) }
+func TestServerPoolWithPreNegFail(t *testing.T) { testServerPool(t, true, true) }
+func testServerPool(t *testing.T, preNeg, fail bool) {
+	s := newServerPoolTest(preNeg, fail)
+	nodes := s.setNodes(100, 200, 200, true, false)
+	s.setNodes(100, 20, 20, false, false)
+	s.start()
+	s.run()
+	s.stop()
+	s.checkNodes(t, nodes)
+}
+
+func TestServerPoolChangedNodes(t *testing.T)           { testServerPoolChangedNodes(t, false) }
+func TestServerPoolChangedNodesWithPreNeg(t *testing.T) { testServerPoolChangedNodes(t, true) }
+func testServerPoolChangedNodes(t *testing.T, preNeg bool) {
+	s := newServerPoolTest(preNeg, false)
+	nodes := s.setNodes(100, 200, 200, true, false)
+	s.setNodes(100, 20, 20, false, false)
+	s.start()
+	s.run()
+	s.checkNodes(t, nodes)
+	for i := 0; i < 3; i++ {
+		s.resetNodes()
+		nodes := s.setNodes(100, 200, 200, true, false)
+		s.setNodes(100, 20, 20, false, false)
+		s.run()
+		s.checkNodes(t, nodes)
+	}
+	s.stop()
+}
+
+func TestServerPoolRestartNoDiscovery(t *testing.T) { testServerPoolRestartNoDiscovery(t, false) }
+func TestServerPoolRestartNoDiscoveryWithPreNeg(t *testing.T) {
+	testServerPoolRestartNoDiscovery(t, true)
+}
+func testServerPoolRestartNoDiscovery(t *testing.T, preNeg bool) {
+	s := newServerPoolTest(preNeg, false)
+	nodes := s.setNodes(100, 200, 200, true, false)
+	s.setNodes(100, 20, 20, false, false)
+	s.start()
+	s.run()
+	s.stop()
+	s.checkNodes(t, nodes)
+	s.input = nil
+	s.start()
+	s.run()
+	s.stop()
+	s.checkNodes(t, nodes)
+}
+
+func TestServerPoolTrustedNoDiscovery(t *testing.T) { testServerPoolTrustedNoDiscovery(t, false) }
+func TestServerPoolTrustedNoDiscoveryWithPreNeg(t *testing.T) {
+	testServerPoolTrustedNoDiscovery(t, true)
+}
+func testServerPoolTrustedNoDiscovery(t *testing.T, preNeg bool) {
+	s := newServerPoolTest(preNeg, false)
+	trusted := s.setNodes(200, 200, 200, true, true)
+	s.input = nil
+	s.start()
+	s.run()
+	s.stop()
+	s.checkNodes(t, trusted)
+}
diff --git a/les/lespay/client/timestats.go b/les/vflux/client/timestats.go
similarity index 100%
rename from les/lespay/client/timestats.go
rename to les/vflux/client/timestats.go
diff --git a/les/lespay/client/timestats_test.go b/les/vflux/client/timestats_test.go
similarity index 100%
rename from les/lespay/client/timestats_test.go
rename to les/vflux/client/timestats_test.go
diff --git a/les/lespay/client/valuetracker.go b/les/vflux/client/valuetracker.go
similarity index 91%
rename from les/lespay/client/valuetracker.go
rename to les/vflux/client/valuetracker.go
index 92bfd694ed..f5390d0920 100644
--- a/les/lespay/client/valuetracker.go
+++ b/les/vflux/client/valuetracker.go
@@ -45,6 +45,7 @@ var (
 type NodeValueTracker struct {
 	lock sync.Mutex
 
+	vt                   *ValueTracker
 	rtStats, lastRtStats ResponseTimeStats
 	lastTransfer         mclock.AbsTime
 	basket               serverBasket
@@ -52,15 +53,12 @@ type NodeValueTracker struct {
 	reqValues            *[]float64
 }
 
-// init initializes a NodeValueTracker.
-// Note that the contents of the referenced reqValues slice will not change; a new
-// reference is passed if the values are updated by ValueTracker.
-func (nv *NodeValueTracker) init(now mclock.AbsTime, reqValues *[]float64) {
-	reqTypeCount := len(*reqValues)
-	nv.reqCosts = make([]uint64, reqTypeCount)
-	nv.lastTransfer = now
-	nv.reqValues = reqValues
-	nv.basket.init(reqTypeCount)
+// UpdateCosts updates the node value tracker's request cost table
+func (nv *NodeValueTracker) UpdateCosts(reqCosts []uint64) {
+	nv.vt.lock.Lock()
+	defer nv.vt.lock.Unlock()
+
+	nv.updateCosts(reqCosts, &nv.vt.refBasket.reqValues, nv.vt.refBasket.reqValueFactor(reqCosts))
 }
 
 // updateCosts updates the request cost table of the server. The request value factor
@@ -97,6 +95,28 @@ func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float
 	return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats
 }
 
+type ServedRequest struct {
+	ReqType, Amount uint32
+}
+
+// Served adds a served request to the node's statistics. An actual request may be composed
+// of one or more request types (service vector indices).
+func (nv *NodeValueTracker) Served(reqs []ServedRequest, respTime time.Duration) {
+	nv.vt.statsExpLock.RLock()
+	expFactor := nv.vt.statsExpFactor
+	nv.vt.statsExpLock.RUnlock()
+
+	nv.lock.Lock()
+	defer nv.lock.Unlock()
+
+	var value float64
+	for _, r := range reqs {
+		nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor)
+		value += (*nv.reqValues)[r.ReqType] * float64(r.Amount)
+	}
+	nv.rtStats.Add(respTime, value, expFactor)
+}
+
 // RtStats returns the node's own response time distribution statistics
 func (nv *NodeValueTracker) RtStats() ResponseTimeStats {
 	nv.lock.Lock()
@@ -213,6 +233,15 @@ func (vt *ValueTracker) StatsExpirer() *utils.Expirer {
 	return &vt.statsExpirer
 }
 
+// StatsExpirer returns the current expiration factor so that other values can be expired
+// with the same rate as the service value statistics.
+func (vt *ValueTracker) StatsExpFactor() utils.ExpirationFactor {
+	vt.statsExpLock.RLock()
+	defer vt.statsExpLock.RUnlock()
+
+	return vt.statsExpFactor
+}
+
 // loadFromDb loads the value tracker's state from the database and converts saved
 // request basket index mapping if it does not match the specified index to name mapping.
 func (vt *ValueTracker) loadFromDb(mapping []string) error {
@@ -324,7 +353,12 @@ func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker {
 		return nil
 	}
 	nv := vt.loadOrNewNode(id)
-	nv.init(vt.clock.Now(), &vt.refBasket.reqValues)
+	reqTypeCount := len(vt.refBasket.reqValues)
+	nv.reqCosts = make([]uint64, reqTypeCount)
+	nv.lastTransfer = vt.clock.Now()
+	nv.reqValues = &vt.refBasket.reqValues
+	nv.basket.init(reqTypeCount)
+
 	vt.connected[id] = nv
 	return nv
 }
@@ -355,7 +389,7 @@ func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker {
 	if nv, ok := vt.connected[id]; ok {
 		return nv
 	}
-	nv := &NodeValueTracker{lastTransfer: vt.clock.Now()}
+	nv := &NodeValueTracker{vt: vt, lastTransfer: vt.clock.Now()}
 	enc, err := vt.db.Get(append(vtNodeKey, id[:]...))
 	if err != nil {
 		return nv
@@ -416,14 +450,6 @@ func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) {
 	}
 }
 
-// UpdateCosts updates the node value tracker's request cost table
-func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) {
-	vt.lock.Lock()
-	defer vt.lock.Unlock()
-
-	nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts))
-}
-
 // RtStats returns the global response time distribution statistics
 func (vt *ValueTracker) RtStats() ResponseTimeStats {
 	vt.lock.Lock()
@@ -455,28 +481,6 @@ func (vt *ValueTracker) periodicUpdate() {
 	vt.saveToDb()
 }
 
-type ServedRequest struct {
-	ReqType, Amount uint32
-}
-
-// Served adds a served request to the node's statistics. An actual request may be composed
-// of one or more request types (service vector indices).
-func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) {
-	vt.statsExpLock.RLock()
-	expFactor := vt.statsExpFactor
-	vt.statsExpLock.RUnlock()
-
-	nv.lock.Lock()
-	defer nv.lock.Unlock()
-
-	var value float64
-	for _, r := range reqs {
-		nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor)
-		value += (*nv.reqValues)[r.ReqType] * float64(r.Amount)
-	}
-	nv.rtStats.Add(respTime, value, vt.statsExpFactor)
-}
-
 type RequestStatsItem struct {
 	Name                string
 	ReqAmount, ReqValue float64
@@ -500,16 +504,3 @@ func (vt *ValueTracker) RequestStats() []RequestStatsItem {
 	}
 	return res
 }
-
-// TotalServiceValue returns the total service value provided by the given node (as
-// a function of the weights which are calculated from the request timeout value).
-func (vt *ValueTracker) TotalServiceValue(nv *NodeValueTracker, weights ResponseTimeWeights) float64 {
-	vt.statsExpLock.RLock()
-	expFactor := vt.statsExpFactor
-	vt.statsExpLock.RUnlock()
-
-	nv.lock.Lock()
-	defer nv.lock.Unlock()
-
-	return nv.rtStats.Value(weights, expFactor)
-}
diff --git a/les/lespay/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go
similarity index 97%
rename from les/lespay/client/valuetracker_test.go
rename to les/vflux/client/valuetracker_test.go
index ad398749e9..87a337be8d 100644
--- a/les/lespay/client/valuetracker_test.go
+++ b/les/vflux/client/valuetracker_test.go
@@ -64,7 +64,7 @@ func TestValueTracker(t *testing.T) {
 			for j := range costList {
 				costList[j] = uint64(baseCost * relPrices[j])
 			}
-			vt.UpdateCosts(nodes[i], costList)
+			nodes[i].UpdateCosts(costList)
 		}
 		for i := range nodes {
 			nodes[i] = vt.Register(enode.ID{byte(i)})
@@ -77,7 +77,7 @@ func TestValueTracker(t *testing.T) {
 				node := rand.Intn(testNodeCount)
 				respTime := time.Duration((rand.Float64() + 1) * float64(time.Second) * float64(node+1) / testNodeCount)
 				totalAmount[reqType] += uint64(reqAmount)
-				vt.Served(nodes[node], []ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime)
+				nodes[node].Served([]ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime)
 				clock.Run(time.Second)
 			}
 		} else {
diff --git a/les/vflux/client/wrsiterator.go b/les/vflux/client/wrsiterator.go
new file mode 100644
index 0000000000..8a2e39ad44
--- /dev/null
+++ b/les/vflux/client/wrsiterator.go
@@ -0,0 +1,128 @@
+// 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 .
+
+package client
+
+import (
+	"sync"
+
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+// WrsIterator returns nodes from the specified selectable set with a weighted random
+// selection. Selection weights are provided by a callback function.
+type WrsIterator struct {
+	lock sync.Mutex
+	cond *sync.Cond
+
+	ns       *nodestate.NodeStateMachine
+	wrs      *utils.WeightedRandomSelect
+	nextNode *enode.Node
+	closed   bool
+}
+
+// NewWrsIterator creates a new WrsIterator. Nodes are selectable if they have all the required
+// and none of the disabled flags set. When a node is selected the selectedFlag is set which also
+// disables further selectability until it is removed or times out.
+func NewWrsIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags nodestate.Flags, weightField nodestate.Field) *WrsIterator {
+	wfn := func(i interface{}) uint64 {
+		n := ns.GetNode(i.(enode.ID))
+		if n == nil {
+			return 0
+		}
+		wt, _ := ns.GetField(n, weightField).(uint64)
+		return wt
+	}
+
+	w := &WrsIterator{
+		ns:  ns,
+		wrs: utils.NewWeightedRandomSelect(wfn),
+	}
+	w.cond = sync.NewCond(&w.lock)
+
+	ns.SubscribeField(weightField, func(n *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
+		if state.HasAll(requireFlags) && state.HasNone(disableFlags) {
+			w.lock.Lock()
+			w.wrs.Update(n.ID())
+			w.lock.Unlock()
+			w.cond.Signal()
+		}
+	})
+
+	ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) {
+		oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags)
+		newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags)
+		if newMatch == oldMatch {
+			return
+		}
+
+		w.lock.Lock()
+		if newMatch {
+			w.wrs.Update(n.ID())
+		} else {
+			w.wrs.Remove(n.ID())
+		}
+		w.lock.Unlock()
+		w.cond.Signal()
+	})
+	return w
+}
+
+// Next selects the next node.
+func (w *WrsIterator) Next() bool {
+	w.nextNode = w.chooseNode()
+	return w.nextNode != nil
+}
+
+func (w *WrsIterator) chooseNode() *enode.Node {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+
+	for {
+		for !w.closed && w.wrs.IsEmpty() {
+			w.cond.Wait()
+		}
+		if w.closed {
+			return nil
+		}
+		// Choose the next node at random. Even though w.wrs is guaranteed
+		// non-empty here, Choose might return nil if all items have weight
+		// zero.
+		if c := w.wrs.Choose(); c != nil {
+			id := c.(enode.ID)
+			w.wrs.Remove(id)
+			return w.ns.GetNode(id)
+		}
+	}
+
+}
+
+// Close ends the iterator.
+func (w *WrsIterator) Close() {
+	w.lock.Lock()
+	w.closed = true
+	w.lock.Unlock()
+	w.cond.Signal()
+}
+
+// Node returns the current node.
+func (w *WrsIterator) Node() *enode.Node {
+	w.lock.Lock()
+	defer w.lock.Unlock()
+	return w.nextNode
+}
diff --git a/les/vflux/client/wrsiterator_test.go b/les/vflux/client/wrsiterator_test.go
new file mode 100644
index 0000000000..77bb5ee0ca
--- /dev/null
+++ b/les/vflux/client/wrsiterator_test.go
@@ -0,0 +1,103 @@
+// 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 .
+
+package client
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+var (
+	testSetup     = &nodestate.Setup{}
+	sfTest1       = testSetup.NewFlag("test1")
+	sfTest2       = testSetup.NewFlag("test2")
+	sfTest3       = testSetup.NewFlag("test3")
+	sfTest4       = testSetup.NewFlag("test4")
+	sfiTestWeight = testSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0)))
+)
+
+const iterTestNodeCount = 6
+
+func TestWrsIterator(t *testing.T) {
+	ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup)
+	w := NewWrsIterator(ns, sfTest2, sfTest3.Or(sfTest4), sfiTestWeight)
+	ns.Start()
+	for i := 1; i <= iterTestNodeCount; i++ {
+		ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0)
+		ns.SetField(testNode(i), sfiTestWeight, uint64(1))
+	}
+	next := func() int {
+		ch := make(chan struct{})
+		go func() {
+			w.Next()
+			close(ch)
+		}()
+		select {
+		case <-ch:
+		case <-time.After(time.Second * 5):
+			t.Fatalf("Iterator.Next() timeout")
+		}
+		node := w.Node()
+		ns.SetState(node, sfTest4, nodestate.Flags{}, 0)
+		return testNodeIndex(node.ID())
+	}
+	set := make(map[int]bool)
+	expset := func() {
+		for len(set) > 0 {
+			n := next()
+			if !set[n] {
+				t.Errorf("Item returned by iterator not in the expected set (got %d)", n)
+			}
+			delete(set, n)
+		}
+	}
+
+	ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0)
+	set[1] = true
+	set[2] = true
+	set[3] = true
+	expset()
+	ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0)
+	ns.SetState(testNode(5), sfTest2.Or(sfTest3), nodestate.Flags{}, 0)
+	ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0)
+	set[4] = true
+	set[6] = true
+	expset()
+	ns.SetField(testNode(2), sfiTestWeight, uint64(0))
+	ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0)
+	ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0)
+	ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0)
+	set[1] = true
+	set[3] = true
+	expset()
+	ns.SetField(testNode(2), sfiTestWeight, uint64(1))
+	ns.SetState(testNode(2), nodestate.Flags{}, sfTest2, 0)
+	ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0)
+	ns.SetState(testNode(2), sfTest2, sfTest4, 0)
+	ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0)
+	set[1] = true
+	set[2] = true
+	set[3] = true
+	expset()
+	ns.Stop()
+}
diff --git a/les/vflux/requests.go b/les/vflux/requests.go
new file mode 100644
index 0000000000..11255607e8
--- /dev/null
+++ b/les/vflux/requests.go
@@ -0,0 +1,180 @@
+// 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 .
+
+package vflux
+
+import (
+	"errors"
+	"math"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+var ErrNoReply = errors.New("no reply for given request")
+
+const (
+	MaxRequestLength    = 16 // max number of individual requests in a batch
+	CapacityQueryName   = "cq"
+	CapacityQueryMaxLen = 16
+)
+
+type (
+	// Request describes a single vflux request inside a batch. Service and request
+	// type are identified by strings, parameters are RLP encoded.
+	Request struct {
+		Service, Name string
+		Params        []byte
+	}
+	// Requests are a batch of vflux requests
+	Requests []Request
+
+	// Replies are the replies to a batch of requests
+	Replies [][]byte
+
+	// CapacityQueryReq is the encoding format of the capacity query
+	CapacityQueryReq struct {
+		Bias      uint64 // seconds
+		AddTokens []IntOrInf
+	}
+	// CapacityQueryReq is the encoding format of the response to the capacity query
+	CapacityQueryReply []uint64
+)
+
+// Add encodes and adds a new request to the batch
+func (r *Requests) Add(service, name string, val interface{}) (int, error) {
+	enc, err := rlp.EncodeToBytes(val)
+	if err != nil {
+		return -1, err
+	}
+	*r = append(*r, Request{
+		Service: service,
+		Name:    name,
+		Params:  enc,
+	})
+	return len(*r) - 1, nil
+}
+
+// Get decodes the reply to the i-th request in the batch
+func (r Replies) Get(i int, val interface{}) error {
+	if i < 0 || i >= len(r) {
+		return ErrNoReply
+	}
+	return rlp.DecodeBytes(r[i], val)
+}
+
+const (
+	IntNonNegative = iota
+	IntNegative
+	IntPlusInf
+	IntMinusInf
+)
+
+// IntOrInf is the encoding format for arbitrary length signed integers that can also
+// hold the values of +Inf or -Inf
+type IntOrInf struct {
+	Type  uint8
+	Value big.Int
+}
+
+// BigInt returns the value as a big.Int or panics if the value is infinity
+func (i *IntOrInf) BigInt() *big.Int {
+	switch i.Type {
+	case IntNonNegative:
+		return new(big.Int).Set(&i.Value)
+	case IntNegative:
+		return new(big.Int).Neg(&i.Value)
+	case IntPlusInf:
+		panic(nil) // caller should check Inf() before trying to convert to big.Int
+	case IntMinusInf:
+		panic(nil)
+	}
+	return &big.Int{} // invalid type decodes to 0 value
+}
+
+// Inf returns 1 if the value is +Inf, -1 if it is -Inf, 0 otherwise
+func (i *IntOrInf) Inf() int {
+	switch i.Type {
+	case IntPlusInf:
+		return 1
+	case IntMinusInf:
+		return -1
+	}
+	return 0 // invalid type decodes to 0 value
+}
+
+// Int64 limits the value between MinInt64 and MaxInt64 (even if it is +-Inf) and returns an int64 type
+func (i *IntOrInf) Int64() int64 {
+	switch i.Type {
+	case IntNonNegative:
+		if i.Value.IsInt64() {
+			return i.Value.Int64()
+		} else {
+			return math.MaxInt64
+		}
+	case IntNegative:
+		if i.Value.IsInt64() {
+			return -i.Value.Int64()
+		} else {
+			return math.MinInt64
+		}
+	case IntPlusInf:
+		return math.MaxInt64
+	case IntMinusInf:
+		return math.MinInt64
+	}
+	return 0 // invalid type decodes to 0 value
+}
+
+// SetBigInt sets the value to the given big.Int
+func (i *IntOrInf) SetBigInt(v *big.Int) {
+	if v.Sign() >= 0 {
+		i.Type = IntNonNegative
+		i.Value.Set(v)
+	} else {
+		i.Type = IntNegative
+		i.Value.Neg(v)
+	}
+}
+
+// SetInt64 sets the value to the given int64. Note that MaxInt64 translates to +Inf
+// while MinInt64 translates to -Inf.
+func (i *IntOrInf) SetInt64(v int64) {
+	if v >= 0 {
+		if v == math.MaxInt64 {
+			i.Type = IntPlusInf
+		} else {
+			i.Type = IntNonNegative
+			i.Value.SetInt64(v)
+		}
+	} else {
+		if v == math.MinInt64 {
+			i.Type = IntMinusInf
+		} else {
+			i.Type = IntNegative
+			i.Value.SetInt64(-v)
+		}
+	}
+}
+
+// SetInf sets the value to +Inf or -Inf
+func (i *IntOrInf) SetInf(sign int) {
+	if sign == 1 {
+		i.Type = IntPlusInf
+	} else {
+		i.Type = IntMinusInf
+	}
+}
diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go
new file mode 100644
index 0000000000..2bc1ddd189
--- /dev/null
+++ b/les/vflux/server/balance.go
@@ -0,0 +1,693 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package server
+
+import (
+	"errors"
+	"math"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+var errBalanceOverflow = errors.New("balance overflow")
+
+const maxBalance = math.MaxInt64 // maximum allowed balance value
+
+const (
+	balanceCallbackUpdate = iota // called when priority drops below the last minimum estimate
+	balanceCallbackZero          // called when priority drops to zero (positive balance exhausted)
+	balanceCallbackCount         // total number of balance callbacks
+)
+
+// PriceFactors determine the pricing policy (may apply either to positive or
+// negative balances which may have different factors).
+// - TimeFactor is cost unit per nanosecond of connection time
+// - CapacityFactor is cost unit per nanosecond of connection time per 1000000 capacity
+// - RequestFactor is cost unit per request "realCost" unit
+type PriceFactors struct {
+	TimeFactor, CapacityFactor, RequestFactor float64
+}
+
+// connectionPrice returns the price of connection per nanosecond at the given capacity
+// and the estimated average request cost.
+func (p PriceFactors) connectionPrice(cap uint64, avgReqCost float64) float64 {
+	return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 + p.RequestFactor*avgReqCost
+}
+
+type (
+	// nodePriority interface provides current and estimated future priorities on demand
+	nodePriority interface {
+		// priority should return the current priority of the node (higher is better)
+		priority(cap uint64) int64
+		// estimatePriority should return a lower estimate for the minimum of the node priority
+		// value starting from the current moment until the given time. If the priority goes
+		// under the returned estimate before the specified moment then it is the caller's
+		// responsibility to signal with updateFlag.
+		estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64
+	}
+
+	// ReadOnlyBalance provides read-only operations on the node balance
+	ReadOnlyBalance interface {
+		nodePriority
+		GetBalance() (uint64, uint64)
+		GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue)
+		GetPriceFactors() (posFactor, negFactor PriceFactors)
+	}
+
+	// ConnectedBalance provides operations permitted on connected nodes (non-read-only
+	// operations are not permitted inside a BalanceOperation)
+	ConnectedBalance interface {
+		ReadOnlyBalance
+		SetPriceFactors(posFactor, negFactor PriceFactors)
+		RequestServed(cost uint64) uint64
+	}
+
+	// AtomicBalanceOperator provides operations permitted in an atomic BalanceOperation
+	AtomicBalanceOperator interface {
+		ReadOnlyBalance
+		AddBalance(amount int64) (uint64, uint64, error)
+		SetBalance(pos, neg uint64) error
+	}
+)
+
+// nodeBalance keeps track of the positive and negative balances of a connected
+// client and calculates actual and projected future priority values.
+// Implements nodePriority interface.
+type nodeBalance struct {
+	bt                               *balanceTracker
+	lock                             sync.RWMutex
+	node                             *enode.Node
+	connAddress                      string
+	active, hasPriority, setFlags    bool
+	capacity                         uint64
+	balance                          balance
+	posFactor, negFactor             PriceFactors
+	sumReqCost                       uint64
+	lastUpdate, nextUpdate, initTime mclock.AbsTime
+	updateEvent                      mclock.Timer
+	// since only a limited and fixed number of callbacks are needed, they are
+	// stored in a fixed size array ordered by priority threshold.
+	callbacks [balanceCallbackCount]balanceCallback
+	// callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active)
+	callbackIndex [balanceCallbackCount]int
+	callbackCount int // number of active callbacks
+}
+
+// balance represents a pair of positive and negative balances
+type balance struct {
+	pos, neg       utils.ExpiredValue
+	posExp, negExp utils.ValueExpirer
+}
+
+// posValue returns the value of positive balance at a given timestamp.
+func (b balance) posValue(now mclock.AbsTime) uint64 {
+	return b.pos.Value(b.posExp.LogOffset(now))
+}
+
+// negValue returns the value of negative balance at a given timestamp.
+func (b balance) negValue(now mclock.AbsTime) uint64 {
+	return b.neg.Value(b.negExp.LogOffset(now))
+}
+
+// addValue adds the value of a given amount to the balance. The original value and
+// updated value will also be returned if the addition is successful.
+// Returns the error if the given value is too large and the value overflows.
+func (b *balance) addValue(now mclock.AbsTime, amount int64, pos bool, force bool) (uint64, uint64, int64, error) {
+	var (
+		val    utils.ExpiredValue
+		offset utils.Fixed64
+	)
+	if pos {
+		offset, val = b.posExp.LogOffset(now), b.pos
+	} else {
+		offset, val = b.negExp.LogOffset(now), b.neg
+	}
+	old := val.Value(offset)
+	if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) {
+		if !force {
+			return old, 0, 0, errBalanceOverflow
+		}
+		val = utils.ExpiredValue{}
+		amount = maxBalance
+	}
+	net := val.Add(amount, offset)
+	if pos {
+		b.pos = val
+	} else {
+		b.neg = val
+	}
+	return old, val.Value(offset), net, nil
+}
+
+// setValue sets the internal balance amount to the given values. Returns the
+// error if the given value is too large.
+func (b *balance) setValue(now mclock.AbsTime, pos uint64, neg uint64) error {
+	if pos > maxBalance || neg > maxBalance {
+		return errBalanceOverflow
+	}
+	var pb, nb utils.ExpiredValue
+	pb.Add(int64(pos), b.posExp.LogOffset(now))
+	nb.Add(int64(neg), b.negExp.LogOffset(now))
+	b.pos = pb
+	b.neg = nb
+	return nil
+}
+
+// balanceCallback represents a single callback that is activated when client priority
+// reaches the given threshold
+type balanceCallback struct {
+	id        int
+	threshold int64
+	callback  func()
+}
+
+// GetBalance returns the current positive and negative balance.
+func (n *nodeBalance) GetBalance() (uint64, uint64) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+	return n.balance.posValue(now), n.balance.negValue(now)
+}
+
+// GetRawBalance returns the current positive and negative balance
+// but in the raw(expired value) format.
+func (n *nodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+	return n.balance.pos, n.balance.neg
+}
+
+// AddBalance adds the given amount to the positive balance and returns the balance
+// before and after the operation. Exceeding maxBalance results in an error (balance is
+// unchanged) while adding a negative amount higher than the current balance results in
+// zero balance.
+// Note: this function should run inside a NodeStateMachine operation
+func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) {
+	var (
+		err         error
+		old, new    uint64
+		now         = n.bt.clock.Now()
+		callbacks   []func()
+		setPriority bool
+	)
+	// Operation with holding the lock
+	n.bt.updateTotalBalance(n, func() bool {
+		n.updateBalance(now)
+		if old, new, _, err = n.balance.addValue(now, amount, true, false); err != nil {
+			return false
+		}
+		callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus()
+		n.storeBalance(true, false)
+		return true
+	})
+	if err != nil {
+		return old, old, err
+	}
+	// Operation without holding the lock
+	for _, cb := range callbacks {
+		cb()
+	}
+	if n.setFlags {
+		if setPriority {
+			n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
+		}
+		// Note: priority flag is automatically removed by the zero priority callback if necessary
+		n.signalPriorityUpdate()
+	}
+	return old, new, nil
+}
+
+// SetBalance sets the positive and negative balance to the given values
+// Note: this function should run inside a NodeStateMachine operation
+func (n *nodeBalance) SetBalance(pos, neg uint64) error {
+	var (
+		now         = n.bt.clock.Now()
+		callbacks   []func()
+		setPriority bool
+	)
+	// Operation with holding the lock
+	n.bt.updateTotalBalance(n, func() bool {
+		n.updateBalance(now)
+		if err := n.balance.setValue(now, pos, neg); err != nil {
+			return false
+		}
+		callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus()
+		n.storeBalance(true, true)
+		return true
+	})
+	// Operation without holding the lock
+	for _, cb := range callbacks {
+		cb()
+	}
+	if n.setFlags {
+		if setPriority {
+			n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
+		}
+		// Note: priority flag is automatically removed by the zero priority callback if necessary
+		n.signalPriorityUpdate()
+	}
+	return nil
+}
+
+// RequestServed should be called after serving a request for the given peer
+func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) {
+	n.lock.Lock()
+
+	var (
+		check bool
+		fcost = float64(cost)
+		now   = n.bt.clock.Now()
+	)
+	n.updateBalance(now)
+	if !n.balance.pos.IsZero() {
+		posCost := -int64(fcost * n.posFactor.RequestFactor)
+		if posCost == 0 {
+			fcost = 0
+			newBalance = n.balance.posValue(now)
+		} else {
+			var net int64
+			_, newBalance, net, _ = n.balance.addValue(now, posCost, true, false)
+			if posCost == net {
+				fcost = 0
+			} else {
+				fcost *= 1 - float64(net)/float64(posCost)
+			}
+			check = true
+		}
+	}
+	if fcost > 0 && n.negFactor.RequestFactor != 0 {
+		n.balance.addValue(now, int64(fcost*n.negFactor.RequestFactor), false, false)
+		check = true
+	}
+	n.sumReqCost += cost
+
+	var callbacks []func()
+	if check {
+		callbacks = n.checkCallbacks(now)
+	}
+	n.lock.Unlock()
+
+	if callbacks != nil {
+		n.bt.ns.Operation(func() {
+			for _, cb := range callbacks {
+				cb()
+			}
+		})
+	}
+	return
+}
+
+// priority returns the actual priority based on the current balance
+func (n *nodeBalance) priority(capacity uint64) int64 {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+	return n.balanceToPriority(now, n.balance, capacity)
+}
+
+// EstMinPriority gives a lower estimate for the priority at a given time in the future.
+// An average request cost per time is assumed that is twice the average cost per time
+// in the current session.
+// If update is true then a priority callback is added that turns updateFlag on and off
+// in case the priority goes below the estimated minimum.
+func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+
+	b := n.balance // copy the balance
+	if addBalance != 0 {
+		b.addValue(now, addBalance, true, true)
+	}
+	if future > 0 {
+		var avgReqCost float64
+		dt := time.Duration(n.lastUpdate - n.initTime)
+		if dt > time.Second {
+			avgReqCost = float64(n.sumReqCost) * 2 / float64(dt)
+		}
+		b = n.reducedBalance(b, now, future, capacity, avgReqCost)
+	}
+	if bias > 0 {
+		b = n.reducedBalance(b, now+mclock.AbsTime(future), bias, capacity, 0)
+	}
+	pri := n.balanceToPriority(now, b, capacity)
+	// Ensure that biased estimates are always lower than actual priorities, even if
+	// the bias is very small.
+	// This ensures that two nodes will not ping-pong update signals forever if both of
+	// them have zero estimated priority drop in the projected future.
+	current := n.balanceToPriority(now, n.balance, capacity)
+	if pri >= current {
+		pri = current - 1
+	}
+	if update {
+		n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate)
+	}
+	return pri
+}
+
+// SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of
+// connection while RequestFactor is the price of a request cost unit.
+func (n *nodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) {
+	n.lock.Lock()
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+	n.posFactor, n.negFactor = posFactor, negFactor
+	callbacks := n.checkCallbacks(now)
+	n.lock.Unlock()
+	if callbacks != nil {
+		n.bt.ns.Operation(func() {
+			for _, cb := range callbacks {
+				cb()
+			}
+		})
+	}
+}
+
+// GetPriceFactors returns the price factors
+func (n *nodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+
+	return n.posFactor, n.negFactor
+}
+
+// activate starts time/capacity cost deduction.
+func (n *nodeBalance) activate() {
+	n.bt.updateTotalBalance(n, func() bool {
+		if n.active {
+			return false
+		}
+		n.active = true
+		n.lastUpdate = n.bt.clock.Now()
+		return true
+	})
+}
+
+// deactivate stops time/capacity cost deduction and saves the balances in the database
+func (n *nodeBalance) deactivate() {
+	n.bt.updateTotalBalance(n, func() bool {
+		if !n.active {
+			return false
+		}
+		n.updateBalance(n.bt.clock.Now())
+		if n.updateEvent != nil {
+			n.updateEvent.Stop()
+			n.updateEvent = nil
+		}
+		n.storeBalance(true, true)
+		n.active = false
+		return true
+	})
+}
+
+// updateBalance updates balance based on the time factor
+func (n *nodeBalance) updateBalance(now mclock.AbsTime) {
+	if n.active && now > n.lastUpdate {
+		n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0)
+		n.lastUpdate = now
+	}
+}
+
+// storeBalance stores the positive and/or negative balance of the node in the database
+func (n *nodeBalance) storeBalance(pos, neg bool) {
+	if pos {
+		n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos)
+	}
+	if neg {
+		n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg)
+	}
+}
+
+// addCallback sets up a one-time callback to be called when priority reaches
+// the threshold. If it has already reached the threshold the callback is called
+// immediately.
+// Note: should be called while n.lock is held
+// Note 2: the callback function runs inside a NodeStateMachine operation
+func (n *nodeBalance) addCallback(id int, threshold int64, callback func()) {
+	n.removeCallback(id)
+	idx := 0
+	for idx < n.callbackCount && threshold > n.callbacks[idx].threshold {
+		idx++
+	}
+	for i := n.callbackCount - 1; i >= idx; i-- {
+		n.callbackIndex[n.callbacks[i].id]++
+		n.callbacks[i+1] = n.callbacks[i]
+	}
+	n.callbackCount++
+	n.callbackIndex[id] = idx
+	n.callbacks[idx] = balanceCallback{id, threshold, callback}
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+	n.scheduleCheck(now)
+}
+
+// removeCallback removes the given callback and returns true if it was active
+// Note: should be called while n.lock is held
+func (n *nodeBalance) removeCallback(id int) bool {
+	idx := n.callbackIndex[id]
+	if idx == -1 {
+		return false
+	}
+	n.callbackIndex[id] = -1
+	for i := idx; i < n.callbackCount-1; i++ {
+		n.callbackIndex[n.callbacks[i+1].id]--
+		n.callbacks[i] = n.callbacks[i+1]
+	}
+	n.callbackCount--
+	return true
+}
+
+// checkCallbacks checks whether the threshold of any of the active callbacks
+// have been reached and returns triggered callbacks.
+// Note: checkCallbacks assumes that the balance has been recently updated.
+func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) {
+	if n.callbackCount == 0 || n.capacity == 0 {
+		return
+	}
+	pri := n.balanceToPriority(now, n.balance, n.capacity)
+	for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri {
+		n.callbackCount--
+		n.callbackIndex[n.callbacks[n.callbackCount].id] = -1
+		callbacks = append(callbacks, n.callbacks[n.callbackCount].callback)
+	}
+	n.scheduleCheck(now)
+	return
+}
+
+// scheduleCheck sets up or updates a scheduled event to ensure that it will be called
+// again just after the next threshold has been reached.
+func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) {
+	if n.callbackCount != 0 {
+		d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold)
+		if !ok {
+			n.nextUpdate = 0
+			n.updateAfter(0)
+			return
+		}
+		if n.nextUpdate == 0 || n.nextUpdate > now+mclock.AbsTime(d) {
+			if d > time.Second {
+				// Note: if the scheduled update is not in the very near future then we
+				// schedule the update a bit earlier. This way we do need to update a few
+				// extra times but don't need to reschedule every time a processed request
+				// brings the expected firing time a little bit closer.
+				d = ((d - time.Second) * 7 / 8) + time.Second
+			}
+			n.nextUpdate = now + mclock.AbsTime(d)
+			n.updateAfter(d)
+		}
+	} else {
+		n.nextUpdate = 0
+		n.updateAfter(0)
+	}
+}
+
+// updateAfter schedules a balance update and callback check in the future
+func (n *nodeBalance) updateAfter(dt time.Duration) {
+	if n.updateEvent == nil || n.updateEvent.Stop() {
+		if dt == 0 {
+			n.updateEvent = nil
+		} else {
+			n.updateEvent = n.bt.clock.AfterFunc(dt, func() {
+				var callbacks []func()
+				n.lock.Lock()
+				if n.callbackCount != 0 {
+					now := n.bt.clock.Now()
+					n.updateBalance(now)
+					callbacks = n.checkCallbacks(now)
+				}
+				n.lock.Unlock()
+				if callbacks != nil {
+					n.bt.ns.Operation(func() {
+						for _, cb := range callbacks {
+							cb()
+						}
+					})
+				}
+			})
+		}
+	}
+}
+
+// balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative)
+// Note: this function should run inside a NodeStateMachine operation
+func (n *nodeBalance) balanceExhausted() {
+	n.lock.Lock()
+	n.storeBalance(true, false)
+	n.hasPriority = false
+	n.lock.Unlock()
+	if n.setFlags {
+		n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.priorityFlag, 0)
+	}
+}
+
+// checkPriorityStatus checks whether the node has gained priority status and sets the priority
+// callback and flag if necessary. It assumes that the balance has been recently updated.
+// Note that the priority flag has to be set by the caller after the mutex has been released.
+func (n *nodeBalance) checkPriorityStatus() bool {
+	if !n.hasPriority && !n.balance.pos.IsZero() {
+		n.hasPriority = true
+		n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() })
+		return true
+	}
+	return false
+}
+
+// signalPriorityUpdate signals that the priority fell below the previous minimum estimate
+// Note: this function should run inside a NodeStateMachine operation
+func (n *nodeBalance) signalPriorityUpdate() {
+	n.bt.ns.SetStateSub(n.node, n.bt.setup.updateFlag, nodestate.Flags{}, 0)
+	n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.updateFlag, 0)
+}
+
+// setCapacity updates the capacity value used for priority calculation
+// Note: capacity should never be zero
+// Note 2: this function should run inside a NodeStateMachine operation
+func (n *nodeBalance) setCapacity(capacity uint64) {
+	n.lock.Lock()
+	now := n.bt.clock.Now()
+	n.updateBalance(now)
+	n.capacity = capacity
+	callbacks := n.checkCallbacks(now)
+	n.lock.Unlock()
+	for _, cb := range callbacks {
+		cb()
+	}
+}
+
+// balanceToPriority converts a balance to a priority value. Lower priority means
+// first to disconnect. Positive balance translates to positive priority. If positive
+// balance is zero then negative balance translates to a negative priority.
+func (n *nodeBalance) balanceToPriority(now mclock.AbsTime, b balance, capacity uint64) int64 {
+	pos := b.posValue(now)
+	if pos > 0 {
+		return int64(pos / capacity)
+	}
+	return -int64(b.negValue(now))
+}
+
+// priorityToBalance converts a target priority to a requested balance value.
+// If the priority is negative, then minimal negative balance is returned;
+// otherwise the minimal positive balance is returned.
+func (n *nodeBalance) priorityToBalance(priority int64, capacity uint64) (uint64, uint64) {
+	if priority > 0 {
+		return uint64(priority) * n.capacity, 0
+	}
+	return 0, uint64(-priority)
+}
+
+// reducedBalance estimates the reduced balance at a given time in the fututre based
+// on the given balance, the time factor and an estimated average request cost per time ratio
+func (n *nodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance {
+	// since the costs are applied continuously during the dt time period we calculate
+	// the expiration offset at the middle of the period
+	var (
+		at  = start + mclock.AbsTime(dt/2)
+		dtf = float64(dt)
+	)
+	if !b.pos.IsZero() {
+		factor := n.posFactor.connectionPrice(capacity, avgReqCost)
+		diff := -int64(dtf * factor)
+		_, _, net, _ := b.addValue(at, diff, true, false)
+		if net == diff {
+			dtf = 0
+		} else {
+			dtf += float64(net) / factor
+		}
+	}
+	if dtf > 0 {
+		factor := n.negFactor.connectionPrice(capacity, avgReqCost)
+		b.addValue(at, int64(dtf*factor), false, false)
+	}
+	return b
+}
+
+// timeUntil calculates the remaining time needed to reach a given priority level
+// assuming that no requests are processed until then. If the given level is never
+// reached then (0, false) is returned. If it has already been reached then (0, true)
+// is returned.
+// Note: the function assumes that the balance has been recently updated and
+// calculates the time starting from the last update.
+func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) {
+	var (
+		now                  = n.bt.clock.Now()
+		pos                  = n.balance.posValue(now)
+		targetPos, targetNeg = n.priorityToBalance(priority, n.capacity)
+		diffTime             float64
+	)
+	if pos > 0 {
+		timePrice := n.posFactor.connectionPrice(n.capacity, 0)
+		if timePrice < 1e-100 {
+			return 0, false
+		}
+		if targetPos > 0 {
+			if targetPos > pos {
+				return 0, true
+			}
+			diffTime = float64(pos-targetPos) / timePrice
+			return time.Duration(diffTime), true
+		} else {
+			diffTime = float64(pos) / timePrice
+		}
+	} else {
+		if targetPos > 0 {
+			return 0, true
+		}
+	}
+	neg := n.balance.negValue(now)
+	if targetNeg > neg {
+		timePrice := n.negFactor.connectionPrice(n.capacity, 0)
+		if timePrice < 1e-100 {
+			return 0, false
+		}
+		diffTime += float64(targetNeg-neg) / timePrice
+	}
+	return time.Duration(diffTime), true
+}
diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go
new file mode 100644
index 0000000000..5af89c18ab
--- /dev/null
+++ b/les/vflux/server/balance_test.go
@@ -0,0 +1,439 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package server
+
+import (
+	"math"
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+type zeroExpirer struct{}
+
+func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64)                 {}
+func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {}
+func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64               { return 0 }
+
+type balanceTestClient struct{}
+
+func (client balanceTestClient) FreeClientId() string { return "" }
+
+type balanceTestSetup struct {
+	clock *mclock.Simulated
+	db    ethdb.KeyValueStore
+	ns    *nodestate.NodeStateMachine
+	setup *serverSetup
+	bt    *balanceTracker
+}
+
+func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpirer) *balanceTestSetup {
+	// Initialize and customize the setup for the balance testing
+	clock := &mclock.Simulated{}
+	setup := newServerSetup()
+	setup.clientField = setup.setup.NewField("balancTestClient", reflect.TypeOf(balanceTestClient{}))
+
+	ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
+	if posExp == nil {
+		posExp = zeroExpirer{}
+	}
+	if negExp == nil {
+		negExp = zeroExpirer{}
+	}
+	if db == nil {
+		db = memorydb.New()
+	}
+	bt := newBalanceTracker(ns, setup, db, clock, posExp, negExp)
+	ns.Start()
+	return &balanceTestSetup{
+		clock: clock,
+		db:    db,
+		ns:    ns,
+		setup: setup,
+		bt:    bt,
+	}
+}
+
+func (b *balanceTestSetup) newNode(capacity uint64) *nodeBalance {
+	node := enode.SignNull(&enr.Record{}, enode.ID{})
+	b.ns.SetField(node, b.setup.clientField, balanceTestClient{})
+	if capacity != 0 {
+		b.ns.SetField(node, b.setup.capacityField, capacity)
+	}
+	n, _ := b.ns.GetField(node, b.setup.balanceField).(*nodeBalance)
+	return n
+}
+
+func (b *balanceTestSetup) setBalance(node *nodeBalance, pos, neg uint64) (err error) {
+	b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) {
+		err = balance.SetBalance(pos, neg)
+	})
+	return
+}
+
+func (b *balanceTestSetup) addBalance(node *nodeBalance, add int64) (old, new uint64, err error) {
+	b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) {
+		old, new, err = balance.AddBalance(add)
+	})
+	return
+}
+
+func (b *balanceTestSetup) stop() {
+	b.bt.stop()
+	b.ns.Stop()
+}
+
+func TestAddBalance(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+
+	node := b.newNode(1000)
+	var inputs = []struct {
+		delta     int64
+		expect    [2]uint64
+		total     uint64
+		expectErr bool
+	}{
+		{100, [2]uint64{0, 100}, 100, false},
+		{-100, [2]uint64{100, 0}, 0, false},
+		{-100, [2]uint64{0, 0}, 0, false},
+		{1, [2]uint64{0, 1}, 1, false},
+		{maxBalance, [2]uint64{0, 0}, 0, true},
+	}
+	for _, i := range inputs {
+		old, new, err := b.addBalance(node, i.delta)
+		if i.expectErr {
+			if err == nil {
+				t.Fatalf("Expect get error but nil")
+			}
+			continue
+		} else if err != nil {
+			t.Fatalf("Expect get no error but %v", err)
+		}
+		if old != i.expect[0] || new != i.expect[1] {
+			t.Fatalf("Positive balance mismatch, got %v -> %v", old, new)
+		}
+		if b.bt.TotalTokenAmount() != i.total {
+			t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount())
+		}
+	}
+}
+
+func TestSetBalance(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000)
+
+	var inputs = []struct {
+		pos, neg uint64
+	}{
+		{1000, 0},
+		{0, 1000},
+		{1000, 1000},
+	}
+	for _, i := range inputs {
+		b.setBalance(node, i.pos, i.neg)
+		pos, neg := node.GetBalance()
+		if pos != i.pos {
+			t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos)
+		}
+		if neg != i.neg {
+			t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg)
+		}
+	}
+}
+
+func TestBalanceTimeCost(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000)
+
+	node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+	b.setBalance(node, uint64(time.Minute), 0) // 1 minute time allowance
+
+	var inputs = []struct {
+		runTime time.Duration
+		expPos  uint64
+		expNeg  uint64
+	}{
+		{time.Second, uint64(time.Second * 59), 0},
+		{0, uint64(time.Second * 59), 0},
+		{time.Second * 59, 0, 0},
+		{time.Second, 0, uint64(time.Second)},
+	}
+	for _, i := range inputs {
+		b.clock.Run(i.runTime)
+		if pos, _ := node.GetBalance(); pos != i.expPos {
+			t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
+		}
+		if _, neg := node.GetBalance(); neg != i.expNeg {
+			t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
+		}
+	}
+
+	b.setBalance(node, uint64(time.Minute), 0) // Refill 1 minute time allowance
+	for _, i := range inputs {
+		b.clock.Run(i.runTime)
+		if pos, _ := node.GetBalance(); pos != i.expPos {
+			t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
+		}
+		if _, neg := node.GetBalance(); neg != i.expNeg {
+			t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
+		}
+	}
+}
+
+func TestBalanceReqCost(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000)
+	node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+
+	b.setBalance(node, uint64(time.Minute), 0) // 1 minute time serving time allowance
+	var inputs = []struct {
+		reqCost uint64
+		expPos  uint64
+		expNeg  uint64
+	}{
+		{uint64(time.Second), uint64(time.Second * 59), 0},
+		{0, uint64(time.Second * 59), 0},
+		{uint64(time.Second * 59), 0, 0},
+		{uint64(time.Second), 0, uint64(time.Second)},
+	}
+	for _, i := range inputs {
+		node.RequestServed(i.reqCost)
+		if pos, _ := node.GetBalance(); pos != i.expPos {
+			t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos)
+		}
+		if _, neg := node.GetBalance(); neg != i.expNeg {
+			t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg)
+		}
+	}
+}
+
+func TestBalanceToPriority(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000)
+	node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+
+	var inputs = []struct {
+		pos      uint64
+		neg      uint64
+		priority int64
+	}{
+		{1000, 0, 1},
+		{2000, 0, 2}, // Higher balance, higher priority value
+		{0, 0, 0},
+		{0, 1000, -1000},
+	}
+	for _, i := range inputs {
+		b.setBalance(node, i.pos, i.neg)
+		priority := node.priority(1000)
+		if priority != i.priority {
+			t.Fatalf("priority mismatch, want %v, got %v", i.priority, priority)
+		}
+	}
+}
+
+func TestEstimatedPriority(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000000000)
+	node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+	b.setBalance(node, uint64(time.Minute), 0)
+	var inputs = []struct {
+		runTime    time.Duration // time cost
+		futureTime time.Duration // diff of future time
+		reqCost    uint64        // single request cost
+		priority   int64         // expected estimated priority
+	}{
+		{time.Second, time.Second, 0, 58},
+		{0, time.Second, 0, 58},
+
+		// 2 seconds time cost, 1 second estimated time cost, 10^9 request cost,
+		// 10^9 estimated request cost per second.
+		{time.Second, time.Second, 1000000000, 55},
+
+		// 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost,
+		// 4*10^9 estimated request cost.
+		{time.Second, 3 * time.Second, 1000000000, 48},
+
+		// All positive balance is used up
+		{time.Second * 55, 0, 0, -1},
+
+		// 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec.
+		{0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29},
+	}
+	for _, i := range inputs {
+		b.clock.Run(i.runTime)
+		node.RequestServed(i.reqCost)
+		priority := node.estimatePriority(1000000000, 0, i.futureTime, 0, false)
+		if priority != i.priority {
+			t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority)
+		}
+	}
+}
+
+func TestPostiveBalanceCounting(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+
+	var nodes []*nodeBalance
+	for i := 0; i < 100; i += 1 {
+		node := b.newNode(1000000)
+		node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+		nodes = append(nodes, node)
+	}
+
+	// Allocate service token
+	var sum uint64
+	for i := 0; i < 100; i += 1 {
+		amount := int64(rand.Intn(100) + 100)
+		b.addBalance(nodes[i], amount)
+		sum += uint64(amount)
+	}
+	if b.bt.TotalTokenAmount() != sum {
+		t.Fatalf("Invalid token amount")
+	}
+
+	// Change client status
+	for i := 0; i < 100; i += 1 {
+		if rand.Intn(2) == 0 {
+			b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1))
+		}
+	}
+	if b.bt.TotalTokenAmount() != sum {
+		t.Fatalf("Invalid token amount")
+	}
+	for i := 0; i < 100; i += 1 {
+		if rand.Intn(2) == 0 {
+			b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1))
+		}
+	}
+	if b.bt.TotalTokenAmount() != sum {
+		t.Fatalf("Invalid token amount")
+	}
+}
+
+func TestCallbackChecking(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000000)
+	node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+
+	var inputs = []struct {
+		priority int64
+		expDiff  time.Duration
+	}{
+		{500, time.Millisecond * 500},
+		{0, time.Second},
+		{-int64(time.Second), 2 * time.Second},
+	}
+	b.setBalance(node, uint64(time.Second), 0)
+	for _, i := range inputs {
+		diff, _ := node.timeUntil(i.priority)
+		if diff != i.expDiff {
+			t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff)
+		}
+	}
+}
+
+func TestCallback(t *testing.T) {
+	b := newBalanceTestSetup(nil, nil, nil)
+	defer b.stop()
+	node := b.newNode(1000)
+	node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1})
+
+	callCh := make(chan struct{}, 1)
+	b.setBalance(node, uint64(time.Minute), 0)
+	node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} })
+
+	b.clock.Run(time.Minute)
+	select {
+	case <-callCh:
+	case <-time.NewTimer(time.Second).C:
+		t.Fatalf("Callback hasn't been called yet")
+	}
+
+	b.setBalance(node, uint64(time.Minute), 0)
+	node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} })
+	node.removeCallback(balanceCallbackZero)
+
+	b.clock.Run(time.Minute)
+	select {
+	case <-callCh:
+		t.Fatalf("Callback shouldn't be called")
+	case <-time.NewTimer(time.Millisecond * 100).C:
+	}
+}
+
+func TestBalancePersistence(t *testing.T) {
+	posExp := &utils.Expirer{}
+	negExp := &utils.Expirer{}
+	posExp.SetRate(0, math.Log(2)/float64(time.Hour*2)) // halves every two hours
+	negExp.SetRate(0, math.Log(2)/float64(time.Hour))   // halves every hour
+	setup := newBalanceTestSetup(nil, posExp, negExp)
+
+	exp := func(balance *nodeBalance, expPos, expNeg uint64) {
+		pos, neg := balance.GetBalance()
+		if pos != expPos {
+			t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos)
+		}
+		if neg != expNeg {
+			t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos)
+		}
+	}
+	expTotal := func(expTotal uint64) {
+		total := setup.bt.TotalTokenAmount()
+		if total != expTotal {
+			t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total)
+		}
+	}
+
+	expTotal(0)
+	balance := setup.newNode(0)
+	expTotal(0)
+	setup.setBalance(balance, 16000000000, 16000000000)
+	exp(balance, 16000000000, 16000000000)
+	expTotal(16000000000)
+
+	setup.clock.Run(time.Hour * 2)
+	exp(balance, 8000000000, 4000000000)
+	expTotal(8000000000)
+	setup.stop()
+
+	// Test the functionalities after restart
+	setup = newBalanceTestSetup(setup.db, posExp, negExp)
+	expTotal(8000000000)
+	balance = setup.newNode(0)
+	exp(balance, 8000000000, 4000000000)
+	expTotal(8000000000)
+	setup.clock.Run(time.Hour * 2)
+	exp(balance, 4000000000, 1000000000)
+	expTotal(4000000000)
+	setup.stop()
+}
diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go
new file mode 100644
index 0000000000..746697a8c7
--- /dev/null
+++ b/les/vflux/server/balance_tracker.go
@@ -0,0 +1,299 @@
+// 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 .
+
+package server
+
+import (
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+const (
+	posThreshold             = 1000000         // minimum positive balance that is persisted in the database
+	negThreshold             = 1000000         // minimum negative balance that is persisted in the database
+	persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence
+)
+
+// balanceTracker tracks positive and negative balances for connected nodes.
+// After clientField is set externally, a nodeBalance is created and previous
+// balance values are loaded from the database. Both balances are exponentially expired
+// values. Costs are deducted from the positive balance if present, otherwise added to
+// the negative balance. If the capacity is non-zero then a time cost is applied
+// continuously while individual request costs are applied immediately.
+// The two balances are translated into a single priority value that also depends
+// on the actual capacity.
+type balanceTracker struct {
+	setup          *serverSetup
+	clock          mclock.Clock
+	lock           sync.Mutex
+	ns             *nodestate.NodeStateMachine
+	ndb            *nodeDB
+	posExp, negExp utils.ValueExpirer
+
+	posExpTC, negExpTC                   uint64
+	defaultPosFactors, defaultNegFactors PriceFactors
+
+	active, inactive utils.ExpiredValue
+	balanceTimer     *utils.UpdateTimer
+	quit             chan struct{}
+}
+
+// newBalanceTracker creates a new balanceTracker
+func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *balanceTracker {
+	ndb := newNodeDB(db, clock)
+	bt := &balanceTracker{
+		ns:           ns,
+		setup:        setup,
+		ndb:          ndb,
+		clock:        clock,
+		posExp:       posExp,
+		negExp:       negExp,
+		balanceTimer: utils.NewUpdateTimer(clock, time.Second*10),
+		quit:         make(chan struct{}),
+	}
+	posOffset, negOffset := bt.ndb.getExpiration()
+	posExp.SetLogOffset(clock.Now(), posOffset)
+	negExp.SetLogOffset(clock.Now(), negOffset)
+
+	// Load all persisted balance entries of priority nodes,
+	// calculate the total number of issued service tokens.
+	bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool {
+		bt.inactive.AddExp(balance)
+		return true
+	})
+
+	ns.SubscribeField(bt.setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
+		n, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance)
+		if n == nil {
+			return
+		}
+
+		ov, _ := oldValue.(uint64)
+		nv, _ := newValue.(uint64)
+		if ov == 0 && nv != 0 {
+			n.activate()
+		}
+		if nv != 0 {
+			n.setCapacity(nv)
+		}
+		if ov != 0 && nv == 0 {
+			n.deactivate()
+		}
+	})
+	ns.SubscribeField(bt.setup.clientField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
+		type peer interface {
+			FreeClientId() string
+		}
+		if newValue != nil {
+			n := bt.newNodeBalance(node, newValue.(peer).FreeClientId(), true)
+			bt.lock.Lock()
+			n.SetPriceFactors(bt.defaultPosFactors, bt.defaultNegFactors)
+			bt.lock.Unlock()
+			ns.SetFieldSub(node, bt.setup.balanceField, n)
+		} else {
+			ns.SetStateSub(node, nodestate.Flags{}, bt.setup.priorityFlag, 0)
+			if b, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance); b != nil {
+				b.deactivate()
+			}
+			ns.SetFieldSub(node, bt.setup.balanceField, nil)
+		}
+	})
+
+	// The positive and negative balances of clients are stored in database
+	// and both of these decay exponentially over time. Delete them if the
+	// value is small enough.
+	bt.ndb.evictCallBack = bt.canDropBalance
+
+	go func() {
+		for {
+			select {
+			case <-clock.After(persistExpirationRefresh):
+				now := clock.Now()
+				bt.ndb.setExpiration(posExp.LogOffset(now), negExp.LogOffset(now))
+			case <-bt.quit:
+				return
+			}
+		}
+	}()
+	return bt
+}
+
+// Stop saves expiration offset and unsaved node balances and shuts balanceTracker down
+func (bt *balanceTracker) stop() {
+	now := bt.clock.Now()
+	bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now))
+	close(bt.quit)
+	bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
+		if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok {
+			n.lock.Lock()
+			n.storeBalance(true, true)
+			n.lock.Unlock()
+			bt.ns.SetField(node, bt.setup.balanceField, nil)
+		}
+	})
+	bt.ndb.close()
+}
+
+// TotalTokenAmount returns the current total amount of service tokens in existence
+func (bt *balanceTracker) TotalTokenAmount() uint64 {
+	bt.lock.Lock()
+	defer bt.lock.Unlock()
+
+	bt.balanceTimer.Update(func(_ time.Duration) bool {
+		bt.active = utils.ExpiredValue{}
+		bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
+			if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok && n.active {
+				pos, _ := n.GetRawBalance()
+				bt.active.AddExp(pos)
+			}
+		})
+		return true
+	})
+	total := bt.active
+	total.AddExp(bt.inactive)
+	return total.Value(bt.posExp.LogOffset(bt.clock.Now()))
+}
+
+// GetPosBalanceIDs lists node IDs with an associated positive balance
+func (bt *balanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) {
+	return bt.ndb.getPosBalanceIDs(start, stop, maxCount)
+}
+
+// SetDefaultFactors sets the default price factors applied to subsequently connected clients
+func (bt *balanceTracker) SetDefaultFactors(posFactors, negFactors PriceFactors) {
+	bt.lock.Lock()
+	bt.defaultPosFactors = posFactors
+	bt.defaultNegFactors = negFactors
+	bt.lock.Unlock()
+}
+
+// SetExpirationTCs sets positive and negative token expiration time constants.
+// Specified in seconds, 0 means infinite (no expiration).
+func (bt *balanceTracker) SetExpirationTCs(pos, neg uint64) {
+	bt.lock.Lock()
+	defer bt.lock.Unlock()
+
+	bt.posExpTC, bt.negExpTC = pos, neg
+	now := bt.clock.Now()
+	if pos > 0 {
+		bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second)))
+	} else {
+		bt.posExp.SetRate(now, 0)
+	}
+	if neg > 0 {
+		bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second)))
+	} else {
+		bt.negExp.SetRate(now, 0)
+	}
+}
+
+// GetExpirationTCs returns the current positive and negative token expiration
+// time constants
+func (bt *balanceTracker) GetExpirationTCs() (pos, neg uint64) {
+	bt.lock.Lock()
+	defer bt.lock.Unlock()
+
+	return bt.posExpTC, bt.negExpTC
+}
+
+// BalanceOperation allows atomic operations on the balance of a node regardless of whether
+// it is currently connected or not
+func (bt *balanceTracker) BalanceOperation(id enode.ID, connAddress string, cb func(AtomicBalanceOperator)) {
+	bt.ns.Operation(func() {
+		var nb *nodeBalance
+		if node := bt.ns.GetNode(id); node != nil {
+			nb, _ = bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance)
+		} else {
+			node = enode.SignNull(&enr.Record{}, id)
+			nb = bt.newNodeBalance(node, connAddress, false)
+		}
+		cb(nb)
+	})
+}
+
+// newNodeBalance loads balances from the database and creates a nodeBalance instance
+// for the given node. It also sets the priorityFlag and adds balanceCallbackZero if
+// the node has a positive balance.
+// Note: this function should run inside a NodeStateMachine operation
+func (bt *balanceTracker) newNodeBalance(node *enode.Node, connAddress string, setFlags bool) *nodeBalance {
+	pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false)
+	nb := bt.ndb.getOrNewBalance([]byte(connAddress), true)
+	n := &nodeBalance{
+		bt:          bt,
+		node:        node,
+		setFlags:    setFlags,
+		connAddress: connAddress,
+		balance:     balance{pos: pb, neg: nb, posExp: bt.posExp, negExp: bt.negExp},
+		initTime:    bt.clock.Now(),
+		lastUpdate:  bt.clock.Now(),
+	}
+	for i := range n.callbackIndex {
+		n.callbackIndex[i] = -1
+	}
+	if setFlags && n.checkPriorityStatus() {
+		n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0)
+	}
+	return n
+}
+
+// storeBalance stores either a positive or a negative balance in the database
+func (bt *balanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) {
+	if bt.canDropBalance(bt.clock.Now(), neg, value) {
+		bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly.
+	} else {
+		bt.ndb.setBalance(id, neg, value)
+	}
+}
+
+// canDropBalance tells whether a positive or negative balance is below the threshold
+// and therefore can be dropped from the database
+func (bt *balanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool {
+	if neg {
+		return b.Value(bt.negExp.LogOffset(now)) <= negThreshold
+	}
+	return b.Value(bt.posExp.LogOffset(now)) <= posThreshold
+}
+
+// updateTotalBalance adjusts the total balance after executing given callback.
+func (bt *balanceTracker) updateTotalBalance(n *nodeBalance, callback func() bool) {
+	bt.lock.Lock()
+	defer bt.lock.Unlock()
+
+	n.lock.Lock()
+	defer n.lock.Unlock()
+
+	original, active := n.balance.pos, n.active
+	if !callback() {
+		return
+	}
+	if active {
+		bt.active.SubExp(original)
+	} else {
+		bt.inactive.SubExp(original)
+	}
+	if n.active {
+		bt.active.AddExp(n.balance.pos)
+	} else {
+		bt.inactive.AddExp(n.balance.pos)
+	}
+}
diff --git a/les/vflux/server/clientdb.go b/les/vflux/server/clientdb.go
new file mode 100644
index 0000000000..30cd9a6528
--- /dev/null
+++ b/les/vflux/server/clientdb.go
@@ -0,0 +1,250 @@
+// 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 .
+
+package server
+
+import (
+	"bytes"
+	"encoding/binary"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/rlp"
+	lru "github.com/hashicorp/golang-lru"
+)
+
+const (
+	balanceCacheLimit = 8192 // the maximum number of cached items in service token balance queue
+
+	// nodeDBVersion is the version identifier of the node data in db
+	//
+	// Changelog:
+	// Version 0 => 1
+	// * Replace `lastTotal` with `meta` in positive balance: version 0=>1
+	//
+	// Version 1 => 2
+	// * Positive Balance and negative balance is changed:
+	// * Cumulative time is replaced with expiration
+	nodeDBVersion = 2
+
+	// dbCleanupCycle is the cycle of db for useless data cleanup
+	dbCleanupCycle = time.Hour
+)
+
+var (
+	positiveBalancePrefix = []byte("pb:")         // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance
+	negativeBalancePrefix = []byte("nb:")         // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance
+	expirationKey         = []byte("expiration:") // dbVersion(uint16 big endian) + expirationKey -> posExp, negExp
+)
+
+type nodeDB struct {
+	db            ethdb.KeyValueStore
+	cache         *lru.Cache
+	auxbuf        []byte                                              // 37-byte auxiliary buffer for key encoding
+	verbuf        [2]byte                                             // 2-byte auxiliary buffer for db version
+	evictCallBack func(mclock.AbsTime, bool, utils.ExpiredValue) bool // Callback to determine whether the balance can be evicted.
+	clock         mclock.Clock
+	closeCh       chan struct{}
+	cleanupHook   func() // Test hook used for testing
+}
+
+func newNodeDB(db ethdb.KeyValueStore, clock mclock.Clock) *nodeDB {
+	cache, _ := lru.New(balanceCacheLimit)
+	ndb := &nodeDB{
+		db:      db,
+		cache:   cache,
+		auxbuf:  make([]byte, 37),
+		clock:   clock,
+		closeCh: make(chan struct{}),
+	}
+	binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion))
+	go ndb.expirer()
+	return ndb
+}
+
+func (db *nodeDB) close() {
+	close(db.closeCh)
+}
+
+func (db *nodeDB) getPrefix(neg bool) []byte {
+	prefix := positiveBalancePrefix
+	if neg {
+		prefix = negativeBalancePrefix
+	}
+	return append(db.verbuf[:], prefix...)
+}
+
+func (db *nodeDB) key(id []byte, neg bool) []byte {
+	prefix := positiveBalancePrefix
+	if neg {
+		prefix = negativeBalancePrefix
+	}
+	if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) {
+		db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...)
+	}
+	copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:])
+	copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix)
+	copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id)
+	return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)]
+}
+
+func (db *nodeDB) getExpiration() (utils.Fixed64, utils.Fixed64) {
+	blob, err := db.db.Get(append(db.verbuf[:], expirationKey...))
+	if err != nil || len(blob) != 16 {
+		return 0, 0
+	}
+	return utils.Fixed64(binary.BigEndian.Uint64(blob[:8])), utils.Fixed64(binary.BigEndian.Uint64(blob[8:16]))
+}
+
+func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) {
+	var buff [16]byte
+	binary.BigEndian.PutUint64(buff[:8], uint64(pos))
+	binary.BigEndian.PutUint64(buff[8:16], uint64(neg))
+	db.db.Put(append(db.verbuf[:], expirationKey...), buff[:16])
+}
+
+func (db *nodeDB) getOrNewBalance(id []byte, neg bool) utils.ExpiredValue {
+	key := db.key(id, neg)
+	item, exist := db.cache.Get(string(key))
+	if exist {
+		return item.(utils.ExpiredValue)
+	}
+	var b utils.ExpiredValue
+	enc, err := db.db.Get(key)
+	if err != nil || len(enc) == 0 {
+		return b
+	}
+	if err := rlp.DecodeBytes(enc, &b); err != nil {
+		log.Crit("Failed to decode positive balance", "err", err)
+	}
+	db.cache.Add(string(key), b)
+	return b
+}
+
+func (db *nodeDB) setBalance(id []byte, neg bool, b utils.ExpiredValue) {
+	key := db.key(id, neg)
+	enc, err := rlp.EncodeToBytes(&(b))
+	if err != nil {
+		log.Crit("Failed to encode positive balance", "err", err)
+	}
+	db.db.Put(key, enc)
+	db.cache.Add(string(key), b)
+}
+
+func (db *nodeDB) delBalance(id []byte, neg bool) {
+	key := db.key(id, neg)
+	db.db.Delete(key)
+	db.cache.Remove(string(key))
+}
+
+// getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts
+// with a positive balance
+func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) {
+	if maxCount <= 0 {
+		return
+	}
+	prefix := db.getPrefix(false)
+	keylen := len(prefix) + len(enode.ID{})
+
+	it := db.db.NewIterator(prefix, start.Bytes())
+	defer it.Release()
+
+	for it.Next() {
+		var id enode.ID
+		if len(it.Key()) != keylen {
+			return
+		}
+		copy(id[:], it.Key()[keylen-len(id):])
+		if bytes.Compare(id.Bytes(), stop.Bytes()) >= 0 {
+			return
+		}
+		result = append(result, id)
+		if len(result) == maxCount {
+			return
+		}
+	}
+	return
+}
+
+// forEachBalance iterates all balances and passes values to callback.
+func (db *nodeDB) forEachBalance(neg bool, callback func(id enode.ID, balance utils.ExpiredValue) bool) {
+	prefix := db.getPrefix(neg)
+	keylen := len(prefix) + len(enode.ID{})
+
+	it := db.db.NewIterator(prefix, nil)
+	defer it.Release()
+
+	for it.Next() {
+		var id enode.ID
+		if len(it.Key()) != keylen {
+			return
+		}
+		copy(id[:], it.Key()[keylen-len(id):])
+
+		var b utils.ExpiredValue
+		if err := rlp.DecodeBytes(it.Value(), &b); err != nil {
+			continue
+		}
+		if !callback(id, b) {
+			return
+		}
+	}
+}
+
+func (db *nodeDB) expirer() {
+	for {
+		select {
+		case <-db.clock.After(dbCleanupCycle):
+			db.expireNodes()
+		case <-db.closeCh:
+			return
+		}
+	}
+}
+
+// expireNodes iterates the whole node db and checks whether the
+// token balances can be deleted.
+func (db *nodeDB) expireNodes() {
+	var (
+		visited int
+		deleted int
+		start   = time.Now()
+	)
+	for _, neg := range []bool{false, true} {
+		iter := db.db.NewIterator(db.getPrefix(neg), nil)
+		for iter.Next() {
+			visited++
+			var balance utils.ExpiredValue
+			if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil {
+				log.Crit("Failed to decode negative balance", "err", err)
+			}
+			if db.evictCallBack != nil && db.evictCallBack(db.clock.Now(), neg, balance) {
+				deleted++
+				db.db.Delete(iter.Key())
+			}
+		}
+	}
+	// Invoke testing hook if it's not nil.
+	if db.cleanupHook != nil {
+		db.cleanupHook()
+	}
+	log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start)))
+}
diff --git a/les/vflux/server/clientdb_test.go b/les/vflux/server/clientdb_test.go
new file mode 100644
index 0000000000..353d84aead
--- /dev/null
+++ b/les/vflux/server/clientdb_test.go
@@ -0,0 +1,144 @@
+// 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 .
+
+package server
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+func expval(v uint64) utils.ExpiredValue {
+	return utils.ExpiredValue{Base: v}
+}
+
+func TestNodeDB(t *testing.T) {
+	ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{})
+	defer ndb.close()
+
+	var cases = []struct {
+		id       enode.ID
+		ip       string
+		balance  utils.ExpiredValue
+		positive bool
+	}{
+		{enode.ID{0x00, 0x01, 0x02}, "", expval(100), true},
+		{enode.ID{0x00, 0x01, 0x02}, "", expval(200), true},
+		{enode.ID{}, "127.0.0.1", expval(100), false},
+		{enode.ID{}, "127.0.0.1", expval(200), false},
+	}
+	for _, c := range cases {
+		if c.positive {
+			ndb.setBalance(c.id.Bytes(), false, c.balance)
+			if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, c.balance) {
+				t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance, pb)
+			}
+		} else {
+			ndb.setBalance([]byte(c.ip), true, c.balance)
+			if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, c.balance) {
+				t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance, nb)
+			}
+		}
+	}
+	for _, c := range cases {
+		if c.positive {
+			ndb.delBalance(c.id.Bytes(), false)
+			if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, utils.ExpiredValue{}) {
+				t.Fatalf("Positive balance mismatch, want %v, got %v", utils.ExpiredValue{}, pb)
+			}
+		} else {
+			ndb.delBalance([]byte(c.ip), true)
+			if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, utils.ExpiredValue{}) {
+				t.Fatalf("Negative balance mismatch, want %v, got %v", utils.ExpiredValue{}, nb)
+			}
+		}
+	}
+	posExp, negExp := utils.Fixed64(1000), utils.Fixed64(2000)
+	ndb.setExpiration(posExp, negExp)
+	if pos, neg := ndb.getExpiration(); pos != posExp || neg != negExp {
+		t.Fatalf("Expiration mismatch, want %v / %v, got %v / %v", posExp, negExp, pos, neg)
+	}
+	/*	curBalance := currencyBalance{typ: "ETH", amount: 10000}
+		ndb.setCurrencyBalance(enode.ID{0x01, 0x02}, curBalance)
+		if got := ndb.getCurrencyBalance(enode.ID{0x01, 0x02}); !reflect.DeepEqual(got, curBalance) {
+			t.Fatalf("Currency balance mismatch, want %v, got %v", curBalance, got)
+		}*/
+}
+
+func TestNodeDBExpiration(t *testing.T) {
+	var (
+		iterated int
+		done     = make(chan struct{}, 1)
+	)
+	callback := func(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool {
+		iterated += 1
+		return true
+	}
+	clock := &mclock.Simulated{}
+	ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock)
+	defer ndb.close()
+	ndb.evictCallBack = callback
+	ndb.cleanupHook = func() { done <- struct{}{} }
+
+	var cases = []struct {
+		id      []byte
+		neg     bool
+		balance utils.ExpiredValue
+	}{
+		{[]byte{0x01, 0x02}, false, expval(1)},
+		{[]byte{0x03, 0x04}, false, expval(1)},
+		{[]byte{0x05, 0x06}, false, expval(1)},
+		{[]byte{0x07, 0x08}, false, expval(1)},
+
+		{[]byte("127.0.0.1"), true, expval(1)},
+		{[]byte("127.0.0.2"), true, expval(1)},
+		{[]byte("127.0.0.3"), true, expval(1)},
+		{[]byte("127.0.0.4"), true, expval(1)},
+	}
+	for _, c := range cases {
+		ndb.setBalance(c.id, c.neg, c.balance)
+	}
+	clock.WaitForTimers(1)
+	clock.Run(time.Hour + time.Minute)
+	select {
+	case <-done:
+	case <-time.NewTimer(time.Second).C:
+		t.Fatalf("timeout")
+	}
+	if iterated != 8 {
+		t.Fatalf("Failed to evict useless balances, want %v, got %d", 8, iterated)
+	}
+
+	for _, c := range cases {
+		ndb.setBalance(c.id, c.neg, c.balance)
+	}
+	clock.WaitForTimers(1)
+	clock.Run(time.Hour + time.Minute)
+	select {
+	case <-done:
+	case <-time.NewTimer(time.Second).C:
+		t.Fatalf("timeout")
+	}
+	if iterated != 16 {
+		t.Fatalf("Failed to evict useless balances, want %v, got %d", 16, iterated)
+	}
+}
diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go
new file mode 100644
index 0000000000..351961b74e
--- /dev/null
+++ b/les/vflux/server/clientpool.go
@@ -0,0 +1,327 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package server
+
+import (
+	"errors"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/les/vflux"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+var (
+	ErrNotConnected    = errors.New("client not connected")
+	ErrNoPriority      = errors.New("priority too low to raise capacity")
+	ErrCantFindMaximum = errors.New("Unable to find maximum allowed capacity")
+)
+
+// ClientPool implements a client database that assigns a priority to each client
+// based on a positive and negative balance. Positive balance is externally assigned
+// to prioritized clients and is decreased with connection time and processed
+// requests (unless the price factors are zero). If the positive balance is zero
+// then negative balance is accumulated.
+//
+// Balance tracking and priority calculation for connected clients is done by
+// balanceTracker. PriorityQueue ensures that clients with the lowest positive or
+// highest negative balance get evicted when the total capacity allowance is full
+// and new clients with a better balance want to connect.
+//
+// Already connected nodes receive a small bias in their favor in order to avoid
+// accepting and instantly kicking out clients. In theory, we try to ensure that
+// each client can have several minutes of connection time.
+//
+// Balances of disconnected clients are stored in nodeDB including positive balance
+// and negative banalce. Boeth positive balance and negative balance will decrease
+// exponentially. If the balance is low enough, then the record will be dropped.
+type ClientPool struct {
+	*priorityPool
+	*balanceTracker
+
+	setup  *serverSetup
+	clock  mclock.Clock
+	closed bool
+	ns     *nodestate.NodeStateMachine
+	synced func() bool
+
+	lock          sync.RWMutex
+	connectedBias time.Duration
+
+	minCap     uint64      // the minimal capacity value allowed for any client
+	capReqNode *enode.Node // node that is requesting capacity change; only used inside NSM operation
+}
+
+// clientPeer represents a peer in the client pool. None of the callbacks should block.
+type clientPeer interface {
+	Node() *enode.Node
+	FreeClientId() string                         // unique id for non-priority clients (typically a prefix of the network address)
+	InactiveAllowance() time.Duration             // disconnection timeout for inactive non-priority peers
+	UpdateCapacity(newCap uint64, requested bool) // signals a capacity update (requested is true if it is a result of a SetCapacity call on the given peer
+	Disconnect()                                  // initiates disconnection (Unregister should always be called)
+}
+
+// NewClientPool creates a new client pool
+func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias time.Duration, clock mclock.Clock, synced func() bool) *ClientPool {
+	setup := newServerSetup()
+	ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
+	cp := &ClientPool{
+		priorityPool:   newPriorityPool(ns, setup, clock, minCap, connectedBias, 4, 100),
+		balanceTracker: newBalanceTracker(ns, setup, balanceDb, clock, &utils.Expirer{}, &utils.Expirer{}),
+		setup:          setup,
+		ns:             ns,
+		clock:          clock,
+		minCap:         minCap,
+		connectedBias:  connectedBias,
+		synced:         synced,
+	}
+
+	ns.SubscribeState(nodestate.MergeFlags(setup.activeFlag, setup.inactiveFlag, setup.priorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
+		if newState.Equals(setup.inactiveFlag) {
+			// set timeout for non-priority inactive client
+			var timeout time.Duration
+			if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok {
+				timeout = c.InactiveAllowance()
+			}
+			ns.AddTimeout(node, setup.inactiveFlag, timeout)
+		}
+		if oldState.Equals(setup.inactiveFlag) && newState.Equals(setup.inactiveFlag.Or(setup.priorityFlag)) {
+			ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) // priority gained; remove timeout
+		}
+		if newState.Equals(setup.activeFlag) {
+			// active with no priority; limit capacity to minCap
+			cap, _ := ns.GetField(node, setup.capacityField).(uint64)
+			if cap > minCap {
+				cp.requestCapacity(node, minCap, minCap, 0)
+			}
+		}
+		if newState.Equals(nodestate.Flags{}) {
+			if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok {
+				c.Disconnect()
+			}
+		}
+	})
+
+	ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
+		if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok {
+			newCap, _ := newValue.(uint64)
+			c.UpdateCapacity(newCap, node == cp.capReqNode)
+		}
+	})
+
+	// add metrics
+	cp.ns.SubscribeState(nodestate.MergeFlags(cp.setup.activeFlag, cp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
+		if oldState.IsEmpty() && !newState.IsEmpty() {
+			clientConnectedMeter.Mark(1)
+		}
+		if !oldState.IsEmpty() && newState.IsEmpty() {
+			clientDisconnectedMeter.Mark(1)
+		}
+		if oldState.HasNone(cp.setup.activeFlag) && oldState.HasAll(cp.setup.activeFlag) {
+			clientActivatedMeter.Mark(1)
+		}
+		if oldState.HasAll(cp.setup.activeFlag) && oldState.HasNone(cp.setup.activeFlag) {
+			clientDeactivatedMeter.Mark(1)
+		}
+		_, connected := cp.Active()
+		totalConnectedGauge.Update(int64(connected))
+	})
+	return cp
+}
+
+// Start starts the client pool. Should be called before Register/Unregister.
+func (cp *ClientPool) Start() {
+	cp.ns.Start()
+}
+
+// Stop shuts the client pool down. The clientPeer interface callbacks will not be called
+// after Stop. Register calls will return nil.
+func (cp *ClientPool) Stop() {
+	cp.balanceTracker.stop()
+	cp.ns.Stop()
+}
+
+// Register registers the peer into the client pool. If the peer has insufficient
+// priority and remains inactive for longer than the allowed timeout then it will be
+// disconnected by calling the Disconnect function of the clientPeer interface.
+func (cp *ClientPool) Register(peer clientPeer) ConnectedBalance {
+	cp.ns.SetField(peer.Node(), cp.setup.clientField, peerWrapper{peer})
+	balance, _ := cp.ns.GetField(peer.Node(), cp.setup.balanceField).(*nodeBalance)
+	return balance
+}
+
+// Unregister removes the peer from the client pool
+func (cp *ClientPool) Unregister(peer clientPeer) {
+	cp.ns.SetField(peer.Node(), cp.setup.clientField, nil)
+}
+
+// setConnectedBias sets the connection bias, which is applied to already connected clients
+// So that already connected client won't be kicked out very soon and we can ensure all
+// connected clients can have enough time to request or sync some data.
+func (cp *ClientPool) SetConnectedBias(bias time.Duration) {
+	cp.lock.Lock()
+	cp.connectedBias = bias
+	cp.setActiveBias(bias)
+	cp.lock.Unlock()
+}
+
+// SetCapacity sets the assigned capacity of a connected client
+func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Duration, requested bool) (capacity uint64, err error) {
+	cp.lock.RLock()
+	if cp.connectedBias > bias {
+		bias = cp.connectedBias
+	}
+	cp.lock.RUnlock()
+
+	cp.ns.Operation(func() {
+		balance, _ := cp.ns.GetField(node, cp.setup.balanceField).(*nodeBalance)
+		if balance == nil {
+			err = ErrNotConnected
+			return
+		}
+		capacity, _ = cp.ns.GetField(node, cp.setup.capacityField).(uint64)
+		if capacity == 0 {
+			// if the client is inactive then it has insufficient priority for the minimal capacity
+			// (will be activated automatically with minCap when possible)
+			return
+		}
+		if reqCap < cp.minCap {
+			// can't request less than minCap; switching between 0 (inactive state) and minCap is
+			// performed by the server automatically as soon as necessary/possible
+			reqCap = cp.minCap
+		}
+		if reqCap > cp.minCap && cp.ns.GetState(node).HasNone(cp.setup.priorityFlag) {
+			err = ErrNoPriority
+			return
+		}
+		if reqCap == capacity {
+			return
+		}
+		if requested {
+			// mark the requested node so that the UpdateCapacity callback can signal
+			// whether the update is the direct result of a SetCapacity call on the given node
+			cp.capReqNode = node
+			defer func() {
+				cp.capReqNode = nil
+			}()
+		}
+
+		var minTarget, maxTarget uint64
+		if reqCap > capacity {
+			// Estimate maximum available capacity at the current priority level and request
+			// the estimated amount.
+			// Note: requestCapacity could find the highest available capacity between the
+			// current and the requested capacity but it could cost a lot of iterations with
+			// fine step adjustment if the requested capacity is very high. By doing a quick
+			// estimation of the maximum available capacity based on the capacity curve we
+			// can limit the number of required iterations.
+			curve := cp.getCapacityCurve().exclude(node.ID())
+			maxTarget = curve.maxCapacity(func(capacity uint64) int64 {
+				return balance.estimatePriority(capacity, 0, 0, bias, false)
+			})
+			if maxTarget < reqCap {
+				return
+			}
+			maxTarget = reqCap
+
+			// Specify a narrow target range that allows a limited number of fine step
+			// iterations
+			minTarget = maxTarget - maxTarget/20
+			if minTarget < capacity {
+				minTarget = capacity
+			}
+		} else {
+			minTarget, maxTarget = reqCap, reqCap
+		}
+		if newCap := cp.requestCapacity(node, minTarget, maxTarget, bias); newCap >= minTarget && newCap <= maxTarget {
+			capacity = newCap
+			return
+		}
+		// we should be able to find the maximum allowed capacity in a few iterations
+		log.Error("Unable to find maximum allowed capacity")
+		err = ErrCantFindMaximum
+	})
+	return
+}
+
+// serveCapQuery serves a vflux capacity query. It receives multiple token amount values
+// and a bias time value. For each given token amount it calculates the maximum achievable
+// capacity in case the amount is added to the balance.
+func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte {
+	var req vflux.CapacityQueryReq
+	if rlp.DecodeBytes(data, &req) != nil {
+		return nil
+	}
+	if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen {
+		return nil
+	}
+	result := make(vflux.CapacityQueryReply, len(req.AddTokens))
+	if !cp.synced() {
+		capacityQueryZeroMeter.Mark(1)
+		reply, _ := rlp.EncodeToBytes(&result)
+		return reply
+	}
+
+	bias := time.Second * time.Duration(req.Bias)
+	cp.lock.RLock()
+	if cp.connectedBias > bias {
+		bias = cp.connectedBias
+	}
+	cp.lock.RUnlock()
+
+	// use capacityCurve to answer request for multiple newly bought token amounts
+	curve := cp.getCapacityCurve().exclude(id)
+	cp.BalanceOperation(id, freeID, func(balance AtomicBalanceOperator) {
+		pb, _ := balance.GetBalance()
+		for i, addTokens := range req.AddTokens {
+			add := addTokens.Int64()
+			result[i] = curve.maxCapacity(func(capacity uint64) int64 {
+				return balance.estimatePriority(capacity, add, 0, bias, false) / int64(capacity)
+			})
+			if add <= 0 && uint64(-add) >= pb && result[i] > cp.minCap {
+				result[i] = cp.minCap
+			}
+			if result[i] < cp.minCap {
+				result[i] = 0
+			}
+		}
+	})
+	// add first result to metrics (don't care about priority client multi-queries yet)
+	if result[0] == 0 {
+		capacityQueryZeroMeter.Mark(1)
+	} else {
+		capacityQueryNonZeroMeter.Mark(1)
+	}
+	reply, _ := rlp.EncodeToBytes(&result)
+	return reply
+}
+
+// Handle implements Service
+func (cp *ClientPool) Handle(id enode.ID, address string, name string, data []byte) []byte {
+	switch name {
+	case vflux.CapacityQueryName:
+		return cp.serveCapQuery(id, address, data)
+	default:
+		return nil
+	}
+}
diff --git a/les/vflux/server/clientpool_test.go b/les/vflux/server/clientpool_test.go
new file mode 100644
index 0000000000..0953e9c800
--- /dev/null
+++ b/les/vflux/server/clientpool_test.go
@@ -0,0 +1,608 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package server
+
+import (
+	"fmt"
+	"math/rand"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+const defaultConnectedBias = time.Minute * 3
+
+func TestClientPoolL10C100Free(t *testing.T) {
+	testClientPool(t, 10, 100, 0, true)
+}
+
+func TestClientPoolL40C200Free(t *testing.T) {
+	testClientPool(t, 40, 200, 0, true)
+}
+
+func TestClientPoolL100C300Free(t *testing.T) {
+	testClientPool(t, 100, 300, 0, true)
+}
+
+func TestClientPoolL10C100P4(t *testing.T) {
+	testClientPool(t, 10, 100, 4, false)
+}
+
+func TestClientPoolL40C200P30(t *testing.T) {
+	testClientPool(t, 40, 200, 30, false)
+}
+
+func TestClientPoolL100C300P20(t *testing.T) {
+	testClientPool(t, 100, 300, 20, false)
+}
+
+const testClientPoolTicks = 100000
+
+type poolTestPeer struct {
+	node            *enode.Node
+	index           int
+	disconnCh       chan int
+	cap             uint64
+	inactiveAllowed bool
+}
+
+func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer {
+	return &poolTestPeer{
+		index:     i,
+		disconnCh: disconnCh,
+		node:      enode.SignNull(&enr.Record{}, enode.ID{byte(i % 256), byte(i >> 8)}),
+	}
+}
+
+func (i *poolTestPeer) Node() *enode.Node {
+	return i.node
+}
+
+func (i *poolTestPeer) FreeClientId() string {
+	return fmt.Sprintf("addr #%d", i.index)
+}
+
+func (i *poolTestPeer) InactiveAllowance() time.Duration {
+	if i.inactiveAllowed {
+		return time.Second * 10
+	}
+	return 0
+}
+
+func (i *poolTestPeer) UpdateCapacity(capacity uint64, requested bool) {
+	i.cap = capacity
+}
+
+func (i *poolTestPeer) Disconnect() {
+	if i.disconnCh == nil {
+		return
+	}
+	id := i.node.ID()
+	i.disconnCh <- int(id[0]) + int(id[1])<<8
+}
+
+func getBalance(pool *ClientPool, p *poolTestPeer) (pos, neg uint64) {
+	pool.BalanceOperation(p.node.ID(), p.FreeClientId(), func(nb AtomicBalanceOperator) {
+		pos, neg = nb.GetBalance()
+	})
+	return
+}
+
+func addBalance(pool *ClientPool, id enode.ID, amount int64) {
+	pool.BalanceOperation(id, "", func(nb AtomicBalanceOperator) {
+		nb.AddBalance(amount)
+	})
+}
+
+func checkDiff(a, b uint64) bool {
+	maxDiff := (a + b) / 2000
+	if maxDiff < 1 {
+		maxDiff = 1
+	}
+	return a > b+maxDiff || b > a+maxDiff
+}
+
+func connect(pool *ClientPool, peer *poolTestPeer) uint64 {
+	pool.Register(peer)
+	return peer.cap
+}
+
+func disconnect(pool *ClientPool, peer *poolTestPeer) {
+	pool.Unregister(peer)
+}
+
+func alwaysTrueFn() bool {
+	return true
+}
+
+func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, randomDisconnect bool) {
+	rand.Seed(time.Now().UnixNano())
+	var (
+		clock     mclock.Simulated
+		db        = rawdb.NewMemoryDatabase()
+		connected = make([]bool, clientCount)
+		connTicks = make([]int, clientCount)
+		disconnCh = make(chan int, clientCount)
+		pool      = NewClientPool(db, 1, 0, &clock, alwaysTrueFn)
+	)
+	pool.Start()
+	pool.SetExpirationTCs(0, 1000)
+
+	pool.SetLimits(uint64(activeLimit), uint64(activeLimit))
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	// pool should accept new peers up to its connected limit
+	for i := 0; i < activeLimit; i++ {
+		if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 {
+			connected[i] = true
+		} else {
+			t.Fatalf("Test peer #%d rejected", i)
+		}
+	}
+	// randomly connect and disconnect peers, expect to have a similar total connection time at the end
+	for tickCounter := 0; tickCounter < testClientPoolTicks; tickCounter++ {
+		clock.Run(1 * time.Second)
+
+		if tickCounter == testClientPoolTicks/4 {
+			// give a positive balance to some of the peers
+			amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period
+			for i := 0; i < paidCount; i++ {
+				addBalance(pool, newPoolTestPeer(i, disconnCh).node.ID(), amount)
+			}
+		}
+
+		i := rand.Intn(clientCount)
+		if connected[i] {
+			if randomDisconnect {
+				disconnect(pool, newPoolTestPeer(i, disconnCh))
+				connected[i] = false
+				connTicks[i] += tickCounter
+			}
+		} else {
+			if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 {
+				connected[i] = true
+				connTicks[i] -= tickCounter
+			} else {
+				disconnect(pool, newPoolTestPeer(i, disconnCh))
+			}
+		}
+	pollDisconnects:
+		for {
+			select {
+			case i := <-disconnCh:
+				disconnect(pool, newPoolTestPeer(i, disconnCh))
+				if connected[i] {
+					connTicks[i] += tickCounter
+					connected[i] = false
+				}
+			default:
+				break pollDisconnects
+			}
+		}
+	}
+
+	expTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2*(activeLimit-paidCount)/(clientCount-paidCount)
+	expMin := expTicks - expTicks/5
+	expMax := expTicks + expTicks/5
+	paidTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2
+	paidMin := paidTicks - paidTicks/5
+	paidMax := paidTicks + paidTicks/5
+
+	// check if the total connected time of peers are all in the expected range
+	for i, c := range connected {
+		if c {
+			connTicks[i] += testClientPoolTicks
+		}
+		min, max := expMin, expMax
+		if i < paidCount {
+			// expect a higher amount for clients with a positive balance
+			min, max = paidMin, paidMax
+		}
+		if connTicks[i] < min || connTicks[i] > max {
+			t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max)
+		}
+	}
+	pool.Stop()
+}
+
+func testPriorityConnect(t *testing.T, pool *ClientPool, p *poolTestPeer, cap uint64, expSuccess bool) {
+	if cap := connect(pool, p); cap == 0 {
+		if expSuccess {
+			t.Fatalf("Failed to connect paid client")
+		} else {
+			return
+		}
+	}
+	if newCap, _ := pool.SetCapacity(p.node, cap, defaultConnectedBias, true); newCap != cap {
+		if expSuccess {
+			t.Fatalf("Failed to raise capacity of paid client")
+		} else {
+			return
+		}
+	}
+	if !expSuccess {
+		t.Fatalf("Should reject high capacity paid client")
+	}
+}
+
+func TestConnectPaidClient(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10))
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	// Add balance for an external client and mark it as paid client
+	addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute))
+	testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 10, true)
+}
+
+func TestConnectPaidClientToSmallPool(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	// Add balance for an external client and mark it as paid client
+	addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute))
+
+	// connect a fat paid client to pool, should reject it.
+	testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 100, false)
+}
+
+func TestConnectPaidClientToFullPool(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	for i := 0; i < 10; i++ {
+		addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20))
+		connect(pool, newPoolTestPeer(i, nil))
+	}
+	addBalance(pool, newPoolTestPeer(11, nil).node.ID(), int64(time.Second*2)) // Add low balance to new paid client
+	if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 {
+		t.Fatalf("Low balance paid client should be rejected")
+	}
+	clock.Run(time.Second)
+	addBalance(pool, newPoolTestPeer(12, nil).node.ID(), int64(time.Minute*5)) // Add high balance to new paid client
+	if cap := connect(pool, newPoolTestPeer(12, nil)); cap == 0 {
+		t.Fatalf("High balance paid client should be accepted")
+	}
+}
+
+func TestPaidClientKickedOut(t *testing.T) {
+	var (
+		clock    mclock.Simulated
+		db       = rawdb.NewMemoryDatabase()
+		kickedCh = make(chan int, 100)
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	pool.SetExpirationTCs(0, 0)
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	for i := 0; i < 10; i++ {
+		addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance
+		connect(pool, newPoolTestPeer(i, kickedCh))
+		clock.Run(time.Millisecond)
+	}
+	clock.Run(defaultConnectedBias + time.Second*11)
+	if cap := connect(pool, newPoolTestPeer(11, kickedCh)); cap == 0 {
+		t.Fatalf("Free client should be accepted")
+	}
+	clock.Run(0)
+	select {
+	case id := <-kickedCh:
+		if id != 0 {
+			t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id)
+		}
+	default:
+		t.Fatalf("timeout")
+	}
+}
+
+func TestConnectFreeClient(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10))
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+	if cap := connect(pool, newPoolTestPeer(0, nil)); cap == 0 {
+		t.Fatalf("Failed to connect free client")
+	}
+	testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 2, false)
+}
+
+func TestConnectFreeClientToFullPool(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	for i := 0; i < 10; i++ {
+		connect(pool, newPoolTestPeer(i, nil))
+	}
+	if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 {
+		t.Fatalf("New free client should be rejected")
+	}
+	clock.Run(time.Minute)
+	if cap := connect(pool, newPoolTestPeer(12, nil)); cap != 0 {
+		t.Fatalf("New free client should be rejected")
+	}
+	clock.Run(time.Millisecond)
+	clock.Run(4 * time.Minute)
+	if cap := connect(pool, newPoolTestPeer(13, nil)); cap == 0 {
+		t.Fatalf("Old client connects more than 5min should be kicked")
+	}
+}
+
+func TestFreeClientKickedOut(t *testing.T) {
+	var (
+		clock  mclock.Simulated
+		db     = rawdb.NewMemoryDatabase()
+		kicked = make(chan int, 100)
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	for i := 0; i < 10; i++ {
+		connect(pool, newPoolTestPeer(i, kicked))
+		clock.Run(time.Millisecond)
+	}
+	if cap := connect(pool, newPoolTestPeer(10, kicked)); cap != 0 {
+		t.Fatalf("New free client should be rejected")
+	}
+	clock.Run(0)
+	select {
+	case <-kicked:
+	default:
+		t.Fatalf("timeout")
+	}
+	disconnect(pool, newPoolTestPeer(10, kicked))
+	clock.Run(5 * time.Minute)
+	for i := 0; i < 10; i++ {
+		connect(pool, newPoolTestPeer(i+10, kicked))
+
+	}
+	clock.Run(0)
+
+	for i := 0; i < 10; i++ {
+		select {
+		case id := <-kicked:
+			if id >= 10 {
+				t.Fatalf("Old client should be kicked, now got: %d", id)
+			}
+		default:
+			t.Fatalf("timeout")
+		}
+	}
+}
+
+func TestPositiveBalanceCalculation(t *testing.T) {
+	var (
+		clock  mclock.Simulated
+		db     = rawdb.NewMemoryDatabase()
+		kicked = make(chan int, 10)
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3))
+	testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true)
+	clock.Run(time.Minute)
+
+	disconnect(pool, newPoolTestPeer(0, kicked))
+	pb, _ := getBalance(pool, newPoolTestPeer(0, kicked))
+	if checkDiff(pb, uint64(time.Minute*2)) {
+		t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb)
+	}
+}
+
+func TestDowngradePriorityClient(t *testing.T) {
+	var (
+		clock  mclock.Simulated
+		db     = rawdb.NewMemoryDatabase()
+		kicked = make(chan int, 10)
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1})
+
+	p := newPoolTestPeer(0, kicked)
+	addBalance(pool, p.node.ID(), int64(time.Minute))
+	testPriorityConnect(t, pool, p, 10, true)
+	if p.cap != 10 {
+		t.Fatalf("The capacity of priority peer hasn't been updated, got: %d", p.cap)
+	}
+
+	clock.Run(time.Minute)             // All positive balance should be used up.
+	time.Sleep(300 * time.Millisecond) // Ensure the callback is called
+	if p.cap != 1 {
+		t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap)
+	}
+	pb, _ := getBalance(pool, newPoolTestPeer(0, kicked))
+	if pb != 0 {
+		t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb)
+	}
+
+	addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute))
+	pb, _ = getBalance(pool, newPoolTestPeer(0, kicked))
+	if checkDiff(pb, uint64(time.Minute)) {
+		t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb)
+	}
+}
+
+func TestNegativeBalanceCalculation(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetExpirationTCs(0, 3600)
+	pool.SetLimits(10, uint64(10)) // Total capacity limit is 10
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1})
+
+	for i := 0; i < 10; i++ {
+		connect(pool, newPoolTestPeer(i, nil))
+	}
+	clock.Run(time.Second)
+
+	for i := 0; i < 10; i++ {
+		disconnect(pool, newPoolTestPeer(i, nil))
+		_, nb := getBalance(pool, newPoolTestPeer(i, nil))
+		if nb != 0 {
+			t.Fatalf("Short connection shouldn't be recorded")
+		}
+	}
+	for i := 0; i < 10; i++ {
+		connect(pool, newPoolTestPeer(i, nil))
+	}
+	clock.Run(time.Minute)
+	for i := 0; i < 10; i++ {
+		disconnect(pool, newPoolTestPeer(i, nil))
+		_, nb := getBalance(pool, newPoolTestPeer(i, nil))
+		exp := uint64(time.Minute) / 1000
+		exp -= exp / 120 // correct for negative balance expiration
+		if checkDiff(nb, exp) {
+			t.Fatalf("Negative balance mismatch, want %v, got %v", exp, nb)
+		}
+	}
+}
+
+func TestInactiveClient(t *testing.T) {
+	var (
+		clock mclock.Simulated
+		db    = rawdb.NewMemoryDatabase()
+	)
+	pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn)
+	pool.Start()
+	defer pool.Stop()
+	pool.SetLimits(2, uint64(2))
+
+	p1 := newPoolTestPeer(1, nil)
+	p1.inactiveAllowed = true
+	p2 := newPoolTestPeer(2, nil)
+	p2.inactiveAllowed = true
+	p3 := newPoolTestPeer(3, nil)
+	p3.inactiveAllowed = true
+	addBalance(pool, p1.node.ID(), 1000*int64(time.Second))
+	addBalance(pool, p3.node.ID(), 2000*int64(time.Second))
+	// p1: 1000  p2: 0  p3: 2000
+	p1.cap = connect(pool, p1)
+	if p1.cap != 1 {
+		t.Fatalf("Failed to connect peer #1")
+	}
+	p2.cap = connect(pool, p2)
+	if p2.cap != 1 {
+		t.Fatalf("Failed to connect peer #2")
+	}
+	p3.cap = connect(pool, p3)
+	if p3.cap != 1 {
+		t.Fatalf("Failed to connect peer #3")
+	}
+	if p2.cap != 0 {
+		t.Fatalf("Failed to deactivate peer #2")
+	}
+	addBalance(pool, p2.node.ID(), 3000*int64(time.Second))
+	// p1: 1000  p2: 3000  p3: 2000
+	if p2.cap != 1 {
+		t.Fatalf("Failed to activate peer #2")
+	}
+	if p1.cap != 0 {
+		t.Fatalf("Failed to deactivate peer #1")
+	}
+	addBalance(pool, p2.node.ID(), -2500*int64(time.Second))
+	// p1: 1000  p2: 500  p3: 2000
+	if p1.cap != 1 {
+		t.Fatalf("Failed to activate peer #1")
+	}
+	if p2.cap != 0 {
+		t.Fatalf("Failed to deactivate peer #2")
+	}
+	pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0})
+	p4 := newPoolTestPeer(4, nil)
+	addBalance(pool, p4.node.ID(), 1500*int64(time.Second))
+	// p1: 1000  p2: 500  p3: 2000  p4: 1500
+	p4.cap = connect(pool, p4)
+	if p4.cap != 1 {
+		t.Fatalf("Failed to activate peer #4")
+	}
+	if p1.cap != 0 {
+		t.Fatalf("Failed to deactivate peer #1")
+	}
+	clock.Run(time.Second * 600)
+	// manually trigger a check to avoid a long real-time wait
+	pool.ns.SetState(p1.node, pool.setup.updateFlag, nodestate.Flags{}, 0)
+	pool.ns.SetState(p1.node, nodestate.Flags{}, pool.setup.updateFlag, 0)
+	// p1: 1000  p2: 500  p3: 2000  p4: 900
+	if p1.cap != 1 {
+		t.Fatalf("Failed to activate peer #1")
+	}
+	if p4.cap != 0 {
+		t.Fatalf("Failed to deactivate peer #4")
+	}
+	disconnect(pool, p2)
+	disconnect(pool, p4)
+	addBalance(pool, p1.node.ID(), -1000*int64(time.Second))
+	if p1.cap != 1 {
+		t.Fatalf("Should not deactivate peer #1")
+	}
+	if p2.cap != 0 {
+		t.Fatalf("Should not activate peer #2")
+	}
+}
diff --git a/les/vflux/server/metrics.go b/les/vflux/server/metrics.go
new file mode 100644
index 0000000000..307b8347af
--- /dev/null
+++ b/les/vflux/server/metrics.go
@@ -0,0 +1,33 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package server
+
+import (
+	"github.com/ethereum/go-ethereum/metrics"
+)
+
+var (
+	totalConnectedGauge = metrics.NewRegisteredGauge("vflux/server/totalConnected", nil)
+
+	clientConnectedMeter    = metrics.NewRegisteredMeter("vflux/server/clientEvent/connected", nil)
+	clientActivatedMeter    = metrics.NewRegisteredMeter("vflux/server/clientEvent/activated", nil)
+	clientDeactivatedMeter  = metrics.NewRegisteredMeter("vflux/server/clientEvent/deactivated", nil)
+	clientDisconnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/disconnected", nil)
+
+	capacityQueryZeroMeter    = metrics.NewRegisteredMeter("vflux/server/capQueryZero", nil)
+	capacityQueryNonZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryNonZero", nil)
+)
diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go
new file mode 100644
index 0000000000..480f77e6af
--- /dev/null
+++ b/les/vflux/server/prioritypool.go
@@ -0,0 +1,683 @@
+// 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 .
+
+package server
+
+import (
+	"math"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/common/prque"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+const (
+	lazyQueueRefresh = time.Second * 10 // refresh period of the active queue
+)
+
+// priorityPool handles a set of nodes where each node has a capacity (a scalar value)
+// and a priority (which can change over time and can also depend on the capacity).
+// A node is active if it has at least the necessary minimal amount of capacity while
+// inactive nodes have 0 capacity (values between 0 and the minimum are not allowed).
+// The pool ensures that the number and total capacity of all active nodes are limited
+// and the highest priority nodes are active at all times (limits can be changed
+// during operation with immediate effect).
+//
+// When activating clients a priority bias is applied in favor of the already active
+// nodes in order to avoid nodes quickly alternating between active and inactive states
+// when their priorities are close to each other. The bias is specified in terms of
+// duration (time) because priorities are expected to usually get lower over time and
+// therefore a future minimum prediction (see EstMinPriority) should monotonously
+// decrease with the specified time parameter.
+// This time bias can be interpreted as minimum expected active time at the given
+// capacity (if the threshold priority stays the same).
+//
+// Nodes in the pool always have either inactiveFlag or activeFlag set. A new node is
+// added to the pool by externally setting inactiveFlag. priorityPool can switch a node
+// between inactiveFlag and activeFlag at any time. Nodes can be removed from the pool
+// by externally resetting both flags. activeFlag should not be set externally.
+//
+// The highest priority nodes in "inactive" state are moved to "active" state as soon as
+// the minimum capacity can be granted for them. The capacity of lower priority active
+// nodes is reduced or they are demoted to "inactive" state if their priority is
+// insufficient even at minimal capacity.
+type priorityPool struct {
+	setup                        *serverSetup
+	ns                           *nodestate.NodeStateMachine
+	clock                        mclock.Clock
+	lock                         sync.Mutex
+	maxCount, maxCap             uint64
+	minCap                       uint64
+	activeBias                   time.Duration
+	capacityStepDiv, fineStepDiv uint64
+
+	// The snapshot of priority pool for query.
+	cachedCurve    *capacityCurve
+	ccUpdatedAt    mclock.AbsTime
+	ccUpdateForced bool
+
+	// Runtime status of prioritypool, represents the
+	// temporary state if tempState is not empty
+	tempState              []*ppNodeInfo
+	activeCount, activeCap uint64
+	activeQueue            *prque.LazyQueue
+	inactiveQueue          *prque.Prque
+}
+
+// ppNodeInfo is the internal node descriptor of priorityPool
+type ppNodeInfo struct {
+	nodePriority               nodePriority
+	node                       *enode.Node
+	connected                  bool
+	capacity                   uint64 // only changed when temporary state is committed
+	activeIndex, inactiveIndex int
+
+	tempState    bool   // should only be true while the priorityPool lock is held
+	tempCapacity uint64 // equals capacity when tempState is false
+
+	// the following fields only affect the temporary state and they are set to their
+	// default value when leaving the temp state
+	minTarget, stepDiv uint64
+	bias               time.Duration
+}
+
+// newPriorityPool creates a new priorityPool
+func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock mclock.Clock, minCap uint64, activeBias time.Duration, capacityStepDiv, fineStepDiv uint64) *priorityPool {
+	pp := &priorityPool{
+		setup:           setup,
+		ns:              ns,
+		clock:           clock,
+		inactiveQueue:   prque.New(inactiveSetIndex),
+		minCap:          minCap,
+		activeBias:      activeBias,
+		capacityStepDiv: capacityStepDiv,
+		fineStepDiv:     fineStepDiv,
+	}
+	if pp.activeBias < time.Duration(1) {
+		pp.activeBias = time.Duration(1)
+	}
+	pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh)
+
+	ns.SubscribeField(pp.setup.balanceField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
+		if newValue != nil {
+			c := &ppNodeInfo{
+				node:          node,
+				nodePriority:  newValue.(nodePriority),
+				activeIndex:   -1,
+				inactiveIndex: -1,
+			}
+			ns.SetFieldSub(node, pp.setup.queueField, c)
+			ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0)
+		} else {
+			ns.SetStateSub(node, nodestate.Flags{}, pp.setup.activeFlag.Or(pp.setup.inactiveFlag), 0)
+			if n, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); n != nil {
+				pp.disconnectedNode(n)
+			}
+			ns.SetFieldSub(node, pp.setup.capacityField, nil)
+			ns.SetFieldSub(node, pp.setup.queueField, nil)
+		}
+	})
+	ns.SubscribeState(pp.setup.activeFlag.Or(pp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) {
+		if c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); c != nil {
+			if oldState.IsEmpty() {
+				pp.connectedNode(c)
+			}
+			if newState.IsEmpty() {
+				pp.disconnectedNode(c)
+			}
+		}
+	})
+	ns.SubscribeState(pp.setup.updateFlag, func(node *enode.Node, oldState, newState nodestate.Flags) {
+		if !newState.IsEmpty() {
+			pp.updatePriority(node)
+		}
+	})
+	return pp
+}
+
+// requestCapacity tries to set the capacity of a connected node to the highest possible
+// value inside the given target range. If maxTarget is not reachable then the capacity is
+// iteratively reduced in fine steps based on the fineStepDiv parameter until minTarget is reached.
+// The function returns the new capacity if successful and the original capacity otherwise.
+// Note: this function should run inside a NodeStateMachine operation
+func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget uint64, bias time.Duration) uint64 {
+	pp.lock.Lock()
+	pp.activeQueue.Refresh()
+
+	if minTarget < pp.minCap {
+		minTarget = pp.minCap
+	}
+	if maxTarget < minTarget {
+		maxTarget = minTarget
+	}
+	if bias < pp.activeBias {
+		bias = pp.activeBias
+	}
+	c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo)
+	if c == nil {
+		log.Error("requestCapacity called for unknown node", "id", node.ID())
+		pp.lock.Unlock()
+		return 0
+	}
+	pp.setTempState(c)
+	if maxTarget > c.capacity {
+		pp.setTempStepDiv(c, pp.fineStepDiv)
+		pp.setTempBias(c, bias)
+	}
+	pp.setTempCapacity(c, maxTarget)
+	c.minTarget = minTarget
+	pp.activeQueue.Remove(c.activeIndex)
+	pp.inactiveQueue.Remove(c.inactiveIndex)
+	pp.activeQueue.Push(c)
+	pp.enforceLimits()
+	updates := pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity)
+	pp.lock.Unlock()
+	pp.updateFlags(updates)
+	return c.capacity
+}
+
+// SetLimits sets the maximum number and total capacity of simultaneously active nodes
+func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) {
+	pp.lock.Lock()
+	pp.activeQueue.Refresh()
+	inc := (maxCount > pp.maxCount) || (maxCap > pp.maxCap)
+	dec := (maxCount < pp.maxCount) || (maxCap < pp.maxCap)
+	pp.maxCount, pp.maxCap = maxCount, maxCap
+
+	var updates []capUpdate
+	if dec {
+		pp.enforceLimits()
+		updates = pp.finalizeChanges(true)
+	}
+	if inc {
+		updates = append(updates, pp.tryActivate(false)...)
+	}
+	pp.lock.Unlock()
+	pp.ns.Operation(func() { pp.updateFlags(updates) })
+}
+
+// setActiveBias sets the bias applied when trying to activate inactive nodes
+func (pp *priorityPool) setActiveBias(bias time.Duration) {
+	pp.lock.Lock()
+	pp.activeBias = bias
+	if pp.activeBias < time.Duration(1) {
+		pp.activeBias = time.Duration(1)
+	}
+	updates := pp.tryActivate(false)
+	pp.lock.Unlock()
+	pp.ns.Operation(func() { pp.updateFlags(updates) })
+}
+
+// Active returns the number and total capacity of currently active nodes
+func (pp *priorityPool) Active() (uint64, uint64) {
+	pp.lock.Lock()
+	defer pp.lock.Unlock()
+
+	return pp.activeCount, pp.activeCap
+}
+
+// Limits returns the maximum allowed number and total capacity of active nodes
+func (pp *priorityPool) Limits() (uint64, uint64) {
+	pp.lock.Lock()
+	defer pp.lock.Unlock()
+
+	return pp.maxCount, pp.maxCap
+}
+
+// inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue
+func inactiveSetIndex(a interface{}, index int) {
+	a.(*ppNodeInfo).inactiveIndex = index
+}
+
+// activeSetIndex callback updates ppNodeInfo item index in activeQueue
+func activeSetIndex(a interface{}, index int) {
+	a.(*ppNodeInfo).activeIndex = index
+}
+
+// invertPriority inverts a priority value. The active queue uses inverted priorities
+// because the node on the top is the first to be deactivated.
+func invertPriority(p int64) int64 {
+	if p == math.MinInt64 {
+		return math.MaxInt64
+	}
+	return -p
+}
+
+// activePriority callback returns actual priority of ppNodeInfo item in activeQueue
+func activePriority(a interface{}) int64 {
+	c := a.(*ppNodeInfo)
+	if c.bias == 0 {
+		return invertPriority(c.nodePriority.priority(c.tempCapacity))
+	} else {
+		return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, 0, c.bias, true))
+	}
+}
+
+// activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue
+func (pp *priorityPool) activeMaxPriority(a interface{}, until mclock.AbsTime) int64 {
+	c := a.(*ppNodeInfo)
+	future := time.Duration(until - pp.clock.Now())
+	if future < 0 {
+		future = 0
+	}
+	return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, future, c.bias, false))
+}
+
+// inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue
+func (pp *priorityPool) inactivePriority(p *ppNodeInfo) int64 {
+	return p.nodePriority.priority(pp.minCap)
+}
+
+// connectedNode is called when a new node has been added to the pool (inactiveFlag set)
+// Note: this function should run inside a NodeStateMachine operation
+func (pp *priorityPool) connectedNode(c *ppNodeInfo) {
+	pp.lock.Lock()
+	pp.activeQueue.Refresh()
+	if c.connected {
+		pp.lock.Unlock()
+		return
+	}
+	c.connected = true
+	pp.inactiveQueue.Push(c, pp.inactivePriority(c))
+	updates := pp.tryActivate(false)
+	pp.lock.Unlock()
+	pp.updateFlags(updates)
+}
+
+// disconnectedNode is called when a node has been removed from the pool (both inactiveFlag
+// and activeFlag reset)
+// Note: this function should run inside a NodeStateMachine operation
+func (pp *priorityPool) disconnectedNode(c *ppNodeInfo) {
+	pp.lock.Lock()
+	pp.activeQueue.Refresh()
+	if !c.connected {
+		pp.lock.Unlock()
+		return
+	}
+	c.connected = false
+	pp.activeQueue.Remove(c.activeIndex)
+	pp.inactiveQueue.Remove(c.inactiveIndex)
+
+	var updates []capUpdate
+	if c.capacity != 0 {
+		pp.setTempState(c)
+		pp.setTempCapacity(c, 0)
+		updates = pp.tryActivate(true)
+	}
+	pp.lock.Unlock()
+	pp.updateFlags(updates)
+}
+
+// setTempState internally puts a node in a temporary state that can either be reverted
+// or confirmed later. This temporary state allows changing the capacity of a node and
+// moving it between the active and inactive queue. activeFlag/inactiveFlag and
+// capacityField are not changed while the changes are still temporary.
+func (pp *priorityPool) setTempState(c *ppNodeInfo) {
+	if c.tempState {
+		return
+	}
+	c.tempState = true
+	if c.tempCapacity != c.capacity { // should never happen
+		log.Error("tempCapacity != capacity when entering tempState")
+	}
+	// Assign all the defaults to the temp state.
+	c.minTarget = pp.minCap
+	c.stepDiv = pp.capacityStepDiv
+	c.bias = 0
+	pp.tempState = append(pp.tempState, c)
+}
+
+// unsetTempState revokes the temp status of the node and reset all internal
+// fields to the default value.
+func (pp *priorityPool) unsetTempState(c *ppNodeInfo) {
+	if !c.tempState {
+		return
+	}
+	c.tempState = false
+	if c.tempCapacity != c.capacity { // should never happen
+		log.Error("tempCapacity != capacity when leaving tempState")
+	}
+	c.minTarget = pp.minCap
+	c.stepDiv = pp.capacityStepDiv
+	c.bias = 0
+}
+
+// setTempCapacity changes the capacity of a node in the temporary state and adjusts
+// activeCap and activeCount accordingly. Since this change is performed in the temporary
+// state it should be called after setTempState and before finalizeChanges.
+func (pp *priorityPool) setTempCapacity(c *ppNodeInfo, cap uint64) {
+	if !c.tempState { // should never happen
+		log.Error("Node is not in temporary state")
+		return
+	}
+	pp.activeCap += cap - c.tempCapacity
+	if c.tempCapacity == 0 {
+		pp.activeCount++
+	}
+	if cap == 0 {
+		pp.activeCount--
+	}
+	c.tempCapacity = cap
+}
+
+// setTempBias changes the connection bias of a node in the temporary state.
+func (pp *priorityPool) setTempBias(c *ppNodeInfo, bias time.Duration) {
+	if !c.tempState { // should never happen
+		log.Error("Node is not in temporary state")
+		return
+	}
+	c.bias = bias
+}
+
+// setTempStepDiv changes the capacity divisor of a node in the temporary state.
+func (pp *priorityPool) setTempStepDiv(c *ppNodeInfo, stepDiv uint64) {
+	if !c.tempState { // should never happen
+		log.Error("Node is not in temporary state")
+		return
+	}
+	c.stepDiv = stepDiv
+}
+
+// enforceLimits enforces active node count and total capacity limits. It returns the
+// lowest active node priority. Note that this function is performed on the temporary
+// internal state.
+func (pp *priorityPool) enforceLimits() (*ppNodeInfo, int64) {
+	if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount {
+		return nil, math.MinInt64
+	}
+	var (
+		c                 *ppNodeInfo
+		maxActivePriority int64
+	)
+	pp.activeQueue.MultiPop(func(data interface{}, priority int64) bool {
+		c = data.(*ppNodeInfo)
+		pp.setTempState(c)
+		maxActivePriority = priority
+		if c.tempCapacity == c.minTarget || pp.activeCount > pp.maxCount {
+			pp.setTempCapacity(c, 0)
+		} else {
+			sub := c.tempCapacity / c.stepDiv
+			if sub == 0 {
+				sub = 1
+			}
+			if c.tempCapacity-sub < c.minTarget {
+				sub = c.tempCapacity - c.minTarget
+			}
+			pp.setTempCapacity(c, c.tempCapacity-sub)
+			pp.activeQueue.Push(c)
+		}
+		return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount
+	})
+	return c, invertPriority(maxActivePriority)
+}
+
+// finalizeChanges either commits or reverts temporary changes. The necessary capacity
+// field and according flag updates are not performed here but returned in a list because
+// they should be performed while the mutex is not held.
+func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) {
+	for _, c := range pp.tempState {
+		// always remove and push back in order to update biased priority
+		pp.activeQueue.Remove(c.activeIndex)
+		pp.inactiveQueue.Remove(c.inactiveIndex)
+		oldCapacity := c.capacity
+		if commit {
+			c.capacity = c.tempCapacity
+		} else {
+			pp.setTempCapacity(c, c.capacity) // revert activeCount/activeCap
+		}
+		pp.unsetTempState(c)
+
+		if c.connected {
+			if c.capacity != 0 {
+				pp.activeQueue.Push(c)
+			} else {
+				pp.inactiveQueue.Push(c, pp.inactivePriority(c))
+			}
+			if c.capacity != oldCapacity {
+				updates = append(updates, capUpdate{c.node, oldCapacity, c.capacity})
+			}
+		}
+	}
+	pp.tempState = nil
+	if commit {
+		pp.ccUpdateForced = true
+	}
+	return
+}
+
+// capUpdate describes a capacityField and activeFlag/inactiveFlag update
+type capUpdate struct {
+	node           *enode.Node
+	oldCap, newCap uint64
+}
+
+// updateFlags performs capacityField and activeFlag/inactiveFlag updates while the
+// pool mutex is not held
+// Note: this function should run inside a NodeStateMachine operation
+func (pp *priorityPool) updateFlags(updates []capUpdate) {
+	for _, f := range updates {
+		if f.oldCap == 0 {
+			pp.ns.SetStateSub(f.node, pp.setup.activeFlag, pp.setup.inactiveFlag, 0)
+		}
+		if f.newCap == 0 {
+			pp.ns.SetStateSub(f.node, pp.setup.inactiveFlag, pp.setup.activeFlag, 0)
+			pp.ns.SetFieldSub(f.node, pp.setup.capacityField, nil)
+		} else {
+			pp.ns.SetFieldSub(f.node, pp.setup.capacityField, f.newCap)
+		}
+	}
+}
+
+// tryActivate tries to activate inactive nodes if possible
+func (pp *priorityPool) tryActivate(commit bool) []capUpdate {
+	for pp.inactiveQueue.Size() > 0 {
+		c := pp.inactiveQueue.PopItem().(*ppNodeInfo)
+		pp.setTempState(c)
+		pp.setTempBias(c, pp.activeBias)
+		pp.setTempCapacity(c, pp.minCap)
+		pp.activeQueue.Push(c)
+		pp.enforceLimits()
+		if c.tempCapacity > 0 {
+			commit = true
+			pp.setTempBias(c, 0)
+		} else {
+			break
+		}
+	}
+	pp.ccUpdateForced = true
+	return pp.finalizeChanges(commit)
+}
+
+// updatePriority gets the current priority value of the given node from the nodePriority
+// interface and performs the necessary changes. It is triggered by updateFlag.
+// Note: this function should run inside a NodeStateMachine operation
+func (pp *priorityPool) updatePriority(node *enode.Node) {
+	pp.lock.Lock()
+	pp.activeQueue.Refresh()
+	c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo)
+	if c == nil || !c.connected {
+		pp.lock.Unlock()
+		return
+	}
+	pp.activeQueue.Remove(c.activeIndex)
+	pp.inactiveQueue.Remove(c.inactiveIndex)
+	if c.capacity != 0 {
+		pp.activeQueue.Push(c)
+	} else {
+		pp.inactiveQueue.Push(c, pp.inactivePriority(c))
+	}
+	updates := pp.tryActivate(false)
+	pp.lock.Unlock()
+	pp.updateFlags(updates)
+}
+
+// capacityCurve is a snapshot of the priority pool contents in a format that can efficiently
+// estimate how much capacity could be granted to a given node at a given priority level.
+type capacityCurve struct {
+	points       []curvePoint       // curve points sorted in descending order of priority
+	index        map[enode.ID][]int // curve point indexes belonging to each node
+	excludeList  []int              // curve point indexes of excluded node
+	excludeFirst bool               // true if activeCount == maxCount
+}
+
+type curvePoint struct {
+	freeCap uint64 // available capacity and node count at the current priority level
+	nextPri int64  // next priority level where more capacity will be available
+}
+
+// getCapacityCurve returns a new or recently cached capacityCurve based on the contents of the pool
+func (pp *priorityPool) getCapacityCurve() *capacityCurve {
+	pp.lock.Lock()
+	defer pp.lock.Unlock()
+
+	now := pp.clock.Now()
+	dt := time.Duration(now - pp.ccUpdatedAt)
+	if !pp.ccUpdateForced && pp.cachedCurve != nil && dt < time.Second*10 {
+		return pp.cachedCurve
+	}
+
+	pp.ccUpdateForced = false
+	pp.ccUpdatedAt = now
+	curve := &capacityCurve{
+		index: make(map[enode.ID][]int),
+	}
+	pp.cachedCurve = curve
+
+	var excludeID enode.ID
+	excludeFirst := pp.maxCount == pp.activeCount
+	// reduce node capacities or remove nodes until nothing is left in the queue;
+	// record the available capacity and the necessary priority after each step
+	lastPri := int64(math.MinInt64)
+	for pp.activeCap > 0 {
+		cp := curvePoint{}
+		if pp.activeCap > pp.maxCap {
+			log.Error("Active capacity is greater than allowed maximum", "active", pp.activeCap, "maximum", pp.maxCap)
+		} else {
+			cp.freeCap = pp.maxCap - pp.activeCap
+		}
+		// temporarily increase activeCap to enforce reducing or removing a node capacity
+		tempCap := cp.freeCap + 1
+		pp.activeCap += tempCap
+		var next *ppNodeInfo
+		// enforceLimits removes the lowest priority node if it has minimal capacity,
+		// otherwise reduces its capacity
+		next, cp.nextPri = pp.enforceLimits()
+		if cp.nextPri < lastPri {
+			// enforce monotonicity which may be broken by continuously changing priorities
+			cp.nextPri = lastPri
+		} else {
+			lastPri = cp.nextPri
+		}
+		pp.activeCap -= tempCap
+		if next == nil {
+			log.Error("getCapacityCurve: cannot remove next element from the priority queue")
+			break
+		}
+		id := next.node.ID()
+		if excludeFirst {
+			// if the node count limit is already reached then mark the node with the
+			// lowest priority for exclusion
+			curve.excludeFirst = true
+			excludeID = id
+			excludeFirst = false
+		}
+		// multiple curve points and therefore multiple indexes may belong to a node
+		// if it was removed in multiple steps (if its capacity was more than the minimum)
+		curve.index[id] = append(curve.index[id], len(curve.points))
+		curve.points = append(curve.points, cp)
+	}
+	// restore original state of the queue
+	pp.finalizeChanges(false)
+	curve.points = append(curve.points, curvePoint{
+		freeCap: pp.maxCap,
+		nextPri: math.MaxInt64,
+	})
+	if curve.excludeFirst {
+		curve.excludeList = curve.index[excludeID]
+	}
+	return curve
+}
+
+// exclude returns a capacityCurve with the given node excluded from the original curve
+func (cc *capacityCurve) exclude(id enode.ID) *capacityCurve {
+	if excludeList, ok := cc.index[id]; ok {
+		// return a new version of the curve (only one excluded node can be selected)
+		// Note: if the first node was excluded by default (excludeFirst == true) then
+		// we can forget about that and exclude the node with the given id instead.
+		return &capacityCurve{
+			points:      cc.points,
+			index:       cc.index,
+			excludeList: excludeList,
+		}
+	}
+	return cc
+}
+
+func (cc *capacityCurve) getPoint(i int) curvePoint {
+	cp := cc.points[i]
+	if i == 0 && cc.excludeFirst {
+		cp.freeCap = 0
+		return cp
+	}
+	for ii := len(cc.excludeList) - 1; ii >= 0; ii-- {
+		ei := cc.excludeList[ii]
+		if ei < i {
+			break
+		}
+		e1, e2 := cc.points[ei], cc.points[ei+1]
+		cp.freeCap += e2.freeCap - e1.freeCap
+	}
+	return cp
+}
+
+// maxCapacity calculates the maximum capacity available for a node with a given
+// (monotonically decreasing) priority vs. capacity function. Note that if the requesting
+// node is already in the pool then it should be excluded from the curve in order to get
+// the correct result.
+func (cc *capacityCurve) maxCapacity(priority func(cap uint64) int64) uint64 {
+	min, max := 0, len(cc.points)-1 // the curve always has at least one point
+	for min < max {
+		mid := (min + max) / 2
+		cp := cc.getPoint(mid)
+		if cp.freeCap == 0 || priority(cp.freeCap) > cp.nextPri {
+			min = mid + 1
+		} else {
+			max = mid
+		}
+	}
+	cp2 := cc.getPoint(min)
+	if cp2.freeCap == 0 || min == 0 {
+		return cp2.freeCap
+	}
+	cp1 := cc.getPoint(min - 1)
+	if priority(cp2.freeCap) > cp1.nextPri {
+		return cp2.freeCap
+	}
+	minc, maxc := cp1.freeCap, cp2.freeCap-1
+	for minc < maxc {
+		midc := (minc + maxc + 1) / 2
+		if midc == 0 || priority(midc) > cp1.nextPri {
+			minc = midc
+		} else {
+			maxc = midc - 1
+		}
+	}
+	return maxc
+}
diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go
new file mode 100644
index 0000000000..5152312116
--- /dev/null
+++ b/les/vflux/server/prioritypool_test.go
@@ -0,0 +1,233 @@
+// 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 .
+
+package server
+
+import (
+	"math/rand"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+const (
+	testCapacityStepDiv      = 100
+	testCapacityToleranceDiv = 10
+	testMinCap               = 100
+)
+
+type ppTestClient struct {
+	node         *enode.Node
+	balance, cap uint64
+}
+
+func (c *ppTestClient) priority(cap uint64) int64 {
+	return int64(c.balance / cap)
+}
+
+func (c *ppTestClient) estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 {
+	return int64(c.balance / cap)
+}
+
+func TestPriorityPool(t *testing.T) {
+	clock := &mclock.Simulated{}
+	setup := newServerSetup()
+	setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{}))
+	ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
+
+	ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
+		if n := ns.GetField(node, setup.balanceField); n != nil {
+			c := n.(*ppTestClient)
+			c.cap = newValue.(uint64)
+		}
+	})
+	pp := newPriorityPool(ns, setup, clock, testMinCap, 0, testCapacityStepDiv, testCapacityStepDiv)
+	ns.Start()
+	pp.SetLimits(100, 1000000)
+	clients := make([]*ppTestClient, 100)
+	raise := func(c *ppTestClient) {
+		for {
+			var ok bool
+			ns.Operation(func() {
+				newCap := c.cap + c.cap/testCapacityStepDiv
+				ok = pp.requestCapacity(c.node, newCap, newCap, 0) == newCap
+			})
+			if !ok {
+				return
+			}
+		}
+	}
+	var sumBalance uint64
+	check := func(c *ppTestClient) {
+		expCap := 1000000 * c.balance / sumBalance
+		capTol := expCap / testCapacityToleranceDiv
+		if c.cap < expCap-capTol || c.cap > expCap+capTol {
+			t.Errorf("Wrong node capacity (expected %d, got %d)", expCap, c.cap)
+		}
+	}
+
+	for i := range clients {
+		c := &ppTestClient{
+			node:    enode.SignNull(&enr.Record{}, enode.ID{byte(i)}),
+			balance: 100000000000,
+			cap:     1000,
+		}
+		sumBalance += c.balance
+		clients[i] = c
+		ns.SetField(c.node, setup.balanceField, c)
+		ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0)
+		raise(c)
+		check(c)
+	}
+
+	for count := 0; count < 100; count++ {
+		c := clients[rand.Intn(len(clients))]
+		oldBalance := c.balance
+		c.balance = uint64(rand.Int63n(100000000000) + 100000000000)
+		sumBalance += c.balance - oldBalance
+		pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0)
+		pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0)
+		if c.balance > oldBalance {
+			raise(c)
+		} else {
+			for _, c := range clients {
+				raise(c)
+			}
+		}
+		// check whether capacities are proportional to balances
+		for _, c := range clients {
+			check(c)
+		}
+		if count%10 == 0 {
+			// test available capacity calculation with capacity curve
+			c = clients[rand.Intn(len(clients))]
+			curve := pp.getCapacityCurve().exclude(c.node.ID())
+
+			add := uint64(rand.Int63n(10000000000000))
+			c.balance += add
+			sumBalance += add
+			expCap := curve.maxCapacity(func(cap uint64) int64 {
+				return int64(c.balance / cap)
+			})
+			var ok bool
+			expFail := expCap + 10
+			if expFail < testMinCap {
+				expFail = testMinCap
+			}
+			ns.Operation(func() {
+				ok = pp.requestCapacity(c.node, expFail, expFail, 0) == expFail
+			})
+			if ok {
+				t.Errorf("Request for more than expected available capacity succeeded")
+			}
+			if expCap >= testMinCap {
+				ns.Operation(func() {
+					ok = pp.requestCapacity(c.node, expCap, expCap, 0) == expCap
+				})
+				if !ok {
+					t.Errorf("Request for expected available capacity failed")
+				}
+			}
+			c.balance -= add
+			sumBalance -= add
+			pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0)
+			pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0)
+			for _, c := range clients {
+				raise(c)
+			}
+		}
+	}
+
+	ns.Stop()
+}
+
+func TestCapacityCurve(t *testing.T) {
+	clock := &mclock.Simulated{}
+	setup := newServerSetup()
+	setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{}))
+	ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup)
+
+	pp := newPriorityPool(ns, setup, clock, 400000, 0, 2, 2)
+	ns.Start()
+	pp.SetLimits(10, 10000000)
+	clients := make([]*ppTestClient, 10)
+
+	for i := range clients {
+		c := &ppTestClient{
+			node:    enode.SignNull(&enr.Record{}, enode.ID{byte(i)}),
+			balance: 100000000000 * uint64(i+1),
+			cap:     1000000,
+		}
+		clients[i] = c
+		ns.SetField(c.node, setup.balanceField, c)
+		ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0)
+		ns.Operation(func() {
+			pp.requestCapacity(c.node, c.cap, c.cap, 0)
+		})
+	}
+
+	curve := pp.getCapacityCurve()
+	check := func(balance, expCap uint64) {
+		cap := curve.maxCapacity(func(cap uint64) int64 {
+			return int64(balance / cap)
+		})
+		var fail bool
+		if cap == 0 || expCap == 0 {
+			fail = cap != expCap
+		} else {
+			pri := balance / cap
+			expPri := balance / expCap
+			fail = pri != expPri && pri != expPri+1
+		}
+		if fail {
+			t.Errorf("Incorrect capacity for %d balance (got %d, expected %d)", balance, cap, expCap)
+		}
+	}
+
+	check(0, 0)
+	check(10000000000, 100000)
+	check(50000000000, 500000)
+	check(100000000000, 1000000)
+	check(200000000000, 1000000)
+	check(300000000000, 1500000)
+	check(450000000000, 1500000)
+	check(600000000000, 2000000)
+	check(800000000000, 2000000)
+	check(1000000000000, 2500000)
+
+	pp.SetLimits(11, 10000000)
+	curve = pp.getCapacityCurve()
+
+	check(0, 0)
+	check(10000000000, 100000)
+	check(50000000000, 500000)
+	check(150000000000, 750000)
+	check(200000000000, 1000000)
+	check(220000000000, 1100000)
+	check(275000000000, 1100000)
+	check(375000000000, 1500000)
+	check(450000000000, 1500000)
+	check(600000000000, 2000000)
+	check(800000000000, 2000000)
+	check(1000000000000, 2500000)
+
+	ns.Stop()
+}
diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go
new file mode 100644
index 0000000000..80a0f47543
--- /dev/null
+++ b/les/vflux/server/service.go
@@ -0,0 +1,120 @@
+// 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 .
+
+package server
+
+import (
+	"net"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/les/utils"
+	"github.com/ethereum/go-ethereum/les/vflux"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+type (
+	// Server serves vflux requests
+	Server struct {
+		limiter         *utils.Limiter
+		lock            sync.Mutex
+		services        map[string]*serviceEntry
+		delayPerRequest time.Duration
+	}
+
+	// Service is a service registered at the Server and identified by a string id
+	Service interface {
+		Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently
+	}
+
+	serviceEntry struct {
+		id, desc string
+		backend  Service
+	}
+)
+
+// NewServer creates a new Server
+func NewServer(delayPerRequest time.Duration) *Server {
+	return &Server{
+		limiter:         utils.NewLimiter(1000),
+		delayPerRequest: delayPerRequest,
+		services:        make(map[string]*serviceEntry),
+	}
+}
+
+// Register registers a Service
+func (s *Server) Register(b Service, id, desc string) {
+	srv := &serviceEntry{backend: b, id: id, desc: desc}
+	if strings.Contains(srv.id, ":") {
+		// srv.id + ":" will be used as a service database prefix
+		log.Error("Service ID contains ':'", "id", srv.id)
+		return
+	}
+	s.lock.Lock()
+	s.services[srv.id] = srv
+	s.lock.Unlock()
+}
+
+// Serve serves a vflux request batch
+// Note: requests are served by the Handle functions of the registered services. Serve
+// may be called concurrently but the Handle functions are called sequentially and
+// therefore thread safety is guaranteed.
+func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vflux.Replies {
+	reqLen := uint(len(requests))
+	if reqLen == 0 || reqLen > vflux.MaxRequestLength {
+		return nil
+	}
+	// Note: the value parameter will be supplied by the token sale module (total amount paid)
+	ch := <-s.limiter.Add(id, address, 0, reqLen)
+	if ch == nil {
+		return nil
+	}
+	// Note: the limiter ensures that the following section is not running concurrently,
+	// the lock only protects against contention caused by new service registration
+	s.lock.Lock()
+	results := make(vflux.Replies, len(requests))
+	for i, req := range requests {
+		if service := s.services[req.Service]; service != nil {
+			results[i] = service.backend.Handle(id, address, req.Name, req.Params)
+		}
+	}
+	s.lock.Unlock()
+	time.Sleep(s.delayPerRequest * time.Duration(reqLen))
+	close(ch)
+	return results
+}
+
+// ServeEncoded serves an encoded vflux request batch and returns the encoded replies
+func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte {
+	var requests vflux.Requests
+	if err := rlp.DecodeBytes(req, &requests); err != nil {
+		return nil
+	}
+	results := s.Serve(id, addr.String(), requests)
+	if results == nil {
+		return nil
+	}
+	res, _ := rlp.EncodeToBytes(&results)
+	return res
+}
+
+// Stop shuts down the server
+func (s *Server) Stop() {
+	s.limiter.Stop()
+}
diff --git a/les/vflux/server/status.go b/les/vflux/server/status.go
new file mode 100644
index 0000000000..469190777b
--- /dev/null
+++ b/les/vflux/server/status.go
@@ -0,0 +1,59 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package server
+
+import (
+	"reflect"
+
+	"github.com/ethereum/go-ethereum/p2p/nodestate"
+)
+
+type peerWrapper struct{ clientPeer } // the NodeStateMachine type system needs this wrapper
+
+// serverSetup is a wrapper of the node state machine setup, which contains
+// all the created flags and fields used in the vflux server side.
+type serverSetup struct {
+	setup       *nodestate.Setup
+	clientField nodestate.Field // Field contains the client peer handler
+
+	// Flags and fields controlled by balance tracker. BalanceTracker
+	// is responsible for setting/deleting these flags or fields.
+	priorityFlag nodestate.Flags // Flag is set if the node has a positive balance
+	updateFlag   nodestate.Flags // Flag is set whenever the node balance is changed(priority changed)
+	balanceField nodestate.Field // Field contains the client balance for priority calculation
+
+	// Flags and fields controlled by priority queue. Priority queue
+	// is responsible for setting/deleting these flags or fields.
+	activeFlag    nodestate.Flags // Flag is set if the node is active
+	inactiveFlag  nodestate.Flags // Flag is set if the node is inactive
+	capacityField nodestate.Field // Field contains the capacity of the node
+	queueField    nodestate.Field // Field contains the infomration in the priority queue
+}
+
+// newServerSetup initializes the setup for state machine and returns the flags/fields group.
+func newServerSetup() *serverSetup {
+	setup := &serverSetup{setup: &nodestate.Setup{}}
+	setup.clientField = setup.setup.NewField("client", reflect.TypeOf(peerWrapper{}))
+	setup.priorityFlag = setup.setup.NewFlag("priority")
+	setup.updateFlag = setup.setup.NewFlag("update")
+	setup.balanceField = setup.setup.NewField("balance", reflect.TypeOf(&nodeBalance{}))
+	setup.activeFlag = setup.setup.NewFlag("active")
+	setup.inactiveFlag = setup.setup.NewFlag("inactive")
+	setup.capacityField = setup.setup.NewField("capacity", reflect.TypeOf(uint64(0)))
+	setup.queueField = setup.setup.NewField("queue", reflect.TypeOf(&ppNodeInfo{}))
+	return setup
+}
diff --git a/light/lightchain.go b/light/lightchain.go
index 636e06f518..ca6fbfac49 100644
--- a/light/lightchain.go
+++ b/light/lightchain.go
@@ -112,7 +112,7 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
 		if header := bc.GetHeaderByHash(hash); header != nil {
 			log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
 			bc.SetHead(header.Number.Uint64() - 1)
-			log.Error("Chain rewind was successful, resuming normal operation")
+			log.Info("Chain rewind was successful, resuming normal operation")
 		}
 	}
 	return bc, nil
@@ -155,7 +155,11 @@ func (lc *LightChain) loadLastState() error {
 		// Corrupt or empty database, init from scratch
 		lc.Reset()
 	} else {
-		if header := lc.GetHeaderByHash(head); header != nil {
+		header := lc.GetHeaderByHash(head)
+		if header == nil {
+			// Corrupt or empty database, init from scratch
+			lc.Reset()
+		} else {
 			lc.hc.SetCurrentHeader(header)
 		}
 	}
@@ -163,7 +167,6 @@ func (lc *LightChain) loadLastState() error {
 	header := lc.hc.CurrentHeader()
 	headerTd := lc.GetTd(header.Hash(), header.Number.Uint64())
 	log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0)))
-
 	return nil
 }
 
@@ -314,10 +317,16 @@ func (lc *LightChain) Stop() {
 		return
 	}
 	close(lc.quit)
-	atomic.StoreInt32(&lc.procInterrupt, 1)
-
+	lc.StopInsert()
 	lc.wg.Wait()
-	log.Info("Blockchain manager stopped")
+	log.Info("Blockchain stopped")
+}
+
+// StopInsert interrupts all insertion methods, causing them to return
+// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after
+// calling this method.
+func (lc *LightChain) StopInsert() {
+	atomic.StoreInt32(&lc.procInterrupt, 1)
 }
 
 // Rollback is designed to remove a chain of links from the database that aren't
@@ -387,24 +396,26 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
 	lc.wg.Add(1)
 	defer lc.wg.Done()
 
-	var events []interface{}
-	whFunc := func(header *types.Header) error {
-		status, err := lc.hc.WriteHeader(header)
-
-		switch status {
-		case core.CanonStatTy:
-			log.Debug("Inserted new header", "number", header.Number, "hash", header.Hash())
-			events = append(events, core.ChainEvent{Block: types.NewBlockWithHeader(header), Hash: header.Hash()})
+	status, err := lc.hc.InsertHeaderChain(chain, start)
+	if err != nil || len(chain) == 0 {
+		return 0, err
+	}
 
-		case core.SideStatTy:
-			log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash())
-			events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)})
-		}
-		return err
+	// Create chain event for the new head block of this insertion.
+	var (
+		events     = make([]interface{}, 0, 1)
+		lastHeader = chain[len(chain)-1]
+		block      = types.NewBlockWithHeader(lastHeader)
+	)
+	switch status {
+	case core.CanonStatTy:
+		events = append(events, core.ChainEvent{Block: block, Hash: block.Hash()})
+	case core.SideStatTy:
+		events = append(events, core.ChainSideEvent{Block: block})
 	}
-	i, err := lc.hc.InsertHeaderChain(chain, whFunc, start)
 	lc.postChainEvents(events)
-	return i, err
+
+	return 0, err
 }
 
 // CurrentHeader retrieves the current head header of the canonical chain. The
@@ -425,6 +436,17 @@ func (lc *LightChain) GetTdByHash(hash common.Hash) *big.Int {
 	return lc.hc.GetTdByHash(hash)
 }
 
+// GetHeaderByNumberOdr retrieves the total difficult from the database or
+// network by hash and number, caching it (associated with its hash) if found.
+func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uint64) *big.Int {
+	td := lc.GetTd(hash, number)
+	if td != nil {
+		return td
+	}
+	td, _ = GetTd(ctx, lc.odr, hash, number)
+	return td
+}
+
 // GetHeader retrieves a block header from the database by hash and number,
 // caching it if found.
 func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header {
diff --git a/light/lightchain_test.go b/light/lightchain_test.go
index 70d2e70c18..2aed08d74e 100644
--- a/light/lightchain_test.go
+++ b/light/lightchain_test.go
@@ -18,6 +18,7 @@ package light
 
 import (
 	"context"
+	"errors"
 	"math/big"
 	"testing"
 
@@ -321,7 +322,7 @@ func TestBadHeaderHashes(t *testing.T) {
 	var err error
 	headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
 	core.BadHashes[headers[2].Hash()] = true
-	if _, err = bc.InsertHeaderChain(headers, 1); err != core.ErrBlacklistedHash {
+	if _, err = bc.InsertHeaderChain(headers, 1); !errors.Is(err, core.ErrBlacklistedHash) {
 		t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBlacklistedHash)
 	}
 }
diff --git a/light/odr.go b/light/odr.go
index 907712ede7..9521dd53e8 100644
--- a/light/odr.go
+++ b/light/odr.go
@@ -42,6 +42,7 @@ type OdrBackend interface {
 	BloomTrieIndexer() *core.ChainIndexer
 	BloomIndexer() *core.ChainIndexer
 	Retrieve(ctx context.Context, req OdrRequest) error
+	RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error
 	IndexerConfig() *IndexerConfig
 }
 
@@ -82,7 +83,6 @@ func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID {
 
 // TrieRequest is the ODR request type for state/storage trie entries
 type TrieRequest struct {
-	OdrRequest
 	Id    *TrieID
 	Key   []byte
 	Proof *NodeSet
@@ -95,7 +95,6 @@ func (req *TrieRequest) StoreResult(db ethdb.Database) {
 
 // CodeRequest is the ODR request type for retrieving contract code
 type CodeRequest struct {
-	OdrRequest
 	Id   *TrieID // references storage trie of the account
 	Hash common.Hash
 	Data []byte
@@ -103,14 +102,14 @@ type CodeRequest struct {
 
 // StoreResult stores the retrieved data in local database
 func (req *CodeRequest) StoreResult(db ethdb.Database) {
-	db.Put(req.Hash[:], req.Data)
+	rawdb.WriteCode(db, req.Hash, req.Data)
 }
 
 // BlockRequest is the ODR request type for retrieving block bodies
 type BlockRequest struct {
-	OdrRequest
 	Hash   common.Hash
 	Number uint64
+	Header *types.Header
 	Rlp    []byte
 }
 
@@ -119,9 +118,8 @@ func (req *BlockRequest) StoreResult(db ethdb.Database) {
 	rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp)
 }
 
-// ReceiptsRequest is the ODR request type for retrieving block bodies
+// ReceiptsRequest is the ODR request type for retrieving receipts.
 type ReceiptsRequest struct {
-	OdrRequest
 	Untrusted bool // Indicator whether the result retrieved is trusted or not
 	Hash      common.Hash
 	Number    uint64
@@ -136,11 +134,8 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
 	}
 }
 
-// ChtRequest is the ODR request type for state/storage trie entries
+// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie
 type ChtRequest struct {
-	OdrRequest
-	Untrusted        bool   // Indicator whether the result retrieved is trusted or not
-	PeerId           string // The specified peer id from which to retrieve data.
 	Config           *IndexerConfig
 	ChtNum, BlockNum uint64
 	ChtRoot          common.Hash
@@ -152,12 +147,9 @@ type ChtRequest struct {
 // StoreResult stores the retrieved data in local database
 func (req *ChtRequest) StoreResult(db ethdb.Database) {
 	hash, num := req.Header.Hash(), req.Header.Number.Uint64()
-
-	if !req.Untrusted {
-		rawdb.WriteHeader(db, req.Header)
-		rawdb.WriteTd(db, hash, num, req.Td)
-		rawdb.WriteCanonicalHash(db, hash, num)
-	}
+	rawdb.WriteHeader(db, req.Header)
+	rawdb.WriteTd(db, hash, num, req.Td)
+	rawdb.WriteCanonicalHash(db, hash, num)
 }
 
 // BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure
@@ -193,7 +185,6 @@ type TxStatus struct {
 
 // TxStatusRequest is the ODR request type for retrieving transaction status
 type TxStatusRequest struct {
-	OdrRequest
 	Hashes []common.Hash
 	Status []TxStatus
 }
diff --git a/light/odr_test.go b/light/odr_test.go
index 9149c02fc2..0fc45b8734 100644
--- a/light/odr_test.go
+++ b/light/odr_test.go
@@ -87,7 +87,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
 		t.Prove(req.Key, 0, nodes)
 		req.Proof = nodes
 	case *CodeRequest:
-		req.Data, _ = odr.sdb.Get(req.Hash[:])
+		req.Data = rawdb.ReadCode(odr.sdb, req.Hash)
 	}
 	req.StoreResult(odr.ldb)
 	return nil
@@ -194,12 +194,13 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
 
 		// Perform read-only call.
 		st.SetBalance(testBankAddress, math.MaxBig256)
-		msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, false)}
-		context := core.NewEVMContext(msg, header, chain, nil)
-		vmenv := vm.NewEVM(context, st, config, vm.Config{})
+		msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), 1000000, new(big.Int), data, nil, false)}
+		txContext := core.NewEVMTxContext(msg)
+		context := core.NewEVMBlockContext(header, chain, nil)
+		vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{})
 		gp := new(core.GasPool).AddGas(math.MaxUint64)
-		ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp)
-		res = append(res, ret...)
+		result, _ := core.ApplyMessage(vmenv, msg, gp)
+		res = append(res, result.Return()...)
 		if st.Error() != nil {
 			return res, st.Error()
 		}
@@ -257,7 +258,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) {
 	)
 	gspec.MustCommit(ldb)
 	// Assemble the test environment
-	blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil)
+	blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil)
 	gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), sdb, 4, testChainGen)
 	if _, err := blockchain.InsertChain(gchain); err != nil {
 		t.Fatal(err)
diff --git a/light/odr_util.go b/light/odr_util.go
index 2c820d40c7..bbbcdbce21 100644
--- a/light/odr_util.go
+++ b/light/odr_util.go
@@ -19,76 +19,82 @@ package light
 import (
 	"bytes"
 	"context"
+	"errors"
+	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/types"
-	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
-var sha3Nil = crypto.Keccak256Hash(nil)
+// errNonCanonicalHash is returned if the requested chain data doesn't belong
+// to the canonical chain. ODR can only retrieve the canonical chain data covered
+// by the CHT or Bloom trie for verification.
+var errNonCanonicalHash = errors.New("hash is not currently canonical")
 
+// GetHeaderByNumber retrieves the canonical block header corresponding to the
+// given number. The returned header is proven by local CHT.
 func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
+	// Try to find it in the local database first.
 	db := odr.Database()
 	hash := rawdb.ReadCanonicalHash(db, number)
-	if (hash != common.Hash{}) {
-		// if there is a canonical hash, there is a header too
-		header := rawdb.ReadHeader(db, hash, number)
-		if header == nil {
-			panic("Canonical hash present but header not found")
-		}
-		return header, nil
-	}
 
-	var (
-		chtCount, sectionHeadNum uint64
-		sectionHead              common.Hash
-	)
-	if odr.ChtIndexer() != nil {
-		chtCount, sectionHeadNum, sectionHead = odr.ChtIndexer().Sections()
-		canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum)
-		// if the CHT was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too
-		for chtCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) {
-			chtCount--
-			if chtCount > 0 {
-				sectionHeadNum = chtCount*odr.IndexerConfig().ChtSize - 1
-				sectionHead = odr.ChtIndexer().SectionHead(chtCount - 1)
-				canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum)
-			}
+	// If there is a canonical hash, there should have a header too.
+	// But if it's pruned, re-fetch from network again.
+	if (hash != common.Hash{}) {
+		if header := rawdb.ReadHeader(db, hash, number); header != nil {
+			return header, nil
 		}
 	}
-	if number >= chtCount*odr.IndexerConfig().ChtSize {
+	// Retrieve the header via ODR, ensure the requested header is covered
+	// by local trusted CHT.
+	chts, _, chtHead := odr.ChtIndexer().Sections()
+	if number >= chts*odr.IndexerConfig().ChtSize {
 		return nil, errNoTrustedCht
 	}
-	r := &ChtRequest{ChtRoot: GetChtRoot(db, chtCount-1, sectionHead), ChtNum: chtCount - 1, BlockNum: number, Config: odr.IndexerConfig()}
-	if err := odr.Retrieve(ctx, r); err != nil {
-		return nil, err
+	r := &ChtRequest{
+		ChtRoot:  GetChtRoot(db, chts-1, chtHead),
+		ChtNum:   chts - 1,
+		BlockNum: number,
+		Config:   odr.IndexerConfig(),
 	}
-	return r.Header, nil
-}
-
-// GetUntrustedHeaderByNumber fetches specified block header without correctness checking.
-// Note this function should only be used in light client checkpoint syncing.
-func GetUntrustedHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64, peerId string) (*types.Header, error) {
-	r := &ChtRequest{BlockNum: number, ChtNum: number / odr.IndexerConfig().ChtSize, Untrusted: true, PeerId: peerId, Config: odr.IndexerConfig()}
 	if err := odr.Retrieve(ctx, r); err != nil {
 		return nil, err
 	}
 	return r.Header, nil
 }
 
+// GetCanonicalHash retrieves the canonical block hash corresponding to the number.
 func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {
 	hash := rawdb.ReadCanonicalHash(odr.Database(), number)
-	if (hash != common.Hash{}) {
+	if hash != (common.Hash{}) {
 		return hash, nil
 	}
 	header, err := GetHeaderByNumber(ctx, odr, number)
-	if header != nil {
-		return header.Hash(), nil
+	if err != nil {
+		return common.Hash{}, err
 	}
-	return common.Hash{}, err
+	// number -> canonical mapping already be stored in db, get it.
+	return header.Hash(), nil
+}
+
+// GetTd retrieves the total difficulty corresponding to the number and hash.
+func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*big.Int, error) {
+	td := rawdb.ReadTd(odr.Database(), hash, number)
+	if td != nil {
+		return td, nil
+	}
+	header, err := GetHeaderByNumber(ctx, odr, number)
+	if err != nil {
+		return nil, err
+	}
+	if header.Hash() != hash {
+		return nil, errNonCanonicalHash
+	}
+	//  -> td mapping already be stored in db, get it.
+	return rawdb.ReadTd(odr.Database(), hash, number), nil
 }
 
 // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
@@ -96,15 +102,22 @@ func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number ui
 	if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil {
 		return data, nil
 	}
-	r := &BlockRequest{Hash: hash, Number: number}
+	// Retrieve the block header first and pass it for verification.
+	header, err := GetHeaderByNumber(ctx, odr, number)
+	if err != nil {
+		return nil, errNoHeader
+	}
+	if header.Hash() != hash {
+		return nil, errNonCanonicalHash
+	}
+	r := &BlockRequest{Hash: hash, Number: number, Header: header}
 	if err := odr.Retrieve(ctx, r); err != nil {
 		return nil, err
-	} else {
-		return r.Rlp, nil
 	}
+	return r.Rlp, nil
 }
 
-// GetBody retrieves the block body (transactons, uncles) corresponding to the
+// GetBody retrieves the block body (transactions, uncles) corresponding to the
 // hash.
 func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) {
 	data, err := GetBodyRLP(ctx, odr, hash, number)
@@ -122,8 +135,8 @@ func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint6
 // back from the stored header and body.
 func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) {
 	// Retrieve the block header and body contents
-	header := rawdb.ReadHeader(odr.Database(), hash, number)
-	if header == nil {
+	header, err := GetHeaderByNumber(ctx, odr, number)
+	if err != nil {
 		return nil, errNoHeader
 	}
 	body, err := GetBody(ctx, odr, hash, number)
@@ -140,7 +153,14 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
 	// Assume receipts are already stored locally and attempt to retrieve.
 	receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number)
 	if receipts == nil {
-		r := &ReceiptsRequest{Hash: hash, Number: number}
+		header, err := GetHeaderByNumber(ctx, odr, number)
+		if err != nil {
+			return nil, errNoHeader
+		}
+		if header.Hash() != hash {
+			return nil, errNonCanonicalHash
+		}
+		r := &ReceiptsRequest{Hash: hash, Number: number, Header: header}
 		if err := odr.Retrieve(ctx, r); err != nil {
 			return nil, err
 		}
@@ -171,7 +191,6 @@ func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number
 	if err != nil {
 		return nil, err
 	}
-	// Return the logs without deriving any computed fields on the receipts
 	logs := make([][]*types.Log, len(receipts))
 	for i, receipt := range receipts {
 		logs[i] = receipt.Logs
@@ -203,82 +222,73 @@ func GetUntrustedBlockLogs(ctx context.Context, odr OdrBackend, header *types.He
 	return logs, nil
 }
 
-// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to the given bit index and section indexes
-func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxList []uint64) ([][]byte, error) {
+// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to
+// the given bit index and section indexes.
+func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) {
 	var (
-		db      = odr.Database()
-		result  = make([][]byte, len(sectionIdxList))
-		reqList []uint64
-		reqIdx  []int
+		reqIndex    []int
+		reqSections []uint64
+		db          = odr.Database()
+		result      = make([][]byte, len(sections))
 	)
-
-	var (
-		bloomTrieCount, sectionHeadNum uint64
-		sectionHead                    common.Hash
-	)
-	if odr.BloomTrieIndexer() != nil {
-		bloomTrieCount, sectionHeadNum, sectionHead = odr.BloomTrieIndexer().Sections()
-		canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum)
-		// if the BloomTrie was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too
-		for bloomTrieCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) {
-			bloomTrieCount--
-			if bloomTrieCount > 0 {
-				sectionHeadNum = bloomTrieCount*odr.IndexerConfig().BloomTrieSize - 1
-				sectionHead = odr.BloomTrieIndexer().SectionHead(bloomTrieCount - 1)
-				canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum)
-			}
-		}
-	}
-
-	for i, sectionIdx := range sectionIdxList {
-		sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*odr.IndexerConfig().BloomSize-1)
-		// if we don't have the canonical hash stored for this section head number, we'll still look for
-		// an entry with a zero sectionHead (we store it with zero section head too if we don't know it
-		// at the time of the retrieval)
-		bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead)
-		if err == nil {
+	blooms, _, sectionHead := odr.BloomTrieIndexer().Sections()
+	for i, section := range sections {
+		sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1)
+		// If we don't have the canonical hash stored for this section head number,
+		// we'll still look for an entry with a zero sectionHead (we store it with
+		// zero section head too if we don't know it at the time of the retrieval)
+		if bloomBits, _ := rawdb.ReadBloomBits(db, bit, section, sectionHead); len(bloomBits) != 0 {
 			result[i] = bloomBits
-		} else {
-			// TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index
-			if sectionIdx >= bloomTrieCount {
-				return nil, errNoTrustedBloomTrie
-			}
-			reqList = append(reqList, sectionIdx)
-			reqIdx = append(reqIdx, i)
+			continue
 		}
+		// TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index
+		if section >= blooms {
+			return nil, errNoTrustedBloomTrie
+		}
+		reqSections = append(reqSections, section)
+		reqIndex = append(reqIndex, i)
 	}
-	if reqList == nil {
+	// Find all bloombits in database, nothing to query via odr, return.
+	if reqSections == nil {
 		return result, nil
 	}
-
-	r := &BloomRequest{BloomTrieRoot: GetBloomTrieRoot(db, bloomTrieCount-1, sectionHead), BloomTrieNum: bloomTrieCount - 1,
-		BitIdx: bitIdx, SectionIndexList: reqList, Config: odr.IndexerConfig()}
+	// Send odr request to retrieve missing bloombits.
+	r := &BloomRequest{
+		BloomTrieRoot:    GetBloomTrieRoot(db, blooms-1, sectionHead),
+		BloomTrieNum:     blooms - 1,
+		BitIdx:           bit,
+		SectionIndexList: reqSections,
+		Config:           odr.IndexerConfig(),
+	}
 	if err := odr.Retrieve(ctx, r); err != nil {
 		return nil, err
-	} else {
-		for i, idx := range reqIdx {
-			result[idx] = r.BloomBits[i]
-		}
-		return result, nil
 	}
+	for i, idx := range reqIndex {
+		result[idx] = r.BloomBits[i]
+	}
+	return result, nil
 }
 
-// GetTransaction retrieves a canonical transaction by hash and also returns its position in the chain
+// GetTransaction retrieves a canonical transaction by hash and also returns
+// its position in the chain. There is no guarantee in the LES protocol that
+// the mined transaction will be retrieved back for sure because of different
+// reasons(the transaction is unindexed, the malicous server doesn't reply it
+// deliberately, etc). Therefore, unretrieved transactions will receive a certain
+// number of retrys, thus giving a weak guarantee.
 func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
 	r := &TxStatusRequest{Hashes: []common.Hash{txHash}}
-	if err := odr.Retrieve(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded {
+	if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded {
+		return nil, common.Hash{}, 0, 0, err
+	}
+	pos := r.Status[0].Lookup
+	// first ensure that we have the header, otherwise block body retrieval will fail
+	// also verify if this is a canonical block by getting the header by number and checking its hash
+	if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash {
+		return nil, common.Hash{}, 0, 0, err
+	}
+	body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex)
+	if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash {
 		return nil, common.Hash{}, 0, 0, err
-	} else {
-		pos := r.Status[0].Lookup
-		// first ensure that we have the header, otherwise block body retrieval will fail
-		// also verify if this is a canonical block by getting the header by number and checking its hash
-		if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash {
-			return nil, common.Hash{}, 0, 0, err
-		}
-		if body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex); err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash {
-			return nil, common.Hash{}, 0, 0, err
-		} else {
-			return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil
-		}
 	}
+	return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil
 }
diff --git a/light/postprocess.go b/light/postprocess.go
index af3b257923..891c8a5869 100644
--- a/light/postprocess.go
+++ b/light/postprocess.go
@@ -17,6 +17,7 @@
 package light
 
 import (
+	"bytes"
 	"context"
 	"encoding/binary"
 	"errors"
@@ -24,6 +25,7 @@ import (
 	"math/big"
 	"time"
 
+	mapset "github.com/deckarep/golang-set"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/bitutil"
 	"github.com/ethereum/go-ethereum/core"
@@ -128,23 +130,27 @@ func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common
 
 // ChtIndexerBackend implements core.ChainIndexerBackend.
 type ChtIndexerBackend struct {
+	disablePruning       bool
 	diskdb, trieTable    ethdb.Database
 	odr                  OdrBackend
 	triedb               *trie.Database
+	trieset              mapset.Set
 	section, sectionSize uint64
 	lastHash             common.Hash
 	trie                 *trie.Trie
 }
 
 // NewChtIndexer creates a Cht chain indexer
-func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64) *core.ChainIndexer {
+func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer {
 	trieTable := rawdb.NewTable(db, ChtTablePrefix)
 	backend := &ChtIndexerBackend{
-		diskdb:      db,
-		odr:         odr,
-		trieTable:   trieTable,
-		triedb:      trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down
-		sectionSize: size,
+		diskdb:         db,
+		odr:            odr,
+		trieTable:      trieTable,
+		triedb:         trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down
+		trieset:        mapset.NewSet(),
+		sectionSize:    size,
+		disablePruning: disablePruning,
 	}
 	return core.NewChainIndexer(db, rawdb.NewTable(db, "chtIndexV2-"), backend, size, confirms, time.Millisecond*100, "cht")
 }
@@ -189,7 +195,6 @@ func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSecti
 			c.trie, err = trie.New(root, c.triedb)
 		}
 	}
-
 	c.section = section
 	return err
 }
@@ -216,13 +221,83 @@ func (c *ChtIndexerBackend) Commit() error {
 	if err != nil {
 		return err
 	}
-	c.triedb.Commit(root, false)
-
+	// Pruning historical trie nodes if necessary.
+	if !c.disablePruning {
+		// Flush the triedb and track the latest trie nodes.
+		c.trieset.Clear()
+		c.triedb.Commit(root, false, func(hash common.Hash) { c.trieset.Add(hash) })
+
+		it := c.trieTable.NewIterator(nil, nil)
+		defer it.Release()
+
+		var (
+			deleted   int
+			remaining int
+			t         = time.Now()
+		)
+		for it.Next() {
+			trimmed := bytes.TrimPrefix(it.Key(), []byte(ChtTablePrefix))
+			if !c.trieset.Contains(common.BytesToHash(trimmed)) {
+				c.trieTable.Delete(trimmed)
+				deleted += 1
+			} else {
+				remaining += 1
+			}
+		}
+		log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", remaining, "elapsed", common.PrettyDuration(time.Since(t)))
+	} else {
+		c.triedb.Commit(root, false, nil)
+	}
 	log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
 	StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
 	return nil
 }
 
+// PruneSections implements core.ChainIndexerBackend which deletes all
+// chain data(except hash<->number mappings) older than the specified
+// threshold.
+func (c *ChtIndexerBackend) Prune(threshold uint64) error {
+	// Short circuit if the light pruning is disabled.
+	if c.disablePruning {
+		return nil
+	}
+	t := time.Now()
+	// Always keep genesis header in database.
+	start, end := uint64(1), (threshold+1)*c.sectionSize
+
+	var batch = c.diskdb.NewBatch()
+	for {
+		numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240)
+		if len(numbers) == 0 {
+			break
+		}
+		for i := 0; i < len(numbers); i++ {
+			// Keep hash<->number mapping in database otherwise the hash based
+			// API(e.g. GetReceipt, GetLogs) will be broken.
+			//
+			// Storage size wise, the size of a mapping is ~41bytes. For one
+			// section is about 1.3MB which is acceptable.
+			//
+			// In order to totally get rid of this index, we need an additional
+			// flag to specify how many historical data light client can serve.
+			rawdb.DeleteCanonicalHash(batch, numbers[i])
+			rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i])
+		}
+		if batch.ValueSize() > ethdb.IdealBatchSize {
+			if err := batch.Write(); err != nil {
+				return err
+			}
+			batch.Reset()
+		}
+		start = numbers[len(numbers)-1] + 1
+	}
+	if err := batch.Write(); err != nil {
+		return err
+	}
+	log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t)))
+	return nil
+}
+
 var (
 	bloomTriePrefix      = []byte("bltRoot-") // bloomTriePrefix + bloomTrieNum (uint64 big endian) -> trie root hash
 	BloomTrieTablePrefix = "blt-"
@@ -245,8 +320,10 @@ func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root
 
 // BloomTrieIndexerBackend implements core.ChainIndexerBackend
 type BloomTrieIndexerBackend struct {
+	disablePruning    bool
 	diskdb, trieTable ethdb.Database
 	triedb            *trie.Database
+	trieset           mapset.Set
 	odr               OdrBackend
 	section           uint64
 	parentSize        uint64
@@ -257,15 +334,17 @@ type BloomTrieIndexerBackend struct {
 }
 
 // NewBloomTrieIndexer creates a BloomTrie chain indexer
-func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64) *core.ChainIndexer {
+func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer {
 	trieTable := rawdb.NewTable(db, BloomTrieTablePrefix)
 	backend := &BloomTrieIndexerBackend{
-		diskdb:     db,
-		odr:        odr,
-		trieTable:  trieTable,
-		triedb:     trie.NewDatabaseWithCache(trieTable, 1), // Use a tiny cache only to keep memory down
-		parentSize: parentSize,
-		size:       size,
+		diskdb:         db,
+		odr:            odr,
+		trieTable:      trieTable,
+		triedb:         trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down
+		trieset:        mapset.NewSet(),
+		parentSize:     parentSize,
+		size:           size,
+		disablePruning: disablePruning,
 	}
 	backend.bloomTrieRatio = size / parentSize
 	backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
@@ -303,7 +382,6 @@ func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section
 			}
 		}()
 	}
-
 	for i := uint(0); i < types.BloomBitLength; i++ {
 		indexCh <- i
 	}
@@ -380,10 +458,51 @@ func (b *BloomTrieIndexerBackend) Commit() error {
 	if err != nil {
 		return err
 	}
-	b.triedb.Commit(root, false)
-
+	// Pruning historical trie nodes if necessary.
+	if !b.disablePruning {
+		// Flush the triedb and track the latest trie nodes.
+		b.trieset.Clear()
+		b.triedb.Commit(root, false, func(hash common.Hash) { b.trieset.Add(hash) })
+
+		it := b.trieTable.NewIterator(nil, nil)
+		defer it.Release()
+
+		var (
+			deleted   int
+			remaining int
+			t         = time.Now()
+		)
+		for it.Next() {
+			trimmed := bytes.TrimPrefix(it.Key(), []byte(BloomTrieTablePrefix))
+			if !b.trieset.Contains(common.BytesToHash(trimmed)) {
+				b.trieTable.Delete(trimmed)
+				deleted += 1
+			} else {
+				remaining += 1
+			}
+		}
+		log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", remaining, "elapsed", common.PrettyDuration(time.Since(t)))
+	} else {
+		b.triedb.Commit(root, false, nil)
+	}
 	sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
-	log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
 	StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
+	log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
+
+	return nil
+}
+
+// Prune implements core.ChainIndexerBackend which deletes all
+// bloombits which older than the specified threshold.
+func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error {
+	// Short circuit if the light pruning is disabled.
+	if b.disablePruning {
+		return nil
+	}
+	start := time.Now()
+	for i := uint(0); i < types.BloomBitLength; i++ {
+		rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio)
+	}
+	log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start)))
 	return nil
 }
diff --git a/light/trie.go b/light/trie.go
index 0d69e74e21..0516b94486 100644
--- a/light/trie.go
+++ b/light/trie.go
@@ -22,6 +22,7 @@ import (
 	"fmt"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -29,6 +30,10 @@ import (
 	"github.com/ethereum/go-ethereum/trie"
 )
 
+var (
+	sha3Nil = crypto.Keccak256Hash(nil)
+)
+
 func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB {
 	state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil)
 	return state
@@ -70,7 +75,8 @@ func (db *odrDatabase) ContractCode(addrHash, codeHash common.Hash) ([]byte, err
 	if codeHash == sha3Nil {
 		return nil, nil
 	}
-	if code, err := db.backend.Database().Get(codeHash[:]); err == nil {
+	code := rawdb.ReadCode(db.backend.Database(), codeHash)
+	if len(code) != 0 {
 		return code, nil
 	}
 	id := *db.id
diff --git a/light/trie_test.go b/light/trie_test.go
index 4919f89641..052194b4d8 100644
--- a/light/trie_test.go
+++ b/light/trie_test.go
@@ -40,7 +40,7 @@ func TestNodeIterator(t *testing.T) {
 		genesis = gspec.MustCommit(fulldb)
 	)
 	gspec.MustCommit(lightdb)
-	blockchain, _ := core.NewBlockChain(fulldb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil)
+	blockchain, _ := core.NewBlockChain(fulldb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil)
 	gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), fulldb, 4, testChainGen)
 	if _, err := blockchain.InsertChain(gchain); err != nil {
 		panic(err)
diff --git a/light/txpool.go b/light/txpool.go
index 11a0e76ae0..1296389e3b 100644
--- a/light/txpool.go
+++ b/light/txpool.go
@@ -32,7 +32,6 @@ import (
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/params"
-	"github.com/ethereum/go-ethereum/rlp"
 )
 
 const (
@@ -69,6 +68,7 @@ type TxPool struct {
 	clearIdx     uint64                               // earliest block nr that can contain mined tx info
 
 	istanbul bool // Fork indicator whether we are in the istanbul stage.
+	eip2718  bool // Fork indicator whether we are in the eip2718 stage.
 }
 
 // TxRelayBackend provides an interface to the mechanism that forwards transacions
@@ -90,7 +90,7 @@ type TxRelayBackend interface {
 func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool {
 	pool := &TxPool{
 		config:      config,
-		signer:      types.NewEIP155Signer(config.ChainID),
+		signer:      types.LatestSigner(config),
 		nonce:       make(map[common.Address]uint64),
 		pending:     make(map[common.Hash]*types.Transaction),
 		mined:       make(map[common.Hash][]*types.Transaction),
@@ -185,7 +185,7 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number
 		if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results
 			return err
 		}
-		rawdb.WriteTxLookupEntries(pool.chainDb, block)
+		rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block)
 
 		// Update the transaction pool's state
 		for _, tx := range list {
@@ -314,6 +314,7 @@ func (pool *TxPool) setNewHead(head *types.Header) {
 	// Update fork indicator by next pending block number
 	next := new(big.Int).Add(head.Number, big.NewInt(1))
 	pool.istanbul = pool.config.IsIstanbul(next)
+	pool.eip2718 = pool.config.IsBerlin(next)
 }
 
 // Stop stops the light transaction pool
@@ -381,7 +382,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
 	}
 
 	// Should supply enough intrinsic gas
-	gas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, true, pool.istanbul)
+	gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
 	if err != nil {
 		return err
 	}
@@ -430,8 +431,7 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error {
 func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error {
 	pool.mu.Lock()
 	defer pool.mu.Unlock()
-
-	data, err := rlp.EncodeToBytes(tx)
+	data, err := tx.MarshalBinary()
 	if err != nil {
 		return err
 	}
diff --git a/light/txpool_test.go b/light/txpool_test.go
index 0996bd7c9c..39d5afe52f 100644
--- a/light/txpool_test.go
+++ b/light/txpool_test.go
@@ -88,7 +88,7 @@ func TestTxPool(t *testing.T) {
 	)
 	gspec.MustCommit(ldb)
 	// Assemble the test environment
-	blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil)
+	blockchain, _ := core.NewBlockChain(sdb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}, nil, nil)
 	gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), sdb, poolTestBlocks, txPoolTestChainGen)
 	if _, err := blockchain.InsertChain(gchain); err != nil {
 		panic(err)
diff --git a/log/doc.go b/log/doc.go
index bff2f4966a..993743c0fd 100644
--- a/log/doc.go
+++ b/log/doc.go
@@ -65,7 +65,7 @@ This will output a log line that includes the path context that is attached to t
 
 Handlers
 
-The Handler interface defines where log lines are printed to and how they are formated. Handler is a
+The Handler interface defines where log lines are printed to and how they are formatted. Handler is a
 single interface that is inspired by net/http's handler interface:
 
     type Handler interface {
diff --git a/log/format.go b/log/format.go
index a1b5dac629..baf8fddac0 100644
--- a/log/format.go
+++ b/log/format.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"math/big"
 	"reflect"
 	"strconv"
 	"strings"
@@ -78,7 +79,7 @@ type TerminalStringer interface {
 // a terminal with color-coded level output and terser human friendly timestamp.
 // This format should only be used for interactive programs or while developing.
 //
-//     [LEVEL] [TIME] MESAGE key=value key=value ...
+//     [LEVEL] [TIME] MESSAGE key=value key=value ...
 //
 // Example:
 //
@@ -329,11 +330,20 @@ func formatLogfmtValue(value interface{}, term bool) string {
 		return "nil"
 	}
 
-	if t, ok := value.(time.Time); ok {
+	switch v := value.(type) {
+	case time.Time:
 		// Performance optimization: No need for escaping since the provided
 		// timeFormat doesn't have any escape characters, and escaping is
 		// expensive.
-		return t.Format(timeFormat)
+		return v.Format(timeFormat)
+
+	case *big.Int:
+		// Big ints get consumed by the Stringer clause so we need to handle
+		// them earlier on.
+		if v == nil {
+			return ""
+		}
+		return formatLogfmtBigInt(v)
 	}
 	if term {
 		if s, ok := value.(TerminalStringer); ok {
@@ -349,8 +359,27 @@ func formatLogfmtValue(value interface{}, term bool) string {
 		return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
 	case float64:
 		return strconv.FormatFloat(v, floatFormat, 3, 64)
-	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
-		return fmt.Sprintf("%d", value)
+	case int8:
+		return strconv.FormatInt(int64(v), 10)
+	case uint8:
+		return strconv.FormatInt(int64(v), 10)
+	case int16:
+		return strconv.FormatInt(int64(v), 10)
+	case uint16:
+		return strconv.FormatInt(int64(v), 10)
+	// Larger integers get thousands separators.
+	case int:
+		return FormatLogfmtInt64(int64(v))
+	case int32:
+		return FormatLogfmtInt64(int64(v))
+	case int64:
+		return FormatLogfmtInt64(v)
+	case uint:
+		return FormatLogfmtUint64(uint64(v))
+	case uint32:
+		return FormatLogfmtUint64(uint64(v))
+	case uint64:
+		return FormatLogfmtUint64(v)
 	case string:
 		return escapeString(v)
 	default:
@@ -358,49 +387,100 @@ func formatLogfmtValue(value interface{}, term bool) string {
 	}
 }
 
-var stringBufPool = sync.Pool{
-	New: func() interface{} { return new(bytes.Buffer) },
+// FormatLogfmtInt64 formats n with thousand separators.
+func FormatLogfmtInt64(n int64) string {
+	if n < 0 {
+		return formatLogfmtUint64(uint64(-n), true)
+	}
+	return formatLogfmtUint64(uint64(n), false)
 }
 
-func escapeString(s string) string {
-	needsQuotes := false
-	needsEscape := false
-	for _, r := range s {
-		if r <= ' ' || r == '=' || r == '"' {
-			needsQuotes = true
+// FormatLogfmtUint64 formats n with thousand separators.
+func FormatLogfmtUint64(n uint64) string {
+	return formatLogfmtUint64(n, false)
+}
+
+func formatLogfmtUint64(n uint64, neg bool) string {
+	// Small numbers are fine as is
+	if n < 100000 {
+		if neg {
+			return strconv.Itoa(-int(n))
+		} else {
+			return strconv.Itoa(int(n))
 		}
-		if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
-			needsEscape = true
+	}
+	// Large numbers should be split
+	const maxLength = 26
+
+	var (
+		out   = make([]byte, maxLength)
+		i     = maxLength - 1
+		comma = 0
+	)
+	for ; n > 0; i-- {
+		if comma == 3 {
+			comma = 0
+			out[i] = ','
+		} else {
+			comma++
+			out[i] = '0' + byte(n%10)
+			n /= 10
 		}
 	}
-	if !needsEscape && !needsQuotes {
-		return s
+	if neg {
+		out[i] = '-'
+		i--
 	}
-	e := stringBufPool.Get().(*bytes.Buffer)
-	e.WriteByte('"')
-	for _, r := range s {
-		switch r {
-		case '\\', '"':
-			e.WriteByte('\\')
-			e.WriteByte(byte(r))
-		case '\n':
-			e.WriteString("\\n")
-		case '\r':
-			e.WriteString("\\r")
-		case '\t':
-			e.WriteString("\\t")
+	return string(out[i+1:])
+}
+
+// formatLogfmtBigInt formats n with thousand separators.
+func formatLogfmtBigInt(n *big.Int) string {
+	if n.IsUint64() {
+		return FormatLogfmtUint64(n.Uint64())
+	}
+	if n.IsInt64() {
+		return FormatLogfmtInt64(n.Int64())
+	}
+
+	var (
+		text  = n.String()
+		buf   = make([]byte, len(text)+len(text)/3)
+		comma = 0
+		i     = len(buf) - 1
+	)
+	for j := len(text) - 1; j >= 0; j, i = j-1, i-1 {
+		c := text[j]
+
+		switch {
+		case c == '-':
+			buf[i] = c
+		case comma == 3:
+			buf[i] = ','
+			i--
+			comma = 0
+			fallthrough
 		default:
-			e.WriteRune(r)
+			buf[i] = c
+			comma++
 		}
 	}
-	e.WriteByte('"')
-	var ret string
-	if needsQuotes {
-		ret = e.String()
-	} else {
-		ret = string(e.Bytes()[1 : e.Len()-1])
+	return string(buf[i+1:])
+}
+
+// escapeString checks if the provided string needs escaping/quoting, and
+// calls strconv.Quote if needed
+func escapeString(s string) string {
+	needsQuoting := false
+	for _, r := range s {
+		// We quote everything below " (0x34) and above~ (0x7E), plus equal-sign
+		if r <= '"' || r > '~' || r == '=' {
+			needsQuoting = true
+			break
+		}
+	}
+	if !needsQuoting {
+		return s
 	}
-	e.Reset()
-	stringBufPool.Put(e)
-	return ret
+	return strconv.Quote(s)
 }
diff --git a/log/format_test.go b/log/format_test.go
new file mode 100644
index 0000000000..d7e0a95768
--- /dev/null
+++ b/log/format_test.go
@@ -0,0 +1,95 @@
+package log
+
+import (
+	"math"
+	"math/big"
+	"math/rand"
+	"testing"
+)
+
+func TestPrettyInt64(t *testing.T) {
+	tests := []struct {
+		n int64
+		s string
+	}{
+		{0, "0"},
+		{10, "10"},
+		{-10, "-10"},
+		{100, "100"},
+		{-100, "-100"},
+		{1000, "1000"},
+		{-1000, "-1000"},
+		{10000, "10000"},
+		{-10000, "-10000"},
+		{99999, "99999"},
+		{-99999, "-99999"},
+		{100000, "100,000"},
+		{-100000, "-100,000"},
+		{1000000, "1,000,000"},
+		{-1000000, "-1,000,000"},
+		{math.MaxInt64, "9,223,372,036,854,775,807"},
+		{math.MinInt64, "-9,223,372,036,854,775,808"},
+	}
+	for i, tt := range tests {
+		if have := FormatLogfmtInt64(tt.n); have != tt.s {
+			t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s)
+		}
+	}
+}
+
+func TestPrettyUint64(t *testing.T) {
+	tests := []struct {
+		n uint64
+		s string
+	}{
+		{0, "0"},
+		{10, "10"},
+		{100, "100"},
+		{1000, "1000"},
+		{10000, "10000"},
+		{99999, "99999"},
+		{100000, "100,000"},
+		{1000000, "1,000,000"},
+		{math.MaxUint64, "18,446,744,073,709,551,615"},
+	}
+	for i, tt := range tests {
+		if have := FormatLogfmtUint64(tt.n); have != tt.s {
+			t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s)
+		}
+	}
+}
+
+func TestPrettyBigInt(t *testing.T) {
+	tests := []struct {
+		int string
+		s   string
+	}{
+		{"111222333444555678999", "111,222,333,444,555,678,999"},
+		{"-111222333444555678999", "-111,222,333,444,555,678,999"},
+		{"11122233344455567899900", "11,122,233,344,455,567,899,900"},
+		{"-11122233344455567899900", "-11,122,233,344,455,567,899,900"},
+	}
+
+	for _, tt := range tests {
+		v, _ := new(big.Int).SetString(tt.int, 10)
+		if have := formatLogfmtBigInt(v); have != tt.s {
+			t.Errorf("invalid output %s, want %s", have, tt.s)
+		}
+	}
+}
+
+var sink string
+
+func BenchmarkPrettyInt64Logfmt(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		sink = FormatLogfmtInt64(rand.Int63())
+	}
+}
+
+func BenchmarkPrettyUint64Logfmt(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		sink = FormatLogfmtUint64(rand.Uint64())
+	}
+}
diff --git a/log/handler.go b/log/handler.go
index b73533d5f3..88dbe99b2e 100644
--- a/log/handler.go
+++ b/log/handler.go
@@ -133,7 +133,7 @@ func formatCall(format string, c stack.Call) string {
 }
 
 // CallerStackHandler returns a Handler that adds a stack trace to the context
-// with key "stack". The stack trace is formated as a space separated list of
+// with key "stack". The stack trace is formatted as a space separated list of
 // call sites inside matching []'s. The most recent call site is listed first.
 // Each call site is formatted according to format. See the documentation of
 // package github.com/go-stack/stack for the list of supported formats.
diff --git a/metrics/config.go b/metrics/config.go
new file mode 100644
index 0000000000..d05d664265
--- /dev/null
+++ b/metrics/config.go
@@ -0,0 +1,45 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// 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 .
+
+package metrics
+
+// Config contains the configuration for the metric collection.
+type Config struct {
+	Enabled          bool   `toml:",omitempty"`
+	EnabledExpensive bool   `toml:",omitempty"`
+	HTTP             string `toml:",omitempty"`
+	Port             int    `toml:",omitempty"`
+	EnableInfluxDB   bool   `toml:",omitempty"`
+	InfluxDBEndpoint string `toml:",omitempty"`
+	InfluxDBDatabase string `toml:",omitempty"`
+	InfluxDBUsername string `toml:",omitempty"`
+	InfluxDBPassword string `toml:",omitempty"`
+	InfluxDBTags     string `toml:",omitempty"`
+}
+
+// DefaultConfig is the default config for metrics used in go-ethereum.
+var DefaultConfig = Config{
+	Enabled:          false,
+	EnabledExpensive: false,
+	HTTP:             "127.0.0.1",
+	Port:             6060,
+	EnableInfluxDB:   false,
+	InfluxDBEndpoint: "http://localhost:8086",
+	InfluxDBDatabase: "geth",
+	InfluxDBUsername: "test",
+	InfluxDBPassword: "test",
+	InfluxDBTags:     "host=localhost",
+}
diff --git a/metrics/cpu_enabled.go b/metrics/cpu_enabled.go
index 99d44e4002..02192928b7 100644
--- a/metrics/cpu_enabled.go
+++ b/metrics/cpu_enabled.go
@@ -18,14 +18,26 @@
 
 package metrics
 
-import "github.com/elastic/gosigar"
+import (
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/shirou/gopsutil/cpu"
+)
 
 // ReadCPUStats retrieves the current CPU stats.
 func ReadCPUStats(stats *CPUStats) {
-	global := gosigar.Cpu{}
-	global.Get()
-
-	stats.GlobalTime = int64(global.User + global.Nice + global.Sys)
-	stats.GlobalWait = int64(global.Wait)
+	// passing false to request all cpu times
+	timeStats, err := cpu.Times(false)
+	if err != nil {
+		log.Error("Could not read cpu stats", "err", err)
+		return
+	}
+	if len(timeStats) == 0 {
+		log.Error("Empty cpu stats")
+		return
+	}
+	// requesting all cpu times will always return an array with only one time stats entry
+	timeStat := timeStats[0]
+	stats.GlobalTime = int64((timeStat.User + timeStat.Nice + timeStat.System) * cpu.ClocksPerSec)
+	stats.GlobalWait = int64((timeStat.Iowait) * cpu.ClocksPerSec)
 	stats.LocalTime = getProcessCPUTime()
 }
diff --git a/metrics/ewma.go b/metrics/ewma.go
index 57c949e7d4..039286493e 100644
--- a/metrics/ewma.go
+++ b/metrics/ewma.go
@@ -4,6 +4,7 @@ import (
 	"math"
 	"sync"
 	"sync/atomic"
+	"time"
 )
 
 // EWMAs continuously calculate an exponentially-weighted moving average
@@ -85,7 +86,7 @@ type StandardEWMA struct {
 func (a *StandardEWMA) Rate() float64 {
 	a.mutex.Lock()
 	defer a.mutex.Unlock()
-	return a.rate * float64(1e9)
+	return a.rate * float64(time.Second)
 }
 
 // Snapshot returns a read-only copy of the EWMA.
@@ -98,7 +99,7 @@ func (a *StandardEWMA) Snapshot() EWMA {
 func (a *StandardEWMA) Tick() {
 	count := atomic.LoadInt64(&a.uncounted)
 	atomic.AddInt64(&a.uncounted, -count)
-	instantRate := float64(count) / float64(5e9)
+	instantRate := float64(count) / float64(5*time.Second)
 	a.mutex.Lock()
 	defer a.mutex.Unlock()
 	if a.init {
diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go
index 55820f1aab..3ebe8cc68a 100644
--- a/metrics/exp/exp.go
+++ b/metrics/exp/exp.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"sync"
 
+	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/metrics/prometheus"
 )
@@ -52,6 +53,20 @@ func ExpHandler(r metrics.Registry) http.Handler {
 	return http.HandlerFunc(e.expHandler)
 }
 
+// Setup starts a dedicated metrics server at the given address.
+// This function enables metrics reporting separate from pprof.
+func Setup(address string) {
+	m := http.NewServeMux()
+	m.Handle("/debug/metrics", ExpHandler(metrics.DefaultRegistry))
+	m.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry))
+	log.Info("Starting metrics server", "addr", fmt.Sprintf("http://%s/debug/metrics", address))
+	go func() {
+		if err := http.ListenAndServe(address, m); err != nil {
+			log.Error("Failure in running metrics server", "err", err)
+		}
+	}()
+}
+
 func (exp *exp) getInt(name string) *expvar.Int {
 	var v *expvar.Int
 	exp.expvarLock.Lock()
@@ -113,7 +128,7 @@ func (exp *exp) publishMeter(name string, metric metrics.Meter) {
 	exp.getInt(name + ".count").Set(m.Count())
 	exp.getFloat(name + ".one-minute").Set(m.Rate1())
 	exp.getFloat(name + ".five-minute").Set(m.Rate5())
-	exp.getFloat(name + ".fifteen-minute").Set((m.Rate15()))
+	exp.getFloat(name + ".fifteen-minute").Set(m.Rate15())
 	exp.getFloat(name + ".mean").Set(m.RateMean())
 }
 
diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go
index 3ee568e7ba..02b75580c4 100644
--- a/metrics/gauge_float64_test.go
+++ b/metrics/gauge_float64_test.go
@@ -12,27 +12,27 @@ func BenchmarkGuageFloat64(b *testing.B) {
 
 func TestGaugeFloat64(t *testing.T) {
 	g := NewGaugeFloat64()
-	g.Update(float64(47.0))
-	if v := g.Value(); float64(47.0) != v {
+	g.Update(47.0)
+	if v := g.Value(); 47.0 != v {
 		t.Errorf("g.Value(): 47.0 != %v\n", v)
 	}
 }
 
 func TestGaugeFloat64Snapshot(t *testing.T) {
 	g := NewGaugeFloat64()
-	g.Update(float64(47.0))
+	g.Update(47.0)
 	snapshot := g.Snapshot()
 	g.Update(float64(0))
-	if v := snapshot.Value(); float64(47.0) != v {
+	if v := snapshot.Value(); 47.0 != v {
 		t.Errorf("g.Value(): 47.0 != %v\n", v)
 	}
 }
 
 func TestGetOrRegisterGaugeFloat64(t *testing.T) {
 	r := NewRegistry()
-	NewRegisteredGaugeFloat64("foo", r).Update(float64(47.0))
+	NewRegisteredGaugeFloat64("foo", r).Update(47.0)
 	t.Logf("registry: %v", r)
-	if g := GetOrRegisterGaugeFloat64("foo", r); float64(47.0) != g.Value() {
+	if g := GetOrRegisterGaugeFloat64("foo", r); 47.0 != g.Value() {
 		t.Fatal(g)
 	}
 }
diff --git a/metrics/histogram.go b/metrics/histogram.go
index 46f3bbd2f1..2c54ce8b40 100644
--- a/metrics/histogram.go
+++ b/metrics/histogram.go
@@ -26,6 +26,15 @@ func GetOrRegisterHistogram(name string, r Registry, s Sample) Histogram {
 	return r.GetOrRegister(name, func() Histogram { return NewHistogram(s) }).(Histogram)
 }
 
+// GetOrRegisterHistogramLazy returns an existing Histogram or constructs and
+// registers a new StandardHistogram.
+func GetOrRegisterHistogramLazy(name string, r Registry, s func() Sample) Histogram {
+	if nil == r {
+		r = DefaultRegistry
+	}
+	return r.GetOrRegister(name, func() Histogram { return NewHistogram(s()) }).(Histogram)
+}
+
 // NewHistogram constructs a new StandardHistogram from a Sample.
 func NewHistogram(s Sample) Histogram {
 	if !Enabled {
diff --git a/metrics/influxdb/influxdb.go b/metrics/influxdb/influxdb.go
index d4fc478e7b..52d0091034 100644
--- a/metrics/influxdb/influxdb.go
+++ b/metrics/influxdb/influxdb.go
@@ -162,26 +162,29 @@ func (r *reporter) send() error {
 			})
 		case metrics.Histogram:
 			ms := metric.Snapshot()
-			ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
-			pts = append(pts, client.Point{
-				Measurement: fmt.Sprintf("%s%s.histogram", namespace, name),
-				Tags:        r.tags,
-				Fields: map[string]interface{}{
-					"count":    ms.Count(),
-					"max":      ms.Max(),
-					"mean":     ms.Mean(),
-					"min":      ms.Min(),
-					"stddev":   ms.StdDev(),
-					"variance": ms.Variance(),
-					"p50":      ps[0],
-					"p75":      ps[1],
-					"p95":      ps[2],
-					"p99":      ps[3],
-					"p999":     ps[4],
-					"p9999":    ps[5],
-				},
-				Time: now,
-			})
+
+			if ms.Count() > 0 {
+				ps := ms.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999})
+				pts = append(pts, client.Point{
+					Measurement: fmt.Sprintf("%s%s.histogram", namespace, name),
+					Tags:        r.tags,
+					Fields: map[string]interface{}{
+						"count":    ms.Count(),
+						"max":      ms.Max(),
+						"mean":     ms.Mean(),
+						"min":      ms.Min(),
+						"stddev":   ms.StdDev(),
+						"variance": ms.Variance(),
+						"p50":      ps[0],
+						"p75":      ps[1],
+						"p95":      ps[2],
+						"p99":      ps[3],
+						"p999":     ps[4],
+						"p9999":    ps[5],
+					},
+					Time: now,
+				})
+			}
 		case metrics.Meter:
 			ms := metric.Snapshot()
 			pts = append(pts, client.Point{
diff --git a/metrics/meter.go b/metrics/meter.go
index 58d170fae0..60ae919d04 100644
--- a/metrics/meter.go
+++ b/metrics/meter.go
@@ -2,6 +2,7 @@ package metrics
 
 import (
 	"sync"
+	"sync/atomic"
 	"time"
 )
 
@@ -100,6 +101,11 @@ func NewRegisteredMeterForced(name string, r Registry) Meter {
 
 // MeterSnapshot is a read-only copy of another Meter.
 type MeterSnapshot struct {
+	// WARNING: The `temp` field is accessed atomically.
+	// On 32 bit platforms, only 64-bit aligned fields can be atomic. The struct is
+	// guaranteed to be so aligned, so take advantage of that. For more information,
+	// see https://golang.org/pkg/sync/atomic/#pkg-note-BUG.
+	temp                           int64
 	count                          int64
 	rate1, rate5, rate15, rateMean float64
 }
@@ -149,7 +155,7 @@ func (NilMeter) Rate1() float64 { return 0.0 }
 // Rate5 is a no-op.
 func (NilMeter) Rate5() float64 { return 0.0 }
 
-// Rate15is a no-op.
+// Rate15 is a no-op.
 func (NilMeter) Rate15() float64 { return 0.0 }
 
 // RateMean is a no-op.
@@ -167,7 +173,7 @@ type StandardMeter struct {
 	snapshot    *MeterSnapshot
 	a1, a5, a15 EWMA
 	startTime   time.Time
-	stopped     bool
+	stopped     uint32
 }
 
 func newStandardMeter() *StandardMeter {
@@ -182,11 +188,8 @@ func newStandardMeter() *StandardMeter {
 
 // Stop stops the meter, Mark() will be a no-op if you use it after being stopped.
 func (m *StandardMeter) Stop() {
-	m.lock.Lock()
-	stopped := m.stopped
-	m.stopped = true
-	m.lock.Unlock()
-	if !stopped {
+	stopped := atomic.SwapUint32(&m.stopped, 1)
+	if stopped != 1 {
 		arbiter.Lock()
 		delete(arbiter.meters, m)
 		arbiter.Unlock()
@@ -194,57 +197,45 @@ func (m *StandardMeter) Stop() {
 }
 
 // Count returns the number of events recorded.
+// It updates the meter to be as accurate as possible
 func (m *StandardMeter) Count() int64 {
-	m.lock.RLock()
-	count := m.snapshot.count
-	m.lock.RUnlock()
-	return count
+	m.lock.Lock()
+	defer m.lock.Unlock()
+	m.updateMeter()
+	return m.snapshot.count
 }
 
 // Mark records the occurrence of n events.
 func (m *StandardMeter) Mark(n int64) {
-	m.lock.Lock()
-	defer m.lock.Unlock()
-	if m.stopped {
-		return
-	}
-	m.snapshot.count += n
-	m.a1.Update(n)
-	m.a5.Update(n)
-	m.a15.Update(n)
-	m.updateSnapshot()
+	atomic.AddInt64(&m.snapshot.temp, n)
 }
 
 // Rate1 returns the one-minute moving average rate of events per second.
 func (m *StandardMeter) Rate1() float64 {
 	m.lock.RLock()
-	rate1 := m.snapshot.rate1
-	m.lock.RUnlock()
-	return rate1
+	defer m.lock.RUnlock()
+	return m.snapshot.rate1
 }
 
 // Rate5 returns the five-minute moving average rate of events per second.
 func (m *StandardMeter) Rate5() float64 {
 	m.lock.RLock()
-	rate5 := m.snapshot.rate5
-	m.lock.RUnlock()
-	return rate5
+	defer m.lock.RUnlock()
+	return m.snapshot.rate5
 }
 
 // Rate15 returns the fifteen-minute moving average rate of events per second.
 func (m *StandardMeter) Rate15() float64 {
 	m.lock.RLock()
-	rate15 := m.snapshot.rate15
-	m.lock.RUnlock()
-	return rate15
+	defer m.lock.RUnlock()
+	return m.snapshot.rate15
 }
 
 // RateMean returns the meter's mean rate of events per second.
 func (m *StandardMeter) RateMean() float64 {
 	m.lock.RLock()
-	rateMean := m.snapshot.rateMean
-	m.lock.RUnlock()
-	return rateMean
+	defer m.lock.RUnlock()
+	return m.snapshot.rateMean
 }
 
 // Snapshot returns a read-only copy of the meter.
@@ -264,9 +255,19 @@ func (m *StandardMeter) updateSnapshot() {
 	snapshot.rateMean = float64(snapshot.count) / time.Since(m.startTime).Seconds()
 }
 
+func (m *StandardMeter) updateMeter() {
+	// should only run with write lock held on m.lock
+	n := atomic.SwapInt64(&m.snapshot.temp, 0)
+	m.snapshot.count += n
+	m.a1.Update(n)
+	m.a5.Update(n)
+	m.a15.Update(n)
+}
+
 func (m *StandardMeter) tick() {
 	m.lock.Lock()
 	defer m.lock.Unlock()
+	m.updateMeter()
 	m.a1.Tick()
 	m.a5.Tick()
 	m.a15.Tick()
@@ -282,7 +283,7 @@ type meterArbiter struct {
 	ticker  *time.Ticker
 }
 
-var arbiter = meterArbiter{ticker: time.NewTicker(5e9), meters: make(map[*StandardMeter]struct{})}
+var arbiter = meterArbiter{ticker: time.NewTicker(5 * time.Second), meters: make(map[*StandardMeter]struct{})}
 
 // Ticks meters on the scheduled interval
 func (ma *meterArbiter) tick() {
diff --git a/metrics/meter_test.go b/metrics/meter_test.go
index 28472253e8..b3f6cb8c0c 100644
--- a/metrics/meter_test.go
+++ b/metrics/meter_test.go
@@ -17,7 +17,7 @@ func TestGetOrRegisterMeter(t *testing.T) {
 	r := NewRegistry()
 	NewRegisteredMeter("foo", r).Mark(47)
 	if m := GetOrRegisterMeter("foo", r); m.Count() != 47 {
-		t.Fatal(m)
+		t.Fatal(m.Count())
 	}
 }
 
@@ -29,10 +29,11 @@ func TestMeterDecay(t *testing.T) {
 	defer ma.ticker.Stop()
 	m := newStandardMeter()
 	ma.meters[m] = struct{}{}
-	go ma.tick()
 	m.Mark(1)
+	ma.tickMeters()
 	rateMean := m.RateMean()
 	time.Sleep(100 * time.Millisecond)
+	ma.tickMeters()
 	if m.RateMean() >= rateMean {
 		t.Error("m.RateMean() didn't decrease")
 	}
@@ -72,3 +73,19 @@ func TestMeterZero(t *testing.T) {
 		t.Errorf("m.Count(): 0 != %v\n", count)
 	}
 }
+
+func TestMeterRepeat(t *testing.T) {
+	m := NewMeter()
+	for i := 0; i < 101; i++ {
+		m.Mark(int64(i))
+	}
+	if count := m.Count(); count != 5050 {
+		t.Errorf("m.Count(): 5050 != %v\n", count)
+	}
+	for i := 0; i < 101; i++ {
+		m.Mark(int64(i))
+	}
+	if count := m.Count(); count != 10100 {
+		t.Errorf("m.Count(): 10100 != %v\n", count)
+	}
+}
diff --git a/metrics/prometheus/collector.go b/metrics/prometheus/collector.go
index 8350fa2aaf..3959cbf5e1 100644
--- a/metrics/prometheus/collector.go
+++ b/metrics/prometheus/collector.go
@@ -30,7 +30,7 @@ var (
 	typeCounterTpl         = "# TYPE %s counter\n"
 	typeSummaryTpl         = "# TYPE %s summary\n"
 	keyValueTpl            = "%s %v\n\n"
-	keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n\n"
+	keyQuantileTagValueTpl = "%s {quantile=\"%s\"} %v\n"
 )
 
 // collector is a collection of byte buffers that aggregate Prometheus reports
@@ -39,7 +39,7 @@ type collector struct {
 	buff *bytes.Buffer
 }
 
-// newCollector createa a new Prometheus metric aggregator.
+// newCollector creates a new Prometheus metric aggregator.
 func newCollector() *collector {
 	return &collector{
 		buff: &bytes.Buffer{},
@@ -62,9 +62,11 @@ func (c *collector) addHistogram(name string, m metrics.Histogram) {
 	pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
 	ps := m.Percentiles(pv)
 	c.writeSummaryCounter(name, m.Count())
+	c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
 	for i := range pv {
 		c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
 	}
+	c.buff.WriteRune('\n')
 }
 
 func (c *collector) addMeter(name string, m metrics.Meter) {
@@ -75,9 +77,11 @@ func (c *collector) addTimer(name string, m metrics.Timer) {
 	pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}
 	ps := m.Percentiles(pv)
 	c.writeSummaryCounter(name, m.Count())
+	c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
 	for i := range pv {
 		c.writeSummaryPercentile(name, strconv.FormatFloat(pv[i], 'f', -1, 64), ps[i])
 	}
+	c.buff.WriteRune('\n')
 }
 
 func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) {
@@ -87,9 +91,11 @@ func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) {
 	ps := m.Percentiles([]float64{50, 95, 99})
 	val := m.Values()
 	c.writeSummaryCounter(name, len(val))
+	c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name)))
 	c.writeSummaryPercentile(name, "0.50", ps[0])
 	c.writeSummaryPercentile(name, "0.95", ps[1])
 	c.writeSummaryPercentile(name, "0.99", ps[2])
+	c.buff.WriteRune('\n')
 }
 
 func (c *collector) writeGaugeCounter(name string, value interface{}) {
@@ -106,7 +112,6 @@ func (c *collector) writeSummaryCounter(name string, value interface{}) {
 
 func (c *collector) writeSummaryPercentile(name, p string, value interface{}) {
 	name = mutateKey(name)
-	c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, name))
 	c.buff.WriteString(fmt.Sprintf(keyQuantileTagValueTpl, name, p, value))
 }
 
diff --git a/metrics/prometheus/collector_test.go b/metrics/prometheus/collector_test.go
new file mode 100644
index 0000000000..43f2f804d3
--- /dev/null
+++ b/metrics/prometheus/collector_test.go
@@ -0,0 +1,110 @@
+package prometheus
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/metrics"
+)
+
+func TestMain(m *testing.M) {
+	metrics.Enabled = true
+	os.Exit(m.Run())
+}
+
+func TestCollector(t *testing.T) {
+	c := newCollector()
+
+	counter := metrics.NewCounter()
+	counter.Inc(12345)
+	c.addCounter("test/counter", counter)
+
+	gauge := metrics.NewGauge()
+	gauge.Update(23456)
+	c.addGauge("test/gauge", gauge)
+
+	gaugeFloat64 := metrics.NewGaugeFloat64()
+	gaugeFloat64.Update(34567.89)
+	c.addGaugeFloat64("test/gauge_float64", gaugeFloat64)
+
+	histogram := metrics.NewHistogram(&metrics.NilSample{})
+	c.addHistogram("test/histogram", histogram)
+
+	meter := metrics.NewMeter()
+	defer meter.Stop()
+	meter.Mark(9999999)
+	c.addMeter("test/meter", meter)
+
+	timer := metrics.NewTimer()
+	defer timer.Stop()
+	timer.Update(20 * time.Millisecond)
+	timer.Update(21 * time.Millisecond)
+	timer.Update(22 * time.Millisecond)
+	timer.Update(120 * time.Millisecond)
+	timer.Update(23 * time.Millisecond)
+	timer.Update(24 * time.Millisecond)
+	c.addTimer("test/timer", timer)
+
+	resettingTimer := metrics.NewResettingTimer()
+	resettingTimer.Update(10 * time.Millisecond)
+	resettingTimer.Update(11 * time.Millisecond)
+	resettingTimer.Update(12 * time.Millisecond)
+	resettingTimer.Update(120 * time.Millisecond)
+	resettingTimer.Update(13 * time.Millisecond)
+	resettingTimer.Update(14 * time.Millisecond)
+	c.addResettingTimer("test/resetting_timer", resettingTimer.Snapshot())
+
+	emptyResettingTimer := metrics.NewResettingTimer().Snapshot()
+	c.addResettingTimer("test/empty_resetting_timer", emptyResettingTimer)
+
+	const expectedOutput = `# TYPE test_counter gauge
+test_counter 12345
+
+# TYPE test_gauge gauge
+test_gauge 23456
+
+# TYPE test_gauge_float64 gauge
+test_gauge_float64 34567.89
+
+# TYPE test_histogram_count counter
+test_histogram_count 0
+
+# TYPE test_histogram summary
+test_histogram {quantile="0.5"} 0
+test_histogram {quantile="0.75"} 0
+test_histogram {quantile="0.95"} 0
+test_histogram {quantile="0.99"} 0
+test_histogram {quantile="0.999"} 0
+test_histogram {quantile="0.9999"} 0
+
+# TYPE test_meter gauge
+test_meter 9999999
+
+# TYPE test_timer_count counter
+test_timer_count 6
+
+# TYPE test_timer summary
+test_timer {quantile="0.5"} 2.25e+07
+test_timer {quantile="0.75"} 4.8e+07
+test_timer {quantile="0.95"} 1.2e+08
+test_timer {quantile="0.99"} 1.2e+08
+test_timer {quantile="0.999"} 1.2e+08
+test_timer {quantile="0.9999"} 1.2e+08
+
+# TYPE test_resetting_timer_count counter
+test_resetting_timer_count 6
+
+# TYPE test_resetting_timer summary
+test_resetting_timer {quantile="0.50"} 12000000
+test_resetting_timer {quantile="0.95"} 120000000
+test_resetting_timer {quantile="0.99"} 120000000
+
+`
+	exp := c.buff.String()
+	if exp != expectedOutput {
+		t.Log("Expected Output:\n", expectedOutput)
+		t.Log("Actual Output:\n", exp)
+		t.Fatal("unexpected collector output")
+	}
+}
diff --git a/metrics/resetting_sample.go b/metrics/resetting_sample.go
new file mode 100644
index 0000000000..43c1129cd0
--- /dev/null
+++ b/metrics/resetting_sample.go
@@ -0,0 +1,24 @@
+package metrics
+
+// ResettingSample converts an ordinary sample into one that resets whenever its
+// snapshot is retrieved. This will break for multi-monitor systems, but when only
+// a single metric is being pushed out, this ensure that low-frequency events don't
+// skew th charts indefinitely.
+func ResettingSample(sample Sample) Sample {
+	return &resettingSample{
+		Sample: sample,
+	}
+}
+
+// resettingSample is a simple wrapper around a sample that resets it upon the
+// snapshot retrieval.
+type resettingSample struct {
+	Sample
+}
+
+// Snapshot returns a read-only copy of the sample with the original reset.
+func (rs *resettingSample) Snapshot() Sample {
+	s := rs.Sample.Snapshot()
+	rs.Sample.Clear()
+	return s
+}
diff --git a/miner/miner.go b/miner/miner.go
index d5c9428954..8c9d249f6c 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -20,7 +20,6 @@ package miner
 import (
 	"fmt"
 	"math/big"
-	"sync/atomic"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -44,7 +43,8 @@ type Backend interface {
 // Config is the configuration parameters of mining.
 type Config struct {
 	Etherbase     common.Address `toml:",omitempty"` // Public address for block mining rewards (default = first account)
-	Notify        []string       `toml:",omitempty"` // HTTP URL list to be notified of new work packages(only useful in ethash).
+	Notify        []string       `toml:",omitempty"` // HTTP URL list to be notified of new work packages (only useful in ethash).
+	NotifyFull    bool           `toml:",omitempty"` // Notify with pending block headers instead of work packages
 	ExtraData     hexutil.Bytes  `toml:",omitempty"` // Block extra data set by the miner
 	DelayLeftOver time.Duration  // Time for broadcast block
 	GasFloor      uint64         // Target gas floor for mined blocks.
@@ -62,19 +62,19 @@ type Miner struct {
 	eth      Backend
 	engine   consensus.Engine
 	exitCh   chan struct{}
-
-	canStart    int32 // can start indicates whether we can start the mining operation
-	shouldStart int32 // should start indicates whether we should start after sync
+	startCh  chan common.Address
+	stopCh   chan struct{}
 }
 
 func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool) *Miner {
 	miner := &Miner{
-		eth:      eth,
-		mux:      mux,
-		engine:   engine,
-		exitCh:   make(chan struct{}),
-		worker:   newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, false),
-		canStart: 1,
+		eth:     eth,
+		mux:     mux,
+		engine:  engine,
+		exitCh:  make(chan struct{}),
+		startCh: make(chan common.Address),
+		stopCh:  make(chan struct{}),
+		worker:  newWorker(config, chainConfig, engine, eth, mux, isLocalBlock, false),
 	}
 	go miner.update()
 
@@ -87,57 +87,73 @@ func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *even
 // and halt your mining operation for as long as the DOS continues.
 func (miner *Miner) update() {
 	events := miner.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
-	defer events.Unsubscribe()
+	defer func() {
+		if !events.Closed() {
+			events.Unsubscribe()
+		}
+	}()
 
+	shouldStart := false
+	canStart := true
+	dlEventCh := events.Chan()
 	for {
 		select {
-		case ev := <-events.Chan():
+		case ev := <-dlEventCh:
 			if ev == nil {
-				return
+				// Unsubscription done, stop listening
+				dlEventCh = nil
+				continue
 			}
 			switch ev.Data.(type) {
 			case downloader.StartEvent:
-				atomic.StoreInt32(&miner.canStart, 0)
-				if miner.Mining() {
-					miner.Stop()
-					atomic.StoreInt32(&miner.shouldStart, 1)
+				wasMining := miner.Mining()
+				miner.worker.stop()
+				canStart = false
+				if wasMining {
+					// Resume mining after sync was finished
+					shouldStart = true
 					log.Info("Mining aborted due to sync")
 				}
-			case downloader.DoneEvent, downloader.FailedEvent:
-				shouldStart := atomic.LoadInt32(&miner.shouldStart) == 1
-
-				atomic.StoreInt32(&miner.canStart, 1)
-				atomic.StoreInt32(&miner.shouldStart, 0)
+			case downloader.FailedEvent:
+				canStart = true
 				if shouldStart {
-					miner.Start(miner.coinbase)
+					miner.SetEtherbase(miner.coinbase)
+					miner.worker.start()
 				}
-				// stop immediately and ignore all further pending events
-				return
+			case downloader.DoneEvent:
+				canStart = true
+				if shouldStart {
+					miner.SetEtherbase(miner.coinbase)
+					miner.worker.start()
+				}
+				// Stop reacting to downloader events
+				events.Unsubscribe()
+			}
+		case addr := <-miner.startCh:
+			miner.SetEtherbase(addr)
+			if canStart {
+				miner.worker.start()
 			}
+			shouldStart = true
+		case <-miner.stopCh:
+			shouldStart = false
+			miner.worker.stop()
 		case <-miner.exitCh:
+			miner.worker.close()
 			return
 		}
 	}
 }
 
 func (miner *Miner) Start(coinbase common.Address) {
-	atomic.StoreInt32(&miner.shouldStart, 1)
-	miner.SetEtherbase(coinbase)
-
-	if atomic.LoadInt32(&miner.canStart) == 0 {
-		log.Info("Network syncing, will start miner afterwards")
-		return
-	}
-	miner.worker.start()
+	miner.startCh <- coinbase
 }
 
 func (miner *Miner) Stop() {
-	miner.worker.stop()
-	atomic.StoreInt32(&miner.shouldStart, 0)
+	miner.stopCh <- struct{}{}
 }
 
 func (miner *Miner) Close() {
-	miner.worker.close()
 	close(miner.exitCh)
 }
 
@@ -145,7 +161,7 @@ func (miner *Miner) Mining() bool {
 	return miner.worker.isRunning()
 }
 
-func (miner *Miner) HashRate() uint64 {
+func (miner *Miner) Hashrate() uint64 {
 	if pow, ok := miner.engine.(consensus.PoW); ok {
 		return uint64(pow.Hashrate())
 	}
@@ -202,8 +218,25 @@ func (miner *Miner) SetEtherbase(addr common.Address) {
 	miner.worker.setEtherbase(addr)
 }
 
+// EnablePreseal turns on the preseal mining feature. It's enabled by default.
+// Note this function shouldn't be exposed to API, it's unnecessary for users
+// (miners) to actually know the underlying detail. It's only for outside project
+// which uses this library.
+func (miner *Miner) EnablePreseal() {
+	miner.worker.enablePreseal()
+}
+
+// DisablePreseal turns off the preseal mining feature. It's necessary for some
+// fake consensus engine which can seal blocks instantaneously.
+// Note this function shouldn't be exposed to API, it's unnecessary for users
+// (miners) to actually know the underlying detail. It's only for outside project
+// which uses this library.
+func (miner *Miner) DisablePreseal() {
+	miner.worker.disablePreseal()
+}
+
 // SubscribePendingLogs starts delivering logs from pending transactions
 // to the given channel.
-func (self *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription {
-	return self.worker.pendingLogsFeed.Subscribe(ch)
+func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription {
+	return miner.worker.pendingLogsFeed.Subscribe(ch)
 }
diff --git a/miner/miner_test.go b/miner/miner_test.go
new file mode 100644
index 0000000000..da1e472dbd
--- /dev/null
+++ b/miner/miner_test.go
@@ -0,0 +1,261 @@
+// 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 .
+
+// Package miner implements Ethereum block creation and mining.
+package miner
+
+import (
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/clique"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/event"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+type mockBackend struct {
+	bc     *core.BlockChain
+	txPool *core.TxPool
+}
+
+func NewMockBackend(bc *core.BlockChain, txPool *core.TxPool) *mockBackend {
+	return &mockBackend{
+		bc:     bc,
+		txPool: txPool,
+	}
+}
+
+func (m *mockBackend) BlockChain() *core.BlockChain {
+	return m.bc
+}
+
+func (m *mockBackend) TxPool() *core.TxPool {
+	return m.txPool
+}
+
+type testBlockChain struct {
+	statedb       *state.StateDB
+	gasLimit      uint64
+	chainHeadFeed *event.Feed
+}
+
+func (bc *testBlockChain) CurrentBlock() *types.Block {
+	return types.NewBlock(&types.Header{
+		GasLimit: bc.gasLimit,
+	}, nil, nil, nil, trie.NewStackTrie(nil))
+}
+
+func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
+	return bc.CurrentBlock()
+}
+
+func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
+	return bc.statedb, nil
+}
+
+func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
+	return bc.chainHeadFeed.Subscribe(ch)
+}
+
+func TestMiner(t *testing.T) {
+	miner, mux := createMiner(t)
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, true)
+	// Start the downloader
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, false)
+	// Stop the downloader and wait for the update loop to run
+	mux.Post(downloader.DoneEvent{})
+	waitForMiningState(t, miner, true)
+
+	// Subsequent downloader events after a successful DoneEvent should not cause the
+	// miner to start or stop. This prevents a security vulnerability
+	// that would allow entities to present fake high blocks that would
+	// stop mining operations by causing a downloader sync
+	// until it was discovered they were invalid, whereon mining would resume.
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, true)
+
+	mux.Post(downloader.FailedEvent{})
+	waitForMiningState(t, miner, true)
+}
+
+// TestMinerDownloaderFirstFails tests that mining is only
+// permitted to run indefinitely once the downloader sees a DoneEvent (success).
+// An initial FailedEvent should allow mining to stop on a subsequent
+// downloader StartEvent.
+func TestMinerDownloaderFirstFails(t *testing.T) {
+	miner, mux := createMiner(t)
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, true)
+	// Start the downloader
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, false)
+
+	// Stop the downloader and wait for the update loop to run
+	mux.Post(downloader.FailedEvent{})
+	waitForMiningState(t, miner, true)
+
+	// Since the downloader hasn't yet emitted a successful DoneEvent,
+	// we expect the miner to stop on next StartEvent.
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, false)
+
+	// Downloader finally succeeds.
+	mux.Post(downloader.DoneEvent{})
+	waitForMiningState(t, miner, true)
+
+	// Downloader starts again.
+	// Since it has achieved a DoneEvent once, we expect miner
+	// state to be unchanged.
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, true)
+
+	mux.Post(downloader.FailedEvent{})
+	waitForMiningState(t, miner, true)
+}
+
+func TestMinerStartStopAfterDownloaderEvents(t *testing.T) {
+	miner, mux := createMiner(t)
+
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, true)
+	// Start the downloader
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, false)
+
+	// Downloader finally succeeds.
+	mux.Post(downloader.DoneEvent{})
+	waitForMiningState(t, miner, true)
+
+	miner.Stop()
+	waitForMiningState(t, miner, false)
+
+	miner.Start(common.HexToAddress("0x678910"))
+	waitForMiningState(t, miner, true)
+
+	miner.Stop()
+	waitForMiningState(t, miner, false)
+}
+
+func TestStartWhileDownload(t *testing.T) {
+	miner, mux := createMiner(t)
+	waitForMiningState(t, miner, false)
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, true)
+	// Stop the downloader and wait for the update loop to run
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, false)
+	// Starting the miner after the downloader should not work
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, false)
+}
+
+func TestStartStopMiner(t *testing.T) {
+	miner, _ := createMiner(t)
+	waitForMiningState(t, miner, false)
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, true)
+	miner.Stop()
+	waitForMiningState(t, miner, false)
+}
+
+func TestCloseMiner(t *testing.T) {
+	miner, _ := createMiner(t)
+	waitForMiningState(t, miner, false)
+	miner.Start(common.HexToAddress("0x12345"))
+	waitForMiningState(t, miner, true)
+	// Terminate the miner and wait for the update loop to run
+	miner.Close()
+	waitForMiningState(t, miner, false)
+}
+
+// TestMinerSetEtherbase checks that etherbase becomes set even if mining isn't
+// possible at the moment
+func TestMinerSetEtherbase(t *testing.T) {
+	miner, mux := createMiner(t)
+	// Start with a 'bad' mining address
+	miner.Start(common.HexToAddress("0xdead"))
+	waitForMiningState(t, miner, true)
+	// Start the downloader
+	mux.Post(downloader.StartEvent{})
+	waitForMiningState(t, miner, false)
+	// Now user tries to configure proper mining address
+	miner.Start(common.HexToAddress("0x1337"))
+	// Stop the downloader and wait for the update loop to run
+	mux.Post(downloader.DoneEvent{})
+
+	waitForMiningState(t, miner, true)
+	// The miner should now be using the good address
+	if got, exp := miner.coinbase, common.HexToAddress("0x1337"); got != exp {
+		t.Fatalf("Wrong coinbase, got %x expected %x", got, exp)
+	}
+}
+
+// waitForMiningState waits until either
+// * the desired mining state was reached
+// * a timeout was reached which fails the test
+func waitForMiningState(t *testing.T, m *Miner, mining bool) {
+	t.Helper()
+
+	var state bool
+	for i := 0; i < 100; i++ {
+		time.Sleep(10 * time.Millisecond)
+		if state = m.Mining(); state == mining {
+			return
+		}
+	}
+	t.Fatalf("Mining() == %t, want %t", state, mining)
+}
+
+func createMiner(t *testing.T) (*Miner, *event.TypeMux) {
+	// Create Ethash config
+	config := Config{
+		Etherbase: common.HexToAddress("123456789"),
+	}
+	// Create chainConfig
+	memdb := memorydb.New()
+	chainDB := rawdb.NewDatabase(memdb)
+	genesis := core.DeveloperGenesisBlock(15, common.HexToAddress("12345"))
+	chainConfig, _, err := core.SetupGenesisBlock(chainDB, genesis)
+	if err != nil {
+		t.Fatalf("can't create new chain config: %v", err)
+	}
+	// Create consensus engine
+	engine := clique.New(chainConfig.Clique, chainDB)
+	// Create Ethereum backend
+	bc, err := core.NewBlockChain(chainDB, nil, chainConfig, engine, vm.Config{}, nil, nil)
+	if err != nil {
+		t.Fatalf("can't create new chain %v", err)
+	}
+	statedb, _ := state.New(common.Hash{}, state.NewDatabase(chainDB), nil)
+	blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)}
+
+	pool := core.NewTxPool(testTxPoolConfig, chainConfig, blockchain)
+	backend := NewMockBackend(bc, pool)
+	// Create event Mux
+	mux := new(event.TypeMux)
+	// Create Miner
+	return New(backend, &config, chainConfig, mux, engine, nil), mux
+}
diff --git a/miner/stress_clique.go b/miner/stress_clique.go
index 2f8a28b68f..c585e0b1f6 100644
--- a/miner/stress_clique.go
+++ b/miner/stress_clique.go
@@ -61,30 +61,31 @@ func main() {
 	genesis := makeGenesis(faucets, sealers)
 
 	var (
-		nodes  []*node.Node
+		nodes  []*eth.Ethereum
 		enodes []*enode.Node
 	)
+
 	for _, sealer := range sealers {
 		// Start the node and wait until it's up
-		node, err := makeSealer(genesis)
+		stack, ethBackend, err := makeSealer(genesis)
 		if err != nil {
 			panic(err)
 		}
-		defer node.Close()
+		defer stack.Close()
 
-		for node.Server().NodeInfo().Ports.Listener == 0 {
+		for stack.Server().NodeInfo().Ports.Listener == 0 {
 			time.Sleep(250 * time.Millisecond)
 		}
-		// Connect the node to al the previous ones
+		// Connect the node to all the previous ones
 		for _, n := range enodes {
-			node.Server().AddPeer(n)
+			stack.Server().AddPeer(n)
 		}
-		// Start tracking the node and it's enode
-		nodes = append(nodes, node)
-		enodes = append(enodes, node.Server().Self())
+		// Start tracking the node and its enode
+		nodes = append(nodes, ethBackend)
+		enodes = append(enodes, stack.Server().Self())
 
 		// Inject the signer key and start sealing with it
-		store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+		store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
 		signer, err := store.ImportECDSA(sealer, "")
 		if err != nil {
 			panic(err)
@@ -93,15 +94,11 @@ func main() {
 			panic(err)
 		}
 	}
-	// Iterate over all the nodes and start signing with them
-	time.Sleep(3 * time.Second)
 
+	// Iterate over all the nodes and start signing on them
+	time.Sleep(3 * time.Second)
 	for _, node := range nodes {
-		var ethereum *eth.Ethereum
-		if err := node.Service(ðereum); err != nil {
-			panic(err)
-		}
-		if err := ethereum.StartMining(1); err != nil {
+		if err := node.StartMining(1); err != nil {
 			panic(err)
 		}
 	}
@@ -110,25 +107,22 @@ func main() {
 	// Start injecting transactions from the faucet like crazy
 	nonces := make([]uint64, len(faucets))
 	for {
+		// Pick a random signer node
 		index := rand.Intn(len(faucets))
+		backend := nodes[index%len(nodes)]
 
-		// Fetch the accessor for the relevant signer
-		var ethereum *eth.Ethereum
-		if err := nodes[index%len(nodes)].Service(ðereum); err != nil {
-			panic(err)
-		}
 		// Create a self transaction and inject into the pool
 		tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index])
 		if err != nil {
 			panic(err)
 		}
-		if err := ethereum.TxPool().AddLocal(tx); err != nil {
+		if err := backend.TxPool().AddLocal(tx); err != nil {
 			panic(err)
 		}
 		nonces[index]++
 
 		// Wait if we're too saturated
-		if pend, _ := ethereum.TxPool().Stats(); pend > 2048 {
+		if pend, _ := backend.TxPool().Stats(); pend > 2048 {
 			time.Sleep(100 * time.Millisecond)
 		}
 	}
@@ -171,7 +165,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core
 	return genesis
 }
 
-func makeSealer(genesis *core.Genesis) (*node.Node, error) {
+func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) {
 	// Define the basic configurations for the Ethereum node
 	datadir, _ := ioutil.TempDir("", "")
 
@@ -184,32 +178,32 @@ func makeSealer(genesis *core.Genesis) (*node.Node, error) {
 			NoDiscovery: true,
 			MaxPeers:    25,
 		},
-		NoUSB: true,
 	}
 	// Start the node and configure a full Ethereum node on it
 	stack, err := node.New(config)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-		return eth.New(ctx, ð.Config{
-			Genesis:         genesis,
-			NetworkId:       genesis.Config.ChainID.Uint64(),
-			SyncMode:        downloader.FullSync,
-			DatabaseCache:   256,
-			DatabaseHandles: 256,
-			TxPool:          core.DefaultTxPoolConfig,
-			GPO:             eth.DefaultConfig.GPO,
-			Miner: miner.Config{
-				GasFloor: genesis.GasLimit * 9 / 10,
-				GasCeil:  genesis.GasLimit * 11 / 10,
-				GasPrice: big.NewInt(1),
-				Recommit: time.Second,
-			},
-		})
-	}); err != nil {
-		return nil, err
+	// Create and register the backend
+	ethBackend, err := eth.New(stack, ðconfig.Config{
+		Genesis:         genesis,
+		NetworkId:       genesis.Config.ChainID.Uint64(),
+		SyncMode:        downloader.FullSync,
+		DatabaseCache:   256,
+		DatabaseHandles: 256,
+		TxPool:          core.DefaultTxPoolConfig,
+		GPO:             eth.DefaultConfig.GPO,
+		Miner: miner.Config{
+			GasFloor: genesis.GasLimit * 9 / 10,
+			GasCeil:  genesis.GasLimit * 11 / 10,
+			GasPrice: big.NewInt(1),
+			Recommit: time.Second,
+		},
+	})
+	if err != nil {
+		return nil, nil, err
 	}
-	// Start the node and return if successful
-	return stack, stack.Start()
+
+	err = stack.Start()
+	return stack, ethBackend, err
 }
diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go
index 94d5b30aa5..0b838d48b9 100644
--- a/miner/stress_ethash.go
+++ b/miner/stress_ethash.go
@@ -61,43 +61,39 @@ func main() {
 	genesis := makeGenesis(faucets)
 
 	var (
-		nodes  []*node.Node
+		nodes  []*eth.Ethereum
 		enodes []*enode.Node
 	)
 	for i := 0; i < 4; i++ {
 		// Start the node and wait until it's up
-		node, err := makeMiner(genesis)
+		stack, ethBackend, err := makeMiner(genesis)
 		if err != nil {
 			panic(err)
 		}
-		defer node.Close()
+		defer stack.Close()
 
-		for node.Server().NodeInfo().Ports.Listener == 0 {
+		for stack.Server().NodeInfo().Ports.Listener == 0 {
 			time.Sleep(250 * time.Millisecond)
 		}
-		// Connect the node to al the previous ones
+		// Connect the node to all the previous ones
 		for _, n := range enodes {
-			node.Server().AddPeer(n)
+			stack.Server().AddPeer(n)
 		}
-		// Start tracking the node and it's enode
-		nodes = append(nodes, node)
-		enodes = append(enodes, node.Server().Self())
+		// Start tracking the node and its enode
+		nodes = append(nodes, ethBackend)
+		enodes = append(enodes, stack.Server().Self())
 
 		// Inject the signer key and start sealing with it
-		store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+		store := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
 		if _, err := store.NewAccount(""); err != nil {
 			panic(err)
 		}
 	}
-	// Iterate over all the nodes and start signing with them
-	time.Sleep(3 * time.Second)
 
+	// Iterate over all the nodes and start mining
+	time.Sleep(3 * time.Second)
 	for _, node := range nodes {
-		var ethereum *eth.Ethereum
-		if err := node.Service(ðereum); err != nil {
-			panic(err)
-		}
-		if err := ethereum.StartMining(1); err != nil {
+		if err := node.StartMining(1); err != nil {
 			panic(err)
 		}
 	}
@@ -106,25 +102,22 @@ func main() {
 	// Start injecting transactions from the faucets like crazy
 	nonces := make([]uint64, len(faucets))
 	for {
+		// Pick a random mining node
 		index := rand.Intn(len(faucets))
+		backend := nodes[index%len(nodes)]
 
-		// Fetch the accessor for the relevant signer
-		var ethereum *eth.Ethereum
-		if err := nodes[index%len(nodes)].Service(ðereum); err != nil {
-			panic(err)
-		}
 		// Create a self transaction and inject into the pool
 		tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index])
 		if err != nil {
 			panic(err)
 		}
-		if err := ethereum.TxPool().AddLocal(tx); err != nil {
+		if err := backend.TxPool().AddLocal(tx); err != nil {
 			panic(err)
 		}
 		nonces[index]++
 
 		// Wait if we're too saturated
-		if pend, _ := ethereum.TxPool().Stats(); pend > 2048 {
+		if pend, _ := backend.TxPool().Stats(); pend > 2048 {
 			time.Sleep(100 * time.Millisecond)
 		}
 	}
@@ -149,7 +142,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis {
 	return genesis
 }
 
-func makeMiner(genesis *core.Genesis) (*node.Node, error) {
+func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) {
 	// Define the basic configurations for the Ethereum node
 	datadir, _ := ioutil.TempDir("", "")
 
@@ -162,34 +155,33 @@ func makeMiner(genesis *core.Genesis) (*node.Node, error) {
 			NoDiscovery: true,
 			MaxPeers:    25,
 		},
-		NoUSB:             true,
 		UseLightweightKDF: true,
 	}
-	// Start the node and configure a full Ethereum node on it
+	// Create the node and configure a full Ethereum node on it
 	stack, err := node.New(config)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
-	if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-		return eth.New(ctx, ð.Config{
-			Genesis:         genesis,
-			NetworkId:       genesis.Config.ChainID.Uint64(),
-			SyncMode:        downloader.FullSync,
-			DatabaseCache:   256,
-			DatabaseHandles: 256,
-			TxPool:          core.DefaultTxPoolConfig,
-			GPO:             eth.DefaultConfig.GPO,
-			Ethash:          eth.DefaultConfig.Ethash,
-			Miner: miner.Config{
-				GasFloor: genesis.GasLimit * 9 / 10,
-				GasCeil:  genesis.GasLimit * 11 / 10,
-				GasPrice: big.NewInt(1),
-				Recommit: time.Second,
-			},
-		})
-	}); err != nil {
-		return nil, err
+	ethBackend, err := eth.New(stack, ðconfig.Config{
+		Genesis:         genesis,
+		NetworkId:       genesis.Config.ChainID.Uint64(),
+		SyncMode:        downloader.FullSync,
+		DatabaseCache:   256,
+		DatabaseHandles: 256,
+		TxPool:          core.DefaultTxPoolConfig,
+		GPO:             eth.DefaultConfig.GPO,
+		Ethash:          eth.DefaultConfig.Ethash,
+		Miner: miner.Config{
+			GasFloor: genesis.GasLimit * 9 / 10,
+			GasCeil:  genesis.GasLimit * 11 / 10,
+			GasPrice: big.NewInt(1),
+			Recommit: time.Second,
+		},
+	})
+	if err != nil {
+		return nil, nil, err
 	}
-	// Start the node and return if successful
-	return stack, stack.Start()
+
+	err = stack.Start()
+	return stack, ethBackend, err
 }
diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go
index 3a176e8bd6..0489f1ea4a 100644
--- a/miner/unconfirmed.go
+++ b/miner/unconfirmed.go
@@ -50,7 +50,7 @@ type unconfirmedBlocks struct {
 	chain  chainRetriever // Blockchain to verify canonical status through
 	depth  uint           // Depth after which to discard previous blocks
 	blocks *ring.Ring     // Block infos to allow canonical chain cross checks
-	lock   sync.RWMutex   // Protects the fields from concurrent access
+	lock   sync.Mutex     // Protects the fields from concurrent access
 }
 
 // newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go
index 42e77f3e64..dc83cb9265 100644
--- a/miner/unconfirmed_test.go
+++ b/miner/unconfirmed_test.go
@@ -19,7 +19,6 @@ package miner
 import (
 	"testing"
 
-	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
 )
 
@@ -43,7 +42,7 @@ func TestUnconfirmedInsertBounds(t *testing.T) {
 	for depth := uint64(0); depth < 2*uint64(limit); depth++ {
 		// Insert multiple blocks for the same level just to stress it
 		for i := 0; i < int(depth); i++ {
-			pool.Insert(depth, common.Hash([32]byte{byte(depth), byte(i)}))
+			pool.Insert(depth, [32]byte{byte(depth), byte(i)})
 		}
 		// Validate that no blocks below the depth allowance are left in
 		pool.blocks.Do(func(block interface{}) {
@@ -63,7 +62,7 @@ func TestUnconfirmedShifts(t *testing.T) {
 
 	pool := newUnconfirmedBlocks(new(noopChainRetriever), limit)
 	for depth := start; depth < start+uint64(limit); depth++ {
-		pool.Insert(depth, common.Hash([32]byte{byte(depth)}))
+		pool.Insert(depth, [32]byte{byte(depth)})
 	}
 	// Try to shift below the limit and ensure no blocks are dropped
 	pool.Shift(start + uint64(limit) - 1)
diff --git a/miner/worker.go b/miner/worker.go
index 54aef7bfbd..950efc7e63 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -37,6 +37,7 @@ import (
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/trie"
 )
 
 const (
@@ -176,6 +177,13 @@ type worker struct {
 	running int32 // The indicator whether the consensus engine is running or not.
 	newTxs  int32 // New arrival transaction count since last sealing work submitting.
 
+	// noempty is the flag used to control whether the feature of pre-seal empty
+	// block is enabled. The default value is false(pre-seal is enabled by default).
+	// But in some special scenario the consensus engine will seal blocks instantaneously,
+	// in this case this feature will add all empty blocks into canonical chain
+	// non-stop and no real transaction will be included.
+	noempty uint32
+
 	// External functions
 	isLocalBlock func(block *types.Block) bool // Function used to determine whether the specified block is mined by local miner.
 
@@ -254,6 +262,16 @@ func (w *worker) setRecommitInterval(interval time.Duration) {
 	w.resubmitIntervalCh <- interval
 }
 
+// disablePreseal disables pre-sealing mining feature
+func (w *worker) disablePreseal() {
+	atomic.StoreUint32(&w.noempty, 1)
+}
+
+// enablePreseal enables pre-sealing mining feature
+func (w *worker) enablePreseal() {
+	atomic.StoreUint32(&w.noempty, 0)
+}
+
 // pending returns the pending state and corresponding block.
 func (w *worker) pending() (*types.Block, *state.StateDB) {
 	// return a snapshot to avoid contention on currentMu mutex
@@ -292,9 +310,35 @@ func (w *worker) isRunning() bool {
 // close terminates all background threads maintained by the worker.
 // Note the worker does not support being closed multiple times.
 func (w *worker) close() {
+	if w.current != nil && w.current.state != nil {
+		w.current.state.StopPrefetcher()
+	}
+	atomic.StoreInt32(&w.running, 0)
 	close(w.exitCh)
 }
 
+// recalcRecommit recalculates the resubmitting interval upon feedback.
+func recalcRecommit(minRecommit, prev time.Duration, target float64, inc bool) time.Duration {
+	var (
+		prevF = float64(prev.Nanoseconds())
+		next  float64
+	)
+	if inc {
+		next = prevF*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias)
+		max := float64(maxRecommitInterval.Nanoseconds())
+		if next > max {
+			next = max
+		}
+	} else {
+		next = prevF*(1-intervalAdjustRatio) + intervalAdjustRatio*(target-intervalAdjustBias)
+		min := float64(minRecommit.Nanoseconds())
+		if next < min {
+			next = min
+		}
+	}
+	return time.Duration(int64(next))
+}
+
 // newWorkLoop is a standalone goroutine to submit new mining work upon received events.
 func (w *worker) newWorkLoop(recommit time.Duration) {
 	var (
@@ -313,31 +357,14 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
 			atomic.StoreInt32(interrupt, s)
 		}
 		interrupt = new(int32)
-		w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}
+		select {
+		case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}:
+		case <-w.exitCh:
+			return
+		}
 		timer.Reset(recommit)
 		atomic.StoreInt32(&w.newTxs, 0)
 	}
-	// recalcRecommit recalculates the resubmitting interval upon feedback.
-	recalcRecommit := func(target float64, inc bool) {
-		var (
-			prev = float64(recommit.Nanoseconds())
-			next float64
-		)
-		if inc {
-			next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias)
-			// Recap if interval is larger than the maximum time interval
-			if next > float64(maxRecommitInterval.Nanoseconds()) {
-				next = float64(maxRecommitInterval.Nanoseconds())
-			}
-		} else {
-			next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target-intervalAdjustBias)
-			// Recap if interval is less than the user specified minimum
-			if next < float64(minRecommit.Nanoseconds()) {
-				next = float64(minRecommit.Nanoseconds())
-			}
-		}
-		recommit = time.Duration(int64(next))
-	}
 	// clearPending cleans the stale pending tasks.
 	clearPending := func(number uint64) {
 		w.pendingMu.Lock()
@@ -394,11 +421,12 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
 			// Adjust resubmit interval by feedback.
 			if adjust.inc {
 				before := recommit
-				recalcRecommit(float64(recommit.Nanoseconds())/adjust.ratio, true)
+				target := float64(recommit.Nanoseconds()) / adjust.ratio
+				recommit = recalcRecommit(minRecommit, recommit, target, true)
 				log.Trace("Increase miner recommit interval", "from", before, "to", recommit)
 			} else {
 				before := recommit
-				recalcRecommit(float64(minRecommit.Nanoseconds()), false)
+				recommit = recalcRecommit(minRecommit, recommit, float64(minRecommit.Nanoseconds()), false)
 				log.Trace("Decrease miner recommit interval", "from", before, "to", recommit)
 			}
 
@@ -494,8 +522,9 @@ func (w *worker) mainLoop() {
 					w.updateSnapshot()
 				}
 			} else {
-				// If clique is running in dev mode(period is 0), disable
-				// advance sealing here.
+				// Special case, if the consensus engine is 0 period clique(dev mode),
+				// submit mining work here since all empty submission will be rejected
+				// by clique. Of course the advance sealing(empty submission) is disabled.
 				if (w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0) ||
 					(w.chainConfig.Parlia != nil && w.chainConfig.Parlia.Period == 0) {
 					w.commitNewWork(nil, true, time.Now().Unix())
@@ -550,7 +579,7 @@ func (w *worker) taskLoop() {
 				continue
 			}
 			w.pendingMu.Lock()
-			w.pendingTasks[w.engine.SealHash(task.block.Header())] = task
+			w.pendingTasks[sealHash] = task
 			w.pendingMu.Unlock()
 
 			if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil {
@@ -631,19 +660,22 @@ func (w *worker) resultLoop() {
 
 // makeCurrent creates a new environment for the current cycle.
 func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
+	// Retrieve the parent state to execute on top and start a prefetcher for
+	// the miner to speed block sealing up a bit
 	state, err := w.chain.StateAt(parent.Root())
 	if err != nil {
 		return err
 	}
+	state.StartPrefetcher("miner")
+
 	env := &environment{
-		signer:    types.NewEIP155Signer(w.chainConfig.ChainID),
+		signer:    types.MakeSigner(w.chainConfig, header.Number),
 		state:     state,
 		ancestors: mapset.NewSet(),
 		family:    mapset.NewSet(),
 		uncles:    mapset.NewSet(),
 		header:    header,
 	}
-
 	// when 08 is processed ancestors contain 07 (quick block)
 	for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) {
 		for _, uncle := range ancestor.Uncles() {
@@ -652,9 +684,14 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
 		env.family.Add(ancestor.Hash())
 		env.ancestors.Add(ancestor.Hash())
 	}
-
 	// Keep track of transactions which return errors so they can be removed
 	env.tcount = 0
+
+	// Swap out the old work with the new one, terminating any leftover prefetcher
+	// processes in the mean time and starting a new one.
+	if w.current != nil && w.current.state != nil {
+		w.current.state.StopPrefetcher()
+	}
 	w.current = env
 	return nil
 }
@@ -706,8 +743,8 @@ func (w *worker) updateSnapshot() {
 		w.current.txs,
 		uncles,
 		w.current.receipts,
+		trie.NewStackTrie(nil),
 	)
-
 	w.snapshotState = w.current.state.Copy()
 }
 
@@ -801,28 +838,33 @@ LOOP:
 		w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount)
 
 		logs, err := w.commitTransaction(tx, coinbase)
-		switch err {
-		case core.ErrGasLimitReached:
+		switch {
+		case errors.Is(err, core.ErrGasLimitReached):
 			// Pop the current out-of-gas transaction without shifting in the next from the account
 			log.Trace("Gas limit exceeded for current block", "sender", from)
 			txs.Pop()
 
-		case core.ErrNonceTooLow:
+		case errors.Is(err, core.ErrNonceTooLow):
 			// New head notification data race between the transaction pool and miner, shift
 			log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
 			txs.Shift()
 
-		case core.ErrNonceTooHigh:
+		case errors.Is(err, core.ErrNonceTooHigh):
 			// Reorg notification data race between the transaction pool and miner, skip account =
 			log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
 			txs.Pop()
 
-		case nil:
+		case errors.Is(err, nil):
 			// Everything ok, collect the logs and shift in the next transaction from the same account
 			coalescedLogs = append(coalescedLogs, logs...)
 			w.current.tcount++
 			txs.Shift()
 
+		case errors.Is(err, core.ErrTxTypeNotSupported):
+			// Pop the unsupported transaction without shifting in the next from the account
+			log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
+			txs.Pop()
+
 		default:
 			// Strange error, discard the transaction and get the next in line (note, the
 			// nonce-too-high clause will prevent us from executing in vain).
@@ -865,13 +907,6 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64)
 	if parent.Time() >= uint64(timestamp) {
 		timestamp = int64(parent.Time() + 1)
 	}
-	// this will ensure we're not going off too far in the future
-	if now := time.Now().Unix(); timestamp > now+1 {
-		wait := time.Duration(timestamp-now) * time.Second
-		log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait))
-		time.Sleep(wait)
-	}
-
 	num := parent.Number()
 	header := &types.Header{
 		ParentHash: parent.Hash(),
@@ -942,9 +977,9 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64)
 	commitUncles(w.localUncles)
 	commitUncles(w.remoteUncles)
 
-	if !noempty {
-		// Create an empty block based on temporary copied state for sealing in advance without waiting block
-		// execution finished.
+	// Create an empty block based on temporary copied state for
+	// sealing in advance without waiting block execution finished.
+	if !noempty && atomic.LoadUint32(&w.noempty) == 0 {
 		w.commit(uncles, nil, false, tstart)
 	}
 
@@ -997,15 +1032,10 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
 		select {
 		case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
 			w.unconfirmed.Shift(block.NumberU64() - 1)
-
-			feesWei := new(big.Int)
-			for i, tx := range block.Transactions() {
-				feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice()))
-			}
-			feesEth := new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
-
 			log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
-				"uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))
+				"uncles", len(uncles), "txs", w.current.tcount,
+				"gas", block.GasUsed(), "fees", totalFees(block, receipts),
+				"elapsed", common.PrettyDuration(time.Since(start)))
 
 		case <-w.exitCh:
 			log.Info("Worker has exited")
@@ -1017,6 +1047,16 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
 	return nil
 }
 
+// copyReceipts makes a deep copy of the given receipts.
+func copyReceipts(receipts []*types.Receipt) []*types.Receipt {
+	result := make([]*types.Receipt, len(receipts))
+	for i, l := range receipts {
+		cpy := *l
+		result[i] = &cpy
+	}
+	return result
+}
+
 // postSideBlock fires a side chain event, only use it for testing.
 func (w *worker) postSideBlock(event core.ChainSideEvent) {
 	select {
@@ -1024,3 +1064,12 @@ func (w *worker) postSideBlock(event core.ChainSideEvent) {
 	case <-w.exitCh:
 	}
 }
+
+// totalFees computes total consumed fees in ETH. Block transactions and receipts have to have the same order.
+func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
+	feesWei := new(big.Int)
+	for i, tx := range block.Transactions() {
+		feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice()))
+	}
+	return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
+}
diff --git a/miner/worker_test.go b/miner/worker_test.go
index 86bb7db646..0fe62316e1 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -81,10 +81,25 @@ func init() {
 		Period: 10,
 		Epoch:  30000,
 	}
-	tx1, _ := types.SignTx(types.NewTransaction(0, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey)
+
+	signer := types.LatestSigner(params.TestChainConfig)
+	tx1 := types.MustSignNewTx(testBankKey, signer, &types.AccessListTx{
+		ChainID: params.TestChainConfig.ChainID,
+		Nonce:   0,
+		To:      &testUserAddress,
+		Value:   big.NewInt(1000),
+		Gas:     params.TxGas,
+	})
 	pendingTxs = append(pendingTxs, tx1)
-	tx2, _ := types.SignTx(types.NewTransaction(1, testUserAddress, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey)
+
+	tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{
+		Nonce: 1,
+		To:    &testUserAddress,
+		Value: big.NewInt(1000),
+		Gas:   params.TxGas,
+	})
 	newTxs = append(newTxs, tx2)
+
 	rand.Seed(time.Now().UnixNano())
 }
 
@@ -117,7 +132,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
 	}
 	genesis := gspec.MustCommit(db)
 
-	chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil)
+	chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil)
 	txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain)
 
 	// Generate a small n-block chain and an uncle block for it
@@ -212,7 +227,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
 	// This test chain imports the mined blocks.
 	db2 := rawdb.NewMemoryDatabase()
 	b.genesis.MustCommit(db2)
-	chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil)
+	chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
 	defer chain.Stop()
 
 	// Ignore empty commit here for less noise.
@@ -285,9 +300,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
 	}
 	w.skipSealHook = func(task *task) bool { return true }
 	w.fullTaskHook = func() {
-		// Arch64 unit tests are running in a VM on travis, they must
-		// be given more time to execute.
-		time.Sleep(time.Second)
+		time.Sleep(100 * time.Millisecond)
 	}
 	w.start() // Start mining!
 	for i := 0; i < 2; i += 1 {
diff --git a/mobile/big.go b/mobile/big.go
index 86ea93245a..c08bcf93f2 100644
--- a/mobile/big.go
+++ b/mobile/big.go
@@ -35,6 +35,16 @@ func NewBigInt(x int64) *BigInt {
 	return &BigInt{big.NewInt(x)}
 }
 
+// NewBigIntFromString allocates and returns a new BigInt set to x
+// interpreted in the provided base.
+func NewBigIntFromString(x string, base int) *BigInt {
+	b, success := new(big.Int).SetString(x, base)
+	if !success {
+		return nil
+	}
+	return &BigInt{b}
+}
+
 // GetBytes returns the absolute value of x as a big-endian byte slice.
 func (bi *BigInt) GetBytes() []byte {
 	return bi.bigint.Bytes()
diff --git a/mobile/bind.go b/mobile/bind.go
index 9b4c0bb0e5..e32d864aa5 100644
--- a/mobile/bind.go
+++ b/mobile/bind.go
@@ -32,7 +32,7 @@ import (
 // Signer is an interface defining the callback when a contract requires a
 // method to sign the transaction before submission.
 type Signer interface {
-	Sign(*Address, *Transaction) (tx *Transaction, _ error)
+	Sign(addr *Address, unsignedTx *Transaction) (tx *Transaction, _ error)
 }
 
 type MobileSigner struct {
@@ -40,7 +40,7 @@ type MobileSigner struct {
 }
 
 func (s *MobileSigner) Sign(addr *Address, unsignedTx *Transaction) (signedTx *Transaction, _ error) {
-	sig, err := s.sign(types.EIP155Signer{}, addr.address, unsignedTx.tx)
+	sig, err := s.sign(addr.address, unsignedTx.tx)
 	if err != nil {
 		return nil, err
 	}
@@ -82,12 +82,16 @@ func NewTransactOpts() *TransactOpts {
 
 // NewKeyedTransactOpts is a utility method to easily create a transaction signer
 // from a single private key.
-func NewKeyedTransactOpts(keyJson []byte, passphrase string) (*TransactOpts, error) {
+func NewKeyedTransactOpts(keyJson []byte, passphrase string, chainID *big.Int) (*TransactOpts, error) {
 	key, err := keystore.DecryptKey(keyJson, passphrase)
 	if err != nil {
 		return nil, err
 	}
-	return &TransactOpts{*bind.NewKeyedTransactor(key.PrivateKey)}, nil
+	auth, err := bind.NewKeyedTransactorWithChainID(key.PrivateKey, chainID)
+	if err != nil {
+		return nil, err
+	}
+	return &TransactOpts{*auth}, nil
 }
 
 func (opts *TransactOpts) GetFrom() *Address    { return &Address{opts.opts.From} }
@@ -106,7 +110,7 @@ func (opts *TransactOpts) GetGasLimit() int64   { return int64(opts.opts.GasLimi
 func (opts *TransactOpts) SetFrom(from *Address) { opts.opts.From = from.address }
 func (opts *TransactOpts) SetNonce(nonce int64)  { opts.opts.Nonce = big.NewInt(nonce) }
 func (opts *TransactOpts) SetSigner(s Signer) {
-	opts.opts.Signer = func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
+	opts.opts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
 		sig, err := s.Sign(&Address{addr}, &Transaction{tx})
 		if err != nil {
 			return nil, err
@@ -171,20 +175,12 @@ func (c *BoundContract) GetDeployer() *Transaction {
 // Call invokes the (constant) contract method with params as input values and
 // sets the output to result.
 func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, args *Interfaces) error {
-	if len(out.objects) == 1 {
-		result := out.objects[0]
-		if err := c.contract.Call(&opts.opts, result, method, args.objects...); err != nil {
-			return err
-		}
-		out.objects[0] = result
-	} else {
-		results := make([]interface{}, len(out.objects))
-		copy(results, out.objects)
-		if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil {
-			return err
-		}
-		copy(out.objects, results)
+	results := make([]interface{}, len(out.objects))
+	copy(results, out.objects)
+	if err := c.contract.Call(&opts.opts, &results, method, args.objects...); err != nil {
+		return err
 	}
+	copy(out.objects, results)
 	return nil
 }
 
diff --git a/mobile/common.go b/mobile/common.go
index d7e0457261..124712b4b1 100644
--- a/mobile/common.go
+++ b/mobile/common.go
@@ -87,6 +87,11 @@ func (h *Hash) GetHex() string {
 	return h.hash.Hex()
 }
 
+// String implements Stringer interface for printable representation of the hash.
+func (h *Hash) String() string {
+	return h.GetHex()
+}
+
 // Hashes represents a slice of hashes.
 type Hashes struct{ hashes []common.Hash }
 
@@ -188,6 +193,11 @@ func (a *Address) GetHex() string {
 	return a.address.Hex()
 }
 
+// String returns a printable representation of the address.
+func (a *Address) String() string {
+	return a.GetHex()
+}
+
 // Addresses represents a slice of addresses.
 type Addresses struct{ addresses []common.Address }
 
diff --git a/mobile/discover.go b/mobile/discover.go
index 9b3c93ccd9..2c699f08be 100644
--- a/mobile/discover.go
+++ b/mobile/discover.go
@@ -22,12 +22,12 @@ package geth
 import (
 	"errors"
 
-	"github.com/ethereum/go-ethereum/p2p/discv5"
+	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
 // Enode represents a host on the network.
 type Enode struct {
-	node *discv5.Node
+	node *enode.Node
 }
 
 // NewEnode parses a node designator.
@@ -53,8 +53,8 @@ type Enode struct {
 // and UDP discovery port 30301.
 //
 //    enode://@10.3.58.6:30303?discport=30301
-func NewEnode(rawurl string) (enode *Enode, _ error) {
-	node, err := discv5.ParseNode(rawurl)
+func NewEnode(rawurl string) (*Enode, error) {
+	node, err := enode.Parse(enode.ValidSchemes, rawurl)
 	if err != nil {
 		return nil, err
 	}
@@ -62,12 +62,12 @@ func NewEnode(rawurl string) (enode *Enode, _ error) {
 }
 
 // Enodes represents a slice of accounts.
-type Enodes struct{ nodes []*discv5.Node }
+type Enodes struct{ nodes []*enode.Node }
 
 // NewEnodes creates a slice of uninitialized enodes.
 func NewEnodes(size int) *Enodes {
 	return &Enodes{
-		nodes: make([]*discv5.Node, size),
+		nodes: make([]*enode.Node, size),
 	}
 }
 
diff --git a/mobile/doc.go b/mobile/doc.go
index 64d47bec2a..20131afc2e 100644
--- a/mobile/doc.go
+++ b/mobile/doc.go
@@ -24,7 +24,7 @@
 //
 // Since gomobile cannot bridge arbitrary types between Go and Android/iOS, the
 // exposed APIs need to be manually wrapped into simplified types, with custom
-// constructors and getters/setters to ensure that they can be meaninfully used
+// constructors and getters/setters to ensure that they can be meaningfully used
 // from Java/ObjC too.
 //
 // With this in mind, please try to limit the scope of this package and only add
diff --git a/mobile/ethereum.go b/mobile/ethereum.go
index 59da852397..97c46ddca7 100644
--- a/mobile/ethereum.go
+++ b/mobile/ethereum.go
@@ -21,7 +21,7 @@ package geth
 import (
 	"errors"
 
-	ethereum "github.com/ethereum/go-ethereum"
+	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 )
 
diff --git a/mobile/geth.go b/mobile/geth.go
index 27f1c4ed97..704d432e04 100644
--- a/mobile/geth.go
+++ b/mobile/geth.go
@@ -25,8 +25,8 @@ import (
 	"path/filepath"
 
 	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/eth/downloader"
+	"github.com/ethereum/go-ethereum/eth/ethconfig"
 	"github.com/ethereum/go-ethereum/ethclient"
 	"github.com/ethereum/go-ethereum/ethstats"
 	"github.com/ethereum/go-ethereum/internal/debug"
@@ -35,7 +35,6 @@ import (
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/nat"
 	"github.com/ethereum/go-ethereum/params"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
 )
 
 // NodeConfig represents the collection of configuration values to fine tune the Geth
@@ -71,9 +70,6 @@ type NodeConfig struct {
 	// It has the form "nodename:secret@host:port"
 	EthereumNetStats string
 
-	// WhisperEnabled specifies whether the node should run the Whisper protocol.
-	WhisperEnabled bool
-
 	// Listening address of pprof server.
 	PprofAddress string
 }
@@ -94,6 +90,22 @@ func NewNodeConfig() *NodeConfig {
 	return &config
 }
 
+// AddBootstrapNode adds an additional bootstrap node to the node config.
+func (conf *NodeConfig) AddBootstrapNode(node *Enode) {
+	conf.BootstrapNodes.Append(node)
+}
+
+// EncodeJSON encodes a NodeConfig into a JSON data dump.
+func (conf *NodeConfig) EncodeJSON() (string, error) {
+	data, err := json.Marshal(conf)
+	return string(data), err
+}
+
+// String returns a printable representation of the node config.
+func (conf *NodeConfig) String() string {
+	return encodeOrError(conf)
+}
+
 // Node represents a Geth Ethereum node instance.
 type Node struct {
 	node *node.Node
@@ -113,7 +125,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
 	}
 
 	if config.PprofAddress != "" {
-		debug.StartPProf(config.PprofAddress)
+		debug.StartPProf(config.PprofAddress, true)
 	}
 
 	// Create the empty networking stack
@@ -170,54 +182,43 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
 	}
 	// Register the Ethereum protocol if requested
 	if config.EthereumEnabled {
-		ethConf := eth.DefaultConfig
+		ethConf := ethconfig.Defaults
 		ethConf.Genesis = genesis
 		ethConf.SyncMode = downloader.LightSync
 		ethConf.NetworkId = uint64(config.EthereumNetworkID)
 		ethConf.DatabaseCache = config.EthereumDatabaseCache
-		if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-			return les.New(ctx, ðConf)
-		}); err != nil {
+		lesBackend, err := les.New(rawStack, ðConf)
+		if err != nil {
 			return nil, fmt.Errorf("ethereum init: %v", err)
 		}
 		// If netstats reporting is requested, do it
 		if config.EthereumNetStats != "" {
-			if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-				var lesServ *les.LightEthereum
-				ctx.Service(&lesServ)
-
-				return ethstats.New(config.EthereumNetStats, nil, lesServ)
-			}); err != nil {
+			if err := ethstats.New(rawStack, lesBackend.ApiBackend, lesBackend.Engine(), config.EthereumNetStats); err != nil {
 				return nil, fmt.Errorf("netstats init: %v", err)
 			}
 		}
 	}
-	// Register the Whisper protocol if requested
-	if config.WhisperEnabled {
-		if err := rawStack.Register(func(*node.ServiceContext) (node.Service, error) {
-			return whisper.New(&whisper.DefaultConfig), nil
-		}); err != nil {
-			return nil, fmt.Errorf("whisper init: %v", err)
-		}
-	}
 	return &Node{rawStack}, nil
 }
 
-// Close terminates a running node along with all it's services, tearing internal
-// state doen too. It's not possible to restart a closed node.
+// Close terminates a running node along with all it's services, tearing internal state
+// down. It is not possible to restart a closed node.
 func (n *Node) Close() error {
 	return n.node.Close()
 }
 
 // Start creates a live P2P node and starts running it.
 func (n *Node) Start() error {
+	// TODO: recreate the node so it can be started multiple times
 	return n.node.Start()
 }
 
-// Stop terminates a running node along with all it's services. If the node was
-// not started, an error is returned.
+// Stop terminates a running node along with all its services. If the node was not started,
+// an error is returned. It is not possible to restart a stopped node.
+//
+// Deprecated: use Close()
 func (n *Node) Stop() error {
-	return n.node.Stop()
+	return n.node.Close()
 }
 
 // GetEthereumClient retrieves a client to access the Ethereum subsystem.
diff --git a/mobile/params.go b/mobile/params.go
index 25d3b8565b..0fc197c9e5 100644
--- a/mobile/params.go
+++ b/mobile/params.go
@@ -22,7 +22,7 @@ import (
 	"encoding/json"
 
 	"github.com/ethereum/go-ethereum/core"
-	"github.com/ethereum/go-ethereum/p2p/discv5"
+	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/params"
 )
 
@@ -62,9 +62,13 @@ func GoerliGenesis() string {
 // FoundationBootnodes returns the enode URLs of the P2P bootstrap nodes operated
 // by the foundation running the V5 discovery protocol.
 func FoundationBootnodes() *Enodes {
-	nodes := &Enodes{nodes: make([]*discv5.Node, len(params.DiscoveryV5Bootnodes))}
-	for i, url := range params.DiscoveryV5Bootnodes {
-		nodes.nodes[i] = discv5.MustParseNode(url)
+	nodes := &Enodes{nodes: make([]*enode.Node, len(params.MainnetBootnodes))}
+	for i, url := range params.MainnetBootnodes {
+		var err error
+		nodes.nodes[i], err = enode.Parse(enode.ValidSchemes, url)
+		if err != nil {
+			panic("invalid node URL: " + err.Error())
+		}
 	}
 	return nodes
 }
diff --git a/mobile/shhclient.go b/mobile/shhclient.go
deleted file mode 100644
index 90a8b83c39..0000000000
--- a/mobile/shhclient.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2018 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 .
-
-// Contains a wrapper for the Whisper client.
-
-package geth
-
-import (
-	"github.com/ethereum/go-ethereum/whisper/shhclient"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
-)
-
-// WhisperClient provides access to the Ethereum APIs.
-type WhisperClient struct {
-	client *shhclient.Client
-}
-
-// NewWhisperClient connects a client to the given URL.
-func NewWhisperClient(rawurl string) (client *WhisperClient, _ error) {
-	rawClient, err := shhclient.Dial(rawurl)
-	return &WhisperClient{rawClient}, err
-}
-
-// GetVersion returns the Whisper sub-protocol version.
-func (wc *WhisperClient) GetVersion(ctx *Context) (version string, _ error) {
-	return wc.client.Version(ctx.context)
-}
-
-// Info returns diagnostic information about the whisper node.
-func (wc *WhisperClient) GetInfo(ctx *Context) (info *Info, _ error) {
-	rawInfo, err := wc.client.Info(ctx.context)
-	return &Info{&rawInfo}, err
-}
-
-// SetMaxMessageSize sets the maximal message size allowed by this node. Incoming
-// and outgoing messages with a larger size will be rejected. Whisper message size
-// can never exceed the limit imposed by the underlying P2P protocol (10 Mb).
-func (wc *WhisperClient) SetMaxMessageSize(ctx *Context, size int32) error {
-	return wc.client.SetMaxMessageSize(ctx.context, uint32(size))
-}
-
-// SetMinimumPoW (experimental) sets the minimal PoW required by this node.
-// This experimental function was introduced for the future dynamic adjustment of
-// PoW requirement. If the node is overwhelmed with messages, it should raise the
-// PoW requirement and notify the peers. The new value should be set relative to
-// the old value (e.g. double). The old value could be obtained via shh_info call.
-func (wc *WhisperClient) SetMinimumPoW(ctx *Context, pow float64) error {
-	return wc.client.SetMinimumPoW(ctx.context, pow)
-}
-
-// Marks specific peer trusted, which will allow it to send historic (expired) messages.
-// Note This function is not adding new nodes, the node needs to exists as a peer.
-func (wc *WhisperClient) MarkTrustedPeer(ctx *Context, enode string) error {
-	return wc.client.MarkTrustedPeer(ctx.context, enode)
-}
-
-// NewKeyPair generates a new public and private key pair for message decryption and encryption.
-// It returns an identifier that can be used to refer to the key.
-func (wc *WhisperClient) NewKeyPair(ctx *Context) (string, error) {
-	return wc.client.NewKeyPair(ctx.context)
-}
-
-// AddPrivateKey stored the key pair, and returns its ID.
-func (wc *WhisperClient) AddPrivateKey(ctx *Context, key []byte) (string, error) {
-	return wc.client.AddPrivateKey(ctx.context, key)
-}
-
-// DeleteKeyPair delete the specifies key.
-func (wc *WhisperClient) DeleteKeyPair(ctx *Context, id string) (string, error) {
-	return wc.client.DeleteKeyPair(ctx.context, id)
-}
-
-// HasKeyPair returns an indication if the node has a private key or
-// key pair matching the given ID.
-func (wc *WhisperClient) HasKeyPair(ctx *Context, id string) (bool, error) {
-	return wc.client.HasKeyPair(ctx.context, id)
-}
-
-// GetPublicKey return the public key for a key ID.
-func (wc *WhisperClient) GetPublicKey(ctx *Context, id string) ([]byte, error) {
-	return wc.client.PublicKey(ctx.context, id)
-}
-
-// GetPrivateKey return the private key for a key ID.
-func (wc *WhisperClient) GetPrivateKey(ctx *Context, id string) ([]byte, error) {
-	return wc.client.PrivateKey(ctx.context, id)
-}
-
-// NewSymmetricKey generates a random symmetric key and returns its identifier.
-// Can be used encrypting and decrypting messages where the key is known to both parties.
-func (wc *WhisperClient) NewSymmetricKey(ctx *Context) (string, error) {
-	return wc.client.NewSymmetricKey(ctx.context)
-}
-
-// AddSymmetricKey stores the key, and returns its identifier.
-func (wc *WhisperClient) AddSymmetricKey(ctx *Context, key []byte) (string, error) {
-	return wc.client.AddSymmetricKey(ctx.context, key)
-}
-
-// GenerateSymmetricKeyFromPassword generates the key from password, stores it, and returns its identifier.
-func (wc *WhisperClient) GenerateSymmetricKeyFromPassword(ctx *Context, passwd string) (string, error) {
-	return wc.client.GenerateSymmetricKeyFromPassword(ctx.context, passwd)
-}
-
-// HasSymmetricKey returns an indication if the key associated with the given id is stored in the node.
-func (wc *WhisperClient) HasSymmetricKey(ctx *Context, id string) (bool, error) {
-	return wc.client.HasSymmetricKey(ctx.context, id)
-}
-
-// GetSymmetricKey returns the symmetric key associated with the given identifier.
-func (wc *WhisperClient) GetSymmetricKey(ctx *Context, id string) ([]byte, error) {
-	return wc.client.GetSymmetricKey(ctx.context, id)
-}
-
-// DeleteSymmetricKey deletes the symmetric key associated with the given identifier.
-func (wc *WhisperClient) DeleteSymmetricKey(ctx *Context, id string) error {
-	return wc.client.DeleteSymmetricKey(ctx.context, id)
-}
-
-// Post a message onto the network.
-func (wc *WhisperClient) Post(ctx *Context, message *NewMessage) (string, error) {
-	return wc.client.Post(ctx.context, *message.newMessage)
-}
-
-// NewHeadHandler is a client-side subscription callback to invoke on events and
-// subscription failure.
-type NewMessageHandler interface {
-	OnNewMessage(message *Message)
-	OnError(failure string)
-}
-
-// SubscribeMessages subscribes to messages that match the given criteria. This method
-// is only supported on bi-directional connections such as websockets and IPC.
-// NewMessageFilter uses polling and is supported over HTTP.
-func (wc *WhisperClient) SubscribeMessages(ctx *Context, criteria *Criteria, handler NewMessageHandler, buffer int) (*Subscription, error) {
-	// Subscribe to the event internally
-	ch := make(chan *whisper.Message, buffer)
-	rawSub, err := wc.client.SubscribeMessages(ctx.context, *criteria.criteria, ch)
-	if err != nil {
-		return nil, err
-	}
-	// Start up a dispatcher to feed into the callback
-	go func() {
-		for {
-			select {
-			case message := <-ch:
-				handler.OnNewMessage(&Message{message})
-
-			case err := <-rawSub.Err():
-				if err != nil {
-					handler.OnError(err.Error())
-				}
-				return
-			}
-		}
-	}()
-	return &Subscription{rawSub}, nil
-}
-
-// NewMessageFilter creates a filter within the node. This filter can be used to poll
-// for new messages (see FilterMessages) that satisfy the given criteria. A filter can
-// timeout when it was polled for in whisper.filterTimeout.
-func (wc *WhisperClient) NewMessageFilter(ctx *Context, criteria *Criteria) (string, error) {
-	return wc.client.NewMessageFilter(ctx.context, *criteria.criteria)
-}
-
-// DeleteMessageFilter removes the filter associated with the given id.
-func (wc *WhisperClient) DeleteMessageFilter(ctx *Context, id string) error {
-	return wc.client.DeleteMessageFilter(ctx.context, id)
-}
-
-// GetFilterMessages retrieves all messages that are received between the last call to
-// this function and match the criteria that where given when the filter was created.
-func (wc *WhisperClient) GetFilterMessages(ctx *Context, id string) (*Messages, error) {
-	rawFilterMessages, err := wc.client.FilterMessages(ctx.context, id)
-	if err != nil {
-		return nil, err
-	}
-	res := make([]*whisper.Message, len(rawFilterMessages))
-	copy(res, rawFilterMessages)
-	return &Messages{res}, nil
-}
diff --git a/mobile/types.go b/mobile/types.go
index b9c44c25d7..de6457e7e1 100644
--- a/mobile/types.go
+++ b/mobile/types.go
@@ -26,9 +26,22 @@ import (
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/rlp"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
 )
 
+type jsonEncoder interface {
+	EncodeJSON() (string, error)
+}
+
+// encodeOrError tries to encode the object into json.
+// If the encoding fails the resulting error is returned.
+func encodeOrError(encoder jsonEncoder) string {
+	enc, err := encoder.EncodeJSON()
+	if err != nil {
+		return err.Error()
+	}
+	return enc
+}
+
 // A Nonce is a 64-bit hash which proves (combined with the mix-hash) that
 // a sufficient amount of computation has been carried out on a block.
 type Nonce struct {
@@ -45,6 +58,11 @@ func (n *Nonce) GetHex() string {
 	return fmt.Sprintf("0x%x", n.nonce[:])
 }
 
+// String returns a printable representation of the nonce.
+func (n *Nonce) String() string {
+	return n.GetHex()
+}
+
 // Bloom represents a 256 bit bloom filter.
 type Bloom struct {
 	bloom types.Bloom
@@ -60,6 +78,11 @@ func (b *Bloom) GetHex() string {
 	return fmt.Sprintf("0x%x", b.bloom[:])
 }
 
+// String returns a printable representation of the bloom filter.
+func (b *Bloom) String() string {
+	return b.GetHex()
+}
+
 // Header represents a block header in the Ethereum blockchain.
 type Header struct {
 	header *types.Header
@@ -98,6 +121,11 @@ func (h *Header) EncodeJSON() (string, error) {
 	return string(data), err
 }
 
+// String returns a printable representation of the header.
+func (h *Header) String() string {
+	return encodeOrError(h)
+}
+
 func (h *Header) GetParentHash() *Hash   { return &Hash{h.header.ParentHash} }
 func (h *Header) GetUncleHash() *Hash    { return &Hash{h.header.UncleHash} }
 func (h *Header) GetCoinbase() *Address  { return &Address{h.header.Coinbase} }
@@ -169,6 +197,11 @@ func (b *Block) EncodeJSON() (string, error) {
 	return string(data), err
 }
 
+// String returns a printable representation of the block.
+func (b *Block) String() string {
+	return encodeOrError(b)
+}
+
 func (b *Block) GetParentHash() *Hash           { return &Hash{b.block.ParentHash()} }
 func (b *Block) GetUncleHash() *Hash            { return &Hash{b.block.UncleHash()} }
 func (b *Block) GetCoinbase() *Address          { return &Address{b.block.Coinbase()} }
@@ -245,6 +278,11 @@ func (tx *Transaction) EncodeJSON() (string, error) {
 	return string(data), err
 }
 
+// String returns a printable representation of the transaction.
+func (tx *Transaction) String() string {
+	return encodeOrError(tx)
+}
+
 func (tx *Transaction) GetData() []byte      { return tx.tx.Data() }
 func (tx *Transaction) GetGas() int64        { return int64(tx.tx.Gas()) }
 func (tx *Transaction) GetGasPrice() *BigInt { return &BigInt{tx.tx.GasPrice()} }
@@ -337,6 +375,11 @@ func (r *Receipt) EncodeJSON() (string, error) {
 	return string(data), err
 }
 
+// String returns a printable representation of the receipt.
+func (r *Receipt) String() string {
+	return encodeOrError(r)
+}
+
 func (r *Receipt) GetStatus() int               { return int(r.receipt.Status) }
 func (r *Receipt) GetPostState() []byte         { return r.receipt.PostState }
 func (r *Receipt) GetCumulativeGasUsed() int64  { return int64(r.receipt.CumulativeGasUsed) }
@@ -345,95 +388,3 @@ func (r *Receipt) GetLogs() *Logs               { return &Logs{r.receipt.Logs} }
 func (r *Receipt) GetTxHash() *Hash             { return &Hash{r.receipt.TxHash} }
 func (r *Receipt) GetContractAddress() *Address { return &Address{r.receipt.ContractAddress} }
 func (r *Receipt) GetGasUsed() int64            { return int64(r.receipt.GasUsed) }
-
-// Info represents a diagnostic information about the whisper node.
-type Info struct {
-	info *whisper.Info
-}
-
-// NewMessage represents a new whisper message that is posted through the RPC.
-type NewMessage struct {
-	newMessage *whisper.NewMessage
-}
-
-func NewNewMessage() *NewMessage {
-	nm := &NewMessage{
-		newMessage: new(whisper.NewMessage),
-	}
-	return nm
-}
-
-func (nm *NewMessage) GetSymKeyID() string         { return nm.newMessage.SymKeyID }
-func (nm *NewMessage) SetSymKeyID(symKeyID string) { nm.newMessage.SymKeyID = symKeyID }
-func (nm *NewMessage) GetPublicKey() []byte        { return nm.newMessage.PublicKey }
-func (nm *NewMessage) SetPublicKey(publicKey []byte) {
-	nm.newMessage.PublicKey = common.CopyBytes(publicKey)
-}
-func (nm *NewMessage) GetSig() string                  { return nm.newMessage.Sig }
-func (nm *NewMessage) SetSig(sig string)               { nm.newMessage.Sig = sig }
-func (nm *NewMessage) GetTTL() int64                   { return int64(nm.newMessage.TTL) }
-func (nm *NewMessage) SetTTL(ttl int64)                { nm.newMessage.TTL = uint32(ttl) }
-func (nm *NewMessage) GetPayload() []byte              { return nm.newMessage.Payload }
-func (nm *NewMessage) SetPayload(payload []byte)       { nm.newMessage.Payload = common.CopyBytes(payload) }
-func (nm *NewMessage) GetPowTime() int64               { return int64(nm.newMessage.PowTime) }
-func (nm *NewMessage) SetPowTime(powTime int64)        { nm.newMessage.PowTime = uint32(powTime) }
-func (nm *NewMessage) GetPowTarget() float64           { return nm.newMessage.PowTarget }
-func (nm *NewMessage) SetPowTarget(powTarget float64)  { nm.newMessage.PowTarget = powTarget }
-func (nm *NewMessage) GetTargetPeer() string           { return nm.newMessage.TargetPeer }
-func (nm *NewMessage) SetTargetPeer(targetPeer string) { nm.newMessage.TargetPeer = targetPeer }
-func (nm *NewMessage) GetTopic() []byte                { return nm.newMessage.Topic[:] }
-func (nm *NewMessage) SetTopic(topic []byte)           { nm.newMessage.Topic = whisper.BytesToTopic(topic) }
-
-// Message represents a whisper message.
-type Message struct {
-	message *whisper.Message
-}
-
-func (m *Message) GetSig() []byte      { return m.message.Sig }
-func (m *Message) GetTTL() int64       { return int64(m.message.TTL) }
-func (m *Message) GetTimestamp() int64 { return int64(m.message.Timestamp) }
-func (m *Message) GetPayload() []byte  { return m.message.Payload }
-func (m *Message) GetPoW() float64     { return m.message.PoW }
-func (m *Message) GetHash() []byte     { return m.message.Hash }
-func (m *Message) GetDst() []byte      { return m.message.Dst }
-
-// Messages represents an array of messages.
-type Messages struct {
-	messages []*whisper.Message
-}
-
-// Size returns the number of messages in the slice.
-func (m *Messages) Size() int {
-	return len(m.messages)
-}
-
-// Get returns the message at the given index from the slice.
-func (m *Messages) Get(index int) (message *Message, _ error) {
-	if index < 0 || index >= len(m.messages) {
-		return nil, errors.New("index out of bounds")
-	}
-	return &Message{m.messages[index]}, nil
-}
-
-// Criteria holds various filter options for inbound messages.
-type Criteria struct {
-	criteria *whisper.Criteria
-}
-
-func NewCriteria(topic []byte) *Criteria {
-	c := &Criteria{
-		criteria: new(whisper.Criteria),
-	}
-	encodedTopic := whisper.BytesToTopic(topic)
-	c.criteria.Topics = []whisper.TopicType{encodedTopic}
-	return c
-}
-
-func (c *Criteria) GetSymKeyID() string                 { return c.criteria.SymKeyID }
-func (c *Criteria) SetSymKeyID(symKeyID string)         { c.criteria.SymKeyID = symKeyID }
-func (c *Criteria) GetPrivateKeyID() string             { return c.criteria.PrivateKeyID }
-func (c *Criteria) SetPrivateKeyID(privateKeyID string) { c.criteria.PrivateKeyID = privateKeyID }
-func (c *Criteria) GetSig() []byte                      { return c.criteria.Sig }
-func (c *Criteria) SetSig(sig []byte)                   { c.criteria.Sig = common.CopyBytes(sig) }
-func (c *Criteria) GetMinPow() float64                  { return c.criteria.MinPow }
-func (c *Criteria) SetMinPow(pow float64)               { c.criteria.MinPow = pow }
diff --git a/node/api.go b/node/api.go
index 1a73d1321d..be20b89d95 100644
--- a/node/api.go
+++ b/node/api.go
@@ -23,26 +23,47 @@ import (
 
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/internal/debug"
+	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
-// PrivateAdminAPI is the collection of administrative API methods exposed only
-// over a secure RPC channel.
-type PrivateAdminAPI struct {
-	node *Node // Node interfaced by this API
+// apis returns the collection of built-in RPC APIs.
+func (n *Node) apis() []rpc.API {
+	return []rpc.API{
+		{
+			Namespace: "admin",
+			Version:   "1.0",
+			Service:   &privateAdminAPI{n},
+		}, {
+			Namespace: "admin",
+			Version:   "1.0",
+			Service:   &publicAdminAPI{n},
+			Public:    true,
+		}, {
+			Namespace: "debug",
+			Version:   "1.0",
+			Service:   debug.Handler,
+		}, {
+			Namespace: "web3",
+			Version:   "1.0",
+			Service:   &publicWeb3API{n},
+			Public:    true,
+		},
+	}
 }
 
-// NewPrivateAdminAPI creates a new API definition for the private admin methods
-// of the node itself.
-func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI {
-	return &PrivateAdminAPI{node: node}
+// privateAdminAPI is the collection of administrative API methods exposed only
+// over a secure RPC channel.
+type privateAdminAPI struct {
+	node *Node // Node interfaced by this API
 }
 
 // AddPeer requests connecting to a remote node, and also maintaining the new
 // connection at all times, even reconnecting if it is lost.
-func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
+func (api *privateAdminAPI) AddPeer(url string) (bool, error) {
 	// Make sure the server is running, fail otherwise
 	server := api.node.Server()
 	if server == nil {
@@ -58,7 +79,7 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) {
 }
 
 // RemovePeer disconnects from a remote node if the connection exists
-func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
+func (api *privateAdminAPI) RemovePeer(url string) (bool, error) {
 	// Make sure the server is running, fail otherwise
 	server := api.node.Server()
 	if server == nil {
@@ -74,7 +95,7 @@ func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) {
 }
 
 // AddTrustedPeer allows a remote node to always connect, even if slots are full
-func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
+func (api *privateAdminAPI) AddTrustedPeer(url string) (bool, error) {
 	// Make sure the server is running, fail otherwise
 	server := api.node.Server()
 	if server == nil {
@@ -90,7 +111,7 @@ func (api *PrivateAdminAPI) AddTrustedPeer(url string) (bool, error) {
 
 // RemoveTrustedPeer removes a remote node from the trusted peer set, but it
 // does not disconnect it automatically.
-func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
+func (api *privateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
 	// Make sure the server is running, fail otherwise
 	server := api.node.Server()
 	if server == nil {
@@ -106,7 +127,7 @@ func (api *PrivateAdminAPI) RemoveTrustedPeer(url string) (bool, error) {
 
 // PeerEvents creates an RPC subscription which receives peer events from the
 // node's p2p.Server
-func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
+func (api *privateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) {
 	// Make sure the server is running, fail otherwise
 	server := api.node.Server()
 	if server == nil {
@@ -142,15 +163,12 @@ func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription,
 	return rpcSub, nil
 }
 
-// StartRPC starts the HTTP RPC API server.
-func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
+// StartHTTP starts the HTTP RPC API server.
+func (api *privateAdminAPI) StartHTTP(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
 	api.node.lock.Lock()
 	defer api.node.lock.Unlock()
 
-	if api.node.httpHandler != nil {
-		return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint)
-	}
-
+	// Determine host and port.
 	if host == nil {
 		h := DefaultHTTPHost
 		if api.node.config.HTTPHost != "" {
@@ -162,57 +180,69 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis
 		port = &api.node.config.HTTPPort
 	}
 
-	allowedOrigins := api.node.config.HTTPCors
+	// Determine config.
+	config := httpConfig{
+		CorsAllowedOrigins: api.node.config.HTTPCors,
+		Vhosts:             api.node.config.HTTPVirtualHosts,
+		Modules:            api.node.config.HTTPModules,
+	}
 	if cors != nil {
-		allowedOrigins = nil
+		config.CorsAllowedOrigins = nil
 		for _, origin := range strings.Split(*cors, ",") {
-			allowedOrigins = append(allowedOrigins, strings.TrimSpace(origin))
+			config.CorsAllowedOrigins = append(config.CorsAllowedOrigins, strings.TrimSpace(origin))
 		}
 	}
-
-	allowedVHosts := api.node.config.HTTPVirtualHosts
 	if vhosts != nil {
-		allowedVHosts = nil
+		config.Vhosts = nil
 		for _, vhost := range strings.Split(*host, ",") {
-			allowedVHosts = append(allowedVHosts, strings.TrimSpace(vhost))
+			config.Vhosts = append(config.Vhosts, strings.TrimSpace(vhost))
 		}
 	}
-
-	modules := api.node.httpWhitelist
 	if apis != nil {
-		modules = nil
+		config.Modules = nil
 		for _, m := range strings.Split(*apis, ",") {
-			modules = append(modules, strings.TrimSpace(m))
+			config.Modules = append(config.Modules, strings.TrimSpace(m))
 		}
 	}
 
-	if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil {
+	if err := api.node.http.setListenAddr(*host, *port); err != nil {
+		return false, err
+	}
+	if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil {
+		return false, err
+	}
+	if err := api.node.http.start(); err != nil {
 		return false, err
 	}
 	return true, nil
 }
 
-// StopRPC terminates an already running HTTP RPC API endpoint.
-func (api *PrivateAdminAPI) StopRPC() (bool, error) {
-	api.node.lock.Lock()
-	defer api.node.lock.Unlock()
+// StartRPC starts the HTTP RPC API server.
+// This method is deprecated. Use StartHTTP instead.
+func (api *privateAdminAPI) StartRPC(host *string, port *int, cors *string, apis *string, vhosts *string) (bool, error) {
+	log.Warn("Deprecation warning", "method", "admin.StartRPC", "use-instead", "admin.StartHTTP")
+	return api.StartHTTP(host, port, cors, apis, vhosts)
+}
 
-	if api.node.httpHandler == nil {
-		return false, fmt.Errorf("HTTP RPC not running")
-	}
-	api.node.stopHTTP()
+// StopHTTP shuts down the HTTP server.
+func (api *privateAdminAPI) StopHTTP() (bool, error) {
+	api.node.http.stop()
 	return true, nil
 }
 
+// StopRPC shuts down the HTTP server.
+// This method is deprecated. Use StopHTTP instead.
+func (api *privateAdminAPI) StopRPC() (bool, error) {
+	log.Warn("Deprecation warning", "method", "admin.StopRPC", "use-instead", "admin.StopHTTP")
+	return api.StopHTTP()
+}
+
 // StartWS starts the websocket RPC API server.
-func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
+func (api *privateAdminAPI) StartWS(host *string, port *int, allowedOrigins *string, apis *string) (bool, error) {
 	api.node.lock.Lock()
 	defer api.node.lock.Unlock()
 
-	if api.node.wsHandler != nil {
-		return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint)
-	}
-
+	// Determine host and port.
 	if host == nil {
 		h := DefaultWSHost
 		if api.node.config.WSHost != "" {
@@ -224,55 +254,56 @@ func (api *PrivateAdminAPI) StartWS(host *string, port *int, allowedOrigins *str
 		port = &api.node.config.WSPort
 	}
 
-	origins := api.node.config.WSOrigins
-	if allowedOrigins != nil {
-		origins = nil
-		for _, origin := range strings.Split(*allowedOrigins, ",") {
-			origins = append(origins, strings.TrimSpace(origin))
-		}
+	// Determine config.
+	config := wsConfig{
+		Modules: api.node.config.WSModules,
+		Origins: api.node.config.WSOrigins,
+		// ExposeAll: api.node.config.WSExposeAll,
 	}
-
-	modules := api.node.config.WSModules
 	if apis != nil {
-		modules = nil
+		config.Modules = nil
 		for _, m := range strings.Split(*apis, ",") {
-			modules = append(modules, strings.TrimSpace(m))
+			config.Modules = append(config.Modules, strings.TrimSpace(m))
+		}
+	}
+	if allowedOrigins != nil {
+		config.Origins = nil
+		for _, origin := range strings.Split(*allowedOrigins, ",") {
+			config.Origins = append(config.Origins, strings.TrimSpace(origin))
 		}
 	}
 
-	if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, origins, api.node.config.WSExposeAll); err != nil {
+	// Enable WebSocket on the server.
+	server := api.node.wsServerForPort(*port)
+	if err := server.setListenAddr(*host, *port); err != nil {
+		return false, err
+	}
+	if err := server.enableWS(api.node.rpcAPIs, config); err != nil {
+		return false, err
+	}
+	if err := server.start(); err != nil {
 		return false, err
 	}
+	api.node.http.log.Info("WebSocket endpoint opened", "url", api.node.WSEndpoint())
 	return true, nil
 }
 
-// StopWS terminates an already running websocket RPC API endpoint.
-func (api *PrivateAdminAPI) StopWS() (bool, error) {
-	api.node.lock.Lock()
-	defer api.node.lock.Unlock()
-
-	if api.node.wsHandler == nil {
-		return false, fmt.Errorf("WebSocket RPC not running")
-	}
-	api.node.stopWS()
+// StopWS terminates all WebSocket servers.
+func (api *privateAdminAPI) StopWS() (bool, error) {
+	api.node.http.stopWS()
+	api.node.ws.stop()
 	return true, nil
 }
 
-// PublicAdminAPI is the collection of administrative API methods exposed over
+// publicAdminAPI is the collection of administrative API methods exposed over
 // both secure and unsecure RPC channels.
-type PublicAdminAPI struct {
+type publicAdminAPI struct {
 	node *Node // Node interfaced by this API
 }
 
-// NewPublicAdminAPI creates a new API definition for the public admin methods
-// of the node itself.
-func NewPublicAdminAPI(node *Node) *PublicAdminAPI {
-	return &PublicAdminAPI{node: node}
-}
-
 // Peers retrieves all the information we know about each individual peer at the
 // protocol granularity.
-func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
+func (api *publicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
 	server := api.node.Server()
 	if server == nil {
 		return nil, ErrNodeStopped
@@ -282,7 +313,7 @@ func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) {
 
 // NodeInfo retrieves all the information we know about the host node at the
 // protocol granularity.
-func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
+func (api *publicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
 	server := api.node.Server()
 	if server == nil {
 		return nil, ErrNodeStopped
@@ -291,27 +322,22 @@ func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) {
 }
 
 // Datadir retrieves the current data directory the node is using.
-func (api *PublicAdminAPI) Datadir() string {
+func (api *publicAdminAPI) Datadir() string {
 	return api.node.DataDir()
 }
 
-// PublicWeb3API offers helper utils
-type PublicWeb3API struct {
+// publicWeb3API offers helper utils
+type publicWeb3API struct {
 	stack *Node
 }
 
-// NewPublicWeb3API creates a new Web3Service instance
-func NewPublicWeb3API(stack *Node) *PublicWeb3API {
-	return &PublicWeb3API{stack}
-}
-
 // ClientVersion returns the node name
-func (s *PublicWeb3API) ClientVersion() string {
+func (s *publicWeb3API) ClientVersion() string {
 	return s.stack.Server().Name
 }
 
 // Sha3 applies the ethereum sha3 implementation on the input.
 // It assumes the input is hex encoded.
-func (s *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
+func (s *publicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
 	return crypto.Keccak256(input)
 }
diff --git a/node/api_test.go b/node/api_test.go
new file mode 100644
index 0000000000..9549adf9c2
--- /dev/null
+++ b/node/api_test.go
@@ -0,0 +1,352 @@
+// 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 .
+
+package node
+
+import (
+	"bytes"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/stretchr/testify/assert"
+)
+
+// This test uses the admin_startRPC and admin_startWS APIs,
+// checking whether the HTTP server is started correctly.
+func TestStartRPC(t *testing.T) {
+	type test struct {
+		name string
+		cfg  Config
+		fn   func(*testing.T, *Node, *privateAdminAPI)
+
+		// Checks. These run after the node is configured and all API calls have been made.
+		wantReachable bool // whether the HTTP server should be reachable at all
+		wantHandlers  bool // whether RegisterHandler handlers should be accessible
+		wantRPC       bool // whether JSON-RPC/HTTP should be accessible
+		wantWS        bool // whether JSON-RPC/WS should be accessible
+	}
+
+	tests := []test{
+		{
+			name: "all off",
+			cfg:  Config{},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+			},
+			wantReachable: false,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        false,
+		},
+		{
+			name: "rpc enabled through config",
+			cfg:  Config{HTTPHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+			},
+			wantReachable: true,
+			wantHandlers:  true,
+			wantRPC:       true,
+			wantWS:        false,
+		},
+		{
+			name: "rpc enabled through API",
+			cfg:  Config{},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil)
+				assert.NoError(t, err)
+			},
+			wantReachable: true,
+			wantHandlers:  true,
+			wantRPC:       true,
+			wantWS:        false,
+		},
+		{
+			name: "rpc start again after failure",
+			cfg:  Config{},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				// Listen on a random port.
+				listener, err := net.Listen("tcp", "127.0.0.1:0")
+				if err != nil {
+					t.Fatal("can't listen:", err)
+				}
+				defer listener.Close()
+				port := listener.Addr().(*net.TCPAddr).Port
+
+				// Now try to start RPC on that port. This should fail.
+				_, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil)
+				if err == nil {
+					t.Fatal("StartHTTP should have failed on port", port)
+				}
+
+				// Try again after unblocking the port. It should work this time.
+				listener.Close()
+				_, err = api.StartHTTP(sp("127.0.0.1"), ip(port), nil, nil, nil)
+				assert.NoError(t, err)
+			},
+			wantReachable: true,
+			wantHandlers:  true,
+			wantRPC:       true,
+			wantWS:        false,
+		},
+		{
+			name: "rpc stopped through API",
+			cfg:  Config{HTTPHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StopHTTP()
+				assert.NoError(t, err)
+			},
+			wantReachable: false,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        false,
+		},
+		{
+			name: "rpc stopped twice",
+			cfg:  Config{HTTPHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StopHTTP()
+				assert.NoError(t, err)
+
+				_, err = api.StopHTTP()
+				assert.NoError(t, err)
+			},
+			wantReachable: false,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        false,
+		},
+		{
+			name:          "ws enabled through config",
+			cfg:           Config{WSHost: "127.0.0.1"},
+			wantReachable: true,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        true,
+		},
+		{
+			name: "ws enabled through API",
+			cfg:  Config{},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
+				assert.NoError(t, err)
+			},
+			wantReachable: true,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        true,
+		},
+		{
+			name: "ws stopped through API",
+			cfg:  Config{WSHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StopWS()
+				assert.NoError(t, err)
+			},
+			wantReachable: false,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        false,
+		},
+		{
+			name: "ws stopped twice",
+			cfg:  Config{WSHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StopWS()
+				assert.NoError(t, err)
+
+				_, err = api.StopWS()
+				assert.NoError(t, err)
+			},
+			wantReachable: false,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        false,
+		},
+		{
+			name: "ws enabled after RPC",
+			cfg:  Config{HTTPHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				wsport := n.http.port
+				_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
+				assert.NoError(t, err)
+			},
+			wantReachable: true,
+			wantHandlers:  true,
+			wantRPC:       true,
+			wantWS:        true,
+		},
+		{
+			name: "ws enabled after RPC then stopped",
+			cfg:  Config{HTTPHost: "127.0.0.1"},
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				wsport := n.http.port
+				_, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
+				assert.NoError(t, err)
+
+				_, err = api.StopWS()
+				assert.NoError(t, err)
+			},
+			wantReachable: true,
+			wantHandlers:  true,
+			wantRPC:       true,
+			wantWS:        false,
+		},
+		{
+			name: "rpc stopped with ws enabled",
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StartHTTP(sp("127.0.0.1"), ip(0), nil, nil, nil)
+				assert.NoError(t, err)
+
+				wsport := n.http.port
+				_, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil)
+				assert.NoError(t, err)
+
+				_, err = api.StopHTTP()
+				assert.NoError(t, err)
+			},
+			wantReachable: false,
+			wantHandlers:  false,
+			wantRPC:       false,
+			wantWS:        false,
+		},
+		{
+			name: "rpc enabled after ws",
+			fn: func(t *testing.T, n *Node, api *privateAdminAPI) {
+				_, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil)
+				assert.NoError(t, err)
+
+				wsport := n.http.port
+				_, err = api.StartHTTP(sp("127.0.0.1"), ip(wsport), nil, nil, nil)
+				assert.NoError(t, err)
+			},
+			wantReachable: true,
+			wantHandlers:  true,
+			wantRPC:       true,
+			wantWS:        true,
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+
+			// Apply some sane defaults.
+			config := test.cfg
+			// config.Logger = testlog.Logger(t, log.LvlDebug)
+			config.P2P.NoDiscovery = true
+
+			// Create Node.
+			stack, err := New(&config)
+			if err != nil {
+				t.Fatal("can't create node:", err)
+			}
+			defer stack.Close()
+
+			// Register the test handler.
+			stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.Write([]byte("OK"))
+			}))
+
+			if err := stack.Start(); err != nil {
+				t.Fatal("can't start node:", err)
+			}
+
+			// Run the API call hook.
+			if test.fn != nil {
+				test.fn(t, stack, &privateAdminAPI{stack})
+			}
+
+			// Check if the HTTP endpoints are available.
+			baseURL := stack.HTTPEndpoint()
+			reachable := checkReachable(baseURL)
+			handlersAvailable := checkBodyOK(baseURL + "/test")
+			rpcAvailable := checkRPC(baseURL)
+			wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1))
+			if reachable != test.wantReachable {
+				t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable))
+			}
+			if handlersAvailable != test.wantHandlers {
+				t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers))
+			}
+			if rpcAvailable != test.wantRPC {
+				t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC))
+			}
+			if wsAvailable != test.wantWS {
+				t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS))
+			}
+		})
+	}
+}
+
+// checkReachable checks if the TCP endpoint in rawurl is open.
+func checkReachable(rawurl string) bool {
+	u, err := url.Parse(rawurl)
+	if err != nil {
+		panic(err)
+	}
+	conn, err := net.Dial("tcp", u.Host)
+	if err != nil {
+		return false
+	}
+	conn.Close()
+	return true
+}
+
+// checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK".
+func checkBodyOK(url string) bool {
+	resp, err := http.Get(url)
+	if err != nil {
+		return false
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != 200 {
+		return false
+	}
+	buf := make([]byte, 2)
+	if _, err = io.ReadFull(resp.Body, buf); err != nil {
+		return false
+	}
+	return bytes.Equal(buf, []byte("OK"))
+}
+
+// checkRPC checks whether JSON-RPC works against the given URL.
+func checkRPC(url string) bool {
+	c, err := rpc.Dial(url)
+	if err != nil {
+		return false
+	}
+	defer c.Close()
+
+	_, err = c.SupportedModules()
+	return err == nil
+}
+
+// string/int pointer helpers.
+func sp(s string) *string { return &s }
+func ip(i int) *int       { return &i }
+
+func not(ok bool) string {
+	if ok {
+		return ""
+	}
+	return "not "
+}
diff --git a/node/config.go b/node/config.go
index 730757e56d..23461c1916 100644
--- a/node/config.go
+++ b/node/config.go
@@ -83,7 +83,7 @@ type Config struct {
 	KeyStoreDir string `toml:",omitempty"`
 
 	// ExternalSigner specifies an external URI for a clef-type signer
-	ExternalSigner string `toml:"omitempty"`
+	ExternalSigner string `toml:",omitempty"`
 
 	// UseLightweightKDF lowers the memory and CPU requirements of the key store
 	// scrypt KDF at the expense of security.
@@ -101,6 +101,9 @@ type Config struct {
 	// RangeLimit enable 5000 blocks limit when handle range query
 	RangeLimit bool `toml:",omitempty"`
 
+	// USB enables hardware wallet monitoring and connectivity.
+	USB bool `toml:",omitempty"`
+
 	// SmartCardDaemonPath is the path to the smartcard daemon's socket
 	SmartCardDaemonPath string `toml:",omitempty"`
 
@@ -108,11 +111,11 @@ type Config struct {
 	// a simple file name, it is placed inside the data directory (or on the root
 	// pipe path on Windows), whereas if it's a resolvable path name (absolute or
 	// relative), then that specific path is enforced. An empty path disables IPC.
-	IPCPath string `toml:",omitempty"`
+	IPCPath string
 
 	// HTTPHost is the host interface on which to start the HTTP RPC server. If this
 	// field is empty, no HTTP API endpoint will be started.
-	HTTPHost string `toml:",omitempty"`
+	HTTPHost string
 
 	// HTTPPort is the TCP port number on which to start the HTTP RPC server. The
 	// default zero value is/ valid and will pick a port number randomly (useful
@@ -136,21 +139,27 @@ type Config struct {
 	// HTTPModules is a list of API modules to expose via the HTTP RPC interface.
 	// If the module list is empty, all RPC API endpoints designated public will be
 	// exposed.
-	HTTPModules []string `toml:",omitempty"`
+	HTTPModules []string
 
 	// HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC
 	// interface.
 	HTTPTimeouts rpc.HTTPTimeouts
 
+	// HTTPPathPrefix specifies a path prefix on which http-rpc is to be served.
+	HTTPPathPrefix string `toml:",omitempty"`
+
 	// WSHost is the host interface on which to start the websocket RPC server. If
 	// this field is empty, no websocket API endpoint will be started.
-	WSHost string `toml:",omitempty"`
+	WSHost string
 
 	// WSPort is the TCP port number on which to start the websocket RPC server. The
 	// default zero value is/ valid and will pick a port number randomly (useful for
 	// ephemeral nodes).
 	WSPort int `toml:",omitempty"`
 
+	// WSPathPrefix specifies a path prefix on which ws-rpc is to be served.
+	WSPathPrefix string `toml:",omitempty"`
+
 	// WSOrigins is the list of domain to accept websocket requests from. Please be
 	// aware that the server can only act upon the HTTP request the client sends and
 	// cannot verify the validity of the request header.
@@ -159,7 +168,7 @@ type Config struct {
 	// WSModules is a list of API modules to expose via the websocket RPC interface.
 	// If the module list is empty, all RPC API endpoints designated public will be
 	// exposed.
-	WSModules []string `toml:",omitempty"`
+	WSModules []string
 
 	// WSExposeAll exposes all API modules via the WebSocket RPC interface rather
 	// than just the public ones.
@@ -168,15 +177,6 @@ type Config struct {
 	// private APIs to untrusted users is a major security risk.
 	WSExposeAll bool `toml:",omitempty"`
 
-	// GraphQLHost is the host interface on which to start the GraphQL server. If this
-	// field is empty, no GraphQL API endpoint will be started.
-	GraphQLHost string `toml:",omitempty"`
-
-	// GraphQLPort is the TCP port number on which to start the GraphQL server. The
-	// default zero value is/ valid and will pick a port number randomly (useful
-	// for ephemeral nodes).
-	GraphQLPort int `toml:",omitempty"`
-
 	// GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting
 	// clients. Please be aware that CORS is a browser enforced security, it's fully
 	// useless for custom HTTP clients.
@@ -199,6 +199,9 @@ type Config struct {
 	staticNodesWarning     bool
 	trustedNodesWarning    bool
 	oldGethResourceWarning bool
+
+	// AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
+	AllowUnprotectedTxs bool `toml:",omitempty"`
 }
 
 // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
@@ -255,15 +258,6 @@ func (c *Config) HTTPEndpoint() string {
 	return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort)
 }
 
-// GraphQLEndpoint resolves a GraphQL endpoint based on the configured host interface
-// and port parameters.
-func (c *Config) GraphQLEndpoint() string {
-	if c.GraphQLHost == "" {
-		return ""
-	}
-	return fmt.Sprintf("%s:%d", c.GraphQLHost, c.GraphQLPort)
-}
-
 // DefaultHTTPEndpoint returns the HTTP endpoint used by default.
 func DefaultHTTPEndpoint() string {
 	config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort}
@@ -288,7 +282,7 @@ func DefaultWSEndpoint() string {
 // ExtRPCEnabled returns the indicator whether node enables the external
 // RPC(http, ws or graphql).
 func (c *Config) ExtRPCEnabled() bool {
-	return c.HTTPHost != "" || c.WSHost != "" || c.GraphQLHost != ""
+	return c.HTTPHost != "" || c.WSHost != ""
 }
 
 // NodeName returns the devp2p node identifier.
@@ -502,7 +496,7 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
 		// we can have both, but it's very confusing for the user to see the same
 		// accounts in both externally and locally, plus very racey.
 		backends = append(backends, keystore.NewKeyStore(keydir, scryptN, scryptP))
-		if !conf.NoUSB {
+		if conf.USB {
 			// Start a USB hub for Ledger hardware wallets
 			if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
 				log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
diff --git a/node/defaults.go b/node/defaults.go
index f84a5d5479..c685dde5d1 100644
--- a/node/defaults.go
+++ b/node/defaults.go
@@ -45,7 +45,6 @@ var DefaultConfig = Config{
 	HTTPTimeouts:        rpc.DefaultHTTPTimeouts,
 	WSPort:              DefaultWSPort,
 	WSModules:           []string{"net", "web3"},
-	GraphQLPort:         DefaultGraphQLPort,
 	GraphQLVirtualHosts: []string{"localhost"},
 	P2P: p2p.Config{
 		ListenAddr: ":30303",
diff --git a/node/doc.go b/node/doc.go
index e3cc58e5f4..b257f412fe 100644
--- a/node/doc.go
+++ b/node/doc.go
@@ -22,6 +22,43 @@ resources to provide RPC APIs. Services can also offer devp2p protocols, which a
 up to the devp2p network when the node instance is started.
 
 
+Node Lifecycle
+
+The Node object has a lifecycle consisting of three basic states, INITIALIZING, RUNNING
+and CLOSED.
+
+
+    ●───────┐
+         New()
+            │
+            ▼
+      INITIALIZING ────Start()─┐
+            │                  │
+            │                  ▼
+        Close()             RUNNING
+            │                  │
+            ▼                  │
+         CLOSED ◀──────Close()─┘
+
+
+Creating a Node allocates basic resources such as the data directory and returns the node
+in its INITIALIZING state. Lifecycle objects, RPC APIs and peer-to-peer networking
+protocols can be registered in this state. Basic operations such as opening a key-value
+database are permitted while initializing.
+
+Once everything is registered, the node can be started, which moves it into the RUNNING
+state. Starting the node starts all registered Lifecycle objects and enables RPC and
+peer-to-peer networking. Note that no additional Lifecycles, APIs or p2p protocols can be
+registered while the node is running.
+
+Closing the node releases all held resources. The actions performed by Close depend on the
+state it was in. When closing a node in INITIALIZING state, resources related to the data
+directory are released. If the node was RUNNING, closing it also stops all Lifecycle
+objects and shuts down RPC and peer-to-peer networking.
+
+You must always call Close on Node, even if the node was not started.
+
+
 Resources Managed By Node
 
 All file-system resources used by a node instance are located in a directory called the
diff --git a/node/endpoints.go b/node/endpoints.go
index 8cd6b4d1c8..1f85a52131 100644
--- a/node/endpoints.go
+++ b/node/endpoints.go
@@ -26,14 +26,14 @@ import (
 )
 
 // StartHTTPEndpoint starts the HTTP RPC endpoint.
-func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) {
+func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (*http.Server, net.Addr, error) {
 	// start the HTTP listener
 	var (
 		listener net.Listener
 		err      error
 	)
 	if listener, err = net.Listen("tcp", endpoint); err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	// make sure timeout values are meaningful
 	CheckTimeouts(&timeouts)
@@ -45,22 +45,7 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.
 		IdleTimeout:  timeouts.IdleTimeout,
 	}
 	go httpSrv.Serve(listener)
-	return listener, err
-}
-
-// startWSEndpoint starts a websocket endpoint.
-func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) {
-	// start the HTTP listener
-	var (
-		listener net.Listener
-		err      error
-	)
-	if listener, err = net.Listen("tcp", endpoint); err != nil {
-		return nil, err
-	}
-	wsSrv := &http.Server{Handler: handler}
-	go wsSrv.Serve(listener)
-	return listener, err
+	return httpSrv, listener.Addr(), err
 }
 
 // checkModuleAvailability checks that all names given in modules are actually
diff --git a/node/errors.go b/node/errors.go
index 2e0dadc4d6..67547bf691 100644
--- a/node/errors.go
+++ b/node/errors.go
@@ -39,17 +39,6 @@ func convertFileLockError(err error) error {
 	return err
 }
 
-// DuplicateServiceError is returned during Node startup if a registered service
-// constructor returns a service of the same type that was already started.
-type DuplicateServiceError struct {
-	Kind reflect.Type
-}
-
-// Error generates a textual representation of the duplicate service error.
-func (e *DuplicateServiceError) Error() string {
-	return fmt.Sprintf("duplicate service: %v", e.Kind)
-}
-
 // StopError is returned if a Node fails to stop either any of its registered
 // services or itself.
 type StopError struct {
diff --git a/node/lifecycle.go b/node/lifecycle.go
new file mode 100644
index 0000000000..0d5f9a0680
--- /dev/null
+++ b/node/lifecycle.go
@@ -0,0 +1,31 @@
+// 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 .
+
+package node
+
+// Lifecycle encompasses the behavior of services that can be started and stopped
+// on the node. Lifecycle management is delegated to the node, but it is the
+// responsibility of the service-specific package to configure and register the
+// service on the node using the `RegisterLifecycle` method.
+type Lifecycle interface {
+	// Start is called after all services have been constructed and the networking
+	// layer was also initialized to spawn any goroutines required by the service.
+	Start() error
+
+	// Stop terminates all goroutines belonging to the service, blocking until they
+	// are all terminated.
+	Stop() error
+}
diff --git a/node/node.go b/node/node.go
index 26515f49c5..f2602dee47 100644
--- a/node/node.go
+++ b/node/node.go
@@ -19,7 +19,7 @@ package node
 import (
 	"errors"
 	"fmt"
-	"net"
+	"net/http"
 	"os"
 	"path"
 	"path/filepath"
@@ -31,7 +31,6 @@ import (
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/event"
-	"github.com/ethereum/go-ethereum/internal/debug"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/rpc"
@@ -40,41 +39,34 @@ import (
 
 // Node is a container on which services can be registered.
 type Node struct {
-	eventmux *event.TypeMux // Event multiplexer used between the services of a stack
-	config   *Config
-	accman   *accounts.Manager
-
-	ephemeralKeystore string            // if non-empty, the key directory that will be removed by Stop
-	instanceDirLock   fileutil.Releaser // prevents concurrent use of instance directory
-
-	serverConfig p2p.Config
-	server       *p2p.Server // Currently running P2P networking layer
-
-	serviceFuncs []ServiceConstructor     // Service constructors (in dependency order)
-	services     map[reflect.Type]Service // Currently running services
-
+	eventmux      *event.TypeMux
+	config        *Config
+	accman        *accounts.Manager
+	log           log.Logger
+	ephemKeystore string            // if non-empty, the key directory that will be removed by Stop
+	dirLock       fileutil.Releaser // prevents concurrent use of instance directory
+	stop          chan struct{}     // Channel to wait for termination notifications
+	server        *p2p.Server       // Currently running P2P networking layer
+	startStopLock sync.Mutex        // Start/Stop are protected by an additional lock
+	state         int               // Tracks state of node lifecycle
+
+	lock          sync.Mutex
+	lifecycles    []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
 	rpcAPIs       []rpc.API   // List of APIs currently provided by the node
+	http          *httpServer //
+	ws            *httpServer //
+	ipc           *ipcServer  // Stores information about the ipc http server
 	inprocHandler *rpc.Server // In-process RPC request handler to process the API requests
 
-	ipcEndpoint string       // IPC endpoint to listen at (empty = IPC disabled)
-	ipcListener net.Listener // IPC RPC listener socket to serve API requests
-	ipcHandler  *rpc.Server  // IPC RPC request handler to process the API requests
-
-	httpEndpoint  string       // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled)
-	httpWhitelist []string     // HTTP RPC modules to allow through this endpoint
-	httpListener  net.Listener // HTTP RPC listener socket to server API requests
-	httpHandler   *rpc.Server  // HTTP RPC request handler to process the API requests
-
-	wsEndpoint string       // Websocket endpoint (interface + port) to listen at (empty = websocket disabled)
-	wsListener net.Listener // Websocket RPC listener socket to server API requests
-	wsHandler  *rpc.Server  // Websocket RPC request handler to process the API requests
-
-	stop chan struct{} // Channel to wait for termination notifications
-	lock sync.RWMutex
-
-	log log.Logger
+	databases map[*closeTrackingDB]struct{} // All open databases
 }
 
+const (
+	initializingState = iota
+	runningState
+	closedState
+)
+
 // New creates a new P2P node, ready for protocol registration.
 func New(conf *Config) (*Node, error) {
 	// Copy config and resolve the datadir so future changes to the current
@@ -88,6 +80,19 @@ func New(conf *Config) (*Node, error) {
 		}
 		conf.DataDir = absdatadir
 	}
+	if conf.LogConfig != nil {
+		logFilePath := ""
+		if conf.LogConfig.FileRoot == "" {
+			logFilePath = path.Join(conf.DataDir, conf.LogConfig.FilePath)
+		} else {
+			logFilePath = path.Join(conf.LogConfig.FileRoot, conf.LogConfig.FilePath)
+		}
+		log.Root().SetHandler(log.NewFileLvlHandler(logFilePath, conf.LogConfig.MaxBytesSize, conf.LogConfig.Level))
+	}
+	if conf.Logger == nil {
+		conf.Logger = log.New()
+	}
+
 	// Ensure that the instance name doesn't cause weird conflicts with
 	// other files in the data directory.
 	if strings.ContainsAny(conf.Name, `/\`) {
@@ -99,52 +104,158 @@ func New(conf *Config) (*Node, error) {
 	if strings.HasSuffix(conf.Name, ".ipc") {
 		return nil, errors.New(`Config.Name cannot end in ".ipc"`)
 	}
-	// Ensure that the AccountManager method works before the node has started.
-	// We rely on this in cmd/geth.
+
+	node := &Node{
+		config:        conf,
+		inprocHandler: rpc.NewServer(),
+		eventmux:      new(event.TypeMux),
+		log:           conf.Logger,
+		stop:          make(chan struct{}),
+		server:        &p2p.Server{Config: conf.P2P},
+		databases:     make(map[*closeTrackingDB]struct{}),
+	}
+
+	// Register built-in APIs.
+	node.rpcAPIs = append(node.rpcAPIs, node.apis()...)
+
+	// Acquire the instance directory lock.
+	if err := node.openDataDir(); err != nil {
+		return nil, err
+	}
+	// Ensure that the AccountManager method works before the node has started. We rely on
+	// this in cmd/geth.
 	am, ephemeralKeystore, err := makeAccountManager(conf)
 	if err != nil {
 		return nil, err
 	}
-	if conf.LogConfig != nil {
-		logFilePath := ""
-		if conf.LogConfig.FileRoot == "" {
-			logFilePath = path.Join(conf.DataDir, conf.LogConfig.FilePath)
-		} else {
-			logFilePath = path.Join(conf.LogConfig.FileRoot, conf.LogConfig.FilePath)
+	node.accman = am
+	node.ephemKeystore = ephemeralKeystore
+
+	// Initialize the p2p server. This creates the node key and discovery databases.
+	node.server.Config.PrivateKey = node.config.NodeKey()
+	node.server.Config.Name = node.config.NodeName()
+	node.server.Config.Logger = node.log
+	if node.server.Config.StaticNodes == nil {
+		node.server.Config.StaticNodes = node.config.StaticNodes()
+	}
+	if node.server.Config.TrustedNodes == nil {
+		node.server.Config.TrustedNodes = node.config.TrustedNodes()
+	}
+	if node.server.Config.NodeDatabase == "" {
+		node.server.Config.NodeDatabase = node.config.NodeDB()
+	}
+
+	// Check HTTP/WS prefixes are valid.
+	if err := validatePrefix("HTTP", conf.HTTPPathPrefix); err != nil {
+		return nil, err
+	}
+	if err := validatePrefix("WebSocket", conf.WSPathPrefix); err != nil {
+		return nil, err
+	}
+
+	// Configure RPC servers.
+	node.http = newHTTPServer(node.log, conf.HTTPTimeouts)
+	node.ws = newHTTPServer(node.log, rpc.DefaultHTTPTimeouts)
+	node.ipc = newIPCServer(node.log, conf.IPCEndpoint())
+
+	return node, nil
+}
+
+// Start starts all registered lifecycles, RPC services and p2p networking.
+// Node can only be started once.
+func (n *Node) Start() error {
+	n.startStopLock.Lock()
+	defer n.startStopLock.Unlock()
+
+	n.lock.Lock()
+	switch n.state {
+	case runningState:
+		n.lock.Unlock()
+		return ErrNodeRunning
+	case closedState:
+		n.lock.Unlock()
+		return ErrNodeStopped
+	}
+	n.state = runningState
+	// open networking and RPC endpoints
+	err := n.openEndpoints()
+	lifecycles := make([]Lifecycle, len(n.lifecycles))
+	copy(lifecycles, n.lifecycles)
+	n.lock.Unlock()
+
+	// Check if endpoint startup failed.
+	if err != nil {
+		n.doClose(nil)
+		return err
+	}
+	// Start all registered lifecycles.
+	var started []Lifecycle
+	for _, lifecycle := range lifecycles {
+		if err = lifecycle.Start(); err != nil {
+			break
 		}
-		log.Root().SetHandler(log.NewFileLvlHandler(logFilePath, conf.LogConfig.MaxBytesSize, conf.LogConfig.Level))
+		started = append(started, lifecycle)
 	}
-	if conf.Logger == nil {
-		conf.Logger = log.New()
+	// Check if any lifecycle failed to start.
+	if err != nil {
+		n.stopServices(started)
+		n.doClose(nil)
 	}
-	// Note: any interaction with Config that would create/touch files
-	// in the data directory or instance directory is delayed until Start.
-	return &Node{
-		accman:            am,
-		ephemeralKeystore: ephemeralKeystore,
-		config:            conf,
-		serviceFuncs:      []ServiceConstructor{},
-		ipcEndpoint:       conf.IPCEndpoint(),
-		httpEndpoint:      conf.HTTPEndpoint(),
-		wsEndpoint:        conf.WSEndpoint(),
-		eventmux:          new(event.TypeMux),
-		log:               conf.Logger,
-	}, nil
+	return err
 }
 
 // Close stops the Node and releases resources acquired in
 // Node constructor New.
 func (n *Node) Close() error {
-	var errs []error
+	n.startStopLock.Lock()
+	defer n.startStopLock.Unlock()
 
-	// Terminate all subsystems and collect any errors
-	if err := n.Stop(); err != nil && err != ErrNodeStopped {
-		errs = append(errs, err)
+	n.lock.Lock()
+	state := n.state
+	n.lock.Unlock()
+	switch state {
+	case initializingState:
+		// The node was never started.
+		return n.doClose(nil)
+	case runningState:
+		// The node was started, release resources acquired by Start().
+		var errs []error
+		if err := n.stopServices(n.lifecycles); err != nil {
+			errs = append(errs, err)
+		}
+		return n.doClose(errs)
+	case closedState:
+		return ErrNodeStopped
+	default:
+		panic(fmt.Sprintf("node is in unknown state %d", state))
 	}
+}
+
+// doClose releases resources acquired by New(), collecting errors.
+func (n *Node) doClose(errs []error) error {
+	// Close databases. This needs the lock because it needs to
+	// synchronize with OpenDatabase*.
+	n.lock.Lock()
+	n.state = closedState
+	errs = append(errs, n.closeDatabases()...)
+	n.lock.Unlock()
+
 	if err := n.accman.Close(); err != nil {
 		errs = append(errs, err)
 	}
-	// Report any errors that might have occurred
+	if n.ephemKeystore != "" {
+		if err := os.RemoveAll(n.ephemKeystore); err != nil {
+			errs = append(errs, err)
+		}
+	}
+
+	// Release instance directory lock.
+	n.closeDataDir()
+
+	// Unblock n.Wait.
+	close(n.stop)
+
+	// Report any errors that might have occurred.
 	switch len(errs) {
 	case 0:
 		return nil
@@ -155,116 +266,54 @@ func (n *Node) Close() error {
 	}
 }
 
-// Register injects a new service into the node's stack. The service created by
-// the passed constructor must be unique in its type with regard to sibling ones.
-func (n *Node) Register(constructor ServiceConstructor) error {
-	n.lock.Lock()
-	defer n.lock.Unlock()
-
-	if n.server != nil {
-		return ErrNodeRunning
-	}
-	n.serviceFuncs = append(n.serviceFuncs, constructor)
-	return nil
-}
-
-// Start creates a live P2P node and starts running it.
-func (n *Node) Start() error {
-	n.lock.Lock()
-	defer n.lock.Unlock()
-
-	// Short circuit if the node's already running
-	if n.server != nil {
-		return ErrNodeRunning
+// openEndpoints starts all network and RPC endpoints.
+func (n *Node) openEndpoints() error {
+	// start networking endpoints
+	n.log.Info("Starting peer-to-peer node", "instance", n.server.Name)
+	if err := n.server.Start(); err != nil {
+		return convertFileLockError(err)
 	}
-	if err := n.openDataDir(); err != nil {
-		return err
+	// start RPC endpoints
+	err := n.startRPC()
+	if err != nil {
+		n.stopRPC()
+		n.server.Stop()
 	}
+	return err
+}
 
-	// Initialize the p2p server. This creates the node key and
-	// discovery databases.
-	n.serverConfig = n.config.P2P
-	n.serverConfig.PrivateKey = n.config.NodeKey()
-	n.serverConfig.Name = n.config.NodeName()
-	n.serverConfig.Logger = n.log
-	if n.serverConfig.StaticNodes == nil {
-		n.serverConfig.StaticNodes = n.config.StaticNodes()
-	}
-	if n.serverConfig.TrustedNodes == nil {
-		n.serverConfig.TrustedNodes = n.config.TrustedNodes()
-	}
-	if n.serverConfig.NodeDatabase == "" {
-		n.serverConfig.NodeDatabase = n.config.NodeDB()
-	}
-	running := &p2p.Server{Config: n.serverConfig}
-	n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)
-
-	// Otherwise copy and specialize the P2P configuration
-	services := make(map[reflect.Type]Service)
-	for _, constructor := range n.serviceFuncs {
-		// Create a new context for the particular service
-		ctx := &ServiceContext{
-			Config:         *n.config,
-			services:       make(map[reflect.Type]Service),
-			EventMux:       n.eventmux,
-			AccountManager: n.accman,
-		}
-		for kind, s := range services { // copy needed for threaded access
-			ctx.services[kind] = s
-		}
-		// Construct and save the service
-		service, err := constructor(ctx)
-		if err != nil {
-			return err
+// containsLifecycle checks if 'lfs' contains 'l'.
+func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool {
+	for _, obj := range lfs {
+		if obj == l {
+			return true
 		}
-		kind := reflect.TypeOf(service)
-		if _, exists := services[kind]; exists {
-			return &DuplicateServiceError{Kind: kind}
-		}
-		services[kind] = service
-	}
-	// Gather the protocols and start the freshly assembled P2P server
-	for _, service := range services {
-		running.Protocols = append(running.Protocols, service.Protocols()...)
-	}
-	if err := running.Start(); err != nil {
-		return convertFileLockError(err)
 	}
-	// Start each of the services
-	var started []reflect.Type
-	for kind, service := range services {
-		// Start the next service, stopping all previous upon failure
-		if err := service.Start(running); err != nil {
-			for _, kind := range started {
-				services[kind].Stop()
-			}
-			running.Stop()
+	return false
+}
 
-			return err
+// stopServices terminates running services, RPC and p2p networking.
+// It is the inverse of Start.
+func (n *Node) stopServices(running []Lifecycle) error {
+	n.stopRPC()
+
+	// Stop running lifecycles in reverse order.
+	failure := &StopError{Services: make(map[reflect.Type]error)}
+	for i := len(running) - 1; i >= 0; i-- {
+		if err := running[i].Stop(); err != nil {
+			failure.Services[reflect.TypeOf(running[i])] = err
 		}
-		// Mark the service started for potential cleanup
-		started = append(started, kind)
 	}
-	// Lastly, start the configured RPC interfaces
-	if err := n.startRPC(services); err != nil {
-		for _, service := range services {
-			service.Stop()
-		}
-		running.Stop()
-		return err
+
+	// Stop p2p networking.
+	n.server.Stop()
+
+	if len(failure.Services) > 0 {
+		return failure
 	}
-	// Finish initializing the startup
-	n.services = services
-	n.server = running
-	n.stop = make(chan struct{})
 	return nil
 }
 
-// Config returns the configuration of node.
-func (n *Node) Config() *Config {
-	return n.config
-}
-
 func (n *Node) openDataDir() error {
 	if n.config.DataDir == "" {
 		return nil // ephemeral
@@ -280,317 +329,190 @@ func (n *Node) openDataDir() error {
 	if err != nil {
 		return convertFileLockError(err)
 	}
-	n.instanceDirLock = release
+	n.dirLock = release
 	return nil
 }
 
-// startRPC is a helper method to start all the various RPC endpoints during node
+func (n *Node) closeDataDir() {
+	// Release instance directory lock.
+	if n.dirLock != nil {
+		if err := n.dirLock.Release(); err != nil {
+			n.log.Error("Can't release datadir lock", "err", err)
+		}
+		n.dirLock = nil
+	}
+}
+
+// configureRPC is a helper method to configure all the various RPC endpoints during node
 // startup. It's not meant to be called at any time afterwards as it makes certain
 // assumptions about the state of the node.
-func (n *Node) startRPC(services map[reflect.Type]Service) error {
-	// Gather all the possible APIs to surface
-	apis := n.apis()
-	for _, service := range services {
-		apis = append(apis, service.APIs()...)
-	}
-	// Start the various API endpoints, terminating all in case of errors
-	if err := n.startInProc(apis); err != nil {
-		return err
-	}
-	if err := n.startIPC(apis); err != nil {
-		n.stopInProc()
-		return err
-	}
-	if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil {
-		n.stopIPC()
-		n.stopInProc()
+func (n *Node) startRPC() error {
+	if err := n.startInProc(); err != nil {
 		return err
 	}
-	// if endpoints are not the same, start separate servers
-	if n.httpEndpoint != n.wsEndpoint {
-		if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil {
-			n.stopHTTP()
-			n.stopIPC()
-			n.stopInProc()
+
+	// Configure IPC.
+	if n.ipc.endpoint != "" {
+		if err := n.ipc.start(n.rpcAPIs); err != nil {
 			return err
 		}
 	}
 
-	// All API endpoints started successfully
-	n.rpcAPIs = apis
-	return nil
-}
-
-// startInProc initializes an in-process RPC endpoint.
-func (n *Node) startInProc(apis []rpc.API) error {
-	// Register all the APIs exposed by the services
-	handler := rpc.NewServer()
-	for _, api := range apis {
-		if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
+	// Configure HTTP.
+	if n.config.HTTPHost != "" {
+		config := httpConfig{
+			CorsAllowedOrigins: n.config.HTTPCors,
+			Vhosts:             n.config.HTTPVirtualHosts,
+			Modules:            n.config.HTTPModules,
+			prefix:             n.config.HTTPPathPrefix,
+		}
+		if err := n.http.setListenAddr(n.config.HTTPHost, n.config.HTTPPort); err != nil {
+			return err
+		}
+		if err := n.http.enableRPC(n.rpcAPIs, config); err != nil {
 			return err
 		}
-		n.log.Debug("InProc registered", "namespace", api.Namespace)
 	}
-	n.inprocHandler = handler
-	return nil
-}
 
-// stopInProc terminates the in-process RPC endpoint.
-func (n *Node) stopInProc() {
-	if n.inprocHandler != nil {
-		n.inprocHandler.Stop()
-		n.inprocHandler = nil
+	// Configure WebSocket.
+	if n.config.WSHost != "" {
+		server := n.wsServerForPort(n.config.WSPort)
+		config := wsConfig{
+			Modules: n.config.WSModules,
+			Origins: n.config.WSOrigins,
+			prefix:  n.config.WSPathPrefix,
+		}
+		if err := server.setListenAddr(n.config.WSHost, n.config.WSPort); err != nil {
+			return err
+		}
+		if err := server.enableWS(n.rpcAPIs, config); err != nil {
+			return err
+		}
 	}
-}
 
-// startIPC initializes and starts the IPC RPC endpoint.
-func (n *Node) startIPC(apis []rpc.API) error {
-	if n.ipcEndpoint == "" {
-		return nil // IPC disabled.
-	}
-	listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis)
-	if err != nil {
+	if err := n.http.start(); err != nil {
 		return err
 	}
-	n.ipcListener = listener
-	n.ipcHandler = handler
-	n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint)
-	return nil
+	return n.ws.start()
 }
 
-// stopIPC terminates the IPC RPC endpoint.
-func (n *Node) stopIPC() {
-	if n.ipcListener != nil {
-		n.ipcListener.Close()
-		n.ipcListener = nil
-
-		n.log.Info("IPC endpoint closed", "url", n.ipcEndpoint)
-	}
-	if n.ipcHandler != nil {
-		n.ipcHandler.Stop()
-		n.ipcHandler = nil
+func (n *Node) wsServerForPort(port int) *httpServer {
+	if n.config.HTTPHost == "" || n.http.port == port {
+		return n.http
 	}
+	return n.ws
 }
 
-// startHTTP initializes and starts the HTTP RPC endpoint.
-func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error {
-	// Short circuit if the HTTP endpoint isn't being exposed
-	if endpoint == "" {
-		return nil
-	}
-	// register apis and create handler stack
-	srv := rpc.NewServer()
-	err := RegisterApisFromWhitelist(apis, modules, srv, false)
-	if err != nil {
-		return err
-	}
-	handler := NewHTTPHandlerStack(srv, cors, vhosts)
-	// wrap handler in websocket handler only if websocket port is the same as http rpc
-	if n.httpEndpoint == n.wsEndpoint {
-		handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins))
-	}
-	listener, err := StartHTTPEndpoint(endpoint, timeouts, handler)
-	if err != nil {
-		return err
-	}
-	n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()),
-		"cors", strings.Join(cors, ","),
-		"vhosts", strings.Join(vhosts, ","))
-	if n.httpEndpoint == n.wsEndpoint {
-		n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr()))
-	}
-	// All listeners booted successfully
-	n.httpEndpoint = endpoint
-	n.httpListener = listener
-	n.httpHandler = srv
-
-	return nil
+func (n *Node) stopRPC() {
+	n.http.stop()
+	n.ws.stop()
+	n.ipc.stop()
+	n.stopInProc()
 }
 
-// stopHTTP terminates the HTTP RPC endpoint.
-func (n *Node) stopHTTP() {
-	if n.httpListener != nil {
-		url := fmt.Sprintf("http://%v/", n.httpListener.Addr())
-		n.httpListener.Close()
-		n.httpListener = nil
-		n.log.Info("HTTP endpoint closed", "url", url)
-	}
-	if n.httpHandler != nil {
-		n.httpHandler.Stop()
-		n.httpHandler = nil
+// startInProc registers all RPC APIs on the inproc server.
+func (n *Node) startInProc() error {
+	for _, api := range n.rpcAPIs {
+		if err := n.inprocHandler.RegisterName(api.Namespace, api.Service); err != nil {
+			return err
+		}
 	}
+	return nil
 }
 
-// startWS initializes and starts the websocket RPC endpoint.
-func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins []string, exposeAll bool) error {
-	// Short circuit if the WS endpoint isn't being exposed
-	if endpoint == "" {
-		return nil
-	}
-
-	srv := rpc.NewServer()
-	handler := srv.WebsocketHandler(wsOrigins)
-	err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll)
-	if err != nil {
-		return err
-	}
-	listener, err := startWSEndpoint(endpoint, handler)
-	if err != nil {
-		return err
-	}
-	n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr()))
-	// All listeners booted successfully
-	n.wsEndpoint = endpoint
-	n.wsListener = listener
-	n.wsHandler = srv
+// stopInProc terminates the in-process RPC endpoint.
+func (n *Node) stopInProc() {
+	n.inprocHandler.Stop()
+}
 
-	return nil
+// Wait blocks until the node is closed.
+func (n *Node) Wait() {
+	<-n.stop
 }
 
-// stopWS terminates the websocket RPC endpoint.
-func (n *Node) stopWS() {
-	if n.wsListener != nil {
-		n.wsListener.Close()
-		n.wsListener = nil
+// RegisterLifecycle registers the given Lifecycle on the node.
+func (n *Node) RegisterLifecycle(lifecycle Lifecycle) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
 
-		n.log.Info("WebSocket endpoint closed", "url", fmt.Sprintf("ws://%s", n.wsEndpoint))
+	if n.state != initializingState {
+		panic("can't register lifecycle on running/stopped node")
 	}
-	if n.wsHandler != nil {
-		n.wsHandler.Stop()
-		n.wsHandler = nil
+	if containsLifecycle(n.lifecycles, lifecycle) {
+		panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle))
 	}
+	n.lifecycles = append(n.lifecycles, lifecycle)
 }
 
-// Stop terminates a running node along with all it's services. In the node was
-// not started, an error is returned.
-func (n *Node) Stop() error {
+// RegisterProtocols adds backend's protocols to the node's p2p server.
+func (n *Node) RegisterProtocols(protocols []p2p.Protocol) {
 	n.lock.Lock()
 	defer n.lock.Unlock()
 
-	// Short circuit if the node's not running
-	if n.server == nil {
-		return ErrNodeStopped
-	}
-
-	// Terminate the API, services and the p2p server.
-	n.stopWS()
-	n.stopHTTP()
-	n.stopIPC()
-	n.rpcAPIs = nil
-	failure := &StopError{
-		Services: make(map[reflect.Type]error),
-	}
-	for kind, service := range n.services {
-		if err := service.Stop(); err != nil {
-			failure.Services[kind] = err
-		}
-	}
-	n.server.Stop()
-	n.services = nil
-	n.server = nil
-
-	// Release instance directory lock.
-	if n.instanceDirLock != nil {
-		if err := n.instanceDirLock.Release(); err != nil {
-			n.log.Error("Can't release datadir lock", "err", err)
-		}
-		n.instanceDirLock = nil
+	if n.state != initializingState {
+		panic("can't register protocols on running/stopped node")
 	}
+	n.server.Protocols = append(n.server.Protocols, protocols...)
+}
 
-	// unblock n.Wait
-	close(n.stop)
-
-	// Remove the keystore if it was created ephemerally.
-	var keystoreErr error
-	if n.ephemeralKeystore != "" {
-		keystoreErr = os.RemoveAll(n.ephemeralKeystore)
-	}
+// RegisterAPIs registers the APIs a service provides on the node.
+func (n *Node) RegisterAPIs(apis []rpc.API) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
 
-	if len(failure.Services) > 0 {
-		return failure
+	if n.state != initializingState {
+		panic("can't register APIs on running/stopped node")
 	}
-	if keystoreErr != nil {
-		return keystoreErr
-	}
-	return nil
+	n.rpcAPIs = append(n.rpcAPIs, apis...)
 }
 
-// Wait blocks the thread until the node is stopped. If the node is not running
-// at the time of invocation, the method immediately returns.
-func (n *Node) Wait() {
-	n.lock.RLock()
-	if n.server == nil {
-		n.lock.RUnlock()
-		return
-	}
-	stop := n.stop
-	n.lock.RUnlock()
-
-	<-stop
-}
+// RegisterHandler mounts a handler on the given path on the canonical HTTP server.
+//
+// The name of the handler is shown in a log message when the HTTP server starts
+// and should be a descriptive term for the service provided by the handler.
+func (n *Node) RegisterHandler(name, path string, handler http.Handler) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
 
-// Restart terminates a running node and boots up a new one in its place. If the
-// node isn't running, an error is returned.
-func (n *Node) Restart() error {
-	if err := n.Stop(); err != nil {
-		return err
-	}
-	if err := n.Start(); err != nil {
-		return err
+	if n.state != initializingState {
+		panic("can't register HTTP handler on running/stopped node")
 	}
-	return nil
+
+	n.http.mux.Handle(path, handler)
+	n.http.handlerNames[path] = name
 }
 
 // Attach creates an RPC client attached to an in-process API handler.
 func (n *Node) Attach() (*rpc.Client, error) {
-	n.lock.RLock()
-	defer n.lock.RUnlock()
-
-	if n.server == nil {
-		return nil, ErrNodeStopped
-	}
 	return rpc.DialInProc(n.inprocHandler), nil
 }
 
 // RPCHandler returns the in-process RPC request handler.
 func (n *Node) RPCHandler() (*rpc.Server, error) {
-	n.lock.RLock()
-	defer n.lock.RUnlock()
+	n.lock.Lock()
+	defer n.lock.Unlock()
 
-	if n.inprocHandler == nil {
+	if n.state == closedState {
 		return nil, ErrNodeStopped
 	}
 	return n.inprocHandler, nil
 }
 
+// Config returns the configuration of node.
+func (n *Node) Config() *Config {
+	return n.config
+}
+
 // Server retrieves the currently running P2P network layer. This method is meant
-// only to inspect fields of the currently running server, life cycle management
-// should be left to this Node entity.
+// only to inspect fields of the currently running server. Callers should not
+// start or stop the returned server.
 func (n *Node) Server() *p2p.Server {
-	n.lock.RLock()
-	defer n.lock.RUnlock()
+	n.lock.Lock()
+	defer n.lock.Unlock()
 
 	return n.server
 }
 
-// Service retrieves a currently running service registered of a specific type.
-func (n *Node) Service(service interface{}) error {
-	n.lock.RLock()
-	defer n.lock.RUnlock()
-
-	// Short circuit if the node's not running
-	if n.server == nil {
-		return ErrNodeStopped
-	}
-	// Otherwise try to find the service to return
-	element := reflect.ValueOf(service).Elem()
-	if running, ok := n.services[element.Type()]; ok {
-		element.Set(reflect.ValueOf(running))
-		return nil
-	}
-	return ErrServiceUnknown
-}
-
 // DataDir retrieves the current datadir used by the protocol stack.
 // Deprecated: No files should be stored in this directory, use InstanceDir instead.
 func (n *Node) DataDir() string {
@@ -609,29 +531,21 @@ func (n *Node) AccountManager() *accounts.Manager {
 
 // IPCEndpoint retrieves the current IPC endpoint used by the protocol stack.
 func (n *Node) IPCEndpoint() string {
-	return n.ipcEndpoint
+	return n.ipc.endpoint
 }
 
-// HTTPEndpoint retrieves the current HTTP endpoint used by the protocol stack.
+// HTTPEndpoint returns the URL of the HTTP server. Note that this URL does not
+// contain the JSON-RPC path prefix set by HTTPPathPrefix.
 func (n *Node) HTTPEndpoint() string {
-	n.lock.Lock()
-	defer n.lock.Unlock()
-
-	if n.httpListener != nil {
-		return n.httpListener.Addr().String()
-	}
-	return n.httpEndpoint
+	return "http://" + n.http.listenAddr()
 }
 
-// WSEndpoint retrieves the current WS endpoint used by the protocol stack.
+// WSEndpoint returns the current JSON-RPC over WebSocket endpoint.
 func (n *Node) WSEndpoint() string {
-	n.lock.Lock()
-	defer n.lock.Unlock()
-
-	if n.wsListener != nil {
-		return n.wsListener.Addr().String()
+	if n.http.wsAllowed() {
+		return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix
 	}
-	return n.wsEndpoint
+	return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix
 }
 
 // EventMux retrieves the event multiplexer used by all the network services in
@@ -643,11 +557,25 @@ func (n *Node) EventMux() *event.TypeMux {
 // OpenDatabase opens an existing database with the given name (or creates one if no
 // previous can be found) from within the node's instance directory. If the node is
 // ephemeral, a memory database is returned.
-func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (ethdb.Database, error) {
+func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+	if n.state == closedState {
+		return nil, ErrNodeStopped
+	}
+
+	var db ethdb.Database
+	var err error
 	if n.config.DataDir == "" {
-		return rawdb.NewMemoryDatabase(), nil
+		db = rawdb.NewMemoryDatabase()
+	} else {
+		db, err = rawdb.NewLevelDBDatabase(n.ResolvePath(name), cache, handles, namespace, readonly)
+	}
+
+	if err == nil {
+		db = n.wrapDatabase(db)
 	}
-	return rawdb.NewLevelDBDatabase(n.config.ResolvePath(name), cache, handles, namespace)
+	return db, err
 }
 
 // OpenDatabaseWithFreezer opens an existing database with the given name (or
@@ -655,19 +583,32 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string) (
 // also attaching a chain freezer to it that moves ancient chain data from the
 // database to immutable append-only files. If the node is an ephemeral one, a
 // memory database is returned.
-func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string) (ethdb.Database, error) {
+func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, freezer, namespace string, readonly bool) (ethdb.Database, error) {
+	n.lock.Lock()
+	defer n.lock.Unlock()
+	if n.state == closedState {
+		return nil, ErrNodeStopped
+	}
+
+	var db ethdb.Database
+	var err error
 	if n.config.DataDir == "" {
-		return rawdb.NewMemoryDatabase(), nil
+		db = rawdb.NewMemoryDatabase()
+	} else {
+		root := n.ResolvePath(name)
+		switch {
+		case freezer == "":
+			freezer = filepath.Join(root, "ancient")
+		case !filepath.IsAbs(freezer):
+			freezer = n.ResolvePath(freezer)
+		}
+		db, err = rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace, readonly)
 	}
-	root := n.config.ResolvePath(name)
 
-	switch {
-	case freezer == "":
-		freezer = filepath.Join(root, "ancient")
-	case !filepath.IsAbs(freezer):
-		freezer = n.config.ResolvePath(freezer)
+	if err == nil {
+		db = n.wrapDatabase(db)
 	}
-	return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace)
+	return db, err
 }
 
 // ResolvePath returns the absolute path of a resource in the instance directory.
@@ -675,49 +616,35 @@ func (n *Node) ResolvePath(x string) string {
 	return n.config.ResolvePath(x)
 }
 
-// apis returns the collection of RPC descriptors this node offers.
-func (n *Node) apis() []rpc.API {
-	return []rpc.API{
-		{
-			Namespace: "admin",
-			Version:   "1.0",
-			Service:   NewPrivateAdminAPI(n),
-		}, {
-			Namespace: "admin",
-			Version:   "1.0",
-			Service:   NewPublicAdminAPI(n),
-			Public:    true,
-		}, {
-			Namespace: "debug",
-			Version:   "1.0",
-			Service:   debug.Handler,
-		}, {
-			Namespace: "web3",
-			Version:   "1.0",
-			Service:   NewPublicWeb3API(n),
-			Public:    true,
-		},
-	}
-}
-
-// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules,
-// and then registers all of the APIs exposed by the services.
-func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error {
-	if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
-		log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
-	}
-	// Generate the whitelist based on the allowed modules
-	whitelist := make(map[string]bool)
-	for _, module := range modules {
-		whitelist[module] = true
-	}
-	// Register all the APIs exposed by the services
-	for _, api := range apis {
-		if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
-			if err := srv.RegisterName(api.Namespace, api.Service); err != nil {
-				return err
-			}
+// closeTrackingDB wraps the Close method of a database. When the database is closed by the
+// service, the wrapper removes it from the node's database map. This ensures that Node
+// won't auto-close the database if it is closed by the service that opened it.
+type closeTrackingDB struct {
+	ethdb.Database
+	n *Node
+}
+
+func (db *closeTrackingDB) Close() error {
+	db.n.lock.Lock()
+	delete(db.n.databases, db)
+	db.n.lock.Unlock()
+	return db.Database.Close()
+}
+
+// wrapDatabase ensures the database will be auto-closed when Node is closed.
+func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database {
+	wrapper := &closeTrackingDB{db, n}
+	n.databases[wrapper] = struct{}{}
+	return wrapper
+}
+
+// closeDatabases closes all open databases.
+func (n *Node) closeDatabases() (errors []error) {
+	for db := range n.databases {
+		delete(n.databases, db)
+		if err := db.Database.Close(); err != nil {
+			errors = append(errors, err)
 		}
 	}
-	return nil
+	return errors
 }
diff --git a/node/node_example_test.go b/node/node_example_test.go
index 57b18855f1..d54fe03067 100644
--- a/node/node_example_test.go
+++ b/node/node_example_test.go
@@ -21,26 +21,20 @@ import (
 	"log"
 
 	"github.com/ethereum/go-ethereum/node"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rpc"
 )
 
-// SampleService is a trivial network service that can be attached to a node for
+// SampleLifecycle is a trivial network service that can be attached to a node for
 // life cycle management.
 //
-// The following methods are needed to implement a node.Service:
-//  - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
-//  - APIs() []rpc.API           - api methods the service wants to expose on rpc channels
+// The following methods are needed to implement a node.Lifecycle:
 //  - Start() error              - method invoked when the node is ready to start the service
 //  - Stop() error               - method invoked when the node terminates the service
-type SampleService struct{}
+type SampleLifecycle struct{}
 
-func (s *SampleService) Protocols() []p2p.Protocol { return nil }
-func (s *SampleService) APIs() []rpc.API           { return nil }
-func (s *SampleService) Start(*p2p.Server) error   { fmt.Println("Service starting..."); return nil }
-func (s *SampleService) Stop() error               { fmt.Println("Service stopping..."); return nil }
+func (s *SampleLifecycle) Start() error { fmt.Println("Service starting..."); return nil }
+func (s *SampleLifecycle) Stop() error  { fmt.Println("Service stopping..."); return nil }
 
-func ExampleService() {
+func ExampleLifecycle() {
 	// Create a network node to run protocols with the default values.
 	stack, err := node.New(&node.Config{})
 	if err != nil {
@@ -48,29 +42,18 @@ func ExampleService() {
 	}
 	defer stack.Close()
 
-	// Create and register a simple network service. This is done through the definition
-	// of a node.ServiceConstructor that will instantiate a node.Service. The reason for
-	// the factory method approach is to support service restarts without relying on the
-	// individual implementations' support for such operations.
-	constructor := func(context *node.ServiceContext) (node.Service, error) {
-		return new(SampleService), nil
-	}
-	if err := stack.Register(constructor); err != nil {
-		log.Fatalf("Failed to register service: %v", err)
-	}
+	// Create and register a simple network Lifecycle.
+	service := new(SampleLifecycle)
+	stack.RegisterLifecycle(service)
+
 	// Boot up the entire protocol stack, do a restart and terminate
 	if err := stack.Start(); err != nil {
 		log.Fatalf("Failed to start the protocol stack: %v", err)
 	}
-	if err := stack.Restart(); err != nil {
-		log.Fatalf("Failed to restart the protocol stack: %v", err)
-	}
-	if err := stack.Stop(); err != nil {
+	if err := stack.Close(); err != nil {
 		log.Fatalf("Failed to stop the protocol stack: %v", err)
 	}
 	// Output:
 	// Service starting...
 	// Service stopping...
-	// Service starting...
-	// Service stopping...
 }
diff --git a/node/node_test.go b/node/node_test.go
index e246731fef..e104630600 100644
--- a/node/node_test.go
+++ b/node/node_test.go
@@ -18,14 +18,18 @@ package node
 
 import (
 	"errors"
+	"fmt"
+	"io"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"os"
 	"reflect"
+	"strings"
 	"testing"
-	"time"
 
 	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/rpc"
 
@@ -43,20 +47,28 @@ func testNodeConfig() *Config {
 	}
 }
 
-// Tests that an empty protocol stack can be started, restarted and stopped.
-func TestNodeLifeCycle(t *testing.T) {
+// Tests that an empty protocol stack can be closed more than once.
+func TestNodeCloseMultipleTimes(t *testing.T) {
 	stack, err := New(testNodeConfig())
 	if err != nil {
 		t.Fatalf("failed to create protocol stack: %v", err)
 	}
-	defer stack.Close()
+	stack.Close()
 
 	// Ensure that a stopped node can be stopped again
 	for i := 0; i < 3; i++ {
-		if err := stack.Stop(); err != ErrNodeStopped {
+		if err := stack.Close(); err != ErrNodeStopped {
 			t.Fatalf("iter %d: stop failure mismatch: have %v, want %v", i, err, ErrNodeStopped)
 		}
 	}
+}
+
+func TestNodeStartMultipleTimes(t *testing.T) {
+	stack, err := New(testNodeConfig())
+	if err != nil {
+		t.Fatalf("failed to create protocol stack: %v", err)
+	}
+
 	// Ensure that a node can be successfully started, but only once
 	if err := stack.Start(); err != nil {
 		t.Fatalf("failed to start node: %v", err)
@@ -64,17 +76,11 @@ func TestNodeLifeCycle(t *testing.T) {
 	if err := stack.Start(); err != ErrNodeRunning {
 		t.Fatalf("start failure mismatch: have %v, want %v ", err, ErrNodeRunning)
 	}
-	// Ensure that a node can be restarted arbitrarily many times
-	for i := 0; i < 3; i++ {
-		if err := stack.Restart(); err != nil {
-			t.Fatalf("iter %d: failed to restart node: %v", i, err)
-		}
-	}
 	// Ensure that a node can be stopped, but only once
-	if err := stack.Stop(); err != nil {
+	if err := stack.Close(); err != nil {
 		t.Fatalf("failed to stop node: %v", err)
 	}
-	if err := stack.Stop(); err != ErrNodeStopped {
+	if err := stack.Close(); err != ErrNodeStopped {
 		t.Fatalf("stop failure mismatch: have %v, want %v ", err, ErrNodeStopped)
 	}
 }
@@ -94,92 +100,152 @@ func TestNodeUsedDataDir(t *testing.T) {
 		t.Fatalf("failed to create original protocol stack: %v", err)
 	}
 	defer original.Close()
-
 	if err := original.Start(); err != nil {
 		t.Fatalf("failed to start original protocol stack: %v", err)
 	}
-	defer original.Stop()
 
 	// Create a second node based on the same data directory and ensure failure
-	duplicate, err := New(&Config{DataDir: dir})
-	if err != nil {
-		t.Fatalf("failed to create duplicate protocol stack: %v", err)
-	}
-	defer duplicate.Close()
-
-	if err := duplicate.Start(); err != ErrDatadirUsed {
+	_, err = New(&Config{DataDir: dir})
+	if err != ErrDatadirUsed {
 		t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed)
 	}
 }
 
-// Tests whether services can be registered and duplicates caught.
-func TestServiceRegistry(t *testing.T) {
+// Tests whether a Lifecycle can be registered.
+func TestLifecycleRegistry_Successful(t *testing.T) {
 	stack, err := New(testNodeConfig())
 	if err != nil {
 		t.Fatalf("failed to create protocol stack: %v", err)
 	}
 	defer stack.Close()
 
-	// Register a batch of unique services and ensure they start successfully
-	services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC}
-	for i, constructor := range services {
-		if err := stack.Register(constructor); err != nil {
-			t.Fatalf("service #%d: registration failed: %v", i, err)
-		}
+	noop := NewNoop()
+	stack.RegisterLifecycle(noop)
+
+	if !containsLifecycle(stack.lifecycles, noop) {
+		t.Fatalf("lifecycle was not properly registered on the node, %v", err)
 	}
-	if err := stack.Start(); err != nil {
-		t.Fatalf("failed to start original service stack: %v", err)
+}
+
+// Tests whether a service's protocols can be registered properly on the node's p2p server.
+func TestRegisterProtocols(t *testing.T) {
+	stack, err := New(testNodeConfig())
+	if err != nil {
+		t.Fatalf("failed to create protocol stack: %v", err)
 	}
-	if err := stack.Stop(); err != nil {
-		t.Fatalf("failed to stop original service stack: %v", err)
+	defer stack.Close()
+
+	fs, err := NewFullService(stack)
+	if err != nil {
+		t.Fatalf("could not create full service: %v", err)
 	}
-	// Duplicate one of the services and retry starting the node
-	if err := stack.Register(NewNoopServiceB); err != nil {
-		t.Fatalf("duplicate registration failed: %v", err)
+
+	for _, protocol := range fs.Protocols() {
+		if !containsProtocol(stack.server.Protocols, protocol) {
+			t.Fatalf("protocol %v was not successfully registered", protocol)
+		}
 	}
-	if err := stack.Start(); err == nil {
-		t.Fatalf("duplicate service started")
-	} else {
-		if _, ok := err.(*DuplicateServiceError); !ok {
-			t.Fatalf("duplicate error mismatch: have %v, want %v", err, DuplicateServiceError{})
+
+	for _, api := range fs.APIs() {
+		if !containsAPI(stack.rpcAPIs, api) {
+			t.Fatalf("api %v was not successfully registered", api)
 		}
 	}
 }
 
-// Tests that registered services get started and stopped correctly.
-func TestServiceLifeCycle(t *testing.T) {
-	stack, err := New(testNodeConfig())
+// This test checks that open databases are closed with node.
+func TestNodeCloseClosesDB(t *testing.T) {
+	stack, _ := New(testNodeConfig())
+	defer stack.Close()
+
+	db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
 	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
+		t.Fatal("can't open DB:", err)
+	}
+	if err = db.Put([]byte{}, []byte{}); err != nil {
+		t.Fatal("can't Put on open DB:", err)
 	}
-	defer stack.Close()
 
-	// Register a batch of life-cycle instrumented services
-	services := map[string]InstrumentingWrapper{
-		"A": InstrumentedServiceMakerA,
-		"B": InstrumentedServiceMakerB,
-		"C": InstrumentedServiceMakerC,
+	stack.Close()
+	if err = db.Put([]byte{}, []byte{}); err == nil {
+		t.Fatal("Put succeeded after node is closed")
 	}
+}
+
+// This test checks that OpenDatabase can be used from within a Lifecycle Start method.
+func TestNodeOpenDatabaseFromLifecycleStart(t *testing.T) {
+	stack, _ := New(testNodeConfig())
+	defer stack.Close()
+
+	var db ethdb.Database
+	var err error
+	stack.RegisterLifecycle(&InstrumentedService{
+		startHook: func() {
+			db, err = stack.OpenDatabase("mydb", 0, 0, "", false)
+			if err != nil {
+				t.Fatal("can't open DB:", err)
+			}
+		},
+		stopHook: func() {
+			db.Close()
+		},
+	})
+
+	stack.Start()
+	stack.Close()
+}
+
+// This test checks that OpenDatabase can be used from within a Lifecycle Stop method.
+func TestNodeOpenDatabaseFromLifecycleStop(t *testing.T) {
+	stack, _ := New(testNodeConfig())
+	defer stack.Close()
+
+	stack.RegisterLifecycle(&InstrumentedService{
+		stopHook: func() {
+			db, err := stack.OpenDatabase("mydb", 0, 0, "", false)
+			if err != nil {
+				t.Fatal("can't open DB:", err)
+			}
+			db.Close()
+		},
+	})
+
+	stack.Start()
+	stack.Close()
+}
+
+// Tests that registered Lifecycles get started and stopped correctly.
+func TestLifecycleLifeCycle(t *testing.T) {
+	stack, _ := New(testNodeConfig())
+	defer stack.Close()
+
 	started := make(map[string]bool)
 	stopped := make(map[string]bool)
 
-	for id, maker := range services {
-		id := id // Closure for the constructor
-		constructor := func(*ServiceContext) (Service, error) {
-			return &InstrumentedService{
-				startHook: func(*p2p.Server) { started[id] = true },
-				stopHook:  func() { stopped[id] = true },
-			}, nil
-		}
-		if err := stack.Register(maker(constructor)); err != nil {
-			t.Fatalf("service %s: registration failed: %v", id, err)
-		}
+	// Create a batch of instrumented services
+	lifecycles := map[string]Lifecycle{
+		"A": &InstrumentedService{
+			startHook: func() { started["A"] = true },
+			stopHook:  func() { stopped["A"] = true },
+		},
+		"B": &InstrumentedService{
+			startHook: func() { started["B"] = true },
+			stopHook:  func() { stopped["B"] = true },
+		},
+		"C": &InstrumentedService{
+			startHook: func() { started["C"] = true },
+			stopHook:  func() { stopped["C"] = true },
+		},
+	}
+	// register lifecycles on node
+	for _, lifecycle := range lifecycles {
+		stack.RegisterLifecycle(lifecycle)
 	}
 	// Start the node and check that all services are running
 	if err := stack.Start(); err != nil {
 		t.Fatalf("failed to start protocol stack: %v", err)
 	}
-	for id := range services {
+	for id := range lifecycles {
 		if !started[id] {
 			t.Fatalf("service %s: freshly started service not running", id)
 		}
@@ -188,470 +254,391 @@ func TestServiceLifeCycle(t *testing.T) {
 		}
 	}
 	// Stop the node and check that all services have been stopped
-	if err := stack.Stop(); err != nil {
+	if err := stack.Close(); err != nil {
 		t.Fatalf("failed to stop protocol stack: %v", err)
 	}
-	for id := range services {
+	for id := range lifecycles {
 		if !stopped[id] {
 			t.Fatalf("service %s: freshly terminated service still running", id)
 		}
 	}
 }
 
-// Tests that services are restarted cleanly as new instances.
-func TestServiceRestarts(t *testing.T) {
+// Tests that if a Lifecycle fails to start, all others started before it will be
+// shut down.
+func TestLifecycleStartupError(t *testing.T) {
 	stack, err := New(testNodeConfig())
 	if err != nil {
 		t.Fatalf("failed to create protocol stack: %v", err)
 	}
 	defer stack.Close()
 
-	// Define a service that does not support restarts
-	var (
-		running bool
-		started int
-	)
-	constructor := func(*ServiceContext) (Service, error) {
-		running = false
-
-		return &InstrumentedService{
-			startHook: func(*p2p.Server) {
-				if running {
-					panic("already running")
-				}
-				running = true
-				started++
-			},
-		}, nil
-	}
-	// Register the service and start the protocol stack
-	if err := stack.Register(constructor); err != nil {
-		t.Fatalf("failed to register the service: %v", err)
-	}
-	if err := stack.Start(); err != nil {
-		t.Fatalf("failed to start protocol stack: %v", err)
-	}
-	defer stack.Stop()
-
-	if !running || started != 1 {
-		t.Fatalf("running/started mismatch: have %v/%d, want true/1", running, started)
-	}
-	// Restart the stack a few times and check successful service restarts
-	for i := 0; i < 3; i++ {
-		if err := stack.Restart(); err != nil {
-			t.Fatalf("iter %d: failed to restart stack: %v", i, err)
-		}
-	}
-	if !running || started != 4 {
-		t.Fatalf("running/started mismatch: have %v/%d, want true/4", running, started)
-	}
-}
+	started := make(map[string]bool)
+	stopped := make(map[string]bool)
 
-// Tests that if a service fails to initialize itself, none of the other services
-// will be allowed to even start.
-func TestServiceConstructionAbortion(t *testing.T) {
-	stack, err := New(testNodeConfig())
-	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
+	// Create a batch of instrumented services
+	lifecycles := map[string]Lifecycle{
+		"A": &InstrumentedService{
+			startHook: func() { started["A"] = true },
+			stopHook:  func() { stopped["A"] = true },
+		},
+		"B": &InstrumentedService{
+			startHook: func() { started["B"] = true },
+			stopHook:  func() { stopped["B"] = true },
+		},
+		"C": &InstrumentedService{
+			startHook: func() { started["C"] = true },
+			stopHook:  func() { stopped["C"] = true },
+		},
+	}
+	// register lifecycles on node
+	for _, lifecycle := range lifecycles {
+		stack.RegisterLifecycle(lifecycle)
 	}
-	defer stack.Close()
 
-	// Define a batch of good services
-	services := map[string]InstrumentingWrapper{
-		"A": InstrumentedServiceMakerA,
-		"B": InstrumentedServiceMakerB,
-		"C": InstrumentedServiceMakerC,
-	}
-	started := make(map[string]bool)
-	for id, maker := range services {
-		id := id // Closure for the constructor
-		constructor := func(*ServiceContext) (Service, error) {
-			return &InstrumentedService{
-				startHook: func(*p2p.Server) { started[id] = true },
-			}, nil
-		}
-		if err := stack.Register(maker(constructor)); err != nil {
-			t.Fatalf("service %s: registration failed: %v", id, err)
-		}
-	}
 	// Register a service that fails to construct itself
 	failure := errors.New("fail")
-	failer := func(*ServiceContext) (Service, error) {
-		return nil, failure
-	}
-	if err := stack.Register(failer); err != nil {
-		t.Fatalf("failer registration failed: %v", err)
+	failer := &InstrumentedService{start: failure}
+	stack.RegisterLifecycle(failer)
+
+	// Start the protocol stack and ensure all started services stop
+	if err := stack.Start(); err != failure {
+		t.Fatalf("stack startup failure mismatch: have %v, want %v", err, failure)
 	}
-	// Start the protocol stack and ensure none of the services get started
-	for i := 0; i < 100; i++ {
-		if err := stack.Start(); err != failure {
-			t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
-		}
-		for id := range services {
-			if started[id] {
-				t.Fatalf("service %s: started should not have", id)
-			}
-			delete(started, id)
+	for id := range lifecycles {
+		if started[id] && !stopped[id] {
+			t.Fatalf("service %s: started but not stopped", id)
 		}
+		delete(started, id)
+		delete(stopped, id)
 	}
 }
 
-// Tests that if a service fails to start, all others started before it will be
-// shut down.
-func TestServiceStartupAbortion(t *testing.T) {
+// Tests that even if a registered Lifecycle fails to shut down cleanly, it does
+// not influence the rest of the shutdown invocations.
+func TestLifecycleTerminationGuarantee(t *testing.T) {
 	stack, err := New(testNodeConfig())
 	if err != nil {
 		t.Fatalf("failed to create protocol stack: %v", err)
 	}
 	defer stack.Close()
 
-	// Register a batch of good services
-	services := map[string]InstrumentingWrapper{
-		"A": InstrumentedServiceMakerA,
-		"B": InstrumentedServiceMakerB,
-		"C": InstrumentedServiceMakerC,
-	}
 	started := make(map[string]bool)
 	stopped := make(map[string]bool)
 
-	for id, maker := range services {
-		id := id // Closure for the constructor
-		constructor := func(*ServiceContext) (Service, error) {
-			return &InstrumentedService{
-				startHook: func(*p2p.Server) { started[id] = true },
-				stopHook:  func() { stopped[id] = true },
-			}, nil
-		}
-		if err := stack.Register(maker(constructor)); err != nil {
-			t.Fatalf("service %s: registration failed: %v", id, err)
-		}
+	// Create a batch of instrumented services
+	lifecycles := map[string]Lifecycle{
+		"A": &InstrumentedService{
+			startHook: func() { started["A"] = true },
+			stopHook:  func() { stopped["A"] = true },
+		},
+		"B": &InstrumentedService{
+			startHook: func() { started["B"] = true },
+			stopHook:  func() { stopped["B"] = true },
+		},
+		"C": &InstrumentedService{
+			startHook: func() { started["C"] = true },
+			stopHook:  func() { stopped["C"] = true },
+		},
+	}
+	// register lifecycles on node
+	for _, lifecycle := range lifecycles {
+		stack.RegisterLifecycle(lifecycle)
 	}
-	// Register a service that fails to start
+
+	// Register a service that fails to shot down cleanly
 	failure := errors.New("fail")
-	failer := func(*ServiceContext) (Service, error) {
-		return &InstrumentedService{
-			start: failure,
-		}, nil
+	failer := &InstrumentedService{stop: failure}
+	stack.RegisterLifecycle(failer)
+
+	// Start the protocol stack, and ensure that a failing shut down terminates all
+	// Start the stack and make sure all is online
+	if err := stack.Start(); err != nil {
+		t.Fatalf("failed to start protocol stack: %v", err)
 	}
-	if err := stack.Register(failer); err != nil {
-		t.Fatalf("failer registration failed: %v", err)
+	for id := range lifecycles {
+		if !started[id] {
+			t.Fatalf("service %s: service not running", id)
+		}
+		if stopped[id] {
+			t.Fatalf("service %s: service already stopped", id)
+		}
 	}
-	// Start the protocol stack and ensure all started services stop
-	for i := 0; i < 100; i++ {
-		if err := stack.Start(); err != failure {
-			t.Fatalf("iter %d: stack startup failure mismatch: have %v, want %v", i, err, failure)
+	// Stop the stack, verify failure and check all terminations
+	err = stack.Close()
+	if err, ok := err.(*StopError); !ok {
+		t.Fatalf("termination failure mismatch: have %v, want StopError", err)
+	} else {
+		failer := reflect.TypeOf(&InstrumentedService{})
+		if err.Services[failer] != failure {
+			t.Fatalf("failer termination failure mismatch: have %v, want %v", err.Services[failer], failure)
 		}
-		for id := range services {
-			if started[id] && !stopped[id] {
-				t.Fatalf("service %s: started but not stopped", id)
-			}
-			delete(started, id)
-			delete(stopped, id)
+		if len(err.Services) != 1 {
+			t.Fatalf("failure count mismatch: have %d, want %d", len(err.Services), 1)
+		}
+	}
+	for id := range lifecycles {
+		if !stopped[id] {
+			t.Fatalf("service %s: service not terminated", id)
 		}
+		delete(started, id)
+		delete(stopped, id)
 	}
+
+	stack.server = &p2p.Server{}
+	stack.server.PrivateKey = testNodeKey
 }
 
-// Tests that even if a registered service fails to shut down cleanly, it does
-// not influece the rest of the shutdown invocations.
-func TestServiceTerminationGuarantee(t *testing.T) {
-	stack, err := New(testNodeConfig())
-	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
-	}
-	defer stack.Close()
+// Tests whether a handler can be successfully mounted on the canonical HTTP server
+// on the given prefix
+func TestRegisterHandler_Successful(t *testing.T) {
+	node := createNode(t, 7878, 7979)
 
-	// Register a batch of good services
-	services := map[string]InstrumentingWrapper{
-		"A": InstrumentedServiceMakerA,
-		"B": InstrumentedServiceMakerB,
-		"C": InstrumentedServiceMakerC,
-	}
-	started := make(map[string]bool)
-	stopped := make(map[string]bool)
+	// create and mount handler
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("success"))
+	})
+	node.RegisterHandler("test", "/test", handler)
 
-	for id, maker := range services {
-		id := id // Closure for the constructor
-		constructor := func(*ServiceContext) (Service, error) {
-			return &InstrumentedService{
-				startHook: func(*p2p.Server) { started[id] = true },
-				stopHook:  func() { stopped[id] = true },
-			}, nil
-		}
-		if err := stack.Register(maker(constructor)); err != nil {
-			t.Fatalf("service %s: registration failed: %v", id, err)
-		}
+	// start node
+	if err := node.Start(); err != nil {
+		t.Fatalf("could not start node: %v", err)
 	}
-	// Register a service that fails to shot down cleanly
-	failure := errors.New("fail")
-	failer := func(*ServiceContext) (Service, error) {
-		return &InstrumentedService{
-			stop: failure,
-		}, nil
-	}
-	if err := stack.Register(failer); err != nil {
-		t.Fatalf("failer registration failed: %v", err)
+
+	// create HTTP request
+	httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7878/test", nil)
+	if err != nil {
+		t.Error("could not issue new http request ", err)
 	}
-	// Start the protocol stack, and ensure that a failing shut down terminates all
-	for i := 0; i < 100; i++ {
-		// Start the stack and make sure all is online
-		if err := stack.Start(); err != nil {
-			t.Fatalf("iter %d: failed to start protocol stack: %v", i, err)
-		}
-		for id := range services {
-			if !started[id] {
-				t.Fatalf("iter %d, service %s: service not running", i, id)
-			}
-			if stopped[id] {
-				t.Fatalf("iter %d, service %s: service already stopped", i, id)
-			}
-		}
-		// Stop the stack, verify failure and check all terminations
-		err := stack.Stop()
-		if err, ok := err.(*StopError); !ok {
-			t.Fatalf("iter %d: termination failure mismatch: have %v, want StopError", i, err)
-		} else {
-			failer := reflect.TypeOf(&InstrumentedService{})
-			if err.Services[failer] != failure {
-				t.Fatalf("iter %d: failer termination failure mismatch: have %v, want %v", i, err.Services[failer], failure)
-			}
-			if len(err.Services) != 1 {
-				t.Fatalf("iter %d: failure count mismatch: have %d, want %d", i, len(err.Services), 1)
-			}
-		}
-		for id := range services {
-			if !stopped[id] {
-				t.Fatalf("iter %d, service %s: service not terminated", i, id)
-			}
-			delete(started, id)
-			delete(stopped, id)
-		}
+
+	// check response
+	resp := doHTTPRequest(t, httpReq)
+	buf := make([]byte, 7)
+	_, err = io.ReadFull(resp.Body, buf)
+	if err != nil {
+		t.Fatalf("could not read response: %v", err)
 	}
+	assert.Equal(t, "success", string(buf))
 }
 
-// TestServiceRetrieval tests that individual services can be retrieved.
-func TestServiceRetrieval(t *testing.T) {
-	// Create a simple stack and register two service types
-	stack, err := New(testNodeConfig())
+// Tests that the given handler will not be successfully mounted since no HTTP server
+// is enabled for RPC
+func TestRegisterHandler_Unsuccessful(t *testing.T) {
+	node, err := New(&DefaultConfig)
 	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
+		t.Fatalf("could not create new node: %v", err)
 	}
-	defer stack.Close()
 
-	if err := stack.Register(NewNoopService); err != nil {
-		t.Fatalf("noop service registration failed: %v", err)
-	}
-	if err := stack.Register(NewInstrumentedService); err != nil {
-		t.Fatalf("instrumented service registration failed: %v", err)
-	}
-	// Make sure none of the services can be retrieved until started
-	var noopServ *NoopService
-	if err := stack.Service(&noopServ); err != ErrNodeStopped {
-		t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
-	}
-	var instServ *InstrumentedService
-	if err := stack.Service(&instServ); err != ErrNodeStopped {
-		t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, ErrNodeStopped)
-	}
-	// Start the stack and ensure everything is retrievable now
-	if err := stack.Start(); err != nil {
-		t.Fatalf("failed to start stack: %v", err)
-	}
-	defer stack.Stop()
+	// create and mount handler
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("success"))
+	})
+	node.RegisterHandler("test", "/test", handler)
+}
+
+// Tests whether websocket requests can be handled on the same port as a regular http server.
+func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) {
+	node := startHTTP(t, 0, 0)
+	defer node.Close()
+
+	ws := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
 
-	if err := stack.Service(&noopServ); err != nil {
-		t.Fatalf("noop service retrieval mismatch: have %v, want %v", err, nil)
+	if node.WSEndpoint() != ws {
+		t.Fatalf("endpoints should be the same")
 	}
-	if err := stack.Service(&instServ); err != nil {
-		t.Fatalf("instrumented service retrieval mismatch: have %v, want %v", err, nil)
+	if !checkRPC(ws) {
+		t.Fatalf("ws request failed")
+	}
+	if !checkRPC(node.HTTPEndpoint()) {
+		t.Fatalf("http request failed")
 	}
 }
 
-// Tests that all protocols defined by individual services get launched.
-func TestProtocolGather(t *testing.T) {
-	stack, err := New(testNodeConfig())
+func TestWebsocketHTTPOnSeparatePort_WSRequest(t *testing.T) {
+	// try and get a free port
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
 	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
+		t.Fatal("can't listen:", err)
 	}
-	defer stack.Close()
+	port := listener.Addr().(*net.TCPAddr).Port
+	listener.Close()
 
-	// Register a batch of services with some configured number of protocols
-	services := map[string]struct {
-		Count int
-		Maker InstrumentingWrapper
-	}{
-		"zero": {0, InstrumentedServiceMakerA},
-		"one":  {1, InstrumentedServiceMakerB},
-		"many": {10, InstrumentedServiceMakerC},
-	}
-	for id, config := range services {
-		protocols := make([]p2p.Protocol, config.Count)
-		for i := 0; i < len(protocols); i++ {
-			protocols[i].Name = id
-			protocols[i].Version = uint(i)
-		}
-		constructor := func(*ServiceContext) (Service, error) {
-			return &InstrumentedService{
-				protocols: protocols,
-			}, nil
-		}
-		if err := stack.Register(config.Maker(constructor)); err != nil {
-			t.Fatalf("service %s: registration failed: %v", id, err)
-		}
+	node := startHTTP(t, 0, port)
+	defer node.Close()
+
+	wsOnHTTP := strings.Replace(node.HTTPEndpoint(), "http://", "ws://", 1)
+	ws := fmt.Sprintf("ws://127.0.0.1:%d", port)
+
+	if node.WSEndpoint() == wsOnHTTP {
+		t.Fatalf("endpoints should not be the same")
 	}
-	// Start the services and ensure all protocols start successfully
-	if err := stack.Start(); err != nil {
-		t.Fatalf("failed to start protocol stack: %v", err)
+	// ensure ws endpoint matches the expected endpoint
+	if node.WSEndpoint() != ws {
+		t.Fatalf("ws endpoint is incorrect: expected %s, got %s", ws, node.WSEndpoint())
 	}
-	defer stack.Stop()
 
-	protocols := stack.Server().Protocols
-	if len(protocols) != 11 {
-		t.Fatalf("mismatching number of protocols launched: have %d, want %d", len(protocols), 26)
+	if !checkRPC(ws) {
+		t.Fatalf("ws request failed")
+	}
+	if !checkRPC(node.HTTPEndpoint()) {
+		t.Fatalf("http request failed")
 	}
-	for id, config := range services {
-		for ver := 0; ver < config.Count; ver++ {
-			launched := false
-			for i := 0; i < len(protocols); i++ {
-				if protocols[i].Name == id && protocols[i].Version == uint(ver) {
-					launched = true
-					break
-				}
+}
+
+type rpcPrefixTest struct {
+	httpPrefix, wsPrefix string
+	// These lists paths on which JSON-RPC should be served / not served.
+	wantHTTP   []string
+	wantNoHTTP []string
+	wantWS     []string
+	wantNoWS   []string
+}
+
+func TestNodeRPCPrefix(t *testing.T) {
+	t.Parallel()
+
+	tests := []rpcPrefixTest{
+		// both off
+		{
+			httpPrefix: "", wsPrefix: "",
+			wantHTTP:   []string{"/", "/?p=1"},
+			wantNoHTTP: []string{"/test", "/test?p=1"},
+			wantWS:     []string{"/", "/?p=1"},
+			wantNoWS:   []string{"/test", "/test?p=1"},
+		},
+		// only http prefix
+		{
+			httpPrefix: "/testprefix", wsPrefix: "",
+			wantHTTP:   []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
+			wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"},
+			wantWS:     []string{"/", "/?p=1"},
+			wantNoWS:   []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"},
+		},
+		// only ws prefix
+		{
+			httpPrefix: "", wsPrefix: "/testprefix",
+			wantHTTP:   []string{"/", "/?p=1"},
+			wantNoHTTP: []string{"/testprefix", "/testprefix?p=1", "/test", "/test?p=1"},
+			wantWS:     []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
+			wantNoWS:   []string{"/", "/?p=1", "/test", "/test?p=1"},
+		},
+		// both set
+		{
+			httpPrefix: "/testprefix", wsPrefix: "/testprefix",
+			wantHTTP:   []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
+			wantNoHTTP: []string{"/", "/?p=1", "/test", "/test?p=1"},
+			wantWS:     []string{"/testprefix", "/testprefix?p=1", "/testprefix/x", "/testprefix/x?p=1"},
+			wantNoWS:   []string{"/", "/?p=1", "/test", "/test?p=1"},
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		name := fmt.Sprintf("http=%s ws=%s", test.httpPrefix, test.wsPrefix)
+		t.Run(name, func(t *testing.T) {
+			cfg := &Config{
+				HTTPHost:       "127.0.0.1",
+				HTTPPathPrefix: test.httpPrefix,
+				WSHost:         "127.0.0.1",
+				WSPathPrefix:   test.wsPrefix,
 			}
-			if !launched {
-				t.Errorf("configured protocol not launched: %s v%d", id, ver)
+			node, err := New(cfg)
+			if err != nil {
+				t.Fatal("can't create node:", err)
 			}
-		}
+			defer node.Close()
+			if err := node.Start(); err != nil {
+				t.Fatal("can't start node:", err)
+			}
+			test.check(t, node)
+		})
 	}
 }
 
-// Tests that all APIs defined by individual services get exposed.
-func TestAPIGather(t *testing.T) {
-	stack, err := New(testNodeConfig())
-	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
+func (test rpcPrefixTest) check(t *testing.T, node *Node) {
+	t.Helper()
+	httpBase := "http://" + node.http.listenAddr()
+	wsBase := "ws://" + node.http.listenAddr()
+
+	if node.WSEndpoint() != wsBase+test.wsPrefix {
+		t.Errorf("Error: node has wrong WSEndpoint %q", node.WSEndpoint())
 	}
-	defer stack.Close()
 
-	// Register a batch of services with some configured APIs
-	calls := make(chan string, 1)
-	makeAPI := func(result string) *OneMethodAPI {
-		return &OneMethodAPI{fun: func() { calls <- result }}
-	}
-	services := map[string]struct {
-		APIs  []rpc.API
-		Maker InstrumentingWrapper
-	}{
-		"Zero APIs": {
-			[]rpc.API{}, InstrumentedServiceMakerA},
-		"Single API": {
-			[]rpc.API{
-				{Namespace: "single", Version: "1", Service: makeAPI("single.v1"), Public: true},
-			}, InstrumentedServiceMakerB},
-		"Many APIs": {
-			[]rpc.API{
-				{Namespace: "multi", Version: "1", Service: makeAPI("multi.v1"), Public: true},
-				{Namespace: "multi.v2", Version: "2", Service: makeAPI("multi.v2"), Public: true},
-				{Namespace: "multi.v2.nested", Version: "2", Service: makeAPI("multi.v2.nested"), Public: true},
-			}, InstrumentedServiceMakerC},
-	}
-
-	for id, config := range services {
-		config := config
-		constructor := func(*ServiceContext) (Service, error) {
-			return &InstrumentedService{apis: config.APIs}, nil
-		}
-		if err := stack.Register(config.Maker(constructor)); err != nil {
-			t.Fatalf("service %s: registration failed: %v", id, err)
+	for _, path := range test.wantHTTP {
+		resp := rpcRequest(t, httpBase+path)
+		if resp.StatusCode != 200 {
+			t.Errorf("Error: %s: bad status code %d, want 200", path, resp.StatusCode)
 		}
 	}
-	// Start the services and ensure all API start successfully
-	if err := stack.Start(); err != nil {
-		t.Fatalf("failed to start protocol stack: %v", err)
-	}
-	defer stack.Stop()
-
-	// Connect to the RPC server and verify the various registered endpoints
-	client, err := stack.Attach()
-	if err != nil {
-		t.Fatalf("failed to connect to the inproc API server: %v", err)
-	}
-	defer client.Close()
-
-	tests := []struct {
-		Method string
-		Result string
-	}{
-		{"single_theOneMethod", "single.v1"},
-		{"multi_theOneMethod", "multi.v1"},
-		{"multi.v2_theOneMethod", "multi.v2"},
-		{"multi.v2.nested_theOneMethod", "multi.v2.nested"},
-	}
-	for i, test := range tests {
-		if err := client.Call(nil, test.Method); err != nil {
-			t.Errorf("test %d: API request failed: %v", i, err)
+	for _, path := range test.wantNoHTTP {
+		resp := rpcRequest(t, httpBase+path)
+		if resp.StatusCode != 404 {
+			t.Errorf("Error: %s: bad status code %d, want 404", path, resp.StatusCode)
 		}
-		select {
-		case result := <-calls:
-			if result != test.Result {
-				t.Errorf("test %d: result mismatch: have %s, want %s", i, result, test.Result)
-			}
-		case <-time.After(time.Second):
-			t.Fatalf("test %d: rpc execution timeout", i)
+	}
+	for _, path := range test.wantWS {
+		err := wsRequest(t, wsBase+path, "")
+		if err != nil {
+			t.Errorf("Error: %s: WebSocket connection failed: %v", path, err)
 		}
 	}
-}
-
-func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) {
-	node := startHTTP(t)
-	defer node.stopHTTP()
+	for _, path := range test.wantNoWS {
+		err := wsRequest(t, wsBase+path, "")
+		if err == nil {
+			t.Errorf("Error: %s: WebSocket connection succeeded for path in wantNoWS", path)
+		}
 
-	wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil)
-	if err != nil {
-		t.Error("could not issue new http request ", err)
 	}
-	wsReq.Header.Set("Connection", "upgrade")
-	wsReq.Header.Set("Upgrade", "websocket")
-	wsReq.Header.Set("Sec-WebSocket-Version", "13")
-	wsReq.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==")
-
-	resp := doHTTPRequest(t, wsReq)
-	assert.Equal(t, "websocket", resp.Header.Get("Upgrade"))
 }
 
-func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) {
-	node := startHTTP(t)
-	defer node.stopHTTP()
-
-	httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil)
-	if err != nil {
-		t.Error("could not issue new http request ", err)
+func createNode(t *testing.T, httpPort, wsPort int) *Node {
+	conf := &Config{
+		HTTPHost: "127.0.0.1",
+		HTTPPort: httpPort,
+		WSHost:   "127.0.0.1",
+		WSPort:   wsPort,
 	}
-	httpReq.Header.Set("Accept-Encoding", "gzip")
-
-	resp := doHTTPRequest(t, httpReq)
-	assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding"))
-}
-
-func startHTTP(t *testing.T) *Node {
-	conf := &Config{HTTPPort: 7453, WSPort: 7453}
 	node, err := New(conf)
 	if err != nil {
-		t.Error("could not create a new node ", err)
+		t.Fatalf("could not create a new node: %v", err)
 	}
+	return node
+}
 
-	err = node.startHTTP("127.0.0.1:7453", []rpc.API{}, []string{}, []string{}, []string{}, rpc.HTTPTimeouts{}, []string{})
+func startHTTP(t *testing.T, httpPort, wsPort int) *Node {
+	node := createNode(t, httpPort, wsPort)
+	err := node.Start()
 	if err != nil {
-		t.Error("could not start http service on node ", err)
+		t.Fatalf("could not start http service on node: %v", err)
 	}
 
 	return node
 }
 
 func doHTTPRequest(t *testing.T, req *http.Request) *http.Response {
-	client := &http.Client{}
+	client := http.DefaultClient
 	resp, err := client.Do(req)
 	if err != nil {
-		t.Error("could not issue a GET request to the given endpoint", err)
+		t.Fatalf("could not issue a GET request to the given endpoint: %v", err)
+
 	}
 	return resp
 }
+
+func containsProtocol(stackProtocols []p2p.Protocol, protocol p2p.Protocol) bool {
+	for _, a := range stackProtocols {
+		if reflect.DeepEqual(a, protocol) {
+			return true
+		}
+	}
+	return false
+}
+
+func containsAPI(stackAPIs []rpc.API, api rpc.API) bool {
+	for _, a := range stackAPIs {
+		if reflect.DeepEqual(a, api) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/node/rpcstack.go b/node/rpcstack.go
index 70aa0d4555..56e23cc5c7 100644
--- a/node/rpcstack.go
+++ b/node/rpcstack.go
@@ -18,17 +18,349 @@ package node
 
 import (
 	"compress/gzip"
+	"context"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"net"
 	"net/http"
+	"sort"
 	"strings"
 	"sync"
+	"sync/atomic"
 
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rpc"
 	"github.com/rs/cors"
 )
 
+// httpConfig is the JSON-RPC/HTTP configuration.
+type httpConfig struct {
+	Modules            []string
+	CorsAllowedOrigins []string
+	Vhosts             []string
+	prefix             string // path prefix on which to mount http handler
+}
+
+// wsConfig is the JSON-RPC/Websocket configuration
+type wsConfig struct {
+	Origins []string
+	Modules []string
+	prefix  string // path prefix on which to mount ws handler
+}
+
+type rpcHandler struct {
+	http.Handler
+	server *rpc.Server
+}
+
+type httpServer struct {
+	log      log.Logger
+	timeouts rpc.HTTPTimeouts
+	mux      http.ServeMux // registered handlers go here
+
+	mu       sync.Mutex
+	server   *http.Server
+	listener net.Listener // non-nil when server is running
+
+	// HTTP RPC handler things.
+
+	httpConfig  httpConfig
+	httpHandler atomic.Value // *rpcHandler
+
+	// WebSocket handler things.
+	wsConfig  wsConfig
+	wsHandler atomic.Value // *rpcHandler
+
+	// These are set by setListenAddr.
+	endpoint string
+	host     string
+	port     int
+
+	handlerNames map[string]string
+}
+
+func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer {
+	h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)}
+
+	h.httpHandler.Store((*rpcHandler)(nil))
+	h.wsHandler.Store((*rpcHandler)(nil))
+	return h
+}
+
+// setListenAddr configures the listening address of the server.
+// The address can only be set while the server isn't running.
+func (h *httpServer) setListenAddr(host string, port int) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	if h.listener != nil && (host != h.host || port != h.port) {
+		return fmt.Errorf("HTTP server already running on %s", h.endpoint)
+	}
+
+	h.host, h.port = host, port
+	h.endpoint = fmt.Sprintf("%s:%d", host, port)
+	return nil
+}
+
+// listenAddr returns the listening address of the server.
+func (h *httpServer) listenAddr() string {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	if h.listener != nil {
+		return h.listener.Addr().String()
+	}
+	return h.endpoint
+}
+
+// start starts the HTTP server if it is enabled and not already running.
+func (h *httpServer) start() error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	if h.endpoint == "" || h.listener != nil {
+		return nil // already running or not configured
+	}
+
+	// Initialize the server.
+	h.server = &http.Server{Handler: h}
+	if h.timeouts != (rpc.HTTPTimeouts{}) {
+		CheckTimeouts(&h.timeouts)
+		h.server.ReadTimeout = h.timeouts.ReadTimeout
+		h.server.WriteTimeout = h.timeouts.WriteTimeout
+		h.server.IdleTimeout = h.timeouts.IdleTimeout
+	}
+
+	// Start the server.
+	listener, err := net.Listen("tcp", h.endpoint)
+	if err != nil {
+		// If the server fails to start, we need to clear out the RPC and WS
+		// configuration so they can be configured another time.
+		h.disableRPC()
+		h.disableWS()
+		return err
+	}
+	h.listener = listener
+	go h.server.Serve(listener)
+
+	if h.wsAllowed() {
+		url := fmt.Sprintf("ws://%v", listener.Addr())
+		if h.wsConfig.prefix != "" {
+			url += h.wsConfig.prefix
+		}
+		h.log.Info("WebSocket enabled", "url", url)
+	}
+	// if server is websocket only, return after logging
+	if !h.rpcAllowed() {
+		return nil
+	}
+	// Log http endpoint.
+	h.log.Info("HTTP server started",
+		"endpoint", listener.Addr(),
+		"prefix", h.httpConfig.prefix,
+		"cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
+		"vhosts", strings.Join(h.httpConfig.Vhosts, ","),
+	)
+
+	// Log all handlers mounted on server.
+	var paths []string
+	for path := range h.handlerNames {
+		paths = append(paths, path)
+	}
+	sort.Strings(paths)
+	logged := make(map[string]bool, len(paths))
+	for _, path := range paths {
+		name := h.handlerNames[path]
+		if !logged[name] {
+			log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path)
+			logged[name] = true
+		}
+	}
+	return nil
+}
+
+func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// check if ws request and serve if ws enabled
+	ws := h.wsHandler.Load().(*rpcHandler)
+	if ws != nil && isWebsocket(r) {
+		if checkPath(r, h.wsConfig.prefix) {
+			ws.ServeHTTP(w, r)
+		}
+		return
+	}
+	// if http-rpc is enabled, try to serve request
+	rpc := h.httpHandler.Load().(*rpcHandler)
+	if rpc != nil {
+		// First try to route in the mux.
+		// Requests to a path below root are handled by the mux,
+		// which has all the handlers registered via Node.RegisterHandler.
+		// These are made available when RPC is enabled.
+		muxHandler, pattern := h.mux.Handler(r)
+		if pattern != "" {
+			muxHandler.ServeHTTP(w, r)
+			return
+		}
+
+		if checkPath(r, h.httpConfig.prefix) {
+			rpc.ServeHTTP(w, r)
+			return
+		}
+	}
+	w.WriteHeader(http.StatusNotFound)
+}
+
+// checkPath checks whether a given request URL matches a given path prefix.
+func checkPath(r *http.Request, path string) bool {
+	// if no prefix has been specified, request URL must be on root
+	if path == "" {
+		return r.URL.Path == "/"
+	}
+	// otherwise, check to make sure prefix matches
+	return len(r.URL.Path) >= len(path) && r.URL.Path[:len(path)] == path
+}
+
+// validatePrefix checks if 'path' is a valid configuration value for the RPC prefix option.
+func validatePrefix(what, path string) error {
+	if path == "" {
+		return nil
+	}
+	if path[0] != '/' {
+		return fmt.Errorf(`%s RPC path prefix %q does not contain leading "/"`, what, path)
+	}
+	if strings.ContainsAny(path, "?#") {
+		// This is just to avoid confusion. While these would match correctly (i.e. they'd
+		// match if URL-escaped into path), it's not easy to understand for users when
+		// setting that on the command line.
+		return fmt.Errorf("%s RPC path prefix %q contains URL meta-characters", what, path)
+	}
+	return nil
+}
+
+// stop shuts down the HTTP server.
+func (h *httpServer) stop() {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	h.doStop()
+}
+
+func (h *httpServer) doStop() {
+	if h.listener == nil {
+		return // not running
+	}
+
+	// Shut down the server.
+	httpHandler := h.httpHandler.Load().(*rpcHandler)
+	wsHandler := h.httpHandler.Load().(*rpcHandler)
+	if httpHandler != nil {
+		h.httpHandler.Store((*rpcHandler)(nil))
+		httpHandler.server.Stop()
+	}
+	if wsHandler != nil {
+		h.wsHandler.Store((*rpcHandler)(nil))
+		wsHandler.server.Stop()
+	}
+	h.server.Shutdown(context.Background())
+	h.listener.Close()
+	h.log.Info("HTTP server stopped", "endpoint", h.listener.Addr())
+
+	// Clear out everything to allow re-configuring it later.
+	h.host, h.port, h.endpoint = "", 0, ""
+	h.server, h.listener = nil, nil
+}
+
+// enableRPC turns on JSON-RPC over HTTP on the server.
+func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	if h.rpcAllowed() {
+		return fmt.Errorf("JSON-RPC over HTTP is already enabled")
+	}
+
+	// Create RPC server and handler.
+	srv := rpc.NewServer()
+	if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil {
+		return err
+	}
+	h.httpConfig = config
+	h.httpHandler.Store(&rpcHandler{
+		Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts),
+		server:  srv,
+	})
+	return nil
+}
+
+// disableRPC stops the HTTP RPC handler. This is internal, the caller must hold h.mu.
+func (h *httpServer) disableRPC() bool {
+	handler := h.httpHandler.Load().(*rpcHandler)
+	if handler != nil {
+		h.httpHandler.Store((*rpcHandler)(nil))
+		handler.server.Stop()
+	}
+	return handler != nil
+}
+
+// enableWS turns on JSON-RPC over WebSocket on the server.
+func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	if h.wsAllowed() {
+		return fmt.Errorf("JSON-RPC over WebSocket is already enabled")
+	}
+
+	// Create RPC server and handler.
+	srv := rpc.NewServer()
+	if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil {
+		return err
+	}
+	h.wsConfig = config
+	h.wsHandler.Store(&rpcHandler{
+		Handler: srv.WebsocketHandler(config.Origins),
+		server:  srv,
+	})
+	return nil
+}
+
+// stopWS disables JSON-RPC over WebSocket and also stops the server if it only serves WebSocket.
+func (h *httpServer) stopWS() {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+
+	if h.disableWS() {
+		if !h.rpcAllowed() {
+			h.doStop()
+		}
+	}
+}
+
+// disableWS disables the WebSocket handler. This is internal, the caller must hold h.mu.
+func (h *httpServer) disableWS() bool {
+	ws := h.wsHandler.Load().(*rpcHandler)
+	if ws != nil {
+		h.wsHandler.Store((*rpcHandler)(nil))
+		ws.server.Stop()
+	}
+	return ws != nil
+}
+
+// rpcAllowed returns true when JSON-RPC over HTTP is enabled.
+func (h *httpServer) rpcAllowed() bool {
+	return h.httpHandler.Load().(*rpcHandler) != nil
+}
+
+// wsAllowed returns true when JSON-RPC over WebSocket is enabled.
+func (h *httpServer) wsAllowed() bool {
+	return h.wsHandler.Load().(*rpcHandler) != nil
+}
+
+// isWebsocket checks the header of an http request for a websocket upgrade request.
+func isWebsocket(r *http.Request) bool {
+	return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" &&
+		strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade")
+}
+
 // NewHTTPHandlerStack returns wrapped http-related handlers
 func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler {
 	// Wrap the CORS-handler within a host-handler
@@ -45,8 +377,8 @@ func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler {
 	c := cors.New(cors.Options{
 		AllowedOrigins: allowedOrigins,
 		AllowedMethods: []string{http.MethodPost, http.MethodGet},
-		MaxAge:         600,
 		AllowedHeaders: []string{"*"},
+		MaxAge:         600,
 	})
 	return c.Handler(srv)
 }
@@ -138,22 +470,69 @@ func newGzipHandler(next http.Handler) http.Handler {
 	})
 }
 
-// NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade
-// request to the websocket protocol. If not, serves the the request with the http handler.
-func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if isWebsocket(r) {
-			ws.ServeHTTP(w, r)
-			log.Debug("serving websocket request")
-			return
-		}
+type ipcServer struct {
+	log      log.Logger
+	endpoint string
 
-		h.ServeHTTP(w, r)
-	})
+	mu       sync.Mutex
+	listener net.Listener
+	srv      *rpc.Server
 }
 
-// isWebsocket checks the header of an http request for a websocket upgrade request.
-func isWebsocket(r *http.Request) bool {
-	return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" &&
-		strings.ToLower(r.Header.Get("Connection")) == "upgrade"
+func newIPCServer(log log.Logger, endpoint string) *ipcServer {
+	return &ipcServer{log: log, endpoint: endpoint}
+}
+
+// Start starts the httpServer's http.Server
+func (is *ipcServer) start(apis []rpc.API) error {
+	is.mu.Lock()
+	defer is.mu.Unlock()
+
+	if is.listener != nil {
+		return nil // already running
+	}
+	listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis)
+	if err != nil {
+		is.log.Warn("IPC opening failed", "url", is.endpoint, "error", err)
+		return err
+	}
+	is.log.Info("IPC endpoint opened", "url", is.endpoint)
+	is.listener, is.srv = listener, srv
+	return nil
+}
+
+func (is *ipcServer) stop() error {
+	is.mu.Lock()
+	defer is.mu.Unlock()
+
+	if is.listener == nil {
+		return nil // not running
+	}
+	err := is.listener.Close()
+	is.srv.Stop()
+	is.listener, is.srv = nil, nil
+	is.log.Info("IPC endpoint closed", "url", is.endpoint)
+	return err
+}
+
+// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules,
+// and then registers all of the APIs exposed by the services.
+func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error {
+	if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
+		log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
+	}
+	// Generate the whitelist based on the allowed modules
+	whitelist := make(map[string]bool)
+	for _, module := range modules {
+		whitelist[module] = true
+	}
+	// Register all the APIs exposed by the services
+	for _, api := range apis {
+		if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
+			if err := srv.RegisterName(api.Namespace, api.Service); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
 }
diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go
index 9db03181c9..f92f0ba396 100644
--- a/node/rpcstack_test.go
+++ b/node/rpcstack_test.go
@@ -1,38 +1,293 @@
+// 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 .
+
 package node
 
 import (
+	"bytes"
+	"fmt"
 	"net/http"
-	"net/http/httptest"
+	"net/url"
+	"strconv"
+	"strings"
 	"testing"
 
+	"github.com/ethereum/go-ethereum/internal/testlog"
+	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/gorilla/websocket"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) {
-	srv := rpc.NewServer()
+// TestCorsHandler makes sure CORS are properly handled on the http server.
+func TestCorsHandler(t *testing.T) {
+	srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{})
+	defer srv.stop()
+	url := "http://" + srv.listenAddr()
+
+	resp := rpcRequest(t, url, "origin", "test.com")
+	assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin"))
+
+	resp2 := rpcRequest(t, url, "origin", "bad")
+	assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin"))
+}
+
+// TestVhosts makes sure vhosts are properly handled on the http server.
+func TestVhosts(t *testing.T) {
+	srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{})
+	defer srv.stop()
+	url := "http://" + srv.listenAddr()
+
+	resp := rpcRequest(t, url, "host", "test")
+	assert.Equal(t, resp.StatusCode, http.StatusOK)
+
+	resp2 := rpcRequest(t, url, "host", "bad")
+	assert.Equal(t, resp2.StatusCode, http.StatusForbidden)
+}
+
+type originTest struct {
+	spec    string
+	expOk   []string
+	expFail []string
+}
+
+// splitAndTrim splits input separated by a comma
+// and trims excessive white space from the substrings.
+// Copied over from flags.go
+func splitAndTrim(input string) (ret []string) {
+	l := strings.Split(input, ",")
+	for _, r := range l {
+		r = strings.TrimSpace(r)
+		if len(r) > 0 {
+			ret = append(ret, r)
+		}
+	}
+	return ret
+}
+
+// TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server.
+func TestWebsocketOrigins(t *testing.T) {
+	tests := []originTest{
+		{
+			spec: "*", // allow all
+			expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540",
+				"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
+		},
+		{
+			spec:    "test",
+			expOk:   []string{"http://test", "https://test", "http://test:8540", "https://test:8540"},
+			expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"},
+		},
+		// scheme tests
+		{
+			spec:  "https://test",
+			expOk: []string{"https://test", "https://test:9999"},
+			expFail: []string{
+				"test",                                // no scheme, required by spec
+				"http://test",                         // wrong scheme
+				"http://test.foo", "https://a.test.x", // subdomain variatoins
+				"http://testx:8540", "https://xtest:8540"},
+		},
+		// ip tests
+		{
+			spec:  "https://12.34.56.78",
+			expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"},
+			expFail: []string{
+				"http://12.34.56.78",     // wrong scheme
+				"http://12.34.56.78:443", // wrong scheme
+				"http://1.12.34.56.78",   // wrong 'domain name'
+				"http://12.34.56.78.a",   // wrong 'domain name'
+				"https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"},
+		},
+		// port tests
+		{
+			spec:  "test:8540",
+			expOk: []string{"http://test:8540", "https://test:8540"},
+			expFail: []string{
+				"http://test", "https://test", // spec says port required
+				"http://test:8541", "https://test:8541", // wrong port
+				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
+		},
+		// scheme and port
+		{
+			spec:  "https://test:8540",
+			expOk: []string{"https://test:8540"},
+			expFail: []string{
+				"https://test",                          // missing port
+				"http://test",                           // missing port, + wrong scheme
+				"http://test:8540",                      // wrong scheme
+				"http://test:8541", "https://test:8541", // wrong port
+				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
+		},
+		// several allowed origins
+		{
+			spec: "localhost,http://127.0.0.1",
+			expOk: []string{"localhost", "http://localhost", "https://localhost:8443",
+				"http://127.0.0.1", "http://127.0.0.1:8080"},
+			expFail: []string{
+				"https://127.0.0.1", // wrong scheme
+				"http://bad", "https://bad", "http://bad:8540", "https://bad:8540"},
+		},
+	}
+	for _, tc := range tests {
+		srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)})
+		url := fmt.Sprintf("ws://%v", srv.listenAddr())
+		for _, origin := range tc.expOk {
+			if err := wsRequest(t, url, origin); err != nil {
+				t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err)
+			}
+		}
+		for _, origin := range tc.expFail {
+			if err := wsRequest(t, url, origin); err == nil {
+				t.Errorf("spec '%v', origin '%v': expected not to allow,  got ok", tc.spec, origin)
+			}
+		}
+		srv.stop()
+	}
+}
+
+// TestIsWebsocket tests if an incoming websocket upgrade request is handled properly.
+func TestIsWebsocket(t *testing.T) {
+	r, _ := http.NewRequest("GET", "/", nil)
+
+	assert.False(t, isWebsocket(r))
+	r.Header.Set("upgrade", "websocket")
+	assert.False(t, isWebsocket(r))
+	r.Header.Set("connection", "upgrade")
+	assert.True(t, isWebsocket(r))
+	r.Header.Set("connection", "upgrade,keep-alive")
+	assert.True(t, isWebsocket(r))
+	r.Header.Set("connection", " UPGRADE,keep-alive")
+	assert.True(t, isWebsocket(r))
+}
+
+func Test_checkPath(t *testing.T) {
+	tests := []struct {
+		req      *http.Request
+		prefix   string
+		expected bool
+	}{
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/test"}},
+			prefix:   "/test",
+			expected: true,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/testing"}},
+			prefix:   "/test",
+			expected: true,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/"}},
+			prefix:   "/test",
+			expected: false,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/fail"}},
+			prefix:   "/test",
+			expected: false,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/"}},
+			prefix:   "",
+			expected: true,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/fail"}},
+			prefix:   "",
+			expected: false,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/"}},
+			prefix:   "/",
+			expected: true,
+		},
+		{
+			req:      &http.Request{URL: &url.URL{Path: "/testing"}},
+			prefix:   "/",
+			expected: true,
+		},
+	}
 
-	handler := NewWebsocketUpgradeHandler(nil, srv.WebsocketHandler([]string{}))
-	ts := httptest.NewServer(handler)
-	defer ts.Close()
+	for i, tt := range tests {
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix))
+		})
+	}
+}
+
+func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig) *httpServer {
+	t.Helper()
+
+	srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), rpc.DefaultHTTPTimeouts)
+	assert.NoError(t, srv.enableRPC(nil, *conf))
+	if ws {
+		assert.NoError(t, srv.enableWS(nil, *wsConf))
+	}
+	assert.NoError(t, srv.setListenAddr("localhost", 0))
+	assert.NoError(t, srv.start())
+	return srv
+}
+
+// wsRequest attempts to open a WebSocket connection to the given URL.
+func wsRequest(t *testing.T, url, browserOrigin string) error {
+	t.Helper()
+	t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin)
+
+	headers := make(http.Header)
+	if browserOrigin != "" {
+		headers.Set("Origin", browserOrigin)
+	}
+	conn, _, err := websocket.DefaultDialer.Dial(url, headers)
+	if conn != nil {
+		conn.Close()
+	}
+	return err
+}
 
-	responses := make(chan *http.Response)
-	go func(responses chan *http.Response) {
-		client := &http.Client{}
+// rpcRequest performs a JSON-RPC request to the given URL.
+func rpcRequest(t *testing.T, url string, extraHeaders ...string) *http.Response {
+	t.Helper()
 
-		req, _ := http.NewRequest(http.MethodGet, ts.URL, nil)
-		req.Header.Set("Connection", "upgrade")
-		req.Header.Set("Upgrade", "websocket")
-		req.Header.Set("Sec-WebSocket-Version", "13")
-		req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==")
+	// Create the request.
+	body := bytes.NewReader([]byte(`{"jsonrpc":"2.0","id":1,"method":"rpc_modules","params":[]}`))
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		t.Fatal("could not create http request:", err)
+	}
+	req.Header.Set("content-type", "application/json")
 
-		resp, err := client.Do(req)
-		if err != nil {
-			t.Error("could not issue a GET request to the test http server", err)
+	// Apply extra headers.
+	if len(extraHeaders)%2 != 0 {
+		panic("odd extraHeaders length")
+	}
+	for i := 0; i < len(extraHeaders); i += 2 {
+		key, value := extraHeaders[i], extraHeaders[i+1]
+		if strings.ToLower(key) == "host" {
+			req.Host = value
+		} else {
+			req.Header.Set(key, value)
 		}
-		responses <- resp
-	}(responses)
+	}
 
-	response := <-responses
-	assert.Equal(t, "websocket", response.Header.Get("Upgrade"))
+	// Perform the request.
+	t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders)
+	resp, err := http.DefaultClient.Do(req)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return resp
 }
diff --git a/node/service.go b/node/service.go
deleted file mode 100644
index ef5b995e4b..0000000000
--- a/node/service.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2015 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 .
-
-package node
-
-import (
-	"path/filepath"
-	"reflect"
-
-	"github.com/ethereum/go-ethereum/accounts"
-	"github.com/ethereum/go-ethereum/core/rawdb"
-	"github.com/ethereum/go-ethereum/ethdb"
-	"github.com/ethereum/go-ethereum/event"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rpc"
-)
-
-// ServiceContext is a collection of service independent options inherited from
-// the protocol stack, that is passed to all constructors to be optionally used;
-// as well as utility methods to operate on the service environment.
-type ServiceContext struct {
-	services       map[reflect.Type]Service // Index of the already constructed services
-	Config         Config
-	EventMux       *event.TypeMux    // Event multiplexer used for decoupled notifications
-	AccountManager *accounts.Manager // Account manager created by the node.
-}
-
-// OpenDatabase opens an existing database with the given name (or creates one
-// if no previous can be found) from within the node's data directory. If the
-// node is an ephemeral one, a memory database is returned.
-func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int, namespace string) (ethdb.Database, error) {
-	if ctx.Config.DataDir == "" {
-		return rawdb.NewMemoryDatabase(), nil
-	}
-	return rawdb.NewLevelDBDatabase(ctx.Config.ResolvePath(name), cache, handles, namespace)
-}
-
-// OpenDatabaseWithFreezer opens an existing database with the given name (or
-// creates one if no previous can be found) from within the node's data directory,
-// also attaching a chain freezer to it that moves ancient chain data from the
-// database to immutable append-only files. If the node is an ephemeral one, a
-// memory database is returned.
-func (ctx *ServiceContext) OpenDatabaseWithFreezer(name string, cache int, handles int, freezer string, namespace string) (ethdb.Database, error) {
-	if ctx.Config.DataDir == "" {
-		return rawdb.NewMemoryDatabase(), nil
-	}
-	root := ctx.Config.ResolvePath(name)
-
-	switch {
-	case freezer == "":
-		freezer = filepath.Join(root, "ancient")
-	case !filepath.IsAbs(freezer):
-		freezer = ctx.Config.ResolvePath(freezer)
-	}
-	return rawdb.NewLevelDBDatabaseWithFreezer(root, cache, handles, freezer, namespace)
-}
-
-// ResolvePath resolves a user path into the data directory if that was relative
-// and if the user actually uses persistent storage. It will return an empty string
-// for emphemeral storage and the user's own input for absolute paths.
-func (ctx *ServiceContext) ResolvePath(path string) string {
-	return ctx.Config.ResolvePath(path)
-}
-
-// Service retrieves a currently running service registered of a specific type.
-func (ctx *ServiceContext) Service(service interface{}) error {
-	element := reflect.ValueOf(service).Elem()
-	if running, ok := ctx.services[element.Type()]; ok {
-		element.Set(reflect.ValueOf(running))
-		return nil
-	}
-	return ErrServiceUnknown
-}
-
-// ExtRPCEnabled returns the indicator whether node enables the external
-// RPC(http, ws or graphql).
-func (ctx *ServiceContext) ExtRPCEnabled() bool {
-	return ctx.Config.ExtRPCEnabled()
-}
-
-// ServiceConstructor is the function signature of the constructors needed to be
-// registered for service instantiation.
-type ServiceConstructor func(ctx *ServiceContext) (Service, error)
-
-// Service is an individual protocol that can be registered into a node.
-//
-// Notes:
-//
-// • Service life-cycle management is delegated to the node. The service is allowed to
-// initialize itself upon creation, but no goroutines should be spun up outside of the
-// Start method.
-//
-// • Restart logic is not required as the node will create a fresh instance
-// every time a service is started.
-type Service interface {
-	// Protocols retrieves the P2P protocols the service wishes to start.
-	Protocols() []p2p.Protocol
-
-	// APIs retrieves the list of RPC descriptors the service provides
-	APIs() []rpc.API
-
-	// Start is called after all services have been constructed and the networking
-	// layer was also initialized to spawn any goroutines required by the service.
-	Start(server *p2p.Server) error
-
-	// Stop terminates all goroutines belonging to the service, blocking until they
-	// are all terminated.
-	Stop() error
-}
diff --git a/node/service_test.go b/node/service_test.go
deleted file mode 100644
index 5da8e9e434..0000000000
--- a/node/service_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2015 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 .
-
-package node
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"testing"
-)
-
-// Tests that databases are correctly created persistent or ephemeral based on
-// the configured service context.
-func TestContextDatabases(t *testing.T) {
-	// Create a temporary folder and ensure no database is contained within
-	dir, err := ioutil.TempDir("", "")
-	if err != nil {
-		t.Fatalf("failed to create temporary data directory: %v", err)
-	}
-	defer os.RemoveAll(dir)
-
-	if _, err := os.Stat(filepath.Join(dir, "database")); err == nil {
-		t.Fatalf("non-created database already exists")
-	}
-	// Request the opening/creation of a database and ensure it persists to disk
-	ctx := &ServiceContext{Config: Config{Name: "unit-test", DataDir: dir}}
-	db, err := ctx.OpenDatabase("persistent", 0, 0, "")
-	if err != nil {
-		t.Fatalf("failed to open persistent database: %v", err)
-	}
-	db.Close()
-
-	if _, err := os.Stat(filepath.Join(dir, "unit-test", "persistent")); err != nil {
-		t.Fatalf("persistent database doesn't exists: %v", err)
-	}
-	// Request th opening/creation of an ephemeral database and ensure it's not persisted
-	ctx = &ServiceContext{Config: Config{DataDir: ""}}
-	db, err = ctx.OpenDatabase("ephemeral", 0, 0, "")
-	if err != nil {
-		t.Fatalf("failed to open ephemeral database: %v", err)
-	}
-	db.Close()
-
-	if _, err := os.Stat(filepath.Join(dir, "ephemeral")); err == nil {
-		t.Fatalf("ephemeral database exists")
-	}
-}
-
-// Tests that already constructed services can be retrieves by later ones.
-func TestContextServices(t *testing.T) {
-	stack, err := New(testNodeConfig())
-	if err != nil {
-		t.Fatalf("failed to create protocol stack: %v", err)
-	}
-	defer stack.Close()
-	// Define a verifier that ensures a NoopA is before it and NoopB after
-	verifier := func(ctx *ServiceContext) (Service, error) {
-		var objA *NoopServiceA
-		if ctx.Service(&objA) != nil {
-			return nil, fmt.Errorf("former service not found")
-		}
-		var objB *NoopServiceB
-		if err := ctx.Service(&objB); err != ErrServiceUnknown {
-			return nil, fmt.Errorf("latters lookup error mismatch: have %v, want %v", err, ErrServiceUnknown)
-		}
-		return new(NoopService), nil
-	}
-	// Register the collection of services
-	if err := stack.Register(NewNoopServiceA); err != nil {
-		t.Fatalf("former failed to register service: %v", err)
-	}
-	if err := stack.Register(verifier); err != nil {
-		t.Fatalf("failed to register service verifier: %v", err)
-	}
-	if err := stack.Register(NewNoopServiceB); err != nil {
-		t.Fatalf("latter failed to register service: %v", err)
-	}
-	// Start the protocol stack and ensure services are constructed in order
-	if err := stack.Start(); err != nil {
-		t.Fatalf("failed to start stack: %v", err)
-	}
-	defer stack.Stop()
-}
diff --git a/node/utils_test.go b/node/utils_test.go
index 9801b1ed45..b7474bb706 100644
--- a/node/utils_test.go
+++ b/node/utils_test.go
@@ -20,61 +20,40 @@
 package node
 
 import (
-	"reflect"
-
 	"github.com/ethereum/go-ethereum/p2p"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
-// NoopService is a trivial implementation of the Service interface.
-type NoopService struct{}
+// NoopLifecycle is a trivial implementation of the Service interface.
+type NoopLifecycle struct{}
 
-func (s *NoopService) Protocols() []p2p.Protocol { return nil }
-func (s *NoopService) APIs() []rpc.API           { return nil }
-func (s *NoopService) Start(*p2p.Server) error   { return nil }
-func (s *NoopService) Stop() error               { return nil }
+func (s *NoopLifecycle) Start() error { return nil }
+func (s *NoopLifecycle) Stop() error  { return nil }
 
-func NewNoopService(*ServiceContext) (Service, error) { return new(NoopService), nil }
+func NewNoop() *Noop {
+	noop := new(Noop)
+	return noop
+}
 
-// Set of services all wrapping the base NoopService resulting in the same method
+// Set of services all wrapping the base NoopLifecycle resulting in the same method
 // signatures but different outer types.
-type NoopServiceA struct{ NoopService }
-type NoopServiceB struct{ NoopService }
-type NoopServiceC struct{ NoopService }
-
-func NewNoopServiceA(*ServiceContext) (Service, error) { return new(NoopServiceA), nil }
-func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB), nil }
-func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil }
+type Noop struct{ NoopLifecycle }
 
-// InstrumentedService is an implementation of Service for which all interface
+// InstrumentedService is an implementation of Lifecycle for which all interface
 // methods can be instrumented both return value as well as event hook wise.
 type InstrumentedService struct {
-	protocols []p2p.Protocol
-	apis      []rpc.API
-	start     error
-	stop      error
+	start error
+	stop  error
 
-	protocolsHook func()
-	startHook     func(*p2p.Server)
-	stopHook      func()
-}
-
-func NewInstrumentedService(*ServiceContext) (Service, error) { return new(InstrumentedService), nil }
-
-func (s *InstrumentedService) Protocols() []p2p.Protocol {
-	if s.protocolsHook != nil {
-		s.protocolsHook()
-	}
-	return s.protocols
-}
+	startHook func()
+	stopHook  func()
 
-func (s *InstrumentedService) APIs() []rpc.API {
-	return s.apis
+	protocols []p2p.Protocol
 }
 
-func (s *InstrumentedService) Start(server *p2p.Server) error {
+func (s *InstrumentedService) Start() error {
 	if s.startHook != nil {
-		s.startHook(server)
+		s.startHook()
 	}
 	return s.start
 }
@@ -86,48 +65,49 @@ func (s *InstrumentedService) Stop() error {
 	return s.stop
 }
 
-// InstrumentingWrapper is a method to specialize a service constructor returning
-// a generic InstrumentedService into one returning a wrapping specific one.
-type InstrumentingWrapper func(base ServiceConstructor) ServiceConstructor
-
-func InstrumentingWrapperMaker(base ServiceConstructor, kind reflect.Type) ServiceConstructor {
-	return func(ctx *ServiceContext) (Service, error) {
-		obj, err := base(ctx)
-		if err != nil {
-			return nil, err
-		}
-		wrapper := reflect.New(kind)
-		wrapper.Elem().Field(0).Set(reflect.ValueOf(obj).Elem())
-
-		return wrapper.Interface().(Service), nil
-	}
-}
+type FullService struct{}
 
-// Set of services all wrapping the base InstrumentedService resulting in the
-// same method signatures but different outer types.
-type InstrumentedServiceA struct{ InstrumentedService }
-type InstrumentedServiceB struct{ InstrumentedService }
-type InstrumentedServiceC struct{ InstrumentedService }
+func NewFullService(stack *Node) (*FullService, error) {
+	fs := new(FullService)
 
-func InstrumentedServiceMakerA(base ServiceConstructor) ServiceConstructor {
-	return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceA{}))
+	stack.RegisterProtocols(fs.Protocols())
+	stack.RegisterAPIs(fs.APIs())
+	stack.RegisterLifecycle(fs)
+	return fs, nil
 }
 
-func InstrumentedServiceMakerB(base ServiceConstructor) ServiceConstructor {
-	return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceB{}))
-}
+func (f *FullService) Start() error { return nil }
 
-func InstrumentedServiceMakerC(base ServiceConstructor) ServiceConstructor {
-	return InstrumentingWrapperMaker(base, reflect.TypeOf(InstrumentedServiceC{}))
-}
+func (f *FullService) Stop() error { return nil }
 
-// OneMethodAPI is a single-method API handler to be returned by test services.
-type OneMethodAPI struct {
-	fun func()
+func (f *FullService) Protocols() []p2p.Protocol {
+	return []p2p.Protocol{
+		{
+			Name:    "test1",
+			Version: uint(1),
+		},
+		{
+			Name:    "test2",
+			Version: uint(2),
+		},
+	}
 }
 
-func (api *OneMethodAPI) TheOneMethod() {
-	if api.fun != nil {
-		api.fun()
+func (f *FullService) APIs() []rpc.API {
+	return []rpc.API{
+		{
+			Namespace: "admin",
+			Version:   "1.0",
+		},
+		{
+			Namespace: "debug",
+			Version:   "1.0",
+			Public:    true,
+		},
+		{
+			Namespace: "net",
+			Version:   "1.0",
+			Public:    true,
+		},
 	}
 }
diff --git a/oss-fuzz.sh b/oss-fuzz.sh
new file mode 100644
index 0000000000..a9bac03257
--- /dev/null
+++ b/oss-fuzz.sh
@@ -0,0 +1,123 @@
+#/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).
+
+# This sets the -coverpgk for the coverage report when the corpus is executed through go test
+coverpkg="github.com/ethereum/go-ethereum/..."
+
+function coverbuild {
+  path=$1
+  function=$2
+  fuzzer=$3
+  tags=""
+
+  if [[ $#  -eq 4 ]]; then
+    tags="-tags $4"
+  fi
+  cd $path
+  fuzzed_package=`pwd | rev | cut -d'/' -f 1 | rev`
+  cp $GOPATH/ossfuzz_coverage_runner.go ./"${function,,}"_test.go
+  sed -i -e 's/FuzzFunction/'$function'/' ./"${function,,}"_test.go
+  sed -i -e 's/mypackagebeingfuzzed/'$fuzzed_package'/' ./"${function,,}"_test.go
+  sed -i -e 's/TestFuzzCorpus/Test'$function'Corpus/' ./"${function,,}"_test.go
+
+cat << DOG > $OUT/$fuzzer
+#/bin/sh
+
+  cd $OUT/$path
+  go test -run Test${function}Corpus -v $tags -coverprofile \$1 -coverpkg $coverpkg
+
+DOG
+
+  chmod +x $OUT/$fuzzer
+  #echo "Built script $OUT/$fuzzer"
+  #cat $OUT/$fuzzer
+  cd -
+}
+
+function compile_fuzzer {
+  # Inputs:
+  # $1: The package to fuzz, within go-ethereum
+  # $2: The name of the fuzzing function
+  # $3: The name to give to the final fuzzing-binary
+
+  path=$GOPATH/src/github.com/ethereum/go-ethereum/$1
+  func=$2
+  fuzzer=$3
+
+  echo "Building $fuzzer"
+
+  # Do a coverage-build or a regular build
+  if [[ $SANITIZER = *coverage* ]]; then
+    coverbuild $path $func $fuzzer $coverpkg
+  else
+    (cd $path && \
+        go-fuzz -func $func -o $WORK/$fuzzer.a . && \
+        $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer)
+  fi
+
+  ## Check if there exists a seed corpus file
+  corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip"
+  if [ -f $corpusfile ]
+  then
+    cp $corpusfile $OUT/
+    echo "Found seed corpus: $corpusfile"
+  fi
+}
+
+compile_fuzzer tests/fuzzers/bitutil  Fuzz      fuzzBitutilCompress
+compile_fuzzer tests/fuzzers/bn256    FuzzAdd   fuzzBn256Add
+compile_fuzzer tests/fuzzers/bn256    FuzzMul   fuzzBn256Mul
+compile_fuzzer tests/fuzzers/bn256    FuzzPair  fuzzBn256Pair
+compile_fuzzer tests/fuzzers/runtime  Fuzz      fuzzVmRuntime
+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
+compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty
+compile_fuzzer tests/fuzzers/abi        Fuzz fuzzAbi
+compile_fuzzer tests/fuzzers/les        Fuzz fuzzLes
+compile_fuzzer tests/fuzzers/vflux      FuzzClientPool fuzzClientPool
+
+compile_fuzzer tests/fuzzers/bls12381  FuzzG1Add fuzz_g1_add
+compile_fuzzer tests/fuzzers/bls12381  FuzzG1Mul fuzz_g1_mul
+compile_fuzzer tests/fuzzers/bls12381  FuzzG1MultiExp fuzz_g1_multiexp
+compile_fuzzer tests/fuzzers/bls12381  FuzzG2Add fuzz_g2_add
+compile_fuzzer tests/fuzzers/bls12381  FuzzG2Mul fuzz_g2_mul
+compile_fuzzer tests/fuzzers/bls12381  FuzzG2MultiExp fuzz_g2_multiexp
+compile_fuzzer tests/fuzzers/bls12381  FuzzPairing fuzz_pairing
+compile_fuzzer tests/fuzzers/bls12381  FuzzMapG1 fuzz_map_g1
+compile_fuzzer tests/fuzzers/bls12381  FuzzMapG2 fuzz_map_g2
+
+compile_fuzzer tests/fuzzers/bls12381  FuzzCrossG1Add fuzz_cross_g1_add
+compile_fuzzer tests/fuzzers/bls12381  FuzzCrossG1MultiExp fuzz_cross_g1_multiexp
+compile_fuzzer tests/fuzzers/bls12381  FuzzCrossG2Add fuzz_cross_g2_add
+compile_fuzzer tests/fuzzers/bls12381  FuzzCrossPairing fuzz_cross_pairing
+
+#TODO: move this to tests/fuzzers, if possible
+compile_fuzzer crypto/blake2b  Fuzz      fuzzBlake2b
diff --git a/p2p/dial.go b/p2p/dial.go
index d190e866af..d36d665501 100644
--- a/p2p/dial.go
+++ b/p2p/dial.go
@@ -78,6 +78,7 @@ var (
 	errAlreadyConnected = errors.New("already connected")
 	errRecentlyDialed   = errors.New("recently dialed")
 	errNotWhitelisted   = errors.New("not contained in netrestrict whitelist")
+	errNoPort           = errors.New("node does not provide TCP port")
 )
 
 // dialer creates outbound connections and submits them into Server.
@@ -388,6 +389,12 @@ func (d *dialScheduler) checkDial(n *enode.Node) error {
 	if n.ID() == d.self {
 		return errSelf
 	}
+	if n.IP() != nil && n.TCP() == 0 {
+		// This check can trigger if a non-TCP node is found
+		// by discovery. If there is no IP, the node is a static
+		// node and the actual endpoint will be resolved later in dialTask.
+		return errNoPort
+	}
 	if _, ok := d.dialing[n.ID()]; ok {
 		return errAlreadyDialing
 	}
@@ -474,15 +481,13 @@ type dialError struct {
 }
 
 func (t *dialTask) run(d *dialScheduler) {
-	if t.dest.Incomplete() {
-		if !t.resolve(d) {
-			return
-		}
+	if t.needResolve() && !t.resolve(d) {
+		return
 	}
 
 	err := t.dial(d, t.dest)
 	if err != nil {
-		// Try resolving the ID of static nodes if dialing failed.
+		// For static nodes, resolve one more time if dialing fails.
 		if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 {
 			if t.resolve(d) {
 				t.dial(d, t.dest)
@@ -491,6 +496,10 @@ func (t *dialTask) run(d *dialScheduler) {
 	}
 }
 
+func (t *dialTask) needResolve() bool {
+	return t.flags&staticDialedConn != 0 && t.dest.IP() == nil
+}
+
 // resolve attempts to find the current endpoint for the destination
 // using discovery.
 //
diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go
index 40b271e6d9..9ab4a71ce7 100644
--- a/p2p/discover/lookup.go
+++ b/p2p/discover/lookup.go
@@ -104,9 +104,7 @@ func (it *lookup) startQueries() bool {
 
 	// The first query returns nodes from the local table.
 	if it.queries == -1 {
-		it.tab.mutex.Lock()
-		closest := it.tab.closest(it.result.target, bucketSize, false)
-		it.tab.mutex.Unlock()
+		closest := it.tab.findnodeByID(it.result.target, bucketSize, false)
 		// Avoid finishing the lookup too quickly if table is empty. It'd be better to wait
 		// for the table to fill in this case, but there is no good mechanism for that
 		// yet.
@@ -150,11 +148,14 @@ func (it *lookup) query(n *node, reply chan<- []*node) {
 	} else if len(r) == 0 {
 		fails++
 		it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails)
-		it.tab.log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "results", len(r), "err", err)
-		if fails >= maxFindnodeFailures {
-			it.tab.log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails)
+		// Remove the node from the local table if it fails to return anything useful too
+		// many times, but only if there are enough other nodes in the bucket.
+		dropped := false
+		if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 {
+			dropped = true
 			it.tab.delete(n)
 		}
+		it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err)
 	} else if fails > 0 {
 		// Reset failure counter because it counts _consecutive_ failures.
 		it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0)
diff --git a/p2p/discover/node.go b/p2p/discover/node.go
index 230638b6d1..9ffe101ccf 100644
--- a/p2p/discover/node.go
+++ b/p2p/discover/node.go
@@ -46,7 +46,10 @@ func encodePubkey(key *ecdsa.PublicKey) encPubkey {
 	return e
 }
 
-func decodePubkey(curve elliptic.Curve, e encPubkey) (*ecdsa.PublicKey, error) {
+func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
+	if len(e) != len(encPubkey{}) {
+		return nil, errors.New("wrong size public key data")
+	}
 	p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
 	half := len(e) / 2
 	p.X.SetBytes(e[:half])
@@ -61,17 +64,6 @@ func (e encPubkey) id() enode.ID {
 	return enode.ID(crypto.Keccak256Hash(e[:]))
 }
 
-// recoverNodeKey computes the public key used to sign the
-// given hash from the signature.
-func recoverNodeKey(hash, sig []byte) (key encPubkey, err error) {
-	pubkey, err := crypto.Ecrecover(hash, sig)
-	if err != nil {
-		return key, err
-	}
-	copy(key[:], pubkey[1:])
-	return key, nil
-}
-
 func wrapNode(n *enode.Node) *node {
 	return &node{Node: *n}
 }
diff --git a/p2p/discover/table.go b/p2p/discover/table.go
index 6d48ab00cd..d08f8a6c69 100644
--- a/p2p/discover/table.go
+++ b/p2p/discover/table.go
@@ -377,7 +377,7 @@ func (tab *Table) nextRevalidateTime() time.Duration {
 }
 
 // copyLiveNodes adds nodes from the table to the database if they have been in the table
-// longer then minTableTime.
+// longer than seedMinTableTime.
 func (tab *Table) copyLiveNodes() {
 	tab.mutex.Lock()
 	defer tab.mutex.Unlock()
@@ -392,22 +392,35 @@ func (tab *Table) copyLiveNodes() {
 	}
 }
 
-// closest returns the n nodes in the table that are closest to the
-// given id. The caller must hold tab.mutex.
-func (tab *Table) closest(target enode.ID, nresults int, checklive bool) *nodesByDistance {
-	// This is a very wasteful way to find the closest nodes but
-	// obviously correct. I believe that tree-based buckets would make
-	// this easier to implement efficiently.
-	close := &nodesByDistance{target: target}
+// findnodeByID returns the n nodes in the table that are closest to the given id.
+// This is used by the FINDNODE/v4 handler.
+//
+// The preferLive parameter says whether the caller wants liveness-checked results. If
+// preferLive is true and the table contains any verified nodes, the result will not
+// contain unverified nodes. However, if there are no verified nodes at all, the result
+// will contain unverified nodes.
+func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	// Scan all buckets. There might be a better way to do this, but there aren't that many
+	// buckets, so this solution should be fine. The worst-case complexity of this loop
+	// is O(tab.len() * nresults).
+	nodes := &nodesByDistance{target: target}
+	liveNodes := &nodesByDistance{target: target}
 	for _, b := range &tab.buckets {
 		for _, n := range b.entries {
-			if checklive && n.livenessChecks == 0 {
-				continue
+			nodes.push(n, nresults)
+			if preferLive && n.livenessChecks > 0 {
+				liveNodes.push(n, nresults)
 			}
-			close.push(n, nresults)
 		}
 	}
-	return close
+
+	if preferLive && len(liveNodes.entries) > 0 {
+		return liveNodes
+	}
+	return nodes
 }
 
 // len returns the number of nodes in the table.
@@ -421,6 +434,14 @@ func (tab *Table) len() (n int) {
 	return n
 }
 
+// bucketLen returns the number of nodes in the bucket for the given ID.
+func (tab *Table) bucketLen(id enode.ID) int {
+	tab.mutex.Lock()
+	defer tab.mutex.Unlock()
+
+	return len(tab.bucket(id).entries)
+}
+
 // bucket returns the bucket for the given node ID hash.
 func (tab *Table) bucket(id enode.ID) *bucket {
 	d := enode.LogDist(tab.self().ID(), id)
@@ -520,6 +541,9 @@ func (tab *Table) delete(node *node) {
 }
 
 func (tab *Table) addIP(b *bucket, ip net.IP) bool {
+	if len(ip) == 0 {
+		return false // Nodes without IP cannot be added.
+	}
 	if netutil.IsLAN(ip) {
 		return true
 	}
diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go
index 895c284b27..5f40c967fd 100644
--- a/p2p/discover/table_test.go
+++ b/p2p/discover/table_test.go
@@ -58,7 +58,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
 
 	// Fill up the sender's bucket.
 	pingKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
-	pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{}, 99, 99))
+	pingSender := wrapNode(enode.NewV4(&pingKey.PublicKey, net.IP{127, 0, 0, 1}, 99, 99))
 	last := fillBucket(tab, pingSender)
 
 	// Add the sender as if it just pinged us. Revalidate should replace the last node in
@@ -190,7 +190,7 @@ func checkIPLimitInvariant(t *testing.T, tab *Table) {
 	}
 }
 
-func TestTable_closest(t *testing.T) {
+func TestTable_findnodeByID(t *testing.T) {
 	t.Parallel()
 
 	test := func(test *closeTest) bool {
@@ -202,7 +202,7 @@ func TestTable_closest(t *testing.T) {
 		fillTable(tab, test.All)
 
 		// check that closest(Target, N) returns nodes
-		result := tab.closest(test.Target, test.N, false).entries
+		result := tab.findnodeByID(test.Target, test.N, false).entries
 		if hasDuplicates(result) {
 			t.Errorf("result contains duplicates")
 			return false
diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go
index 44b62e751b..47a2e7ac3c 100644
--- a/p2p/discover/table_util_test.go
+++ b/p2p/discover/table_util_test.go
@@ -146,7 +146,6 @@ func (t *pingRecorder) updateRecord(n *enode.Node) {
 func (t *pingRecorder) Self() *enode.Node           { return nullNode }
 func (t *pingRecorder) lookupSelf() []*enode.Node   { return nil }
 func (t *pingRecorder) lookupRandom() []*enode.Node { return nil }
-func (t *pingRecorder) close()                      {}
 
 // ping simulates a ping request.
 func (t *pingRecorder) ping(n *enode.Node) (seq uint64, err error) {
@@ -188,15 +187,16 @@ func hasDuplicates(slice []*node) bool {
 	return false
 }
 
+// checkNodesEqual checks whether the two given node lists contain the same nodes.
 func checkNodesEqual(got, want []*enode.Node) error {
 	if len(got) == len(want) {
 		for i := range got {
 			if !nodeEqual(got[i], want[i]) {
 				goto NotEqual
 			}
-			return nil
 		}
 	}
+	return nil
 
 NotEqual:
 	output := new(bytes.Buffer)
@@ -227,6 +227,7 @@ func sortedByDistanceTo(distbase enode.ID, slice []*node) bool {
 	})
 }
 
+// hexEncPrivkey decodes h as a private key.
 func hexEncPrivkey(h string) *ecdsa.PrivateKey {
 	b, err := hex.DecodeString(h)
 	if err != nil {
@@ -239,6 +240,7 @@ func hexEncPrivkey(h string) *ecdsa.PrivateKey {
 	return key
 }
 
+// hexEncPubkey decodes h as a public key.
 func hexEncPubkey(h string) (ret encPubkey) {
 	b, err := hex.DecodeString(h)
 	if err != nil {
diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go
index 83480d35e8..a00de9ca18 100644
--- a/p2p/discover/v4_lookup_test.go
+++ b/p2p/discover/v4_lookup_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
 )
@@ -33,7 +34,7 @@ func TestUDPv4_Lookup(t *testing.T) {
 	test := newUDPTest(t)
 
 	// Lookup on empty table returns no nodes.
-	targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target)
+	targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target[:])
 	if results := test.udp.LookupPubkey(targetKey); len(results) > 0 {
 		t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
 	}
@@ -135,15 +136,15 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) {
 
 func serveTestnet(test *udpTest, testnet *preminedTestnet) {
 	for done := false; !done; {
-		done = test.waitPacketOut(func(p packetV4, to *net.UDPAddr, hash []byte) {
+		done = test.waitPacketOut(func(p v4wire.Packet, to *net.UDPAddr, hash []byte) {
 			n, key := testnet.nodeByAddr(to)
 			switch p.(type) {
-			case *pingV4:
-				test.packetInFrom(nil, key, to, &pongV4{Expiration: futureExp, ReplyTok: hash})
-			case *findnodeV4:
+			case *v4wire.Ping:
+				test.packetInFrom(nil, key, to, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash})
+			case *v4wire.Findnode:
 				dist := enode.LogDist(n.ID(), testnet.target.id())
 				nodes := testnet.nodesAtDistance(dist - 1)
-				test.packetInFrom(nil, key, to, &neighborsV4{Expiration: futureExp, Nodes: nodes})
+				test.packetInFrom(nil, key, to, &v4wire.Neighbors{Expiration: futureExp, Nodes: nodes})
 			}
 		})
 	}
@@ -270,25 +271,29 @@ func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.Pr
 	return tn.node(dist, index), key
 }
 
-func (tn *preminedTestnet) nodesAtDistance(dist int) []rpcNode {
-	result := make([]rpcNode, len(tn.dists[dist]))
+func (tn *preminedTestnet) nodesAtDistance(dist int) []v4wire.Node {
+	result := make([]v4wire.Node, len(tn.dists[dist]))
 	for i := range result {
 		result[i] = nodeToRPC(wrapNode(tn.node(dist, i)))
 	}
 	return result
 }
 
-func (tn *preminedTestnet) neighborsAtDistance(base *enode.Node, distance uint, elems int) []*enode.Node {
-	nodes := nodesByDistance{target: base.ID()}
+func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []uint, elems int) []*enode.Node {
+	var result []*enode.Node
 	for d := range lookupTestnet.dists {
 		for i := range lookupTestnet.dists[d] {
 			n := lookupTestnet.node(d, i)
-			if uint(enode.LogDist(n.ID(), base.ID())) == distance {
-				nodes.push(wrapNode(n), elems)
+			d := enode.LogDist(base.ID(), n.ID())
+			if containsUint(uint(d), distances) {
+				result = append(result, n)
+				if len(result) >= elems {
+					return result
+				}
 			}
 		}
 	}
-	return unwrapNodes(nodes.entries)
+	return result
 }
 
 func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) {
diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go
index 6af05f93dd..ad23eee6b4 100644
--- a/p2p/discover/v4_udp.go
+++ b/p2p/discover/v4_udp.go
@@ -31,16 +31,14 @@ import (
 
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
 	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/p2p/netutil"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
 // Errors
 var (
-	errPacketTooSmall   = errors.New("too small")
-	errBadHash          = errors.New("bad hash")
 	errExpired          = errors.New("expired")
 	errUnsolicitedReply = errors.New("unsolicited reply")
 	errUnknownNode      = errors.New("unknown node")
@@ -66,135 +64,6 @@ const (
 	maxPacketSize = 1280
 )
 
-// RPC packet types
-const (
-	p_pingV4 = iota + 1 // zero is 'reserved'
-	p_pongV4
-	p_findnodeV4
-	p_neighborsV4
-	p_enrRequestV4
-	p_enrResponseV4
-)
-
-// RPC request structures
-type (
-	pingV4 struct {
-		senderKey *ecdsa.PublicKey // filled in by preverify
-
-		Version    uint
-		From, To   rpcEndpoint
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// pongV4 is the reply to pingV4.
-	pongV4 struct {
-		// This field should mirror the UDP envelope address
-		// of the ping packet, which provides a way to discover the
-		// the external address (after NAT).
-		To rpcEndpoint
-
-		ReplyTok   []byte // This contains the hash of the ping packet.
-		Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// findnodeV4 is a query for nodes close to the given target.
-	findnodeV4 struct {
-		Target     encPubkey
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// neighborsV4 is the reply to findnodeV4.
-	neighborsV4 struct {
-		Nodes      []rpcNode
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// enrRequestV4 queries for the remote node's record.
-	enrRequestV4 struct {
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// enrResponseV4 is the reply to enrRequestV4.
-	enrResponseV4 struct {
-		ReplyTok []byte // Hash of the enrRequest packet.
-		Record   enr.Record
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	rpcNode struct {
-		IP  net.IP // len 4 for IPv4 or 16 for IPv6
-		UDP uint16 // for discovery protocol
-		TCP uint16 // for RLPx protocol
-		ID  encPubkey
-	}
-
-	rpcEndpoint struct {
-		IP  net.IP // len 4 for IPv4 or 16 for IPv6
-		UDP uint16 // for discovery protocol
-		TCP uint16 // for RLPx protocol
-	}
-)
-
-// packetV4 is implemented by all v4 protocol messages.
-type packetV4 interface {
-	// preverify checks whether the packet is valid and should be handled at all.
-	preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error
-	// handle handles the packet.
-	handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte)
-	// packet name and type for logging purposes.
-	name() string
-	kind() byte
-}
-
-func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
-	ip := net.IP{}
-	if ip4 := addr.IP.To4(); ip4 != nil {
-		ip = ip4
-	} else if ip6 := addr.IP.To16(); ip6 != nil {
-		ip = ip6
-	}
-	return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
-}
-
-func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) {
-	if rn.UDP <= 1024 {
-		return nil, errors.New("low port")
-	}
-	if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
-		return nil, err
-	}
-	if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
-		return nil, errors.New("not contained in netrestrict whitelist")
-	}
-	key, err := decodePubkey(crypto.S256(), rn.ID)
-	if err != nil {
-		return nil, err
-	}
-	n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
-	err = n.ValidateComplete()
-	return n, err
-}
-
-func nodeToRPC(n *node) rpcNode {
-	var key ecdsa.PublicKey
-	var ekey encPubkey
-	if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
-		ekey = encodePubkey(&key)
-	}
-	return rpcNode{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
-}
-
 // UDPv4 implements the v4 wire protocol.
 type UDPv4 struct {
 	conn        UDPConn
@@ -243,16 +112,16 @@ type replyMatcher struct {
 
 	// reply contains the most recent reply. This field is safe for reading after errc has
 	// received a value.
-	reply packetV4
+	reply v4wire.Packet
 }
 
-type replyMatchFunc func(interface{}) (matched bool, requestDone bool)
+type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
 
 // reply is a reply packet from a certain node.
 type reply struct {
 	from enode.ID
 	ip   net.IP
-	data packetV4
+	data v4wire.Packet
 	// loop indicates whether there was
 	// a matching request by sending on this channel.
 	matched chan<- bool
@@ -332,10 +201,10 @@ func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
 	return n
 }
 
-func (t *UDPv4) ourEndpoint() rpcEndpoint {
+func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
 	n := t.Self()
 	a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
-	return makeEndpoint(a, uint16(n.TCP()))
+	return v4wire.NewEndpoint(a, uint16(n.TCP()))
 }
 
 // Ping sends a ping message to the given node.
@@ -348,7 +217,7 @@ func (t *UDPv4) Ping(n *enode.Node) error {
 func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
 	rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
 	if err = <-rm.errc; err == nil {
-		seq = seqFromTail(rm.reply.(*pongV4).Rest)
+		seq = rm.reply.(*v4wire.Pong).ENRSeq()
 	}
 	return seq, err
 }
@@ -357,7 +226,7 @@ func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
 // when the reply arrives.
 func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
 	req := t.makePing(toaddr)
-	packet, hash, err := t.encode(t.priv, req)
+	packet, hash, err := v4wire.Encode(t.priv, req)
 	if err != nil {
 		errc := make(chan error, 1)
 		errc <- err
@@ -365,8 +234,8 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
 	}
 	// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
 	// reference the ping we're about to send.
-	rm := t.pending(toid, toaddr.IP, p_pongV4, func(p interface{}) (matched bool, requestDone bool) {
-		matched = bytes.Equal(p.(*pongV4).ReplyTok, hash)
+	rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
+		matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
 		if matched && callback != nil {
 			callback()
 		}
@@ -374,16 +243,16 @@ func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *r
 	})
 	// Send the packet.
 	t.localNode.UDPContact(toaddr)
-	t.write(toaddr, toid, req.name(), packet)
+	t.write(toaddr, toid, req.Name(), packet)
 	return rm
 }
 
-func (t *UDPv4) makePing(toaddr *net.UDPAddr) *pingV4 {
+func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
 	seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq())
-	return &pingV4{
+	return &v4wire.Ping{
 		Version:    4,
 		From:       t.ourEndpoint(),
-		To:         makeEndpoint(toaddr, 0),
+		To:         v4wire.NewEndpoint(toaddr, 0),
 		Expiration: uint64(time.Now().Add(expiration).Unix()),
 		Rest:       []rlp.RawValue{seq},
 	}
@@ -422,23 +291,24 @@ func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
 
 func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
 	target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
+	ekey := v4wire.Pubkey(targetKey)
 	it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
-		return t.findnode(n.ID(), n.addr(), targetKey)
+		return t.findnode(n.ID(), n.addr(), ekey)
 	})
 	return it
 }
 
 // findnode sends a findnode request to the given node and waits until
 // the node has sent up to k neighbors.
-func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) ([]*node, error) {
+func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
 	t.ensureBond(toid, toaddr)
 
 	// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
 	// active until enough nodes have been received.
 	nodes := make([]*node, 0, bucketSize)
 	nreceived := 0
-	rm := t.pending(toid, toaddr.IP, p_neighborsV4, func(r interface{}) (matched bool, requestDone bool) {
-		reply := r.(*neighborsV4)
+	rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
+		reply := r.(*v4wire.Neighbors)
 		for _, rn := range reply.Nodes {
 			nreceived++
 			n, err := t.nodeFromRPC(toaddr, rn)
@@ -450,11 +320,20 @@ func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target encPubkey) (
 		}
 		return true, nreceived >= bucketSize
 	})
-	t.send(toaddr, toid, &findnodeV4{
+	t.send(toaddr, toid, &v4wire.Findnode{
 		Target:     target,
 		Expiration: uint64(time.Now().Add(expiration).Unix()),
 	})
-	return nodes, <-rm.errc
+	// Ensure that callers don't see a timeout if the node actually responded. Since
+	// findnode can receive more than one neighbors response, the reply matcher will be
+	// active until the remote node sends enough nodes. If the remote end doesn't have
+	// enough nodes the reply matcher will time out waiting for the second reply, but
+	// there's no need for an error in that case.
+	err := <-rm.errc
+	if err == errTimeout && rm.reply != nil {
+		err = nil
+	}
+	return nodes, err
 }
 
 // RequestENR sends enrRequest to the given node and waits for a response.
@@ -462,26 +341,27 @@ func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
 	addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
 	t.ensureBond(n.ID(), addr)
 
-	req := &enrRequestV4{
+	req := &v4wire.ENRRequest{
 		Expiration: uint64(time.Now().Add(expiration).Unix()),
 	}
-	packet, hash, err := t.encode(t.priv, req)
+	packet, hash, err := v4wire.Encode(t.priv, req)
 	if err != nil {
 		return nil, err
 	}
+
 	// Add a matcher for the reply to the pending reply queue. Responses are matched if
 	// they reference the request we're about to send.
-	rm := t.pending(n.ID(), addr.IP, p_enrResponseV4, func(r interface{}) (matched bool, requestDone bool) {
-		matched = bytes.Equal(r.(*enrResponseV4).ReplyTok, hash)
+	rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
+		matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
 		return matched, matched
 	})
 	// Send the packet and wait for the reply.
-	t.write(addr, n.ID(), req.name(), packet)
+	t.write(addr, n.ID(), req.Name(), packet)
 	if err := <-rm.errc; err != nil {
 		return nil, err
 	}
 	// Verify the response record.
-	respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*enrResponseV4).Record)
+	respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*v4wire.ENRResponse).Record)
 	if err != nil {
 		return nil, err
 	}
@@ -513,7 +393,7 @@ func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchF
 
 // handleReply dispatches a reply packet, invoking reply matchers. It returns
 // whether any matcher considered the packet acceptable.
-func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req packetV4) bool {
+func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
 	matched := make(chan bool, 1)
 	select {
 	case t.gotreply <- reply{from, fromIP, req, matched}:
@@ -579,12 +459,12 @@ func (t *UDPv4) loop() {
 			var matched bool // whether any replyMatcher considered the reply acceptable.
 			for el := plist.Front(); el != nil; el = el.Next() {
 				p := el.Value.(*replyMatcher)
-				if p.from == r.from && p.ptype == r.data.kind() && p.ip.Equal(r.ip) {
+				if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
 					ok, requestDone := p.callback(r.data)
 					matched = matched || ok
+					p.reply = r.data
 					// Remove the matcher if callback indicates that all replies have been received.
 					if requestDone {
-						p.reply = r.data
 						p.errc <- nil
 						plist.Remove(el)
 					}
@@ -618,44 +498,12 @@ func (t *UDPv4) loop() {
 	}
 }
 
-const (
-	macSize  = 256 / 8
-	sigSize  = 520 / 8
-	headSize = macSize + sigSize // space of packet frame data
-)
-
-var (
-	headSpace = make([]byte, headSize)
-
-	// Neighbors replies are sent across multiple packets to
-	// stay below the packet size limit. We compute the maximum number
-	// of entries by stuffing a packet until it grows too large.
-	maxNeighbors int
-)
-
-func init() {
-	p := neighborsV4{Expiration: ^uint64(0)}
-	maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
-	for n := 0; ; n++ {
-		p.Nodes = append(p.Nodes, maxSizeNode)
-		size, _, err := rlp.EncodeToReader(p)
-		if err != nil {
-			// If this ever happens, it will be caught by the unit tests.
-			panic("cannot encode: " + err.Error())
-		}
-		if headSize+size+1 >= maxPacketSize {
-			maxNeighbors = n
-			break
-		}
-	}
-}
-
-func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req packetV4) ([]byte, error) {
-	packet, hash, err := t.encode(t.priv, req)
+func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
+	packet, hash, err := v4wire.Encode(t.priv, req)
 	if err != nil {
 		return hash, err
 	}
-	return hash, t.write(toaddr, toid, req.name(), packet)
+	return hash, t.write(toaddr, toid, req.Name(), packet)
 }
 
 func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
@@ -664,30 +512,6 @@ func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []
 	return err
 }
 
-func (t *UDPv4) encode(priv *ecdsa.PrivateKey, req packetV4) (packet, hash []byte, err error) {
-	name := req.name()
-	b := new(bytes.Buffer)
-	b.Write(headSpace)
-	b.WriteByte(req.kind())
-	if err := rlp.Encode(b, req); err != nil {
-		t.log.Error(fmt.Sprintf("Can't encode %s packet", name), "err", err)
-		return nil, nil, err
-	}
-	packet = b.Bytes()
-	sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
-	if err != nil {
-		t.log.Error(fmt.Sprintf("Can't sign %s packet", name), "err", err)
-		return nil, nil, err
-	}
-	copy(packet[macSize:], sig)
-	// add the hash to the front. Note: this doesn't protect the
-	// packet in any way. Our public key will be part of this hash in
-	// The future.
-	hash = crypto.Keccak256(packet[macSize:])
-	copy(packet, hash)
-	return packet, hash, nil
-}
-
 // readLoop runs in its own goroutine. it handles incoming UDP packets.
 func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
 	defer t.wg.Done()
@@ -719,58 +543,23 @@ func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
 }
 
 func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
-	packet, fromKey, hash, err := decodeV4(buf)
+	rawpacket, fromKey, hash, err := v4wire.Decode(buf)
 	if err != nil {
 		t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
 		return err
 	}
-	fromID := fromKey.id()
-	if err == nil {
-		err = packet.preverify(t, from, fromID, fromKey)
+	packet := t.wrapPacket(rawpacket)
+	fromID := fromKey.ID()
+	if err == nil && packet.preverify != nil {
+		err = packet.preverify(packet, from, fromID, fromKey)
 	}
-	t.log.Trace("<< "+packet.name(), "id", fromID, "addr", from, "err", err)
-	if err == nil {
-		packet.handle(t, from, fromID, hash)
+	t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err)
+	if err == nil && packet.handle != nil {
+		packet.handle(packet, from, fromID, hash)
 	}
 	return err
 }
 
-func decodeV4(buf []byte) (packetV4, encPubkey, []byte, error) {
-	if len(buf) < headSize+1 {
-		return nil, encPubkey{}, nil, errPacketTooSmall
-	}
-	hash, sig, sigdata := buf[:macSize], buf[macSize:headSize], buf[headSize:]
-	shouldhash := crypto.Keccak256(buf[macSize:])
-	if !bytes.Equal(hash, shouldhash) {
-		return nil, encPubkey{}, nil, errBadHash
-	}
-	fromKey, err := recoverNodeKey(crypto.Keccak256(buf[headSize:]), sig)
-	if err != nil {
-		return nil, fromKey, hash, err
-	}
-
-	var req packetV4
-	switch ptype := sigdata[0]; ptype {
-	case p_pingV4:
-		req = new(pingV4)
-	case p_pongV4:
-		req = new(pongV4)
-	case p_findnodeV4:
-		req = new(findnodeV4)
-	case p_neighborsV4:
-		req = new(neighborsV4)
-	case p_enrRequestV4:
-		req = new(enrRequestV4)
-	case p_enrResponseV4:
-		req = new(enrResponseV4)
-	default:
-		return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
-	}
-	s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
-	err = s.Decode(req)
-	return req, fromKey, hash, err
-}
-
 // checkBond checks if the given node has a recent enough endpoint proof.
 func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
 	return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
@@ -788,49 +577,99 @@ func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
 	}
 }
 
-// expired checks whether the given UNIX time stamp is in the past.
-func expired(ts uint64) bool {
-	return time.Unix(int64(ts), 0).Before(time.Now())
+func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
+	if rn.UDP <= 1024 {
+		return nil, errLowPort
+	}
+	if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
+		return nil, err
+	}
+	if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
+		return nil, errors.New("not contained in netrestrict whitelist")
+	}
+	key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID)
+	if err != nil {
+		return nil, err
+	}
+	n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
+	err = n.ValidateComplete()
+	return n, err
 }
 
-func seqFromTail(tail []rlp.RawValue) uint64 {
-	if len(tail) == 0 {
-		return 0
-	}
-	var seq uint64
-	rlp.DecodeBytes(tail[0], &seq)
-	return seq
+func nodeToRPC(n *node) v4wire.Node {
+	var key ecdsa.PublicKey
+	var ekey v4wire.Pubkey
+	if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
+		ekey = v4wire.EncodePubkey(&key)
+	}
+	return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
+}
+
+// wrapPacket returns the handler functions applicable to a packet.
+func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 {
+	var h packetHandlerV4
+	h.Packet = p
+	switch p.(type) {
+	case *v4wire.Ping:
+		h.preverify = t.verifyPing
+		h.handle = t.handlePing
+	case *v4wire.Pong:
+		h.preverify = t.verifyPong
+	case *v4wire.Findnode:
+		h.preverify = t.verifyFindnode
+		h.handle = t.handleFindnode
+	case *v4wire.Neighbors:
+		h.preverify = t.verifyNeighbors
+	case *v4wire.ENRRequest:
+		h.preverify = t.verifyENRRequest
+		h.handle = t.handleENRRequest
+	case *v4wire.ENRResponse:
+		h.preverify = t.verifyENRResponse
+	}
+	return &h
+}
+
+// packetHandlerV4 wraps a packet with handler functions.
+type packetHandlerV4 struct {
+	v4wire.Packet
+	senderKey *ecdsa.PublicKey // used for ping
+
+	// preverify checks whether the packet is valid and should be handled at all.
+	preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
+	// handle handles the packet.
+	handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
 }
 
 // PING/v4
 
-func (req *pingV4) name() string { return "PING/v4" }
-func (req *pingV4) kind() byte   { return p_pingV4 }
+func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Ping)
 
-func (req *pingV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error {
-	if expired(req.Expiration) {
-		return errExpired
-	}
-	key, err := decodePubkey(crypto.S256(), fromKey)
+	senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey)
 	if err != nil {
-		return errors.New("invalid public key")
+		return err
+	}
+	if v4wire.Expired(req.Expiration) {
+		return errExpired
 	}
-	req.senderKey = key
+	h.senderKey = senderKey
 	return nil
 }
 
-func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+	req := h.Packet.(*v4wire.Ping)
+
 	// Reply.
 	seq, _ := rlp.EncodeToBytes(t.localNode.Node().Seq())
-	t.send(from, fromID, &pongV4{
-		To:         makeEndpoint(from, req.From.TCP),
+	t.send(from, fromID, &v4wire.Pong{
+		To:         v4wire.NewEndpoint(from, req.From.TCP),
 		ReplyTok:   mac,
 		Expiration: uint64(time.Now().Add(expiration).Unix()),
 		Rest:       []rlp.RawValue{seq},
 	})
 
 	// Ping back if our last pong on file is too far in the past.
-	n := wrapNode(enode.NewV4(req.senderKey, from.IP, int(req.From.TCP), from.Port))
+	n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
 	if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
 		t.sendPing(fromID, from, func() {
 			t.tab.addVerifiedNode(n)
@@ -846,31 +685,26 @@ func (req *pingV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []by
 
 // PONG/v4
 
-func (req *pongV4) name() string { return "PONG/v4" }
-func (req *pongV4) kind() byte   { return p_pongV4 }
+func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Pong)
 
-func (req *pongV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error {
-	if expired(req.Expiration) {
+	if v4wire.Expired(req.Expiration) {
 		return errExpired
 	}
 	if !t.handleReply(fromID, from.IP, req) {
 		return errUnsolicitedReply
 	}
-	return nil
-}
-
-func (req *pongV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
 	t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
 	t.db.UpdateLastPongReceived(fromID, from.IP, time.Now())
+	return nil
 }
 
 // FINDNODE/v4
 
-func (req *findnodeV4) name() string { return "FINDNODE/v4" }
-func (req *findnodeV4) kind() byte   { return p_findnodeV4 }
+func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Findnode)
 
-func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error {
-	if expired(req.Expiration) {
+	if v4wire.Expired(req.Expiration) {
 		return errExpired
 	}
 	if !t.checkBond(fromID, from.IP) {
@@ -885,22 +719,22 @@ func (req *findnodeV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, f
 	return nil
 }
 
-func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+	req := h.Packet.(*v4wire.Findnode)
+
 	// Determine closest nodes.
 	target := enode.ID(crypto.Keccak256Hash(req.Target[:]))
-	t.tab.mutex.Lock()
-	closest := t.tab.closest(target, bucketSize, true).entries
-	t.tab.mutex.Unlock()
+	closest := t.tab.findnodeByID(target, bucketSize, true).entries
 
 	// Send neighbors in chunks with at most maxNeighbors per packet
 	// to stay below the packet size limit.
-	p := neighborsV4{Expiration: uint64(time.Now().Add(expiration).Unix())}
+	p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
 	var sent bool
 	for _, n := range closest {
 		if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
 			p.Nodes = append(p.Nodes, nodeToRPC(n))
 		}
-		if len(p.Nodes) == maxNeighbors {
+		if len(p.Nodes) == v4wire.MaxNeighbors {
 			t.send(from, fromID, &p)
 			p.Nodes = p.Nodes[:0]
 			sent = true
@@ -913,29 +747,24 @@ func (req *findnodeV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac
 
 // NEIGHBORS/v4
 
-func (req *neighborsV4) name() string { return "NEIGHBORS/v4" }
-func (req *neighborsV4) kind() byte   { return p_neighborsV4 }
+func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.Neighbors)
 
-func (req *neighborsV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error {
-	if expired(req.Expiration) {
+	if v4wire.Expired(req.Expiration) {
 		return errExpired
 	}
-	if !t.handleReply(fromID, from.IP, req) {
+	if !t.handleReply(fromID, from.IP, h.Packet) {
 		return errUnsolicitedReply
 	}
 	return nil
 }
 
-func (req *neighborsV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
-}
-
 // ENRREQUEST/v4
 
-func (req *enrRequestV4) name() string { return "ENRREQUEST/v4" }
-func (req *enrRequestV4) kind() byte   { return p_enrRequestV4 }
+func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	req := h.Packet.(*v4wire.ENRRequest)
 
-func (req *enrRequestV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error {
-	if expired(req.Expiration) {
+	if v4wire.Expired(req.Expiration) {
 		return errExpired
 	}
 	if !t.checkBond(fromID, from.IP) {
@@ -944,8 +773,8 @@ func (req *enrRequestV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID,
 	return nil
 }
 
-func (req *enrRequestV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
-	t.send(from, fromID, &enrResponseV4{
+func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
+	t.send(from, fromID, &v4wire.ENRResponse{
 		ReplyTok: mac,
 		Record:   *t.localNode.Node().Record(),
 	})
@@ -953,15 +782,9 @@ func (req *enrRequestV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, ma
 
 // ENRRESPONSE/v4
 
-func (req *enrResponseV4) name() string { return "ENRRESPONSE/v4" }
-func (req *enrResponseV4) kind() byte   { return p_enrResponseV4 }
-
-func (req *enrResponseV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromKey encPubkey) error {
-	if !t.handleReply(fromID, from.IP, req) {
+func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
+	if !t.handleReply(fromID, from.IP, h.Packet) {
 		return errUnsolicitedReply
 	}
 	return nil
 }
-
-func (req *enrResponseV4) handle(t *UDPv4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
-}
diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go
index ea7194e43e..262e3f0ba3 100644
--- a/p2p/discover/v4_udp_test.go
+++ b/p2p/discover/v4_udp_test.go
@@ -21,8 +21,8 @@ import (
 	"crypto/ecdsa"
 	crand "crypto/rand"
 	"encoding/binary"
-	"encoding/hex"
 	"errors"
+	"fmt"
 	"io"
 	"math/rand"
 	"net"
@@ -31,23 +31,20 @@ import (
 	"testing"
 	"time"
 
-	"github.com/davecgh/go-spew/spew"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/internal/testlog"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
-	"github.com/ethereum/go-ethereum/rlp"
 )
 
 // shared test variables
 var (
 	futureExp          = uint64(time.Now().Add(10 * time.Hour).Unix())
-	testTarget         = encPubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
-	testRemote         = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2}
-	testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4}
-	testLocal          = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6}
+	testTarget         = v4wire.Pubkey{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}
+	testRemote         = v4wire.Endpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2}
+	testLocalAnnounced = v4wire.Endpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4}
+	testLocal          = v4wire.Endpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6}
 )
 
 type udpTest struct {
@@ -88,19 +85,19 @@ func (test *udpTest) close() {
 }
 
 // handles a packet as if it had been sent to the transport.
-func (test *udpTest) packetIn(wantError error, data packetV4) {
+func (test *udpTest) packetIn(wantError error, data v4wire.Packet) {
 	test.t.Helper()
 
 	test.packetInFrom(wantError, test.remotekey, test.remoteaddr, data)
 }
 
 // handles a packet as if it had been sent to the transport by the key/endpoint.
-func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data packetV4) {
+func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr *net.UDPAddr, data v4wire.Packet) {
 	test.t.Helper()
 
-	enc, _, err := test.udp.encode(key, data)
+	enc, _, err := v4wire.Encode(key, data)
 	if err != nil {
-		test.t.Errorf("%s encode error: %v", data.name(), err)
+		test.t.Errorf("%s encode error: %v", data.Name(), err)
 	}
 	test.sent = append(test.sent, enc)
 	if err = test.udp.handlePacket(addr, enc); err != wantError {
@@ -120,7 +117,7 @@ func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) {
 		test.t.Error("packet receive error:", err)
 		return false
 	}
-	p, _, hash, err := decodeV4(dgram.data)
+	p, _, hash, err := v4wire.Decode(dgram.data)
 	if err != nil {
 		test.t.Errorf("sent packet decode error: %v", err)
 		return false
@@ -139,10 +136,10 @@ func TestUDPv4_packetErrors(t *testing.T) {
 	test := newUDPTest(t)
 	defer test.close()
 
-	test.packetIn(errExpired, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4})
-	test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: []byte{}, Expiration: futureExp})
-	test.packetIn(errUnknownNode, &findnodeV4{Expiration: futureExp})
-	test.packetIn(errUnsolicitedReply, &neighborsV4{Expiration: futureExp})
+	test.packetIn(errExpired, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4})
+	test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: []byte{}, Expiration: futureExp})
+	test.packetIn(errUnknownNode, &v4wire.Findnode{Expiration: futureExp})
+	test.packetIn(errUnsolicitedReply, &v4wire.Neighbors{Expiration: futureExp})
 }
 
 func TestUDPv4_pingTimeout(t *testing.T) {
@@ -160,13 +157,8 @@ func TestUDPv4_pingTimeout(t *testing.T) {
 
 type testPacket byte
 
-func (req testPacket) kind() byte   { return byte(req) }
-func (req testPacket) name() string { return "" }
-func (req testPacket) preverify(*UDPv4, *net.UDPAddr, enode.ID, encPubkey) error {
-	return nil
-}
-func (req testPacket) handle(*UDPv4, *net.UDPAddr, enode.ID, []byte) {
-}
+func (req testPacket) Kind() byte   { return byte(req) }
+func (req testPacket) Name() string { return "" }
 
 func TestUDPv4_responseTimeouts(t *testing.T) {
 	t.Parallel()
@@ -191,7 +183,7 @@ func TestUDPv4_responseTimeouts(t *testing.T) {
 		// within the timeout window.
 		p := &replyMatcher{
 			ptype:    byte(rand.Intn(255)),
-			callback: func(interface{}) (bool, bool) { return true, true },
+			callback: func(v4wire.Packet) (bool, bool) { return true, true },
 		}
 		binary.BigEndian.PutUint64(p.from[:], uint64(i))
 		if p.ptype <= 128 {
@@ -247,7 +239,7 @@ func TestUDPv4_findnodeTimeout(t *testing.T) {
 
 	toaddr := &net.UDPAddr{IP: net.ParseIP("1.2.3.4"), Port: 2222}
 	toid := enode.ID{1, 2, 3, 4}
-	target := encPubkey{4, 5, 6, 7}
+	target := v4wire.Pubkey{4, 5, 6, 7}
 	result, err := test.udp.findnode(toid, toaddr, target)
 	if err != errTimeout {
 		t.Error("expected timeout error, got", err)
@@ -264,7 +256,7 @@ func TestUDPv4_findnode(t *testing.T) {
 	// put a few nodes into the table. their exact
 	// distribution shouldn't matter much, although we need to
 	// take care not to overflow any bucket.
-	nodes := &nodesByDistance{target: testTarget.id()}
+	nodes := &nodesByDistance{target: testTarget.ID()}
 	live := make(map[enode.ID]bool)
 	numCandidates := 2 * bucketSize
 	for i := 0; i < numCandidates; i++ {
@@ -282,32 +274,32 @@ func TestUDPv4_findnode(t *testing.T) {
 
 	// ensure there's a bond with the test node,
 	// findnode won't be accepted otherwise.
-	remoteID := encodePubkey(&test.remotekey.PublicKey).id()
+	remoteID := v4wire.EncodePubkey(&test.remotekey.PublicKey).ID()
 	test.table.db.UpdateLastPongReceived(remoteID, test.remoteaddr.IP, time.Now())
 
 	// check that closest neighbors are returned.
-	expected := test.table.closest(testTarget.id(), bucketSize, true)
-	test.packetIn(nil, &findnodeV4{Target: testTarget, Expiration: futureExp})
+	expected := test.table.findnodeByID(testTarget.ID(), bucketSize, true)
+	test.packetIn(nil, &v4wire.Findnode{Target: testTarget, Expiration: futureExp})
 	waitNeighbors := func(want []*node) {
-		test.waitPacketOut(func(p *neighborsV4, to *net.UDPAddr, hash []byte) {
+		test.waitPacketOut(func(p *v4wire.Neighbors, to *net.UDPAddr, hash []byte) {
 			if len(p.Nodes) != len(want) {
 				t.Errorf("wrong number of results: got %d, want %d", len(p.Nodes), bucketSize)
 			}
 			for i, n := range p.Nodes {
-				if n.ID.id() != want[i].ID() {
+				if n.ID.ID() != want[i].ID() {
 					t.Errorf("result mismatch at %d:\n  got:  %v\n  want: %v", i, n, expected.entries[i])
 				}
-				if !live[n.ID.id()] {
-					t.Errorf("result includes dead node %v", n.ID.id())
+				if !live[n.ID.ID()] {
+					t.Errorf("result includes dead node %v", n.ID.ID())
 				}
 			}
 		})
 	}
 	// Receive replies.
 	want := expected.entries
-	if len(want) > maxNeighbors {
-		waitNeighbors(want[:maxNeighbors])
-		want = want[maxNeighbors:]
+	if len(want) > v4wire.MaxNeighbors {
+		waitNeighbors(want[:v4wire.MaxNeighbors])
+		want = want[v4wire.MaxNeighbors:]
 	}
 	waitNeighbors(want)
 }
@@ -333,7 +325,7 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
 
 	// wait for the findnode to be sent.
 	// after it is sent, the transport is waiting for a reply
-	test.waitPacketOut(func(p *findnodeV4, to *net.UDPAddr, hash []byte) {
+	test.waitPacketOut(func(p *v4wire.Findnode, to *net.UDPAddr, hash []byte) {
 		if p.Target != testTarget {
 			t.Errorf("wrong target: got %v, want %v", p.Target, testTarget)
 		}
@@ -346,12 +338,12 @@ func TestUDPv4_findnodeMultiReply(t *testing.T) {
 		wrapNode(enode.MustParse("enode://9bffefd833d53fac8e652415f4973bee289e8b1a5c6c4cbe70abf817ce8a64cee11b823b66a987f51aaa9fba0d6a91b3e6bf0d5a5d1042de8e9eeea057b217f8@10.0.1.36:30301?discport=17")),
 		wrapNode(enode.MustParse("enode://1b5b4aa662d7cb44a7221bfba67302590b643028197a7d5214790f3bac7aaa4a3241be9e83c09cf1f6c69d007c634faae3dc1b1221793e8446c0b3a09de65960@10.0.1.16:30303")),
 	}
-	rpclist := make([]rpcNode, len(list))
+	rpclist := make([]v4wire.Node, len(list))
 	for i := range list {
 		rpclist[i] = nodeToRPC(list[i])
 	}
-	test.packetIn(nil, &neighborsV4{Expiration: futureExp, Nodes: rpclist[:2]})
-	test.packetIn(nil, &neighborsV4{Expiration: futureExp, Nodes: rpclist[2:]})
+	test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[:2]})
+	test.packetIn(nil, &v4wire.Neighbors{Expiration: futureExp, Nodes: rpclist[2:]})
 
 	// check that the sent neighbors are all returned by findnode
 	select {
@@ -375,10 +367,10 @@ func TestUDPv4_pingMatch(t *testing.T) {
 	randToken := make([]byte, 32)
 	crand.Read(randToken)
 
-	test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
-	test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {})
-	test.waitPacketOut(func(*pingV4, *net.UDPAddr, []byte) {})
-	test.packetIn(errUnsolicitedReply, &pongV4{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
+	test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
+	test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
+	test.waitPacketOut(func(*v4wire.Ping, *net.UDPAddr, []byte) {})
+	test.packetIn(errUnsolicitedReply, &v4wire.Pong{ReplyTok: randToken, To: testLocalAnnounced, Expiration: futureExp})
 }
 
 // This test checks that reply matching of pong verifies the sender IP address.
@@ -386,12 +378,12 @@ func TestUDPv4_pingMatchIP(t *testing.T) {
 	test := newUDPTest(t)
 	defer test.close()
 
-	test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
-	test.waitPacketOut(func(*pongV4, *net.UDPAddr, []byte) {})
+	test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
+	test.waitPacketOut(func(*v4wire.Pong, *net.UDPAddr, []byte) {})
 
-	test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) {
+	test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
 		wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 1, 2}, Port: 30000}
-		test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &pongV4{
+		test.packetInFrom(errUnsolicitedReply, test.remotekey, wrongAddr, &v4wire.Pong{
 			ReplyTok:   hash,
 			To:         testLocalAnnounced,
 			Expiration: futureExp,
@@ -406,15 +398,15 @@ func TestUDPv4_successfulPing(t *testing.T) {
 	defer test.close()
 
 	// The remote side sends a ping packet to initiate the exchange.
-	go test.packetIn(nil, &pingV4{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
+	go test.packetIn(nil, &v4wire.Ping{From: testRemote, To: testLocalAnnounced, Version: 4, Expiration: futureExp})
 
 	// The ping is replied to.
-	test.waitPacketOut(func(p *pongV4, to *net.UDPAddr, hash []byte) {
-		pinghash := test.sent[0][:macSize]
+	test.waitPacketOut(func(p *v4wire.Pong, to *net.UDPAddr, hash []byte) {
+		pinghash := test.sent[0][:32]
 		if !bytes.Equal(p.ReplyTok, pinghash) {
 			t.Errorf("got pong.ReplyTok %x, want %x", p.ReplyTok, pinghash)
 		}
-		wantTo := rpcEndpoint{
+		wantTo := v4wire.Endpoint{
 			// The mirrored UDP address is the UDP packet sender
 			IP: test.remoteaddr.IP, UDP: uint16(test.remoteaddr.Port),
 			// The mirrored TCP port is the one from the ping packet
@@ -426,11 +418,11 @@ func TestUDPv4_successfulPing(t *testing.T) {
 	})
 
 	// Remote is unknown, the table pings back.
-	test.waitPacketOut(func(p *pingV4, to *net.UDPAddr, hash []byte) {
+	test.waitPacketOut(func(p *v4wire.Ping, to *net.UDPAddr, hash []byte) {
 		if !reflect.DeepEqual(p.From, test.udp.ourEndpoint()) {
 			t.Errorf("got ping.From %#v, want %#v", p.From, test.udp.ourEndpoint())
 		}
-		wantTo := rpcEndpoint{
+		wantTo := v4wire.Endpoint{
 			// The mirrored UDP address is the UDP packet sender.
 			IP:  test.remoteaddr.IP,
 			UDP: uint16(test.remoteaddr.Port),
@@ -439,7 +431,7 @@ func TestUDPv4_successfulPing(t *testing.T) {
 		if !reflect.DeepEqual(p.To, wantTo) {
 			t.Errorf("got ping.To %v, want %v", p.To, wantTo)
 		}
-		test.packetIn(nil, &pongV4{ReplyTok: hash, Expiration: futureExp})
+		test.packetIn(nil, &v4wire.Pong{ReplyTok: hash, Expiration: futureExp})
 	})
 
 	// The node should be added to the table shortly after getting the
@@ -473,25 +465,25 @@ func TestUDPv4_EIP868(t *testing.T) {
 	wantNode := test.udp.localNode.Node()
 
 	// ENR requests aren't allowed before endpoint proof.
-	test.packetIn(errUnknownNode, &enrRequestV4{Expiration: futureExp})
+	test.packetIn(errUnknownNode, &v4wire.ENRRequest{Expiration: futureExp})
 
 	// Perform endpoint proof and check for sequence number in packet tail.
-	test.packetIn(nil, &pingV4{Expiration: futureExp})
-	test.waitPacketOut(func(p *pongV4, addr *net.UDPAddr, hash []byte) {
-		if seq := seqFromTail(p.Rest); seq != wantNode.Seq() {
-			t.Errorf("wrong sequence number in pong: %d, want %d", seq, wantNode.Seq())
+	test.packetIn(nil, &v4wire.Ping{Expiration: futureExp})
+	test.waitPacketOut(func(p *v4wire.Pong, addr *net.UDPAddr, hash []byte) {
+		if p.ENRSeq() != wantNode.Seq() {
+			t.Errorf("wrong sequence number in pong: %d, want %d", p.ENRSeq(), wantNode.Seq())
 		}
 	})
-	test.waitPacketOut(func(p *pingV4, addr *net.UDPAddr, hash []byte) {
-		if seq := seqFromTail(p.Rest); seq != wantNode.Seq() {
-			t.Errorf("wrong sequence number in ping: %d, want %d", seq, wantNode.Seq())
+	test.waitPacketOut(func(p *v4wire.Ping, addr *net.UDPAddr, hash []byte) {
+		if p.ENRSeq() != wantNode.Seq() {
+			t.Errorf("wrong sequence number in ping: %d, want %d", p.ENRSeq(), wantNode.Seq())
 		}
-		test.packetIn(nil, &pongV4{Expiration: futureExp, ReplyTok: hash})
+		test.packetIn(nil, &v4wire.Pong{Expiration: futureExp, ReplyTok: hash})
 	})
 
 	// Request should work now.
-	test.packetIn(nil, &enrRequestV4{Expiration: futureExp})
-	test.waitPacketOut(func(p *enrResponseV4, addr *net.UDPAddr, hash []byte) {
+	test.packetIn(nil, &v4wire.ENRRequest{Expiration: futureExp})
+	test.waitPacketOut(func(p *v4wire.ENRResponse, addr *net.UDPAddr, hash []byte) {
 		n, err := enode.New(enode.ValidSchemes, &p.Record)
 		if err != nil {
 			t.Fatalf("invalid record: %v", err)
@@ -502,116 +494,91 @@ func TestUDPv4_EIP868(t *testing.T) {
 	})
 }
 
-// EIP-8 test vectors.
-var testPackets = []struct {
-	input      string
-	wantPacket interface{}
-}{
-	{
-		input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355",
-		wantPacket: &pingV4{
-			Version:    4,
-			From:       rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
-			To:         rpcEndpoint{net.ParseIP("::1"), 2222, 3333},
-			Expiration: 1136239445,
-			Rest:       []rlp.RawValue{},
-		},
-	},
-	{
-		input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102",
-		wantPacket: &pingV4{
-			Version:    4,
-			From:       rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
-			To:         rpcEndpoint{net.ParseIP("::1"), 2222, 3333},
-			Expiration: 1136239445,
-			Rest:       []rlp.RawValue{{0x01}, {0x02}},
-		},
-	},
-	{
-		input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3",
-		wantPacket: &pingV4{
-			Version:    555,
-			From:       rpcEndpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544},
-			To:         rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338},
-			Expiration: 1136239445,
-			Rest:       []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}},
-		},
-	},
-	{
-		input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e",
-		wantPacket: &pongV4{
-			To:         rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338},
-			ReplyTok:   common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"),
-			Expiration: 1136239445,
-			Rest:       []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}},
-		},
-	},
-	{
-		input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396",
-		wantPacket: &findnodeV4{
-			Target:     hexEncPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
-			Expiration: 1136239445,
-			Rest:       []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}},
-		},
-	},
-	{
-		input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0",
-		wantPacket: &neighborsV4{
-			Nodes: []rpcNode{
-				{
-					ID:  hexEncPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
-					IP:  net.ParseIP("99.33.22.55").To4(),
-					UDP: 4444,
-					TCP: 4445,
-				},
-				{
-					ID:  hexEncPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
-					IP:  net.ParseIP("1.2.3.4").To4(),
-					UDP: 1,
-					TCP: 1,
-				},
-				{
-					ID:  hexEncPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
-					IP:  net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
-					UDP: 3333,
-					TCP: 3333,
-				},
-				{
-					ID:  hexEncPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
-					IP:  net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
-					UDP: 999,
-					TCP: 1000,
-				},
-			},
-			Expiration: 1136239445,
-			Rest:       []rlp.RawValue{{0x01}, {0x02}, {0x03}},
-		},
-	},
-}
-
-func TestUDPv4_forwardCompatibility(t *testing.T) {
-	testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-	wantNodeKey := encodePubkey(&testkey.PublicKey)
+// This test verifies that a small network of nodes can boot up into a healthy state.
+func TestUDPv4_smallNetConvergence(t *testing.T) {
+	t.Parallel()
 
-	for _, test := range testPackets {
-		input, err := hex.DecodeString(test.input)
-		if err != nil {
-			t.Fatalf("invalid hex: %s", test.input)
+	// Start the network.
+	nodes := make([]*UDPv4, 4)
+	for i := range nodes {
+		var cfg Config
+		if i > 0 {
+			bn := nodes[0].Self()
+			cfg.Bootnodes = []*enode.Node{bn}
 		}
-		packet, nodekey, _, err := decodeV4(input)
-		if err != nil {
-			t.Errorf("did not accept packet %s\n%v", test.input, err)
-			continue
-		}
-		if !reflect.DeepEqual(packet, test.wantPacket) {
-			t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket))
-		}
-		if nodekey != wantNodeKey {
-			t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey)
+		nodes[i] = startLocalhostV4(t, cfg)
+		defer nodes[i].Close()
+	}
+
+	// Run through the iterator on all nodes until
+	// they have all found each other.
+	status := make(chan error, len(nodes))
+	for i := range nodes {
+		node := nodes[i]
+		go func() {
+			found := make(map[enode.ID]bool, len(nodes))
+			it := node.RandomNodes()
+			for it.Next() {
+				found[it.Node().ID()] = true
+				if len(found) == len(nodes) {
+					status <- nil
+					return
+				}
+			}
+			status <- fmt.Errorf("node %s didn't find all nodes", node.Self().ID().TerminalString())
+		}()
+	}
+
+	// Wait for all status reports.
+	timeout := time.NewTimer(30 * time.Second)
+	defer timeout.Stop()
+	for received := 0; received < len(nodes); {
+		select {
+		case <-timeout.C:
+			for _, node := range nodes {
+				node.Close()
+			}
+		case err := <-status:
+			received++
+			if err != nil {
+				t.Error("ERROR:", err)
+				return
+			}
 		}
 	}
 }
 
+func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 {
+	t.Helper()
+
+	cfg.PrivateKey = newkey()
+	db, _ := enode.OpenDB("")
+	ln := enode.NewLocalNode(db, cfg.PrivateKey)
+
+	// Prefix logs with node ID.
+	lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString())
+	lfmt := log.TerminalFormat(false)
+	cfg.Log = testlog.Logger(t, log.LvlTrace)
+	cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error {
+		t.Logf("%s %s", lprefix, lfmt.Format(r))
+		return nil
+	}))
+
+	// Listen.
+	socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}})
+	if err != nil {
+		t.Fatal(err)
+	}
+	realaddr := socket.LocalAddr().(*net.UDPAddr)
+	ln.SetStaticIP(realaddr.IP)
+	ln.SetFallbackUDP(realaddr.Port)
+	udp, err := ListenV4(socket, ln, cfg)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return udp
+}
+
 // dgramPipe is a fake UDP socket. It queues all sent datagrams.
 type dgramPipe struct {
 	mu      *sync.Mutex
diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go
new file mode 100644
index 0000000000..b5dcb6e517
--- /dev/null
+++ b/p2p/discover/v4wire/v4wire.go
@@ -0,0 +1,300 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package v4wire implements the Discovery v4 Wire Protocol.
+package v4wire
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"errors"
+	"fmt"
+	"math/big"
+	"net"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// RPC packet types
+const (
+	PingPacket = iota + 1 // zero is 'reserved'
+	PongPacket
+	FindnodePacket
+	NeighborsPacket
+	ENRRequestPacket
+	ENRResponsePacket
+)
+
+// RPC request structures
+type (
+	Ping struct {
+		Version    uint
+		From, To   Endpoint
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// Pong is the reply to ping.
+	Pong struct {
+		// This field should mirror the UDP envelope address
+		// of the ping packet, which provides a way to discover the
+		// the external address (after NAT).
+		To         Endpoint
+		ReplyTok   []byte // This contains the hash of the ping packet.
+		Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// Findnode is a query for nodes close to the given target.
+	Findnode struct {
+		Target     Pubkey
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// Neighbors is the reply to findnode.
+	Neighbors struct {
+		Nodes      []Node
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// enrRequest queries for the remote node's record.
+	ENRRequest struct {
+		Expiration uint64
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+
+	// enrResponse is the reply to enrRequest.
+	ENRResponse struct {
+		ReplyTok []byte // Hash of the enrRequest packet.
+		Record   enr.Record
+		// Ignore additional fields (for forward compatibility).
+		Rest []rlp.RawValue `rlp:"tail"`
+	}
+)
+
+// This number is the maximum number of neighbor nodes in a Neigbors packet.
+const MaxNeighbors = 12
+
+// This code computes the MaxNeighbors constant value.
+
+// func init() {
+// 	var maxNeighbors int
+// 	p := Neighbors{Expiration: ^uint64(0)}
+// 	maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
+// 	for n := 0; ; n++ {
+// 		p.Nodes = append(p.Nodes, maxSizeNode)
+// 		size, _, err := rlp.EncodeToReader(p)
+// 		if err != nil {
+// 			// If this ever happens, it will be caught by the unit tests.
+// 			panic("cannot encode: " + err.Error())
+// 		}
+// 		if headSize+size+1 >= 1280 {
+// 			maxNeighbors = n
+// 			break
+// 		}
+// 	}
+// 	fmt.Println("maxNeighbors", maxNeighbors)
+// }
+
+// Pubkey represents an encoded 64-byte secp256k1 public key.
+type Pubkey [64]byte
+
+// ID returns the node ID corresponding to the public key.
+func (e Pubkey) ID() enode.ID {
+	return enode.ID(crypto.Keccak256Hash(e[:]))
+}
+
+// Node represents information about a node.
+type Node struct {
+	IP  net.IP // len 4 for IPv4 or 16 for IPv6
+	UDP uint16 // for discovery protocol
+	TCP uint16 // for RLPx protocol
+	ID  Pubkey
+}
+
+// Endpoint represents a network endpoint.
+type Endpoint struct {
+	IP  net.IP // len 4 for IPv4 or 16 for IPv6
+	UDP uint16 // for discovery protocol
+	TCP uint16 // for RLPx protocol
+}
+
+// NewEndpoint creates an endpoint.
+func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
+	ip := net.IP{}
+	if ip4 := addr.IP.To4(); ip4 != nil {
+		ip = ip4
+	} else if ip6 := addr.IP.To16(); ip6 != nil {
+		ip = ip6
+	}
+	return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
+}
+
+type Packet interface {
+	// packet name and type for logging purposes.
+	Name() string
+	Kind() byte
+}
+
+func (req *Ping) Name() string   { return "PING/v4" }
+func (req *Ping) Kind() byte     { return PingPacket }
+func (req *Ping) ENRSeq() uint64 { return seqFromTail(req.Rest) }
+
+func (req *Pong) Name() string   { return "PONG/v4" }
+func (req *Pong) Kind() byte     { return PongPacket }
+func (req *Pong) ENRSeq() uint64 { return seqFromTail(req.Rest) }
+
+func (req *Findnode) Name() string { return "FINDNODE/v4" }
+func (req *Findnode) Kind() byte   { return FindnodePacket }
+
+func (req *Neighbors) Name() string { return "NEIGHBORS/v4" }
+func (req *Neighbors) Kind() byte   { return NeighborsPacket }
+
+func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" }
+func (req *ENRRequest) Kind() byte   { return ENRRequestPacket }
+
+func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }
+func (req *ENRResponse) Kind() byte   { return ENRResponsePacket }
+
+// Expired checks whether the given UNIX time stamp is in the past.
+func Expired(ts uint64) bool {
+	return time.Unix(int64(ts), 0).Before(time.Now())
+}
+
+func seqFromTail(tail []rlp.RawValue) uint64 {
+	if len(tail) == 0 {
+		return 0
+	}
+	var seq uint64
+	rlp.DecodeBytes(tail[0], &seq)
+	return seq
+}
+
+// Encoder/decoder.
+
+const (
+	macSize  = 32
+	sigSize  = crypto.SignatureLength
+	headSize = macSize + sigSize // space of packet frame data
+)
+
+var (
+	ErrPacketTooSmall = errors.New("too small")
+	ErrBadHash        = errors.New("bad hash")
+	ErrBadPoint       = errors.New("invalid curve point")
+)
+
+var headSpace = make([]byte, headSize)
+
+// Decode reads a discovery v4 packet.
+func Decode(input []byte) (Packet, Pubkey, []byte, error) {
+	if len(input) < headSize+1 {
+		return nil, Pubkey{}, nil, ErrPacketTooSmall
+	}
+	hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:]
+	shouldhash := crypto.Keccak256(input[macSize:])
+	if !bytes.Equal(hash, shouldhash) {
+		return nil, Pubkey{}, nil, ErrBadHash
+	}
+	fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig)
+	if err != nil {
+		return nil, fromKey, hash, err
+	}
+
+	var req Packet
+	switch ptype := sigdata[0]; ptype {
+	case PingPacket:
+		req = new(Ping)
+	case PongPacket:
+		req = new(Pong)
+	case FindnodePacket:
+		req = new(Findnode)
+	case NeighborsPacket:
+		req = new(Neighbors)
+	case ENRRequestPacket:
+		req = new(ENRRequest)
+	case ENRResponsePacket:
+		req = new(ENRResponse)
+	default:
+		return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
+	}
+	s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
+	err = s.Decode(req)
+	return req, fromKey, hash, err
+}
+
+// Encode encodes a discovery packet.
+func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) {
+	b := new(bytes.Buffer)
+	b.Write(headSpace)
+	b.WriteByte(req.Kind())
+	if err := rlp.Encode(b, req); err != nil {
+		return nil, nil, err
+	}
+	packet = b.Bytes()
+	sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
+	if err != nil {
+		return nil, nil, err
+	}
+	copy(packet[macSize:], sig)
+	// Add the hash to the front. Note: this doesn't protect the packet in any way.
+	hash = crypto.Keccak256(packet[macSize:])
+	copy(packet, hash)
+	return packet, hash, nil
+}
+
+// recoverNodeKey computes the public key used to sign the given hash from the signature.
+func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) {
+	pubkey, err := crypto.Ecrecover(hash, sig)
+	if err != nil {
+		return key, err
+	}
+	copy(key[:], pubkey[1:])
+	return key, nil
+}
+
+// EncodePubkey encodes a secp256k1 public key.
+func EncodePubkey(key *ecdsa.PublicKey) Pubkey {
+	var e Pubkey
+	math.ReadBits(key.X, e[:len(e)/2])
+	math.ReadBits(key.Y, e[len(e)/2:])
+	return e
+}
+
+// DecodePubkey reads an encoded secp256k1 public key.
+func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) {
+	p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
+	half := len(e) / 2
+	p.X.SetBytes(e[:half])
+	p.Y.SetBytes(e[half:])
+	if !p.Curve.IsOnCurve(p.X, p.Y) {
+		return nil, ErrBadPoint
+	}
+	return p, nil
+}
diff --git a/p2p/discover/v4wire/v4wire_test.go b/p2p/discover/v4wire/v4wire_test.go
new file mode 100644
index 0000000000..4dddeadd20
--- /dev/null
+++ b/p2p/discover/v4wire/v4wire_test.go
@@ -0,0 +1,152 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package v4wire
+
+import (
+	"encoding/hex"
+	"net"
+	"reflect"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// EIP-8 test vectors.
+var testPackets = []struct {
+	input      string
+	wantPacket interface{}
+}{
+	{
+		input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355",
+		wantPacket: &Ping{
+			Version:    4,
+			From:       Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
+			To:         Endpoint{net.ParseIP("::1"), 2222, 3333},
+			Expiration: 1136239445,
+			Rest:       []rlp.RawValue{},
+		},
+	},
+	{
+		input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102",
+		wantPacket: &Ping{
+			Version:    4,
+			From:       Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
+			To:         Endpoint{net.ParseIP("::1"), 2222, 3333},
+			Expiration: 1136239445,
+			Rest:       []rlp.RawValue{{0x01}, {0x02}},
+		},
+	},
+	{
+		input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3",
+		wantPacket: &Ping{
+			Version:    555,
+			From:       Endpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544},
+			To:         Endpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338},
+			Expiration: 1136239445,
+			Rest:       []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}},
+		},
+	},
+	{
+		input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e",
+		wantPacket: &Pong{
+			To:         Endpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338},
+			ReplyTok:   common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"),
+			Expiration: 1136239445,
+			Rest:       []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}},
+		},
+	},
+	{
+		input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396",
+		wantPacket: &Findnode{
+			Target:     hexPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
+			Expiration: 1136239445,
+			Rest:       []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}},
+		},
+	},
+	{
+		input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0",
+		wantPacket: &Neighbors{
+			Nodes: []Node{
+				{
+					ID:  hexPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
+					IP:  net.ParseIP("99.33.22.55").To4(),
+					UDP: 4444,
+					TCP: 4445,
+				},
+				{
+					ID:  hexPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
+					IP:  net.ParseIP("1.2.3.4").To4(),
+					UDP: 1,
+					TCP: 1,
+				},
+				{
+					ID:  hexPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
+					IP:  net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
+					UDP: 3333,
+					TCP: 3333,
+				},
+				{
+					ID:  hexPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
+					IP:  net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
+					UDP: 999,
+					TCP: 1000,
+				},
+			},
+			Expiration: 1136239445,
+			Rest:       []rlp.RawValue{{0x01}, {0x02}, {0x03}},
+		},
+	},
+}
+
+// This test checks that the decoder accepts packets according to EIP-8.
+func TestForwardCompatibility(t *testing.T) {
+	testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+	wantNodeKey := EncodePubkey(&testkey.PublicKey)
+
+	for _, test := range testPackets {
+		input, err := hex.DecodeString(test.input)
+		if err != nil {
+			t.Fatalf("invalid hex: %s", test.input)
+		}
+		packet, nodekey, _, err := Decode(input)
+		if err != nil {
+			t.Errorf("did not accept packet %s\n%v", test.input, err)
+			continue
+		}
+		if !reflect.DeepEqual(packet, test.wantPacket) {
+			t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket))
+		}
+		if nodekey != wantNodeKey {
+			t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey)
+		}
+	}
+}
+
+func hexPubkey(h string) (ret Pubkey) {
+	b, err := hex.DecodeString(h)
+	if err != nil {
+		panic(err)
+	}
+	if len(b) != len(ret) {
+		panic("invalid length")
+	}
+	copy(ret[:], b)
+	return ret
+}
diff --git a/p2p/discover/v5_encoding.go b/p2p/discover/v5_encoding.go
deleted file mode 100644
index 842234e790..0000000000
--- a/p2p/discover/v5_encoding.go
+++ /dev/null
@@ -1,659 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
-	"bytes"
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/ecdsa"
-	"crypto/elliptic"
-	crand "crypto/rand"
-	"crypto/sha256"
-	"errors"
-	"fmt"
-	"hash"
-	"net"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common/math"
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/p2p/enr"
-	"github.com/ethereum/go-ethereum/rlp"
-	"golang.org/x/crypto/hkdf"
-)
-
-// TODO concurrent WHOAREYOU tie-breaker
-// TODO deal with WHOAREYOU amplification factor (min packet size?)
-// TODO add counter to nonce
-// TODO rehandshake after X packets
-
-// Discovery v5 packet types.
-const (
-	p_pingV5 byte = iota + 1
-	p_pongV5
-	p_findnodeV5
-	p_nodesV5
-	p_requestTicketV5
-	p_ticketV5
-	p_regtopicV5
-	p_regconfirmationV5
-	p_topicqueryV5
-	p_unknownV5   = byte(255) // any non-decryptable packet
-	p_whoareyouV5 = byte(254) // the WHOAREYOU packet
-)
-
-// Discovery v5 packet structures.
-type (
-	// unknownV5 represents any packet that can't be decrypted.
-	unknownV5 struct {
-		AuthTag []byte
-	}
-
-	// WHOAREYOU contains the handshake challenge.
-	whoareyouV5 struct {
-		AuthTag   []byte
-		IDNonce   [32]byte // To be signed by recipient.
-		RecordSeq uint64   // ENR sequence number of recipient
-
-		node *enode.Node
-		sent mclock.AbsTime
-	}
-
-	// PING is sent during liveness checks.
-	pingV5 struct {
-		ReqID  []byte
-		ENRSeq uint64
-	}
-
-	// PONG is the reply to PING.
-	pongV5 struct {
-		ReqID  []byte
-		ENRSeq uint64
-		ToIP   net.IP // These fields should mirror the UDP envelope address of the ping
-		ToPort uint16 // packet, which provides a way to discover the the external address (after NAT).
-	}
-
-	// FINDNODE is a query for nodes in the given bucket.
-	findnodeV5 struct {
-		ReqID    []byte
-		Distance uint
-	}
-
-	// NODES is the reply to FINDNODE and TOPICQUERY.
-	nodesV5 struct {
-		ReqID []byte
-		Total uint8
-		Nodes []*enr.Record
-	}
-
-	// REQUESTTICKET requests a ticket for a topic queue.
-	requestTicketV5 struct {
-		ReqID []byte
-		Topic []byte
-	}
-
-	// TICKET is the response to REQUESTTICKET.
-	ticketV5 struct {
-		ReqID  []byte
-		Ticket []byte
-	}
-
-	// REGTOPIC registers the sender in a topic queue using a ticket.
-	regtopicV5 struct {
-		ReqID  []byte
-		Ticket []byte
-		ENR    *enr.Record
-	}
-
-	// REGCONFIRMATION is the reply to REGTOPIC.
-	regconfirmationV5 struct {
-		ReqID      []byte
-		Registered bool
-	}
-
-	// TOPICQUERY asks for nodes with the given topic.
-	topicqueryV5 struct {
-		ReqID []byte
-		Topic []byte
-	}
-)
-
-const (
-	// Encryption/authentication parameters.
-	authSchemeName   = "gcm"
-	aesKeySize       = 16
-	gcmNonceSize     = 12
-	idNoncePrefix    = "discovery-id-nonce"
-	handshakeTimeout = time.Second
-)
-
-var (
-	errTooShort               = errors.New("packet too short")
-	errUnexpectedHandshake    = errors.New("unexpected auth response, not in handshake")
-	errHandshakeNonceMismatch = errors.New("wrong nonce in auth response")
-	errInvalidAuthKey         = errors.New("invalid ephemeral pubkey")
-	errUnknownAuthScheme      = errors.New("unknown auth scheme in handshake")
-	errNoRecord               = errors.New("expected ENR in handshake but none sent")
-	errInvalidNonceSig        = errors.New("invalid ID nonce signature")
-	zeroNonce                 = make([]byte, gcmNonceSize)
-)
-
-// wireCodec encodes and decodes discovery v5 packets.
-type wireCodec struct {
-	sha256           hash.Hash
-	localnode        *enode.LocalNode
-	privkey          *ecdsa.PrivateKey
-	myChtagHash      enode.ID
-	myWhoareyouMagic []byte
-
-	sc *sessionCache
-}
-
-type handshakeSecrets struct {
-	writeKey, readKey, authRespKey []byte
-}
-
-type authHeader struct {
-	authHeaderList
-	isHandshake bool
-}
-
-type authHeaderList struct {
-	Auth         []byte   // authentication info of packet
-	IDNonce      [32]byte // IDNonce of WHOAREYOU
-	Scheme       string   // name of encryption/authentication scheme
-	EphemeralKey []byte   // ephemeral public key
-	Response     []byte   // encrypted authResponse
-}
-
-type authResponse struct {
-	Version   uint
-	Signature []byte
-	Record    *enr.Record `rlp:"nil"` // sender's record
-}
-
-func (h *authHeader) DecodeRLP(r *rlp.Stream) error {
-	k, _, err := r.Kind()
-	if err != nil {
-		return err
-	}
-	if k == rlp.Byte || k == rlp.String {
-		return r.Decode(&h.Auth)
-	}
-	h.isHandshake = true
-	return r.Decode(&h.authHeaderList)
-}
-
-// ephemeralKey decodes the ephemeral public key in the header.
-func (h *authHeaderList) ephemeralKey(curve elliptic.Curve) *ecdsa.PublicKey {
-	var key encPubkey
-	copy(key[:], h.EphemeralKey)
-	pubkey, _ := decodePubkey(curve, key)
-	return pubkey
-}
-
-// newWireCodec creates a wire codec.
-func newWireCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *wireCodec {
-	c := &wireCodec{
-		sha256:    sha256.New(),
-		localnode: ln,
-		privkey:   key,
-		sc:        newSessionCache(1024, clock),
-	}
-	// Create magic strings for packet matching.
-	self := ln.ID()
-	c.myWhoareyouMagic = c.sha256sum(self[:], []byte("WHOAREYOU"))
-	copy(c.myChtagHash[:], c.sha256sum(self[:]))
-	return c
-}
-
-// encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
-// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
-// node.
-func (c *wireCodec) encode(id enode.ID, addr string, packet packetV5, challenge *whoareyouV5) ([]byte, []byte, error) {
-	if packet.kind() == p_whoareyouV5 {
-		p := packet.(*whoareyouV5)
-		enc, err := c.encodeWhoareyou(id, p)
-		if err == nil {
-			c.sc.storeSentHandshake(id, addr, p)
-		}
-		return enc, nil, err
-	}
-	// Ensure calling code sets node if needed.
-	if challenge != nil && challenge.node == nil {
-		panic("BUG: missing challenge.node in encode")
-	}
-	writeKey := c.sc.writeKey(id, addr)
-	if writeKey != nil || challenge != nil {
-		return c.encodeEncrypted(id, addr, packet, writeKey, challenge)
-	}
-	return c.encodeRandom(id)
-}
-
-// encodeRandom encodes a random packet.
-func (c *wireCodec) encodeRandom(toID enode.ID) ([]byte, []byte, error) {
-	tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID())
-	r := make([]byte, 44) // TODO randomize size
-	if _, err := crand.Read(r); err != nil {
-		return nil, nil, err
-	}
-	nonce := make([]byte, gcmNonceSize)
-	if _, err := crand.Read(nonce); err != nil {
-		return nil, nil, fmt.Errorf("can't get random data: %v", err)
-	}
-	b := new(bytes.Buffer)
-	b.Write(tag[:])
-	rlp.Encode(b, nonce)
-	b.Write(r)
-	return b.Bytes(), nonce, nil
-}
-
-// encodeWhoareyou encodes WHOAREYOU.
-func (c *wireCodec) encodeWhoareyou(toID enode.ID, packet *whoareyouV5) ([]byte, error) {
-	// Sanity check node field to catch misbehaving callers.
-	if packet.RecordSeq > 0 && packet.node == nil {
-		panic("BUG: missing node in whoareyouV5 with non-zero seq")
-	}
-	b := new(bytes.Buffer)
-	b.Write(c.sha256sum(toID[:], []byte("WHOAREYOU")))
-	err := rlp.Encode(b, packet)
-	return b.Bytes(), err
-}
-
-// encodeEncrypted encodes an encrypted packet.
-func (c *wireCodec) encodeEncrypted(toID enode.ID, toAddr string, packet packetV5, writeKey []byte, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) {
-	nonce := make([]byte, gcmNonceSize)
-	if _, err := crand.Read(nonce); err != nil {
-		return nil, nil, fmt.Errorf("can't get random data: %v", err)
-	}
-
-	var headEnc []byte
-	if challenge == nil {
-		// Regular packet, use existing key and simply encode nonce.
-		headEnc, _ = rlp.EncodeToBytes(nonce)
-	} else {
-		// We're answering WHOAREYOU, generate new keys and encrypt with those.
-		header, sec, err := c.makeAuthHeader(nonce, challenge)
-		if err != nil {
-			return nil, nil, err
-		}
-		if headEnc, err = rlp.EncodeToBytes(header); err != nil {
-			return nil, nil, err
-		}
-		c.sc.storeNewSession(toID, toAddr, sec.readKey, sec.writeKey)
-		writeKey = sec.writeKey
-	}
-
-	// Encode the packet.
-	body := new(bytes.Buffer)
-	body.WriteByte(packet.kind())
-	if err := rlp.Encode(body, packet); err != nil {
-		return nil, nil, err
-	}
-	tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID())
-	headsize := len(tag) + len(headEnc)
-	headbuf := make([]byte, headsize)
-	copy(headbuf[:], tag[:])
-	copy(headbuf[len(tag):], headEnc)
-
-	// Encrypt the body.
-	enc, err = encryptGCM(headbuf, writeKey, nonce, body.Bytes(), tag[:])
-	return enc, nonce, err
-}
-
-// encodeAuthHeader creates the auth header on a call packet following WHOAREYOU.
-func (c *wireCodec) makeAuthHeader(nonce []byte, challenge *whoareyouV5) (*authHeaderList, *handshakeSecrets, error) {
-	resp := &authResponse{Version: 5}
-
-	// Add our record to response if it's newer than what remote
-	// side has.
-	ln := c.localnode.Node()
-	if challenge.RecordSeq < ln.Seq() {
-		resp.Record = ln.Record()
-	}
-
-	// Create the ephemeral key. This needs to be first because the
-	// key is part of the ID nonce signature.
-	var remotePubkey = new(ecdsa.PublicKey)
-	if err := challenge.node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
-		return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
-	}
-	ephkey, err := crypto.GenerateKey()
-	if err != nil {
-		return nil, nil, fmt.Errorf("can't generate ephemeral key")
-	}
-	ephpubkey := encodePubkey(&ephkey.PublicKey)
-
-	// Add ID nonce signature to response.
-	idsig, err := c.signIDNonce(challenge.IDNonce[:], ephpubkey[:])
-	if err != nil {
-		return nil, nil, fmt.Errorf("can't sign: %v", err)
-	}
-	resp.Signature = idsig
-
-	// Create session keys.
-	sec := c.deriveKeys(c.localnode.ID(), challenge.node.ID(), ephkey, remotePubkey, challenge)
-	if sec == nil {
-		return nil, nil, fmt.Errorf("key derivation failed")
-	}
-
-	// Encrypt the authentication response and assemble the auth header.
-	respRLP, err := rlp.EncodeToBytes(resp)
-	if err != nil {
-		return nil, nil, fmt.Errorf("can't encode auth response: %v", err)
-	}
-	respEnc, err := encryptGCM(nil, sec.authRespKey, zeroNonce, respRLP, nil)
-	if err != nil {
-		return nil, nil, fmt.Errorf("can't encrypt auth response: %v", err)
-	}
-	head := &authHeaderList{
-		Auth:         nonce,
-		Scheme:       authSchemeName,
-		IDNonce:      challenge.IDNonce,
-		EphemeralKey: ephpubkey[:],
-		Response:     respEnc,
-	}
-	return head, sec, err
-}
-
-// deriveKeys generates session keys using elliptic-curve Diffie-Hellman key agreement.
-func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, challenge *whoareyouV5) *handshakeSecrets {
-	eph := ecdh(priv, pub)
-	if eph == nil {
-		return nil
-	}
-
-	info := []byte("discovery v5 key agreement")
-	info = append(info, n1[:]...)
-	info = append(info, n2[:]...)
-	kdf := hkdf.New(c.sha256reset, eph, challenge.IDNonce[:], info)
-	sec := handshakeSecrets{
-		writeKey:    make([]byte, aesKeySize),
-		readKey:     make([]byte, aesKeySize),
-		authRespKey: make([]byte, aesKeySize),
-	}
-	kdf.Read(sec.writeKey)
-	kdf.Read(sec.readKey)
-	kdf.Read(sec.authRespKey)
-	for i := range eph {
-		eph[i] = 0
-	}
-	return &sec
-}
-
-// signIDNonce creates the ID nonce signature.
-func (c *wireCodec) signIDNonce(nonce, ephkey []byte) ([]byte, error) {
-	idsig, err := crypto.Sign(c.idNonceHash(nonce, ephkey), c.privkey)
-	if err != nil {
-		return nil, fmt.Errorf("can't sign: %v", err)
-	}
-	return idsig[:len(idsig)-1], nil // remove recovery ID
-}
-
-// idNonceHash computes the hash of id nonce with prefix.
-func (c *wireCodec) idNonceHash(nonce, ephkey []byte) []byte {
-	h := c.sha256reset()
-	h.Write([]byte(idNoncePrefix))
-	h.Write(nonce)
-	h.Write(ephkey)
-	return h.Sum(nil)
-}
-
-// decode decodes a discovery packet.
-func (c *wireCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) {
-	// Delete timed-out handshakes. This must happen before decoding to avoid
-	// processing the same handshake twice.
-	c.sc.handshakeGC()
-
-	if len(input) < 32 {
-		return enode.ID{}, nil, nil, errTooShort
-	}
-	if bytes.HasPrefix(input, c.myWhoareyouMagic) {
-		p, err := c.decodeWhoareyou(input)
-		return enode.ID{}, nil, p, err
-	}
-	sender := xorTag(input[:32], c.myChtagHash)
-	p, n, err := c.decodeEncrypted(sender, addr, input)
-	return sender, n, p, err
-}
-
-// decodeWhoareyou decode a WHOAREYOU packet.
-func (c *wireCodec) decodeWhoareyou(input []byte) (packetV5, error) {
-	packet := new(whoareyouV5)
-	err := rlp.DecodeBytes(input[32:], packet)
-	return packet, err
-}
-
-// decodeEncrypted decodes an encrypted discovery packet.
-func (c *wireCodec) decodeEncrypted(fromID enode.ID, fromAddr string, input []byte) (packetV5, *enode.Node, error) {
-	// Decode packet header.
-	var head authHeader
-	r := bytes.NewReader(input[32:])
-	err := rlp.Decode(r, &head)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Decrypt and process auth response.
-	readKey, node, err := c.decodeAuth(fromID, fromAddr, &head)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	// Decrypt and decode the packet body.
-	headsize := len(input) - r.Len()
-	bodyEnc := input[headsize:]
-	body, err := decryptGCM(readKey, head.Auth, bodyEnc, input[:32])
-	if err != nil {
-		if !head.isHandshake {
-			// Can't decrypt, start handshake.
-			return &unknownV5{AuthTag: head.Auth}, nil, nil
-		}
-		return nil, nil, fmt.Errorf("handshake failed: %v", err)
-	}
-	if len(body) == 0 {
-		return nil, nil, errTooShort
-	}
-	p, err := decodePacketBodyV5(body[0], body[1:])
-	return p, node, err
-}
-
-// decodeAuth processes an auth header.
-func (c *wireCodec) decodeAuth(fromID enode.ID, fromAddr string, head *authHeader) ([]byte, *enode.Node, error) {
-	if !head.isHandshake {
-		return c.sc.readKey(fromID, fromAddr), nil, nil
-	}
-
-	// Remote is attempting handshake. Verify against our last WHOAREYOU.
-	challenge := c.sc.getHandshake(fromID, fromAddr)
-	if challenge == nil {
-		return nil, nil, errUnexpectedHandshake
-	}
-	if head.IDNonce != challenge.IDNonce {
-		return nil, nil, errHandshakeNonceMismatch
-	}
-	sec, n, err := c.decodeAuthResp(fromID, fromAddr, &head.authHeaderList, challenge)
-	if err != nil {
-		return nil, n, err
-	}
-	// Swap keys to match remote.
-	sec.readKey, sec.writeKey = sec.writeKey, sec.readKey
-	c.sc.storeNewSession(fromID, fromAddr, sec.readKey, sec.writeKey)
-	c.sc.deleteHandshake(fromID, fromAddr)
-	return sec.readKey, n, err
-}
-
-// decodeAuthResp decodes and verifies an authentication response.
-func (c *wireCodec) decodeAuthResp(fromID enode.ID, fromAddr string, head *authHeaderList, challenge *whoareyouV5) (*handshakeSecrets, *enode.Node, error) {
-	// Decrypt / decode the response.
-	if head.Scheme != authSchemeName {
-		return nil, nil, errUnknownAuthScheme
-	}
-	ephkey := head.ephemeralKey(c.privkey.Curve)
-	if ephkey == nil {
-		return nil, nil, errInvalidAuthKey
-	}
-	sec := c.deriveKeys(fromID, c.localnode.ID(), c.privkey, ephkey, challenge)
-	respPT, err := decryptGCM(sec.authRespKey, zeroNonce, head.Response, nil)
-	if err != nil {
-		return nil, nil, fmt.Errorf("can't decrypt auth response header: %v", err)
-	}
-	var resp authResponse
-	if err := rlp.DecodeBytes(respPT, &resp); err != nil {
-		return nil, nil, fmt.Errorf("invalid auth response: %v", err)
-	}
-
-	// Verify response node record. The remote node should include the record
-	// if we don't have one or if ours is older than the latest version.
-	node := challenge.node
-	if resp.Record != nil {
-		if node == nil || node.Seq() < resp.Record.Seq() {
-			n, err := enode.New(enode.ValidSchemes, resp.Record)
-			if err != nil {
-				return nil, nil, fmt.Errorf("invalid node record: %v", err)
-			}
-			if n.ID() != fromID {
-				return nil, nil, fmt.Errorf("record in auth respose has wrong ID: %v", n.ID())
-			}
-			node = n
-		}
-	}
-	if node == nil {
-		return nil, nil, errNoRecord
-	}
-
-	// Verify ID nonce signature.
-	err = c.verifyIDSignature(challenge.IDNonce[:], head.EphemeralKey, resp.Signature, node)
-	if err != nil {
-		return nil, nil, err
-	}
-	return sec, node, nil
-}
-
-// verifyIDSignature checks that signature over idnonce was made by the node with given record.
-func (c *wireCodec) verifyIDSignature(nonce, ephkey, sig []byte, n *enode.Node) error {
-	switch idscheme := n.Record().IdentityScheme(); idscheme {
-	case "v4":
-		var pk ecdsa.PublicKey
-		n.Load((*enode.Secp256k1)(&pk)) // cannot fail because record is valid
-		if !crypto.VerifySignature(crypto.FromECDSAPub(&pk), c.idNonceHash(nonce, ephkey), sig) {
-			return errInvalidNonceSig
-		}
-		return nil
-	default:
-		return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
-	}
-}
-
-// decodePacketBody decodes the body of an encrypted discovery packet.
-func decodePacketBodyV5(ptype byte, body []byte) (packetV5, error) {
-	var dec packetV5
-	switch ptype {
-	case p_pingV5:
-		dec = new(pingV5)
-	case p_pongV5:
-		dec = new(pongV5)
-	case p_findnodeV5:
-		dec = new(findnodeV5)
-	case p_nodesV5:
-		dec = new(nodesV5)
-	case p_requestTicketV5:
-		dec = new(requestTicketV5)
-	case p_ticketV5:
-		dec = new(ticketV5)
-	case p_regtopicV5:
-		dec = new(regtopicV5)
-	case p_regconfirmationV5:
-		dec = new(regconfirmationV5)
-	case p_topicqueryV5:
-		dec = new(topicqueryV5)
-	default:
-		return nil, fmt.Errorf("unknown packet type %d", ptype)
-	}
-	if err := rlp.DecodeBytes(body, dec); err != nil {
-		return nil, err
-	}
-	return dec, nil
-}
-
-// sha256reset returns the shared hash instance.
-func (c *wireCodec) sha256reset() hash.Hash {
-	c.sha256.Reset()
-	return c.sha256
-}
-
-// sha256sum computes sha256 on the concatenation of inputs.
-func (c *wireCodec) sha256sum(inputs ...[]byte) []byte {
-	c.sha256.Reset()
-	for _, b := range inputs {
-		c.sha256.Write(b)
-	}
-	return c.sha256.Sum(nil)
-}
-
-func xorTag(a []byte, b enode.ID) enode.ID {
-	var r enode.ID
-	for i := range r {
-		r[i] = a[i] ^ b[i]
-	}
-	return r
-}
-
-// ecdh creates a shared secret.
-func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
-	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
-	if secX == nil {
-		return nil
-	}
-	sec := make([]byte, 33)
-	sec[0] = 0x02 | byte(secY.Bit(0))
-	math.ReadBits(secX, sec[1:])
-	return sec
-}
-
-// encryptGCM encrypts pt using AES-GCM with the given key and nonce.
-func encryptGCM(dest, key, nonce, pt, authData []byte) ([]byte, error) {
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		panic(fmt.Errorf("can't create block cipher: %v", err))
-	}
-	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
-	if err != nil {
-		panic(fmt.Errorf("can't create GCM: %v", err))
-	}
-	return aesgcm.Seal(dest, nonce, pt, authData), nil
-}
-
-// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
-func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return nil, fmt.Errorf("can't create block cipher: %v", err)
-	}
-	if len(nonce) != gcmNonceSize {
-		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
-	}
-	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
-	if err != nil {
-		return nil, fmt.Errorf("can't create GCM: %v", err)
-	}
-	pt := make([]byte, 0, len(ct))
-	return aesgcm.Open(pt, nonce, ct, authData)
-}
diff --git a/p2p/discover/v5_encoding_test.go b/p2p/discover/v5_encoding_test.go
deleted file mode 100644
index 77e6bae6ae..0000000000
--- a/p2p/discover/v5_encoding_test.go
+++ /dev/null
@@ -1,373 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package discover
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"encoding/hex"
-	"fmt"
-	"net"
-	"reflect"
-	"testing"
-
-	"github.com/davecgh/go-spew/spew"
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-var (
-	testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f")
-	testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628")
-	testIDnonce = [32]byte{5, 6, 7, 8, 9, 10, 11, 12}
-)
-
-func TestDeriveKeysV5(t *testing.T) {
-	t.Parallel()
-
-	var (
-		n1        = enode.ID{1}
-		n2        = enode.ID{2}
-		challenge = &whoareyouV5{}
-		db, _     = enode.OpenDB("")
-		ln        = enode.NewLocalNode(db, testKeyA)
-		c         = newWireCodec(ln, testKeyA, mclock.System{})
-	)
-	defer db.Close()
-
-	sec1 := c.deriveKeys(n1, n2, testKeyA, &testKeyB.PublicKey, challenge)
-	sec2 := c.deriveKeys(n1, n2, testKeyB, &testKeyA.PublicKey, challenge)
-	if sec1 == nil || sec2 == nil {
-		t.Fatal("key agreement failed")
-	}
-	if !reflect.DeepEqual(sec1, sec2) {
-		t.Fatalf("keys not equal:\n  %+v\n  %+v", sec1, sec2)
-	}
-}
-
-// This test checks the basic handshake flow where A talks to B and A has no secrets.
-func TestHandshakeV5(t *testing.T) {
-	t.Parallel()
-	net := newHandshakeTest()
-	defer net.close()
-
-	// A -> B   RANDOM PACKET
-	packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{})
-	resp := net.nodeB.expectDecode(t, p_unknownV5, packet)
-
-	// A <- B   WHOAREYOU
-	challenge := &whoareyouV5{
-		AuthTag:   resp.(*unknownV5).AuthTag,
-		IDNonce:   testIDnonce,
-		RecordSeq: 0,
-	}
-	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
-	net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou)
-
-	// A -> B   FINDNODE
-	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{})
-	net.nodeB.expectDecode(t, p_findnodeV5, findnode)
-	if len(net.nodeB.c.sc.handshakes) > 0 {
-		t.Fatalf("node B didn't remove handshake from challenge map")
-	}
-
-	// A <- B   NODES
-	nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1})
-	net.nodeA.expectDecode(t, p_nodesV5, nodes)
-}
-
-// This test checks that handshake attempts are removed within the timeout.
-func TestHandshakeV5_timeout(t *testing.T) {
-	t.Parallel()
-	net := newHandshakeTest()
-	defer net.close()
-
-	// A -> B   RANDOM PACKET
-	packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{})
-	resp := net.nodeB.expectDecode(t, p_unknownV5, packet)
-
-	// A <- B   WHOAREYOU
-	challenge := &whoareyouV5{
-		AuthTag:   resp.(*unknownV5).AuthTag,
-		IDNonce:   testIDnonce,
-		RecordSeq: 0,
-	}
-	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
-	net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou)
-
-	// A -> B   FINDNODE after timeout
-	net.clock.Run(handshakeTimeout + 1)
-	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{})
-	net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode)
-}
-
-// This test checks handshake behavior when no record is sent in the auth response.
-func TestHandshakeV5_norecord(t *testing.T) {
-	t.Parallel()
-	net := newHandshakeTest()
-	defer net.close()
-
-	// A -> B   RANDOM PACKET
-	packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{})
-	resp := net.nodeB.expectDecode(t, p_unknownV5, packet)
-
-	// A <- B   WHOAREYOU
-	nodeA := net.nodeA.n()
-	if nodeA.Seq() == 0 {
-		t.Fatal("need non-zero sequence number")
-	}
-	challenge := &whoareyouV5{
-		AuthTag:   resp.(*unknownV5).AuthTag,
-		IDNonce:   testIDnonce,
-		RecordSeq: nodeA.Seq(),
-		node:      nodeA,
-	}
-	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
-	net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou)
-
-	// A -> B   FINDNODE
-	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{})
-	net.nodeB.expectDecode(t, p_findnodeV5, findnode)
-
-	// A <- B   NODES
-	nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1})
-	net.nodeA.expectDecode(t, p_nodesV5, nodes)
-}
-
-// In this test, A tries to send FINDNODE with existing secrets but B doesn't know
-// anything about A.
-func TestHandshakeV5_rekey(t *testing.T) {
-	t.Parallel()
-	net := newHandshakeTest()
-	defer net.close()
-
-	initKeys := &handshakeSecrets{
-		readKey:  []byte("BBBBBBBBBBBBBBBB"),
-		writeKey: []byte("AAAAAAAAAAAAAAAA"),
-	}
-	net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeys.readKey, initKeys.writeKey)
-
-	// A -> B   FINDNODE (encrypted with zero keys)
-	findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{})
-	net.nodeB.expectDecode(t, p_unknownV5, findnode)
-
-	// A <- B   WHOAREYOU
-	challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce}
-	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
-	net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou)
-
-	// Check that new keys haven't been stored yet.
-	if s := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()); !bytes.Equal(s.writeKey, initKeys.writeKey) || !bytes.Equal(s.readKey, initKeys.readKey) {
-		t.Fatal("node A stored keys too early")
-	}
-	if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil {
-		t.Fatal("node B stored keys too early")
-	}
-
-	// A -> B   FINDNODE encrypted with new keys
-	findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{})
-	net.nodeB.expectDecode(t, p_findnodeV5, findnode)
-
-	// A <- B   NODES
-	nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1})
-	net.nodeA.expectDecode(t, p_nodesV5, nodes)
-}
-
-// In this test A and B have different keys before the handshake.
-func TestHandshakeV5_rekey2(t *testing.T) {
-	t.Parallel()
-	net := newHandshakeTest()
-	defer net.close()
-
-	initKeysA := &handshakeSecrets{
-		readKey:  []byte("BBBBBBBBBBBBBBBB"),
-		writeKey: []byte("AAAAAAAAAAAAAAAA"),
-	}
-	initKeysB := &handshakeSecrets{
-		readKey:  []byte("CCCCCCCCCCCCCCCC"),
-		writeKey: []byte("DDDDDDDDDDDDDDDD"),
-	}
-	net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA.readKey, initKeysA.writeKey)
-	net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB.readKey, initKeysA.writeKey)
-
-	// A -> B   FINDNODE encrypted with initKeysA
-	findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{Distance: 3})
-	net.nodeB.expectDecode(t, p_unknownV5, findnode)
-
-	// A <- B   WHOAREYOU
-	challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce}
-	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
-	net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou)
-
-	// A -> B   FINDNODE encrypted with new keys
-	findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{})
-	net.nodeB.expectDecode(t, p_findnodeV5, findnode)
-
-	// A <- B   NODES
-	nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1})
-	net.nodeA.expectDecode(t, p_nodesV5, nodes)
-}
-
-// This test checks some malformed packets.
-func TestDecodeErrorsV5(t *testing.T) {
-	t.Parallel()
-	net := newHandshakeTest()
-	defer net.close()
-
-	net.nodeA.expectDecodeErr(t, errTooShort, []byte{})
-	// TODO some more tests would be nice :)
-}
-
-// This benchmark checks performance of authHeader decoding, verification and key derivation.
-func BenchmarkV5_DecodeAuthSecp256k1(b *testing.B) {
-	net := newHandshakeTest()
-	defer net.close()
-
-	var (
-		idA       = net.nodeA.id()
-		addrA     = net.nodeA.addr()
-		challenge = &whoareyouV5{AuthTag: []byte("authresp"), RecordSeq: 0, node: net.nodeB.n()}
-		nonce     = make([]byte, gcmNonceSize)
-	)
-	header, _, _ := net.nodeA.c.makeAuthHeader(nonce, challenge)
-	challenge.node = nil // force ENR signature verification in decoder
-	b.ResetTimer()
-
-	for i := 0; i < b.N; i++ {
-		_, _, err := net.nodeB.c.decodeAuthResp(idA, addrA, header, challenge)
-		if err != nil {
-			b.Fatal(err)
-		}
-	}
-}
-
-// This benchmark checks how long it takes to decode an encrypted ping packet.
-func BenchmarkV5_DecodePing(b *testing.B) {
-	net := newHandshakeTest()
-	defer net.close()
-
-	r := []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17}
-	w := []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134}
-	net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), r, w)
-	net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), w, r)
-	addrB := net.nodeA.addr()
-	ping := &pingV5{ReqID: []byte("reqid"), ENRSeq: 5}
-	enc, _, err := net.nodeA.c.encode(net.nodeB.id(), addrB, ping, nil)
-	if err != nil {
-		b.Fatalf("can't encode: %v", err)
-	}
-	b.ResetTimer()
-
-	for i := 0; i < b.N; i++ {
-		_, _, p, _ := net.nodeB.c.decode(enc, addrB)
-		if _, ok := p.(*pingV5); !ok {
-			b.Fatalf("wrong packet type %T", p)
-		}
-	}
-}
-
-var pp = spew.NewDefaultConfig()
-
-type handshakeTest struct {
-	nodeA, nodeB handshakeTestNode
-	clock        mclock.Simulated
-}
-
-type handshakeTestNode struct {
-	ln *enode.LocalNode
-	c  *wireCodec
-}
-
-func newHandshakeTest() *handshakeTest {
-	t := new(handshakeTest)
-	t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock)
-	t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock)
-	return t
-}
-
-func (t *handshakeTest) close() {
-	t.nodeA.ln.Database().Close()
-	t.nodeB.ln.Database().Close()
-}
-
-func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) {
-	db, _ := enode.OpenDB("")
-	n.ln = enode.NewLocalNode(db, key)
-	n.ln.SetStaticIP(ip)
-	n.c = newWireCodec(n.ln, key, clock)
-}
-
-func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p packetV5) ([]byte, []byte) {
-	t.Helper()
-	return n.encodeWithChallenge(t, to, nil, p)
-}
-
-func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *whoareyouV5, p packetV5) ([]byte, []byte) {
-	t.Helper()
-	// Copy challenge and add destination node. This avoids sharing 'c' among the two codecs.
-	var challenge *whoareyouV5
-	if c != nil {
-		challengeCopy := *c
-		challenge = &challengeCopy
-		challenge.node = to.n()
-	}
-	// Encode to destination.
-	enc, authTag, err := n.c.encode(to.id(), to.addr(), p, challenge)
-	if err != nil {
-		t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err))
-	}
-	t.Logf("(%s) -> (%s)   %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.name(), hex.Dump(enc))
-	return enc, authTag
-}
-
-func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) packetV5 {
-	t.Helper()
-	dec, err := n.decode(p)
-	if err != nil {
-		t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err))
-	}
-	t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec))
-	if dec.kind() != ptype {
-		t.Fatalf("expected packet type %d, got %d", ptype, dec.kind())
-	}
-	return dec
-}
-
-func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) {
-	t.Helper()
-	if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) {
-		t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr))
-	}
-}
-
-func (n *handshakeTestNode) decode(input []byte) (packetV5, error) {
-	_, _, p, err := n.c.decode(input, "127.0.0.1")
-	return p, err
-}
-
-func (n *handshakeTestNode) n() *enode.Node {
-	return n.ln.Node()
-}
-
-func (n *handshakeTestNode) addr() string {
-	return n.ln.Node().IP().String()
-}
-
-func (n *handshakeTestNode) id() enode.ID {
-	return n.ln.ID()
-}
diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go
index 65550fc65f..71a39ea5a5 100644
--- a/p2p/discover/v5_udp.go
+++ b/p2p/discover/v5_udp.go
@@ -31,6 +31,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/common/mclock"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/p2p/netutil"
@@ -38,36 +39,24 @@ import (
 
 const (
 	lookupRequestLimit      = 3  // max requests against a single node during lookup
-	findnodeResultLimit     = 15 // applies in FINDNODE handler
+	findnodeResultLimit     = 16 // applies in FINDNODE handler
 	totalNodesResponseLimit = 5  // applies in waitForNodes
 	nodesResponseItemLimit  = 3  // applies in sendNodes
 
 	respTimeoutV5 = 700 * time.Millisecond
 )
 
-// codecV5 is implemented by wireCodec (and testCodec).
+// codecV5 is implemented by v5wire.Codec (and testCodec).
 //
 // The UDPv5 transport is split into two objects: the codec object deals with
 // encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns.
 type codecV5 interface {
-	// encode encodes a packet. The 'challenge' parameter is non-nil for calls which got a
-	// WHOAREYOU response.
-	encode(fromID enode.ID, fromAddr string, p packetV5, challenge *whoareyouV5) (enc []byte, authTag []byte, err error)
+	// Encode encodes a packet.
+	Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error)
 
-	// decode decodes a packet. It returns an *unknownV5 packet if decryption fails.
-	// The fromNode return value is non-nil when the input contains a handshake response.
-	decode(input []byte, fromAddr string) (fromID enode.ID, fromNode *enode.Node, p packetV5, err error)
-}
-
-// packetV5 is implemented by all discv5 packet type structs.
-type packetV5 interface {
-	// These methods provide information and set the request ID.
-	name() string
-	kind() byte
-	setreqid([]byte)
-	// handle should perform the appropriate action to handle the packet, i.e. this is the
-	// place to send the response.
-	handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr)
+	// decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails.
+	// The *enode.Node return value is non-nil when the input contains a handshake response.
+	Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error)
 }
 
 // UDPv5 is the implementation of protocol version 5.
@@ -83,6 +72,10 @@ type UDPv5 struct {
 	clock        mclock.Clock
 	validSchemes enr.IdentityScheme
 
+	// talkreq handler registry
+	trlock     sync.Mutex
+	trhandlers map[string]TalkRequestHandler
+
 	// channels into dispatch
 	packetInCh    chan ReadPacket
 	readNextCh    chan struct{}
@@ -93,7 +86,7 @@ type UDPv5 struct {
 	// state of dispatch
 	codec            codecV5
 	activeCallByNode map[enode.ID]*callV5
-	activeCallByAuth map[string]*callV5
+	activeCallByAuth map[v5wire.Nonce]*callV5
 	callQueue        map[enode.ID][]*callV5
 
 	// shutdown stuff
@@ -103,19 +96,22 @@ type UDPv5 struct {
 	wg             sync.WaitGroup
 }
 
+// TalkRequestHandler callback processes a talk request and optionally returns a reply
+type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte
+
 // callV5 represents a remote procedure call against another node.
 type callV5 struct {
 	node         *enode.Node
-	packet       packetV5
+	packet       v5wire.Packet
 	responseType byte // expected packet type of response
 	reqid        []byte
-	ch           chan packetV5 // responses sent here
-	err          chan error    // errors sent here
+	ch           chan v5wire.Packet // responses sent here
+	err          chan error         // errors sent here
 
 	// Valid for active calls only:
-	authTag        []byte       // authTag of request packet
-	handshakeCount int          // # times we attempted handshake for this call
-	challenge      *whoareyouV5 // last sent handshake challenge
+	nonce          v5wire.Nonce      // nonce of request packet
+	handshakeCount int               // # times we attempted handshake for this call
+	challenge      *v5wire.Whoareyou // last sent handshake challenge
 	timeout        mclock.Timer
 }
 
@@ -152,6 +148,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
 		log:          cfg.Log,
 		validSchemes: cfg.ValidSchemes,
 		clock:        cfg.Clock,
+		trhandlers:   make(map[string]TalkRequestHandler),
 		// channels into dispatch
 		packetInCh:    make(chan ReadPacket, 1),
 		readNextCh:    make(chan struct{}, 1),
@@ -159,9 +156,9 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
 		callDoneCh:    make(chan *callV5),
 		respTimeoutCh: make(chan *callTimeout),
 		// state of dispatch
-		codec:            newWireCodec(ln, cfg.PrivateKey, cfg.Clock),
+		codec:            v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock),
 		activeCallByNode: make(map[enode.ID]*callV5),
-		activeCallByAuth: make(map[string]*callV5),
+		activeCallByAuth: make(map[v5wire.Nonce]*callV5),
 		callQueue:        make(map[enode.ID][]*callV5),
 		// shutdown
 		closeCtx:       closeCtx,
@@ -216,6 +213,49 @@ func (t *UDPv5) Resolve(n *enode.Node) *enode.Node {
 	return n
 }
 
+// AllNodes returns all the nodes stored in the local table.
+func (t *UDPv5) AllNodes() []*enode.Node {
+	t.tab.mutex.Lock()
+	defer t.tab.mutex.Unlock()
+	nodes := make([]*enode.Node, 0)
+
+	for _, b := range &t.tab.buckets {
+		for _, n := range b.entries {
+			nodes = append(nodes, unwrapNode(n))
+		}
+	}
+	return nodes
+}
+
+// LocalNode returns the current local node running the
+// protocol.
+func (t *UDPv5) LocalNode() *enode.LocalNode {
+	return t.localNode
+}
+
+// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called
+// whenever a request for the given protocol is received and should return the response
+// data or nil.
+func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) {
+	t.trlock.Lock()
+	defer t.trlock.Unlock()
+	t.trhandlers[protocol] = handler
+}
+
+// TalkRequest sends a talk request to n and waits for a response.
+func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) {
+	req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
+	resp := t.call(n, v5wire.TalkResponseMsg, req)
+	defer t.callDone(resp)
+	select {
+	case respMsg := <-resp.ch:
+		return respMsg.(*v5wire.TalkResponse).Message, nil
+	case err := <-resp.err:
+		return nil, err
+	}
+}
+
+// RandomNodes returns an iterator that finds random nodes in the DHT.
 func (t *UDPv5) RandomNodes() enode.Iterator {
 	if t.tab.len() == 0 {
 		// All nodes were dropped, refresh. The very first query will hit this
@@ -263,16 +303,14 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
 		nodes = nodesByDistance{target: target}
 		err   error
 	)
-	for i := 0; i < lookupRequestLimit && len(nodes.entries) < findnodeResultLimit; i++ {
-		var r []*enode.Node
-		r, err = t.findnode(unwrapNode(destNode), dists[i])
-		if err == errClosed {
-			return nil, err
-		}
-		for _, n := range r {
-			if n.ID() != t.Self().ID() {
-				nodes.push(wrapNode(n), findnodeResultLimit)
-			}
+	var r []*enode.Node
+	r, err = t.findnode(unwrapNode(destNode), dists)
+	if err == errClosed {
+		return nil, err
+	}
+	for _, n := range r {
+		if n.ID() != t.Self().ID() {
+			nodes.push(wrapNode(n), findnodeResultLimit)
 		}
 	}
 	return nodes.entries, err
@@ -281,15 +319,15 @@ func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
 // lookupDistances computes the distance parameter for FINDNODE calls to dest.
 // It chooses distances adjacent to logdist(target, dest), e.g. for a target
 // with logdist(target, dest) = 255 the result is [255, 256, 254].
-func lookupDistances(target, dest enode.ID) (dists []int) {
+func lookupDistances(target, dest enode.ID) (dists []uint) {
 	td := enode.LogDist(target, dest)
-	dists = append(dists, td)
+	dists = append(dists, uint(td))
 	for i := 1; len(dists) < lookupRequestLimit; i++ {
 		if td+i < 256 {
-			dists = append(dists, td+i)
+			dists = append(dists, uint(td+i))
 		}
 		if td-i > 0 {
-			dists = append(dists, td-i)
+			dists = append(dists, uint(td-i))
 		}
 	}
 	return dists
@@ -297,11 +335,13 @@ func lookupDistances(target, dest enode.ID) (dists []int) {
 
 // ping calls PING on a node and waits for a PONG response.
 func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
-	resp := t.call(n, p_pongV5, &pingV5{ENRSeq: t.localNode.Node().Seq()})
+	req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()}
+	resp := t.call(n, v5wire.PongMsg, req)
 	defer t.callDone(resp)
+
 	select {
 	case pong := <-resp.ch:
-		return pong.(*pongV5).ENRSeq, nil
+		return pong.(*v5wire.Pong).ENRSeq, nil
 	case err := <-resp.err:
 		return 0, err
 	}
@@ -309,7 +349,7 @@ func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
 
 // requestENR requests n's record.
 func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
-	nodes, err := t.findnode(n, 0)
+	nodes, err := t.findnode(n, []uint{0})
 	if err != nil {
 		return nil, err
 	}
@@ -319,26 +359,14 @@ func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
 	return nodes[0], nil
 }
 
-// requestTicket calls REQUESTTICKET on a node and waits for a TICKET response.
-func (t *UDPv5) requestTicket(n *enode.Node) ([]byte, error) {
-	resp := t.call(n, p_ticketV5, &pingV5{})
-	defer t.callDone(resp)
-	select {
-	case response := <-resp.ch:
-		return response.(*ticketV5).Ticket, nil
-	case err := <-resp.err:
-		return nil, err
-	}
-}
-
 // findnode calls FINDNODE on a node and waits for responses.
-func (t *UDPv5) findnode(n *enode.Node, distance int) ([]*enode.Node, error) {
-	resp := t.call(n, p_nodesV5, &findnodeV5{Distance: uint(distance)})
-	return t.waitForNodes(resp, distance)
+func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
+	resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances})
+	return t.waitForNodes(resp, distances)
 }
 
 // waitForNodes waits for NODES responses to the given call.
-func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) {
+func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) {
 	defer t.callDone(c)
 
 	var (
@@ -349,11 +377,11 @@ func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) {
 	for {
 		select {
 		case responseP := <-c.ch:
-			response := responseP.(*nodesV5)
+			response := responseP.(*v5wire.Nodes)
 			for _, record := range response.Nodes {
-				node, err := t.verifyResponseNode(c, record, distance, seen)
+				node, err := t.verifyResponseNode(c, record, distances, seen)
 				if err != nil {
-					t.log.Debug("Invalid record in "+response.name(), "id", c.node.ID(), "err", err)
+					t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err)
 					continue
 				}
 				nodes = append(nodes, node)
@@ -371,7 +399,7 @@ func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) {
 }
 
 // verifyResponseNode checks validity of a record in a NODES response.
-func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen map[enode.ID]struct{}) (*enode.Node, error) {
+func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) {
 	node, err := enode.New(t.validSchemes, r)
 	if err != nil {
 		return nil, err
@@ -382,9 +410,10 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen
 	if c.node.UDP() <= 1024 {
 		return nil, errLowPort
 	}
-	if distance != -1 {
-		if d := enode.LogDist(c.node.ID(), node.ID()); d != distance {
-			return nil, fmt.Errorf("wrong distance %d", d)
+	if distances != nil {
+		nd := enode.LogDist(c.node.ID(), node.ID())
+		if !containsUint(uint(nd), distances) {
+			return nil, errors.New("does not match any requested distance")
 		}
 	}
 	if _, ok := seen[node.ID()]; ok {
@@ -394,20 +423,29 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen
 	return node, nil
 }
 
-// call sends the given call and sets up a handler for response packets (of type c.responseType).
-// Responses are dispatched to the call's response channel.
-func (t *UDPv5) call(node *enode.Node, responseType byte, packet packetV5) *callV5 {
+func containsUint(x uint, xs []uint) bool {
+	for _, v := range xs {
+		if x == v {
+			return true
+		}
+	}
+	return false
+}
+
+// call sends the given call and sets up a handler for response packets (of message type
+// responseType). Responses are dispatched to the call's response channel.
+func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 {
 	c := &callV5{
 		node:         node,
 		packet:       packet,
 		responseType: responseType,
 		reqid:        make([]byte, 8),
-		ch:           make(chan packetV5, 1),
+		ch:           make(chan v5wire.Packet, 1),
 		err:          make(chan error, 1),
 	}
 	// Assign request ID.
 	crand.Read(c.reqid)
-	packet.setreqid(c.reqid)
+	packet.SetRequestID(c.reqid)
 	// Send call to dispatch.
 	select {
 	case t.callCh <- c:
@@ -473,7 +511,7 @@ func (t *UDPv5) dispatch() {
 				panic("BUG: callDone for inactive call")
 			}
 			c.timeout.Stop()
-			delete(t.activeCallByAuth, string(c.authTag))
+			delete(t.activeCallByAuth, c.nonce)
 			delete(t.activeCallByNode, id)
 			t.sendNextCall(id)
 
@@ -493,7 +531,7 @@ func (t *UDPv5) dispatch() {
 			for id, c := range t.activeCallByNode {
 				c.err <- errClosed
 				delete(t.activeCallByNode, id)
-				delete(t.activeCallByAuth, string(c.authTag))
+				delete(t.activeCallByAuth, c.nonce)
 			}
 			return
 		}
@@ -539,38 +577,37 @@ func (t *UDPv5) sendNextCall(id enode.ID) {
 // sendCall encodes and sends a request packet to the call's recipient node.
 // This performs a handshake if needed.
 func (t *UDPv5) sendCall(c *callV5) {
-	if len(c.authTag) > 0 {
-		// The call already has an authTag from a previous handshake attempt. Remove the
-		// entry for the authTag because we're about to generate a new authTag for this
-		// call.
-		delete(t.activeCallByAuth, string(c.authTag))
+	// The call might have a nonce from a previous handshake attempt. Remove the entry for
+	// the old nonce because we're about to generate a new nonce for this call.
+	if c.nonce != (v5wire.Nonce{}) {
+		delete(t.activeCallByAuth, c.nonce)
 	}
 
 	addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()}
-	newTag, _ := t.send(c.node.ID(), addr, c.packet, c.challenge)
-	c.authTag = newTag
-	t.activeCallByAuth[string(c.authTag)] = c
+	newNonce, _ := t.send(c.node.ID(), addr, c.packet, c.challenge)
+	c.nonce = newNonce
+	t.activeCallByAuth[newNonce] = c
 	t.startResponseTimeout(c)
 }
 
 // sendResponse sends a response packet to the given node.
 // This doesn't trigger a handshake even if no keys are available.
-func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet packetV5) error {
+func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
 	_, err := t.send(toID, toAddr, packet, nil)
 	return err
 }
 
 // send sends a packet to the given node.
-func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet packetV5, c *whoareyouV5) ([]byte, error) {
+func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
 	addr := toAddr.String()
-	enc, authTag, err := t.codec.encode(toID, addr, packet, c)
+	enc, nonce, err := t.codec.Encode(toID, addr, packet, c)
 	if err != nil {
-		t.log.Warn(">> "+packet.name(), "id", toID, "addr", addr, "err", err)
-		return authTag, err
+		t.log.Warn(">> "+packet.Name(), "id", toID, "addr", addr, "err", err)
+		return nonce, err
 	}
 	_, err = t.conn.WriteToUDP(enc, toAddr)
-	t.log.Trace(">> "+packet.name(), "id", toID, "addr", addr)
-	return authTag, err
+	t.log.Trace(">> "+packet.Name(), "id", toID, "addr", addr)
+	return nonce, err
 }
 
 // readLoop runs in its own goroutine and reads packets from the network.
@@ -608,7 +645,7 @@ func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
 // handlePacket decodes and processes an incoming packet from the network.
 func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
 	addr := fromAddr.String()
-	fromID, fromNode, packet, err := t.codec.decode(rawpacket, addr)
+	fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
 	if err != nil {
 		t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err)
 		return err
@@ -617,31 +654,32 @@ func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
 		// Handshake succeeded, add to table.
 		t.tab.addSeenNode(wrapNode(fromNode))
 	}
-	if packet.kind() != p_whoareyouV5 {
-		// WHOAREYOU logged separately to report the sender ID.
-		t.log.Trace("<< "+packet.name(), "id", fromID, "addr", addr)
+	if packet.Kind() != v5wire.WhoareyouPacket {
+		// WHOAREYOU logged separately to report errors.
+		t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", addr)
 	}
-	packet.handle(t, fromID, fromAddr)
+	t.handle(packet, fromID, fromAddr)
 	return nil
 }
 
 // handleCallResponse dispatches a response packet to the call waiting for it.
-func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, reqid []byte, p packetV5) {
+func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
 	ac := t.activeCallByNode[fromID]
-	if ac == nil || !bytes.Equal(reqid, ac.reqid) {
-		t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.name()), "id", fromID, "addr", fromAddr)
-		return
+	if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
+		t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
+		return false
 	}
 	if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() {
-		t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.name()), "id", fromID, "addr", fromAddr)
-		return
+		t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
+		return false
 	}
-	if p.kind() != ac.responseType {
-		t.log.Debug(fmt.Sprintf("Wrong disv5 response type %s", p.name()), "id", fromID, "addr", fromAddr)
-		return
+	if p.Kind() != ac.responseType {
+		t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr)
+		return false
 	}
 	t.startResponseTimeout(ac)
 	ac.ch <- p
+	return true
 }
 
 // getNode looks for a node record in table and database.
@@ -655,50 +693,65 @@ func (t *UDPv5) getNode(id enode.ID) *enode.Node {
 	return nil
 }
 
-// UNKNOWN
-
-func (p *unknownV5) name() string       { return "UNKNOWN/v5" }
-func (p *unknownV5) kind() byte         { return p_unknownV5 }
-func (p *unknownV5) setreqid(id []byte) {}
+// handle processes incoming packets according to their message type.
+func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
+	switch p := p.(type) {
+	case *v5wire.Unknown:
+		t.handleUnknown(p, fromID, fromAddr)
+	case *v5wire.Whoareyou:
+		t.handleWhoareyou(p, fromID, fromAddr)
+	case *v5wire.Ping:
+		t.handlePing(p, fromID, fromAddr)
+	case *v5wire.Pong:
+		if t.handleCallResponse(fromID, fromAddr, p) {
+			t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
+		}
+	case *v5wire.Findnode:
+		t.handleFindnode(p, fromID, fromAddr)
+	case *v5wire.Nodes:
+		t.handleCallResponse(fromID, fromAddr, p)
+	case *v5wire.TalkRequest:
+		t.handleTalkRequest(p, fromID, fromAddr)
+	case *v5wire.TalkResponse:
+		t.handleCallResponse(fromID, fromAddr, p)
+	}
+}
 
-func (p *unknownV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	challenge := &whoareyouV5{AuthTag: p.AuthTag}
+// handleUnknown initiates a handshake by responding with WHOAREYOU.
+func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
+	challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
 	crand.Read(challenge.IDNonce[:])
 	if n := t.getNode(fromID); n != nil {
-		challenge.node = n
+		challenge.Node = n
 		challenge.RecordSeq = n.Seq()
 	}
 	t.sendResponse(fromID, fromAddr, challenge)
 }
 
-// WHOAREYOU
-
-func (p *whoareyouV5) name() string       { return "WHOAREYOU/v5" }
-func (p *whoareyouV5) kind() byte         { return p_whoareyouV5 }
-func (p *whoareyouV5) setreqid(id []byte) {}
+var (
+	errChallengeNoCall = errors.New("no matching call")
+	errChallengeTwice  = errors.New("second handshake")
+)
 
-func (p *whoareyouV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	c, err := p.matchWithCall(t, p.AuthTag)
+// handleWhoareyou resends the active call as a handshake packet.
+func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
+	c, err := t.matchWithCall(fromID, p.Nonce)
 	if err != nil {
-		t.log.Debug("Invalid WHOAREYOU/v5", "addr", fromAddr, "err", err)
+		t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
 		return
 	}
+
 	// Resend the call that was answered by WHOAREYOU.
-	t.log.Trace("<< "+p.name(), "id", c.node.ID(), "addr", fromAddr)
+	t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr)
 	c.handshakeCount++
 	c.challenge = p
-	p.node = c.node
+	p.Node = c.node
 	t.sendCall(c)
 }
 
-var (
-	errChallengeNoCall = errors.New("no matching call")
-	errChallengeTwice  = errors.New("second handshake")
-)
-
-// matchWithCall checks whether the handshake attempt matches the active call.
-func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) {
-	c := t.activeCallByAuth[string(authTag)]
+// matchWithCall checks whether a handshake attempt matches the active call.
+func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) {
+	c := t.activeCallByAuth[nonce]
 	if c == nil {
 		return nil, errChallengeNoCall
 	}
@@ -708,136 +761,98 @@ func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) {
 	return c, nil
 }
 
-// PING
-
-func (p *pingV5) name() string       { return "PING/v5" }
-func (p *pingV5) kind() byte         { return p_pingV5 }
-func (p *pingV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *pingV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.sendResponse(fromID, fromAddr, &pongV5{
+// handlePing sends a PONG response.
+func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
+	remoteIP := fromAddr.IP
+	// Handle IPv4 mapped IPv6 addresses in the
+	// event the local node is binded to an
+	// ipv6 interface.
+	if remoteIP.To4() != nil {
+		remoteIP = remoteIP.To4()
+	}
+	t.sendResponse(fromID, fromAddr, &v5wire.Pong{
 		ReqID:  p.ReqID,
-		ToIP:   fromAddr.IP,
+		ToIP:   remoteIP,
 		ToPort: uint16(fromAddr.Port),
 		ENRSeq: t.localNode.Node().Seq(),
 	})
 }
 
-// PONG
-
-func (p *pongV5) name() string       { return "PONG/v5" }
-func (p *pongV5) kind() byte         { return p_pongV5 }
-func (p *pongV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *pongV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
-	t.handleCallResponse(fromID, fromAddr, p.ReqID, p)
+// handleFindnode returns nodes to the requester.
+func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
+	nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
+	for _, resp := range packNodes(p.ReqID, nodes) {
+		t.sendResponse(fromID, fromAddr, resp)
+	}
 }
 
-// FINDNODE
+// collectTableNodes creates a FINDNODE result set for the given distances.
+func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
+	var nodes []*enode.Node
+	var processed = make(map[uint]struct{})
+	for _, dist := range distances {
+		// Reject duplicate / invalid distances.
+		_, seen := processed[dist]
+		if seen || dist > 256 {
+			continue
+		}
 
-func (p *findnodeV5) name() string       { return "FINDNODE/v5" }
-func (p *findnodeV5) kind() byte         { return p_findnodeV5 }
-func (p *findnodeV5) setreqid(id []byte) { p.ReqID = id }
+		// Get the nodes.
+		var bn []*enode.Node
+		if dist == 0 {
+			bn = []*enode.Node{t.Self()}
+		} else if dist <= 256 {
+			t.tab.mutex.Lock()
+			bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries)
+			t.tab.mutex.Unlock()
+		}
+		processed[dist] = struct{}{}
 
-func (p *findnodeV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	if p.Distance == 0 {
-		t.sendNodes(fromID, fromAddr, p.ReqID, []*enode.Node{t.Self()})
-		return
-	}
-	if p.Distance > 256 {
-		p.Distance = 256
-	}
-	// Get bucket entries.
-	t.tab.mutex.Lock()
-	nodes := unwrapNodes(t.tab.bucketAtDistance(int(p.Distance)).entries)
-	t.tab.mutex.Unlock()
-	if len(nodes) > findnodeResultLimit {
-		nodes = nodes[:findnodeResultLimit]
+		// Apply some pre-checks to avoid sending invalid nodes.
+		for _, n := range bn {
+			// TODO livenessChecks > 1
+			if netutil.CheckRelayIP(rip, n.IP()) != nil {
+				continue
+			}
+			nodes = append(nodes, n)
+			if len(nodes) >= limit {
+				return nodes
+			}
+		}
 	}
-	t.sendNodes(fromID, fromAddr, p.ReqID, nodes)
+	return nodes
 }
 
-// sendNodes sends the given records in one or more NODES packets.
-func (t *UDPv5) sendNodes(toID enode.ID, toAddr *net.UDPAddr, reqid []byte, nodes []*enode.Node) {
-	// TODO livenessChecks > 1
-	// TODO CheckRelayIP
+// packNodes creates NODES response packets for the given node list.
+func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes {
+	if len(nodes) == 0 {
+		return []*v5wire.Nodes{{ReqID: reqid, Total: 1}}
+	}
+
 	total := uint8(math.Ceil(float64(len(nodes)) / 3))
-	resp := &nodesV5{ReqID: reqid, Total: total, Nodes: make([]*enr.Record, 3)}
-	sent := false
+	var resp []*v5wire.Nodes
 	for len(nodes) > 0 {
+		p := &v5wire.Nodes{ReqID: reqid, Total: total}
 		items := min(nodesResponseItemLimit, len(nodes))
-		resp.Nodes = resp.Nodes[:items]
 		for i := 0; i < items; i++ {
-			resp.Nodes[i] = nodes[i].Record()
+			p.Nodes = append(p.Nodes, nodes[i].Record())
 		}
-		t.sendResponse(toID, toAddr, resp)
 		nodes = nodes[items:]
-		sent = true
-	}
-	// Ensure at least one response is sent.
-	if !sent {
-		resp.Total = 1
-		resp.Nodes = nil
-		t.sendResponse(toID, toAddr, resp)
+		resp = append(resp, p)
 	}
+	return resp
 }
 
-// NODES
-
-func (p *nodesV5) name() string       { return "NODES/v5" }
-func (p *nodesV5) kind() byte         { return p_nodesV5 }
-func (p *nodesV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *nodesV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.handleCallResponse(fromID, fromAddr, p.ReqID, p)
-}
-
-// REQUESTTICKET
-
-func (p *requestTicketV5) name() string       { return "REQUESTTICKET/v5" }
-func (p *requestTicketV5) kind() byte         { return p_requestTicketV5 }
-func (p *requestTicketV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *requestTicketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.sendResponse(fromID, fromAddr, &ticketV5{ReqID: p.ReqID})
-}
-
-// TICKET
-
-func (p *ticketV5) name() string       { return "TICKET/v5" }
-func (p *ticketV5) kind() byte         { return p_ticketV5 }
-func (p *ticketV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *ticketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.handleCallResponse(fromID, fromAddr, p.ReqID, p)
-}
-
-// REGTOPIC
-
-func (p *regtopicV5) name() string       { return "REGTOPIC/v5" }
-func (p *regtopicV5) kind() byte         { return p_regtopicV5 }
-func (p *regtopicV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *regtopicV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.sendResponse(fromID, fromAddr, ®confirmationV5{ReqID: p.ReqID, Registered: false})
-}
-
-// REGCONFIRMATION
+// handleTalkRequest runs the talk request handler of the requested protocol.
+func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAddr *net.UDPAddr) {
+	t.trlock.Lock()
+	handler := t.trhandlers[p.Protocol]
+	t.trlock.Unlock()
 
-func (p *regconfirmationV5) name() string       { return "REGCONFIRMATION/v5" }
-func (p *regconfirmationV5) kind() byte         { return p_regconfirmationV5 }
-func (p *regconfirmationV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *regconfirmationV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
-	t.handleCallResponse(fromID, fromAddr, p.ReqID, p)
-}
-
-// TOPICQUERY
-
-func (p *topicqueryV5) name() string       { return "TOPICQUERY/v5" }
-func (p *topicqueryV5) kind() byte         { return p_topicqueryV5 }
-func (p *topicqueryV5) setreqid(id []byte) { p.ReqID = id }
-
-func (p *topicqueryV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) {
+	var response []byte
+	if handler != nil {
+		response = handler(fromID, fromAddr, p.Message)
+	}
+	resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response}
+	t.sendResponse(fromID, fromAddr, resp)
 }
diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go
index 15ea0402c2..f061f5ab41 100644
--- a/p2p/discover/v5_udp_test.go
+++ b/p2p/discover/v5_udp_test.go
@@ -24,22 +24,25 @@ import (
 	"math/rand"
 	"net"
 	"reflect"
+	"sort"
 	"testing"
 	"time"
 
 	"github.com/ethereum/go-ethereum/internal/testlog"
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
 // Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5.
-func TestEndToEndV5(t *testing.T) {
+func TestUDPv5_lookupE2E(t *testing.T) {
 	t.Parallel()
 
+	const N = 5
 	var nodes []*UDPv5
-	for i := 0; i < 5; i++ {
+	for i := 0; i < N; i++ {
 		var cfg Config
 		if len(nodes) > 0 {
 			bn := nodes[0].Self()
@@ -49,12 +52,22 @@ func TestEndToEndV5(t *testing.T) {
 		nodes = append(nodes, node)
 		defer node.Close()
 	}
+	last := nodes[N-1]
+	target := nodes[rand.Intn(N-2)].Self()
 
-	last := nodes[len(nodes)-1]
-	target := nodes[rand.Intn(len(nodes)-2)].Self()
+	// It is expected that all nodes can be found.
+	expectedResult := make([]*enode.Node, len(nodes))
+	for i := range nodes {
+		expectedResult[i] = nodes[i].Self()
+	}
+	sort.Slice(expectedResult, func(i, j int) bool {
+		return enode.DistCmp(target.ID(), expectedResult[i].ID(), expectedResult[j].ID()) < 0
+	})
+
+	// Do the lookup.
 	results := last.Lookup(target.ID())
-	if len(results) == 0 || results[0].ID() != target.ID() {
-		t.Fatalf("lookup returned wrong results: %v", results)
+	if err := checkNodesEqual(results, expectedResult); err != nil {
+		t.Fatalf("lookup returned wrong results: %v", err)
 	}
 }
 
@@ -93,8 +106,8 @@ func TestUDPv5_pingHandling(t *testing.T) {
 	test := newUDPV5Test(t)
 	defer test.close()
 
-	test.packetIn(&pingV5{ReqID: []byte("foo")})
-	test.waitPacketOut(func(p *pongV5, addr *net.UDPAddr, authTag []byte) {
+	test.packetIn(&v5wire.Ping{ReqID: []byte("foo")})
+	test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
 		if !bytes.Equal(p.ReqID, []byte("foo")) {
 			t.Error("wrong request ID in response:", p.ReqID)
 		}
@@ -110,13 +123,13 @@ func TestUDPv5_unknownPacket(t *testing.T) {
 	test := newUDPV5Test(t)
 	defer test.close()
 
-	authTag := [12]byte{1, 2, 3}
-	check := func(p *whoareyouV5, wantSeq uint64) {
+	nonce := v5wire.Nonce{1, 2, 3}
+	check := func(p *v5wire.Whoareyou, wantSeq uint64) {
 		t.Helper()
-		if !bytes.Equal(p.AuthTag, authTag[:]) {
-			t.Error("wrong token in WHOAREYOU:", p.AuthTag, authTag[:])
+		if p.Nonce != nonce {
+			t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce)
 		}
-		if p.IDNonce == ([32]byte{}) {
+		if p.IDNonce == ([16]byte{}) {
 			t.Error("all zero ID nonce")
 		}
 		if p.RecordSeq != wantSeq {
@@ -125,8 +138,8 @@ func TestUDPv5_unknownPacket(t *testing.T) {
 	}
 
 	// Unknown packet from unknown node.
-	test.packetIn(&unknownV5{AuthTag: authTag[:]})
-	test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) {
+	test.packetIn(&v5wire.Unknown{Nonce: nonce})
+	test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
 		check(p, 0)
 	})
 
@@ -134,8 +147,8 @@ func TestUDPv5_unknownPacket(t *testing.T) {
 	n := test.getNode(test.remotekey, test.remoteaddr).Node()
 	test.table.addSeenNode(wrapNode(n))
 
-	test.packetIn(&unknownV5{AuthTag: authTag[:]})
-	test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) {
+	test.packetIn(&v5wire.Unknown{Nonce: nonce})
+	test.waitPacketOut(func(p *v5wire.Whoareyou, addr *net.UDPAddr, _ v5wire.Nonce) {
 		check(p, n.Seq())
 	})
 }
@@ -147,24 +160,40 @@ func TestUDPv5_findnodeHandling(t *testing.T) {
 	defer test.close()
 
 	// Create test nodes and insert them into the table.
-	nodes := nodesAtDistance(test.table.self().ID(), 253, 10)
-	fillTable(test.table, wrapNodes(nodes))
+	nodes253 := nodesAtDistance(test.table.self().ID(), 253, 10)
+	nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4)
+	nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10)
+	fillTable(test.table, wrapNodes(nodes253))
+	fillTable(test.table, wrapNodes(nodes249))
+	fillTable(test.table, wrapNodes(nodes248))
 
 	// Requesting with distance zero should return the node's own record.
-	test.packetIn(&findnodeV5{ReqID: []byte{0}, Distance: 0})
+	test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}})
 	test.expectNodes([]byte{0}, 1, []*enode.Node{test.udp.Self()})
 
-	// Requesting with distance > 256 caps it at 256.
-	test.packetIn(&findnodeV5{ReqID: []byte{1}, Distance: 4234098})
+	// Requesting with distance > 256 shouldn't crash.
+	test.packetIn(&v5wire.Findnode{ReqID: []byte{1}, Distances: []uint{4234098}})
 	test.expectNodes([]byte{1}, 1, nil)
 
-	// This request gets no nodes because the corresponding bucket is empty.
-	test.packetIn(&findnodeV5{ReqID: []byte{2}, Distance: 254})
+	// Requesting with empty distance list shouldn't crash either.
+	test.packetIn(&v5wire.Findnode{ReqID: []byte{2}, Distances: []uint{}})
 	test.expectNodes([]byte{2}, 1, nil)
 
-	// This request gets all test nodes.
-	test.packetIn(&findnodeV5{ReqID: []byte{3}, Distance: 253})
-	test.expectNodes([]byte{3}, 4, nodes)
+	// This request gets no nodes because the corresponding bucket is empty.
+	test.packetIn(&v5wire.Findnode{ReqID: []byte{3}, Distances: []uint{254}})
+	test.expectNodes([]byte{3}, 1, nil)
+
+	// This request gets all the distance-253 nodes.
+	test.packetIn(&v5wire.Findnode{ReqID: []byte{4}, Distances: []uint{253}})
+	test.expectNodes([]byte{4}, 4, nodes253)
+
+	// This request gets all the distance-249 nodes and some more at 248 because
+	// the bucket at 249 is not full.
+	test.packetIn(&v5wire.Findnode{ReqID: []byte{5}, Distances: []uint{249, 248}})
+	var nodes []*enode.Node
+	nodes = append(nodes, nodes249...)
+	nodes = append(nodes, nodes248[:10]...)
+	test.expectNodes([]byte{5}, 5, nodes)
 }
 
 func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes []*enode.Node) {
@@ -172,16 +201,17 @@ func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes
 	for _, n := range wantNodes {
 		nodeSet[n.ID()] = n.Record()
 	}
+
 	for {
-		test.waitPacketOut(func(p *nodesV5, addr *net.UDPAddr, authTag []byte) {
+		test.waitPacketOut(func(p *v5wire.Nodes, addr *net.UDPAddr, _ v5wire.Nonce) {
+			if !bytes.Equal(p.ReqID, wantReqID) {
+				test.t.Fatalf("wrong request ID %v in response, want %v", p.ReqID, wantReqID)
+			}
 			if len(p.Nodes) > 3 {
 				test.t.Fatalf("too many nodes in response")
 			}
 			if p.Total != wantTotal {
-				test.t.Fatalf("wrong total response count %d", p.Total)
-			}
-			if !bytes.Equal(p.ReqID, wantReqID) {
-				test.t.Fatalf("wrong request ID in response: %v", p.ReqID)
+				test.t.Fatalf("wrong total response count %d, want %d", p.Total, wantTotal)
 			}
 			for _, record := range p.Nodes {
 				n, _ := enode.New(enode.ValidSchemesForTesting, record)
@@ -215,7 +245,7 @@ func TestUDPv5_pingCall(t *testing.T) {
 		_, err := test.udp.ping(remote)
 		done <- err
 	}()
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {})
 	if err := <-done; err != errTimeout {
 		t.Fatalf("want errTimeout, got %q", err)
 	}
@@ -225,8 +255,8 @@ func TestUDPv5_pingCall(t *testing.T) {
 		_, err := test.udp.ping(remote)
 		done <- err
 	}()
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
-		test.packetInFrom(test.remotekey, test.remoteaddr, &pongV5{ReqID: p.ReqID})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
+		test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.Pong{ReqID: p.ReqID})
 	})
 	if err := <-done; err != nil {
 		t.Fatal(err)
@@ -237,9 +267,9 @@ func TestUDPv5_pingCall(t *testing.T) {
 		_, err := test.udp.ping(remote)
 		done <- err
 	}()
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
 		wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101}
-		test.packetInFrom(test.remotekey, wrongAddr, &pongV5{ReqID: p.ReqID})
+		test.packetInFrom(test.remotekey, wrongAddr, &v5wire.Pong{ReqID: p.ReqID})
 	})
 	if err := <-done; err != errTimeout {
 		t.Fatalf("want errTimeout for reply from wrong IP, got %q", err)
@@ -255,29 +285,29 @@ func TestUDPv5_findnodeCall(t *testing.T) {
 
 	// Launch the request:
 	var (
-		distance = 230
-		remote   = test.getNode(test.remotekey, test.remoteaddr).Node()
-		nodes    = nodesAtDistance(remote.ID(), distance, 8)
-		done     = make(chan error, 1)
-		response []*enode.Node
+		distances = []uint{230}
+		remote    = test.getNode(test.remotekey, test.remoteaddr).Node()
+		nodes     = nodesAtDistance(remote.ID(), int(distances[0]), 8)
+		done      = make(chan error, 1)
+		response  []*enode.Node
 	)
 	go func() {
 		var err error
-		response, err = test.udp.findnode(remote, distance)
+		response, err = test.udp.findnode(remote, distances)
 		done <- err
 	}()
 
 	// Serve the responses:
-	test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) {
-		if p.Distance != uint(distance) {
-			t.Fatalf("wrong bucket: %d", p.Distance)
+	test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
+		if !reflect.DeepEqual(p.Distances, distances) {
+			t.Fatalf("wrong distances in request: %v", p.Distances)
 		}
-		test.packetIn(&nodesV5{
+		test.packetIn(&v5wire.Nodes{
 			ReqID: p.ReqID,
 			Total: 2,
 			Nodes: nodesToRecords(nodes[:4]),
 		})
-		test.packetIn(&nodesV5{
+		test.packetIn(&v5wire.Nodes{
 			ReqID: p.ReqID,
 			Total: 2,
 			Nodes: nodesToRecords(nodes[4:]),
@@ -314,16 +344,16 @@ func TestUDPv5_callResend(t *testing.T) {
 	}()
 
 	// Ping answered by WHOAREYOU.
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
-		test.packetIn(&whoareyouV5{AuthTag: authTag})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
+		test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
 	})
 	// Ping should be re-sent.
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
-		test.packetIn(&pongV5{ReqID: p.ReqID})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
+		test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
 	})
 	// Answer the other ping.
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
-		test.packetIn(&pongV5{ReqID: p.ReqID})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, _ v5wire.Nonce) {
+		test.packetIn(&v5wire.Pong{ReqID: p.ReqID})
 	})
 	if err := <-done; err != nil {
 		t.Fatalf("unexpected ping error: %v", err)
@@ -347,12 +377,12 @@ func TestUDPv5_multipleHandshakeRounds(t *testing.T) {
 	}()
 
 	// Ping answered by WHOAREYOU.
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
-		test.packetIn(&whoareyouV5{AuthTag: authTag})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
+		test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
 	})
 	// Ping answered by WHOAREYOU again.
-	test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {
-		test.packetIn(&whoareyouV5{AuthTag: authTag})
+	test.waitPacketOut(func(p *v5wire.Ping, addr *net.UDPAddr, nonce v5wire.Nonce) {
+		test.packetIn(&v5wire.Whoareyou{Nonce: nonce})
 	})
 	if err := <-done; err != errTimeout {
 		t.Fatalf("unexpected ping error: %q", err)
@@ -367,27 +397,27 @@ func TestUDPv5_callTimeoutReset(t *testing.T) {
 
 	// Launch the request:
 	var (
-		distance = 230
+		distance = uint(230)
 		remote   = test.getNode(test.remotekey, test.remoteaddr).Node()
-		nodes    = nodesAtDistance(remote.ID(), distance, 8)
+		nodes    = nodesAtDistance(remote.ID(), int(distance), 8)
 		done     = make(chan error, 1)
 	)
 	go func() {
-		_, err := test.udp.findnode(remote, distance)
+		_, err := test.udp.findnode(remote, []uint{distance})
 		done <- err
 	}()
 
 	// Serve two responses, slowly.
-	test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) {
+	test.waitPacketOut(func(p *v5wire.Findnode, addr *net.UDPAddr, _ v5wire.Nonce) {
 		time.Sleep(respTimeout - 50*time.Millisecond)
-		test.packetIn(&nodesV5{
+		test.packetIn(&v5wire.Nodes{
 			ReqID: p.ReqID,
 			Total: 2,
 			Nodes: nodesToRecords(nodes[:4]),
 		})
 
 		time.Sleep(respTimeout - 50*time.Millisecond)
-		test.packetIn(&nodesV5{
+		test.packetIn(&v5wire.Nodes{
 			ReqID: p.ReqID,
 			Total: 2,
 			Nodes: nodesToRecords(nodes[4:]),
@@ -398,6 +428,97 @@ func TestUDPv5_callTimeoutReset(t *testing.T) {
 	}
 }
 
+// This test checks that TALKREQ calls the registered handler function.
+func TestUDPv5_talkHandling(t *testing.T) {
+	t.Parallel()
+	test := newUDPV5Test(t)
+	defer test.close()
+
+	var recvMessage []byte
+	test.udp.RegisterTalkHandler("test", func(id enode.ID, addr *net.UDPAddr, message []byte) []byte {
+		recvMessage = message
+		return []byte("test response")
+	})
+
+	// Successful case:
+	test.packetIn(&v5wire.TalkRequest{
+		ReqID:    []byte("foo"),
+		Protocol: "test",
+		Message:  []byte("test request"),
+	})
+	test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
+		if !bytes.Equal(p.ReqID, []byte("foo")) {
+			t.Error("wrong request ID in response:", p.ReqID)
+		}
+		if string(p.Message) != "test response" {
+			t.Errorf("wrong talk response message: %q", p.Message)
+		}
+		if string(recvMessage) != "test request" {
+			t.Errorf("wrong message received in handler: %q", recvMessage)
+		}
+	})
+
+	// Check that empty response is returned for unregistered protocols.
+	recvMessage = nil
+	test.packetIn(&v5wire.TalkRequest{
+		ReqID:    []byte("2"),
+		Protocol: "wrong",
+		Message:  []byte("test request"),
+	})
+	test.waitPacketOut(func(p *v5wire.TalkResponse, addr *net.UDPAddr, _ v5wire.Nonce) {
+		if !bytes.Equal(p.ReqID, []byte("2")) {
+			t.Error("wrong request ID in response:", p.ReqID)
+		}
+		if string(p.Message) != "" {
+			t.Errorf("wrong talk response message: %q", p.Message)
+		}
+		if recvMessage != nil {
+			t.Errorf("handler was called for wrong protocol: %q", recvMessage)
+		}
+	})
+}
+
+// This test checks that outgoing TALKREQ calls work.
+func TestUDPv5_talkRequest(t *testing.T) {
+	t.Parallel()
+	test := newUDPV5Test(t)
+	defer test.close()
+
+	remote := test.getNode(test.remotekey, test.remoteaddr).Node()
+	done := make(chan error, 1)
+
+	// This request times out.
+	go func() {
+		_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
+		done <- err
+	}()
+	test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {})
+	if err := <-done; err != errTimeout {
+		t.Fatalf("want errTimeout, got %q", err)
+	}
+
+	// This request works.
+	go func() {
+		_, err := test.udp.TalkRequest(remote, "test", []byte("test request"))
+		done <- err
+	}()
+	test.waitPacketOut(func(p *v5wire.TalkRequest, addr *net.UDPAddr, _ v5wire.Nonce) {
+		if p.Protocol != "test" {
+			t.Errorf("wrong protocol ID in talk request: %q", p.Protocol)
+		}
+		if string(p.Message) != "test request" {
+			t.Errorf("wrong message talk request: %q", p.Message)
+		}
+		test.packetInFrom(test.remotekey, test.remoteaddr, &v5wire.TalkResponse{
+			ReqID:   p.ReqID,
+			Message: []byte("test response"),
+		})
+	})
+	if err := <-done; err != nil {
+		t.Fatal(err)
+	}
+}
+
 // This test checks that lookup works.
 func TestUDPv5_lookup(t *testing.T) {
 	t.Parallel()
@@ -417,7 +538,8 @@ func TestUDPv5_lookup(t *testing.T) {
 	}
 
 	// Seed table with initial node.
-	fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))})
+	initialNode := lookupTestnet.node(256, 0)
+	fillTable(test.table, []*node{wrapNode(initialNode)})
 
 	// Start the lookup.
 	resultC := make(chan []*enode.Node, 1)
@@ -427,22 +549,84 @@ func TestUDPv5_lookup(t *testing.T) {
 	}()
 
 	// Answer lookup packets.
+	asked := make(map[enode.ID]bool)
 	for done := false; !done; {
-		done = test.waitPacketOut(func(p packetV5, to *net.UDPAddr, authTag []byte) {
+		done = test.waitPacketOut(func(p v5wire.Packet, to *net.UDPAddr, _ v5wire.Nonce) {
 			recipient, key := lookupTestnet.nodeByAddr(to)
 			switch p := p.(type) {
-			case *pingV5:
-				test.packetInFrom(key, to, &pongV5{ReqID: p.ReqID})
-			case *findnodeV5:
-				nodes := lookupTestnet.neighborsAtDistance(recipient, p.Distance, 3)
-				response := &nodesV5{ReqID: p.ReqID, Total: 1, Nodes: nodesToRecords(nodes)}
-				test.packetInFrom(key, to, response)
+			case *v5wire.Ping:
+				test.packetInFrom(key, to, &v5wire.Pong{ReqID: p.ReqID})
+			case *v5wire.Findnode:
+				if asked[recipient.ID()] {
+					t.Error("Asked node", recipient.ID(), "twice")
+				}
+				asked[recipient.ID()] = true
+				nodes := lookupTestnet.neighborsAtDistances(recipient, p.Distances, 16)
+				t.Logf("Got FINDNODE for %v, returning %d nodes", p.Distances, len(nodes))
+				for _, resp := range packNodes(p.ReqID, nodes) {
+					test.packetInFrom(key, to, resp)
+				}
 			}
 		})
 	}
 
 	// Verify result nodes.
-	checkLookupResults(t, lookupTestnet, <-resultC)
+	results := <-resultC
+	checkLookupResults(t, lookupTestnet, results)
+}
+
+// This test checks the local node can be utilised to set key-values.
+func TestUDPv5_LocalNode(t *testing.T) {
+	t.Parallel()
+	var cfg Config
+	node := startLocalhostV5(t, cfg)
+	defer node.Close()
+	localNd := node.LocalNode()
+
+	// set value in node's local record
+	testVal := [4]byte{'A', 'B', 'C', 'D'}
+	localNd.Set(enr.WithEntry("testing", &testVal))
+
+	// retrieve the value from self to make sure it matches.
+	outputVal := [4]byte{}
+	if err := node.Self().Load(enr.WithEntry("testing", &outputVal)); err != nil {
+		t.Errorf("Could not load value from record: %v", err)
+	}
+	if testVal != outputVal {
+		t.Errorf("Wanted %#x to be retrieved from the record but instead got %#x", testVal, outputVal)
+	}
+}
+
+func TestUDPv5_PingWithIPV4MappedAddress(t *testing.T) {
+	t.Parallel()
+	test := newUDPV5Test(t)
+	defer test.close()
+
+	rawIP := net.IPv4(0xFF, 0x12, 0x33, 0xE5)
+	test.remoteaddr = &net.UDPAddr{
+		IP:   rawIP.To16(),
+		Port: 0,
+	}
+	remote := test.getNode(test.remotekey, test.remoteaddr).Node()
+	done := make(chan struct{}, 1)
+
+	// This handler will truncate the ipv4-mapped in ipv6 address.
+	go func() {
+		test.udp.handlePing(&v5wire.Ping{ENRSeq: 1}, remote.ID(), test.remoteaddr)
+		done <- struct{}{}
+	}()
+	test.waitPacketOut(func(p *v5wire.Pong, addr *net.UDPAddr, _ v5wire.Nonce) {
+		if len(p.ToIP) == net.IPv6len {
+			t.Error("Received untruncated ip address")
+		}
+		if len(p.ToIP) != net.IPv4len {
+			t.Errorf("Received ip address with incorrect length: %d", len(p.ToIP))
+		}
+		if !p.ToIP.Equal(rawIP) {
+			t.Errorf("Received incorrect ip address: wanted %s but received %s", rawIP.String(), p.ToIP.String())
+		}
+	})
+	<-done
 }
 
 // udpV5Test is the framework for all tests above.
@@ -459,6 +643,7 @@ type udpV5Test struct {
 	nodesByIP           map[string]*enode.LocalNode
 }
 
+// testCodec is the packet encoding used by protocol tests. This codec does not perform encryption.
 type testCodec struct {
 	test *udpV5Test
 	id   enode.ID
@@ -467,46 +652,44 @@ type testCodec struct {
 
 type testCodecFrame struct {
 	NodeID  enode.ID
-	AuthTag []byte
+	AuthTag v5wire.Nonce
 	Ptype   byte
 	Packet  rlp.RawValue
 }
 
-func (c *testCodec) encode(toID enode.ID, addr string, p packetV5, _ *whoareyouV5) ([]byte, []byte, error) {
+func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error) {
 	c.ctr++
-	authTag := make([]byte, 8)
-	binary.BigEndian.PutUint64(authTag, c.ctr)
+	var authTag v5wire.Nonce
+	binary.BigEndian.PutUint64(authTag[:], c.ctr)
+
 	penc, _ := rlp.EncodeToBytes(p)
-	frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.kind(), penc})
+	frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
 	return frame, authTag, err
 }
 
-func (c *testCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) {
+func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) {
 	frame, p, err := c.decodeFrame(input)
 	if err != nil {
 		return enode.ID{}, nil, nil, err
 	}
-	if p.kind() == p_whoareyouV5 {
-		frame.NodeID = enode.ID{} // match wireCodec behavior
-	}
 	return frame.NodeID, nil, p, nil
 }
 
-func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p packetV5, err error) {
+func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p v5wire.Packet, err error) {
 	if err = rlp.DecodeBytes(input, &frame); err != nil {
 		return frame, nil, fmt.Errorf("invalid frame: %v", err)
 	}
 	switch frame.Ptype {
-	case p_unknownV5:
-		dec := new(unknownV5)
+	case v5wire.UnknownPacket:
+		dec := new(v5wire.Unknown)
 		err = rlp.DecodeBytes(frame.Packet, &dec)
 		p = dec
-	case p_whoareyouV5:
-		dec := new(whoareyouV5)
+	case v5wire.WhoareyouPacket:
+		dec := new(v5wire.Whoareyou)
 		err = rlp.DecodeBytes(frame.Packet, &dec)
 		p = dec
 	default:
-		p, err = decodePacketBodyV5(frame.Ptype, frame.Packet)
+		p, err = v5wire.DecodeMessage(frame.Ptype, frame.Packet)
 	}
 	return frame, p, err
 }
@@ -539,20 +722,20 @@ func newUDPV5Test(t *testing.T) *udpV5Test {
 }
 
 // handles a packet as if it had been sent to the transport.
-func (test *udpV5Test) packetIn(packet packetV5) {
+func (test *udpV5Test) packetIn(packet v5wire.Packet) {
 	test.t.Helper()
 	test.packetInFrom(test.remotekey, test.remoteaddr, packet)
 }
 
 // handles a packet as if it had been sent to the transport by the key/endpoint.
-func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet packetV5) {
+func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet v5wire.Packet) {
 	test.t.Helper()
 
 	ln := test.getNode(key, addr)
 	codec := &testCodec{test: test, id: ln.ID()}
-	enc, _, err := codec.encode(test.udp.Self().ID(), addr.String(), packet, nil)
+	enc, _, err := codec.Encode(test.udp.Self().ID(), addr.String(), packet, nil)
 	if err != nil {
-		test.t.Errorf("%s encode error: %v", packet.name(), err)
+		test.t.Errorf("%s encode error: %v", packet.Name(), err)
 	}
 	if test.udp.dispatchReadPacket(addr, enc) {
 		<-test.udp.readNextCh // unblock UDPv5.dispatch
@@ -574,8 +757,12 @@ func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.
 	return ln
 }
 
+// waitPacketOut waits for the next output packet and handles it using the given 'validate'
+// function. The function must be of type func (X, *net.UDPAddr, v5wire.Nonce) where X is
+// assignable to packetV5.
 func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) {
 	test.t.Helper()
+
 	fn := reflect.ValueOf(validate)
 	exptype := fn.Type().In(0)
 
diff --git a/p2p/discover/v5wire/crypto.go b/p2p/discover/v5wire/crypto.go
new file mode 100644
index 0000000000..fc0a0edef5
--- /dev/null
+++ b/p2p/discover/v5wire/crypto.go
@@ -0,0 +1,180 @@
+// 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 .
+
+package v5wire
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"errors"
+	"fmt"
+	"hash"
+
+	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"golang.org/x/crypto/hkdf"
+)
+
+const (
+	// Encryption/authentication parameters.
+	aesKeySize   = 16
+	gcmNonceSize = 12
+)
+
+// Nonce represents a nonce used for AES/GCM.
+type Nonce [gcmNonceSize]byte
+
+// EncodePubkey encodes a public key.
+func EncodePubkey(key *ecdsa.PublicKey) []byte {
+	switch key.Curve {
+	case crypto.S256():
+		return crypto.CompressPubkey(key)
+	default:
+		panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey")
+	}
+}
+
+// DecodePubkey decodes a public key in compressed format.
+func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
+	switch curve {
+	case crypto.S256():
+		if len(e) != 33 {
+			return nil, errors.New("wrong size public key data")
+		}
+		return crypto.DecompressPubkey(e)
+	default:
+		return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name)
+	}
+}
+
+// idNonceHash computes the ID signature hash used in the handshake.
+func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte {
+	h.Reset()
+	h.Write([]byte("discovery v5 identity proof"))
+	h.Write(challenge)
+	h.Write(ephkey)
+	h.Write(destID[:])
+	return h.Sum(nil)
+}
+
+// makeIDSignature creates the ID nonce signature.
+func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) {
+	input := idNonceHash(hash, challenge, ephkey, destID)
+	switch key.Curve {
+	case crypto.S256():
+		idsig, err := crypto.Sign(input, key)
+		if err != nil {
+			return nil, err
+		}
+		return idsig[:len(idsig)-1], nil // remove recovery ID
+	default:
+		return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name)
+	}
+}
+
+// s256raw is an unparsed secp256k1 public key ENR entry.
+type s256raw []byte
+
+func (s256raw) ENRKey() string { return "secp256k1" }
+
+// verifyIDSignature checks that signature over idnonce was made by the given node.
+func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error {
+	switch idscheme := n.Record().IdentityScheme(); idscheme {
+	case "v4":
+		var pubkey s256raw
+		if n.Load(&pubkey) != nil {
+			return errors.New("no secp256k1 public key in record")
+		}
+		input := idNonceHash(hash, challenge, ephkey, destID)
+		if !crypto.VerifySignature(pubkey, input, sig) {
+			return errInvalidNonceSig
+		}
+		return nil
+	default:
+		return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
+	}
+}
+
+type hashFn func() hash.Hash
+
+// deriveKeys creates the session keys.
+func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session {
+	const text = "discovery v5 key agreement"
+	var info = make([]byte, 0, len(text)+len(n1)+len(n2))
+	info = append(info, text...)
+	info = append(info, n1[:]...)
+	info = append(info, n2[:]...)
+
+	eph := ecdh(priv, pub)
+	if eph == nil {
+		return nil
+	}
+	kdf := hkdf.New(hash, eph, challenge, info)
+	sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)}
+	kdf.Read(sec.writeKey)
+	kdf.Read(sec.readKey)
+	for i := range eph {
+		eph[i] = 0
+	}
+	return &sec
+}
+
+// ecdh creates a shared secret.
+func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
+	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
+	if secX == nil {
+		return nil
+	}
+	sec := make([]byte, 33)
+	sec[0] = 0x02 | byte(secY.Bit(0))
+	math.ReadBits(secX, sec[1:])
+	return sec
+}
+
+// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is
+// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16
+// bytes longer than plaintext because it contains an authentication tag.
+func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(fmt.Errorf("can't create block cipher: %v", err))
+	}
+	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
+	if err != nil {
+		panic(fmt.Errorf("can't create GCM: %v", err))
+	}
+	return aesgcm.Seal(dest, nonce, plaintext, authData), nil
+}
+
+// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
+func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, fmt.Errorf("can't create block cipher: %v", err)
+	}
+	if len(nonce) != gcmNonceSize {
+		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
+	}
+	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
+	if err != nil {
+		return nil, fmt.Errorf("can't create GCM: %v", err)
+	}
+	pt := make([]byte, 0, len(ct))
+	return aesgcm.Open(pt, nonce, ct, authData)
+}
diff --git a/p2p/discover/v5wire/crypto_test.go b/p2p/discover/v5wire/crypto_test.go
new file mode 100644
index 0000000000..72169b4314
--- /dev/null
+++ b/p2p/discover/v5wire/crypto_test.go
@@ -0,0 +1,124 @@
+// 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 .
+
+package v5wire
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/sha256"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+func TestVector_ECDH(t *testing.T) {
+	var (
+		staticKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736")
+		publicKey = hexPubkey(crypto.S256(), "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231")
+		want      = hexutil.MustDecode("0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e")
+	)
+	result := ecdh(staticKey, publicKey)
+	check(t, "shared-secret", result, want)
+}
+
+func TestVector_KDF(t *testing.T) {
+	var (
+		ephKey = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736")
+		cdata  = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000")
+		net    = newHandshakeTest()
+	)
+	defer net.close()
+
+	destKey := &testKeyB.PublicKey
+	s := deriveKeys(sha256.New, ephKey, destKey, net.nodeA.id(), net.nodeB.id(), cdata)
+	t.Logf("ephemeral-key = %#x", ephKey.D)
+	t.Logf("dest-pubkey = %#x", EncodePubkey(destKey))
+	t.Logf("node-id-a = %#x", net.nodeA.id().Bytes())
+	t.Logf("node-id-b = %#x", net.nodeB.id().Bytes())
+	t.Logf("challenge-data = %#x", cdata)
+	check(t, "initiator-key", s.writeKey, hexutil.MustDecode("0xdccc82d81bd610f4f76d3ebe97a40571"))
+	check(t, "recipient-key", s.readKey, hexutil.MustDecode("0xac74bb8773749920b0d3a8881c173ec5"))
+}
+
+func TestVector_IDSignature(t *testing.T) {
+	var (
+		key    = hexPrivkey("0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736")
+		destID = enode.HexID("0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9")
+		ephkey = hexutil.MustDecode("0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231")
+		cdata  = hexutil.MustDecode("0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000")
+	)
+
+	sig, err := makeIDSignature(sha256.New(), key, cdata, ephkey, destID)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Logf("static-key = %#x", key.D)
+	t.Logf("challenge-data = %#x", cdata)
+	t.Logf("ephemeral-pubkey = %#x", ephkey)
+	t.Logf("node-id-B = %#x", destID.Bytes())
+	expected := "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6"
+	check(t, "id-signature", sig, hexutil.MustDecode(expected))
+}
+
+func TestDeriveKeys(t *testing.T) {
+	t.Parallel()
+
+	var (
+		n1    = enode.ID{1}
+		n2    = enode.ID{2}
+		cdata = []byte{1, 2, 3, 4}
+	)
+	sec1 := deriveKeys(sha256.New, testKeyA, &testKeyB.PublicKey, n1, n2, cdata)
+	sec2 := deriveKeys(sha256.New, testKeyB, &testKeyA.PublicKey, n1, n2, cdata)
+	if sec1 == nil || sec2 == nil {
+		t.Fatal("key agreement failed")
+	}
+	if !reflect.DeepEqual(sec1, sec2) {
+		t.Fatalf("keys not equal:\n  %+v\n  %+v", sec1, sec2)
+	}
+}
+
+func check(t *testing.T, what string, x, y []byte) {
+	t.Helper()
+
+	if !bytes.Equal(x, y) {
+		t.Errorf("wrong %s: %#x != %#x", what, x, y)
+	} else {
+		t.Logf("%s = %#x", what, x)
+	}
+}
+
+func hexPrivkey(input string) *ecdsa.PrivateKey {
+	key, err := crypto.HexToECDSA(strings.TrimPrefix(input, "0x"))
+	if err != nil {
+		panic(err)
+	}
+	return key
+}
+
+func hexPubkey(curve elliptic.Curve, input string) *ecdsa.PublicKey {
+	key, err := DecodePubkey(curve, hexutil.MustDecode(input))
+	if err != nil {
+		panic(err)
+	}
+	return key
+}
diff --git a/p2p/discover/v5wire/encoding.go b/p2p/discover/v5wire/encoding.go
new file mode 100644
index 0000000000..f502339e1e
--- /dev/null
+++ b/p2p/discover/v5wire/encoding.go
@@ -0,0 +1,648 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package v5wire
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/ecdsa"
+	crand "crypto/rand"
+	"crypto/sha256"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"hash"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// TODO concurrent WHOAREYOU tie-breaker
+// TODO rehandshake after X packets
+
+// Header represents a packet header.
+type Header struct {
+	IV [sizeofMaskingIV]byte
+	StaticHeader
+	AuthData []byte
+
+	src enode.ID // used by decoder
+}
+
+// StaticHeader contains the static fields of a packet header.
+type StaticHeader struct {
+	ProtocolID [6]byte
+	Version    uint16
+	Flag       byte
+	Nonce      Nonce
+	AuthSize   uint16
+}
+
+// Authdata layouts.
+type (
+	whoareyouAuthData struct {
+		IDNonce   [16]byte // ID proof data
+		RecordSeq uint64   // highest known ENR sequence of requester
+	}
+
+	handshakeAuthData struct {
+		h struct {
+			SrcID      enode.ID
+			SigSize    byte // ignature data
+			PubkeySize byte // offset of
+		}
+		// Trailing variable-size data.
+		signature, pubkey, record []byte
+	}
+
+	messageAuthData struct {
+		SrcID enode.ID
+	}
+)
+
+// Packet header flag values.
+const (
+	flagMessage = iota
+	flagWhoareyou
+	flagHandshake
+)
+
+// Protocol constants.
+const (
+	version         = 1
+	minVersion      = 1
+	sizeofMaskingIV = 16
+
+	minMessageSize      = 48 // this refers to data after static headers
+	randomPacketMsgSize = 20
+)
+
+var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'}
+
+// Errors.
+var (
+	errTooShort            = errors.New("packet too short")
+	errInvalidHeader       = errors.New("invalid packet header")
+	errInvalidFlag         = errors.New("invalid flag value in header")
+	errMinVersion          = errors.New("version of packet header below minimum")
+	errMsgTooShort         = errors.New("message/handshake packet below minimum size")
+	errAuthSize            = errors.New("declared auth size is beyond packet length")
+	errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake")
+	errInvalidAuthKey      = errors.New("invalid ephemeral pubkey")
+	errNoRecord            = errors.New("expected ENR in handshake but none sent")
+	errInvalidNonceSig     = errors.New("invalid ID nonce signature")
+	errMessageTooShort     = errors.New("message contains no data")
+	errMessageDecrypt      = errors.New("cannot decrypt message")
+)
+
+// Public errors.
+var (
+	ErrInvalidReqID = errors.New("request ID larger than 8 bytes")
+)
+
+// Packet sizes.
+var (
+	sizeofStaticHeader      = binary.Size(StaticHeader{})
+	sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{})
+	sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h)
+	sizeofMessageAuthData   = binary.Size(messageAuthData{})
+	sizeofStaticPacketData  = sizeofMaskingIV + sizeofStaticHeader
+)
+
+// Codec encodes and decodes Discovery v5 packets.
+// This type is not safe for concurrent use.
+type Codec struct {
+	sha256    hash.Hash
+	localnode *enode.LocalNode
+	privkey   *ecdsa.PrivateKey
+	sc        *SessionCache
+
+	// encoder buffers
+	buf      bytes.Buffer // whole packet
+	headbuf  bytes.Buffer // packet header
+	msgbuf   bytes.Buffer // message RLP plaintext
+	msgctbuf []byte       // message data ciphertext
+
+	// decoder buffer
+	reader bytes.Reader
+}
+
+// NewCodec creates a wire codec.
+func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec {
+	c := &Codec{
+		sha256:    sha256.New(),
+		localnode: ln,
+		privkey:   key,
+		sc:        NewSessionCache(1024, clock),
+	}
+	return c
+}
+
+// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
+// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
+// node.
+func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) {
+	// Create the packet header.
+	var (
+		head    Header
+		session *session
+		msgData []byte
+		err     error
+	)
+	switch {
+	case packet.Kind() == WhoareyouPacket:
+		head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
+	case challenge != nil:
+		// We have an unanswered challenge, send handshake.
+		head, session, err = c.encodeHandshakeHeader(id, addr, challenge)
+	default:
+		session = c.sc.session(id, addr)
+		if session != nil {
+			// There is a session, use it.
+			head, err = c.encodeMessageHeader(id, session)
+		} else {
+			// No keys, send random data to kick off the handshake.
+			head, msgData, err = c.encodeRandom(id)
+		}
+	}
+	if err != nil {
+		return nil, Nonce{}, err
+	}
+
+	// Generate masking IV.
+	if err := c.sc.maskingIVGen(head.IV[:]); err != nil {
+		return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err)
+	}
+
+	// Encode header data.
+	c.writeHeaders(&head)
+
+	// Store sent WHOAREYOU challenges.
+	if challenge, ok := packet.(*Whoareyou); ok {
+		challenge.ChallengeData = bytesCopy(&c.buf)
+		c.sc.storeSentHandshake(id, addr, challenge)
+	} else if msgData == nil {
+		headerData := c.buf.Bytes()
+		msgData, err = c.encryptMessage(session, packet, &head, headerData)
+		if err != nil {
+			return nil, Nonce{}, err
+		}
+	}
+
+	enc, err := c.EncodeRaw(id, head, msgData)
+	return enc, head.Nonce, err
+}
+
+// EncodeRaw encodes a packet with the given header.
+func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) {
+	c.writeHeaders(&head)
+
+	// Apply masking.
+	masked := c.buf.Bytes()[sizeofMaskingIV:]
+	mask := head.mask(id)
+	mask.XORKeyStream(masked[:], masked[:])
+
+	// Write message data.
+	c.buf.Write(msgdata)
+	return c.buf.Bytes(), nil
+}
+
+func (c *Codec) writeHeaders(head *Header) {
+	c.buf.Reset()
+	c.buf.Write(head.IV[:])
+	binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader)
+	c.buf.Write(head.AuthData)
+}
+
+// makeHeader creates a packet header.
+func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header {
+	var authsize int
+	switch flag {
+	case flagMessage:
+		authsize = sizeofMessageAuthData
+	case flagWhoareyou:
+		authsize = sizeofWhoareyouAuthData
+	case flagHandshake:
+		authsize = sizeofHandshakeAuthData
+	default:
+		panic(fmt.Errorf("BUG: invalid packet header flag %x", flag))
+	}
+	authsize += authsizeExtra
+	if authsize > int(^uint16(0)) {
+		panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize))
+	}
+	return Header{
+		StaticHeader: StaticHeader{
+			ProtocolID: protocolID,
+			Version:    version,
+			Flag:       flag,
+			AuthSize:   uint16(authsize),
+		},
+	}
+}
+
+// encodeRandom encodes a packet with random content.
+func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) {
+	head := c.makeHeader(toID, flagMessage, 0)
+
+	// Encode auth data.
+	auth := messageAuthData{SrcID: c.localnode.ID()}
+	if _, err := crand.Read(head.Nonce[:]); err != nil {
+		return head, nil, fmt.Errorf("can't get random data: %v", err)
+	}
+	c.headbuf.Reset()
+	binary.Write(&c.headbuf, binary.BigEndian, auth)
+	head.AuthData = c.headbuf.Bytes()
+
+	// Fill message ciphertext buffer with random bytes.
+	c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...)
+	crand.Read(c.msgctbuf)
+	return head, c.msgctbuf, nil
+}
+
+// encodeWhoareyou encodes a WHOAREYOU packet.
+func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) {
+	// Sanity check node field to catch misbehaving callers.
+	if packet.RecordSeq > 0 && packet.Node == nil {
+		panic("BUG: missing node in whoareyou with non-zero seq")
+	}
+
+	// Create header.
+	head := c.makeHeader(toID, flagWhoareyou, 0)
+	head.AuthData = bytesCopy(&c.buf)
+	head.Nonce = packet.Nonce
+
+	// Encode auth data.
+	auth := &whoareyouAuthData{
+		IDNonce:   packet.IDNonce,
+		RecordSeq: packet.RecordSeq,
+	}
+	c.headbuf.Reset()
+	binary.Write(&c.headbuf, binary.BigEndian, auth)
+	head.AuthData = c.headbuf.Bytes()
+	return head, nil
+}
+
+// encodeHandshakeMessage encodes the handshake message packet header.
+func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) {
+	// Ensure calling code sets challenge.node.
+	if challenge.Node == nil {
+		panic("BUG: missing challenge.Node in encode")
+	}
+
+	// Generate new secrets.
+	auth, session, err := c.makeHandshakeAuth(toID, addr, challenge)
+	if err != nil {
+		return Header{}, nil, err
+	}
+
+	// Generate nonce for message.
+	nonce, err := c.sc.nextNonce(session)
+	if err != nil {
+		return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err)
+	}
+
+	// TODO: this should happen when the first authenticated message is received
+	c.sc.storeNewSession(toID, addr, session)
+
+	// Encode the auth header.
+	var (
+		authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record)
+		head          = c.makeHeader(toID, flagHandshake, authsizeExtra)
+	)
+	c.headbuf.Reset()
+	binary.Write(&c.headbuf, binary.BigEndian, &auth.h)
+	c.headbuf.Write(auth.signature)
+	c.headbuf.Write(auth.pubkey)
+	c.headbuf.Write(auth.record)
+	head.AuthData = c.headbuf.Bytes()
+	head.Nonce = nonce
+	return head, session, err
+}
+
+// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU.
+func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) {
+	auth := new(handshakeAuthData)
+	auth.h.SrcID = c.localnode.ID()
+
+	// Create the ephemeral key. This needs to be first because the
+	// key is part of the ID nonce signature.
+	var remotePubkey = new(ecdsa.PublicKey)
+	if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
+		return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
+	}
+	ephkey, err := c.sc.ephemeralKeyGen()
+	if err != nil {
+		return nil, nil, fmt.Errorf("can't generate ephemeral key")
+	}
+	ephpubkey := EncodePubkey(&ephkey.PublicKey)
+	auth.pubkey = ephpubkey[:]
+	auth.h.PubkeySize = byte(len(auth.pubkey))
+
+	// Add ID nonce signature to response.
+	cdata := challenge.ChallengeData
+	idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID)
+	if err != nil {
+		return nil, nil, fmt.Errorf("can't sign: %v", err)
+	}
+	auth.signature = idsig
+	auth.h.SigSize = byte(len(auth.signature))
+
+	// Add our record to response if it's newer than what remote side has.
+	ln := c.localnode.Node()
+	if challenge.RecordSeq < ln.Seq() {
+		auth.record, _ = rlp.EncodeToBytes(ln.Record())
+	}
+
+	// Create session keys.
+	sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata)
+	if sec == nil {
+		return nil, nil, fmt.Errorf("key derivation failed")
+	}
+	return auth, sec, err
+}
+
+// encodeMessage encodes an encrypted message packet.
+func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) {
+	head := c.makeHeader(toID, flagMessage, 0)
+
+	// Create the header.
+	nonce, err := c.sc.nextNonce(s)
+	if err != nil {
+		return Header{}, fmt.Errorf("can't generate nonce: %v", err)
+	}
+	auth := messageAuthData{SrcID: c.localnode.ID()}
+	c.buf.Reset()
+	binary.Write(&c.buf, binary.BigEndian, &auth)
+	head.AuthData = bytesCopy(&c.buf)
+	head.Nonce = nonce
+	return head, err
+}
+
+func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) {
+	// Encode message plaintext.
+	c.msgbuf.Reset()
+	c.msgbuf.WriteByte(p.Kind())
+	if err := rlp.Encode(&c.msgbuf, p); err != nil {
+		return nil, err
+	}
+	messagePT := c.msgbuf.Bytes()
+
+	// Encrypt into message ciphertext buffer.
+	messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData)
+	if err == nil {
+		c.msgctbuf = messageCT
+	}
+	return messageCT, err
+}
+
+// Decode decodes a discovery packet.
+func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) {
+	// Unmask the static header.
+	if len(input) < sizeofStaticPacketData {
+		return enode.ID{}, nil, nil, errTooShort
+	}
+	var head Header
+	copy(head.IV[:], input[:sizeofMaskingIV])
+	mask := head.mask(c.localnode.ID())
+	staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData]
+	mask.XORKeyStream(staticHeader, staticHeader)
+
+	// Decode and verify the static header.
+	c.reader.Reset(staticHeader)
+	binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader)
+	remainingInput := len(input) - sizeofStaticPacketData
+	if err := head.checkValid(remainingInput); err != nil {
+		return enode.ID{}, nil, nil, err
+	}
+
+	// Unmask auth data.
+	authDataEnd := sizeofStaticPacketData + int(head.AuthSize)
+	authData := input[sizeofStaticPacketData:authDataEnd]
+	mask.XORKeyStream(authData, authData)
+	head.AuthData = authData
+
+	// Delete timed-out handshakes. This must happen before decoding to avoid
+	// processing the same handshake twice.
+	c.sc.handshakeGC()
+
+	// Decode auth part and message.
+	headerData := input[:authDataEnd]
+	msgData := input[authDataEnd:]
+	switch head.Flag {
+	case flagWhoareyou:
+		p, err = c.decodeWhoareyou(&head, headerData)
+	case flagHandshake:
+		n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData)
+	case flagMessage:
+		p, err = c.decodeMessage(addr, &head, headerData, msgData)
+	default:
+		err = errInvalidFlag
+	}
+	return head.src, n, p, err
+}
+
+// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet.
+func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) {
+	if len(head.AuthData) != sizeofWhoareyouAuthData {
+		return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData))
+	}
+	var auth whoareyouAuthData
+	c.reader.Reset(head.AuthData)
+	binary.Read(&c.reader, binary.BigEndian, &auth)
+	p := &Whoareyou{
+		Nonce:         head.Nonce,
+		IDNonce:       auth.IDNonce,
+		RecordSeq:     auth.RecordSeq,
+		ChallengeData: make([]byte, len(headerData)),
+	}
+	copy(p.ChallengeData, headerData)
+	return p, nil
+}
+
+func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) {
+	node, auth, session, err := c.decodeHandshake(fromAddr, head)
+	if err != nil {
+		c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
+		return nil, nil, err
+	}
+
+	// Decrypt the message using the new session keys.
+	msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey)
+	if err != nil {
+		c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
+		return node, msg, err
+	}
+
+	// Handshake OK, drop the challenge and store the new session keys.
+	c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
+	c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
+	return node, msg, nil
+}
+
+func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) {
+	if auth, err = c.decodeHandshakeAuthData(head); err != nil {
+		return nil, auth, nil, err
+	}
+
+	// Verify against our last WHOAREYOU.
+	challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr)
+	if challenge == nil {
+		return nil, auth, nil, errUnexpectedHandshake
+	}
+	// Get node record.
+	n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record)
+	if err != nil {
+		return nil, auth, nil, err
+	}
+	// Verify ID nonce signature.
+	sig := auth.signature
+	cdata := challenge.ChallengeData
+	err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID())
+	if err != nil {
+		return nil, auth, nil, err
+	}
+	// Verify ephemeral key is on curve.
+	ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey)
+	if err != nil {
+		return nil, auth, nil, errInvalidAuthKey
+	}
+	// Derive sesssion keys.
+	session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata)
+	session = session.keysFlipped()
+	return n, auth, session, nil
+}
+
+// decodeHandshakeAuthData reads the authdata section of a handshake packet.
+func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) {
+	// Decode fixed size part.
+	if len(head.AuthData) < sizeofHandshakeAuthData {
+		return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize)
+	}
+	c.reader.Reset(head.AuthData)
+	binary.Read(&c.reader, binary.BigEndian, &auth.h)
+	head.src = auth.h.SrcID
+
+	// Decode variable-size part.
+	var (
+		vardata       = head.AuthData[sizeofHandshakeAuthData:]
+		sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize)
+		keyOffset     = int(auth.h.SigSize)
+		recOffset     = keyOffset + int(auth.h.PubkeySize)
+	)
+	if len(vardata) < sigAndKeySize {
+		return auth, errTooShort
+	}
+	auth.signature = vardata[:keyOffset]
+	auth.pubkey = vardata[keyOffset:recOffset]
+	auth.record = vardata[recOffset:]
+	return auth, nil
+}
+
+// decodeHandshakeRecord verifies the node record contained in a handshake packet. The
+// remote node should include the record if we don't have one or if ours is older than the
+// latest sequence number.
+func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) {
+	node := local
+	if len(remote) > 0 {
+		var record enr.Record
+		if err := rlp.DecodeBytes(remote, &record); err != nil {
+			return nil, err
+		}
+		if local == nil || local.Seq() < record.Seq() {
+			n, err := enode.New(enode.ValidSchemes, &record)
+			if err != nil {
+				return nil, fmt.Errorf("invalid node record: %v", err)
+			}
+			if n.ID() != wantID {
+				return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID())
+			}
+			node = n
+		}
+	}
+	if node == nil {
+		return nil, errNoRecord
+	}
+	return node, nil
+}
+
+// decodeMessage reads packet data following the header as an ordinary message packet.
+func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) {
+	if len(head.AuthData) != sizeofMessageAuthData {
+		return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData))
+	}
+	var auth messageAuthData
+	c.reader.Reset(head.AuthData)
+	binary.Read(&c.reader, binary.BigEndian, &auth)
+	head.src = auth.SrcID
+
+	// Try decrypting the message.
+	key := c.sc.readKey(auth.SrcID, fromAddr)
+	msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key)
+	if err == errMessageDecrypt {
+		// It didn't work. Start the handshake since this is an ordinary message packet.
+		return &Unknown{Nonce: head.Nonce}, nil
+	}
+	return msg, err
+}
+
+func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) {
+	msgdata, err := decryptGCM(readKey, nonce, input, headerData)
+	if err != nil {
+		return nil, errMessageDecrypt
+	}
+	if len(msgdata) == 0 {
+		return nil, errMessageTooShort
+	}
+	return DecodeMessage(msgdata[0], msgdata[1:])
+}
+
+// checkValid performs some basic validity checks on the header.
+// The packetLen here is the length remaining after the static header.
+func (h *StaticHeader) checkValid(packetLen int) error {
+	if h.ProtocolID != protocolID {
+		return errInvalidHeader
+	}
+	if h.Version < minVersion {
+		return errMinVersion
+	}
+	if h.Flag != flagWhoareyou && packetLen < minMessageSize {
+		return errMsgTooShort
+	}
+	if int(h.AuthSize) > packetLen {
+		return errAuthSize
+	}
+	return nil
+}
+
+// headerMask returns a cipher for 'masking' / 'unmasking' packet headers.
+func (h *Header) mask(destID enode.ID) cipher.Stream {
+	block, err := aes.NewCipher(destID[:16])
+	if err != nil {
+		panic("can't create cipher")
+	}
+	return cipher.NewCTR(block, h.IV[:])
+}
+
+func bytesCopy(r *bytes.Buffer) []byte {
+	b := make([]byte, r.Len())
+	copy(b, r.Bytes())
+	return b
+}
diff --git a/p2p/discover/v5wire/encoding_test.go b/p2p/discover/v5wire/encoding_test.go
new file mode 100644
index 0000000000..d9c807e0a8
--- /dev/null
+++ b/p2p/discover/v5wire/encoding_test.go
@@ -0,0 +1,636 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package v5wire
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"encoding/hex"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"path/filepath"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+)
+
+// To regenerate discv5 test vectors, run
+//
+//     go test -run TestVectors -write-test-vectors
+//
+var writeTestVectorsFlag = flag.Bool("write-test-vectors", false, "Overwrite discv5 test vectors in testdata/")
+
+var (
+	testKeyA, _   = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f")
+	testKeyB, _   = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628")
+	testEphKey, _ = crypto.HexToECDSA("0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6")
+	testIDnonce   = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
+)
+
+// This test checks that the minPacketSize and randomPacketMsgSize constants are well-defined.
+func TestMinSizes(t *testing.T) {
+	var (
+		gcmTagSize = 16
+		emptyMsg   = sizeofMessageAuthData + gcmTagSize
+	)
+	t.Log("static header size", sizeofStaticPacketData)
+	t.Log("whoareyou size", sizeofStaticPacketData+sizeofWhoareyouAuthData)
+	t.Log("empty msg size", sizeofStaticPacketData+emptyMsg)
+	if want := emptyMsg; minMessageSize != want {
+		t.Fatalf("wrong minMessageSize %d, want %d", minMessageSize, want)
+	}
+	if sizeofMessageAuthData+randomPacketMsgSize < minMessageSize {
+		t.Fatalf("randomPacketMsgSize %d too small", randomPacketMsgSize)
+	}
+}
+
+// This test checks the basic handshake flow where A talks to B and A has no secrets.
+func TestHandshake(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	// A -> B   RANDOM PACKET
+	packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
+	resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
+
+	// A <- B   WHOAREYOU
+	challenge := &Whoareyou{
+		Nonce:     resp.(*Unknown).Nonce,
+		IDNonce:   testIDnonce,
+		RecordSeq: 0,
+	}
+	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
+	net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
+
+	// A -> B   FINDNODE (handshake packet)
+	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
+	net.nodeB.expectDecode(t, FindnodeMsg, findnode)
+	if len(net.nodeB.c.sc.handshakes) > 0 {
+		t.Fatalf("node B didn't remove handshake from challenge map")
+	}
+
+	// A <- B   NODES
+	nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1})
+	net.nodeA.expectDecode(t, NodesMsg, nodes)
+}
+
+// This test checks that handshake attempts are removed within the timeout.
+func TestHandshake_timeout(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	// A -> B   RANDOM PACKET
+	packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
+	resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
+
+	// A <- B   WHOAREYOU
+	challenge := &Whoareyou{
+		Nonce:     resp.(*Unknown).Nonce,
+		IDNonce:   testIDnonce,
+		RecordSeq: 0,
+	}
+	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
+	net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
+
+	// A -> B   FINDNODE (handshake packet) after timeout
+	net.clock.Run(handshakeTimeout + 1)
+	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
+	net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode)
+}
+
+// This test checks handshake behavior when no record is sent in the auth response.
+func TestHandshake_norecord(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	// A -> B   RANDOM PACKET
+	packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
+	resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
+
+	// A <- B   WHOAREYOU
+	nodeA := net.nodeA.n()
+	if nodeA.Seq() == 0 {
+		t.Fatal("need non-zero sequence number")
+	}
+	challenge := &Whoareyou{
+		Nonce:     resp.(*Unknown).Nonce,
+		IDNonce:   testIDnonce,
+		RecordSeq: nodeA.Seq(),
+		Node:      nodeA,
+	}
+	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
+	net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
+
+	// A -> B   FINDNODE
+	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
+	net.nodeB.expectDecode(t, FindnodeMsg, findnode)
+
+	// A <- B   NODES
+	nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1})
+	net.nodeA.expectDecode(t, NodesMsg, nodes)
+}
+
+// In this test, A tries to send FINDNODE with existing secrets but B doesn't know
+// anything about A.
+func TestHandshake_rekey(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	session := &session{
+		readKey:  []byte("BBBBBBBBBBBBBBBB"),
+		writeKey: []byte("AAAAAAAAAAAAAAAA"),
+	}
+	net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session)
+
+	// A -> B   FINDNODE (encrypted with zero keys)
+	findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{})
+	net.nodeB.expectDecode(t, UnknownPacket, findnode)
+
+	// A <- B   WHOAREYOU
+	challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce}
+	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
+	net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
+
+	// Check that new keys haven't been stored yet.
+	sa := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr())
+	if !bytes.Equal(sa.writeKey, session.writeKey) || !bytes.Equal(sa.readKey, session.readKey) {
+		t.Fatal("node A stored keys too early")
+	}
+	if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil {
+		t.Fatal("node B stored keys too early")
+	}
+
+	// A -> B   FINDNODE encrypted with new keys
+	findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
+	net.nodeB.expectDecode(t, FindnodeMsg, findnode)
+
+	// A <- B   NODES
+	nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1})
+	net.nodeA.expectDecode(t, NodesMsg, nodes)
+}
+
+// In this test A and B have different keys before the handshake.
+func TestHandshake_rekey2(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	initKeysA := &session{
+		readKey:  []byte("BBBBBBBBBBBBBBBB"),
+		writeKey: []byte("AAAAAAAAAAAAAAAA"),
+	}
+	initKeysB := &session{
+		readKey:  []byte("CCCCCCCCCCCCCCCC"),
+		writeKey: []byte("DDDDDDDDDDDDDDDD"),
+	}
+	net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA)
+	net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB)
+
+	// A -> B   FINDNODE encrypted with initKeysA
+	findnode, authTag := net.nodeA.encode(t, net.nodeB, &Findnode{Distances: []uint{3}})
+	net.nodeB.expectDecode(t, UnknownPacket, findnode)
+
+	// A <- B   WHOAREYOU
+	challenge := &Whoareyou{Nonce: authTag, IDNonce: testIDnonce}
+	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
+	net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
+
+	// A -> B   FINDNODE (handshake packet)
+	findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
+	net.nodeB.expectDecode(t, FindnodeMsg, findnode)
+
+	// A <- B   NODES
+	nodes, _ := net.nodeB.encode(t, net.nodeA, &Nodes{Total: 1})
+	net.nodeA.expectDecode(t, NodesMsg, nodes)
+}
+
+func TestHandshake_BadHandshakeAttack(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	// A -> B   RANDOM PACKET
+	packet, _ := net.nodeA.encode(t, net.nodeB, &Findnode{})
+	resp := net.nodeB.expectDecode(t, UnknownPacket, packet)
+
+	// A <- B   WHOAREYOU
+	challenge := &Whoareyou{
+		Nonce:     resp.(*Unknown).Nonce,
+		IDNonce:   testIDnonce,
+		RecordSeq: 0,
+	}
+	whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge)
+	net.nodeA.expectDecode(t, WhoareyouPacket, whoareyou)
+
+	// A -> B   FINDNODE
+	incorrect_challenge := &Whoareyou{
+		IDNonce:   [16]byte{5, 6, 7, 8, 9, 6, 11, 12},
+		RecordSeq: challenge.RecordSeq,
+		Node:      challenge.Node,
+		sent:      challenge.sent,
+	}
+	incorrect_findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, incorrect_challenge, &Findnode{})
+	incorrect_findnode2 := make([]byte, len(incorrect_findnode))
+	copy(incorrect_findnode2, incorrect_findnode)
+
+	net.nodeB.expectDecodeErr(t, errInvalidNonceSig, incorrect_findnode)
+
+	// Reject new findnode as previous handshake is now deleted.
+	net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, incorrect_findnode2)
+
+	// The findnode packet is again rejected even with a valid challenge this time.
+	findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &Findnode{})
+	net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode)
+}
+
+// This test checks some malformed packets.
+func TestDecodeErrorsV5(t *testing.T) {
+	t.Parallel()
+	net := newHandshakeTest()
+	defer net.close()
+
+	net.nodeA.expectDecodeErr(t, errTooShort, []byte{})
+	// TODO some more tests would be nice :)
+	// - check invalid authdata sizes
+	// - check invalid handshake data sizes
+}
+
+// This test checks that all test vectors can be decoded.
+func TestTestVectorsV5(t *testing.T) {
+	var (
+		idA     = enode.PubkeyToIDV4(&testKeyA.PublicKey)
+		idB     = enode.PubkeyToIDV4(&testKeyB.PublicKey)
+		addr    = "127.0.0.1"
+		session = &session{
+			writeKey: hexutil.MustDecode("0x00000000000000000000000000000000"),
+			readKey:  hexutil.MustDecode("0x01010101010101010101010101010101"),
+		}
+		challenge0A, challenge1A, challenge0B Whoareyou
+	)
+
+	// Create challenge packets.
+	c := Whoareyou{
+		Nonce:   Nonce{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
+		IDNonce: testIDnonce,
+	}
+	challenge0A, challenge1A, challenge0B = c, c, c
+	challenge1A.RecordSeq = 1
+	net := newHandshakeTest()
+	challenge0A.Node = net.nodeA.n()
+	challenge0B.Node = net.nodeB.n()
+	challenge1A.Node = net.nodeA.n()
+	net.close()
+
+	type testVectorTest struct {
+		name      string               // test vector name
+		packet    Packet               // the packet to be encoded
+		challenge *Whoareyou           // handshake challenge passed to encoder
+		prep      func(*handshakeTest) // called before encode/decode
+	}
+	tests := []testVectorTest{
+		{
+			name:   "v5.1-whoareyou",
+			packet: &challenge0B,
+		},
+		{
+			name: "v5.1-ping-message",
+			packet: &Ping{
+				ReqID:  []byte{0, 0, 0, 1},
+				ENRSeq: 2,
+			},
+			prep: func(net *handshakeTest) {
+				net.nodeA.c.sc.storeNewSession(idB, addr, session)
+				net.nodeB.c.sc.storeNewSession(idA, addr, session.keysFlipped())
+			},
+		},
+		{
+			name: "v5.1-ping-handshake-enr",
+			packet: &Ping{
+				ReqID:  []byte{0, 0, 0, 1},
+				ENRSeq: 1,
+			},
+			challenge: &challenge0A,
+			prep: func(net *handshakeTest) {
+				// Update challenge.Header.AuthData.
+				net.nodeA.c.Encode(idB, "", &challenge0A, nil)
+				net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge0A)
+			},
+		},
+		{
+			name: "v5.1-ping-handshake",
+			packet: &Ping{
+				ReqID:  []byte{0, 0, 0, 1},
+				ENRSeq: 1,
+			},
+			challenge: &challenge1A,
+			prep: func(net *handshakeTest) {
+				// Update challenge data.
+				net.nodeA.c.Encode(idB, "", &challenge1A, nil)
+				net.nodeB.c.sc.storeSentHandshake(idA, addr, &challenge1A)
+			},
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			net := newHandshakeTest()
+			defer net.close()
+
+			// Override all random inputs.
+			net.nodeA.c.sc.nonceGen = func(counter uint32) (Nonce, error) {
+				return Nonce{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, nil
+			}
+			net.nodeA.c.sc.maskingIVGen = func(buf []byte) error {
+				return nil // all zero
+			}
+			net.nodeA.c.sc.ephemeralKeyGen = func() (*ecdsa.PrivateKey, error) {
+				return testEphKey, nil
+			}
+
+			// Prime the codec for encoding/decoding.
+			if test.prep != nil {
+				test.prep(net)
+			}
+
+			file := filepath.Join("testdata", test.name+".txt")
+			if *writeTestVectorsFlag {
+				// Encode the packet.
+				d, nonce := net.nodeA.encodeWithChallenge(t, net.nodeB, test.challenge, test.packet)
+				comment := testVectorComment(net, test.packet, test.challenge, nonce)
+				writeTestVector(file, comment, d)
+			}
+			enc := hexFile(file)
+			net.nodeB.expectDecode(t, test.packet.Kind(), enc)
+		})
+	}
+}
+
+// testVectorComment creates the commentary for discv5 test vector files.
+func testVectorComment(net *handshakeTest, p Packet, challenge *Whoareyou, nonce Nonce) string {
+	o := new(strings.Builder)
+	printWhoareyou := func(p *Whoareyou) {
+		fmt.Fprintf(o, "whoareyou.challenge-data = %#x\n", p.ChallengeData)
+		fmt.Fprintf(o, "whoareyou.request-nonce = %#x\n", p.Nonce[:])
+		fmt.Fprintf(o, "whoareyou.id-nonce = %#x\n", p.IDNonce[:])
+		fmt.Fprintf(o, "whoareyou.enr-seq = %d\n", p.RecordSeq)
+	}
+
+	fmt.Fprintf(o, "src-node-id = %#x\n", net.nodeA.id().Bytes())
+	fmt.Fprintf(o, "dest-node-id = %#x\n", net.nodeB.id().Bytes())
+	switch p := p.(type) {
+	case *Whoareyou:
+		// WHOAREYOU packet.
+		printWhoareyou(p)
+	case *Ping:
+		fmt.Fprintf(o, "nonce = %#x\n", nonce[:])
+		fmt.Fprintf(o, "read-key = %#x\n", net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()).writeKey)
+		fmt.Fprintf(o, "ping.req-id = %#x\n", p.ReqID)
+		fmt.Fprintf(o, "ping.enr-seq = %d\n", p.ENRSeq)
+		if challenge != nil {
+			// Handshake message packet.
+			fmt.Fprint(o, "\nhandshake inputs:\n\n")
+			printWhoareyou(challenge)
+			fmt.Fprintf(o, "ephemeral-key = %#x\n", testEphKey.D.Bytes())
+			fmt.Fprintf(o, "ephemeral-pubkey = %#x\n", crypto.CompressPubkey(&testEphKey.PublicKey))
+		}
+	default:
+		panic(fmt.Errorf("unhandled packet type %T", p))
+	}
+	return o.String()
+}
+
+// This benchmark checks performance of handshake packet decoding.
+func BenchmarkV5_DecodeHandshakePingSecp256k1(b *testing.B) {
+	net := newHandshakeTest()
+	defer net.close()
+
+	var (
+		idA       = net.nodeA.id()
+		challenge = &Whoareyou{Node: net.nodeB.n()}
+		message   = &Ping{ReqID: []byte("reqid")}
+	)
+	enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), "", message, challenge)
+	if err != nil {
+		b.Fatal("can't encode handshake packet")
+	}
+	challenge.Node = nil // force ENR signature verification in decoder
+	b.ResetTimer()
+
+	input := make([]byte, len(enc))
+	for i := 0; i < b.N; i++ {
+		copy(input, enc)
+		net.nodeB.c.sc.storeSentHandshake(idA, "", challenge)
+		_, _, _, err := net.nodeB.c.Decode(input, "")
+		if err != nil {
+			b.Fatal(err)
+		}
+	}
+}
+
+// This benchmark checks how long it takes to decode an encrypted ping packet.
+func BenchmarkV5_DecodePing(b *testing.B) {
+	net := newHandshakeTest()
+	defer net.close()
+
+	session := &session{
+		readKey:  []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17},
+		writeKey: []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134},
+	}
+	net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), session)
+	net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), session.keysFlipped())
+	addrB := net.nodeA.addr()
+	ping := &Ping{ReqID: []byte("reqid"), ENRSeq: 5}
+	enc, _, err := net.nodeA.c.Encode(net.nodeB.id(), addrB, ping, nil)
+	if err != nil {
+		b.Fatalf("can't encode: %v", err)
+	}
+	b.ResetTimer()
+
+	input := make([]byte, len(enc))
+	for i := 0; i < b.N; i++ {
+		copy(input, enc)
+		_, _, packet, _ := net.nodeB.c.Decode(input, addrB)
+		if _, ok := packet.(*Ping); !ok {
+			b.Fatalf("wrong packet type %T", packet)
+		}
+	}
+}
+
+var pp = spew.NewDefaultConfig()
+
+type handshakeTest struct {
+	nodeA, nodeB handshakeTestNode
+	clock        mclock.Simulated
+}
+
+type handshakeTestNode struct {
+	ln *enode.LocalNode
+	c  *Codec
+}
+
+func newHandshakeTest() *handshakeTest {
+	t := new(handshakeTest)
+	t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock)
+	t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock)
+	return t
+}
+
+func (t *handshakeTest) close() {
+	t.nodeA.ln.Database().Close()
+	t.nodeB.ln.Database().Close()
+}
+
+func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) {
+	db, _ := enode.OpenDB("")
+	n.ln = enode.NewLocalNode(db, key)
+	n.ln.SetStaticIP(ip)
+	if n.ln.Node().Seq() != 1 {
+		panic(fmt.Errorf("unexpected seq %d", n.ln.Node().Seq()))
+	}
+	n.c = NewCodec(n.ln, key, clock)
+}
+
+func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p Packet) ([]byte, Nonce) {
+	t.Helper()
+	return n.encodeWithChallenge(t, to, nil, p)
+}
+
+func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *Whoareyou, p Packet) ([]byte, Nonce) {
+	t.Helper()
+
+	// Copy challenge and add destination node. This avoids sharing 'c' among the two codecs.
+	var challenge *Whoareyou
+	if c != nil {
+		challengeCopy := *c
+		challenge = &challengeCopy
+		challenge.Node = to.n()
+	}
+	// Encode to destination.
+	enc, nonce, err := n.c.Encode(to.id(), to.addr(), p, challenge)
+	if err != nil {
+		t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err))
+	}
+	t.Logf("(%s) -> (%s)   %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.Name(), hex.Dump(enc))
+	return enc, nonce
+}
+
+func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) Packet {
+	t.Helper()
+
+	dec, err := n.decode(p)
+	if err != nil {
+		t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err))
+	}
+	t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec))
+	if dec.Kind() != ptype {
+		t.Fatalf("expected packet type %d, got %d", ptype, dec.Kind())
+	}
+	return dec
+}
+
+func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) {
+	t.Helper()
+	if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) {
+		t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr))
+	}
+}
+
+func (n *handshakeTestNode) decode(input []byte) (Packet, error) {
+	_, _, p, err := n.c.Decode(input, "127.0.0.1")
+	return p, err
+}
+
+func (n *handshakeTestNode) n() *enode.Node {
+	return n.ln.Node()
+}
+
+func (n *handshakeTestNode) addr() string {
+	return n.ln.Node().IP().String()
+}
+
+func (n *handshakeTestNode) id() enode.ID {
+	return n.ln.ID()
+}
+
+// hexFile reads the given file and decodes the hex data contained in it.
+// Whitespace and any lines beginning with the # character are ignored.
+func hexFile(file string) []byte {
+	fileContent, err := ioutil.ReadFile(file)
+	if err != nil {
+		panic(err)
+	}
+
+	// Gather hex data, ignore comments.
+	var text []byte
+	for _, line := range bytes.Split(fileContent, []byte("\n")) {
+		line = bytes.TrimSpace(line)
+		if len(line) > 0 && line[0] == '#' {
+			continue
+		}
+		text = append(text, line...)
+	}
+
+	// Parse the hex.
+	if bytes.HasPrefix(text, []byte("0x")) {
+		text = text[2:]
+	}
+	data := make([]byte, hex.DecodedLen(len(text)))
+	if _, err := hex.Decode(data, text); err != nil {
+		panic("invalid hex in " + file)
+	}
+	return data
+}
+
+// writeTestVector writes a test vector file with the given commentary and binary data.
+func writeTestVector(file, comment string, data []byte) {
+	fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+	if err != nil {
+		panic(err)
+	}
+	defer fd.Close()
+
+	if len(comment) > 0 {
+		for _, line := range strings.Split(strings.TrimSpace(comment), "\n") {
+			fmt.Fprintf(fd, "# %s\n", line)
+		}
+		fmt.Fprintln(fd)
+	}
+	for len(data) > 0 {
+		var chunk []byte
+		if len(data) < 32 {
+			chunk = data
+		} else {
+			chunk = data[:32]
+		}
+		data = data[len(chunk):]
+		fmt.Fprintf(fd, "%x\n", chunk)
+	}
+}
diff --git a/p2p/discover/v5wire/msg.go b/p2p/discover/v5wire/msg.go
new file mode 100644
index 0000000000..7c3686111b
--- /dev/null
+++ b/p2p/discover/v5wire/msg.go
@@ -0,0 +1,249 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package v5wire
+
+import (
+	"fmt"
+	"net"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+// Packet is implemented by all message types.
+type Packet interface {
+	Name() string        // Name returns a string corresponding to the message type.
+	Kind() byte          // Kind returns the message type.
+	RequestID() []byte   // Returns the request ID.
+	SetRequestID([]byte) // Sets the request ID.
+}
+
+// Message types.
+const (
+	PingMsg byte = iota + 1
+	PongMsg
+	FindnodeMsg
+	NodesMsg
+	TalkRequestMsg
+	TalkResponseMsg
+	RequestTicketMsg
+	TicketMsg
+	RegtopicMsg
+	RegconfirmationMsg
+	TopicQueryMsg
+
+	UnknownPacket   = byte(255) // any non-decryptable packet
+	WhoareyouPacket = byte(254) // the WHOAREYOU packet
+)
+
+// Protocol messages.
+type (
+	// Unknown represents any packet that can't be decrypted.
+	Unknown struct {
+		Nonce Nonce
+	}
+
+	// WHOAREYOU contains the handshake challenge.
+	Whoareyou struct {
+		ChallengeData []byte   // Encoded challenge
+		Nonce         Nonce    // Nonce of request packet
+		IDNonce       [16]byte // Identity proof data
+		RecordSeq     uint64   // ENR sequence number of recipient
+
+		// Node is the locally known node record of recipient.
+		// This must be set by the caller of Encode.
+		Node *enode.Node
+
+		sent mclock.AbsTime // for handshake GC.
+	}
+
+	// PING is sent during liveness checks.
+	Ping struct {
+		ReqID  []byte
+		ENRSeq uint64
+	}
+
+	// PONG is the reply to PING.
+	Pong struct {
+		ReqID  []byte
+		ENRSeq uint64
+		ToIP   net.IP // These fields should mirror the UDP envelope address of the ping
+		ToPort uint16 // packet, which provides a way to discover the the external address (after NAT).
+	}
+
+	// FINDNODE is a query for nodes in the given bucket.
+	Findnode struct {
+		ReqID     []byte
+		Distances []uint
+	}
+
+	// NODES is the reply to FINDNODE and TOPICQUERY.
+	Nodes struct {
+		ReqID []byte
+		Total uint8
+		Nodes []*enr.Record
+	}
+
+	// TALKREQ is an application-level request.
+	TalkRequest struct {
+		ReqID    []byte
+		Protocol string
+		Message  []byte
+	}
+
+	// TALKRESP is the reply to TALKREQ.
+	TalkResponse struct {
+		ReqID   []byte
+		Message []byte
+	}
+
+	// REQUESTTICKET requests a ticket for a topic queue.
+	RequestTicket struct {
+		ReqID []byte
+		Topic []byte
+	}
+
+	// TICKET is the response to REQUESTTICKET.
+	Ticket struct {
+		ReqID  []byte
+		Ticket []byte
+	}
+
+	// REGTOPIC registers the sender in a topic queue using a ticket.
+	Regtopic struct {
+		ReqID  []byte
+		Ticket []byte
+		ENR    *enr.Record
+	}
+
+	// REGCONFIRMATION is the reply to REGTOPIC.
+	Regconfirmation struct {
+		ReqID      []byte
+		Registered bool
+	}
+
+	// TOPICQUERY asks for nodes with the given topic.
+	TopicQuery struct {
+		ReqID []byte
+		Topic []byte
+	}
+)
+
+// DecodeMessage decodes the message body of a packet.
+func DecodeMessage(ptype byte, body []byte) (Packet, error) {
+	var dec Packet
+	switch ptype {
+	case PingMsg:
+		dec = new(Ping)
+	case PongMsg:
+		dec = new(Pong)
+	case FindnodeMsg:
+		dec = new(Findnode)
+	case NodesMsg:
+		dec = new(Nodes)
+	case TalkRequestMsg:
+		dec = new(TalkRequest)
+	case TalkResponseMsg:
+		dec = new(TalkResponse)
+	case RequestTicketMsg:
+		dec = new(RequestTicket)
+	case TicketMsg:
+		dec = new(Ticket)
+	case RegtopicMsg:
+		dec = new(Regtopic)
+	case RegconfirmationMsg:
+		dec = new(Regconfirmation)
+	case TopicQueryMsg:
+		dec = new(TopicQuery)
+	default:
+		return nil, fmt.Errorf("unknown packet type %d", ptype)
+	}
+	if err := rlp.DecodeBytes(body, dec); err != nil {
+		return nil, err
+	}
+	if dec.RequestID() != nil && len(dec.RequestID()) > 8 {
+		return nil, ErrInvalidReqID
+	}
+	return dec, nil
+}
+
+func (*Whoareyou) Name() string        { return "WHOAREYOU/v5" }
+func (*Whoareyou) Kind() byte          { return WhoareyouPacket }
+func (*Whoareyou) RequestID() []byte   { return nil }
+func (*Whoareyou) SetRequestID([]byte) {}
+
+func (*Unknown) Name() string        { return "UNKNOWN/v5" }
+func (*Unknown) Kind() byte          { return UnknownPacket }
+func (*Unknown) RequestID() []byte   { return nil }
+func (*Unknown) SetRequestID([]byte) {}
+
+func (*Ping) Name() string             { return "PING/v5" }
+func (*Ping) Kind() byte               { return PingMsg }
+func (p *Ping) RequestID() []byte      { return p.ReqID }
+func (p *Ping) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Pong) Name() string             { return "PONG/v5" }
+func (*Pong) Kind() byte               { return PongMsg }
+func (p *Pong) RequestID() []byte      { return p.ReqID }
+func (p *Pong) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Findnode) Name() string             { return "FINDNODE/v5" }
+func (*Findnode) Kind() byte               { return FindnodeMsg }
+func (p *Findnode) RequestID() []byte      { return p.ReqID }
+func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Nodes) Name() string             { return "NODES/v5" }
+func (*Nodes) Kind() byte               { return NodesMsg }
+func (p *Nodes) RequestID() []byte      { return p.ReqID }
+func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*TalkRequest) Name() string             { return "TALKREQ/v5" }
+func (*TalkRequest) Kind() byte               { return TalkRequestMsg }
+func (p *TalkRequest) RequestID() []byte      { return p.ReqID }
+func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*TalkResponse) Name() string             { return "TALKRESP/v5" }
+func (*TalkResponse) Kind() byte               { return TalkResponseMsg }
+func (p *TalkResponse) RequestID() []byte      { return p.ReqID }
+func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*RequestTicket) Name() string             { return "REQTICKET/v5" }
+func (*RequestTicket) Kind() byte               { return RequestTicketMsg }
+func (p *RequestTicket) RequestID() []byte      { return p.ReqID }
+func (p *RequestTicket) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Regtopic) Name() string             { return "REGTOPIC/v5" }
+func (*Regtopic) Kind() byte               { return RegtopicMsg }
+func (p *Regtopic) RequestID() []byte      { return p.ReqID }
+func (p *Regtopic) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Ticket) Name() string             { return "TICKET/v5" }
+func (*Ticket) Kind() byte               { return TicketMsg }
+func (p *Ticket) RequestID() []byte      { return p.ReqID }
+func (p *Ticket) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*Regconfirmation) Name() string             { return "REGCONFIRMATION/v5" }
+func (*Regconfirmation) Kind() byte               { return RegconfirmationMsg }
+func (p *Regconfirmation) RequestID() []byte      { return p.ReqID }
+func (p *Regconfirmation) SetRequestID(id []byte) { p.ReqID = id }
+
+func (*TopicQuery) Name() string             { return "TOPICQUERY/v5" }
+func (*TopicQuery) Kind() byte               { return TopicQueryMsg }
+func (p *TopicQuery) RequestID() []byte      { return p.ReqID }
+func (p *TopicQuery) SetRequestID(id []byte) { p.ReqID = id }
diff --git a/p2p/discover/v5_session.go b/p2p/discover/v5wire/session.go
similarity index 58%
rename from p2p/discover/v5_session.go
rename to p2p/discover/v5wire/session.go
index 8a0eeb6977..d52b5c1181 100644
--- a/p2p/discover/v5_session.go
+++ b/p2p/discover/v5wire/session.go
@@ -14,22 +14,33 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package discover
+package v5wire
 
 import (
+	"crypto/ecdsa"
 	crand "crypto/rand"
+	"encoding/binary"
+	"time"
 
 	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/hashicorp/golang-lru/simplelru"
 )
 
-// The sessionCache keeps negotiated encryption keys and
+const handshakeTimeout = time.Second
+
+// The SessionCache keeps negotiated encryption keys and
 // state for in-progress handshakes in the Discovery v5 wire protocol.
-type sessionCache struct {
+type SessionCache struct {
 	sessions   *simplelru.LRU
-	handshakes map[sessionID]*whoareyouV5
+	handshakes map[sessionID]*Whoareyou
 	clock      mclock.Clock
+
+	// hooks for overriding randomness.
+	nonceGen        func(uint32) (Nonce, error)
+	maskingIVGen    func([]byte) error
+	ephemeralKeyGen func() (*ecdsa.PrivateKey, error)
 }
 
 // sessionID identifies a session or handshake.
@@ -45,27 +56,45 @@ type session struct {
 	nonceCounter uint32
 }
 
-func newSessionCache(maxItems int, clock mclock.Clock) *sessionCache {
+// keysFlipped returns a copy of s with the read and write keys flipped.
+func (s *session) keysFlipped() *session {
+	return &session{s.readKey, s.writeKey, s.nonceCounter}
+}
+
+func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
 	cache, err := simplelru.NewLRU(maxItems, nil)
 	if err != nil {
 		panic("can't create session cache")
 	}
-	return &sessionCache{
-		sessions:   cache,
-		handshakes: make(map[sessionID]*whoareyouV5),
-		clock:      clock,
+	return &SessionCache{
+		sessions:        cache,
+		handshakes:      make(map[sessionID]*Whoareyou),
+		clock:           clock,
+		nonceGen:        generateNonce,
+		maskingIVGen:    generateMaskingIV,
+		ephemeralKeyGen: crypto.GenerateKey,
 	}
 }
 
+func generateNonce(counter uint32) (n Nonce, err error) {
+	binary.BigEndian.PutUint32(n[:4], counter)
+	_, err = crand.Read(n[4:])
+	return n, err
+}
+
+func generateMaskingIV(buf []byte) error {
+	_, err := crand.Read(buf)
+	return err
+}
+
 // nextNonce creates a nonce for encrypting a message to the given session.
-func (sc *sessionCache) nextNonce(id enode.ID, addr string) []byte {
-	n := make([]byte, gcmNonceSize)
-	crand.Read(n)
-	return n
+func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
+	s.nonceCounter++
+	return sc.nonceGen(s.nonceCounter)
 }
 
 // session returns the current session for the given node, if any.
-func (sc *sessionCache) session(id enode.ID, addr string) *session {
+func (sc *SessionCache) session(id enode.ID, addr string) *session {
 	item, ok := sc.sessions.Get(sessionID{id, addr})
 	if !ok {
 		return nil
@@ -74,46 +103,36 @@ func (sc *sessionCache) session(id enode.ID, addr string) *session {
 }
 
 // readKey returns the current read key for the given node.
-func (sc *sessionCache) readKey(id enode.ID, addr string) []byte {
+func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
 	if s := sc.session(id, addr); s != nil {
 		return s.readKey
 	}
 	return nil
 }
 
-// writeKey returns the current read key for the given node.
-func (sc *sessionCache) writeKey(id enode.ID, addr string) []byte {
-	if s := sc.session(id, addr); s != nil {
-		return s.writeKey
-	}
-	return nil
-}
-
 // storeNewSession stores new encryption keys in the cache.
-func (sc *sessionCache) storeNewSession(id enode.ID, addr string, r, w []byte) {
-	sc.sessions.Add(sessionID{id, addr}, &session{
-		readKey: r, writeKey: w,
-	})
+func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) {
+	sc.sessions.Add(sessionID{id, addr}, s)
 }
 
 // getHandshake gets the handshake challenge we previously sent to the given remote node.
-func (sc *sessionCache) getHandshake(id enode.ID, addr string) *whoareyouV5 {
+func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou {
 	return sc.handshakes[sessionID{id, addr}]
 }
 
 // storeSentHandshake stores the handshake challenge sent to the given remote node.
-func (sc *sessionCache) storeSentHandshake(id enode.ID, addr string, challenge *whoareyouV5) {
+func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) {
 	challenge.sent = sc.clock.Now()
 	sc.handshakes[sessionID{id, addr}] = challenge
 }
 
 // deleteHandshake deletes handshake data for the given node.
-func (sc *sessionCache) deleteHandshake(id enode.ID, addr string) {
+func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) {
 	delete(sc.handshakes, sessionID{id, addr})
 }
 
 // handshakeGC deletes timed-out handshakes.
-func (sc *sessionCache) handshakeGC() {
+func (sc *SessionCache) handshakeGC() {
 	deadline := sc.clock.Now().Add(-handshakeTimeout)
 	for key, challenge := range sc.handshakes {
 		if challenge.sent < deadline {
diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt
new file mode 100644
index 0000000000..477f9e15a8
--- /dev/null
+++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake-enr.txt
@@ -0,0 +1,27 @@
+# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
+# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
+# nonce = 0xffffffffffffffffffffffff
+# read-key = 0x53b1c075f41876423154e157470c2f48
+# ping.req-id = 0x00000001
+# ping.enr-seq = 1
+# 
+# handshake inputs:
+# 
+# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000
+# whoareyou.request-nonce = 0x0102030405060708090a0b0c
+# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10
+# whoareyou.enr-seq = 0
+# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6
+# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5
+
+00000000000000000000000000000000088b3d4342774649305f313964a39e55
+ea96c005ad539c8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3
+4c4f53245d08da4bb23698868350aaad22e3ab8dd034f548a1c43cd246be9856
+2fafa0a1fa86d8e7a3b95ae78cc2b988ded6a5b59eb83ad58097252188b902b2
+1481e30e5e285f19735796706adff216ab862a9186875f9494150c4ae06fa4d1
+f0396c93f215fa4ef524e0ed04c3c21e39b1868e1ca8105e585ec17315e755e6
+cfc4dd6cb7fd8e1a1f55e49b4b5eb024221482105346f3c82b15fdaae36a3bb1
+2a494683b4a3c7f2ae41306252fed84785e2bbff3b022812d0882f06978df84a
+80d443972213342d04b9048fc3b1d5fcb1df0f822152eced6da4d3f6df27e70e
+4539717307a0208cd208d65093ccab5aa596a34d7511401987662d8cf62b1394
+71
diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt
new file mode 100644
index 0000000000..b3f304766c
--- /dev/null
+++ b/p2p/discover/v5wire/testdata/v5.1-ping-handshake.txt
@@ -0,0 +1,23 @@
+# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
+# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
+# nonce = 0xffffffffffffffffffffffff
+# read-key = 0x4f9fac6de7567d1e3b1241dffe90f662
+# ping.req-id = 0x00000001
+# ping.enr-seq = 1
+# 
+# handshake inputs:
+# 
+# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000001
+# whoareyou.request-nonce = 0x0102030405060708090a0b0c
+# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10
+# whoareyou.enr-seq = 1
+# ephemeral-key = 0x0288ef00023598499cb6c940146d050d2b1fb914198c327f76aad590bead68b6
+# ephemeral-pubkey = 0x039a003ba6517b473fa0cd74aefe99dadfdb34627f90fec6362df85803908f53a5
+
+00000000000000000000000000000000088b3d4342774649305f313964a39e55
+ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3
+4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef
+268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb
+a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1
+f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83
+9cf8
diff --git a/p2p/discover/v5wire/testdata/v5.1-ping-message.txt b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt
new file mode 100644
index 0000000000..f82b99c3bc
--- /dev/null
+++ b/p2p/discover/v5wire/testdata/v5.1-ping-message.txt
@@ -0,0 +1,10 @@
+# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
+# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
+# nonce = 0xffffffffffffffffffffffff
+# read-key = 0x00000000000000000000000000000000
+# ping.req-id = 0x00000001
+# ping.enr-seq = 2
+
+00000000000000000000000000000000088b3d4342774649325f313964a39e55
+ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3
+4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc
diff --git a/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt
new file mode 100644
index 0000000000..1a75f525ee
--- /dev/null
+++ b/p2p/discover/v5wire/testdata/v5.1-whoareyou.txt
@@ -0,0 +1,9 @@
+# src-node-id = 0xaaaa8419e9f49d0083561b48287df592939a8d19947d8c0ef88f2a4856a69fbb
+# dest-node-id = 0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9
+# whoareyou.challenge-data = 0x000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000
+# whoareyou.request-nonce = 0x0102030405060708090a0b0c
+# whoareyou.id-nonce = 0x0102030405060708090a0b0c0d0e0f10
+# whoareyou.enr-seq = 0
+
+00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad
+1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d
diff --git a/p2p/discv5/README b/p2p/discv5/README
deleted file mode 100644
index 617a473d7f..0000000000
--- a/p2p/discv5/README
+++ /dev/null
@@ -1,4 +0,0 @@
-This package is an early prototype of Discovery v5. Do not use this code.
-
-See https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md for the
-current Discovery v5 specification.
\ No newline at end of file
diff --git a/p2p/discv5/database.go b/p2p/discv5/database.go
deleted file mode 100644
index ca118e7f80..0000000000
--- a/p2p/discv5/database.go
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright 2015 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 .
-
-// Contains the node database, storing previously seen nodes and any collected
-// metadata about them for QoS purposes.
-
-package discv5
-
-import (
-	"bytes"
-	"crypto/rand"
-	"encoding/binary"
-	"fmt"
-	"os"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/rlp"
-	"github.com/syndtr/goleveldb/leveldb"
-	"github.com/syndtr/goleveldb/leveldb/errors"
-	"github.com/syndtr/goleveldb/leveldb/iterator"
-	"github.com/syndtr/goleveldb/leveldb/opt"
-	"github.com/syndtr/goleveldb/leveldb/storage"
-	"github.com/syndtr/goleveldb/leveldb/util"
-)
-
-var (
-	nodeDBNilNodeID      = NodeID{}       // Special node ID to use as a nil element.
-	nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
-	nodeDBCleanupCycle   = time.Hour      // Time period for running the expiration task.
-)
-
-// nodeDB stores all nodes we know about.
-type nodeDB struct {
-	lvl    *leveldb.DB   // Interface to the database itself
-	self   NodeID        // Own node id to prevent adding it into the database
-	runner sync.Once     // Ensures we can start at most one expirer
-	quit   chan struct{} // Channel to signal the expiring thread to stop
-}
-
-// Schema layout for the node database
-var (
-	nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
-	nodeDBItemPrefix = []byte("n:")      // Identifier to prefix node entries with
-
-	nodeDBDiscoverRoot      = ":discover"
-	nodeDBDiscoverPing      = nodeDBDiscoverRoot + ":lastping"
-	nodeDBDiscoverPong      = nodeDBDiscoverRoot + ":lastpong"
-	nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
-	nodeDBTopicRegTickets   = ":tickets"
-)
-
-// newNodeDB creates a new node database for storing and retrieving infos about
-// known peers in the network. If no path is given, an in-memory, temporary
-// database is constructed.
-func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
-	if path == "" {
-		return newMemoryNodeDB(self)
-	}
-	return newPersistentNodeDB(path, version, self)
-}
-
-// newMemoryNodeDB creates a new in-memory node database without a persistent
-// backend.
-func newMemoryNodeDB(self NodeID) (*nodeDB, error) {
-	db, err := leveldb.Open(storage.NewMemStorage(), nil)
-	if err != nil {
-		return nil, err
-	}
-	return &nodeDB{
-		lvl:  db,
-		self: self,
-		quit: make(chan struct{}),
-	}, nil
-}
-
-// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
-// also flushing its contents in case of a version mismatch.
-func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
-	opts := &opt.Options{OpenFilesCacheCapacity: 5}
-	db, err := leveldb.OpenFile(path, opts)
-	if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
-		db, err = leveldb.RecoverFile(path, nil)
-	}
-	if err != nil {
-		return nil, err
-	}
-	// The nodes contained in the cache correspond to a certain protocol version.
-	// Flush all nodes if the version doesn't match.
-	currentVer := make([]byte, binary.MaxVarintLen64)
-	currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))]
-
-	blob, err := db.Get(nodeDBVersionKey, nil)
-	switch err {
-	case leveldb.ErrNotFound:
-		// Version not found (i.e. empty cache), insert it
-		if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil {
-			db.Close()
-			return nil, err
-		}
-
-	case nil:
-		// Version present, flush if different
-		if !bytes.Equal(blob, currentVer) {
-			db.Close()
-			if err = os.RemoveAll(path); err != nil {
-				return nil, err
-			}
-			return newPersistentNodeDB(path, version, self)
-		}
-	}
-	return &nodeDB{
-		lvl:  db,
-		self: self,
-		quit: make(chan struct{}),
-	}, nil
-}
-
-// makeKey generates the leveldb key-blob from a node id and its particular
-// field of interest.
-func makeKey(id NodeID, field string) []byte {
-	if bytes.Equal(id[:], nodeDBNilNodeID[:]) {
-		return []byte(field)
-	}
-	return append(nodeDBItemPrefix, append(id[:], field...)...)
-}
-
-// splitKey tries to split a database key into a node id and a field part.
-func splitKey(key []byte) (id NodeID, field string) {
-	// If the key is not of a node, return it plainly
-	if !bytes.HasPrefix(key, nodeDBItemPrefix) {
-		return NodeID{}, string(key)
-	}
-	// Otherwise split the id and field
-	item := key[len(nodeDBItemPrefix):]
-	copy(id[:], item[:len(id)])
-	field = string(item[len(id):])
-
-	return id, field
-}
-
-// fetchInt64 retrieves an integer instance associated with a particular
-// database key.
-func (db *nodeDB) fetchInt64(key []byte) int64 {
-	blob, err := db.lvl.Get(key, nil)
-	if err != nil {
-		return 0
-	}
-	val, read := binary.Varint(blob)
-	if read <= 0 {
-		return 0
-	}
-	return val
-}
-
-// storeInt64 update a specific database entry to the current time instance as a
-// unix timestamp.
-func (db *nodeDB) storeInt64(key []byte, n int64) error {
-	blob := make([]byte, binary.MaxVarintLen64)
-	blob = blob[:binary.PutVarint(blob, n)]
-	return db.lvl.Put(key, blob, nil)
-}
-
-func (db *nodeDB) storeRLP(key []byte, val interface{}) error {
-	blob, err := rlp.EncodeToBytes(val)
-	if err != nil {
-		return err
-	}
-	return db.lvl.Put(key, blob, nil)
-}
-
-func (db *nodeDB) fetchRLP(key []byte, val interface{}) error {
-	blob, err := db.lvl.Get(key, nil)
-	if err != nil {
-		return err
-	}
-	err = rlp.DecodeBytes(blob, val)
-	if err != nil {
-		log.Warn(fmt.Sprintf("key %x (%T) %v", key, val, err))
-	}
-	return err
-}
-
-// node retrieves a node with a given id from the database.
-func (db *nodeDB) node(id NodeID) *Node {
-	var node Node
-	if err := db.fetchRLP(makeKey(id, nodeDBDiscoverRoot), &node); err != nil {
-		return nil
-	}
-	node.sha = crypto.Keccak256Hash(node.ID[:])
-	return &node
-}
-
-// updateNode inserts - potentially overwriting - a node into the peer database.
-func (db *nodeDB) updateNode(node *Node) error {
-	return db.storeRLP(makeKey(node.ID, nodeDBDiscoverRoot), node)
-}
-
-// deleteNode deletes all information/keys associated with a node.
-func (db *nodeDB) deleteNode(id NodeID) error {
-	deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil)
-	for deleter.Next() {
-		if err := db.lvl.Delete(deleter.Key(), nil); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// ensureExpirer is a small helper method ensuring that the data expiration
-// mechanism is running. If the expiration goroutine is already running, this
-// method simply returns.
-//
-// The goal is to start the data evacuation only after the network successfully
-// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
-// it would require significant overhead to exactly trace the first successful
-// convergence, it's simpler to "ensure" the correct state when an appropriate
-// condition occurs (i.e. a successful bonding), and discard further events.
-func (db *nodeDB) ensureExpirer() {
-	db.runner.Do(func() { go db.expirer() })
-}
-
-// expirer should be started in a go routine, and is responsible for looping ad
-// infinitum and dropping stale data from the database.
-func (db *nodeDB) expirer() {
-	tick := time.NewTicker(nodeDBCleanupCycle)
-	defer tick.Stop()
-	for {
-		select {
-		case <-tick.C:
-			if err := db.expireNodes(); err != nil {
-				log.Error(fmt.Sprintf("Failed to expire nodedb items: %v", err))
-			}
-		case <-db.quit:
-			return
-		}
-	}
-}
-
-// expireNodes iterates over the database and deletes all nodes that have not
-// been seen (i.e. received a pong from) for some allotted time.
-func (db *nodeDB) expireNodes() error {
-	threshold := time.Now().Add(-nodeDBNodeExpiration)
-
-	// Find discovered nodes that are older than the allowance
-	it := db.lvl.NewIterator(nil, nil)
-	defer it.Release()
-
-	for it.Next() {
-		// Skip the item if not a discovery node
-		id, field := splitKey(it.Key())
-		if field != nodeDBDiscoverRoot {
-			continue
-		}
-		// Skip the node if not expired yet (and not self)
-		if !bytes.Equal(id[:], db.self[:]) {
-			if seen := db.lastPong(id); seen.After(threshold) {
-				continue
-			}
-		}
-		// Otherwise delete all associated information
-		db.deleteNode(id)
-	}
-	return nil
-}
-
-// lastPing retrieves the time of the last ping packet send to a remote node,
-// requesting binding.
-func (db *nodeDB) lastPing(id NodeID) time.Time {
-	return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
-}
-
-// updateLastPing updates the last time we tried contacting a remote node.
-func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error {
-	return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
-}
-
-// lastPong retrieves the time of the last successful contact from remote node.
-func (db *nodeDB) lastPong(id NodeID) time.Time {
-	return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
-}
-
-// updateLastPong updates the last time a remote node successfully contacted.
-func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error {
-	return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
-}
-
-// findFails retrieves the number of findnode failures since bonding.
-func (db *nodeDB) findFails(id NodeID) int {
-	return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
-}
-
-// updateFindFails updates the number of findnode failures since bonding.
-func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
-	return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
-}
-
-// querySeeds retrieves random nodes to be used as potential seed nodes
-// for bootstrapping.
-func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node {
-	var (
-		now   = time.Now()
-		nodes = make([]*Node, 0, n)
-		it    = db.lvl.NewIterator(nil, nil)
-		id    NodeID
-	)
-	defer it.Release()
-
-seek:
-	for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
-		// Seek to a random entry. The first byte is incremented by a
-		// random amount each time in order to increase the likelihood
-		// of hitting all existing nodes in very small databases.
-		ctr := id[0]
-		rand.Read(id[:])
-		id[0] = ctr + id[0]%16
-		it.Seek(makeKey(id, nodeDBDiscoverRoot))
-
-		n := nextNode(it)
-		if n == nil {
-			id[0] = 0
-			continue seek // iterator exhausted
-		}
-		if n.ID == db.self {
-			continue seek
-		}
-		if now.Sub(db.lastPong(n.ID)) > maxAge {
-			continue seek
-		}
-		for i := range nodes {
-			if nodes[i].ID == n.ID {
-				continue seek // duplicate
-			}
-		}
-		nodes = append(nodes, n)
-	}
-	return nodes
-}
-
-func (db *nodeDB) fetchTopicRegTickets(id NodeID) (issued, used uint32) {
-	key := makeKey(id, nodeDBTopicRegTickets)
-	blob, _ := db.lvl.Get(key, nil)
-	if len(blob) != 8 {
-		return 0, 0
-	}
-	issued = binary.BigEndian.Uint32(blob[0:4])
-	used = binary.BigEndian.Uint32(blob[4:8])
-	return
-}
-
-func (db *nodeDB) updateTopicRegTickets(id NodeID, issued, used uint32) error {
-	key := makeKey(id, nodeDBTopicRegTickets)
-	blob := make([]byte, 8)
-	binary.BigEndian.PutUint32(blob[0:4], issued)
-	binary.BigEndian.PutUint32(blob[4:8], used)
-	return db.lvl.Put(key, blob, nil)
-}
-
-// reads the next node record from the iterator, skipping over other
-// database entries.
-func nextNode(it iterator.Iterator) *Node {
-	for end := false; !end; end = !it.Next() {
-		id, field := splitKey(it.Key())
-		if field != nodeDBDiscoverRoot {
-			continue
-		}
-		var n Node
-		if err := rlp.DecodeBytes(it.Value(), &n); err != nil {
-			log.Warn(fmt.Sprintf("invalid node %x: %v", id, err))
-			continue
-		}
-		return &n
-	}
-	return nil
-}
-
-// close flushes and closes the database files.
-func (db *nodeDB) close() {
-	close(db.quit)
-	db.lvl.Close()
-}
diff --git a/p2p/discv5/database_test.go b/p2p/discv5/database_test.go
deleted file mode 100644
index 2b86dc9cec..0000000000
--- a/p2p/discv5/database_test.go
+++ /dev/null
@@ -1,380 +0,0 @@
-// Copyright 2015 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 .
-
-package discv5
-
-import (
-	"bytes"
-	"io/ioutil"
-	"net"
-	"os"
-	"path/filepath"
-	"reflect"
-	"testing"
-	"time"
-)
-
-var nodeDBKeyTests = []struct {
-	id    NodeID
-	field string
-	key   []byte
-}{
-	{
-		id:    NodeID{},
-		field: "version",
-		key:   []byte{0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e}, // field
-	},
-	{
-		id:    MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-		field: ":discover",
-		key: []byte{0x6e, 0x3a, // prefix
-			0x1d, 0xd9, 0xd6, 0x5c, 0x45, 0x52, 0xb5, 0xeb, // node id
-			0x43, 0xd5, 0xad, 0x55, 0xa2, 0xee, 0x3f, 0x56, //
-			0xc6, 0xcb, 0xc1, 0xc6, 0x4a, 0x5c, 0x8d, 0x65, //
-			0x9f, 0x51, 0xfc, 0xd5, 0x1b, 0xac, 0xe2, 0x43, //
-			0x51, 0x23, 0x2b, 0x8d, 0x78, 0x21, 0x61, 0x7d, //
-			0x2b, 0x29, 0xb5, 0x4b, 0x81, 0xcd, 0xef, 0xb9, //
-			0xb3, 0xe9, 0xc3, 0x7d, 0x7f, 0xd5, 0xf6, 0x32, //
-			0x70, 0xbc, 0xc9, 0xe1, 0xa6, 0xf6, 0xa4, 0x39, //
-			0x3a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, // field
-		},
-	},
-}
-
-func TestNodeDBKeys(t *testing.T) {
-	for i, tt := range nodeDBKeyTests {
-		if key := makeKey(tt.id, tt.field); !bytes.Equal(key, tt.key) {
-			t.Errorf("make test %d: key mismatch: have 0x%x, want 0x%x", i, key, tt.key)
-		}
-		id, field := splitKey(tt.key)
-		if !bytes.Equal(id[:], tt.id[:]) {
-			t.Errorf("split test %d: id mismatch: have 0x%x, want 0x%x", i, id, tt.id)
-		}
-		if field != tt.field {
-			t.Errorf("split test %d: field mismatch: have 0x%x, want 0x%x", i, field, tt.field)
-		}
-	}
-}
-
-var nodeDBInt64Tests = []struct {
-	key   []byte
-	value int64
-}{
-	{key: []byte{0x01}, value: 1},
-	{key: []byte{0x02}, value: 2},
-	{key: []byte{0x03}, value: 3},
-}
-
-func TestNodeDBInt64(t *testing.T) {
-	db, _ := newNodeDB("", Version, NodeID{})
-	defer db.close()
-
-	tests := nodeDBInt64Tests
-	for i := 0; i < len(tests); i++ {
-		// Insert the next value
-		if err := db.storeInt64(tests[i].key, tests[i].value); err != nil {
-			t.Errorf("test %d: failed to store value: %v", i, err)
-		}
-		// Check all existing and non existing values
-		for j := 0; j < len(tests); j++ {
-			num := db.fetchInt64(tests[j].key)
-			switch {
-			case j <= i && num != tests[j].value:
-				t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, tests[j].value)
-			case j > i && num != 0:
-				t.Errorf("test %d, item %d: value mismatch: have %v, want %v", i, j, num, 0)
-			}
-		}
-	}
-}
-
-func TestNodeDBFetchStore(t *testing.T) {
-	node := NewNode(
-		MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-		net.IP{192, 168, 0, 1},
-		30303,
-		30303,
-	)
-	inst := time.Now()
-	num := 314
-
-	db, _ := newNodeDB("", Version, NodeID{})
-	defer db.close()
-
-	// Check fetch/store operations on a node ping object
-	if stored := db.lastPing(node.ID); stored.Unix() != 0 {
-		t.Errorf("ping: non-existing object: %v", stored)
-	}
-	if err := db.updateLastPing(node.ID, inst); err != nil {
-		t.Errorf("ping: failed to update: %v", err)
-	}
-	if stored := db.lastPing(node.ID); stored.Unix() != inst.Unix() {
-		t.Errorf("ping: value mismatch: have %v, want %v", stored, inst)
-	}
-	// Check fetch/store operations on a node pong object
-	if stored := db.lastPong(node.ID); stored.Unix() != 0 {
-		t.Errorf("pong: non-existing object: %v", stored)
-	}
-	if err := db.updateLastPong(node.ID, inst); err != nil {
-		t.Errorf("pong: failed to update: %v", err)
-	}
-	if stored := db.lastPong(node.ID); stored.Unix() != inst.Unix() {
-		t.Errorf("pong: value mismatch: have %v, want %v", stored, inst)
-	}
-	// Check fetch/store operations on a node findnode-failure object
-	if stored := db.findFails(node.ID); stored != 0 {
-		t.Errorf("find-node fails: non-existing object: %v", stored)
-	}
-	if err := db.updateFindFails(node.ID, num); err != nil {
-		t.Errorf("find-node fails: failed to update: %v", err)
-	}
-	if stored := db.findFails(node.ID); stored != num {
-		t.Errorf("find-node fails: value mismatch: have %v, want %v", stored, num)
-	}
-	// Check fetch/store operations on an actual node object
-	if stored := db.node(node.ID); stored != nil {
-		t.Errorf("node: non-existing object: %v", stored)
-	}
-	if err := db.updateNode(node); err != nil {
-		t.Errorf("node: failed to update: %v", err)
-	}
-	if stored := db.node(node.ID); stored == nil {
-		t.Errorf("node: not found")
-	} else if !reflect.DeepEqual(stored, node) {
-		t.Errorf("node: data mismatch: have %v, want %v", stored, node)
-	}
-}
-
-var nodeDBSeedQueryNodes = []struct {
-	node *Node
-	pong time.Time
-}{
-	// This one should not be in the result set because its last
-	// pong time is too far in the past.
-	{
-		node: NewNode(
-			MustHexID("0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 3},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-3 * time.Hour),
-	},
-	// This one shouldn't be in the result set because its
-	// nodeID is the local node's ID.
-	{
-		node: NewNode(
-			MustHexID("0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 3},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-4 * time.Second),
-	},
-
-	// These should be in the result set.
-	{
-		node: NewNode(
-			MustHexID("0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 1},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-2 * time.Second),
-	},
-	{
-		node: NewNode(
-			MustHexID("0x44d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 2},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-3 * time.Second),
-	},
-	{
-		node: NewNode(
-			MustHexID("0xe2d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 3},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-1 * time.Second),
-	},
-}
-
-func TestNodeDBSeedQuery(t *testing.T) {
-	db, _ := newNodeDB("", Version, nodeDBSeedQueryNodes[1].node.ID)
-	defer db.close()
-
-	// Insert a batch of nodes for querying
-	for i, seed := range nodeDBSeedQueryNodes {
-		if err := db.updateNode(seed.node); err != nil {
-			t.Fatalf("node %d: failed to insert: %v", i, err)
-		}
-		if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil {
-			t.Fatalf("node %d: failed to insert lastPong: %v", i, err)
-		}
-	}
-
-	// Retrieve the entire batch and check for duplicates
-	seeds := db.querySeeds(len(nodeDBSeedQueryNodes)*2, time.Hour)
-	have := make(map[NodeID]struct{})
-	for _, seed := range seeds {
-		have[seed.ID] = struct{}{}
-	}
-	want := make(map[NodeID]struct{})
-	for _, seed := range nodeDBSeedQueryNodes[2:] {
-		want[seed.node.ID] = struct{}{}
-	}
-	if len(seeds) != len(want) {
-		t.Errorf("seed count mismatch: have %v, want %v", len(seeds), len(want))
-	}
-	for id := range have {
-		if _, ok := want[id]; !ok {
-			t.Errorf("extra seed: %v", id)
-		}
-	}
-	for id := range want {
-		if _, ok := have[id]; !ok {
-			t.Errorf("missing seed: %v", id)
-		}
-	}
-}
-
-func TestNodeDBPersistency(t *testing.T) {
-	root, err := ioutil.TempDir("", "nodedb-")
-	if err != nil {
-		t.Fatalf("failed to create temporary data folder: %v", err)
-	}
-	defer os.RemoveAll(root)
-
-	var (
-		testKey = []byte("somekey")
-		testInt = int64(314)
-	)
-
-	// Create a persistent database and store some values
-	db, err := newNodeDB(filepath.Join(root, "database"), Version, NodeID{})
-	if err != nil {
-		t.Fatalf("failed to create persistent database: %v", err)
-	}
-	if err := db.storeInt64(testKey, testInt); err != nil {
-		t.Fatalf("failed to store value: %v.", err)
-	}
-	db.close()
-
-	// Reopen the database and check the value
-	db, err = newNodeDB(filepath.Join(root, "database"), Version, NodeID{})
-	if err != nil {
-		t.Fatalf("failed to open persistent database: %v", err)
-	}
-	if val := db.fetchInt64(testKey); val != testInt {
-		t.Fatalf("value mismatch: have %v, want %v", val, testInt)
-	}
-	db.close()
-
-	// Change the database version and check flush
-	db, err = newNodeDB(filepath.Join(root, "database"), Version+1, NodeID{})
-	if err != nil {
-		t.Fatalf("failed to open persistent database: %v", err)
-	}
-	if val := db.fetchInt64(testKey); val != 0 {
-		t.Fatalf("value mismatch: have %v, want %v", val, 0)
-	}
-	db.close()
-}
-
-var nodeDBExpirationNodes = []struct {
-	node *Node
-	pong time.Time
-	exp  bool
-}{
-	{
-		node: NewNode(
-			MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 1},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute),
-		exp:  false,
-	}, {
-		node: NewNode(
-			MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{127, 0, 0, 2},
-			30303,
-			30303,
-		),
-		pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute),
-		exp:  true,
-	},
-}
-
-func TestNodeDBExpiration(t *testing.T) {
-	db, _ := newNodeDB("", Version, NodeID{})
-	defer db.close()
-
-	// Add all the test nodes and set their last pong time
-	for i, seed := range nodeDBExpirationNodes {
-		if err := db.updateNode(seed.node); err != nil {
-			t.Fatalf("node %d: failed to insert: %v", i, err)
-		}
-		if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil {
-			t.Fatalf("node %d: failed to update pong: %v", i, err)
-		}
-	}
-	// Expire some of them, and check the rest
-	if err := db.expireNodes(); err != nil {
-		t.Fatalf("failed to expire nodes: %v", err)
-	}
-	for i, seed := range nodeDBExpirationNodes {
-		node := db.node(seed.node.ID)
-		if (node == nil && !seed.exp) || (node != nil && seed.exp) {
-			t.Errorf("node %d: expiration mismatch: have %v, want %v", i, node, seed.exp)
-		}
-	}
-}
-
-func TestNodeDBSelfExpiration(t *testing.T) {
-	// Find a node in the tests that shouldn't expire, and assign it as self
-	var self NodeID
-	for _, node := range nodeDBExpirationNodes {
-		if !node.exp {
-			self = node.node.ID
-			break
-		}
-	}
-	db, _ := newNodeDB("", Version, self)
-	defer db.close()
-
-	// Add all the test nodes and set their last pong time
-	for i, seed := range nodeDBExpirationNodes {
-		if err := db.updateNode(seed.node); err != nil {
-			t.Fatalf("node %d: failed to insert: %v", i, err)
-		}
-		if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil {
-			t.Fatalf("node %d: failed to update pong: %v", i, err)
-		}
-	}
-	// Expire the nodes and make sure self has been evacuated too
-	if err := db.expireNodes(); err != nil {
-		t.Fatalf("failed to expire nodes: %v", err)
-	}
-	node := db.node(self)
-	if node != nil {
-		t.Errorf("self not evacuated")
-	}
-}
diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go
deleted file mode 100644
index c912cba7d1..0000000000
--- a/p2p/discv5/net.go
+++ /dev/null
@@ -1,1266 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"errors"
-	"fmt"
-	"net"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p/netutil"
-	"github.com/ethereum/go-ethereum/rlp"
-	"golang.org/x/crypto/sha3"
-)
-
-var (
-	errInvalidEvent = errors.New("invalid in current state")
-	errNoQuery      = errors.New("no pending query")
-)
-
-const (
-	autoRefreshInterval   = 1 * time.Hour
-	bucketRefreshInterval = 1 * time.Minute
-	seedCount             = 30
-	seedMaxAge            = 5 * 24 * time.Hour
-	lowPort               = 1024
-)
-
-const testTopic = "foo"
-
-const (
-	printTestImgLogs = false
-)
-
-// Network manages the table and all protocol interaction.
-type Network struct {
-	db          *nodeDB // database of known nodes
-	conn        transport
-	netrestrict *netutil.Netlist
-
-	closed           chan struct{}          // closed when loop is done
-	closeReq         chan struct{}          // 'request to close'
-	refreshReq       chan []*Node           // lookups ask for refresh on this channel
-	refreshResp      chan (<-chan struct{}) // ...and get the channel to block on from this one
-	read             chan ingressPacket     // ingress packets arrive here
-	timeout          chan timeoutEvent
-	queryReq         chan *findnodeQuery // lookups submit findnode queries on this channel
-	tableOpReq       chan func()
-	tableOpResp      chan struct{}
-	topicRegisterReq chan topicRegisterReq
-	topicSearchReq   chan topicSearchReq
-
-	// State of the main loop.
-	tab           *Table
-	topictab      *topicTable
-	ticketStore   *ticketStore
-	nursery       []*Node
-	nodes         map[NodeID]*Node // tracks active nodes with state != known
-	timeoutTimers map[timeoutEvent]*time.Timer
-}
-
-// transport is implemented by the UDP transport.
-// it is an interface so we can test without opening lots of UDP
-// sockets and without generating a private key.
-type transport interface {
-	sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) (hash []byte)
-	sendNeighbours(remote *Node, nodes []*Node)
-	sendFindnodeHash(remote *Node, target common.Hash)
-	sendTopicRegister(remote *Node, topics []Topic, topicIdx int, pong []byte)
-	sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node)
-
-	send(remote *Node, ptype nodeEvent, p interface{}) (hash []byte)
-
-	localAddr() *net.UDPAddr
-	Close()
-}
-
-type findnodeQuery struct {
-	remote *Node
-	target common.Hash
-	reply  chan<- []*Node
-}
-
-type topicRegisterReq struct {
-	add   bool
-	topic Topic
-}
-
-type topicSearchReq struct {
-	topic  Topic
-	found  chan<- *Node
-	lookup chan<- bool
-	delay  time.Duration
-}
-
-type topicSearchResult struct {
-	target lookupInfo
-	nodes  []*Node
-}
-
-type timeoutEvent struct {
-	ev   nodeEvent
-	node *Node
-}
-
-func newNetwork(conn transport, ourPubkey ecdsa.PublicKey, dbPath string, netrestrict *netutil.Netlist) (*Network, error) {
-	ourID := PubkeyID(&ourPubkey)
-
-	var db *nodeDB
-	if dbPath != "" {
-		var err error
-		if db, err = newNodeDB(dbPath, Version, ourID); err != nil {
-			return nil, err
-		}
-	}
-
-	tab := newTable(ourID, conn.localAddr())
-	net := &Network{
-		db:               db,
-		conn:             conn,
-		netrestrict:      netrestrict,
-		tab:              tab,
-		topictab:         newTopicTable(db, tab.self),
-		ticketStore:      newTicketStore(),
-		refreshReq:       make(chan []*Node),
-		refreshResp:      make(chan (<-chan struct{})),
-		closed:           make(chan struct{}),
-		closeReq:         make(chan struct{}),
-		read:             make(chan ingressPacket, 100),
-		timeout:          make(chan timeoutEvent),
-		timeoutTimers:    make(map[timeoutEvent]*time.Timer),
-		tableOpReq:       make(chan func()),
-		tableOpResp:      make(chan struct{}),
-		queryReq:         make(chan *findnodeQuery),
-		topicRegisterReq: make(chan topicRegisterReq),
-		topicSearchReq:   make(chan topicSearchReq),
-		nodes:            make(map[NodeID]*Node),
-	}
-	go net.loop()
-	return net, nil
-}
-
-// Close terminates the network listener and flushes the node database.
-func (net *Network) Close() {
-	net.conn.Close()
-	select {
-	case <-net.closed:
-	case net.closeReq <- struct{}{}:
-		<-net.closed
-	}
-}
-
-// Self returns the local node.
-// The returned node should not be modified by the caller.
-func (net *Network) Self() *Node {
-	return net.tab.self
-}
-
-// ReadRandomNodes fills the given slice with random nodes from the
-// table. It will not write the same node more than once. The nodes in
-// the slice are copies and can be modified by the caller.
-func (net *Network) ReadRandomNodes(buf []*Node) (n int) {
-	net.reqTableOp(func() { n = net.tab.readRandomNodes(buf) })
-	return n
-}
-
-// SetFallbackNodes sets the initial points of contact. These nodes
-// are used to connect to the network if the table is empty and there
-// are no known nodes in the database.
-func (net *Network) SetFallbackNodes(nodes []*Node) error {
-	nursery := make([]*Node, 0, len(nodes))
-	for _, n := range nodes {
-		if err := n.validateComplete(); err != nil {
-			return fmt.Errorf("bad bootstrap/fallback node %q (%v)", n, err)
-		}
-		// Recompute cpy.sha because the node might not have been
-		// created by NewNode or ParseNode.
-		cpy := *n
-		cpy.sha = crypto.Keccak256Hash(n.ID[:])
-		nursery = append(nursery, &cpy)
-	}
-	net.reqRefresh(nursery)
-	return nil
-}
-
-// Resolve searches for a specific node with the given ID.
-// It returns nil if the node could not be found.
-func (net *Network) Resolve(targetID NodeID) *Node {
-	result := net.lookup(crypto.Keccak256Hash(targetID[:]), true)
-	for _, n := range result {
-		if n.ID == targetID {
-			return n
-		}
-	}
-	return nil
-}
-
-// Lookup performs a network search for nodes close
-// to the given target. It approaches the target by querying
-// nodes that are closer to it on each iteration.
-// The given target does not need to be an actual node
-// identifier.
-//
-// The local node may be included in the result.
-func (net *Network) Lookup(targetID NodeID) []*Node {
-	return net.lookup(crypto.Keccak256Hash(targetID[:]), false)
-}
-
-func (net *Network) lookup(target common.Hash, stopOnMatch bool) []*Node {
-	var (
-		asked          = make(map[NodeID]bool)
-		seen           = make(map[NodeID]bool)
-		reply          = make(chan []*Node, alpha)
-		result         = nodesByDistance{target: target}
-		pendingQueries = 0
-	)
-	// Get initial answers from the local node.
-	result.push(net.tab.self, bucketSize)
-	for {
-		// Ask the α closest nodes that we haven't asked yet.
-		for i := 0; i < len(result.entries) && pendingQueries < alpha; i++ {
-			n := result.entries[i]
-			if !asked[n.ID] {
-				asked[n.ID] = true
-				pendingQueries++
-				net.reqQueryFindnode(n, target, reply)
-			}
-		}
-		if pendingQueries == 0 {
-			// We have asked all closest nodes, stop the search.
-			break
-		}
-		// Wait for the next reply.
-		select {
-		case nodes := <-reply:
-			for _, n := range nodes {
-				if n != nil && !seen[n.ID] {
-					seen[n.ID] = true
-					result.push(n, bucketSize)
-					if stopOnMatch && n.sha == target {
-						return result.entries
-					}
-				}
-			}
-			pendingQueries--
-		case <-time.After(respTimeout):
-			// forget all pending requests, start new ones
-			pendingQueries = 0
-			reply = make(chan []*Node, alpha)
-		}
-	}
-	return result.entries
-}
-
-func (net *Network) RegisterTopic(topic Topic, stop <-chan struct{}) {
-	select {
-	case net.topicRegisterReq <- topicRegisterReq{true, topic}:
-	case <-net.closed:
-		return
-	}
-	select {
-	case <-net.closed:
-	case <-stop:
-		select {
-		case net.topicRegisterReq <- topicRegisterReq{false, topic}:
-		case <-net.closed:
-		}
-	}
-}
-
-func (net *Network) SearchTopic(topic Topic, setPeriod <-chan time.Duration, found chan<- *Node, lookup chan<- bool) {
-	for {
-		select {
-		case <-net.closed:
-			return
-		case delay, ok := <-setPeriod:
-			select {
-			case net.topicSearchReq <- topicSearchReq{topic: topic, found: found, lookup: lookup, delay: delay}:
-			case <-net.closed:
-				return
-			}
-			if !ok {
-				return
-			}
-		}
-	}
-}
-
-func (net *Network) reqRefresh(nursery []*Node) <-chan struct{} {
-	select {
-	case net.refreshReq <- nursery:
-		return <-net.refreshResp
-	case <-net.closed:
-		return net.closed
-	}
-}
-
-func (net *Network) reqQueryFindnode(n *Node, target common.Hash, reply chan []*Node) bool {
-	q := &findnodeQuery{remote: n, target: target, reply: reply}
-	select {
-	case net.queryReq <- q:
-		return true
-	case <-net.closed:
-		return false
-	}
-}
-
-func (net *Network) reqReadPacket(pkt ingressPacket) {
-	select {
-	case net.read <- pkt:
-	case <-net.closed:
-	}
-}
-
-func (net *Network) reqTableOp(f func()) (called bool) {
-	select {
-	case net.tableOpReq <- f:
-		<-net.tableOpResp
-		return true
-	case <-net.closed:
-		return false
-	}
-}
-
-// TODO: external address handling.
-
-type topicSearchInfo struct {
-	lookupChn chan<- bool
-	period    time.Duration
-}
-
-const maxSearchCount = 5
-
-func (net *Network) loop() {
-	var (
-		refreshTimer       = time.NewTicker(autoRefreshInterval)
-		bucketRefreshTimer = time.NewTimer(bucketRefreshInterval)
-		refreshDone        chan struct{} // closed when the 'refresh' lookup has ended
-	)
-	defer refreshTimer.Stop()
-	defer bucketRefreshTimer.Stop()
-
-	// Tracking the next ticket to register.
-	var (
-		nextTicket        *ticketRef
-		nextRegisterTimer *time.Timer
-		nextRegisterTime  <-chan time.Time
-	)
-	defer func() {
-		if nextRegisterTimer != nil {
-			nextRegisterTimer.Stop()
-		}
-	}()
-	resetNextTicket := func() {
-		ticket, timeout := net.ticketStore.nextFilteredTicket()
-		if nextTicket != ticket {
-			nextTicket = ticket
-			if nextRegisterTimer != nil {
-				nextRegisterTimer.Stop()
-				nextRegisterTime = nil
-			}
-			if ticket != nil {
-				nextRegisterTimer = time.NewTimer(timeout)
-				nextRegisterTime = nextRegisterTimer.C
-			}
-		}
-	}
-
-	// Tracking registration and search lookups.
-	var (
-		topicRegisterLookupTarget lookupInfo
-		topicRegisterLookupDone   chan []*Node
-		topicRegisterLookupTick   = time.NewTimer(0)
-		searchReqWhenRefreshDone  []topicSearchReq
-		searchInfo                = make(map[Topic]topicSearchInfo)
-		activeSearchCount         int
-	)
-	defer topicRegisterLookupTick.Stop()
-	topicSearchLookupDone := make(chan topicSearchResult, 100)
-	topicSearch := make(chan Topic, 100)
-	<-topicRegisterLookupTick.C
-
-	statsDump := time.NewTicker(10 * time.Second)
-	defer statsDump.Stop()
-
-loop:
-	for {
-		resetNextTicket()
-
-		select {
-		case <-net.closeReq:
-			log.Trace("<-net.closeReq")
-			break loop
-
-		// Ingress packet handling.
-		case pkt := <-net.read:
-			//fmt.Println("read", pkt.ev)
-			log.Trace("<-net.read")
-			n := net.internNode(&pkt)
-			prestate := n.state
-			status := "ok"
-			if err := net.handle(n, pkt.ev, &pkt); err != nil {
-				status = err.Error()
-			}
-			log.Trace("", "msg", log.Lazy{Fn: func() string {
-				return fmt.Sprintf("<<< (%d) %v from %x@%v: %v -> %v (%v)",
-					net.tab.count, pkt.ev, pkt.remoteID[:8], pkt.remoteAddr, prestate, n.state, status)
-			}})
-			// TODO: persist state if n.state goes >= known, delete if it goes <= known
-
-		// State transition timeouts.
-		case timeout := <-net.timeout:
-			log.Trace("<-net.timeout")
-			if net.timeoutTimers[timeout] == nil {
-				// Stale timer (was aborted).
-				continue
-			}
-			delete(net.timeoutTimers, timeout)
-			prestate := timeout.node.state
-			status := "ok"
-			if err := net.handle(timeout.node, timeout.ev, nil); err != nil {
-				status = err.Error()
-			}
-			log.Trace("", "msg", log.Lazy{Fn: func() string {
-				return fmt.Sprintf("--- (%d) %v for %x@%v: %v -> %v (%v)",
-					net.tab.count, timeout.ev, timeout.node.ID[:8], timeout.node.addr(), prestate, timeout.node.state, status)
-			}})
-
-		// Querying.
-		case q := <-net.queryReq:
-			log.Trace("<-net.queryReq")
-			if !q.start(net) {
-				q.remote.deferQuery(q)
-			}
-
-		// Interacting with the table.
-		case f := <-net.tableOpReq:
-			log.Trace("<-net.tableOpReq")
-			f()
-			net.tableOpResp <- struct{}{}
-
-		// Topic registration stuff.
-		case req := <-net.topicRegisterReq:
-			log.Trace("<-net.topicRegisterReq")
-			if !req.add {
-				net.ticketStore.removeRegisterTopic(req.topic)
-				continue
-			}
-			net.ticketStore.addTopic(req.topic, true)
-			// If we're currently waiting idle (nothing to look up), give the ticket store a
-			// chance to start it sooner. This should speed up convergence of the radius
-			// determination for new topics.
-			// if topicRegisterLookupDone == nil {
-			if topicRegisterLookupTarget.target == (common.Hash{}) {
-				log.Trace("topicRegisterLookupTarget == null")
-				if topicRegisterLookupTick.Stop() {
-					<-topicRegisterLookupTick.C
-				}
-				target, delay := net.ticketStore.nextRegisterLookup()
-				topicRegisterLookupTarget = target
-				topicRegisterLookupTick.Reset(delay)
-			}
-
-		case nodes := <-topicRegisterLookupDone:
-			log.Trace("<-topicRegisterLookupDone")
-			net.ticketStore.registerLookupDone(topicRegisterLookupTarget, nodes, func(n *Node) []byte {
-				net.ping(n, n.addr())
-				return n.pingEcho
-			})
-			target, delay := net.ticketStore.nextRegisterLookup()
-			topicRegisterLookupTarget = target
-			topicRegisterLookupTick.Reset(delay)
-			topicRegisterLookupDone = nil
-
-		case <-topicRegisterLookupTick.C:
-			log.Trace("<-topicRegisterLookupTick")
-			if (topicRegisterLookupTarget.target == common.Hash{}) {
-				target, delay := net.ticketStore.nextRegisterLookup()
-				topicRegisterLookupTarget = target
-				topicRegisterLookupTick.Reset(delay)
-				topicRegisterLookupDone = nil
-			} else {
-				topicRegisterLookupDone = make(chan []*Node)
-				target := topicRegisterLookupTarget.target
-				go func() { topicRegisterLookupDone <- net.lookup(target, false) }()
-			}
-
-		case <-nextRegisterTime:
-			log.Trace("<-nextRegisterTime")
-			net.ticketStore.ticketRegistered(*nextTicket)
-			//fmt.Println("sendTopicRegister", nextTicket.t.node.addr().String(), nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong)
-			net.conn.sendTopicRegister(nextTicket.t.node, nextTicket.t.topics, nextTicket.idx, nextTicket.t.pong)
-
-		case req := <-net.topicSearchReq:
-			if refreshDone == nil {
-				log.Trace("<-net.topicSearchReq")
-				info, ok := searchInfo[req.topic]
-				if ok {
-					if req.delay == time.Duration(0) {
-						delete(searchInfo, req.topic)
-						net.ticketStore.removeSearchTopic(req.topic)
-					} else {
-						info.period = req.delay
-						searchInfo[req.topic] = info
-					}
-					continue
-				}
-				if req.delay != time.Duration(0) {
-					var info topicSearchInfo
-					info.period = req.delay
-					info.lookupChn = req.lookup
-					searchInfo[req.topic] = info
-					net.ticketStore.addSearchTopic(req.topic, req.found)
-					topicSearch <- req.topic
-				}
-			} else {
-				searchReqWhenRefreshDone = append(searchReqWhenRefreshDone, req)
-			}
-
-		case topic := <-topicSearch:
-			if activeSearchCount < maxSearchCount {
-				activeSearchCount++
-				target := net.ticketStore.nextSearchLookup(topic)
-				go func() {
-					nodes := net.lookup(target.target, false)
-					topicSearchLookupDone <- topicSearchResult{target: target, nodes: nodes}
-				}()
-			}
-			period := searchInfo[topic].period
-			if period != time.Duration(0) {
-				go func() {
-					time.Sleep(period)
-					topicSearch <- topic
-				}()
-			}
-
-		case res := <-topicSearchLookupDone:
-			activeSearchCount--
-			if lookupChn := searchInfo[res.target.topic].lookupChn; lookupChn != nil {
-				lookupChn <- net.ticketStore.radius[res.target.topic].converged
-			}
-			net.ticketStore.searchLookupDone(res.target, res.nodes, func(n *Node, topic Topic) []byte {
-				if n.state != nil && n.state.canQuery {
-					return net.conn.send(n, topicQueryPacket, topicQuery{Topic: topic}) // TODO: set expiration
-				}
-				if n.state == unknown {
-					net.ping(n, n.addr())
-				}
-				return nil
-			})
-
-		case <-statsDump.C:
-			log.Trace("<-statsDump.C")
-			/*r, ok := net.ticketStore.radius[testTopic]
-			if !ok {
-				fmt.Printf("(%x) no radius @ %v\n", net.tab.self.ID[:8], time.Now())
-			} else {
-				topics := len(net.ticketStore.tickets)
-				tickets := len(net.ticketStore.nodes)
-				rad := r.radius / (maxRadius/10000+1)
-				fmt.Printf("(%x) topics:%d radius:%d tickets:%d @ %v\n", net.tab.self.ID[:8], topics, rad, tickets, time.Now())
-			}*/
-
-			tm := mclock.Now()
-			for topic, r := range net.ticketStore.radius {
-				if printTestImgLogs {
-					rad := r.radius / (maxRadius/1000000 + 1)
-					minrad := r.minRadius / (maxRadius/1000000 + 1)
-					fmt.Printf("*R %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], rad)
-					fmt.Printf("*MR %d %v %016x %v\n", tm/1000000, topic, net.tab.self.sha[:8], minrad)
-				}
-			}
-			for topic, t := range net.topictab.topics {
-				wp := t.wcl.nextWaitPeriod(tm)
-				if printTestImgLogs {
-					fmt.Printf("*W %d %v %016x %d\n", tm/1000000, topic, net.tab.self.sha[:8], wp/1000000)
-				}
-			}
-
-		// Periodic / lookup-initiated bucket refresh.
-		case <-refreshTimer.C:
-			log.Trace("<-refreshTimer.C")
-			// TODO: ideally we would start the refresh timer after
-			// fallback nodes have been set for the first time.
-			if refreshDone == nil {
-				refreshDone = make(chan struct{})
-				net.refresh(refreshDone)
-			}
-		case <-bucketRefreshTimer.C:
-			target := net.tab.chooseBucketRefreshTarget()
-			go func() {
-				net.lookup(target, false)
-				bucketRefreshTimer.Reset(bucketRefreshInterval)
-			}()
-		case newNursery := <-net.refreshReq:
-			log.Trace("<-net.refreshReq")
-			if newNursery != nil {
-				net.nursery = newNursery
-			}
-			if refreshDone == nil {
-				refreshDone = make(chan struct{})
-				net.refresh(refreshDone)
-			}
-			net.refreshResp <- refreshDone
-		case <-refreshDone:
-			log.Trace("<-net.refreshDone", "table size", net.tab.count)
-			if net.tab.count != 0 {
-				refreshDone = nil
-				list := searchReqWhenRefreshDone
-				searchReqWhenRefreshDone = nil
-				go func() {
-					for _, req := range list {
-						net.topicSearchReq <- req
-					}
-				}()
-			} else {
-				refreshDone = make(chan struct{})
-				net.refresh(refreshDone)
-			}
-		}
-	}
-	log.Trace("loop stopped")
-
-	log.Debug("shutting down")
-	if net.conn != nil {
-		net.conn.Close()
-	}
-	// TODO: wait for pending refresh.
-	// if refreshDone != nil {
-	// 	<-refreshResults
-	// }
-	// Cancel all pending timeouts.
-	for _, timer := range net.timeoutTimers {
-		timer.Stop()
-	}
-	if net.db != nil {
-		net.db.close()
-	}
-	close(net.closed)
-}
-
-// Everything below runs on the Network.loop goroutine
-// and can modify Node, Table and Network at any time without locking.
-
-func (net *Network) refresh(done chan<- struct{}) {
-	var seeds []*Node
-	if net.db != nil {
-		seeds = net.db.querySeeds(seedCount, seedMaxAge)
-	}
-	if len(seeds) == 0 {
-		seeds = net.nursery
-	}
-	if len(seeds) == 0 {
-		log.Trace("no seed nodes found")
-		time.AfterFunc(time.Second*10, func() { close(done) })
-		return
-	}
-	for _, n := range seeds {
-		log.Debug("", "msg", log.Lazy{Fn: func() string {
-			var age string
-			if net.db != nil {
-				age = time.Since(net.db.lastPong(n.ID)).String()
-			} else {
-				age = "unknown"
-			}
-			return fmt.Sprintf("seed node (age %s): %v", age, n)
-		}})
-		n = net.internNodeFromDB(n)
-		if n.state == unknown {
-			net.transition(n, verifyinit)
-		}
-		// Force-add the seed node so Lookup does something.
-		// It will be deleted again if verification fails.
-		net.tab.add(n)
-	}
-	// Start self lookup to fill up the buckets.
-	go func() {
-		net.Lookup(net.tab.self.ID)
-		close(done)
-	}()
-}
-
-// Node Interning.
-
-func (net *Network) internNode(pkt *ingressPacket) *Node {
-	if n := net.nodes[pkt.remoteID]; n != nil {
-		n.IP = pkt.remoteAddr.IP
-		n.UDP = uint16(pkt.remoteAddr.Port)
-		n.TCP = uint16(pkt.remoteAddr.Port)
-		return n
-	}
-	n := NewNode(pkt.remoteID, pkt.remoteAddr.IP, uint16(pkt.remoteAddr.Port), uint16(pkt.remoteAddr.Port))
-	n.state = unknown
-	net.nodes[pkt.remoteID] = n
-	return n
-}
-
-func (net *Network) internNodeFromDB(dbn *Node) *Node {
-	if n := net.nodes[dbn.ID]; n != nil {
-		return n
-	}
-	n := NewNode(dbn.ID, dbn.IP, dbn.UDP, dbn.TCP)
-	n.state = unknown
-	net.nodes[n.ID] = n
-	return n
-}
-
-func (net *Network) internNodeFromNeighbours(sender *net.UDPAddr, rn rpcNode) (n *Node, err error) {
-	if rn.ID == net.tab.self.ID {
-		return nil, errors.New("is self")
-	}
-	if rn.UDP <= lowPort {
-		return nil, errors.New("low port")
-	}
-	n = net.nodes[rn.ID]
-	if n == nil {
-		// We haven't seen this node before.
-		n, err = nodeFromRPC(sender, rn)
-		if net.netrestrict != nil && !net.netrestrict.Contains(n.IP) {
-			return n, errors.New("not contained in netrestrict whitelist")
-		}
-		if err == nil {
-			n.state = unknown
-			net.nodes[n.ID] = n
-		}
-		return n, err
-	}
-	if !n.IP.Equal(rn.IP) || n.UDP != rn.UDP || n.TCP != rn.TCP {
-		if n.state == known {
-			// reject address change if node is known by us
-			err = fmt.Errorf("metadata mismatch: got %v, want %v", rn, n)
-		} else {
-			// accept otherwise; this will be handled nicer with signed ENRs
-			n.IP = rn.IP
-			n.UDP = rn.UDP
-			n.TCP = rn.TCP
-		}
-	}
-	return n, err
-}
-
-// nodeNetGuts is embedded in Node and contains fields.
-type nodeNetGuts struct {
-	// This is a cached copy of sha3(ID) which is used for node
-	// distance calculations. This is part of Node in order to make it
-	// possible to write tests that need a node at a certain distance.
-	// In those tests, the content of sha will not actually correspond
-	// with ID.
-	sha common.Hash
-
-	// State machine fields. Access to these fields
-	// is restricted to the Network.loop goroutine.
-	state             *nodeState
-	pingEcho          []byte           // hash of last ping sent by us
-	pingTopics        []Topic          // topic set sent by us in last ping
-	deferredQueries   []*findnodeQuery // queries that can't be sent yet
-	pendingNeighbours *findnodeQuery   // current query, waiting for reply
-	queryTimeouts     int
-}
-
-func (n *nodeNetGuts) deferQuery(q *findnodeQuery) {
-	n.deferredQueries = append(n.deferredQueries, q)
-}
-
-func (n *nodeNetGuts) startNextQuery(net *Network) {
-	if len(n.deferredQueries) == 0 {
-		return
-	}
-	nextq := n.deferredQueries[0]
-	if nextq.start(net) {
-		n.deferredQueries = append(n.deferredQueries[:0], n.deferredQueries[1:]...)
-	}
-}
-
-func (q *findnodeQuery) start(net *Network) bool {
-	// Satisfy queries against the local node directly.
-	if q.remote == net.tab.self {
-		closest := net.tab.closest(q.target, bucketSize)
-		q.reply <- closest.entries
-		return true
-	}
-	if q.remote.state.canQuery && q.remote.pendingNeighbours == nil {
-		net.conn.sendFindnodeHash(q.remote, q.target)
-		net.timedEvent(respTimeout, q.remote, neighboursTimeout)
-		q.remote.pendingNeighbours = q
-		return true
-	}
-	// If the node is not known yet, it won't accept queries.
-	// Initiate the transition to known.
-	// The request will be sent later when the node reaches known state.
-	if q.remote.state == unknown {
-		net.transition(q.remote, verifyinit)
-	}
-	return false
-}
-
-// Node Events (the input to the state machine).
-
-type nodeEvent uint
-
-//go:generate stringer -type=nodeEvent
-
-const (
-
-	// Packet type events.
-	// These correspond to packet types in the UDP protocol.
-	pingPacket = iota + 1
-	pongPacket
-	findnodePacket
-	neighborsPacket
-	findnodeHashPacket
-	topicRegisterPacket
-	topicQueryPacket
-	topicNodesPacket
-
-	// Non-packet events.
-	// Event values in this category are allocated outside
-	// the packet type range (packet types are encoded as a single byte).
-	pongTimeout nodeEvent = iota + 256
-	pingTimeout
-	neighboursTimeout
-)
-
-// Node State Machine.
-
-type nodeState struct {
-	name     string
-	handle   func(*Network, *Node, nodeEvent, *ingressPacket) (next *nodeState, err error)
-	enter    func(*Network, *Node)
-	canQuery bool
-}
-
-func (s *nodeState) String() string {
-	return s.name
-}
-
-var (
-	unknown          *nodeState
-	verifyinit       *nodeState
-	verifywait       *nodeState
-	remoteverifywait *nodeState
-	known            *nodeState
-	contested        *nodeState
-	unresponsive     *nodeState
-)
-
-func init() {
-	unknown = &nodeState{
-		name: "unknown",
-		enter: func(net *Network, n *Node) {
-			net.tab.delete(n)
-			n.pingEcho = nil
-			// Abort active queries.
-			for _, q := range n.deferredQueries {
-				q.reply <- nil
-			}
-			n.deferredQueries = nil
-			if n.pendingNeighbours != nil {
-				n.pendingNeighbours.reply <- nil
-				n.pendingNeighbours = nil
-			}
-			n.queryTimeouts = 0
-		},
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pingPacket:
-				net.handlePing(n, pkt)
-				net.ping(n, pkt.remoteAddr)
-				return verifywait, nil
-			default:
-				return unknown, errInvalidEvent
-			}
-		},
-	}
-
-	verifyinit = &nodeState{
-		name: "verifyinit",
-		enter: func(net *Network, n *Node) {
-			net.ping(n, n.addr())
-		},
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pingPacket:
-				net.handlePing(n, pkt)
-				return verifywait, nil
-			case pongPacket:
-				err := net.handleKnownPong(n, pkt)
-				return remoteverifywait, err
-			case pongTimeout:
-				return unknown, nil
-			default:
-				return verifyinit, errInvalidEvent
-			}
-		},
-	}
-
-	verifywait = &nodeState{
-		name: "verifywait",
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pingPacket:
-				net.handlePing(n, pkt)
-				return verifywait, nil
-			case pongPacket:
-				err := net.handleKnownPong(n, pkt)
-				return known, err
-			case pongTimeout:
-				return unknown, nil
-			default:
-				return verifywait, errInvalidEvent
-			}
-		},
-	}
-
-	remoteverifywait = &nodeState{
-		name: "remoteverifywait",
-		enter: func(net *Network, n *Node) {
-			net.timedEvent(respTimeout, n, pingTimeout)
-		},
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pingPacket:
-				net.handlePing(n, pkt)
-				return remoteverifywait, nil
-			case pingTimeout:
-				return known, nil
-			default:
-				return remoteverifywait, errInvalidEvent
-			}
-		},
-	}
-
-	known = &nodeState{
-		name:     "known",
-		canQuery: true,
-		enter: func(net *Network, n *Node) {
-			n.queryTimeouts = 0
-			n.startNextQuery(net)
-			// Insert into the table and start revalidation of the last node
-			// in the bucket if it is full.
-			last := net.tab.add(n)
-			if last != nil && last.state == known {
-				// TODO: do this asynchronously
-				net.transition(last, contested)
-			}
-		},
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pingPacket:
-				net.handlePing(n, pkt)
-				return known, nil
-			case pongPacket:
-				err := net.handleKnownPong(n, pkt)
-				return known, err
-			default:
-				return net.handleQueryEvent(n, ev, pkt)
-			}
-		},
-	}
-
-	contested = &nodeState{
-		name:     "contested",
-		canQuery: true,
-		enter: func(net *Network, n *Node) {
-			net.ping(n, n.addr())
-		},
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pongPacket:
-				// Node is still alive.
-				err := net.handleKnownPong(n, pkt)
-				return known, err
-			case pongTimeout:
-				net.tab.deleteReplace(n)
-				return unresponsive, nil
-			case pingPacket:
-				net.handlePing(n, pkt)
-				return contested, nil
-			default:
-				return net.handleQueryEvent(n, ev, pkt)
-			}
-		},
-	}
-
-	unresponsive = &nodeState{
-		name:     "unresponsive",
-		canQuery: true,
-		handle: func(net *Network, n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-			switch ev {
-			case pingPacket:
-				net.handlePing(n, pkt)
-				return known, nil
-			case pongPacket:
-				err := net.handleKnownPong(n, pkt)
-				return known, err
-			default:
-				return net.handleQueryEvent(n, ev, pkt)
-			}
-		},
-	}
-}
-
-// handle processes packets sent by n and events related to n.
-func (net *Network) handle(n *Node, ev nodeEvent, pkt *ingressPacket) error {
-	//fmt.Println("handle", n.addr().String(), n.state, ev)
-	if pkt != nil {
-		if err := net.checkPacket(n, ev, pkt); err != nil {
-			//fmt.Println("check err:", err)
-			return err
-		}
-		// Start the background expiration goroutine after the first
-		// successful communication. Subsequent calls have no effect if it
-		// is already running. We do this here instead of somewhere else
-		// so that the search for seed nodes also considers older nodes
-		// that would otherwise be removed by the expirer.
-		if net.db != nil {
-			net.db.ensureExpirer()
-		}
-	}
-	if n.state == nil {
-		n.state = unknown //???
-	}
-	next, err := n.state.handle(net, n, ev, pkt)
-	net.transition(n, next)
-	//fmt.Println("new state:", n.state)
-	return err
-}
-
-func (net *Network) checkPacket(n *Node, ev nodeEvent, pkt *ingressPacket) error {
-	// Replay prevention checks.
-	switch ev {
-	case pingPacket, findnodeHashPacket, neighborsPacket:
-		// TODO: check date is > last date seen
-		// TODO: check ping version
-	case pongPacket:
-		if !bytes.Equal(pkt.data.(*pong).ReplyTok, n.pingEcho) {
-			// fmt.Println("pong reply token mismatch")
-			return fmt.Errorf("pong reply token mismatch")
-		}
-		n.pingEcho = nil
-	}
-	// Address validation.
-	// TODO: Ideally we would do the following:
-	//  - reject all packets with wrong address except ping.
-	//  - for ping with new address, transition to verifywait but keep the
-	//    previous node (with old address) around. if the new one reaches known,
-	//    swap it out.
-	return nil
-}
-
-func (net *Network) transition(n *Node, next *nodeState) {
-	if n.state != next {
-		n.state = next
-		if next.enter != nil {
-			next.enter(net, n)
-		}
-	}
-
-	// TODO: persist/unpersist node
-}
-
-func (net *Network) timedEvent(d time.Duration, n *Node, ev nodeEvent) {
-	timeout := timeoutEvent{ev, n}
-	net.timeoutTimers[timeout] = time.AfterFunc(d, func() {
-		select {
-		case net.timeout <- timeout:
-		case <-net.closed:
-		}
-	})
-}
-
-func (net *Network) abortTimedEvent(n *Node, ev nodeEvent) {
-	timer := net.timeoutTimers[timeoutEvent{ev, n}]
-	if timer != nil {
-		timer.Stop()
-		delete(net.timeoutTimers, timeoutEvent{ev, n})
-	}
-}
-
-func (net *Network) ping(n *Node, addr *net.UDPAddr) {
-	//fmt.Println("ping", n.addr().String(), n.ID.String(), n.sha.Hex())
-	if n.pingEcho != nil || n.ID == net.tab.self.ID {
-		//fmt.Println(" not sent")
-		return
-	}
-	log.Trace("Pinging remote node", "node", n.ID)
-	n.pingTopics = net.ticketStore.regTopicSet()
-	n.pingEcho = net.conn.sendPing(n, addr, n.pingTopics)
-	net.timedEvent(respTimeout, n, pongTimeout)
-}
-
-func (net *Network) handlePing(n *Node, pkt *ingressPacket) {
-	log.Trace("Handling remote ping", "node", n.ID)
-	ping := pkt.data.(*ping)
-	n.TCP = ping.From.TCP
-	t := net.topictab.getTicket(n, ping.Topics)
-
-	pong := &pong{
-		To:         makeEndpoint(n.addr(), n.TCP), // TODO: maybe use known TCP port from DB
-		ReplyTok:   pkt.hash,
-		Expiration: uint64(time.Now().Add(expiration).Unix()),
-	}
-	ticketToPong(t, pong)
-	net.conn.send(n, pongPacket, pong)
-}
-
-func (net *Network) handleKnownPong(n *Node, pkt *ingressPacket) error {
-	log.Trace("Handling known pong", "node", n.ID)
-	net.abortTimedEvent(n, pongTimeout)
-	now := mclock.Now()
-	ticket, err := pongToTicket(now, n.pingTopics, n, pkt)
-	if err == nil {
-		// fmt.Printf("(%x) ticket: %+v\n", net.tab.self.ID[:8], pkt.data)
-		net.ticketStore.addTicket(now, pkt.data.(*pong).ReplyTok, ticket)
-	} else {
-		log.Trace("Failed to convert pong to ticket", "err", err)
-	}
-	n.pingEcho = nil
-	n.pingTopics = nil
-	return err
-}
-
-func (net *Network) handleQueryEvent(n *Node, ev nodeEvent, pkt *ingressPacket) (*nodeState, error) {
-	switch ev {
-	case findnodePacket:
-		target := crypto.Keccak256Hash(pkt.data.(*findnode).Target[:])
-		results := net.tab.closest(target, bucketSize).entries
-		net.conn.sendNeighbours(n, results)
-		return n.state, nil
-	case neighborsPacket:
-		err := net.handleNeighboursPacket(n, pkt)
-		return n.state, err
-	case neighboursTimeout:
-		if n.pendingNeighbours != nil {
-			n.pendingNeighbours.reply <- nil
-			n.pendingNeighbours = nil
-		}
-		n.queryTimeouts++
-		if n.queryTimeouts > maxFindnodeFailures && n.state == known {
-			return contested, errors.New("too many timeouts")
-		}
-		return n.state, nil
-
-	// v5
-
-	case findnodeHashPacket:
-		results := net.tab.closest(pkt.data.(*findnodeHash).Target, bucketSize).entries
-		net.conn.sendNeighbours(n, results)
-		return n.state, nil
-	case topicRegisterPacket:
-		//fmt.Println("got topicRegisterPacket")
-		regdata := pkt.data.(*topicRegister)
-		pong, err := net.checkTopicRegister(regdata)
-		if err != nil {
-			//fmt.Println(err)
-			return n.state, fmt.Errorf("bad waiting ticket: %v", err)
-		}
-		net.topictab.useTicket(n, pong.TicketSerial, regdata.Topics, int(regdata.Idx), pong.Expiration, pong.WaitPeriods)
-		return n.state, nil
-	case topicQueryPacket:
-		// TODO: handle expiration
-		topic := pkt.data.(*topicQuery).Topic
-		results := net.topictab.getEntries(topic)
-		if _, ok := net.ticketStore.tickets[topic]; ok {
-			results = append(results, net.tab.self) // we're not registering in our own table but if we're advertising, return ourselves too
-		}
-		if len(results) > 10 {
-			results = results[:10]
-		}
-		var hash common.Hash
-		copy(hash[:], pkt.hash)
-		net.conn.sendTopicNodes(n, hash, results)
-		return n.state, nil
-	case topicNodesPacket:
-		p := pkt.data.(*topicNodes)
-		if net.ticketStore.gotTopicNodes(n, p.Echo, p.Nodes) {
-			n.queryTimeouts++
-			if n.queryTimeouts > maxFindnodeFailures && n.state == known {
-				return contested, errors.New("too many timeouts")
-			}
-		}
-		return n.state, nil
-
-	default:
-		return n.state, errInvalidEvent
-	}
-}
-
-func (net *Network) checkTopicRegister(data *topicRegister) (*pong, error) {
-	var pongpkt ingressPacket
-	if err := decodePacket(data.Pong, &pongpkt); err != nil {
-		return nil, err
-	}
-	if pongpkt.ev != pongPacket {
-		return nil, errors.New("is not pong packet")
-	}
-	if pongpkt.remoteID != net.tab.self.ID {
-		return nil, errors.New("not signed by us")
-	}
-	// check that we previously authorised all topics
-	// that the other side is trying to register.
-	if rlpHash(data.Topics) != pongpkt.data.(*pong).TopicHash {
-		return nil, errors.New("topic hash mismatch")
-	}
-	if data.Idx >= uint(len(data.Topics)) {
-		return nil, errors.New("topic index out of range")
-	}
-	return pongpkt.data.(*pong), nil
-}
-
-func rlpHash(x interface{}) (h common.Hash) {
-	hw := sha3.NewLegacyKeccak256()
-	rlp.Encode(hw, x)
-	hw.Sum(h[:0])
-	return h
-}
-
-func (net *Network) handleNeighboursPacket(n *Node, pkt *ingressPacket) error {
-	if n.pendingNeighbours == nil {
-		return errNoQuery
-	}
-	net.abortTimedEvent(n, neighboursTimeout)
-
-	req := pkt.data.(*neighbors)
-	nodes := make([]*Node, len(req.Nodes))
-	for i, rn := range req.Nodes {
-		nn, err := net.internNodeFromNeighbours(pkt.remoteAddr, rn)
-		if err != nil {
-			log.Debug(fmt.Sprintf("invalid neighbour (%v) from %x@%v: %v", rn.IP, n.ID[:8], pkt.remoteAddr, err))
-			continue
-		}
-		nodes[i] = nn
-		// Start validation of query results immediately.
-		// This fills the table quickly.
-		// TODO: generates way too many packets, maybe do it via queue.
-		if nn.state == unknown {
-			net.transition(nn, verifyinit)
-		}
-	}
-	// TODO: don't ignore second packet
-	n.pendingNeighbours.reply <- nodes
-	n.pendingNeighbours = nil
-	// Now that this query is done, start the next one.
-	n.startNextQuery(net)
-	return nil
-}
diff --git a/p2p/discv5/net_test.go b/p2p/discv5/net_test.go
deleted file mode 100644
index 29321bc86f..0000000000
--- a/p2p/discv5/net_test.go
+++ /dev/null
@@ -1,330 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"net"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-func TestNetwork_Lookup(t *testing.T) {
-	key, _ := crypto.GenerateKey()
-	network, err := newNetwork(lookupTestnet, key.PublicKey, "", nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	lookupTestnet.net = network
-	defer network.Close()
-
-	// lookup on empty table returns no nodes
-	// if results := network.Lookup(lookupTestnet.target, false); len(results) > 0 {
-	// 	t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results)
-	// }
-	// seed table with initial node (otherwise lookup will terminate immediately)
-	seeds := []*Node{NewNode(lookupTestnet.dists[256][0], net.IP{10, 0, 2, 99}, lowPort+256, 999)}
-	if err := network.SetFallbackNodes(seeds); err != nil {
-		t.Fatal(err)
-	}
-	time.Sleep(3 * time.Second)
-
-	results := network.Lookup(lookupTestnet.target)
-	t.Logf("results:")
-	for _, e := range results {
-		t.Logf("  ld=%d, %x", logdist(lookupTestnet.targetSha, e.sha), e.sha[:])
-	}
-	if len(results) != bucketSize {
-		t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize)
-	}
-	if hasDuplicates(results) {
-		t.Errorf("result set contains duplicate entries")
-	}
-	if !sortedByDistanceTo(lookupTestnet.targetSha, results) {
-		t.Errorf("result set not sorted by distance to target")
-	}
-	// TODO: check result nodes are actually closest
-}
-
-// This is the test network for the Lookup test.
-// The nodes were obtained by running testnet.mine with a random NodeID as target.
-var lookupTestnet = &preminedTestnet{
-	target:    MustHexID("166aea4f556532c6d34e8b740e5d314af7e9ac0ca79833bd751d6b665f12dfd38ec563c363b32f02aef4a80b44fd3def94612d497b99cb5f17fd24de454927ec"),
-	targetSha: common.Hash{0x5c, 0x94, 0x4e, 0xe5, 0x1c, 0x5a, 0xe9, 0xf7, 0x2a, 0x95, 0xec, 0xcb, 0x8a, 0xed, 0x3, 0x74, 0xee, 0xcb, 0x51, 0x19, 0xd7, 0x20, 0xcb, 0xea, 0x68, 0x13, 0xe8, 0xe0, 0xd6, 0xad, 0x92, 0x61},
-	dists: [257][]NodeID{
-		240: {
-			MustHexID("2001ad5e3e80c71b952161bc0186731cf5ffe942d24a79230a0555802296238e57ea7a32f5b6f18564eadc1c65389448481f8c9338df0a3dbd18f708cbc2cbcb"),
-			MustHexID("6ba3f4f57d084b6bf94cc4555b8c657e4a8ac7b7baf23c6874efc21dd1e4f56b7eb2721e07f5242d2f1d8381fc8cae535e860197c69236798ba1ad231b105794"),
-		},
-		244: {
-			MustHexID("696ba1f0a9d55c59246f776600542a9e6432490f0cd78f8bb55a196918df2081a9b521c3c3ba48e465a75c10768807717f8f689b0b4adce00e1c75737552a178"),
-		},
-		246: {
-			MustHexID("d6d32178bdc38416f46ffb8b3ec9e4cb2cfff8d04dd7e4311a70e403cb62b10be1b447311b60b4f9ee221a8131fc2cbd45b96dd80deba68a949d467241facfa8"),
-			MustHexID("3ea3d04a43a3dfb5ac11cffc2319248cf41b6279659393c2f55b8a0a5fc9d12581a9d97ef5d8ff9b5abf3321a290e8f63a4f785f450dc8a672aba3ba2ff4fdab"),
-			MustHexID("2fc897f05ae585553e5c014effd3078f84f37f9333afacffb109f00ca8e7a3373de810a3946be971cbccdfd40249f9fe7f322118ea459ac71acca85a1ef8b7f4"),
-		},
-		247: {
-			MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
-			MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
-			MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
-			MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
-			MustHexID("8b58c6073dd98bbad4e310b97186c8f822d3a5c7d57af40e2136e88e315afd115edb27d2d0685a908cfe5aa49d0debdda6e6e63972691d6bd8c5af2d771dd2a9"),
-			MustHexID("2cbb718b7dc682da19652e7d9eb4fefaf7b7147d82c1c2b6805edf77b85e29fde9f6da195741467ff2638dc62c8d3e014ea5686693c15ed0080b6de90354c137"),
-			MustHexID("e84027696d3f12f2de30a9311afea8fbd313c2360daff52bb5fc8c7094d5295758bec3134e4eef24e4cdf377b40da344993284628a7a346eba94f74160998feb"),
-			MustHexID("f1357a4f04f9d33753a57c0b65ba20a5d8777abbffd04e906014491c9103fb08590e45548d37aa4bd70965e2e81ddba94f31860348df01469eec8c1829200a68"),
-			MustHexID("4ab0a75941b12892369b4490a1928c8ca52a9ad6d3dffbd1d8c0b907bc200fe74c022d011ec39b64808a39c0ca41f1d3254386c3e7733e7044c44259486461b6"),
-			MustHexID("d45150a72dc74388773e68e03133a3b5f51447fe91837d566706b3c035ee4b56f160c878c6273394daee7f56cc398985269052f22f75a8057df2fe6172765354"),
-		},
-		248: {
-			MustHexID("6aadfce366a189bab08ac84721567483202c86590642ea6d6a14f37ca78d82bdb6509eb7b8b2f6f63c78ae3ae1d8837c89509e41497d719b23ad53dd81574afa"),
-			MustHexID("a605ecfd6069a4cf4cf7f5840e5bc0ce10d23a3ac59e2aaa70c6afd5637359d2519b4524f56fc2ca180cdbebe54262f720ccaae8c1b28fd553c485675831624d"),
-			MustHexID("29701451cb9448ca33fc33680b44b840d815be90146eb521641efbffed0859c154e8892d3906eae9934bfacee72cd1d2fa9dd050fd18888eea49da155ab0efd2"),
-			MustHexID("3ed426322dee7572b08592e1e079f8b6c6b30e10e6243edd144a6a48fdbdb83df73a6e41b1143722cb82604f2203a32758610b5d9544f44a1a7921ba001528c1"),
-			MustHexID("b2e2a2b7fdd363572a3256e75435fab1da3b16f7891a8bd2015f30995dae665d7eabfd194d87d99d5df628b4bbc7b04e5b492c596422dd8272746c7a1b0b8e4f"),
-			MustHexID("0c69c9756162c593e85615b814ce57a2a8ca2df6c690b9c4e4602731b61e1531a3bbe3f7114271554427ffabea80ad8f36fa95a49fa77b675ae182c6ccac1728"),
-			MustHexID("8d28be21d5a97b0876442fa4f5e5387f5bf3faad0b6f13b8607b64d6e448c0991ca28dd7fe2f64eb8eadd7150bff5d5666aa6ed868b84c71311f4ba9a38569dd"),
-			MustHexID("2c677e1c64b9c9df6359348a7f5f33dc79e22f0177042486d125f8b6ca7f0dc756b1f672aceee5f1746bcff80aaf6f92a8dc0c9fbeb259b3fa0da060de5ab7e8"),
-			MustHexID("3994880f94a8678f0cd247a43f474a8af375d2a072128da1ad6cae84a244105ff85e94fc7d8496f639468de7ee998908a91c7e33ef7585fff92e984b210941a1"),
-			MustHexID("b45a9153c08d002a48090d15d61a7c7dad8c2af85d4ff5bd36ce23a9a11e0709bf8d56614c7b193bc028c16cbf7f20dfbcc751328b64a924995d47b41e452422"),
-			MustHexID("057ab3a9e53c7a84b0f3fc586117a525cdd18e313f52a67bf31798d48078e325abe5cfee3f6c2533230cb37d0549289d692a29dd400e899b8552d4b928f6f907"),
-			MustHexID("0ddf663d308791eb92e6bd88a2f8cb45e4f4f35bb16708a0e6ff7f1362aa6a73fedd0a1b1557fb3365e38e1b79d6918e2fae2788728b70c9ab6b51a3b94a4338"),
-			MustHexID("f637e07ff50cc1e3731735841c4798411059f2023abcf3885674f3e8032531b0edca50fd715df6feb489b6177c345374d64f4b07d257a7745de393a107b013a5"),
-			MustHexID("e24ec7c6eec094f63c7b3239f56d311ec5a3e45bc4e622a1095a65b95eea6fe13e29f3b6b7a2cbfe40906e3989f17ac834c3102dd0cadaaa26e16ee06d782b72"),
-			MustHexID("b76ea1a6fd6506ef6e3506a4f1f60ed6287fff8114af6141b2ff13e61242331b54082b023cfea5b3083354a4fb3f9eb8be01fb4a518f579e731a5d0707291a6b"),
-			MustHexID("9b53a37950ca8890ee349b325032d7b672cab7eced178d3060137b24ef6b92a43977922d5bdfb4a3409a2d80128e02f795f9dae6d7d99973ad0e23a2afb8442f"),
-		},
-		249: {
-			MustHexID("675ae65567c3c72c50c73bc0fd4f61f202ea5f93346ca57b551de3411ccc614fad61cb9035493af47615311b9d44ee7a161972ee4d77c28fe1ec029d01434e6a"),
-			MustHexID("8eb81408389da88536ae5800392b16ef5109d7ea132c18e9a82928047ecdb502693f6e4a4cdd18b54296caf561db937185731456c456c98bfe7de0baf0eaa495"),
-			MustHexID("2adba8b1612a541771cb93a726a38a4b88e97b18eced2593eb7daf82f05a5321ca94a72cc780c306ff21e551a932fc2c6d791e4681907b5ceab7f084c3fa2944"),
-			MustHexID("b1b4bfbda514d9b8f35b1c28961da5d5216fe50548f4066f69af3b7666a3b2e06eac646735e963e5c8f8138a2fb95af15b13b23ff00c6986eccc0efaa8ee6fb4"),
-			MustHexID("d2139281b289ad0e4d7b4243c4364f5c51aac8b60f4806135de06b12b5b369c9e43a6eb494eab860d115c15c6fbb8c5a1b0e382972e0e460af395b8385363de7"),
-			MustHexID("4a693df4b8fc5bdc7cec342c3ed2e228d7c5b4ab7321ddaa6cccbeb45b05a9f1d95766b4002e6d4791c2deacb8a667aadea6a700da28a3eea810a30395701bbc"),
-			MustHexID("ab41611195ec3c62bb8cd762ee19fb182d194fd141f4a66780efbef4b07ce916246c022b841237a3a6b512a93431157edd221e854ed2a259b72e9c5351f44d0c"),
-			MustHexID("68e8e26099030d10c3c703ae7045c0a48061fb88058d853b3e67880014c449d4311014da99d617d3150a20f1a3da5e34bf0f14f1c51fe4dd9d58afd222823176"),
-			MustHexID("3fbcacf546fb129cd70fc48de3b593ba99d3c473798bc309292aca280320e0eacc04442c914cad5c4cf6950345ba79b0d51302df88285d4e83ee3fe41339eee7"),
-			MustHexID("1d4a623659f7c8f80b6c3939596afdf42e78f892f682c768ad36eb7bfba402dbf97aea3a268f3badd8fe7636be216edf3d67ee1e08789ebbc7be625056bd7109"),
-			MustHexID("a283c474ab09da02bbc96b16317241d0627646fcc427d1fe790b76a7bf1989ced90f92101a973047ae9940c92720dffbac8eff21df8cae468a50f72f9e159417"),
-			MustHexID("dbf7e5ad7f87c3dfecae65d87c3039e14ed0bdc56caf00ce81931073e2e16719d746295512ff7937a15c3b03603e7c41a4f9df94fcd37bb200dd8f332767e9cb"),
-			MustHexID("caaa070a26692f64fc77f30d7b5ae980d419b4393a0f442b1c821ef58c0862898b0d22f74a4f8c5d83069493e3ec0b92f17dc1fe6e4cd437c1ec25039e7ce839"),
-			MustHexID("874cc8d1213beb65c4e0e1de38ef5d8165235893ac74ab5ea937c885eaab25c8d79dad0456e9fd3e9450626cac7e107b004478fb59842f067857f39a47cee695"),
-			MustHexID("d94193f236105010972f5df1b7818b55846592a0445b9cdc4eaed811b8c4c0f7c27dc8cc9837a4774656d6b34682d6d329d42b6ebb55da1d475c2474dc3dfdf4"),
-			MustHexID("edd9af6aded4094e9785637c28fccbd3980cbe28e2eb9a411048a23c2ace4bd6b0b7088a7817997b49a3dd05fc6929ca6c7abbb69438dbdabe65e971d2a794b2"),
-		},
-		250: {
-			MustHexID("53a5bd1215d4ab709ae8fdc2ced50bba320bced78bd9c5dc92947fb402250c914891786db0978c898c058493f86fc68b1c5de8a5cb36336150ac7a88655b6c39"),
-			MustHexID("b7f79e3ab59f79262623c9ccefc8f01d682323aee56ffbe295437487e9d5acaf556a9c92e1f1c6a9601f2b9eb6b027ae1aeaebac71d61b9b78e88676efd3e1a3"),
-			MustHexID("d374bf7e8d7ffff69cc00bebff38ef5bc1dcb0a8d51c1a3d70e61ac6b2e2d6617109254b0ac224354dfbf79009fe4239e09020c483cc60c071e00b9238684f30"),
-			MustHexID("1e1eac1c9add703eb252eb991594f8f5a173255d526a855fab24ae57dc277e055bc3c7a7ae0b45d437c4f47a72d97eb7b126f2ba344ba6c0e14b2c6f27d4b1e6"),
-			MustHexID("ae28953f63d4bc4e706712a59319c111f5ff8f312584f65d7436b4cd3d14b217b958f8486bad666b4481fe879019fb1f767cf15b3e3e2711efc33b56d460448a"),
-			MustHexID("934bb1edf9c7a318b82306aca67feb3d6b434421fa275d694f0b4927afd8b1d3935b727fd4ff6e3d012e0c82f1824385174e8c6450ade59c2a43281a4b3446b6"),
-			MustHexID("9eef3f28f70ce19637519a0916555bf76d26de31312ac656cf9d3e379899ea44e4dd7ffcce923b4f3563f8a00489a34bd6936db0cbb4c959d32c49f017e07d05"),
-			MustHexID("82200872e8f871c48f1fad13daec6478298099b591bb3dbc4ef6890aa28ebee5860d07d70be62f4c0af85085a90ae8179ee8f937cf37915c67ea73e704b03ee7"),
-			MustHexID("6c75a5834a08476b7fc37ff3dc2011dc3ea3b36524bad7a6d319b18878fad813c0ba76d1f4555cacd3890c865438c21f0e0aed1f80e0a157e642124c69f43a11"),
-			MustHexID("995b873742206cb02b736e73a88580c2aacb0bd4a3c97a647b647bcab3f5e03c0e0736520a8b3600da09edf4248991fb01091ec7ff3ec7cdc8a1beae011e7aae"),
-			MustHexID("c773a056594b5cdef2e850d30891ff0e927c3b1b9c35cd8e8d53a1017001e237468e1ece3ae33d612ca3e6abb0a9169aa352e9dcda358e5af2ad982b577447db"),
-			MustHexID("2b46a5f6923f475c6be99ec6d134437a6d11f6bb4b4ac6bcd94572fa1092639d1c08aeefcb51f0912f0a060f71d4f38ee4da70ecc16010b05dd4a674aab14c3a"),
-			MustHexID("af6ab501366debbaa0d22e20e9688f32ef6b3b644440580fd78de4fe0e99e2a16eb5636bbae0d1c259df8ddda77b35b9a35cbc36137473e9c68fbc9d203ba842"),
-			MustHexID("c9f6f2dd1a941926f03f770695bda289859e85fabaf94baaae20b93e5015dc014ba41150176a36a1884adb52f405194693e63b0c464a6891cc9cc1c80d450326"),
-			MustHexID("5b116f0751526868a909b61a30b0c5282c37df6925cc03ddea556ef0d0602a9595fd6c14d371f8ed7d45d89918a032dcd22be4342a8793d88fdbeb3ca3d75bd7"),
-			MustHexID("50f3222fb6b82481c7c813b2172e1daea43e2710a443b9c2a57a12bd160dd37e20f87aa968c82ad639af6972185609d47036c0d93b4b7269b74ebd7073221c10"),
-		},
-		251: {
-			MustHexID("9b8f702a62d1bee67bedfeb102eca7f37fa1713e310f0d6651cc0c33ea7c5477575289ccd463e5a2574a00a676a1fdce05658ba447bb9d2827f0ba47b947e894"),
-			MustHexID("b97532eb83054ed054b4abdf413bb30c00e4205545c93521554dbe77faa3cfaa5bd31ef466a107b0b34a71ec97214c0c83919720142cddac93aa7a3e928d4708"),
-			MustHexID("2f7a5e952bfb67f2f90b8441b5fadc9ee13b1dcde3afeeb3dd64bf937f86663cc5c55d1fa83952b5422763c7df1b7f2794b751c6be316ebc0beb4942e65ab8c1"),
-			MustHexID("42c7483781727051a0b3660f14faf39e0d33de5e643702ae933837d036508ab856ce7eec8ec89c4929a4901256e5233a3d847d5d4893f91bcf21835a9a880fee"),
-			MustHexID("873bae27bf1dc854408fba94046a53ab0c965cebe1e4e12290806fc62b88deb1f4a47f9e18f78fc0e7913a0c6e42ac4d0fc3a20cea6bc65f0c8a0ca90b67521e"),
-			MustHexID("a7e3a370bbd761d413f8d209e85886f68bf73d5c3089b2dc6fa42aab1ecb5162635497eed95dee2417f3c9c74a3e76319625c48ead2e963c7de877cd4551f347"),
-			MustHexID("528597534776a40df2addaaea15b6ff832ce36b9748a265768368f657e76d58569d9f30dbb91e91cf0ae7efe8f402f17aa0ae15f5c55051ba03ba830287f4c42"),
-			MustHexID("461d8bd4f13c3c09031fdb84f104ed737a52f630261463ce0bdb5704259bab4b737dda688285b8444dbecaecad7f50f835190b38684ced5e90c54219e5adf1bc"),
-			MustHexID("6ec50c0be3fd232737090fc0111caaf0bb6b18f72be453428087a11a97fd6b52db0344acbf789a689bd4f5f50f79017ea784f8fd6fe723ad6ae675b9e3b13e21"),
-			MustHexID("12fc5e2f77a83fdcc727b79d8ae7fe6a516881138d3011847ee136b400fed7cfba1f53fd7a9730253c7aa4f39abeacd04f138417ba7fcb0f36cccc3514e0dab6"),
-			MustHexID("4fdbe75914ccd0bce02101606a1ccf3657ec963e3b3c20239d5fec87673fe446d649b4f15f1fe1a40e6cfbd446dda2d31d40bb602b1093b8fcd5f139ba0eb46a"),
-			MustHexID("3753668a0f6281e425ea69b52cb2d17ab97afbe6eb84cf5d25425bc5e53009388857640668fadd7c110721e6047c9697803bd8a6487b43bb343bfa32ebf24039"),
-			MustHexID("2e81b16346637dec4410fd88e527346145b9c0a849dbf2628049ac7dae016c8f4305649d5659ec77f1e8a0fac0db457b6080547226f06283598e3740ad94849a"),
-			MustHexID("802c3cc27f91c89213223d758f8d2ecd41135b357b6d698f24d811cdf113033a81c38e0bdff574a5c005b00a8c193dc2531f8c1fa05fa60acf0ab6f2858af09f"),
-			MustHexID("fcc9a2e1ac3667026ff16192876d1813bb75abdbf39b929a92863012fe8b1d890badea7a0de36274d5c1eb1e8f975785532c50d80fd44b1a4b692f437303393f"),
-			MustHexID("6d8b3efb461151dd4f6de809b62726f5b89e9b38e9ba1391967f61cde844f7528fecf821b74049207cee5a527096b31f3ad623928cd3ce51d926fa345a6b2951"),
-		},
-		252: {
-			MustHexID("f1ae93157cc48c2075dd5868fbf523e79e06caf4b8198f352f6e526680b78ff4227263de92612f7d63472bd09367bb92a636fff16fe46ccf41614f7a72495c2a"),
-			MustHexID("587f482d111b239c27c0cb89b51dd5d574db8efd8de14a2e6a1400c54d4567e77c65f89c1da52841212080b91604104768350276b6682f2f961cdaf4039581c7"),
-			MustHexID("e3f88274d35cefdaabdf205afe0e80e936cc982b8e3e47a84ce664c413b29016a4fb4f3a3ebae0a2f79671f8323661ed462bf4390af94c424dc8ace0c301b90f"),
-			MustHexID("0ddc736077da9a12ba410dc5ea63cbcbe7659dd08596485b2bff3435221f82c10d263efd9af938e128464be64a178b7cd22e19f400d5802f4c9df54bf89f2619"),
-			MustHexID("784aa34d833c6ce63fcc1279630113c3272e82c4ae8c126c5a52a88ac461b6baeed4244e607b05dc14e5b2f41c70a273c3804dea237f14f7a1e546f6d1309d14"),
-			MustHexID("f253a2c354ee0e27cfcae786d726753d4ad24be6516b279a936195a487de4a59dbc296accf20463749ff55293263ed8c1b6365eecb248d44e75e9741c0d18205"),
-			MustHexID("a1910b80357b3ad9b4593e0628922939614dc9056a5fbf477279c8b2c1d0b4b31d89a0c09d0d41f795271d14d3360ef08a3f821e65e7e1f56c07a36afe49c7c5"),
-			MustHexID("f1168552c2efe541160f0909b0b4a9d6aeedcf595cdf0e9b165c97e3e197471a1ee6320e93389edfba28af6eaf10de98597ad56e7ab1b504ed762451996c3b98"),
-			MustHexID("b0c8e5d2c8634a7930e1a6fd082e448c6cf9d2d8b7293558b59238815a4df926c286bf297d2049f14e8296a6eb3256af614ec1812c4f2bbe807673b58bf14c8c"),
-			MustHexID("0fb346076396a38badc342df3679b55bd7f40a609ab103411fe45082c01f12ea016729e95914b2b5540e987ff5c9b133e85862648e7f36abdfd23100d248d234"),
-			MustHexID("f736e0cc83417feaa280d9483f5d4d72d1b036cd0c6d9cbdeb8ac35ceb2604780de46dddaa32a378474e1d5ccdf79b373331c30c7911ade2ae32f98832e5de1f"),
-			MustHexID("8b02991457602f42b38b342d3f2259ae4100c354b3843885f7e4e07bd644f64dab94bb7f38a3915f8b7f11d8e3f81c28e07a0078cf79d7397e38a7b7e0c857e2"),
-			MustHexID("9221d9f04a8a184993d12baa91116692bb685f887671302999d69300ad103eb2d2c75a09d8979404c6dd28f12362f58a1a43619c493d9108fd47588a23ce5824"),
-			MustHexID("652797801744dada833fff207d67484742eea6835d695925f3e618d71b68ec3c65bdd85b4302b2cdcb835ad3f94fd00d8da07e570b41bc0d2bcf69a8de1b3284"),
-			MustHexID("d84f06fe64debc4cd0625e36d19b99014b6218375262cc2209202bdbafd7dffcc4e34ce6398e182e02fd8faeed622c3e175545864902dfd3d1ac57647cddf4c6"),
-			MustHexID("d0ed87b294f38f1d741eb601020eeec30ac16331d05880fe27868f1e454446de367d7457b41c79e202eaf9525b029e4f1d7e17d85a55f83a557c005c68d7328a"),
-		},
-		253: {
-			MustHexID("ad4485e386e3cc7c7310366a7c38fb810b8896c0d52e55944bfd320ca294e7912d6c53c0a0cf85e7ce226e92491d60430e86f8f15cda0161ed71893fb4a9e3a1"),
-			MustHexID("36d0e7e5b7734f98c6183eeeb8ac5130a85e910a925311a19c4941b1290f945d4fc3996b12ef4966960b6fa0fb29b1604f83a0f81bd5fd6398d2e1a22e46af0c"),
-			MustHexID("7d307d8acb4a561afa23bdf0bd945d35c90245e26345ec3a1f9f7df354222a7cdcb81339c9ed6744526c27a1a0c8d10857e98df942fa433602facac71ac68a31"),
-			MustHexID("d97bf55f88c83fae36232661af115d66ca600fc4bd6d1fb35ff9bb4dad674c02cf8c8d05f317525b5522250db58bb1ecafb7157392bf5aa61b178c61f098d995"),
-			MustHexID("7045d678f1f9eb7a4613764d17bd5698796494d0bf977b16f2dbc272b8a0f7858a60805c022fc3d1fe4f31c37e63cdaca0416c0d053ef48a815f8b19121605e0"),
-			MustHexID("14e1f21418d445748de2a95cd9a8c3b15b506f86a0acabd8af44bb968ce39885b19c8822af61b3dd58a34d1f265baec30e3ae56149dc7d2aa4a538f7319f69c8"),
-			MustHexID("b9453d78281b66a4eac95a1546017111eaaa5f92a65d0de10b1122940e92b319728a24edf4dec6acc412321b1c95266d39c7b3a5d265c629c3e49a65fb022c09"),
-			MustHexID("e8a49248419e3824a00d86af422f22f7366e2d4922b304b7169937616a01d9d6fa5abf5cc01061a352dc866f48e1fa2240dbb453d872b1d7be62bdfc1d5e248c"),
-			MustHexID("bebcff24b52362f30e0589ee573ce2d86f073d58d18e6852a592fa86ceb1a6c9b96d7fb9ec7ed1ed98a51b6743039e780279f6bb49d0a04327ac7a182d9a56f6"),
-			MustHexID("d0835e5a4291db249b8d2fca9f503049988180c7d247bedaa2cf3a1bad0a76709360a85d4f9a1423b2cbc82bb4d94b47c0cde20afc430224834c49fe312a9ae3"),
-			MustHexID("6b087fe2a2da5e4f0b0f4777598a4a7fb66bf77dbd5bfc44e8a7eaa432ab585a6e226891f56a7d4f5ed11a7c57b90f1661bba1059590ca4267a35801c2802913"),
-			MustHexID("d901e5bde52d1a0f4ddf010a686a53974cdae4ebe5c6551b3c37d6b6d635d38d5b0e5f80bc0186a2c7809dbf3a42870dd09643e68d32db896c6da8ba734579e7"),
-			MustHexID("96419fb80efae4b674402bb969ebaab86c1274f29a83a311e24516d36cdf148fe21754d46c97688cdd7468f24c08b13e4727c29263393638a3b37b99ff60ebca"),
-			MustHexID("7b9c1889ae916a5d5abcdfb0aaedcc9c6f9eb1c1a4f68d0c2d034fe79ac610ce917c3abc670744150fa891bfcd8ab14fed6983fca964de920aa393fa7b326748"),
-			MustHexID("7a369b2b8962cc4c65900be046482fbf7c14f98a135bbbae25152c82ad168fb2097b3d1429197cf46d3ce9fdeb64808f908a489cc6019725db040060fdfe5405"),
-			MustHexID("47bcae48288da5ecc7f5058dfa07cf14d89d06d6e449cb946e237aa6652ea050d9f5a24a65efdc0013ccf232bf88670979eddef249b054f63f38da9d7796dbd8"),
-		},
-		254: {
-			MustHexID("099739d7abc8abd38ecc7a816c521a1168a4dbd359fa7212a5123ab583ffa1cf485a5fed219575d6475dbcdd541638b2d3631a6c7fce7474e7fe3cba1d4d5853"),
-			MustHexID("c2b01603b088a7182d0cf7ef29fb2b04c70acb320fccf78526bf9472e10c74ee70b3fcfa6f4b11d167bd7d3bc4d936b660f2c9bff934793d97cb21750e7c3d31"),
-			MustHexID("20e4d8f45f2f863e94b45548c1ef22a11f7d36f263e4f8623761e05a64c4572379b000a52211751e2561b0f14f4fc92dd4130410c8ccc71eb4f0e95a700d4ca9"),
-			MustHexID("27f4a16cc085e72d86e25c98bd2eca173eaaee7565c78ec5a52e9e12b2211f35de81b5b45e9195de2ebfe29106742c59112b951a04eb7ae48822911fc1f9389e"),
-			MustHexID("55db5ee7d98e7f0b1c3b9d5be6f2bc619a1b86c3cdd513160ad4dcf267037a5fffad527ac15d50aeb32c59c13d1d4c1e567ebbf4de0d25236130c8361f9aac63"),
-			MustHexID("883df308b0130fc928a8559fe50667a0fff80493bc09685d18213b2db241a3ad11310ed86b0ef662b3ce21fc3d9aa7f3fc24b8d9afe17c7407e9afd3345ae548"),
-			MustHexID("c7af968cc9bc8200c3ee1a387405f7563be1dce6710a3439f42ea40657d0eae9d2b3c16c42d779605351fcdece4da637b9804e60ca08cfb89aec32c197beffa6"),
-			MustHexID("3e66f2b788e3ff1d04106b80597915cd7afa06c405a7ae026556b6e583dca8e05cfbab5039bb9a1b5d06083ffe8de5780b1775550e7218f5e98624bf7af9a0a8"),
-			MustHexID("4fc7f53764de3337fdaec0a711d35d3a923e72fa65025444d12230b3552ed43d9b2d1ad08ccb11f2d50c58809e6dd74dde910e195294fca3b47ae5a3967cc479"),
-			MustHexID("bafdfdcf6ccaa989436752fa97c77477b6baa7deb374b16c095492c529eb133e8e2f99e1977012b64767b9d34b2cf6d2048ed489bd822b5139b523f6a423167b"),
-			MustHexID("7f5d78008a4312fe059104ce80202c82b8915c2eb4411c6b812b16f7642e57c00f2c9425121f5cbac4257fe0b3e81ef5dea97ea2dbaa98f6a8b6fd4d1e5980bb"),
-			MustHexID("598c37fe78f922751a052f463aeb0cb0bc7f52b7c2a4cf2da72ec0931c7c32175d4165d0f8998f7320e87324ac3311c03f9382a5385c55f0407b7a66b2acd864"),
-			MustHexID("f758c4136e1c148777a7f3275a76e2db0b2b04066fd738554ec398c1c6cc9fb47e14a3b4c87bd47deaeab3ffd2110514c3855685a374794daff87b605b27ee2e"),
-			MustHexID("0307bb9e4fd865a49dcf1fe4333d1b944547db650ab580af0b33e53c4fef6c789531110fac801bbcbce21fc4d6f61b6d5b24abdf5b22e3030646d579f6dca9c2"),
-			MustHexID("82504b6eb49bb2c0f91a7006ce9cefdbaf6df38706198502c2e06601091fc9dc91e4f15db3410d45c6af355bc270b0f268d3dff560f956985c7332d4b10bd1ed"),
-			MustHexID("b39b5b677b45944ceebe76e76d1f051de2f2a0ec7b0d650da52135743e66a9a5dba45f638258f9a7545d9a790c7fe6d3fdf82c25425c7887323e45d27d06c057"),
-		},
-		255: {
-			MustHexID("5c4d58d46e055dd1f093f81ee60a675e1f02f54da6206720adee4dccef9b67a31efc5c2a2949c31a04ee31beadc79aba10da31440a1f9ff2a24093c63c36d784"),
-			MustHexID("ea72161ffdd4b1e124c7b93b0684805f4c4b58d617ed498b37a145c670dbc2e04976f8785583d9c805ffbf343c31d492d79f841652bbbd01b61ed85640b23495"),
-			MustHexID("51caa1d93352d47a8e531692a3612adac1e8ac68d0a200d086c1c57ae1e1a91aa285ab242e8c52ef9d7afe374c9485b122ae815f1707b875569d0433c1c3ce85"),
-			MustHexID("c08397d5751b47bd3da044b908be0fb0e510d3149574dff7aeab33749b023bb171b5769990fe17469dbebc100bc150e798aeda426a2dcc766699a225fddd75c6"),
-			MustHexID("0222c1c194b749736e593f937fad67ee348ac57287a15c7e42877aa38a9b87732a408bca370f812efd0eedbff13e6d5b854bf3ba1dec431a796ed47f32552b09"),
-			MustHexID("03d859cd46ef02d9bfad5268461a6955426845eef4126de6be0fa4e8d7e0727ba2385b78f1a883a8239e95ebb814f2af8379632c7d5b100688eebc5841209582"),
-			MustHexID("64d5004b7e043c39ff0bd10cb20094c287721d5251715884c280a612b494b3e9e1c64ba6f67614994c7d969a0d0c0295d107d53fc225d47c44c4b82852d6f960"),
-			MustHexID("b0a5eefb2dab6f786670f35bf9641eefe6dd87fd3f1362bcab4aaa792903500ab23d88fae68411372e0813b057535a601d46e454323745a948017f6063a47b1f"),
-			MustHexID("0cc6df0a3433d448b5684d2a3ffa9d1a825388177a18f44ad0008c7bd7702f1ec0fc38b83506f7de689c3b6ecb552599927e29699eed6bb867ff08f80068b287"),
-			MustHexID("50772f7b8c03a4e153355fbbf79c8a80cf32af656ff0c7873c99911099d04a0dae0674706c357e0145ad017a0ade65e6052cb1b0d574fcd6f67da3eee0ace66b"),
-			MustHexID("1ae37829c9ef41f8b508b82259ebac76b1ed900d7a45c08b7970f25d2d48ddd1829e2f11423a18749940b6dab8598c6e416cef0efd47e46e51f29a0bc65b37cd"),
-			MustHexID("ba973cab31c2af091fc1644a93527d62b2394999e2b6ccbf158dd5ab9796a43d408786f1803ef4e29debfeb62fce2b6caa5ab2b24d1549c822a11c40c2856665"),
-			MustHexID("bc413ad270dd6ea25bddba78f3298b03b8ba6f8608ac03d06007d4116fa78ef5a0cfe8c80155089382fc7a193243ee5500082660cb5d7793f60f2d7d18650964"),
-			MustHexID("5a6a9ef07634d9eec3baa87c997b529b92652afa11473dfee41ef7037d5c06e0ddb9fe842364462d79dd31cff8a59a1b8d5bc2b810dea1d4cbbd3beb80ecec83"),
-			MustHexID("f492c6ee2696d5f682f7f537757e52744c2ae560f1090a07024609e903d334e9e174fc01609c5a229ddbcac36c9d21adaf6457dab38a25bfd44f2f0ee4277998"),
-			MustHexID("459e4db99298cb0467a90acee6888b08bb857450deac11015cced5104853be5adce5b69c740968bc7f931495d671a70cad9f48546d7cd203357fe9af0e8d2164"),
-		},
-		256: {
-			MustHexID("a8593af8a4aef7b806b5197612017951bac8845a1917ca9a6a15dd6086d608505144990b245785c4cd2d67a295701c7aac2aa18823fb0033987284b019656268"),
-			MustHexID("d2eebef914928c3aad77fc1b2a495f52d2294acf5edaa7d8a530b540f094b861a68fe8348a46a7c302f08ab609d85912a4968eacfea0740847b29421b4795d9e"),
-			MustHexID("b14bfcb31495f32b650b63cf7d08492e3e29071fdc73cf2da0da48d4b191a70ba1a65f42ad8c343206101f00f8a48e8db4b08bf3f622c0853e7323b250835b91"),
-			MustHexID("7feaee0d818c03eb30e4e0bf03ade0f3c21ca38e938a761aa1781cf70bda8cc5cd631a6cc53dd44f1d4a6d3e2dae6513c6c66ee50cb2f0e9ad6f7e319b309fd9"),
-			MustHexID("4ca3b657b139311db8d583c25dd5963005e46689e1317620496cc64129c7f3e52870820e0ec7941d28809311df6db8a2867bbd4f235b4248af24d7a9c22d1232"),
-			MustHexID("1181defb1d16851d42dd951d84424d6bd1479137f587fa184d5a8152be6b6b16ed08bcdb2c2ed8539bcde98c80c432875f9f724737c316a2bd385a39d3cab1d8"),
-			MustHexID("d9dd818769fa0c3ec9f553c759b92476f082817252a04a47dc1777740b1731d280058c66f982812f173a294acf4944a85ba08346e2de153ba3ba41ce8a62cb64"),
-			MustHexID("bd7c4f8a9e770aa915c771b15e107ca123d838762da0d3ffc53aa6b53e9cd076cffc534ec4d2e4c334c683f1f5ea72e0e123f6c261915ed5b58ac1b59f003d88"),
-			MustHexID("3dd5739c73649d510456a70e9d6b46a855864a4a3f744e088fd8c8da11b18e4c9b5f2d7da50b1c147b2bae5ca9609ae01f7a3cdea9dce34f80a91d29cd82f918"),
-			MustHexID("f0d7df1efc439b4bcc0b762118c1cfa99b2a6143a9f4b10e3c9465125f4c9fca4ab88a2504169bbcad65492cf2f50da9dd5d077c39574a944f94d8246529066b"),
-			MustHexID("dd598b9ba441448e5fb1a6ec6c5f5aa9605bad6e223297c729b1705d11d05f6bfd3d41988b694681ae69bb03b9a08bff4beab5596503d12a39bffb5cd6e94c7c"),
-			MustHexID("3fce284ac97e567aebae681b15b7a2b6df9d873945536335883e4bbc26460c064370537f323fd1ada828ea43154992d14ac0cec0940a2bd2a3f42ec156d60c83"),
-			MustHexID("7c8dfa8c1311cb14fb29a8ac11bca23ecc115e56d9fcf7b7ac1db9066aa4eb39f8b1dabf46e192a65be95ebfb4e839b5ab4533fef414921825e996b210dd53bd"),
-			MustHexID("cafa6934f82120456620573d7f801390ed5e16ed619613a37e409e44ab355ef755e83565a913b48a9466db786f8d4fbd590bfec474c2524d4a2608d4eafd6abd"),
-			MustHexID("9d16600d0dd310d77045769fed2cb427f32db88cd57d86e49390c2ba8a9698cfa856f775be2013237226e7bf47b248871cf865d23015937d1edeb20db5e3e760"),
-			MustHexID("17be6b6ba54199b1d80eff866d348ea11d8a4b341d63ad9a6681d3ef8a43853ac564d153eb2a8737f0afc9ab320f6f95c55aa11aaa13bbb1ff422fd16bdf8188"),
-		},
-	},
-}
-
-type preminedTestnet struct {
-	target    NodeID
-	targetSha common.Hash // sha3(target)
-	dists     [hashBits + 1][]NodeID
-	net       *Network
-}
-
-func (tn *preminedTestnet) sendFindnodeHash(to *Node, target common.Hash) {
-	// current log distance is encoded in port number
-	// fmt.Println("findnode query at dist", toaddr.Port)
-	if to.UDP <= lowPort {
-		panic("query to node at or below distance 0")
-	}
-	next := to.UDP - 1
-	var result []rpcNode
-	for i, id := range tn.dists[to.UDP-lowPort] {
-		result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort)))
-	}
-	injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result})
-}
-
-func (tn *preminedTestnet) sendPing(to *Node, addr *net.UDPAddr, topics []Topic) []byte {
-	injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}})
-	return []byte{1}
-}
-
-func (tn *preminedTestnet) send(to *Node, ptype nodeEvent, data interface{}) (hash []byte) {
-	switch ptype {
-	case pingPacket:
-		injectResponse(tn.net, to, pongPacket, &pong{ReplyTok: []byte{1}})
-	case pongPacket:
-		// ignored
-	case findnodeHashPacket:
-		// current log distance is encoded in port number
-		// fmt.Println("findnode query at dist", toaddr.Port-lowPort)
-		if to.UDP <= lowPort {
-			panic("query to node at or below  distance 0")
-		}
-		next := to.UDP - 1
-		var result []rpcNode
-		for i, id := range tn.dists[to.UDP-lowPort] {
-			result = append(result, nodeToRPC(NewNode(id, net.ParseIP("10.0.2.99"), next, uint16(i)+1+lowPort)))
-		}
-		injectResponse(tn.net, to, neighborsPacket, &neighbors{Nodes: result})
-	default:
-		panic("send(" + ptype.String() + ")")
-	}
-	return []byte{2}
-}
-
-func (tn *preminedTestnet) sendNeighbours(to *Node, nodes []*Node) {
-	panic("sendNeighbours called")
-}
-
-func (tn *preminedTestnet) sendTopicNodes(to *Node, queryHash common.Hash, nodes []*Node) {
-	panic("sendTopicNodes called")
-}
-
-func (tn *preminedTestnet) sendTopicRegister(to *Node, topics []Topic, idx int, pong []byte) {
-	panic("sendTopicRegister called")
-}
-
-func (*preminedTestnet) Close() {}
-
-func (*preminedTestnet) localAddr() *net.UDPAddr {
-	return &net.UDPAddr{IP: net.ParseIP("10.0.1.1"), Port: 40000}
-}
-
-func injectResponse(net *Network, from *Node, ev nodeEvent, packet interface{}) {
-	go net.reqReadPacket(ingressPacket{remoteID: from.ID, remoteAddr: from.addr(), ev: ev, data: packet})
-}
diff --git a/p2p/discv5/node.go b/p2p/discv5/node.go
deleted file mode 100644
index 44d3025b70..0000000000
--- a/p2p/discv5/node.go
+++ /dev/null
@@ -1,413 +0,0 @@
-// Copyright 2015 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 .
-
-package discv5
-
-import (
-	"crypto/ecdsa"
-	"crypto/elliptic"
-	"encoding/hex"
-	"errors"
-	"fmt"
-	"math/big"
-	"math/rand"
-	"net"
-	"net/url"
-	"regexp"
-	"strconv"
-	"strings"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-// Node represents a host on the network.
-// The public fields of Node may not be modified.
-type Node struct {
-	IP       net.IP // len 4 for IPv4 or 16 for IPv6
-	UDP, TCP uint16 // port numbers
-	ID       NodeID // the node's public key
-
-	// Network-related fields are contained in nodeNetGuts.
-	// These fields are not supposed to be used off the
-	// Network.loop goroutine.
-	nodeNetGuts
-}
-
-// NewNode creates a new node. It is mostly meant to be used for
-// testing purposes.
-func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node {
-	if ipv4 := ip.To4(); ipv4 != nil {
-		ip = ipv4
-	}
-	return &Node{
-		IP:          ip,
-		UDP:         udpPort,
-		TCP:         tcpPort,
-		ID:          id,
-		nodeNetGuts: nodeNetGuts{sha: crypto.Keccak256Hash(id[:])},
-	}
-}
-
-func (n *Node) addr() *net.UDPAddr {
-	return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
-}
-
-// Incomplete returns true for nodes with no IP address.
-func (n *Node) Incomplete() bool {
-	return n.IP == nil
-}
-
-// checks whether n is a valid complete node.
-func (n *Node) validateComplete() error {
-	if n.Incomplete() {
-		return errors.New("incomplete node")
-	}
-	if n.UDP == 0 {
-		return errors.New("missing UDP port")
-	}
-	if n.TCP == 0 {
-		return errors.New("missing TCP port")
-	}
-	if n.IP.IsMulticast() || n.IP.IsUnspecified() {
-		return errors.New("invalid IP (multicast/unspecified)")
-	}
-	_, err := n.ID.Pubkey() // validate the key (on curve, etc.)
-	return err
-}
-
-// The string representation of a Node is a URL.
-// Please see ParseNode for a description of the format.
-func (n *Node) String() string {
-	u := url.URL{Scheme: "enode"}
-	if n.Incomplete() {
-		u.Host = fmt.Sprintf("%x", n.ID[:])
-	} else {
-		addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
-		u.User = url.User(fmt.Sprintf("%x", n.ID[:]))
-		u.Host = addr.String()
-		if n.UDP != n.TCP {
-			u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
-		}
-	}
-	return u.String()
-}
-
-var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
-
-// ParseNode parses a node designator.
-//
-// There are two basic forms of node designators
-//   - incomplete nodes, which only have the public key (node ID)
-//   - complete nodes, which contain the public key and IP/Port information
-//
-// For incomplete nodes, the designator must look like one of these
-//
-//    enode://
-//    
-//
-// For complete nodes, the node ID is encoded in the username portion
-// of the URL, separated from the host by an @ sign. The hostname can
-// only be given as an IP address, DNS domain names are not allowed.
-// The port in the host name section is the TCP listening port. If the
-// TCP and UDP (discovery) ports differ, the UDP port is specified as
-// query parameter "discport".
-//
-// In the following example, the node URL describes
-// a node with IP address 10.3.58.6, TCP listening port 30303
-// and UDP discovery port 30301.
-//
-//    enode://@10.3.58.6:30303?discport=30301
-func ParseNode(rawurl string) (*Node, error) {
-	if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
-		id, err := HexID(m[1])
-		if err != nil {
-			return nil, fmt.Errorf("invalid node ID (%v)", err)
-		}
-		return NewNode(id, nil, 0, 0), nil
-	}
-	return parseComplete(rawurl)
-}
-
-func parseComplete(rawurl string) (*Node, error) {
-	var (
-		id               NodeID
-		ip               net.IP
-		tcpPort, udpPort uint64
-	)
-	u, err := url.Parse(rawurl)
-	if err != nil {
-		return nil, err
-	}
-	if u.Scheme != "enode" {
-		return nil, errors.New("invalid URL scheme, want \"enode\"")
-	}
-	// Parse the Node ID from the user portion.
-	if u.User == nil {
-		return nil, errors.New("does not contain node ID")
-	}
-	if id, err = HexID(u.User.String()); err != nil {
-		return nil, fmt.Errorf("invalid node ID (%v)", err)
-	}
-	// Parse the IP address.
-	host, port, err := net.SplitHostPort(u.Host)
-	if err != nil {
-		return nil, fmt.Errorf("invalid host: %v", err)
-	}
-	if ip = net.ParseIP(host); ip == nil {
-		return nil, errors.New("invalid IP address")
-	}
-	// Ensure the IP is 4 bytes long for IPv4 addresses.
-	if ipv4 := ip.To4(); ipv4 != nil {
-		ip = ipv4
-	}
-	// Parse the port numbers.
-	if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
-		return nil, errors.New("invalid port")
-	}
-	udpPort = tcpPort
-	qv := u.Query()
-	if qv.Get("discport") != "" {
-		udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
-		if err != nil {
-			return nil, errors.New("invalid discport in query")
-		}
-	}
-	return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil
-}
-
-// MustParseNode parses a node URL. It panics if the URL is not valid.
-func MustParseNode(rawurl string) *Node {
-	n, err := ParseNode(rawurl)
-	if err != nil {
-		panic("invalid node URL: " + err.Error())
-	}
-	return n
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (n *Node) MarshalText() ([]byte, error) {
-	return []byte(n.String()), nil
-}
-
-// UnmarshalText implements encoding.TextUnmarshaler.
-func (n *Node) UnmarshalText(text []byte) error {
-	dec, err := ParseNode(string(text))
-	if err == nil {
-		*n = *dec
-	}
-	return err
-}
-
-// type nodeQueue []*Node
-//
-// // pushNew adds n to the end if it is not present.
-// func (nl *nodeList) appendNew(n *Node) {
-// 	for _, entry := range n {
-// 		if entry == n {
-// 			return
-// 		}
-// 	}
-// 	*nq = append(*nq, n)
-// }
-//
-// // popRandom removes a random node. Nodes closer to
-// // to the head of the beginning of the have a slightly higher probability.
-// func (nl *nodeList) popRandom() *Node {
-// 	ix := rand.Intn(len(*nq))
-// 	//TODO: probability as mentioned above.
-// 	nl.removeIndex(ix)
-// }
-//
-// func (nl *nodeList) removeIndex(i int) *Node {
-// 	slice = *nl
-// 	if len(*slice) <= i {
-// 		return nil
-// 	}
-// 	*nl = append(slice[:i], slice[i+1:]...)
-// }
-
-const nodeIDBits = 512
-
-// NodeID is a unique identifier for each node.
-// The node identifier is a marshaled elliptic curve public key.
-type NodeID [nodeIDBits / 8]byte
-
-// NodeID prints as a long hexadecimal number.
-func (n NodeID) String() string {
-	return fmt.Sprintf("%x", n[:])
-}
-
-// The Go syntax representation of a NodeID is a call to HexID.
-func (n NodeID) GoString() string {
-	return fmt.Sprintf("discover.HexID(\"%x\")", n[:])
-}
-
-// TerminalString returns a shortened hex string for terminal logging.
-func (n NodeID) TerminalString() string {
-	return hex.EncodeToString(n[:8])
-}
-
-// HexID converts a hex string to a NodeID.
-// The string may be prefixed with 0x.
-func HexID(in string) (NodeID, error) {
-	var id NodeID
-	b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
-	if err != nil {
-		return id, err
-	} else if len(b) != len(id) {
-		return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
-	}
-	copy(id[:], b)
-	return id, nil
-}
-
-// MustHexID converts a hex string to a NodeID.
-// It panics if the string is not a valid NodeID.
-func MustHexID(in string) NodeID {
-	id, err := HexID(in)
-	if err != nil {
-		panic(err)
-	}
-	return id
-}
-
-// PubkeyID returns a marshaled representation of the given public key.
-func PubkeyID(pub *ecdsa.PublicKey) NodeID {
-	var id NodeID
-	pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y)
-	if len(pbytes)-1 != len(id) {
-		panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes)))
-	}
-	copy(id[:], pbytes[1:])
-	return id
-}
-
-// Pubkey returns the public key represented by the node ID.
-// It returns an error if the ID is not a point on the curve.
-func (n NodeID) Pubkey() (*ecdsa.PublicKey, error) {
-	p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)}
-	half := len(n) / 2
-	p.X.SetBytes(n[:half])
-	p.Y.SetBytes(n[half:])
-	if !p.Curve.IsOnCurve(p.X, p.Y) {
-		return nil, errors.New("id is invalid secp256k1 curve point")
-	}
-	return p, nil
-}
-
-// recoverNodeID computes the public key used to sign the
-// given hash from the signature.
-func recoverNodeID(hash, sig []byte) (id NodeID, err error) {
-	pubkey, err := crypto.Ecrecover(hash, sig)
-	if err != nil {
-		return id, err
-	}
-	if len(pubkey)-1 != len(id) {
-		return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8)
-	}
-	for i := range id {
-		id[i] = pubkey[i+1]
-	}
-	return id, nil
-}
-
-// distcmp compares the distances a->target and b->target.
-// Returns -1 if a is closer to target, 1 if b is closer to target
-// and 0 if they are equal.
-func distcmp(target, a, b common.Hash) int {
-	for i := range target {
-		da := a[i] ^ target[i]
-		db := b[i] ^ target[i]
-		if da > db {
-			return 1
-		} else if da < db {
-			return -1
-		}
-	}
-	return 0
-}
-
-// table of leading zero counts for bytes [0..255]
-var lzcount = [256]int{
-	8, 7, 6, 6, 5, 5, 5, 5,
-	4, 4, 4, 4, 4, 4, 4, 4,
-	3, 3, 3, 3, 3, 3, 3, 3,
-	3, 3, 3, 3, 3, 3, 3, 3,
-	2, 2, 2, 2, 2, 2, 2, 2,
-	2, 2, 2, 2, 2, 2, 2, 2,
-	2, 2, 2, 2, 2, 2, 2, 2,
-	2, 2, 2, 2, 2, 2, 2, 2,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	1, 1, 1, 1, 1, 1, 1, 1,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0,
-}
-
-// logdist returns the logarithmic distance between a and b, log2(a ^ b).
-func logdist(a, b common.Hash) int {
-	lz := 0
-	for i := range a {
-		x := a[i] ^ b[i]
-		if x == 0 {
-			lz += 8
-		} else {
-			lz += lzcount[x]
-			break
-		}
-	}
-	return len(a)*8 - lz
-}
-
-// hashAtDistance returns a random hash such that logdist(a, b) == n
-func hashAtDistance(a common.Hash, n int) (b common.Hash) {
-	if n == 0 {
-		return a
-	}
-	// flip bit at position n, fill the rest with random bits
-	b = a
-	pos := len(a) - n/8 - 1
-	bit := byte(0x01) << (byte(n%8) - 1)
-	if bit == 0 {
-		pos++
-		bit = 0x80
-	}
-	b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
-	for i := pos + 1; i < len(a); i++ {
-		b[i] = byte(rand.Intn(255))
-	}
-	return b
-}
diff --git a/p2p/discv5/node_test.go b/p2p/discv5/node_test.go
deleted file mode 100644
index 4e0fdbe3db..0000000000
--- a/p2p/discv5/node_test.go
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2015 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 .
-
-package discv5
-
-import (
-	"fmt"
-	"math/big"
-	"math/rand"
-	"net"
-	"reflect"
-	"strings"
-	"testing"
-	"testing/quick"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-func ExampleNewNode() {
-	id := MustHexID("1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439")
-
-	// Complete nodes contain UDP and TCP endpoints:
-	n1 := NewNode(id, net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 52150, 30303)
-	fmt.Println("n1:", n1)
-	fmt.Println("n1.Incomplete() ->", n1.Incomplete())
-
-	// An incomplete node can be created by passing zero values
-	// for all parameters except id.
-	n2 := NewNode(id, nil, 0, 0)
-	fmt.Println("n2:", n2)
-	fmt.Println("n2.Incomplete() ->", n2.Incomplete())
-
-	// Output:
-	// n1: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:30303?discport=52150
-	// n1.Incomplete() -> false
-	// n2: enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439
-	// n2.Incomplete() -> true
-}
-
-var parseNodeTests = []struct {
-	rawurl     string
-	wantError  string
-	wantResult *Node
-}{
-	{
-		rawurl:    "http://foobar",
-		wantError: `invalid URL scheme, want "enode"`,
-	},
-	{
-		rawurl:    "enode://01010101@123.124.125.126:3",
-		wantError: `invalid node ID (wrong length, want 128 hex chars)`,
-	},
-	// Complete nodes with IP address.
-	{
-		rawurl:    "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3",
-		wantError: `invalid IP address`,
-	},
-	{
-		rawurl:    "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo",
-		wantError: `invalid port`,
-	},
-	{
-		rawurl:    "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo",
-		wantError: `invalid discport in query`,
-	},
-	{
-		rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
-		wantResult: NewNode(
-			MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{0x7f, 0x0, 0x0, 0x1},
-			52150,
-			52150,
-		),
-	},
-	{
-		rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
-		wantResult: NewNode(
-			MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.ParseIP("::"),
-			52150,
-			52150,
-		),
-	},
-	{
-		rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150",
-		wantResult: NewNode(
-			MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
-			52150,
-			52150,
-		),
-	},
-	{
-		rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
-		wantResult: NewNode(
-			MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			net.IP{0x7f, 0x0, 0x0, 0x1},
-			22334,
-			52150,
-		),
-	},
-	// Incomplete nodes with no address.
-	{
-		rawurl: "1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
-		wantResult: NewNode(
-			MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			nil, 0, 0,
-		),
-	},
-	{
-		rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439",
-		wantResult: NewNode(
-			MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
-			nil, 0, 0,
-		),
-	},
-	// Invalid URLs
-	{
-		rawurl:    "01010101",
-		wantError: `invalid node ID (wrong length, want 128 hex chars)`,
-	},
-	{
-		rawurl:    "enode://01010101",
-		wantError: `invalid node ID (wrong length, want 128 hex chars)`,
-	},
-	{
-		// This test checks that errors from url.Parse are handled.
-		rawurl:    "://foo",
-		wantError: `missing protocol scheme`,
-	},
-}
-
-func TestParseNode(t *testing.T) {
-	for _, test := range parseNodeTests {
-		n, err := ParseNode(test.rawurl)
-		if test.wantError != "" {
-			if err == nil {
-				t.Errorf("test %q:\n  got nil error, expected %#q", test.rawurl, test.wantError)
-				continue
-			} else if !strings.Contains(err.Error(), test.wantError) {
-				t.Errorf("test %q:\n  got error %#q, expected %#q", test.rawurl, err.Error(), test.wantError)
-				continue
-			}
-		} else {
-			if err != nil {
-				t.Errorf("test %q:\n  unexpected error: %v", test.rawurl, err)
-				continue
-			}
-			if !reflect.DeepEqual(n, test.wantResult) {
-				t.Errorf("test %q:\n  result mismatch:\ngot:  %#v, want: %#v", test.rawurl, n, test.wantResult)
-			}
-		}
-	}
-}
-
-func TestNodeString(t *testing.T) {
-	for i, test := range parseNodeTests {
-		if test.wantError == "" && strings.HasPrefix(test.rawurl, "enode://") {
-			str := test.wantResult.String()
-			if str != test.rawurl {
-				t.Errorf("test %d: Node.String() mismatch:\ngot:  %s\nwant: %s", i, str, test.rawurl)
-			}
-		}
-	}
-}
-
-func TestHexID(t *testing.T) {
-	ref := NodeID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 106, 217, 182, 31, 165, 174, 1, 67, 7, 235, 220, 150, 66, 83, 173, 205, 159, 44, 10, 57, 42, 161, 26, 188}
-	id1 := MustHexID("0x000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
-	id2 := MustHexID("000000000000000000000000000000000000000000000000000000000000000000000000000000806ad9b61fa5ae014307ebdc964253adcd9f2c0a392aa11abc")
-
-	if id1 != ref {
-		t.Errorf("wrong id1\ngot  %v\nwant %v", id1[:], ref[:])
-	}
-	if id2 != ref {
-		t.Errorf("wrong id2\ngot  %v\nwant %v", id2[:], ref[:])
-	}
-}
-
-func TestNodeID_recover(t *testing.T) {
-	prv := newkey()
-	hash := make([]byte, 32)
-	sig, err := crypto.Sign(hash, prv)
-	if err != nil {
-		t.Fatalf("signing error: %v", err)
-	}
-
-	pub := PubkeyID(&prv.PublicKey)
-	recpub, err := recoverNodeID(hash, sig)
-	if err != nil {
-		t.Fatalf("recovery error: %v", err)
-	}
-	if pub != recpub {
-		t.Errorf("recovered wrong pubkey:\ngot:  %v\nwant: %v", recpub, pub)
-	}
-
-	ecdsa, err := pub.Pubkey()
-	if err != nil {
-		t.Errorf("Pubkey error: %v", err)
-	}
-	if !reflect.DeepEqual(ecdsa, &prv.PublicKey) {
-		t.Errorf("Pubkey mismatch:\n  got:  %#v\n  want: %#v", ecdsa, &prv.PublicKey)
-	}
-}
-
-func TestNodeID_pubkeyBad(t *testing.T) {
-	ecdsa, err := NodeID{}.Pubkey()
-	if err == nil {
-		t.Error("expected error for zero ID")
-	}
-	if ecdsa != nil {
-		t.Error("expected nil result")
-	}
-}
-
-func TestNodeID_distcmp(t *testing.T) {
-	distcmpBig := func(target, a, b common.Hash) int {
-		tbig := new(big.Int).SetBytes(target[:])
-		abig := new(big.Int).SetBytes(a[:])
-		bbig := new(big.Int).SetBytes(b[:])
-		return new(big.Int).Xor(tbig, abig).Cmp(new(big.Int).Xor(tbig, bbig))
-	}
-	if err := quick.CheckEqual(distcmp, distcmpBig, quickcfg()); err != nil {
-		t.Error(err)
-	}
-}
-
-// the random tests is likely to miss the case where they're equal.
-func TestNodeID_distcmpEqual(t *testing.T) {
-	base := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
-	x := common.Hash{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
-	if distcmp(base, x, x) != 0 {
-		t.Errorf("distcmp(base, x, x) != 0")
-	}
-}
-
-func TestNodeID_logdist(t *testing.T) {
-	logdistBig := func(a, b common.Hash) int {
-		abig, bbig := new(big.Int).SetBytes(a[:]), new(big.Int).SetBytes(b[:])
-		return new(big.Int).Xor(abig, bbig).BitLen()
-	}
-	if err := quick.CheckEqual(logdist, logdistBig, quickcfg()); err != nil {
-		t.Error(err)
-	}
-}
-
-// the random tests is likely to miss the case where they're equal.
-func TestNodeID_logdistEqual(t *testing.T) {
-	x := common.Hash{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
-	if logdist(x, x) != 0 {
-		t.Errorf("logdist(x, x) != 0")
-	}
-}
-
-func TestNodeID_hashAtDistance(t *testing.T) {
-	// we don't use quick.Check here because its output isn't
-	// very helpful when the test fails.
-	cfg := quickcfg()
-	for i := 0; i < cfg.MaxCount; i++ {
-		a := gen(common.Hash{}, cfg.Rand).(common.Hash)
-		dist := cfg.Rand.Intn(len(common.Hash{}) * 8)
-		result := hashAtDistance(a, dist)
-		actualdist := logdist(result, a)
-
-		if dist != actualdist {
-			t.Log("a:     ", a)
-			t.Log("result:", result)
-			t.Fatalf("#%d: distance of result is %d, want %d", i, actualdist, dist)
-		}
-	}
-}
-
-func quickcfg() *quick.Config {
-	return &quick.Config{
-		MaxCount: 5000,
-		Rand:     rand.New(rand.NewSource(time.Now().Unix())),
-	}
-}
-
-// TODO: The Generate method can be dropped when we require Go >= 1.5
-// because testing/quick learned to generate arrays in 1.5.
-
-func (NodeID) Generate(rand *rand.Rand, size int) reflect.Value {
-	var id NodeID
-	m := rand.Intn(len(id))
-	for i := len(id) - 1; i > m; i-- {
-		id[i] = byte(rand.Uint32())
-	}
-	return reflect.ValueOf(id)
-}
diff --git a/p2p/discv5/nodeevent_string.go b/p2p/discv5/nodeevent_string.go
deleted file mode 100644
index 38c1993bac..0000000000
--- a/p2p/discv5/nodeevent_string.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Code generated by "stringer -type=nodeEvent"; DO NOT EDIT.
-
-package discv5
-
-import "strconv"
-
-const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout"
-
-var _nodeEvent_index = [...]uint8{0, 11, 22, 39}
-
-func (i nodeEvent) String() string {
-	i -= 264
-	if i >= nodeEvent(len(_nodeEvent_index)-1) {
-		return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")"
-	}
-	return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]]
-}
diff --git a/p2p/discv5/sim_run_test.go b/p2p/discv5/sim_run_test.go
deleted file mode 100644
index bded0cc023..0000000000
--- a/p2p/discv5/sim_run_test.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/binary"
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"os/exec"
-	"runtime"
-	"strings"
-	"testing"
-)
-
-func getnacl() (string, error) {
-	switch runtime.GOARCH {
-	case "amd64":
-		_, err := exec.LookPath("sel_ldr_x86_64")
-		return "amd64p32", err
-	case "i386":
-		_, err := exec.LookPath("sel_ldr_i386")
-		return "i386", err
-	default:
-		return "", errors.New("nacl is not supported on " + runtime.GOARCH)
-	}
-}
-
-// runWithPlaygroundTime executes the caller
-// in the NaCl sandbox with faketime enabled.
-//
-// This function must be called from a Test* function
-// and the caller must skip the actual test when isHost is true.
-func runWithPlaygroundTime(t *testing.T) (isHost bool) {
-	if runtime.GOOS == "nacl" {
-		return false
-	}
-
-	// Get the caller.
-	callerPC, _, _, ok := runtime.Caller(1)
-	if !ok {
-		panic("can't get caller")
-	}
-	callerFunc := runtime.FuncForPC(callerPC)
-	if callerFunc == nil {
-		panic("can't get caller")
-	}
-	callerName := callerFunc.Name()[strings.LastIndexByte(callerFunc.Name(), '.')+1:]
-	if !strings.HasPrefix(callerName, "Test") {
-		panic("must be called from witin a Test* function")
-	}
-	testPattern := "^" + callerName + "$"
-
-	// Unfortunately runtime.faketime (playground time mode) only works on NaCl. The NaCl
-	// SDK must be installed and linked into PATH for this to work.
-	arch, err := getnacl()
-	if err != nil {
-		t.Skip(err)
-	}
-
-	// Compile and run the calling test using NaCl.
-	// The extra tag ensures that the TestMain function in sim_main_test.go is used.
-	cmd := exec.Command("go", "test", "-v", "-tags", "faketime_simulation", "-timeout", "100h", "-run", testPattern, ".")
-	cmd.Env = append([]string{"GOOS=nacl", "GOARCH=" + arch}, os.Environ()...)
-	stdout, _ := cmd.StdoutPipe()
-	stderr, _ := cmd.StderrPipe()
-	go skipPlaygroundOutputHeaders(os.Stdout, stdout)
-	go skipPlaygroundOutputHeaders(os.Stderr, stderr)
-	if err := cmd.Run(); err != nil {
-		t.Error(err)
-	}
-
-	// Ensure that the test function doesn't run in the (non-NaCl) host process.
-	return true
-}
-
-func skipPlaygroundOutputHeaders(out io.Writer, in io.Reader) {
-	// Additional output can be printed without the headers
-	// before the NaCl binary starts running (e.g. compiler error messages).
-	bufin := bufio.NewReader(in)
-	output, err := bufin.ReadBytes(0)
-	output = bytes.TrimSuffix(output, []byte{0})
-	if len(output) > 0 {
-		out.Write(output)
-	}
-	if err != nil {
-		return
-	}
-	bufin.UnreadByte()
-
-	// Playback header: 0 0 P B <8-byte time> <4-byte data length>
-	head := make([]byte, 4+8+4)
-	for {
-		if _, err := io.ReadFull(bufin, head); err != nil {
-			if err != io.EOF {
-				fmt.Fprintln(out, "read error:", err)
-			}
-			return
-		}
-		if !bytes.HasPrefix(head, []byte{0x00, 0x00, 'P', 'B'}) {
-			fmt.Fprintf(out, "expected playback header, got %q\n", head)
-			io.Copy(out, bufin)
-			return
-		}
-		// Copy data until next header.
-		size := binary.BigEndian.Uint32(head[12:])
-		io.CopyN(out, bufin, int64(size))
-	}
-}
diff --git a/p2p/discv5/sim_test.go b/p2p/discv5/sim_test.go
deleted file mode 100644
index 3d1e610d3a..0000000000
--- a/p2p/discv5/sim_test.go
+++ /dev/null
@@ -1,432 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"crypto/ecdsa"
-	"encoding/binary"
-	"fmt"
-	"math/rand"
-	"net"
-	"strconv"
-	"sync"
-	"sync/atomic"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-)
-
-// In this test, nodes try to randomly resolve each other.
-func TestSimRandomResolve(t *testing.T) {
-	t.Skip("boring")
-	if runWithPlaygroundTime(t) {
-		return
-	}
-
-	sim := newSimulation()
-	bootnode := sim.launchNode(false)
-
-	// A new node joins every 10s.
-	launcher := time.NewTicker(10 * time.Second)
-	defer launcher.Stop()
-	go func() {
-		for range launcher.C {
-			net := sim.launchNode(false)
-			go randomResolves(t, sim, net)
-			if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
-				panic(err)
-			}
-			t.Logf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16])
-		}
-	}()
-
-	time.Sleep(3 * time.Hour)
-	sim.shutdown()
-	sim.printStats()
-}
-
-func TestSimTopics(t *testing.T) {
-	t.Skip("NaCl test")
-	if runWithPlaygroundTime(t) {
-		return
-	}
-	sim := newSimulation()
-	bootnode := sim.launchNode(false)
-
-	go func() {
-		nets := make([]*Network, 1024)
-		for i := range nets {
-			net := sim.launchNode(false)
-			nets[i] = net
-			if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
-				panic(err)
-			}
-			time.Sleep(time.Second * 5)
-		}
-
-		for i, net := range nets {
-			if i < 256 {
-				stop := make(chan struct{})
-				go net.RegisterTopic(testTopic, stop)
-				go func() {
-					//time.Sleep(time.Second * 36000)
-					time.Sleep(time.Second * 40000)
-					close(stop)
-				}()
-				time.Sleep(time.Millisecond * 100)
-			}
-			//			time.Sleep(time.Second * 10)
-			//time.Sleep(time.Second)
-			/*if i%500 == 499 {
-				time.Sleep(time.Second * 9501)
-			} else {
-				time.Sleep(time.Second)
-			}*/
-		}
-	}()
-
-	// A new node joins every 10s.
-	/*	launcher := time.NewTicker(5 * time.Second)
-		cnt := 0
-		var printNet *Network
-		go func() {
-			for range launcher.C {
-				cnt++
-				if cnt <= 1000 {
-					log := false //(cnt == 500)
-					net := sim.launchNode(log)
-					if log {
-						printNet = net
-					}
-					if cnt > 500 {
-						go net.RegisterTopic(testTopic, nil)
-					}
-					if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
-						panic(err)
-					}
-				}
-				//fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16])
-			}
-		}()
-	*/
-	time.Sleep(55000 * time.Second)
-	//launcher.Stop()
-	sim.shutdown()
-	//sim.printStats()
-	//printNet.log.printLogs()
-}
-
-/*func testHierarchicalTopics(i int) []Topic {
-	digits := strconv.FormatInt(int64(256+i/4), 4)
-	res := make([]Topic, 5)
-	for i, _ := range res {
-		res[i] = Topic("foo" + digits[1:i+1])
-	}
-	return res
-}*/
-
-func testHierarchicalTopics(i int) []Topic {
-	digits := strconv.FormatInt(int64(128+i/8), 2)
-	res := make([]Topic, 8)
-	for i := range res {
-		res[i] = Topic("foo" + digits[1:i+1])
-	}
-	return res
-}
-
-func TestSimTopicHierarchy(t *testing.T) {
-	t.Skip("NaCl test")
-	if runWithPlaygroundTime(t) {
-		return
-	}
-	sim := newSimulation()
-	bootnode := sim.launchNode(false)
-
-	go func() {
-		nets := make([]*Network, 1024)
-		for i := range nets {
-			net := sim.launchNode(false)
-			nets[i] = net
-			if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
-				panic(err)
-			}
-			time.Sleep(time.Second * 5)
-		}
-
-		stop := make(chan struct{})
-		for i, net := range nets {
-			//if i < 256 {
-			for _, topic := range testHierarchicalTopics(i)[:5] {
-				//fmt.Println("reg", topic)
-				go net.RegisterTopic(topic, stop)
-			}
-			time.Sleep(time.Millisecond * 100)
-			//}
-		}
-		time.Sleep(time.Second * 90000)
-		close(stop)
-	}()
-
-	time.Sleep(100000 * time.Second)
-	sim.shutdown()
-}
-
-func randomResolves(t *testing.T, s *simulation, net *Network) {
-	randtime := func() time.Duration {
-		return time.Duration(rand.Intn(50)+20) * time.Second
-	}
-	lookup := func(target NodeID) bool {
-		result := net.Resolve(target)
-		return result != nil && result.ID == target
-	}
-
-	timer := time.NewTimer(randtime())
-	defer timer.Stop()
-	for {
-		select {
-		case <-timer.C:
-			target := s.randomNode().Self().ID
-			if !lookup(target) {
-				t.Errorf("node %x: target %x not found", net.Self().ID[:8], target[:8])
-			}
-			timer.Reset(randtime())
-		case <-net.closed:
-			return
-		}
-	}
-}
-
-type simulation struct {
-	mu      sync.RWMutex
-	nodes   map[NodeID]*Network
-	nodectr uint32
-}
-
-func newSimulation() *simulation {
-	return &simulation{nodes: make(map[NodeID]*Network)}
-}
-
-func (s *simulation) shutdown() {
-	s.mu.RLock()
-	alive := make([]*Network, 0, len(s.nodes))
-	for _, n := range s.nodes {
-		alive = append(alive, n)
-	}
-	defer s.mu.RUnlock()
-
-	for _, n := range alive {
-		n.Close()
-	}
-}
-
-func (s *simulation) printStats() {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-	fmt.Println("node counter:", s.nodectr)
-	fmt.Println("alive nodes:", len(s.nodes))
-
-	// for _, n := range s.nodes {
-	// 	fmt.Printf("%x\n", n.tab.self.ID[:8])
-	// 	transport := n.conn.(*simTransport)
-	// 	fmt.Println("   joined:", transport.joinTime)
-	// 	fmt.Println("   sends:", transport.hashctr)
-	// 	fmt.Println("   table size:", n.tab.count)
-	// }
-
-	/*for _, n := range s.nodes {
-		fmt.Println()
-		fmt.Printf("*** Node %x\n", n.tab.self.ID[:8])
-		n.log.printLogs()
-	}*/
-
-}
-
-func (s *simulation) randomNode() *Network {
-	s.mu.Lock()
-	defer s.mu.Unlock()
-
-	n := rand.Intn(len(s.nodes))
-	for _, net := range s.nodes {
-		if n == 0 {
-			return net
-		}
-		n--
-	}
-	return nil
-}
-
-func (s *simulation) launchNode(log bool) *Network {
-	var (
-		num = s.nodectr
-		key = newkey()
-		id  = PubkeyID(&key.PublicKey)
-		ip  = make(net.IP, 4)
-	)
-	s.nodectr++
-	binary.BigEndian.PutUint32(ip, num)
-	ip[0] = 10
-	addr := &net.UDPAddr{IP: ip, Port: 30303}
-
-	transport := &simTransport{joinTime: time.Now(), sender: id, senderAddr: addr, sim: s, priv: key}
-	net, err := newNetwork(transport, key.PublicKey, "", nil)
-	if err != nil {
-		panic("cannot launch new node: " + err.Error())
-	}
-
-	s.mu.Lock()
-	s.nodes[id] = net
-	s.mu.Unlock()
-
-	return net
-}
-
-type simTransport struct {
-	joinTime   time.Time
-	sender     NodeID
-	senderAddr *net.UDPAddr
-	sim        *simulation
-	hashctr    uint64
-	priv       *ecdsa.PrivateKey
-}
-
-func (st *simTransport) localAddr() *net.UDPAddr {
-	return st.senderAddr
-}
-
-func (st *simTransport) Close() {}
-
-func (st *simTransport) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) {
-	hash = st.nextHash()
-	var raw []byte
-	if ptype == pongPacket {
-		var err error
-		raw, _, err = encodePacket(st.priv, byte(ptype), data)
-		if err != nil {
-			panic(err)
-		}
-	}
-
-	st.sendPacket(remote.ID, ingressPacket{
-		remoteID:   st.sender,
-		remoteAddr: st.senderAddr,
-		hash:       hash,
-		ev:         ptype,
-		data:       data,
-		rawData:    raw,
-	})
-	return hash
-}
-
-func (st *simTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) []byte {
-	hash := st.nextHash()
-	st.sendPacket(remote.ID, ingressPacket{
-		remoteID:   st.sender,
-		remoteAddr: st.senderAddr,
-		hash:       hash,
-		ev:         pingPacket,
-		data: &ping{
-			Version:    4,
-			From:       rpcEndpoint{IP: st.senderAddr.IP, UDP: uint16(st.senderAddr.Port), TCP: 30303},
-			To:         rpcEndpoint{IP: remoteAddr.IP, UDP: uint16(remoteAddr.Port), TCP: 30303},
-			Expiration: uint64(time.Now().Unix() + int64(expiration)),
-			Topics:     topics,
-		},
-	})
-	return hash
-}
-
-func (st *simTransport) sendFindnodeHash(remote *Node, target common.Hash) {
-	st.sendPacket(remote.ID, ingressPacket{
-		remoteID:   st.sender,
-		remoteAddr: st.senderAddr,
-		hash:       st.nextHash(),
-		ev:         findnodeHashPacket,
-		data: &findnodeHash{
-			Target:     target,
-			Expiration: uint64(time.Now().Unix() + int64(expiration)),
-		},
-	})
-}
-
-func (st *simTransport) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) {
-	//fmt.Println("send", topics, pong)
-	st.sendPacket(remote.ID, ingressPacket{
-		remoteID:   st.sender,
-		remoteAddr: st.senderAddr,
-		hash:       st.nextHash(),
-		ev:         topicRegisterPacket,
-		data: &topicRegister{
-			Topics: topics,
-			Idx:    uint(idx),
-			Pong:   pong,
-		},
-	})
-}
-
-func (st *simTransport) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) {
-	rnodes := make([]rpcNode, len(nodes))
-	for i := range nodes {
-		rnodes[i] = nodeToRPC(nodes[i])
-	}
-	st.sendPacket(remote.ID, ingressPacket{
-		remoteID:   st.sender,
-		remoteAddr: st.senderAddr,
-		hash:       st.nextHash(),
-		ev:         topicNodesPacket,
-		data:       &topicNodes{Echo: queryHash, Nodes: rnodes},
-	})
-}
-
-func (st *simTransport) sendNeighbours(remote *Node, nodes []*Node) {
-	// TODO: send multiple packets
-	rnodes := make([]rpcNode, len(nodes))
-	for i := range nodes {
-		rnodes[i] = nodeToRPC(nodes[i])
-	}
-	st.sendPacket(remote.ID, ingressPacket{
-		remoteID:   st.sender,
-		remoteAddr: st.senderAddr,
-		hash:       st.nextHash(),
-		ev:         neighborsPacket,
-		data: &neighbors{
-			Nodes:      rnodes,
-			Expiration: uint64(time.Now().Unix() + int64(expiration)),
-		},
-	})
-}
-
-func (st *simTransport) nextHash() []byte {
-	v := atomic.AddUint64(&st.hashctr, 1)
-	var hash common.Hash
-	binary.BigEndian.PutUint64(hash[:], v)
-	return hash[:]
-}
-
-const packetLoss = 0 // 1/1000
-
-func (st *simTransport) sendPacket(remote NodeID, p ingressPacket) {
-	if rand.Int31n(1000) >= packetLoss {
-		st.sim.mu.RLock()
-		recipient := st.sim.nodes[remote]
-		st.sim.mu.RUnlock()
-
-		time.AfterFunc(200*time.Millisecond, func() {
-			recipient.reqReadPacket(p)
-		})
-	}
-}
diff --git a/p2p/discv5/table.go b/p2p/discv5/table.go
deleted file mode 100644
index 64c3ecd1c7..0000000000
--- a/p2p/discv5/table.go
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright 2016 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 .
-
-// Package discv5 is a prototype implementation of Discvery v5.
-// Deprecated: do not use this package.
-package discv5
-
-import (
-	"crypto/rand"
-	"encoding/binary"
-	"fmt"
-	"net"
-	"sort"
-
-	"github.com/ethereum/go-ethereum/common"
-)
-
-const (
-	alpha      = 3  // Kademlia concurrency factor
-	bucketSize = 16 // Kademlia bucket size
-	hashBits   = len(common.Hash{}) * 8
-	nBuckets   = hashBits + 1 // Number of buckets
-
-	maxFindnodeFailures = 5
-)
-
-type Table struct {
-	count         int               // number of nodes
-	buckets       [nBuckets]*bucket // index of known nodes by distance
-	nodeAddedHook func(*Node)       // for testing
-	self          *Node             // metadata of the local node
-}
-
-// bucket contains nodes, ordered by their last activity. the entry
-// that was most recently active is the first element in entries.
-type bucket struct {
-	entries      []*Node
-	replacements []*Node
-}
-
-func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table {
-	self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port))
-	tab := &Table{self: self}
-	for i := range tab.buckets {
-		tab.buckets[i] = new(bucket)
-	}
-	return tab
-}
-
-const printTable = false
-
-// chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia
-// buckets filled with live connections and keep the network topology healthy.
-// This requires selecting addresses closer to our own with a higher probability
-// in order to refresh closer buckets too.
-//
-// This algorithm approximates the distance distribution of existing nodes in the
-// table by selecting a random node from the table and selecting a target address
-// with a distance less than twice of that of the selected node.
-// This algorithm will be improved later to specifically target the least recently
-// used buckets.
-func (tab *Table) chooseBucketRefreshTarget() common.Hash {
-	entries := 0
-	if printTable {
-		fmt.Println()
-	}
-	for i, b := range &tab.buckets {
-		entries += len(b.entries)
-		if printTable {
-			for _, e := range b.entries {
-				fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex())
-			}
-		}
-	}
-
-	prefix := binary.BigEndian.Uint64(tab.self.sha[0:8])
-	dist := ^uint64(0)
-	entry := int(randUint(uint32(entries + 1)))
-	for _, b := range &tab.buckets {
-		if entry < len(b.entries) {
-			n := b.entries[entry]
-			dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix
-			break
-		}
-		entry -= len(b.entries)
-	}
-
-	ddist := ^uint64(0)
-	if dist+dist > dist {
-		ddist = dist
-	}
-	targetPrefix := prefix ^ randUint64n(ddist)
-
-	var target common.Hash
-	binary.BigEndian.PutUint64(target[0:8], targetPrefix)
-	rand.Read(target[8:])
-	return target
-}
-
-// readRandomNodes fills the given slice with random nodes from the
-// table. It will not write the same node more than once. The nodes in
-// the slice are copies and can be modified by the caller.
-func (tab *Table) readRandomNodes(buf []*Node) (n int) {
-	// TODO: tree-based buckets would help here
-	// Find all non-empty buckets and get a fresh slice of their entries.
-	var buckets [][]*Node
-	for _, b := range &tab.buckets {
-		if len(b.entries) > 0 {
-			buckets = append(buckets, b.entries)
-		}
-	}
-	if len(buckets) == 0 {
-		return 0
-	}
-	// Shuffle the buckets.
-	for i := uint32(len(buckets)) - 1; i > 0; i-- {
-		j := randUint(i)
-		buckets[i], buckets[j] = buckets[j], buckets[i]
-	}
-	// Move head of each bucket into buf, removing buckets that become empty.
-	var i, j int
-	for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
-		b := buckets[j]
-		buf[i] = &(*b[0])
-		buckets[j] = b[1:]
-		if len(b) == 1 {
-			buckets = append(buckets[:j], buckets[j+1:]...)
-		}
-		if len(buckets) == 0 {
-			break
-		}
-	}
-	return i + 1
-}
-
-func randUint(max uint32) uint32 {
-	if max < 2 {
-		return 0
-	}
-	var b [4]byte
-	rand.Read(b[:])
-	return binary.BigEndian.Uint32(b[:]) % max
-}
-
-func randUint64n(max uint64) uint64 {
-	if max < 2 {
-		return 0
-	}
-	var b [8]byte
-	rand.Read(b[:])
-	return binary.BigEndian.Uint64(b[:]) % max
-}
-
-// closest returns the n nodes in the table that are closest to the
-// given id. The caller must hold tab.mutex.
-func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance {
-	// This is a very wasteful way to find the closest nodes but
-	// obviously correct. I believe that tree-based buckets would make
-	// this easier to implement efficiently.
-	close := &nodesByDistance{target: target}
-	for _, b := range &tab.buckets {
-		for _, n := range b.entries {
-			close.push(n, nresults)
-		}
-	}
-	return close
-}
-
-// add attempts to add the given node its corresponding bucket. If the
-// bucket has space available, adding the node succeeds immediately.
-// Otherwise, the node is added to the replacement cache for the bucket.
-func (tab *Table) add(n *Node) (contested *Node) {
-	//fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex())
-	if n.ID == tab.self.ID {
-		return
-	}
-	b := tab.buckets[logdist(tab.self.sha, n.sha)]
-	switch {
-	case b.bump(n):
-		// n exists in b.
-		return nil
-	case len(b.entries) < bucketSize:
-		// b has space available.
-		b.addFront(n)
-		tab.count++
-		if tab.nodeAddedHook != nil {
-			tab.nodeAddedHook(n)
-		}
-		return nil
-	default:
-		// b has no space left, add to replacement cache
-		// and revalidate the last entry.
-		// TODO: drop previous node
-		b.replacements = append(b.replacements, n)
-		if len(b.replacements) > bucketSize {
-			copy(b.replacements, b.replacements[1:])
-			b.replacements = b.replacements[:len(b.replacements)-1]
-		}
-		return b.entries[len(b.entries)-1]
-	}
-}
-
-// stuff adds nodes the table to the end of their corresponding bucket
-// if the bucket is not full.
-func (tab *Table) stuff(nodes []*Node) {
-outer:
-	for _, n := range nodes {
-		if n.ID == tab.self.ID {
-			continue // don't add self
-		}
-		bucket := tab.buckets[logdist(tab.self.sha, n.sha)]
-		for i := range bucket.entries {
-			if bucket.entries[i].ID == n.ID {
-				continue outer // already in bucket
-			}
-		}
-		if len(bucket.entries) < bucketSize {
-			bucket.entries = append(bucket.entries, n)
-			tab.count++
-			if tab.nodeAddedHook != nil {
-				tab.nodeAddedHook(n)
-			}
-		}
-	}
-}
-
-// delete removes an entry from the node table (used to evacuate
-// failed/non-bonded discovery peers).
-func (tab *Table) delete(node *Node) {
-	//fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex())
-	bucket := tab.buckets[logdist(tab.self.sha, node.sha)]
-	for i := range bucket.entries {
-		if bucket.entries[i].ID == node.ID {
-			bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...)
-			tab.count--
-			return
-		}
-	}
-}
-
-func (tab *Table) deleteReplace(node *Node) {
-	b := tab.buckets[logdist(tab.self.sha, node.sha)]
-	i := 0
-	for i < len(b.entries) {
-		if b.entries[i].ID == node.ID {
-			b.entries = append(b.entries[:i], b.entries[i+1:]...)
-			tab.count--
-		} else {
-			i++
-		}
-	}
-	// refill from replacement cache
-	// TODO: maybe use random index
-	if len(b.entries) < bucketSize && len(b.replacements) > 0 {
-		ri := len(b.replacements) - 1
-		b.addFront(b.replacements[ri])
-		tab.count++
-		b.replacements[ri] = nil
-		b.replacements = b.replacements[:ri]
-	}
-}
-
-func (b *bucket) addFront(n *Node) {
-	b.entries = append(b.entries, nil)
-	copy(b.entries[1:], b.entries)
-	b.entries[0] = n
-}
-
-func (b *bucket) bump(n *Node) bool {
-	for i := range b.entries {
-		if b.entries[i].ID == n.ID {
-			// move it to the front
-			copy(b.entries[1:], b.entries[:i])
-			b.entries[0] = n
-			return true
-		}
-	}
-	return false
-}
-
-// nodesByDistance is a list of nodes, ordered by
-// distance to target.
-type nodesByDistance struct {
-	entries []*Node
-	target  common.Hash
-}
-
-// push adds the given node to the list, keeping the total size below maxElems.
-func (h *nodesByDistance) push(n *Node, maxElems int) {
-	ix := sort.Search(len(h.entries), func(i int) bool {
-		return distcmp(h.target, h.entries[i].sha, n.sha) > 0
-	})
-	if len(h.entries) < maxElems {
-		h.entries = append(h.entries, n)
-	}
-	if ix == len(h.entries) {
-		// farther away than all nodes we already have.
-		// if there was room for it, the node is now the last element.
-	} else {
-		// slide existing entries down to make room
-		// this will overwrite the entry we just appended.
-		copy(h.entries[ix+1:], h.entries[ix:])
-		h.entries[ix] = n
-	}
-}
diff --git a/p2p/discv5/table_test.go b/p2p/discv5/table_test.go
deleted file mode 100644
index 872a4f6836..0000000000
--- a/p2p/discv5/table_test.go
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"crypto/ecdsa"
-	"fmt"
-	"math/rand"
-
-	"net"
-	"reflect"
-	"testing"
-	"testing/quick"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-func TestBucket_bumpNoDuplicates(t *testing.T) {
-	t.Parallel()
-	cfg := &quick.Config{
-		MaxCount: 1000,
-		Rand:     rand.New(rand.NewSource(time.Now().Unix())),
-		Values: func(args []reflect.Value, rand *rand.Rand) {
-			// generate a random list of nodes. this will be the content of the bucket.
-			n := rand.Intn(bucketSize-1) + 1
-			nodes := make([]*Node, n)
-			for i := range nodes {
-				nodes[i] = nodeAtDistance(common.Hash{}, 200)
-			}
-			args[0] = reflect.ValueOf(nodes)
-			// generate random bump positions.
-			bumps := make([]int, rand.Intn(100))
-			for i := range bumps {
-				bumps[i] = rand.Intn(len(nodes))
-			}
-			args[1] = reflect.ValueOf(bumps)
-		},
-	}
-
-	prop := func(nodes []*Node, bumps []int) (ok bool) {
-		b := &bucket{entries: make([]*Node, len(nodes))}
-		copy(b.entries, nodes)
-		for i, pos := range bumps {
-			b.bump(b.entries[pos])
-			if hasDuplicates(b.entries) {
-				t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps))
-				for _, n := range b.entries {
-					t.Logf("  %p", n)
-				}
-				return false
-			}
-		}
-		return true
-	}
-	if err := quick.Check(prop, cfg); err != nil {
-		t.Error(err)
-	}
-}
-
-// nodeAtDistance creates a node for which logdist(base, n.sha) == ld.
-// The node's ID does not correspond to n.sha.
-func nodeAtDistance(base common.Hash, ld int) (n *Node) {
-	n = new(Node)
-	n.sha = hashAtDistance(base, ld)
-	copy(n.ID[:], n.sha[:]) // ensure the node still has a unique ID
-	return n
-}
-
-func TestTable_closest(t *testing.T) {
-	t.Parallel()
-
-	test := func(test *closeTest) bool {
-		// for any node table, Target and N
-		tab := newTable(test.Self, &net.UDPAddr{})
-		tab.stuff(test.All)
-
-		// check that doClosest(Target, N) returns nodes
-		result := tab.closest(test.Target, test.N).entries
-		if hasDuplicates(result) {
-			t.Errorf("result contains duplicates")
-			return false
-		}
-		if !sortedByDistanceTo(test.Target, result) {
-			t.Errorf("result is not sorted by distance to target")
-			return false
-		}
-
-		// check that the number of results is min(N, tablen)
-		wantN := test.N
-		if tab.count < test.N {
-			wantN = tab.count
-		}
-		if len(result) != wantN {
-			t.Errorf("wrong number of nodes: got %d, want %d", len(result), wantN)
-			return false
-		} else if len(result) == 0 {
-			return true // no need to check distance
-		}
-
-		// check that the result nodes have minimum distance to target.
-		for _, b := range tab.buckets {
-			for _, n := range b.entries {
-				if contains(result, n.ID) {
-					continue // don't run the check below for nodes in result
-				}
-				farthestResult := result[len(result)-1].sha
-				if distcmp(test.Target, n.sha, farthestResult) < 0 {
-					t.Errorf("table contains node that is closer to target but it's not in result")
-					t.Logf("  Target:          %v", test.Target)
-					t.Logf("  Farthest Result: %v", farthestResult)
-					t.Logf("  ID:              %v", n.ID)
-					return false
-				}
-			}
-		}
-		return true
-	}
-	if err := quick.Check(test, quickcfg()); err != nil {
-		t.Error(err)
-	}
-}
-
-func TestTable_ReadRandomNodesGetAll(t *testing.T) {
-	cfg := &quick.Config{
-		MaxCount: 200,
-		Rand:     rand.New(rand.NewSource(time.Now().Unix())),
-		Values: func(args []reflect.Value, rand *rand.Rand) {
-			args[0] = reflect.ValueOf(make([]*Node, rand.Intn(1000)))
-		},
-	}
-	test := func(buf []*Node) bool {
-		tab := newTable(NodeID{}, &net.UDPAddr{})
-		for i := 0; i < len(buf); i++ {
-			ld := cfg.Rand.Intn(len(tab.buckets))
-			tab.stuff([]*Node{nodeAtDistance(tab.self.sha, ld)})
-		}
-		gotN := tab.readRandomNodes(buf)
-		if gotN != tab.count {
-			t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.count)
-			return false
-		}
-		if hasDuplicates(buf[:gotN]) {
-			t.Errorf("result contains duplicates")
-			return false
-		}
-		return true
-	}
-	if err := quick.Check(test, cfg); err != nil {
-		t.Error(err)
-	}
-}
-
-type closeTest struct {
-	Self   NodeID
-	Target common.Hash
-	All    []*Node
-	N      int
-}
-
-func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
-	t := &closeTest{
-		Self:   gen(NodeID{}, rand).(NodeID),
-		Target: gen(common.Hash{}, rand).(common.Hash),
-		N:      rand.Intn(bucketSize),
-	}
-	for _, id := range gen([]NodeID{}, rand).([]NodeID) {
-		t.All = append(t.All, &Node{ID: id})
-	}
-	return reflect.ValueOf(t)
-}
-
-func hasDuplicates(slice []*Node) bool {
-	seen := make(map[NodeID]bool)
-	for i, e := range slice {
-		if e == nil {
-			panic(fmt.Sprintf("nil *Node at %d", i))
-		}
-		if seen[e.ID] {
-			return true
-		}
-		seen[e.ID] = true
-	}
-	return false
-}
-
-func sortedByDistanceTo(distbase common.Hash, slice []*Node) bool {
-	var last common.Hash
-	for i, e := range slice {
-		if i > 0 && distcmp(distbase, e.sha, last) < 0 {
-			return false
-		}
-		last = e.sha
-	}
-	return true
-}
-
-func contains(ns []*Node, id NodeID) bool {
-	for _, n := range ns {
-		if n.ID == id {
-			return true
-		}
-	}
-	return false
-}
-
-// gen wraps quick.Value so it's easier to use.
-// it generates a random value of the given value's type.
-func gen(typ interface{}, rand *rand.Rand) interface{} {
-	v, ok := quick.Value(reflect.TypeOf(typ), rand)
-	if !ok {
-		panic(fmt.Sprintf("couldn't generate random value of type %T", typ))
-	}
-	return v.Interface()
-}
-
-func newkey() *ecdsa.PrivateKey {
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		panic("couldn't generate key: " + err.Error())
-	}
-	return key
-}
diff --git a/p2p/discv5/ticket.go b/p2p/discv5/ticket.go
deleted file mode 100644
index c5e3d6c08f..0000000000
--- a/p2p/discv5/ticket.go
+++ /dev/null
@@ -1,884 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"math"
-	"math/rand"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-)
-
-const (
-	ticketTimeBucketLen = time.Minute
-	collectFrequency    = time.Second * 30
-	registerFrequency   = time.Second * 60
-	maxCollectDebt      = 10
-	maxRegisterDebt     = 5
-	keepTicketConst     = time.Minute * 10
-	keepTicketExp       = time.Minute * 5
-	targetWaitTime      = time.Minute * 10
-	topicQueryTimeout   = time.Second * 5
-	topicQueryResend    = time.Minute
-	// topic radius detection
-	maxRadius           = 0xffffffffffffffff
-	radiusTC            = time.Minute * 20
-	radiusBucketsPerBit = 8
-	minSlope            = 1
-	minPeakSize         = 40
-	maxNoAdjust         = 20
-	lookupWidth         = 8
-	minRightSum         = 20
-	searchForceQuery    = 4
-)
-
-// timeBucket represents absolute monotonic time in minutes.
-// It is used as the index into the per-topic ticket buckets.
-type timeBucket int
-
-type ticket struct {
-	topics  []Topic
-	regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used.
-
-	// The serial number that was issued by the server.
-	serial uint32
-	// Used by registrar, tracks absolute time when the ticket was created.
-	issueTime mclock.AbsTime
-
-	// Fields used only by registrants
-	node   *Node  // the registrar node that signed this ticket
-	refCnt int    // tracks number of topics that will be registered using this ticket
-	pong   []byte // encoded pong packet signed by the registrar
-}
-
-// ticketRef refers to a single topic in a ticket.
-type ticketRef struct {
-	t   *ticket
-	idx int // index of the topic in t.topics and t.regTime
-}
-
-func (ref ticketRef) topic() Topic {
-	return ref.t.topics[ref.idx]
-}
-
-func (ref ticketRef) topicRegTime() mclock.AbsTime {
-	return ref.t.regTime[ref.idx]
-}
-
-func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) {
-	wps := p.data.(*pong).WaitPeriods
-	if len(topics) != len(wps) {
-		return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps))
-	}
-	if rlpHash(topics) != p.data.(*pong).TopicHash {
-		return nil, fmt.Errorf("bad topic hash")
-	}
-	t := &ticket{
-		issueTime: localTime,
-		node:      node,
-		topics:    topics,
-		pong:      p.rawData,
-		regTime:   make([]mclock.AbsTime, len(wps)),
-	}
-	// Convert wait periods to local absolute time.
-	for i, wp := range wps {
-		t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp))
-	}
-	return t, nil
-}
-
-func ticketToPong(t *ticket, pong *pong) {
-	pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second))
-	pong.TopicHash = rlpHash(t.topics)
-	pong.TicketSerial = t.serial
-	pong.WaitPeriods = make([]uint32, len(t.regTime))
-	for i, regTime := range t.regTime {
-		pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second)
-	}
-}
-
-type ticketStore struct {
-	// radius detector and target address generator
-	// exists for both searched and registered topics
-	radius map[Topic]*topicRadius
-
-	// Contains buckets (for each absolute minute) of tickets
-	// that can be used in that minute.
-	// This is only set if the topic is being registered.
-	tickets map[Topic]*topicTickets
-
-	regQueue []Topic            // Topic registration queue for round robin attempts
-	regSet   map[Topic]struct{} // Topic registration queue contents for fast filling
-
-	nodes       map[*Node]*ticket
-	nodeLastReq map[*Node]reqInfo
-
-	lastBucketFetched timeBucket
-	nextTicketCached  *ticketRef
-
-	searchTopicMap        map[Topic]searchTopic
-	nextTopicQueryCleanup mclock.AbsTime
-	queriesSent           map[*Node]map[common.Hash]sentQuery
-}
-
-type searchTopic struct {
-	foundChn chan<- *Node
-}
-
-type sentQuery struct {
-	sent   mclock.AbsTime
-	lookup lookupInfo
-}
-
-type topicTickets struct {
-	buckets    map[timeBucket][]ticketRef
-	nextLookup mclock.AbsTime
-	nextReg    mclock.AbsTime
-}
-
-func newTicketStore() *ticketStore {
-	return &ticketStore{
-		radius:         make(map[Topic]*topicRadius),
-		tickets:        make(map[Topic]*topicTickets),
-		regSet:         make(map[Topic]struct{}),
-		nodes:          make(map[*Node]*ticket),
-		nodeLastReq:    make(map[*Node]reqInfo),
-		searchTopicMap: make(map[Topic]searchTopic),
-		queriesSent:    make(map[*Node]map[common.Hash]sentQuery),
-	}
-}
-
-// addTopic starts tracking a topic. If register is true,
-// the local node will register the topic and tickets will be collected.
-func (s *ticketStore) addTopic(topic Topic, register bool) {
-	log.Trace("Adding discovery topic", "topic", topic, "register", register)
-	if s.radius[topic] == nil {
-		s.radius[topic] = newTopicRadius(topic)
-	}
-	if register && s.tickets[topic] == nil {
-		s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)}
-	}
-}
-
-func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) {
-	s.addTopic(t, false)
-	if s.searchTopicMap[t].foundChn == nil {
-		s.searchTopicMap[t] = searchTopic{foundChn: foundChn}
-	}
-}
-
-func (s *ticketStore) removeSearchTopic(t Topic) {
-	if st := s.searchTopicMap[t]; st.foundChn != nil {
-		delete(s.searchTopicMap, t)
-	}
-}
-
-// removeRegisterTopic deletes all tickets for the given topic.
-func (s *ticketStore) removeRegisterTopic(topic Topic) {
-	log.Trace("Removing discovery topic", "topic", topic)
-	if s.tickets[topic] == nil {
-		log.Warn("Removing non-existent discovery topic", "topic", topic)
-		return
-	}
-	for _, list := range s.tickets[topic].buckets {
-		for _, ref := range list {
-			ref.t.refCnt--
-			if ref.t.refCnt == 0 {
-				delete(s.nodes, ref.t.node)
-				delete(s.nodeLastReq, ref.t.node)
-			}
-		}
-	}
-	delete(s.tickets, topic)
-}
-
-func (s *ticketStore) regTopicSet() []Topic {
-	topics := make([]Topic, 0, len(s.tickets))
-	for topic := range s.tickets {
-		topics = append(topics, topic)
-	}
-	return topics
-}
-
-// nextRegisterLookup returns the target of the next lookup for ticket collection.
-func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) {
-	// Queue up any new topics (or discarded ones), preserving iteration order
-	for topic := range s.tickets {
-		if _, ok := s.regSet[topic]; !ok {
-			s.regQueue = append(s.regQueue, topic)
-			s.regSet[topic] = struct{}{}
-		}
-	}
-	// Iterate over the set of all topics and look up the next suitable one
-	for len(s.regQueue) > 0 {
-		// Fetch the next topic from the queue, and ensure it still exists
-		topic := s.regQueue[0]
-		s.regQueue = s.regQueue[1:]
-		delete(s.regSet, topic)
-
-		if s.tickets[topic] == nil {
-			continue
-		}
-		// If the topic needs more tickets, return it
-		if s.tickets[topic].nextLookup < mclock.Now() {
-			next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond
-			log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay)
-			return next, delay
-		}
-	}
-	// No registration topics found or all exhausted, sleep
-	delay := 40 * time.Second
-	log.Trace("No topic found to register", "delay", delay)
-	return lookupInfo{}, delay
-}
-
-func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo {
-	tr := s.radius[topic]
-	target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery)
-	if target.radiusLookup {
-		tr.radiusLookupCnt++
-	} else {
-		tr.radiusLookupCnt = 0
-	}
-	return target
-}
-
-func (s *ticketStore) addTicketRef(r ticketRef) {
-	topic := r.t.topics[r.idx]
-	tickets := s.tickets[topic]
-	if tickets == nil {
-		log.Warn("Adding ticket to non-existent topic", "topic", topic)
-		return
-	}
-	bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen))
-	tickets.buckets[bucket] = append(tickets.buckets[bucket], r)
-	r.t.refCnt++
-
-	min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt
-	if tickets.nextLookup < min {
-		tickets.nextLookup = min
-	}
-	tickets.nextLookup += mclock.AbsTime(collectFrequency)
-
-	//s.removeExcessTickets(topic)
-}
-
-func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) {
-	now := mclock.Now()
-	for {
-		ticket, wait := s.nextRegisterableTicket()
-		if ticket == nil {
-			return ticket, wait
-		}
-		log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait)
-
-		regTime := now + mclock.AbsTime(wait)
-		topic := ticket.t.topics[ticket.idx]
-		if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg {
-			return ticket, wait
-		}
-		s.removeTicketRef(*ticket)
-	}
-}
-
-func (s *ticketStore) ticketRegistered(ref ticketRef) {
-	now := mclock.Now()
-
-	topic := ref.t.topics[ref.idx]
-	tickets := s.tickets[topic]
-	min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt
-	if min > tickets.nextReg {
-		tickets.nextReg = min
-	}
-	tickets.nextReg += mclock.AbsTime(registerFrequency)
-	s.tickets[topic] = tickets
-
-	s.removeTicketRef(ref)
-}
-
-// nextRegisterableTicket returns the next ticket that can be used
-// to register.
-//
-// If the returned wait time <= zero the ticket can be used. For a positive
-// wait time, the caller should requery the next ticket later.
-//
-// A ticket can be returned more than once with <= zero wait time in case
-// the ticket contains multiple topics.
-func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) {
-	now := mclock.Now()
-	if s.nextTicketCached != nil {
-		return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now)
-	}
-
-	for bucket := s.lastBucketFetched; ; bucket++ {
-		var (
-			empty      = true    // true if there are no tickets
-			nextTicket ticketRef // uninitialized if this bucket is empty
-		)
-		for _, tickets := range s.tickets {
-			//s.removeExcessTickets(topic)
-			if len(tickets.buckets) != 0 {
-				empty = false
-
-				list := tickets.buckets[bucket]
-				for _, ref := range list {
-					//debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now)))
-					if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() {
-						nextTicket = ref
-					}
-				}
-			}
-		}
-		if empty {
-			return nil, 0
-		}
-		if nextTicket.t != nil {
-			s.nextTicketCached = &nextTicket
-			return &nextTicket, time.Duration(nextTicket.topicRegTime() - now)
-		}
-		s.lastBucketFetched = bucket
-	}
-}
-
-// removeTicket removes a ticket from the ticket store
-func (s *ticketStore) removeTicketRef(ref ticketRef) {
-	log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial)
-
-	// Make nextRegisterableTicket return the next available ticket.
-	s.nextTicketCached = nil
-
-	topic := ref.topic()
-	tickets := s.tickets[topic]
-
-	if tickets == nil {
-		log.Trace("Removing tickets from unknown topic", "topic", topic)
-		return
-	}
-	bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen))
-	list := tickets.buckets[bucket]
-	idx := -1
-	for i, bt := range list {
-		if bt.t == ref.t {
-			idx = i
-			break
-		}
-	}
-	if idx == -1 {
-		panic(nil)
-	}
-	list = append(list[:idx], list[idx+1:]...)
-	if len(list) != 0 {
-		tickets.buckets[bucket] = list
-	} else {
-		delete(tickets.buckets, bucket)
-	}
-	ref.t.refCnt--
-	if ref.t.refCnt == 0 {
-		delete(s.nodes, ref.t.node)
-		delete(s.nodeLastReq, ref.t.node)
-	}
-}
-
-type lookupInfo struct {
-	target       common.Hash
-	topic        Topic
-	radiusLookup bool
-}
-
-type reqInfo struct {
-	pingHash []byte
-	lookup   lookupInfo
-	time     mclock.AbsTime
-}
-
-// returns -1 if not found
-func (t *ticket) findIdx(topic Topic) int {
-	for i, tt := range t.topics {
-		if tt == topic {
-			return i
-		}
-	}
-	return -1
-}
-
-func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) {
-	now := mclock.Now()
-	for i, n := range nodes {
-		if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius {
-			if lookup.radiusLookup {
-				if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC {
-					s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now}
-				}
-			} else {
-				if s.nodes[n] == nil {
-					s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now}
-				}
-			}
-		}
-	}
-}
-
-func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, query func(n *Node, topic Topic) []byte) {
-	now := mclock.Now()
-	for i, n := range nodes {
-		if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius {
-			if lookup.radiusLookup {
-				if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC {
-					s.nodeLastReq[n] = reqInfo{pingHash: nil, lookup: lookup, time: now}
-				}
-			} // else {
-			if s.canQueryTopic(n, lookup.topic) {
-				hash := query(n, lookup.topic)
-				if hash != nil {
-					s.addTopicQuery(common.BytesToHash(hash), n, lookup)
-				}
-			}
-			//}
-		}
-	}
-}
-
-func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) {
-	for i, topic := range t.topics {
-		if tt, ok := s.radius[topic]; ok {
-			tt.adjustWithTicket(now, targetHash, ticketRef{t, i})
-		}
-	}
-}
-
-func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, ticket *ticket) {
-	log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial)
-
-	lastReq, ok := s.nodeLastReq[ticket.node]
-	if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) {
-		return
-	}
-	s.adjustWithTicket(localTime, lastReq.lookup.target, ticket)
-
-	if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil {
-		return
-	}
-
-	topic := lastReq.lookup.topic
-	topicIdx := ticket.findIdx(topic)
-	if topicIdx == -1 {
-		return
-	}
-
-	bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen))
-	if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched {
-		s.lastBucketFetched = bucket
-	}
-
-	if _, ok := s.tickets[topic]; ok {
-		wait := ticket.regTime[topicIdx] - localTime
-		rnd := rand.ExpFloat64()
-		if rnd > 10 {
-			rnd = 10
-		}
-		if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd {
-			// use the ticket to register this topic
-			//fmt.Println("addTicket", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong)
-			s.addTicketRef(ticketRef{ticket, topicIdx})
-		}
-	}
-
-	if ticket.refCnt > 0 {
-		s.nextTicketCached = nil
-		s.nodes[ticket.node] = ticket
-	}
-}
-
-func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool {
-	qq := s.queriesSent[node]
-	if qq != nil {
-		now := mclock.Now()
-		for _, sq := range qq {
-			if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) {
-				return false
-			}
-		}
-	}
-	return true
-}
-
-func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) {
-	now := mclock.Now()
-	qq := s.queriesSent[node]
-	if qq == nil {
-		qq = make(map[common.Hash]sentQuery)
-		s.queriesSent[node] = qq
-	}
-	qq[hash] = sentQuery{sent: now, lookup: lookup}
-	s.cleanupTopicQueries(now)
-}
-
-func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) {
-	if s.nextTopicQueryCleanup > now {
-		return
-	}
-	exp := now - mclock.AbsTime(topicQueryResend)
-	for n, qq := range s.queriesSent {
-		for h, q := range qq {
-			if q.sent < exp {
-				delete(qq, h)
-			}
-		}
-		if len(qq) == 0 {
-			delete(s.queriesSent, n)
-		}
-	}
-	s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout)
-}
-
-func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) {
-	now := mclock.Now()
-	//fmt.Println("got", from.addr().String(), hash, len(nodes))
-	qq := s.queriesSent[from]
-	if qq == nil {
-		return true
-	}
-	q, ok := qq[hash]
-	if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) {
-		return true
-	}
-	inside := float64(0)
-	if len(nodes) > 0 {
-		inside = 1
-	}
-	s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside)
-	chn := s.searchTopicMap[q.lookup.topic].foundChn
-	if chn == nil {
-		//fmt.Println("no channel")
-		return false
-	}
-	for _, node := range nodes {
-		ip := node.IP
-		if ip.IsUnspecified() || ip.IsLoopback() {
-			ip = from.IP
-		}
-		n := NewNode(node.ID, ip, node.UDP, node.TCP)
-		select {
-		case chn <- n:
-		default:
-			return false
-		}
-	}
-	return false
-}
-
-type topicRadius struct {
-	topic             Topic
-	topicHashPrefix   uint64
-	radius, minRadius uint64
-	buckets           []topicRadiusBucket
-	converged         bool
-	radiusLookupCnt   int
-}
-
-type topicRadiusEvent int
-
-const (
-	trOutside topicRadiusEvent = iota
-	trInside
-	trNoAdjust
-	trCount
-)
-
-type topicRadiusBucket struct {
-	weights    [trCount]float64
-	lastTime   mclock.AbsTime
-	value      float64
-	lookupSent map[common.Hash]mclock.AbsTime
-}
-
-func (b *topicRadiusBucket) update(now mclock.AbsTime) {
-	if now == b.lastTime {
-		return
-	}
-	exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC))
-	for i, w := range b.weights {
-		b.weights[i] = w * exp
-	}
-	b.lastTime = now
-
-	for target, tm := range b.lookupSent {
-		if now-tm > mclock.AbsTime(respTimeout) {
-			b.weights[trNoAdjust] += 1
-			delete(b.lookupSent, target)
-		}
-	}
-}
-
-func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) {
-	b.update(now)
-	if inside <= 0 {
-		b.weights[trOutside] += 1
-	} else {
-		if inside >= 1 {
-			b.weights[trInside] += 1
-		} else {
-			b.weights[trInside] += inside
-			b.weights[trOutside] += 1 - inside
-		}
-	}
-}
-
-func newTopicRadius(t Topic) *topicRadius {
-	topicHash := crypto.Keccak256Hash([]byte(t))
-	topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8])
-
-	return &topicRadius{
-		topic:           t,
-		topicHashPrefix: topicHashPrefix,
-		radius:          maxRadius,
-		minRadius:       maxRadius,
-	}
-}
-
-func (r *topicRadius) getBucketIdx(addrHash common.Hash) int {
-	prefix := binary.BigEndian.Uint64(addrHash[0:8])
-	var log2 float64
-	if prefix != r.topicHashPrefix {
-		log2 = math.Log2(float64(prefix ^ r.topicHashPrefix))
-	}
-	bucket := int((64 - log2) * radiusBucketsPerBit)
-	max := 64*radiusBucketsPerBit - 1
-	if bucket > max {
-		return max
-	}
-	if bucket < 0 {
-		return 0
-	}
-	return bucket
-}
-
-func (r *topicRadius) targetForBucket(bucket int) common.Hash {
-	min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit)
-	max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit)
-	a := uint64(min)
-	b := randUint64n(uint64(max - min))
-	xor := a + b
-	if xor < a {
-		xor = ^uint64(0)
-	}
-	prefix := r.topicHashPrefix ^ xor
-	var target common.Hash
-	binary.BigEndian.PutUint64(target[0:8], prefix)
-	globalRandRead(target[8:])
-	return target
-}
-
-// package rand provides a Read function in Go 1.6 and later, but
-// we can't use it yet because we still support Go 1.5.
-func globalRandRead(b []byte) {
-	pos := 0
-	val := 0
-	for n := 0; n < len(b); n++ {
-		if pos == 0 {
-			val = rand.Int()
-			pos = 7
-		}
-		b[n] = byte(val)
-		val >>= 8
-		pos--
-	}
-}
-
-func (r *topicRadius) chooseLookupBucket(a, b int) int {
-	if a < 0 {
-		a = 0
-	}
-	if a > b {
-		return -1
-	}
-	c := 0
-	for i := a; i <= b; i++ {
-		if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust {
-			c++
-		}
-	}
-	if c == 0 {
-		return -1
-	}
-	rnd := randUint(uint32(c))
-	for i := a; i <= b; i++ {
-		if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust {
-			if rnd == 0 {
-				return i
-			}
-			rnd--
-		}
-	}
-	panic(nil) // should never happen
-}
-
-func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool {
-	var max float64
-	if a < 0 {
-		a = 0
-	}
-	if b >= len(r.buckets) {
-		b = len(r.buckets) - 1
-		if r.buckets[b].value > max {
-			max = r.buckets[b].value
-		}
-	}
-	if b >= a {
-		for i := a; i <= b; i++ {
-			if r.buckets[i].value > max {
-				max = r.buckets[i].value
-			}
-		}
-	}
-	return maxValue-max < minPeakSize
-}
-
-func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) {
-	maxBucket := 0
-	maxValue := float64(0)
-	now := mclock.Now()
-	v := float64(0)
-	for i := range r.buckets {
-		r.buckets[i].update(now)
-		v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside]
-		r.buckets[i].value = v
-		//fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust])
-	}
-	//fmt.Println()
-	slopeCross := -1
-	for i, b := range r.buckets {
-		v := b.value
-		if v < float64(i)*minSlope {
-			slopeCross = i
-			break
-		}
-		if v > maxValue {
-			maxValue = v
-			maxBucket = i + 1
-		}
-	}
-
-	minRadBucket := len(r.buckets)
-	sum := float64(0)
-	for minRadBucket > 0 && sum < minRightSum {
-		minRadBucket--
-		b := r.buckets[minRadBucket]
-		sum += b.weights[trInside] + b.weights[trOutside]
-	}
-	r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit))
-
-	lookupLeft := -1
-	if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) {
-		lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1)
-	}
-	lookupRight := -1
-	if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) {
-		for len(r.buckets) <= maxBucket+lookupWidth {
-			r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)})
-		}
-		lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1)
-	}
-	if lookupLeft == -1 {
-		radiusLookup = lookupRight
-	} else {
-		if lookupRight == -1 {
-			radiusLookup = lookupLeft
-		} else {
-			if randUint(2) == 0 {
-				radiusLookup = lookupLeft
-			} else {
-				radiusLookup = lookupRight
-			}
-		}
-	}
-
-	//fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue)
-
-	if radiusLookup == -1 {
-		// no more radius lookups needed at the moment, return a radius
-		r.converged = true
-		rad := maxBucket
-		if minRadBucket < rad {
-			rad = minRadBucket
-		}
-		radius = ^uint64(0)
-		if rad > 0 {
-			radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit))
-		}
-		r.radius = radius
-	}
-
-	return
-}
-
-func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo {
-	if !forceRegular {
-		_, radiusLookup := r.recalcRadius()
-		if radiusLookup != -1 {
-			target := r.targetForBucket(radiusLookup)
-			r.buckets[radiusLookup].lookupSent[target] = mclock.Now()
-			return lookupInfo{target: target, topic: r.topic, radiusLookup: true}
-		}
-	}
-
-	radExt := r.radius / 2
-	if radExt > maxRadius-r.radius {
-		radExt = maxRadius - r.radius
-	}
-	rnd := randUint64n(r.radius) + randUint64n(2*radExt)
-	if rnd > radExt {
-		rnd -= radExt
-	} else {
-		rnd = radExt - rnd
-	}
-
-	prefix := r.topicHashPrefix ^ rnd
-	var target common.Hash
-	binary.BigEndian.PutUint64(target[0:8], prefix)
-	globalRandRead(target[8:])
-	return lookupInfo{target: target, topic: r.topic, radiusLookup: false}
-}
-
-func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) {
-	wait := t.t.regTime[t.idx] - t.t.issueTime
-	inside := float64(wait)/float64(targetWaitTime) - 0.5
-	if inside > 1 {
-		inside = 1
-	}
-	if inside < 0 {
-		inside = 0
-	}
-	r.adjust(now, targetHash, t.t.node.sha, inside)
-}
-
-func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) {
-	bucket := r.getBucketIdx(addrHash)
-	//fmt.Println("adjust", bucket, len(r.buckets), inside)
-	if bucket >= len(r.buckets) {
-		return
-	}
-	r.buckets[bucket].adjust(now, inside)
-	delete(r.buckets[bucket].lookupSent, targetHash)
-}
diff --git a/p2p/discv5/topic.go b/p2p/discv5/topic.go
deleted file mode 100644
index 609a41297f..0000000000
--- a/p2p/discv5/topic.go
+++ /dev/null
@@ -1,407 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"container/heap"
-	"fmt"
-	"math"
-	"math/rand"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common/mclock"
-	"github.com/ethereum/go-ethereum/log"
-)
-
-const (
-	maxEntries         = 10000
-	maxEntriesPerTopic = 50
-
-	fallbackRegistrationExpiry = 1 * time.Hour
-)
-
-type Topic string
-
-type topicEntry struct {
-	topic   Topic
-	fifoIdx uint64
-	node    *Node
-	expire  mclock.AbsTime
-}
-
-type topicInfo struct {
-	entries            map[uint64]*topicEntry
-	fifoHead, fifoTail uint64
-	rqItem             *topicRequestQueueItem
-	wcl                waitControlLoop
-}
-
-// removes tail element from the fifo
-func (t *topicInfo) getFifoTail() *topicEntry {
-	for t.entries[t.fifoTail] == nil {
-		t.fifoTail++
-	}
-	tail := t.entries[t.fifoTail]
-	t.fifoTail++
-	return tail
-}
-
-type nodeInfo struct {
-	entries                          map[Topic]*topicEntry
-	lastIssuedTicket, lastUsedTicket uint32
-	// you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time)
-	noRegUntil mclock.AbsTime
-}
-
-type topicTable struct {
-	db                    *nodeDB
-	self                  *Node
-	nodes                 map[*Node]*nodeInfo
-	topics                map[Topic]*topicInfo
-	globalEntries         uint64
-	requested             topicRequestQueue
-	requestCnt            uint64
-	lastGarbageCollection mclock.AbsTime
-}
-
-func newTopicTable(db *nodeDB, self *Node) *topicTable {
-	if printTestImgLogs {
-		fmt.Printf("*N %016x\n", self.sha[:8])
-	}
-	return &topicTable{
-		db:     db,
-		nodes:  make(map[*Node]*nodeInfo),
-		topics: make(map[Topic]*topicInfo),
-		self:   self,
-	}
-}
-
-func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo {
-	ti := t.topics[topic]
-	if ti == nil {
-		rqItem := &topicRequestQueueItem{
-			topic:    topic,
-			priority: t.requestCnt,
-		}
-		ti = &topicInfo{
-			entries: make(map[uint64]*topicEntry),
-			rqItem:  rqItem,
-		}
-		t.topics[topic] = ti
-		heap.Push(&t.requested, rqItem)
-	}
-	return ti
-}
-
-func (t *topicTable) checkDeleteTopic(topic Topic) {
-	ti := t.topics[topic]
-	if ti == nil {
-		return
-	}
-	if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() {
-		delete(t.topics, topic)
-		heap.Remove(&t.requested, ti.rqItem.index)
-	}
-}
-
-func (t *topicTable) getOrNewNode(node *Node) *nodeInfo {
-	n := t.nodes[node]
-	if n == nil {
-		//fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8])
-		var issued, used uint32
-		if t.db != nil {
-			issued, used = t.db.fetchTopicRegTickets(node.ID)
-		}
-		n = &nodeInfo{
-			entries:          make(map[Topic]*topicEntry),
-			lastIssuedTicket: issued,
-			lastUsedTicket:   used,
-		}
-		t.nodes[node] = n
-	}
-	return n
-}
-
-func (t *topicTable) checkDeleteNode(node *Node) {
-	if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < mclock.Now() {
-		//fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8])
-		delete(t.nodes, node)
-	}
-}
-
-func (t *topicTable) storeTicketCounters(node *Node) {
-	n := t.getOrNewNode(node)
-	if t.db != nil {
-		t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket)
-	}
-}
-
-func (t *topicTable) getEntries(topic Topic) []*Node {
-	t.collectGarbage()
-
-	te := t.topics[topic]
-	if te == nil {
-		return nil
-	}
-	nodes := make([]*Node, len(te.entries))
-	i := 0
-	for _, e := range te.entries {
-		nodes[i] = e.node
-		i++
-	}
-	t.requestCnt++
-	t.requested.update(te.rqItem, t.requestCnt)
-	return nodes
-}
-
-func (t *topicTable) addEntry(node *Node, topic Topic) {
-	n := t.getOrNewNode(node)
-	// clear previous entries by the same node
-	for _, e := range n.entries {
-		t.deleteEntry(e)
-	}
-	// ***
-	n = t.getOrNewNode(node)
-
-	tm := mclock.Now()
-	te := t.getOrNewTopic(topic)
-
-	if len(te.entries) == maxEntriesPerTopic {
-		t.deleteEntry(te.getFifoTail())
-	}
-
-	if t.globalEntries == maxEntries {
-		t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil
-	}
-
-	fifoIdx := te.fifoHead
-	te.fifoHead++
-	entry := &topicEntry{
-		topic:   topic,
-		fifoIdx: fifoIdx,
-		node:    node,
-		expire:  tm + mclock.AbsTime(fallbackRegistrationExpiry),
-	}
-	if printTestImgLogs {
-		fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8])
-	}
-	te.entries[fifoIdx] = entry
-	n.entries[topic] = entry
-	t.globalEntries++
-	te.wcl.registered(tm)
-}
-
-// removes least requested element from the fifo
-func (t *topicTable) leastRequested() *topicEntry {
-	for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil {
-		heap.Pop(&t.requested)
-	}
-	if t.requested.Len() == 0 {
-		return nil
-	}
-	return t.topics[t.requested[0].topic].getFifoTail()
-}
-
-// entry should exist
-func (t *topicTable) deleteEntry(e *topicEntry) {
-	if printTestImgLogs {
-		fmt.Printf("*- %d %v %016x %016x\n", mclock.Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8])
-	}
-	ne := t.nodes[e.node].entries
-	delete(ne, e.topic)
-	if len(ne) == 0 {
-		t.checkDeleteNode(e.node)
-	}
-	te := t.topics[e.topic]
-	delete(te.entries, e.fifoIdx)
-	if len(te.entries) == 0 {
-		t.checkDeleteTopic(e.topic)
-	}
-	t.globalEntries--
-}
-
-// It is assumed that topics and waitPeriods have the same length.
-func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) {
-	log.Trace("Using discovery ticket", "serial", serialNo, "topics", topics, "waits", waitPeriods)
-	//fmt.Println("useTicket", serialNo, topics, waitPeriods)
-	t.collectGarbage()
-
-	n := t.getOrNewNode(node)
-	if serialNo < n.lastUsedTicket {
-		return false
-	}
-
-	tm := mclock.Now()
-	if serialNo > n.lastUsedTicket && tm < n.noRegUntil {
-		return false
-	}
-	if serialNo != n.lastUsedTicket {
-		n.lastUsedTicket = serialNo
-		n.noRegUntil = tm + mclock.AbsTime(noRegTimeout())
-		t.storeTicketCounters(node)
-	}
-
-	currTime := uint64(tm / mclock.AbsTime(time.Second))
-	regTime := issueTime + uint64(waitPeriods[idx])
-	relTime := int64(currTime - regTime)
-	if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends
-		if e := n.entries[topics[idx]]; e == nil {
-			t.addEntry(node, topics[idx])
-		} else {
-			// if there is an active entry, don't move to the front of the FIFO but prolong expire time
-			e.expire = tm + mclock.AbsTime(fallbackRegistrationExpiry)
-		}
-		return true
-	}
-
-	return false
-}
-
-func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket {
-	t.collectGarbage()
-
-	now := mclock.Now()
-	n := t.getOrNewNode(node)
-	n.lastIssuedTicket++
-	t.storeTicketCounters(node)
-
-	tic := &ticket{
-		issueTime: now,
-		topics:    topics,
-		serial:    n.lastIssuedTicket,
-		regTime:   make([]mclock.AbsTime, len(topics)),
-	}
-	for i, topic := range topics {
-		var waitPeriod time.Duration
-		if topic := t.topics[topic]; topic != nil {
-			waitPeriod = topic.wcl.waitPeriod
-		} else {
-			waitPeriod = minWaitPeriod
-		}
-
-		tic.regTime[i] = now + mclock.AbsTime(waitPeriod)
-	}
-	return tic
-}
-
-const gcInterval = time.Minute
-
-func (t *topicTable) collectGarbage() {
-	tm := mclock.Now()
-	if time.Duration(tm-t.lastGarbageCollection) < gcInterval {
-		return
-	}
-	t.lastGarbageCollection = tm
-
-	for node, n := range t.nodes {
-		for _, e := range n.entries {
-			if e.expire <= tm {
-				t.deleteEntry(e)
-			}
-		}
-
-		t.checkDeleteNode(node)
-	}
-
-	for topic := range t.topics {
-		t.checkDeleteTopic(topic)
-	}
-}
-
-const (
-	minWaitPeriod   = time.Minute
-	regTimeWindow   = 10 // seconds
-	avgnoRegTimeout = time.Minute * 10
-	// target average interval between two incoming ad requests
-	wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic
-	//
-	wcTimeConst = time.Minute * 10
-)
-
-// initialization is not required, will set to minWaitPeriod at first registration
-type waitControlLoop struct {
-	lastIncoming mclock.AbsTime
-	waitPeriod   time.Duration
-}
-
-func (w *waitControlLoop) registered(tm mclock.AbsTime) {
-	w.waitPeriod = w.nextWaitPeriod(tm)
-	w.lastIncoming = tm
-}
-
-func (w *waitControlLoop) nextWaitPeriod(tm mclock.AbsTime) time.Duration {
-	period := tm - w.lastIncoming
-	wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst)))
-	if wp < minWaitPeriod {
-		wp = minWaitPeriod
-	}
-	return wp
-}
-
-func (w *waitControlLoop) hasMinimumWaitPeriod() bool {
-	return w.nextWaitPeriod(mclock.Now()) == minWaitPeriod
-}
-
-func noRegTimeout() time.Duration {
-	e := rand.ExpFloat64()
-	if e > 100 {
-		e = 100
-	}
-	return time.Duration(float64(avgnoRegTimeout) * e)
-}
-
-type topicRequestQueueItem struct {
-	topic    Topic
-	priority uint64
-	index    int
-}
-
-// A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems.
-type topicRequestQueue []*topicRequestQueueItem
-
-func (tq topicRequestQueue) Len() int { return len(tq) }
-
-func (tq topicRequestQueue) Less(i, j int) bool {
-	return tq[i].priority < tq[j].priority
-}
-
-func (tq topicRequestQueue) Swap(i, j int) {
-	tq[i], tq[j] = tq[j], tq[i]
-	tq[i].index = i
-	tq[j].index = j
-}
-
-func (tq *topicRequestQueue) Push(x interface{}) {
-	n := len(*tq)
-	item := x.(*topicRequestQueueItem)
-	item.index = n
-	*tq = append(*tq, item)
-}
-
-func (tq *topicRequestQueue) Pop() interface{} {
-	old := *tq
-	n := len(old)
-	item := old[n-1]
-	item.index = -1
-	*tq = old[0 : n-1]
-	return item
-}
-
-func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) {
-	item.priority = priority
-	heap.Fix(tq, item.index)
-}
diff --git a/p2p/discv5/topic_test.go b/p2p/discv5/topic_test.go
deleted file mode 100644
index ba79993f29..0000000000
--- a/p2p/discv5/topic_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"encoding/binary"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/mclock"
-)
-
-func TestTopicRadius(t *testing.T) {
-	now := mclock.Now()
-	topic := Topic("qwerty")
-	rad := newTopicRadius(topic)
-	targetRad := (^uint64(0)) / 100
-
-	waitFn := func(addr common.Hash) time.Duration {
-		prefix := binary.BigEndian.Uint64(addr[0:8])
-		dist := prefix ^ rad.topicHashPrefix
-		relDist := float64(dist) / float64(targetRad)
-		relTime := (1 - relDist/2) * 2
-		if relTime < 0 {
-			relTime = 0
-		}
-		return time.Duration(float64(targetWaitTime) * relTime)
-	}
-
-	bcnt := 0
-	cnt := 0
-	var sum float64
-	for cnt < 100 {
-		addr := rad.nextTarget(false).target
-		wait := waitFn(addr)
-		ticket := &ticket{
-			topics:  []Topic{topic},
-			regTime: []mclock.AbsTime{mclock.AbsTime(wait)},
-			node:    &Node{nodeNetGuts: nodeNetGuts{sha: addr}},
-		}
-		rad.adjustWithTicket(now, addr, ticketRef{ticket, 0})
-		if rad.radius != maxRadius {
-			cnt++
-			sum += float64(rad.radius)
-		} else {
-			bcnt++
-			if bcnt > 500 {
-				t.Errorf("Radius did not converge in 500 iterations")
-			}
-		}
-	}
-	avgRel := sum / float64(cnt) / float64(targetRad)
-	if avgRel > 1.05 || avgRel < 0.95 {
-		t.Errorf("Average/target ratio is too far from 1 (%v)", avgRel)
-	}
-}
diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go
deleted file mode 100644
index 088f95cac6..0000000000
--- a/p2p/discv5/udp.go
+++ /dev/null
@@ -1,429 +0,0 @@
-// Copyright 2016 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 .
-
-package discv5
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"errors"
-	"fmt"
-	"net"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p/netutil"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-const Version = 4
-
-// Errors
-var (
-	errPacketTooSmall = errors.New("too small")
-	errBadPrefix      = errors.New("bad prefix")
-)
-
-// Timeouts
-const (
-	respTimeout = 500 * time.Millisecond
-	expiration  = 20 * time.Second
-)
-
-// RPC request structures
-type (
-	ping struct {
-		Version    uint
-		From, To   rpcEndpoint
-		Expiration uint64
-
-		// v5
-		Topics []Topic
-
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// pong is the reply to ping.
-	pong struct {
-		// This field should mirror the UDP envelope address
-		// of the ping packet, which provides a way to discover the
-		// the external address (after NAT).
-		To rpcEndpoint
-
-		ReplyTok   []byte // This contains the hash of the ping packet.
-		Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
-
-		// v5
-		TopicHash    common.Hash
-		TicketSerial uint32
-		WaitPeriods  []uint32
-
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// findnode is a query for nodes close to the given target.
-	findnode struct {
-		Target     NodeID // doesn't need to be an actual public key
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// findnode is a query for nodes close to the given target.
-	findnodeHash struct {
-		Target     common.Hash
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	// reply to findnode
-	neighbors struct {
-		Nodes      []rpcNode
-		Expiration uint64
-		// Ignore additional fields (for forward compatibility).
-		Rest []rlp.RawValue `rlp:"tail"`
-	}
-
-	topicRegister struct {
-		Topics []Topic
-		Idx    uint
-		Pong   []byte
-	}
-
-	topicQuery struct {
-		Topic      Topic
-		Expiration uint64
-	}
-
-	// reply to topicQuery
-	topicNodes struct {
-		Echo  common.Hash
-		Nodes []rpcNode
-	}
-
-	rpcNode struct {
-		IP  net.IP // len 4 for IPv4 or 16 for IPv6
-		UDP uint16 // for discovery protocol
-		TCP uint16 // for RLPx protocol
-		ID  NodeID
-	}
-
-	rpcEndpoint struct {
-		IP  net.IP // len 4 for IPv4 or 16 for IPv6
-		UDP uint16 // for discovery protocol
-		TCP uint16 // for RLPx protocol
-	}
-)
-
-var (
-	versionPrefix     = []byte("temporary discovery v5")
-	versionPrefixSize = len(versionPrefix)
-	sigSize           = 520 / 8
-	headSize          = versionPrefixSize + sigSize // space of packet frame data
-)
-
-// Neighbors replies are sent across multiple packets to
-// stay below the 1280 byte limit. We compute the maximum number
-// of entries by stuffing a packet until it grows too large.
-var maxNeighbors = func() int {
-	p := neighbors{Expiration: ^uint64(0)}
-	maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
-	for n := 0; ; n++ {
-		p.Nodes = append(p.Nodes, maxSizeNode)
-		size, _, err := rlp.EncodeToReader(p)
-		if err != nil {
-			// If this ever happens, it will be caught by the unit tests.
-			panic("cannot encode: " + err.Error())
-		}
-		if headSize+size+1 >= 1280 {
-			return n
-		}
-	}
-}()
-
-var maxTopicNodes = func() int {
-	p := topicNodes{}
-	maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
-	for n := 0; ; n++ {
-		p.Nodes = append(p.Nodes, maxSizeNode)
-		size, _, err := rlp.EncodeToReader(p)
-		if err != nil {
-			// If this ever happens, it will be caught by the unit tests.
-			panic("cannot encode: " + err.Error())
-		}
-		if headSize+size+1 >= 1280 {
-			return n
-		}
-	}
-}()
-
-func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
-	ip := addr.IP.To4()
-	if ip == nil {
-		ip = addr.IP.To16()
-	}
-	return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
-}
-
-func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) {
-	if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
-		return nil, err
-	}
-	n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP)
-	err := n.validateComplete()
-	return n, err
-}
-
-func nodeToRPC(n *Node) rpcNode {
-	return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP}
-}
-
-type ingressPacket struct {
-	remoteID   NodeID
-	remoteAddr *net.UDPAddr
-	ev         nodeEvent
-	hash       []byte
-	data       interface{} // one of the RPC structs
-	rawData    []byte
-}
-
-type conn interface {
-	ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
-	WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
-	Close() error
-	LocalAddr() net.Addr
-}
-
-// udp implements the RPC protocol.
-type udp struct {
-	conn        conn
-	priv        *ecdsa.PrivateKey
-	ourEndpoint rpcEndpoint
-	net         *Network
-}
-
-// ListenUDP returns a new table that listens for UDP packets on laddr.
-func ListenUDP(priv *ecdsa.PrivateKey, conn conn, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) {
-	realaddr := conn.LocalAddr().(*net.UDPAddr)
-	transport, err := listenUDP(priv, conn, realaddr)
-	if err != nil {
-		return nil, err
-	}
-	net, err := newNetwork(transport, priv.PublicKey, nodeDBPath, netrestrict)
-	if err != nil {
-		return nil, err
-	}
-	log.Info("UDP listener up", "net", net.tab.self)
-	transport.net = net
-	go transport.readLoop()
-	return net, nil
-}
-
-func listenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr) (*udp, error) {
-	return &udp{conn: conn, priv: priv, ourEndpoint: makeEndpoint(realaddr, uint16(realaddr.Port))}, nil
-}
-
-func (t *udp) localAddr() *net.UDPAddr {
-	return t.conn.LocalAddr().(*net.UDPAddr)
-}
-
-func (t *udp) Close() {
-	t.conn.Close()
-}
-
-func (t *udp) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) {
-	hash, _ = t.sendPacket(remote.ID, remote.addr(), byte(ptype), data)
-	return hash
-}
-
-func (t *udp) sendPing(remote *Node, toaddr *net.UDPAddr, topics []Topic) (hash []byte) {
-	hash, _ = t.sendPacket(remote.ID, toaddr, byte(pingPacket), ping{
-		Version:    Version,
-		From:       t.ourEndpoint,
-		To:         makeEndpoint(toaddr, uint16(toaddr.Port)), // TODO: maybe use known TCP port from DB
-		Expiration: uint64(time.Now().Add(expiration).Unix()),
-		Topics:     topics,
-	})
-	return hash
-}
-
-func (t *udp) sendNeighbours(remote *Node, results []*Node) {
-	// Send neighbors in chunks with at most maxNeighbors per packet
-	// to stay below the 1280 byte limit.
-	p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
-	for i, result := range results {
-		p.Nodes = append(p.Nodes, nodeToRPC(result))
-		if len(p.Nodes) == maxNeighbors || i == len(results)-1 {
-			t.sendPacket(remote.ID, remote.addr(), byte(neighborsPacket), p)
-			p.Nodes = p.Nodes[:0]
-		}
-	}
-}
-
-func (t *udp) sendFindnodeHash(remote *Node, target common.Hash) {
-	t.sendPacket(remote.ID, remote.addr(), byte(findnodeHashPacket), findnodeHash{
-		Target:     target,
-		Expiration: uint64(time.Now().Add(expiration).Unix()),
-	})
-}
-
-func (t *udp) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) {
-	t.sendPacket(remote.ID, remote.addr(), byte(topicRegisterPacket), topicRegister{
-		Topics: topics,
-		Idx:    uint(idx),
-		Pong:   pong,
-	})
-}
-
-func (t *udp) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) {
-	p := topicNodes{Echo: queryHash}
-	var sent bool
-	for _, result := range nodes {
-		if result.IP.Equal(t.net.tab.self.IP) || netutil.CheckRelayIP(remote.IP, result.IP) == nil {
-			p.Nodes = append(p.Nodes, nodeToRPC(result))
-		}
-		if len(p.Nodes) == maxTopicNodes {
-			t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p)
-			p.Nodes = p.Nodes[:0]
-			sent = true
-		}
-	}
-	if !sent || len(p.Nodes) > 0 {
-		t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p)
-	}
-}
-
-func (t *udp) sendPacket(toid NodeID, toaddr *net.UDPAddr, ptype byte, req interface{}) (hash []byte, err error) {
-	//fmt.Println("sendPacket", nodeEvent(ptype), toaddr.String(), toid.String())
-	packet, hash, err := encodePacket(t.priv, ptype, req)
-	if err != nil {
-		//fmt.Println(err)
-		return hash, err
-	}
-	log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr))
-	if nbytes, err := t.conn.WriteToUDP(packet, toaddr); err != nil {
-		log.Trace(fmt.Sprint("UDP send failed:", err))
-	} else {
-		egressTrafficMeter.Mark(int64(nbytes))
-	}
-	//fmt.Println(err)
-	return hash, err
-}
-
-// zeroed padding space for encodePacket.
-var headSpace = make([]byte, headSize)
-
-func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (p, hash []byte, err error) {
-	b := new(bytes.Buffer)
-	b.Write(headSpace)
-	b.WriteByte(ptype)
-	if err := rlp.Encode(b, req); err != nil {
-		log.Error(fmt.Sprint("error encoding packet:", err))
-		return nil, nil, err
-	}
-	packet := b.Bytes()
-	sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
-	if err != nil {
-		log.Error(fmt.Sprint("could not sign packet:", err))
-		return nil, nil, err
-	}
-	copy(packet, versionPrefix)
-	copy(packet[versionPrefixSize:], sig)
-	hash = crypto.Keccak256(packet[versionPrefixSize:])
-	return packet, hash, nil
-}
-
-// readLoop runs in its own goroutine. it injects ingress UDP packets
-// into the network loop.
-func (t *udp) readLoop() {
-	defer t.conn.Close()
-	// Discovery packets are defined to be no larger than 1280 bytes.
-	// Packets larger than this size will be cut at the end and treated
-	// as invalid because their hash won't match.
-	buf := make([]byte, 1280)
-	for {
-		nbytes, from, err := t.conn.ReadFromUDP(buf)
-		ingressTrafficMeter.Mark(int64(nbytes))
-		if netutil.IsTemporaryError(err) {
-			// Ignore temporary read errors.
-			log.Debug(fmt.Sprintf("Temporary read error: %v", err))
-			continue
-		} else if err != nil {
-			// Shut down the loop for permament errors.
-			log.Debug(fmt.Sprintf("Read error: %v", err))
-			return
-		}
-		t.handlePacket(from, buf[:nbytes])
-	}
-}
-
-func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error {
-	pkt := ingressPacket{remoteAddr: from}
-	if err := decodePacket(buf, &pkt); err != nil {
-		log.Debug(fmt.Sprintf("Bad packet from %v: %v", from, err))
-		//fmt.Println("bad packet", err)
-		return err
-	}
-	t.net.reqReadPacket(pkt)
-	return nil
-}
-
-func decodePacket(buffer []byte, pkt *ingressPacket) error {
-	if len(buffer) < headSize+1 {
-		return errPacketTooSmall
-	}
-	buf := make([]byte, len(buffer))
-	copy(buf, buffer)
-	prefix, sig, sigdata := buf[:versionPrefixSize], buf[versionPrefixSize:headSize], buf[headSize:]
-	if !bytes.Equal(prefix, versionPrefix) {
-		return errBadPrefix
-	}
-	fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig)
-	if err != nil {
-		return err
-	}
-	pkt.rawData = buf
-	pkt.hash = crypto.Keccak256(buf[versionPrefixSize:])
-	pkt.remoteID = fromID
-	switch pkt.ev = nodeEvent(sigdata[0]); pkt.ev {
-	case pingPacket:
-		pkt.data = new(ping)
-	case pongPacket:
-		pkt.data = new(pong)
-	case findnodePacket:
-		pkt.data = new(findnode)
-	case neighborsPacket:
-		pkt.data = new(neighbors)
-	case findnodeHashPacket:
-		pkt.data = new(findnodeHash)
-	case topicRegisterPacket:
-		pkt.data = new(topicRegister)
-	case topicQueryPacket:
-		pkt.data = new(topicQuery)
-	case topicNodesPacket:
-		pkt.data = new(topicNodes)
-	default:
-		return fmt.Errorf("unknown packet type: %d", sigdata[0])
-	}
-	s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
-	err = s.Decode(pkt.data)
-	return err
-}
diff --git a/p2p/dnsdisc/client.go b/p2p/dnsdisc/client.go
index a29f82cd81..f2a4bed4c6 100644
--- a/p2p/dnsdisc/client.go
+++ b/p2p/dnsdisc/client.go
@@ -32,14 +32,17 @@ import (
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
 	lru "github.com/hashicorp/golang-lru"
+	"golang.org/x/sync/singleflight"
 	"golang.org/x/time/rate"
 )
 
 // Client discovers nodes by querying DNS servers.
 type Client struct {
-	cfg     Config
-	clock   mclock.Clock
-	entries *lru.Cache
+	cfg          Config
+	clock        mclock.Clock
+	entries      *lru.Cache
+	ratelimit    *rate.Limiter
+	singleflight singleflight.Group
 }
 
 // Config holds configuration options for the client.
@@ -97,8 +100,12 @@ func NewClient(cfg Config) *Client {
 		panic(err)
 	}
 	rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
-	cfg.Resolver = &rateLimitResolver{cfg.Resolver, rlimit}
-	return &Client{cfg: cfg, entries: cache, clock: mclock.System{}}
+	return &Client{
+		cfg:       cfg,
+		entries:   cache,
+		clock:     mclock.System{},
+		ratelimit: rlimit,
+	}
 }
 
 // SyncTree downloads the entire node tree at the given URL.
@@ -130,17 +137,20 @@ func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
 
 // resolveRoot retrieves a root entry via DNS.
 func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
-	txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
-	c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
-	if err != nil {
-		return rootEntry{}, err
-	}
-	for _, txt := range txts {
-		if strings.HasPrefix(txt, rootPrefix) {
-			return parseAndVerifyRoot(txt, loc)
+	e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) {
+		txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
+		c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
+		if err != nil {
+			return rootEntry{}, err
 		}
-	}
-	return rootEntry{}, nameError{loc.domain, errNoRoot}
+		for _, txt := range txts {
+			if strings.HasPrefix(txt, rootPrefix) {
+				return parseAndVerifyRoot(txt, loc)
+			}
+		}
+		return rootEntry{}, nameError{loc.domain, errNoRoot}
+	})
+	return e.(rootEntry), err
 }
 
 func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
@@ -157,16 +167,27 @@ func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
 // resolveEntry retrieves an entry from the cache or fetches it from the network
 // if it isn't cached.
 func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
+	// The rate limit always applies, even when the result might be cached. This is
+	// important because it avoids hot-spinning in consumers of node iterators created on
+	// this client.
+	if err := c.ratelimit.Wait(ctx); err != nil {
+		return nil, err
+	}
 	cacheKey := truncateHash(hash)
 	if e, ok := c.entries.Get(cacheKey); ok {
 		return e.(entry), nil
 	}
-	e, err := c.doResolveEntry(ctx, domain, hash)
-	if err != nil {
-		return nil, err
-	}
-	c.entries.Add(cacheKey, e)
-	return e, nil
+
+	ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {
+		e, err := c.doResolveEntry(ctx, domain, hash)
+		if err != nil {
+			return nil, err
+		}
+		c.entries.Add(cacheKey, e)
+		return e, nil
+	})
+	e, _ := ei.(entry)
+	return e, err
 }
 
 // doResolveEntry fetches an entry via DNS.
@@ -196,19 +217,6 @@ func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry
 	return nil, nameError{name, errNoEntry}
 }
 
-// rateLimitResolver applies a rate limit to a Resolver.
-type rateLimitResolver struct {
-	r       Resolver
-	limiter *rate.Limiter
-}
-
-func (r *rateLimitResolver) LookupTXT(ctx context.Context, domain string) ([]string, error) {
-	if err := r.limiter.Wait(ctx); err != nil {
-		return nil, err
-	}
-	return r.r.LookupTXT(ctx, domain)
-}
-
 // randomIterator traverses a set of trees and returns nodes found in them.
 type randomIterator struct {
 	cur      *enode.Node
@@ -217,8 +225,11 @@ type randomIterator struct {
 	c        *Client
 
 	mu    sync.Mutex
-	trees map[string]*clientTree // all trees
 	lc    linkCache              // tracks tree dependencies
+	trees map[string]*clientTree // all trees
+	// buffers for syncableTrees
+	syncableList []*clientTree
+	disabledList []*clientTree
 }
 
 func (c *Client) newRandomIterator() *randomIterator {
@@ -238,10 +249,10 @@ func (it *randomIterator) Node() *enode.Node {
 
 // Close closes the iterator.
 func (it *randomIterator) Close() {
+	it.cancelFn()
+
 	it.mu.Lock()
 	defer it.mu.Unlock()
-
-	it.cancelFn()
 	it.trees = nil
 }
 
@@ -251,7 +262,7 @@ func (it *randomIterator) Next() bool {
 	return it.cur != nil
 }
 
-// addTree adds a enrtree:// URL to the iterator.
+// addTree adds an enrtree:// URL to the iterator.
 func (it *randomIterator) addTree(url string) error {
 	le, err := parseLink(url)
 	if err != nil {
@@ -264,7 +275,7 @@ func (it *randomIterator) addTree(url string) error {
 // nextNode syncs random tree entries until it finds a node.
 func (it *randomIterator) nextNode() *enode.Node {
 	for {
-		ct := it.nextTree()
+		ct := it.pickTree()
 		if ct == nil {
 			return nil
 		}
@@ -282,26 +293,79 @@ func (it *randomIterator) nextNode() *enode.Node {
 	}
 }
 
-// nextTree returns a random tree.
-func (it *randomIterator) nextTree() *clientTree {
+// pickTree returns a random tree to sync from.
+func (it *randomIterator) pickTree() *clientTree {
 	it.mu.Lock()
 	defer it.mu.Unlock()
 
+	// Rebuild the trees map if any links have changed.
 	if it.lc.changed {
 		it.rebuildTrees()
 		it.lc.changed = false
 	}
-	if len(it.trees) == 0 {
-		return nil
+
+	for {
+		canSync, trees := it.syncableTrees()
+		switch {
+		case canSync:
+			// Pick a random tree.
+			return trees[rand.Intn(len(trees))]
+		case len(trees) > 0:
+			// No sync action can be performed on any tree right now. The only meaningful
+			// thing to do is waiting for any root record to get updated.
+			if !it.waitForRootUpdates(trees) {
+				// Iterator was closed while waiting.
+				return nil
+			}
+		default:
+			// There are no trees left, the iterator was closed.
+			return nil
+		}
 	}
-	limit := rand.Intn(len(it.trees))
+}
+
+// syncableTrees finds trees on which any meaningful sync action can be performed.
+func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) {
+	// Resize tree lists.
+	it.syncableList = it.syncableList[:0]
+	it.disabledList = it.disabledList[:0]
+
+	// Partition them into the two lists.
 	for _, ct := range it.trees {
-		if limit == 0 {
-			return ct
+		if ct.canSyncRandom() {
+			it.syncableList = append(it.syncableList, ct)
+		} else {
+			it.disabledList = append(it.disabledList, ct)
 		}
-		limit--
 	}
-	return nil
+	if len(it.syncableList) > 0 {
+		return true, it.syncableList
+	}
+	return false, it.disabledList
+}
+
+// waitForRootUpdates waits for the closest scheduled root check time on the given trees.
+func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool {
+	var minTree *clientTree
+	var nextCheck mclock.AbsTime
+	for _, ct := range trees {
+		check := ct.nextScheduledRootCheck()
+		if minTree == nil || check < nextCheck {
+			minTree = ct
+			nextCheck = check
+		}
+	}
+
+	sleep := nextCheck.Sub(it.c.clock.Now())
+	it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain)
+	timeout := it.c.clock.NewTimer(sleep)
+	defer timeout.Stop()
+	select {
+	case <-timeout.C():
+		return true
+	case <-it.ctx.Done():
+		return false // Iterator was closed.
+	}
 }
 
 // rebuildTrees rebuilds the 'trees' map.
diff --git a/p2p/dnsdisc/client_test.go b/p2p/dnsdisc/client_test.go
index 6a6705abf2..741bee4230 100644
--- a/p2p/dnsdisc/client_test.go
+++ b/p2p/dnsdisc/client_test.go
@@ -231,6 +231,53 @@ func TestIteratorRootRecheckOnFail(t *testing.T) {
 	checkIterator(t, it, nodes)
 }
 
+// This test checks that the iterator works correctly when the tree is initially empty.
+func TestIteratorEmptyTree(t *testing.T) {
+	var (
+		clock    = new(mclock.Simulated)
+		nodes    = testNodes(nodesSeed1, 1)
+		resolver = newMapResolver()
+		c        = NewClient(Config{
+			Resolver:        resolver,
+			Logger:          testlog.Logger(t, log.LvlTrace),
+			RecheckInterval: 20 * time.Minute,
+			RateLimit:       500,
+		})
+	)
+	c.clock = clock
+	tree1, url := makeTestTree("n", nil, nil)
+	tree2, _ := makeTestTree("n", nodes, nil)
+	resolver.add(tree1.ToTXT("n"))
+
+	// Start the iterator.
+	node := make(chan *enode.Node)
+	it, err := c.NewIterator(url)
+	if err != nil {
+		t.Fatal(err)
+	}
+	go func() {
+		it.Next()
+		node <- it.Node()
+	}()
+
+	// Wait for the client to get stuck in waitForRootUpdates.
+	clock.WaitForTimers(1)
+
+	// Now update the root.
+	resolver.add(tree2.ToTXT("n"))
+
+	// Wait for it to pick up the root change.
+	clock.Run(c.cfg.RecheckInterval)
+	select {
+	case n := <-node:
+		if n.ID() != nodes[0].ID() {
+			t.Fatalf("wrong node returned")
+		}
+	case <-time.After(5 * time.Second):
+		t.Fatal("it.Next() did not unblock within 5s of real time")
+	}
+}
+
 // updateSomeNodes applies ENR updates to some of the given nodes.
 func updateSomeNodes(keySeed int64, nodes []*enode.Node) {
 	keys := testKeys(nodesSeed1, len(nodes))
diff --git a/p2p/dnsdisc/sync.go b/p2p/dnsdisc/sync.go
index 36f02acba6..073547c90d 100644
--- a/p2p/dnsdisc/sync.go
+++ b/p2p/dnsdisc/sync.go
@@ -25,9 +25,9 @@ import (
 	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
-const (
-	rootRecheckFailCount = 5 // update root if this many leaf requests fail
-)
+// This is the number of consecutive leaf requests that may fail before
+// we consider re-resolving the tree root.
+const rootRecheckFailCount = 5
 
 // clientTree is a full tree being synced.
 type clientTree struct {
@@ -89,13 +89,22 @@ func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error)
 	ct.gcLinks()
 
 	// Sync next random entry in ENR tree. Once every node has been visited, we simply
-	// start over. This is fine because entries are cached.
+	// start over. This is fine because entries are cached internally by the client LRU
+	// also by DNS resolvers.
 	if ct.enrs.done() {
 		ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
 	}
 	return ct.syncNextRandomENR(ctx)
 }
 
+// canSyncRandom checks if any meaningful action can be performed by syncRandom.
+func (ct *clientTree) canSyncRandom() bool {
+	// Note: the check for non-zero leaf count is very important here.
+	// If we're done syncing all nodes, and no leaves were found, the tree
+	// is empty and we can't use it for sync.
+	return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0
+}
+
 // gcLinks removes outdated links from the global link cache. GC runs once
 // when the link sync finishes.
 func (ct *clientTree) gcLinks() {
@@ -184,10 +193,14 @@ func (ct *clientTree) updateRoot(ctx context.Context) error {
 // rootUpdateDue returns true when a root update is needed.
 func (ct *clientTree) rootUpdateDue() bool {
 	tooManyFailures := ct.leafFailCount > rootRecheckFailCount
-	scheduledCheck := ct.c.clock.Now().Sub(ct.lastRootCheck) > ct.c.cfg.RecheckInterval
+	scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck()
 	return ct.root == nil || tooManyFailures || scheduledCheck
 }
 
+func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime {
+	return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval)
+}
+
 // slowdownRootUpdate applies a delay to root resolution if is tried
 // too frequently. This avoids busy polling when the client is offline.
 // Returns true if the timeout passed, false if sync was canceled.
@@ -218,10 +231,11 @@ type subtreeSync struct {
 	root    string
 	missing []string // missing tree node hashes
 	link    bool     // true if this sync is for the link tree
+	leaves  int      // counter of synced leaves
 }
 
 func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
-	return &subtreeSync{c, loc, root, []string{root}, link}
+	return &subtreeSync{c, loc, root, []string{root}, link, 0}
 }
 
 func (ts *subtreeSync) done() bool {
@@ -253,10 +267,12 @@ func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, err
 		if ts.link {
 			return nil, errENRInLinkTree
 		}
+		ts.leaves++
 	case *linkEntry:
 		if !ts.link {
 			return nil, errLinkInENRTree
 		}
+		ts.leaves++
 	case *branchEntry:
 		ts.missing = append(ts.missing, e.children...)
 	}
diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go
index 82a935ca41..410ec3b854 100644
--- a/p2p/dnsdisc/tree.go
+++ b/p2p/dnsdisc/tree.go
@@ -113,10 +113,41 @@ func (t *Tree) Nodes() []*enode.Node {
 	return nodes
 }
 
+/*
+We want to keep the UDP size below 512 bytes. The UDP size is roughly:
+UDP length = 8 + UDP payload length ( 229 )
+UPD Payload length:
+ - dns.id 2
+ - dns.flags 2
+ - dns.count.queries 2
+ - dns.count.answers 2
+ - dns.count.auth_rr 2
+ - dns.count.add_rr 2
+ - queries (query-size + 6)
+ - answers :
+ 	- dns.resp.name 2
+ 	- dns.resp.type 2
+ 	- dns.resp.class 2
+ 	- dns.resp.ttl 4
+ 	- dns.resp.len 2
+ 	- dns.txt.length 1
+ 	- dns.txt resp_data_size
+
+So the total size is roughly a fixed overhead of `39`, and the size of the
+query (domain name) and response.
+The query size is, for example, FVY6INQ6LZ33WLCHO3BPR3FH6Y.snap.mainnet.ethdisco.net (52)
+
+We also have some static data in the response, such as `enrtree-branch:`, and potentially
+splitting the response up with `" "`, leaving us with a size of roughly `400` that we need
+to stay below.
+
+The number `370` is used to have some margin for extra overhead (for example, the dns query
+may be larger - more subdomains).
+*/
 const (
-	hashAbbrev    = 16
-	maxChildren   = 300 / hashAbbrev * (13 / 8)
-	minHashLength = 12
+	hashAbbrevSize = 1 + 16*13/8          // Size of an encoded hash (plus comma)
+	maxChildren    = 370 / hashAbbrevSize // 13 children
+	minHashLength  = 12
 )
 
 // MakeTree creates a tree containing the given nodes and links.
diff --git a/p2p/enode/node.go b/p2p/enode/node.go
index 3f6cda6d4a..c2429e0e8e 100644
--- a/p2p/enode/node.go
+++ b/p2p/enode/node.go
@@ -23,7 +23,6 @@ import (
 	"errors"
 	"fmt"
 	"math/bits"
-	"math/rand"
 	"net"
 	"strings"
 
@@ -278,23 +277,3 @@ func LogDist(a, b ID) int {
 	}
 	return len(a)*8 - lz
 }
-
-// RandomID returns a random ID b such that logdist(a, b) == n.
-func RandomID(a ID, n int) (b ID) {
-	if n == 0 {
-		return a
-	}
-	// flip bit at position n, fill the rest with random bits
-	b = a
-	pos := len(a) - n/8 - 1
-	bit := byte(0x01) << (byte(n%8) - 1)
-	if bit == 0 {
-		pos++
-		bit = 0x80
-	}
-	b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
-	for i := pos + 1; i < len(a); i++ {
-		b[i] = byte(rand.Intn(255))
-	}
-	return b
-}
diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go
index 08a0511494..d62f383f0b 100644
--- a/p2p/enode/nodedb.go
+++ b/p2p/enode/nodedb.go
@@ -167,7 +167,7 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) {
 	}
 	key = key[len(dbDiscoverRoot)+1:]
 	// Split out the IP.
-	ip = net.IP(key[:16])
+	ip = key[:16]
 	if ip4 := ip.To4(); ip4 != nil {
 		ip = ip4
 	}
diff --git a/p2p/enode/urlv4.go b/p2p/enode/urlv4.go
index 2e3f9ba00e..c445049102 100644
--- a/p2p/enode/urlv4.go
+++ b/p2p/enode/urlv4.go
@@ -59,7 +59,7 @@ func MustParseV4(rawurl string) *Node {
 //
 // For complete nodes, the node ID is encoded in the username portion
 // of the URL, separated from the host by an @ sign. The hostname can
-// only be given as an IP address, DNS domain names are not allowed.
+// only be given as an IP address or using DNS domain name.
 // The port in the host name section is the TCP listening port. If the
 // TCP and UDP (discovery) ports differ, the UDP port is specified as
 // query parameter "discport".
diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go
index c36ae9e3ed..05e43fd805 100644
--- a/p2p/enr/enr.go
+++ b/p2p/enr/enr.go
@@ -50,6 +50,7 @@ var (
 	errNotSorted      = errors.New("record key/value pairs are not sorted by key")
 	errDuplicateKey   = errors.New("record contains duplicate key")
 	errIncompletePair = errors.New("record contains incomplete k/v pair")
+	errIncompleteList = errors.New("record contains less than two list elements")
 	errTooBig         = fmt.Errorf("record bigger than %d bytes", SizeLimit)
 	errEncodeUnsigned = errors.New("can't encode unsigned record")
 	errNotFound       = errors.New("no such key in record")
@@ -209,9 +210,15 @@ func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
 		return dec, raw, err
 	}
 	if err = s.Decode(&dec.signature); err != nil {
+		if err == rlp.EOL {
+			err = errIncompleteList
+		}
 		return dec, raw, err
 	}
 	if err = s.Decode(&dec.seq); err != nil {
+		if err == rlp.EOL {
+			err = errIncompleteList
+		}
 		return dec, raw, err
 	}
 	// The rest of the record contains sorted k/v pairs.
diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go
index 96a9ced5cf..bf3f104744 100644
--- a/p2p/enr/enr_test.go
+++ b/p2p/enr/enr_test.go
@@ -231,6 +231,29 @@ func TestRecordTooBig(t *testing.T) {
 	require.NoError(t, signTest([]byte{5}, &r))
 }
 
+// This checks that incomplete RLP inputs are handled correctly.
+func TestDecodeIncomplete(t *testing.T) {
+	type decTest struct {
+		input []byte
+		err   error
+	}
+	tests := []decTest{
+		{[]byte{0xC0}, errIncompleteList},
+		{[]byte{0xC1, 0x1}, errIncompleteList},
+		{[]byte{0xC2, 0x1, 0x2}, nil},
+		{[]byte{0xC3, 0x1, 0x2, 0x3}, errIncompletePair},
+		{[]byte{0xC4, 0x1, 0x2, 0x3, 0x4}, nil},
+		{[]byte{0xC5, 0x1, 0x2, 0x3, 0x4, 0x5}, errIncompletePair},
+	}
+	for _, test := range tests {
+		var r Record
+		err := rlp.DecodeBytes(test.input, &r)
+		if err != test.err {
+			t.Errorf("wrong error for %X: %v", test.input, err)
+		}
+	}
+}
+
 // TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
 func TestSignEncodeAndDecodeRandom(t *testing.T) {
 	var r Record
diff --git a/p2p/message.go b/p2p/message.go
index 10b55a939c..bd048138c3 100644
--- a/p2p/message.go
+++ b/p2p/message.go
@@ -70,6 +70,10 @@ func (msg Msg) Discard() error {
 	return err
 }
 
+func (msg Msg) Time() time.Time {
+	return msg.ReceivedAt
+}
+
 type MsgReader interface {
 	ReadMsg() (Msg, error)
 }
diff --git a/p2p/message_test.go b/p2p/message_test.go
index a01f755561..e575c5d96e 100644
--- a/p2p/message_test.go
+++ b/p2p/message_test.go
@@ -18,11 +18,9 @@ package p2p
 
 import (
 	"bytes"
-	"encoding/hex"
 	"fmt"
 	"io"
 	"runtime"
-	"strings"
 	"testing"
 	"time"
 )
@@ -141,12 +139,3 @@ func TestEOFSignal(t *testing.T) {
 	default:
 	}
 }
-
-func unhex(str string) []byte {
-	r := strings.NewReplacer("\t", "", " ", "", "\n", "")
-	b, err := hex.DecodeString(r.Replace(str))
-	if err != nil {
-		panic(fmt.Sprintf("invalid hex string: %q", str))
-	}
-	return b
-}
diff --git a/p2p/metrics.go b/p2p/metrics.go
index 44946473fa..1bb505cdfb 100644
--- a/p2p/metrics.go
+++ b/p2p/metrics.go
@@ -25,8 +25,14 @@ import (
 )
 
 const (
+	// ingressMeterName is the prefix of the per-packet inbound metrics.
 	ingressMeterName = "p2p/ingress"
-	egressMeterName  = "p2p/egress"
+
+	// egressMeterName is the prefix of the per-packet outbound metrics.
+	egressMeterName = "p2p/egress"
+
+	// HandleHistName is the prefix of the per-packet serving time histograms.
+	HandleHistName = "p2p/handle"
 )
 
 var (
diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go
index 504b7b074a..9d5519b9c4 100644
--- a/p2p/nat/nat.go
+++ b/p2p/nat/nat.go
@@ -91,15 +91,14 @@ func Parse(spec string) (Interface, error) {
 }
 
 const (
-	mapTimeout        = 20 * time.Minute
-	mapUpdateInterval = 15 * time.Minute
+	mapTimeout = 10 * time.Minute
 )
 
 // Map adds a port mapping on m and keeps it alive until c is closed.
 // This function is typically invoked in its own goroutine.
-func Map(m Interface, c chan struct{}, protocol string, extport, intport int, name string) {
+func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
 	log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
-	refresh := time.NewTimer(mapUpdateInterval)
+	refresh := time.NewTimer(mapTimeout)
 	defer func() {
 		refresh.Stop()
 		log.Debug("Deleting port mapping")
@@ -121,7 +120,7 @@ func Map(m Interface, c chan struct{}, protocol string, extport, intport int, na
 			if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
 				log.Debug("Couldn't add port mapping", "err", err)
 			}
-			refresh.Reset(mapUpdateInterval)
+			refresh.Reset(mapTimeout)
 		}
 	}
 }
@@ -220,9 +219,8 @@ func (n *autodisc) String() string {
 	defer n.mu.Unlock()
 	if n.found == nil {
 		return n.what
-	} else {
-		return n.found.String()
 	}
+	return n.found.String()
 }
 
 // wait blocks until auto-discovery has been performed.
diff --git a/p2p/nat/natupnp.go b/p2p/nat/natupnp.go
index 029143b7bc..1f5d714664 100644
--- a/p2p/nat/natupnp.go
+++ b/p2p/nat/natupnp.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"net"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/huin/goupnp"
@@ -28,12 +29,17 @@ import (
 	"github.com/huin/goupnp/dcps/internetgateway2"
 )
 
-const soapRequestTimeout = 3 * time.Second
+const (
+	soapRequestTimeout = 3 * time.Second
+	rateLimit          = 200 * time.Millisecond
+)
 
 type upnp struct {
-	dev     *goupnp.RootDevice
-	service string
-	client  upnpClient
+	dev         *goupnp.RootDevice
+	service     string
+	client      upnpClient
+	mu          sync.Mutex
+	lastReqTime time.Time
 }
 
 type upnpClient interface {
@@ -43,8 +49,23 @@ type upnpClient interface {
 	GetNATRSIPStatus() (sip bool, nat bool, err error)
 }
 
+func (n *upnp) natEnabled() bool {
+	var ok bool
+	var err error
+	n.withRateLimit(func() error {
+		_, ok, err = n.client.GetNATRSIPStatus()
+		return err
+	})
+	return err == nil && ok
+}
+
 func (n *upnp) ExternalIP() (addr net.IP, err error) {
-	ipString, err := n.client.GetExternalIPAddress()
+	var ipString string
+	n.withRateLimit(func() error {
+		ipString, err = n.client.GetExternalIPAddress()
+		return err
+	})
+
 	if err != nil {
 		return nil, err
 	}
@@ -63,7 +84,10 @@ func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, li
 	protocol = strings.ToUpper(protocol)
 	lifetimeS := uint32(lifetime / time.Second)
 	n.DeleteMapping(protocol, extport, intport)
-	return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
+
+	return n.withRateLimit(func() error {
+		return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
+	})
 }
 
 func (n *upnp) internalAddress() (net.IP, error) {
@@ -90,36 +114,51 @@ func (n *upnp) internalAddress() (net.IP, error) {
 }
 
 func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
-	return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
+	return n.withRateLimit(func() error {
+		return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
+	})
 }
 
 func (n *upnp) String() string {
 	return "UPNP " + n.service
 }
 
+func (n *upnp) withRateLimit(fn func() error) error {
+	n.mu.Lock()
+	defer n.mu.Unlock()
+
+	lastreq := time.Since(n.lastReqTime)
+	if lastreq < rateLimit {
+		time.Sleep(rateLimit - lastreq)
+	}
+	err := fn()
+	n.lastReqTime = time.Now()
+	return err
+}
+
 // discoverUPnP searches for Internet Gateway Devices
 // and returns the first one it can find on the local network.
 func discoverUPnP() Interface {
 	found := make(chan *upnp, 2)
 	// IGDv1
-	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp {
+	go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
 		switch sc.Service.ServiceType {
 		case internetgateway1.URN_WANIPConnection_1:
-			return &upnp{dev, "IGDv1-IP1", &internetgateway1.WANIPConnection1{ServiceClient: sc}}
+			return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
 		case internetgateway1.URN_WANPPPConnection_1:
-			return &upnp{dev, "IGDv1-PPP1", &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
+			return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
 		}
 		return nil
 	})
 	// IGDv2
-	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(dev *goupnp.RootDevice, sc goupnp.ServiceClient) *upnp {
+	go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
 		switch sc.Service.ServiceType {
 		case internetgateway2.URN_WANIPConnection_1:
-			return &upnp{dev, "IGDv2-IP1", &internetgateway2.WANIPConnection1{ServiceClient: sc}}
+			return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
 		case internetgateway2.URN_WANIPConnection_2:
-			return &upnp{dev, "IGDv2-IP2", &internetgateway2.WANIPConnection2{ServiceClient: sc}}
+			return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
 		case internetgateway2.URN_WANPPPConnection_1:
-			return &upnp{dev, "IGDv2-PPP1", &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
+			return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
 		}
 		return nil
 	})
@@ -134,7 +173,7 @@ func discoverUPnP() Interface {
 // finds devices matching the given target and calls matcher for all
 // advertised services of each device. The first non-nil service found
 // is sent into out. If no service matched, nil is sent.
-func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice, goupnp.ServiceClient) *upnp) {
+func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
 	devs, err := goupnp.DiscoverDevices(target)
 	if err != nil {
 		out <- nil
@@ -157,16 +196,17 @@ func discover(out chan<- *upnp, target string, matcher func(*goupnp.RootDevice,
 				Service:    service,
 			}
 			sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
-			upnp := matcher(devs[i].Root, sc)
+			upnp := matcher(sc)
 			if upnp == nil {
 				return
 			}
+			upnp.dev = devs[i].Root
+
 			// check whether port mapping is enabled
-			if _, nat, err := upnp.client.GetNATRSIPStatus(); err != nil || !nat {
-				return
+			if upnp.natEnabled() {
+				out <- upnp
+				found = true
 			}
-			out <- upnp
-			found = true
 		})
 	}
 	if !found {
diff --git a/p2p/netutil/error.go b/p2p/netutil/error.go
index cb21b9cd4c..5d3d9bfd65 100644
--- a/p2p/netutil/error.go
+++ b/p2p/netutil/error.go
@@ -23,3 +23,11 @@ func IsTemporaryError(err error) bool {
 	})
 	return ok && tempErr.Temporary() || isPacketTooBig(err)
 }
+
+// IsTimeout checks whether the given error is a timeout.
+func IsTimeout(err error) bool {
+	timeoutErr, ok := err.(interface {
+		Timeout() bool
+	})
+	return ok && timeoutErr.Timeout()
+}
diff --git a/p2p/nodestate/nodestate.go b/p2p/nodestate/nodestate.go
new file mode 100644
index 0000000000..9323d53cbd
--- /dev/null
+++ b/p2p/nodestate/nodestate.go
@@ -0,0 +1,1016 @@
+// 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 .
+
+package nodestate
+
+import (
+	"errors"
+	"reflect"
+	"sync"
+	"time"
+	"unsafe"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+var (
+	ErrInvalidField = errors.New("invalid field type")
+	ErrClosed       = errors.New("already closed")
+)
+
+type (
+	// NodeStateMachine implements a network node-related event subscription system.
+	// It can assign binary state flags and fields of arbitrary type to each node and allows
+	// subscriptions to flag/field changes which can also modify further flags and fields,
+	// potentially triggering further subscriptions. An operation includes an initial change
+	// and all resulting subsequent changes and always ends in a consistent global state.
+	// It is initiated by a "top level" SetState/SetField call that blocks (also blocking other
+	// top-level functions) until the operation is finished. Callbacks making further changes
+	// should use the non-blocking SetStateSub/SetFieldSub functions. The tree of events
+	// resulting from the initial changes is traversed in a breadth-first order, ensuring for
+	// each subscription callback that all other callbacks caused by the same change triggering
+	// the current callback are processed before anything is triggered by the changes made in the
+	// current callback. In practice this logic ensures that all subscriptions "see" events in
+	// the logical order, callbacks are never called concurrently and "back and forth" effects
+	// are also possible. The state machine design should ensure that infinite event cycles
+	// cannot happen.
+	// The caller can also add timeouts assigned to a certain node and a subset of state flags.
+	// If the timeout elapses, the flags are reset. If all relevant flags are reset then the timer
+	// is dropped. State flags with no timeout are persisted in the database if the flag
+	// descriptor enables saving. If a node has no state flags set at any moment then it is discarded.
+	// Note: in order to avoid mutex deadlocks the callbacks should never lock a mutex that
+	// might be locked when the top level SetState/SetField functions are called. If a function
+	// potentially performs state/field changes then it is recommended to mention this fact in the
+	// function description, along with whether it should run inside an operation callback.
+	NodeStateMachine struct {
+		started, closed     bool
+		lock                sync.Mutex
+		clock               mclock.Clock
+		db                  ethdb.KeyValueStore
+		dbNodeKey           []byte
+		nodes               map[enode.ID]*nodeInfo
+		offlineCallbackList []offlineCallback
+		opFlag              bool       // an operation has started
+		opWait              *sync.Cond // signaled when the operation ends
+		opPending           []func()   // pending callback list of the current operation
+
+		// Registered state flags or fields. Modifications are allowed
+		// only when the node state machine has not been started.
+		setup     *Setup
+		fields    []*fieldInfo
+		saveFlags bitMask
+
+		// Installed callbacks. Modifications are allowed only when the
+		// node state machine has not been started.
+		stateSubs []stateSub
+
+		// Testing hooks, only for testing purposes.
+		saveNodeHook func(*nodeInfo)
+	}
+
+	// Flags represents a set of flags from a certain setup
+	Flags struct {
+		mask  bitMask
+		setup *Setup
+	}
+
+	// Field represents a field from a certain setup
+	Field struct {
+		index int
+		setup *Setup
+	}
+
+	// flagDefinition describes a node state flag. Each registered instance is automatically
+	// mapped to a bit of the 64 bit node states.
+	// If persistent is true then the node is saved when state machine is shutdown.
+	flagDefinition struct {
+		name       string
+		persistent bool
+	}
+
+	// fieldDefinition describes an optional node field of the given type. The contents
+	// of the field are only retained for each node as long as at least one of the
+	// state flags is set.
+	fieldDefinition struct {
+		name   string
+		ftype  reflect.Type
+		encode func(interface{}) ([]byte, error)
+		decode func([]byte) (interface{}, error)
+	}
+
+	// stateSetup contains the list of flags and fields used by the application
+	Setup struct {
+		Version uint
+		flags   []flagDefinition
+		fields  []fieldDefinition
+	}
+
+	// bitMask describes a node state or state mask. It represents a subset
+	// of node flags with each bit assigned to a flag index (LSB represents flag 0).
+	bitMask uint64
+
+	// StateCallback is a subscription callback which is called when one of the
+	// state flags that is included in the subscription state mask is changed.
+	// Note: oldState and newState are also masked with the subscription mask so only
+	// the relevant bits are included.
+	StateCallback func(n *enode.Node, oldState, newState Flags)
+
+	// FieldCallback is a subscription callback which is called when the value of
+	// a specific field is changed.
+	FieldCallback func(n *enode.Node, state Flags, oldValue, newValue interface{})
+
+	// nodeInfo contains node state, fields and state timeouts
+	nodeInfo struct {
+		node       *enode.Node
+		state      bitMask
+		timeouts   []*nodeStateTimeout
+		fields     []interface{}
+		fieldCount int
+		db, dirty  bool
+	}
+
+	nodeInfoEnc struct {
+		Enr     enr.Record
+		Version uint
+		State   bitMask
+		Fields  [][]byte
+	}
+
+	stateSub struct {
+		mask     bitMask
+		callback StateCallback
+	}
+
+	nodeStateTimeout struct {
+		mask  bitMask
+		timer mclock.Timer
+	}
+
+	fieldInfo struct {
+		fieldDefinition
+		subs []FieldCallback
+	}
+
+	offlineCallback struct {
+		node   *nodeInfo
+		state  bitMask
+		fields []interface{}
+	}
+)
+
+// offlineState is a special state that is assumed to be set before a node is loaded from
+// the database and after it is shut down.
+const offlineState = bitMask(1)
+
+// NewFlag creates a new node state flag
+func (s *Setup) NewFlag(name string) Flags {
+	if s.flags == nil {
+		s.flags = []flagDefinition{{name: "offline"}}
+	}
+	f := Flags{mask: bitMask(1) << uint(len(s.flags)), setup: s}
+	s.flags = append(s.flags, flagDefinition{name: name})
+	return f
+}
+
+// NewPersistentFlag creates a new persistent node state flag
+func (s *Setup) NewPersistentFlag(name string) Flags {
+	if s.flags == nil {
+		s.flags = []flagDefinition{{name: "offline"}}
+	}
+	f := Flags{mask: bitMask(1) << uint(len(s.flags)), setup: s}
+	s.flags = append(s.flags, flagDefinition{name: name, persistent: true})
+	return f
+}
+
+// OfflineFlag returns the system-defined offline flag belonging to the given setup
+func (s *Setup) OfflineFlag() Flags {
+	return Flags{mask: offlineState, setup: s}
+}
+
+// NewField creates a new node state field
+func (s *Setup) NewField(name string, ftype reflect.Type) Field {
+	f := Field{index: len(s.fields), setup: s}
+	s.fields = append(s.fields, fieldDefinition{
+		name:  name,
+		ftype: ftype,
+	})
+	return f
+}
+
+// NewPersistentField creates a new persistent node field
+func (s *Setup) NewPersistentField(name string, ftype reflect.Type, encode func(interface{}) ([]byte, error), decode func([]byte) (interface{}, error)) Field {
+	f := Field{index: len(s.fields), setup: s}
+	s.fields = append(s.fields, fieldDefinition{
+		name:   name,
+		ftype:  ftype,
+		encode: encode,
+		decode: decode,
+	})
+	return f
+}
+
+// flagOp implements binary flag operations and also checks whether the operands belong to the same setup
+func flagOp(a, b Flags, trueIfA, trueIfB, trueIfBoth bool) Flags {
+	if a.setup == nil {
+		if a.mask != 0 {
+			panic("Node state flags have no setup reference")
+		}
+		a.setup = b.setup
+	}
+	if b.setup == nil {
+		if b.mask != 0 {
+			panic("Node state flags have no setup reference")
+		}
+		b.setup = a.setup
+	}
+	if a.setup != b.setup {
+		panic("Node state flags belong to a different setup")
+	}
+	res := Flags{setup: a.setup}
+	if trueIfA {
+		res.mask |= a.mask & ^b.mask
+	}
+	if trueIfB {
+		res.mask |= b.mask & ^a.mask
+	}
+	if trueIfBoth {
+		res.mask |= a.mask & b.mask
+	}
+	return res
+}
+
+// And returns the set of flags present in both a and b
+func (a Flags) And(b Flags) Flags { return flagOp(a, b, false, false, true) }
+
+// AndNot returns the set of flags present in a but not in b
+func (a Flags) AndNot(b Flags) Flags { return flagOp(a, b, true, false, false) }
+
+// Or returns the set of flags present in either a or b
+func (a Flags) Or(b Flags) Flags { return flagOp(a, b, true, true, true) }
+
+// Xor returns the set of flags present in either a or b but not both
+func (a Flags) Xor(b Flags) Flags { return flagOp(a, b, true, true, false) }
+
+// HasAll returns true if b is a subset of a
+func (a Flags) HasAll(b Flags) bool { return flagOp(a, b, false, true, false).mask == 0 }
+
+// HasNone returns true if a and b have no shared flags
+func (a Flags) HasNone(b Flags) bool { return flagOp(a, b, false, false, true).mask == 0 }
+
+// Equals returns true if a and b have the same flags set
+func (a Flags) Equals(b Flags) bool { return flagOp(a, b, true, true, false).mask == 0 }
+
+// IsEmpty returns true if a has no flags set
+func (a Flags) IsEmpty() bool { return a.mask == 0 }
+
+// MergeFlags merges multiple sets of state flags
+func MergeFlags(list ...Flags) Flags {
+	if len(list) == 0 {
+		return Flags{}
+	}
+	res := list[0]
+	for i := 1; i < len(list); i++ {
+		res = res.Or(list[i])
+	}
+	return res
+}
+
+// String returns a list of the names of the flags specified in the bit mask
+func (f Flags) String() string {
+	if f.mask == 0 {
+		return "[]"
+	}
+	s := "["
+	comma := false
+	for index, flag := range f.setup.flags {
+		if f.mask&(bitMask(1)< 8*int(unsafe.Sizeof(bitMask(0))) {
+		panic("Too many node state flags")
+	}
+	ns := &NodeStateMachine{
+		db:        db,
+		dbNodeKey: dbKey,
+		clock:     clock,
+		setup:     setup,
+		nodes:     make(map[enode.ID]*nodeInfo),
+		fields:    make([]*fieldInfo, len(setup.fields)),
+	}
+	ns.opWait = sync.NewCond(&ns.lock)
+	stateNameMap := make(map[string]int)
+	for index, flag := range setup.flags {
+		if _, ok := stateNameMap[flag.name]; ok {
+			panic("Node state flag name collision: " + flag.name)
+		}
+		stateNameMap[flag.name] = index
+		if flag.persistent {
+			ns.saveFlags |= bitMask(1) << uint(index)
+		}
+	}
+	fieldNameMap := make(map[string]int)
+	for index, field := range setup.fields {
+		if _, ok := fieldNameMap[field.name]; ok {
+			panic("Node field name collision: " + field.name)
+		}
+		ns.fields[index] = &fieldInfo{fieldDefinition: field}
+		fieldNameMap[field.name] = index
+	}
+	return ns
+}
+
+// stateMask checks whether the set of flags belongs to the same setup and returns its internal bit mask
+func (ns *NodeStateMachine) stateMask(flags Flags) bitMask {
+	if flags.setup != ns.setup && flags.mask != 0 {
+		panic("Node state flags belong to a different setup")
+	}
+	return flags.mask
+}
+
+// fieldIndex checks whether the field belongs to the same setup and returns its internal index
+func (ns *NodeStateMachine) fieldIndex(field Field) int {
+	if field.setup != ns.setup {
+		panic("Node field belongs to a different setup")
+	}
+	return field.index
+}
+
+// SubscribeState adds a node state subscription. The callback is called while the state
+// machine mutex is not held and it is allowed to make further state updates using the
+// non-blocking SetStateSub/SetFieldSub functions. All callbacks of an operation are running
+// from the thread/goroutine of the initial caller and parallel operations are not permitted.
+// Therefore the callback is never called concurrently. It is the responsibility of the
+// implemented state logic to avoid deadlocks and to reach a stable state in a finite amount
+// of steps.
+// State subscriptions should be installed before loading the node database or making the
+// first state update.
+func (ns *NodeStateMachine) SubscribeState(flags Flags, callback StateCallback) {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	if ns.started {
+		panic("state machine already started")
+	}
+	ns.stateSubs = append(ns.stateSubs, stateSub{ns.stateMask(flags), callback})
+}
+
+// SubscribeField adds a node field subscription. Same rules apply as for SubscribeState.
+func (ns *NodeStateMachine) SubscribeField(field Field, callback FieldCallback) {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	if ns.started {
+		panic("state machine already started")
+	}
+	f := ns.fields[ns.fieldIndex(field)]
+	f.subs = append(f.subs, callback)
+}
+
+// newNode creates a new nodeInfo
+func (ns *NodeStateMachine) newNode(n *enode.Node) *nodeInfo {
+	return &nodeInfo{node: n, fields: make([]interface{}, len(ns.fields))}
+}
+
+// checkStarted checks whether the state machine has already been started and panics otherwise.
+func (ns *NodeStateMachine) checkStarted() {
+	if !ns.started {
+		panic("state machine not started yet")
+	}
+}
+
+// Start starts the state machine, enabling state and field operations and disabling
+// further subscriptions.
+func (ns *NodeStateMachine) Start() {
+	ns.lock.Lock()
+	if ns.started {
+		panic("state machine already started")
+	}
+	ns.started = true
+	if ns.db != nil {
+		ns.loadFromDb()
+	}
+
+	ns.opStart()
+	ns.offlineCallbacks(true)
+	ns.opFinish()
+	ns.lock.Unlock()
+}
+
+// Stop stops the state machine and saves its state if a database was supplied
+func (ns *NodeStateMachine) Stop() {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.checkStarted()
+	if !ns.opStart() {
+		panic("already closed")
+	}
+	for _, node := range ns.nodes {
+		fields := make([]interface{}, len(node.fields))
+		copy(fields, node.fields)
+		ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields})
+	}
+	if ns.db != nil {
+		ns.saveToDb()
+	}
+	ns.offlineCallbacks(false)
+	ns.closed = true
+	ns.opFinish()
+}
+
+// loadFromDb loads persisted node states from the database
+func (ns *NodeStateMachine) loadFromDb() {
+	it := ns.db.NewIterator(ns.dbNodeKey, nil)
+	for it.Next() {
+		var id enode.ID
+		if len(it.Key()) != len(ns.dbNodeKey)+len(id) {
+			log.Error("Node state db entry with invalid length", "found", len(it.Key()), "expected", len(ns.dbNodeKey)+len(id))
+			continue
+		}
+		copy(id[:], it.Key()[len(ns.dbNodeKey):])
+		ns.decodeNode(id, it.Value())
+	}
+}
+
+type dummyIdentity enode.ID
+
+func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil }
+func (id dummyIdentity) NodeAddr(r *enr.Record) []byte          { return id[:] }
+
+// decodeNode decodes a node database entry and adds it to the node set if successful
+func (ns *NodeStateMachine) decodeNode(id enode.ID, data []byte) {
+	var enc nodeInfoEnc
+	if err := rlp.DecodeBytes(data, &enc); err != nil {
+		log.Error("Failed to decode node info", "id", id, "error", err)
+		return
+	}
+	n, _ := enode.New(dummyIdentity(id), &enc.Enr)
+	node := ns.newNode(n)
+	node.db = true
+
+	if enc.Version != ns.setup.Version {
+		log.Debug("Removing stored node with unknown version", "current", ns.setup.Version, "stored", enc.Version)
+		ns.deleteNode(id)
+		return
+	}
+	if len(enc.Fields) > len(ns.setup.fields) {
+		log.Error("Invalid node field count", "id", id, "stored", len(enc.Fields))
+		return
+	}
+	// Resolve persisted node fields
+	for i, encField := range enc.Fields {
+		if len(encField) == 0 {
+			continue
+		}
+		if decode := ns.fields[i].decode; decode != nil {
+			if field, err := decode(encField); err == nil {
+				node.fields[i] = field
+				node.fieldCount++
+			} else {
+				log.Error("Failed to decode node field", "id", id, "field name", ns.fields[i].name, "error", err)
+				return
+			}
+		} else {
+			log.Error("Cannot decode node field", "id", id, "field name", ns.fields[i].name)
+			return
+		}
+	}
+	// It's a compatible node record, add it to set.
+	ns.nodes[id] = node
+	node.state = enc.State
+	fields := make([]interface{}, len(node.fields))
+	copy(fields, node.fields)
+	ns.offlineCallbackList = append(ns.offlineCallbackList, offlineCallback{node, node.state, fields})
+	log.Debug("Loaded node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup})
+}
+
+// saveNode saves the given node info to the database
+func (ns *NodeStateMachine) saveNode(id enode.ID, node *nodeInfo) error {
+	if ns.db == nil {
+		return nil
+	}
+
+	storedState := node.state & ns.saveFlags
+	for _, t := range node.timeouts {
+		storedState &= ^t.mask
+	}
+	enc := nodeInfoEnc{
+		Enr:     *node.node.Record(),
+		Version: ns.setup.Version,
+		State:   storedState,
+		Fields:  make([][]byte, len(ns.fields)),
+	}
+	log.Debug("Saved node state", "id", id, "state", Flags{mask: enc.State, setup: ns.setup})
+	lastIndex := -1
+	for i, f := range node.fields {
+		if f == nil {
+			continue
+		}
+		encode := ns.fields[i].encode
+		if encode == nil {
+			continue
+		}
+		blob, err := encode(f)
+		if err != nil {
+			return err
+		}
+		enc.Fields[i] = blob
+		lastIndex = i
+	}
+	if storedState == 0 && lastIndex == -1 {
+		if node.db {
+			node.db = false
+			ns.deleteNode(id)
+		}
+		node.dirty = false
+		return nil
+	}
+	enc.Fields = enc.Fields[:lastIndex+1]
+	data, err := rlp.EncodeToBytes(&enc)
+	if err != nil {
+		return err
+	}
+	if err := ns.db.Put(append(ns.dbNodeKey, id[:]...), data); err != nil {
+		return err
+	}
+	node.dirty, node.db = false, true
+
+	if ns.saveNodeHook != nil {
+		ns.saveNodeHook(node)
+	}
+	return nil
+}
+
+// deleteNode removes a node info from the database
+func (ns *NodeStateMachine) deleteNode(id enode.ID) {
+	ns.db.Delete(append(ns.dbNodeKey, id[:]...))
+}
+
+// saveToDb saves the persistent flags and fields of all nodes that have been changed
+func (ns *NodeStateMachine) saveToDb() {
+	for id, node := range ns.nodes {
+		if node.dirty {
+			err := ns.saveNode(id, node)
+			if err != nil {
+				log.Error("Failed to save node", "id", id, "error", err)
+			}
+		}
+	}
+}
+
+// updateEnode updates the enode entry belonging to the given node if it already exists
+func (ns *NodeStateMachine) updateEnode(n *enode.Node) (enode.ID, *nodeInfo) {
+	id := n.ID()
+	node := ns.nodes[id]
+	if node != nil && n.Seq() > node.node.Seq() {
+		node.node = n
+		node.dirty = true
+	}
+	return id, node
+}
+
+// Persist saves the persistent state and fields of the given node immediately
+func (ns *NodeStateMachine) Persist(n *enode.Node) error {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.checkStarted()
+	if id, node := ns.updateEnode(n); node != nil && node.dirty {
+		err := ns.saveNode(id, node)
+		if err != nil {
+			log.Error("Failed to save node", "id", id, "error", err)
+		}
+		return err
+	}
+	return nil
+}
+
+// SetState updates the given node state flags and blocks until the operation is finished.
+// If a flag with a timeout is set again, the operation removes or replaces the existing timeout.
+func (ns *NodeStateMachine) SetState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) error {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	if !ns.opStart() {
+		return ErrClosed
+	}
+	ns.setState(n, setFlags, resetFlags, timeout)
+	ns.opFinish()
+	return nil
+}
+
+// SetStateSub updates the given node state flags without blocking (should be called
+// from a subscription/operation callback).
+func (ns *NodeStateMachine) SetStateSub(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.opCheck()
+	ns.setState(n, setFlags, resetFlags, timeout)
+}
+
+func (ns *NodeStateMachine) setState(n *enode.Node, setFlags, resetFlags Flags, timeout time.Duration) {
+	ns.checkStarted()
+	set, reset := ns.stateMask(setFlags), ns.stateMask(resetFlags)
+	id, node := ns.updateEnode(n)
+	if node == nil {
+		if set == 0 {
+			return
+		}
+		node = ns.newNode(n)
+		ns.nodes[id] = node
+	}
+	oldState := node.state
+	newState := (node.state & (^reset)) | set
+	changed := oldState ^ newState
+	node.state = newState
+
+	// Remove the timeout callbacks for all reset and set flags,
+	// even they are not existent(it's noop).
+	ns.removeTimeouts(node, set|reset)
+
+	// Register the timeout callback if required
+	if timeout != 0 && set != 0 {
+		ns.addTimeout(n, set, timeout)
+	}
+	if newState == oldState {
+		return
+	}
+	if newState == 0 && node.fieldCount == 0 {
+		delete(ns.nodes, id)
+		if node.db {
+			ns.deleteNode(id)
+		}
+	} else {
+		if changed&ns.saveFlags != 0 {
+			node.dirty = true
+		}
+	}
+	callback := func() {
+		for _, sub := range ns.stateSubs {
+			if changed&sub.mask != 0 {
+				sub.callback(n, Flags{mask: oldState & sub.mask, setup: ns.setup}, Flags{mask: newState & sub.mask, setup: ns.setup})
+			}
+		}
+	}
+	ns.opPending = append(ns.opPending, callback)
+}
+
+// opCheck checks whether an operation is active
+func (ns *NodeStateMachine) opCheck() {
+	if !ns.opFlag {
+		panic("Operation has not started")
+	}
+}
+
+// opStart waits until other operations are finished and starts a new one
+func (ns *NodeStateMachine) opStart() bool {
+	for ns.opFlag {
+		ns.opWait.Wait()
+	}
+	if ns.closed {
+		return false
+	}
+	ns.opFlag = true
+	return true
+}
+
+// opFinish finishes the current operation by running all pending callbacks.
+// Callbacks resulting from a state/field change performed in a previous callback are always
+// put at the end of the pending list and therefore processed after all callbacks resulting
+// from the previous state/field change.
+func (ns *NodeStateMachine) opFinish() {
+	for len(ns.opPending) != 0 {
+		list := ns.opPending
+		ns.lock.Unlock()
+		for _, cb := range list {
+			cb()
+		}
+		ns.lock.Lock()
+		ns.opPending = ns.opPending[len(list):]
+	}
+	ns.opPending = nil
+	ns.opFlag = false
+	ns.opWait.Broadcast()
+}
+
+// Operation calls the given function as an operation callback. This allows the caller
+// to start an operation with multiple initial changes. The same rules apply as for
+// subscription callbacks.
+func (ns *NodeStateMachine) Operation(fn func()) error {
+	ns.lock.Lock()
+	started := ns.opStart()
+	ns.lock.Unlock()
+	if !started {
+		return ErrClosed
+	}
+	fn()
+	ns.lock.Lock()
+	ns.opFinish()
+	ns.lock.Unlock()
+	return nil
+}
+
+// offlineCallbacks calls state update callbacks at startup or shutdown
+func (ns *NodeStateMachine) offlineCallbacks(start bool) {
+	for _, cb := range ns.offlineCallbackList {
+		cb := cb
+		callback := func() {
+			for _, sub := range ns.stateSubs {
+				offState := offlineState & sub.mask
+				onState := cb.state & sub.mask
+				if offState == onState {
+					continue
+				}
+				if start {
+					sub.callback(cb.node.node, Flags{mask: offState, setup: ns.setup}, Flags{mask: onState, setup: ns.setup})
+				} else {
+					sub.callback(cb.node.node, Flags{mask: onState, setup: ns.setup}, Flags{mask: offState, setup: ns.setup})
+				}
+			}
+			for i, f := range cb.fields {
+				if f == nil || ns.fields[i].subs == nil {
+					continue
+				}
+				for _, fsub := range ns.fields[i].subs {
+					if start {
+						fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, nil, f)
+					} else {
+						fsub(cb.node.node, Flags{mask: offlineState, setup: ns.setup}, f, nil)
+					}
+				}
+			}
+		}
+		ns.opPending = append(ns.opPending, callback)
+	}
+	ns.offlineCallbackList = nil
+}
+
+// AddTimeout adds a node state timeout associated to the given state flag(s).
+// After the specified time interval, the relevant states will be reset.
+func (ns *NodeStateMachine) AddTimeout(n *enode.Node, flags Flags, timeout time.Duration) error {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.checkStarted()
+	if ns.closed {
+		return ErrClosed
+	}
+	ns.addTimeout(n, ns.stateMask(flags), timeout)
+	return nil
+}
+
+// addTimeout adds a node state timeout associated to the given state flag(s).
+func (ns *NodeStateMachine) addTimeout(n *enode.Node, mask bitMask, timeout time.Duration) {
+	_, node := ns.updateEnode(n)
+	if node == nil {
+		return
+	}
+	mask &= node.state
+	if mask == 0 {
+		return
+	}
+	ns.removeTimeouts(node, mask)
+	t := &nodeStateTimeout{mask: mask}
+	t.timer = ns.clock.AfterFunc(timeout, func() {
+		ns.SetState(n, Flags{}, Flags{mask: t.mask, setup: ns.setup}, 0)
+	})
+	node.timeouts = append(node.timeouts, t)
+	if mask&ns.saveFlags != 0 {
+		node.dirty = true
+	}
+}
+
+// removeTimeout removes node state timeouts associated to the given state flag(s).
+// If a timeout was associated to multiple flags which are not all included in the
+// specified remove mask then only the included flags are de-associated and the timer
+// stays active.
+func (ns *NodeStateMachine) removeTimeouts(node *nodeInfo, mask bitMask) {
+	for i := 0; i < len(node.timeouts); i++ {
+		t := node.timeouts[i]
+		match := t.mask & mask
+		if match == 0 {
+			continue
+		}
+		t.mask -= match
+		if t.mask != 0 {
+			continue
+		}
+		t.timer.Stop()
+		node.timeouts[i] = node.timeouts[len(node.timeouts)-1]
+		node.timeouts = node.timeouts[:len(node.timeouts)-1]
+		i--
+		if match&ns.saveFlags != 0 {
+			node.dirty = true
+		}
+	}
+}
+
+// GetField retrieves the given field of the given node. Note that when used in a
+// subscription callback the result can be out of sync with the state change represented
+// by the callback parameters so extra safety checks might be necessary.
+func (ns *NodeStateMachine) GetField(n *enode.Node, field Field) interface{} {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.checkStarted()
+	if ns.closed {
+		return nil
+	}
+	if _, node := ns.updateEnode(n); node != nil {
+		return node.fields[ns.fieldIndex(field)]
+	}
+	return nil
+}
+
+// GetState retrieves the current state of the given node. Note that when used in a
+// subscription callback the result can be out of sync with the state change represented
+// by the callback parameters so extra safety checks might be necessary.
+func (ns *NodeStateMachine) GetState(n *enode.Node) Flags {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.checkStarted()
+	if ns.closed {
+		return Flags{}
+	}
+	if _, node := ns.updateEnode(n); node != nil {
+		return Flags{mask: node.state, setup: ns.setup}
+	}
+	return Flags{}
+}
+
+// SetField sets the given field of the given node and blocks until the operation is finished
+func (ns *NodeStateMachine) SetField(n *enode.Node, field Field, value interface{}) error {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	if !ns.opStart() {
+		return ErrClosed
+	}
+	err := ns.setField(n, field, value)
+	ns.opFinish()
+	return err
+}
+
+// SetFieldSub sets the given field of the given node without blocking (should be called
+// from a subscription/operation callback).
+func (ns *NodeStateMachine) SetFieldSub(n *enode.Node, field Field, value interface{}) error {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.opCheck()
+	return ns.setField(n, field, value)
+}
+
+func (ns *NodeStateMachine) setField(n *enode.Node, field Field, value interface{}) error {
+	ns.checkStarted()
+	id, node := ns.updateEnode(n)
+	if node == nil {
+		if value == nil {
+			return nil
+		}
+		node = ns.newNode(n)
+		ns.nodes[id] = node
+	}
+	fieldIndex := ns.fieldIndex(field)
+	f := ns.fields[fieldIndex]
+	if value != nil && reflect.TypeOf(value) != f.ftype {
+		log.Error("Invalid field type", "type", reflect.TypeOf(value), "required", f.ftype)
+		return ErrInvalidField
+	}
+	oldValue := node.fields[fieldIndex]
+	if value == oldValue {
+		return nil
+	}
+	if oldValue != nil {
+		node.fieldCount--
+	}
+	if value != nil {
+		node.fieldCount++
+	}
+	node.fields[fieldIndex] = value
+	if node.state == 0 && node.fieldCount == 0 {
+		delete(ns.nodes, id)
+		if node.db {
+			ns.deleteNode(id)
+		}
+	} else {
+		if f.encode != nil {
+			node.dirty = true
+		}
+	}
+	state := node.state
+	callback := func() {
+		for _, cb := range f.subs {
+			cb(n, Flags{mask: state, setup: ns.setup}, oldValue, value)
+		}
+	}
+	ns.opPending = append(ns.opPending, callback)
+	return nil
+}
+
+// ForEach calls the callback for each node having all of the required and none of the
+// disabled flags set.
+// Note that this callback is not an operation callback but ForEach can be called from an
+// Operation callback or Operation can also be called from a ForEach callback if necessary.
+func (ns *NodeStateMachine) ForEach(requireFlags, disableFlags Flags, cb func(n *enode.Node, state Flags)) {
+	ns.lock.Lock()
+	ns.checkStarted()
+	type callback struct {
+		node  *enode.Node
+		state bitMask
+	}
+	require, disable := ns.stateMask(requireFlags), ns.stateMask(disableFlags)
+	var callbacks []callback
+	for _, node := range ns.nodes {
+		if node.state&require == require && node.state&disable == 0 {
+			callbacks = append(callbacks, callback{node.node, node.state & (require | disable)})
+		}
+	}
+	ns.lock.Unlock()
+	for _, c := range callbacks {
+		cb(c.node, Flags{mask: c.state, setup: ns.setup})
+	}
+}
+
+// GetNode returns the enode currently associated with the given ID
+func (ns *NodeStateMachine) GetNode(id enode.ID) *enode.Node {
+	ns.lock.Lock()
+	defer ns.lock.Unlock()
+
+	ns.checkStarted()
+	if node := ns.nodes[id]; node != nil {
+		return node.node
+	}
+	return nil
+}
+
+// AddLogMetrics adds logging and/or metrics for nodes entering, exiting and currently
+// being in a given set specified by required and disabled state flags
+func (ns *NodeStateMachine) AddLogMetrics(requireFlags, disableFlags Flags, name string, inMeter, outMeter metrics.Meter, gauge metrics.Gauge) {
+	var count int64
+	ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState Flags) {
+		oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags)
+		newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags)
+		if newMatch == oldMatch {
+			return
+		}
+
+		if newMatch {
+			count++
+			if name != "" {
+				log.Debug("Node entered", "set", name, "id", n.ID(), "count", count)
+			}
+			if inMeter != nil {
+				inMeter.Mark(1)
+			}
+		} else {
+			count--
+			if name != "" {
+				log.Debug("Node left", "set", name, "id", n.ID(), "count", count)
+			}
+			if outMeter != nil {
+				outMeter.Mark(1)
+			}
+		}
+		if gauge != nil {
+			gauge.Update(count)
+		}
+	})
+}
diff --git a/p2p/nodestate/nodestate_test.go b/p2p/nodestate/nodestate_test.go
new file mode 100644
index 0000000000..d06ad755e2
--- /dev/null
+++ b/p2p/nodestate/nodestate_test.go
@@ -0,0 +1,407 @@
+// 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 .
+
+package nodestate
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+func testSetup(flagPersist []bool, fieldType []reflect.Type) (*Setup, []Flags, []Field) {
+	setup := &Setup{}
+	flags := make([]Flags, len(flagPersist))
+	for i, persist := range flagPersist {
+		if persist {
+			flags[i] = setup.NewPersistentFlag(fmt.Sprintf("flag-%d", i))
+		} else {
+			flags[i] = setup.NewFlag(fmt.Sprintf("flag-%d", i))
+		}
+	}
+	fields := make([]Field, len(fieldType))
+	for i, ftype := range fieldType {
+		switch ftype {
+		case reflect.TypeOf(uint64(0)):
+			fields[i] = setup.NewPersistentField(fmt.Sprintf("field-%d", i), ftype, uint64FieldEnc, uint64FieldDec)
+		case reflect.TypeOf(""):
+			fields[i] = setup.NewPersistentField(fmt.Sprintf("field-%d", i), ftype, stringFieldEnc, stringFieldDec)
+		default:
+			fields[i] = setup.NewField(fmt.Sprintf("field-%d", i), ftype)
+		}
+	}
+	return setup, flags, fields
+}
+
+func testNode(b byte) *enode.Node {
+	r := &enr.Record{}
+	r.SetSig(dummyIdentity{b}, []byte{42})
+	n, _ := enode.New(dummyIdentity{b}, r)
+	return n
+}
+
+func TestCallback(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, _ := testSetup([]bool{false, false, false}, nil)
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	set0 := make(chan struct{}, 1)
+	set1 := make(chan struct{}, 1)
+	set2 := make(chan struct{}, 1)
+	ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) { set0 <- struct{}{} })
+	ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) { set1 <- struct{}{} })
+	ns.SubscribeState(flags[2], func(n *enode.Node, oldState, newState Flags) { set2 <- struct{}{} })
+
+	ns.Start()
+
+	ns.SetState(testNode(1), flags[0], Flags{}, 0)
+	ns.SetState(testNode(1), flags[1], Flags{}, time.Second)
+	ns.SetState(testNode(1), flags[2], Flags{}, 2*time.Second)
+
+	for i := 0; i < 3; i++ {
+		select {
+		case <-set0:
+		case <-set1:
+		case <-set2:
+		case <-time.After(time.Second):
+			t.Fatalf("failed to invoke callback")
+		}
+	}
+}
+
+func TestPersistentFlags(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, _ := testSetup([]bool{true, true, true, false}, nil)
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	saveNode := make(chan *nodeInfo, 5)
+	ns.saveNodeHook = func(node *nodeInfo) {
+		saveNode <- node
+	}
+
+	ns.Start()
+
+	ns.SetState(testNode(1), flags[0], Flags{}, time.Second) // state with timeout should not be saved
+	ns.SetState(testNode(2), flags[1], Flags{}, 0)
+	ns.SetState(testNode(3), flags[2], Flags{}, 0)
+	ns.SetState(testNode(4), flags[3], Flags{}, 0)
+	ns.SetState(testNode(5), flags[0], Flags{}, 0)
+	ns.Persist(testNode(5))
+	select {
+	case <-saveNode:
+	case <-time.After(time.Second):
+		t.Fatalf("Timeout")
+	}
+	ns.Stop()
+
+	for i := 0; i < 2; i++ {
+		select {
+		case <-saveNode:
+		case <-time.After(time.Second):
+			t.Fatalf("Timeout")
+		}
+	}
+	select {
+	case <-saveNode:
+		t.Fatalf("Unexpected saveNode")
+	case <-time.After(time.Millisecond * 100):
+	}
+}
+
+func TestSetField(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf("")})
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	saveNode := make(chan *nodeInfo, 1)
+	ns.saveNodeHook = func(node *nodeInfo) {
+		saveNode <- node
+	}
+
+	ns.Start()
+
+	// Set field before setting state
+	ns.SetField(testNode(1), fields[0], "hello world")
+	field := ns.GetField(testNode(1), fields[0])
+	if field == nil {
+		t.Fatalf("Field should be set before setting states")
+	}
+	ns.SetField(testNode(1), fields[0], nil)
+	field = ns.GetField(testNode(1), fields[0])
+	if field != nil {
+		t.Fatalf("Field should be unset")
+	}
+	// Set field after setting state
+	ns.SetState(testNode(1), flags[0], Flags{}, 0)
+	ns.SetField(testNode(1), fields[0], "hello world")
+	field = ns.GetField(testNode(1), fields[0])
+	if field == nil {
+		t.Fatalf("Field should be set after setting states")
+	}
+	if err := ns.SetField(testNode(1), fields[0], 123); err == nil {
+		t.Fatalf("Invalid field should be rejected")
+	}
+	// Dirty node should be written back
+	ns.Stop()
+	select {
+	case <-saveNode:
+	case <-time.After(time.Second):
+		t.Fatalf("Timeout")
+	}
+}
+
+func TestSetState(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, _ := testSetup([]bool{false, false, false}, nil)
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	type change struct{ old, new Flags }
+	set := make(chan change, 1)
+	ns.SubscribeState(flags[0].Or(flags[1]), func(n *enode.Node, oldState, newState Flags) {
+		set <- change{
+			old: oldState,
+			new: newState,
+		}
+	})
+
+	ns.Start()
+
+	check := func(expectOld, expectNew Flags, expectChange bool) {
+		if expectChange {
+			select {
+			case c := <-set:
+				if !c.old.Equals(expectOld) {
+					t.Fatalf("Old state mismatch")
+				}
+				if !c.new.Equals(expectNew) {
+					t.Fatalf("New state mismatch")
+				}
+			case <-time.After(time.Second):
+			}
+			return
+		}
+		select {
+		case <-set:
+			t.Fatalf("Unexpected change")
+		case <-time.After(time.Millisecond * 100):
+			return
+		}
+	}
+	ns.SetState(testNode(1), flags[0], Flags{}, 0)
+	check(Flags{}, flags[0], true)
+
+	ns.SetState(testNode(1), flags[1], Flags{}, 0)
+	check(flags[0], flags[0].Or(flags[1]), true)
+
+	ns.SetState(testNode(1), flags[2], Flags{}, 0)
+	check(Flags{}, Flags{}, false)
+
+	ns.SetState(testNode(1), Flags{}, flags[0], 0)
+	check(flags[0].Or(flags[1]), flags[1], true)
+
+	ns.SetState(testNode(1), Flags{}, flags[1], 0)
+	check(flags[1], Flags{}, true)
+
+	ns.SetState(testNode(1), Flags{}, flags[2], 0)
+	check(Flags{}, Flags{}, false)
+
+	ns.SetState(testNode(1), flags[0].Or(flags[1]), Flags{}, time.Second)
+	check(Flags{}, flags[0].Or(flags[1]), true)
+	clock.Run(time.Second)
+	check(flags[0].Or(flags[1]), Flags{}, true)
+}
+
+func uint64FieldEnc(field interface{}) ([]byte, error) {
+	if u, ok := field.(uint64); ok {
+		enc, err := rlp.EncodeToBytes(&u)
+		return enc, err
+	}
+	return nil, errors.New("invalid field type")
+}
+
+func uint64FieldDec(enc []byte) (interface{}, error) {
+	var u uint64
+	err := rlp.DecodeBytes(enc, &u)
+	return u, err
+}
+
+func stringFieldEnc(field interface{}) ([]byte, error) {
+	if s, ok := field.(string); ok {
+		return []byte(s), nil
+	}
+	return nil, errors.New("invalid field type")
+}
+
+func stringFieldDec(enc []byte) (interface{}, error) {
+	return string(enc), nil
+}
+
+func TestPersistentFields(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf(uint64(0)), reflect.TypeOf("")})
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	ns.Start()
+	ns.SetState(testNode(1), flags[0], Flags{}, 0)
+	ns.SetField(testNode(1), fields[0], uint64(100))
+	ns.SetField(testNode(1), fields[1], "hello world")
+	ns.Stop()
+
+	ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	ns2.Start()
+	field0 := ns2.GetField(testNode(1), fields[0])
+	if !reflect.DeepEqual(field0, uint64(100)) {
+		t.Fatalf("Field changed")
+	}
+	field1 := ns2.GetField(testNode(1), fields[1])
+	if !reflect.DeepEqual(field1, "hello world") {
+		t.Fatalf("Field changed")
+	}
+
+	s.Version++
+	ns3 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+	ns3.Start()
+	if ns3.GetField(testNode(1), fields[0]) != nil {
+		t.Fatalf("Old field version should have been discarded")
+	}
+}
+
+func TestFieldSub(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, fields := testSetup([]bool{true}, []reflect.Type{reflect.TypeOf(uint64(0))})
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	var (
+		lastState                  Flags
+		lastOldValue, lastNewValue interface{}
+	)
+	ns.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) {
+		lastState, lastOldValue, lastNewValue = state, oldValue, newValue
+	})
+	check := func(state Flags, oldValue, newValue interface{}) {
+		if !lastState.Equals(state) || lastOldValue != oldValue || lastNewValue != newValue {
+			t.Fatalf("Incorrect field sub callback (expected [%v %v %v], got [%v %v %v])", state, oldValue, newValue, lastState, lastOldValue, lastNewValue)
+		}
+	}
+	ns.Start()
+	ns.SetState(testNode(1), flags[0], Flags{}, 0)
+	ns.SetField(testNode(1), fields[0], uint64(100))
+	check(flags[0], nil, uint64(100))
+	ns.Stop()
+	check(s.OfflineFlag(), uint64(100), nil)
+
+	ns2 := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+	ns2.SubscribeField(fields[0], func(n *enode.Node, state Flags, oldValue, newValue interface{}) {
+		lastState, lastOldValue, lastNewValue = state, oldValue, newValue
+	})
+	ns2.Start()
+	check(s.OfflineFlag(), nil, uint64(100))
+	ns2.SetState(testNode(1), Flags{}, flags[0], 0)
+	ns2.SetField(testNode(1), fields[0], nil)
+	check(Flags{}, uint64(100), nil)
+	ns2.Stop()
+}
+
+func TestDuplicatedFlags(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, _ := testSetup([]bool{true}, nil)
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	type change struct{ old, new Flags }
+	set := make(chan change, 1)
+	ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) {
+		set <- change{oldState, newState}
+	})
+
+	ns.Start()
+	defer ns.Stop()
+
+	check := func(expectOld, expectNew Flags, expectChange bool) {
+		if expectChange {
+			select {
+			case c := <-set:
+				if !c.old.Equals(expectOld) {
+					t.Fatalf("Old state mismatch")
+				}
+				if !c.new.Equals(expectNew) {
+					t.Fatalf("New state mismatch")
+				}
+			case <-time.After(time.Second):
+			}
+			return
+		}
+		select {
+		case <-set:
+			t.Fatalf("Unexpected change")
+		case <-time.After(time.Millisecond * 100):
+			return
+		}
+	}
+	ns.SetState(testNode(1), flags[0], Flags{}, time.Second)
+	check(Flags{}, flags[0], true)
+	ns.SetState(testNode(1), flags[0], Flags{}, 2*time.Second) // extend the timeout to 2s
+	check(Flags{}, flags[0], false)
+
+	clock.Run(2 * time.Second)
+	check(flags[0], Flags{}, true)
+}
+
+func TestCallbackOrder(t *testing.T) {
+	mdb, clock := rawdb.NewMemoryDatabase(), &mclock.Simulated{}
+
+	s, flags, _ := testSetup([]bool{false, false, false, false}, nil)
+	ns := NewNodeStateMachine(mdb, []byte("-ns"), clock, s)
+
+	ns.SubscribeState(flags[0], func(n *enode.Node, oldState, newState Flags) {
+		if newState.Equals(flags[0]) {
+			ns.SetStateSub(n, flags[1], Flags{}, 0)
+			ns.SetStateSub(n, flags[2], Flags{}, 0)
+		}
+	})
+	ns.SubscribeState(flags[1], func(n *enode.Node, oldState, newState Flags) {
+		if newState.Equals(flags[1]) {
+			ns.SetStateSub(n, flags[3], Flags{}, 0)
+		}
+	})
+	lastState := Flags{}
+	ns.SubscribeState(MergeFlags(flags[1], flags[2], flags[3]), func(n *enode.Node, oldState, newState Flags) {
+		if !oldState.Equals(lastState) {
+			t.Fatalf("Wrong callback order")
+		}
+		lastState = newState
+	})
+
+	ns.Start()
+	defer ns.Stop()
+
+	ns.SetState(testNode(1), flags[0], Flags{}, 0)
+}
diff --git a/p2p/peer.go b/p2p/peer.go
index 4398ad0f23..8ebc858392 100644
--- a/p2p/peer.go
+++ b/p2p/peer.go
@@ -138,8 +138,17 @@ func (p *Peer) Node() *enode.Node {
 	return p.rw.node
 }
 
-// Name returns the node name that the remote node advertised.
+// Name returns an abbreviated form of the name
 func (p *Peer) Name() string {
+	s := p.rw.name
+	if len(s) > 20 {
+		return s[:20] + "..."
+	}
+	return s
+}
+
+// Fullname returns the node name that the remote node advertised.
+func (p *Peer) Fullname() string {
 	return p.rw.name
 }
 
@@ -149,6 +158,20 @@ func (p *Peer) Caps() []Cap {
 	return p.rw.caps
 }
 
+// RunningCap returns true if the peer is actively connected using any of the
+// enumerated versions of a specific protocol, meaning that at least one of the
+// versions is supported by both this node and the peer p.
+func (p *Peer) RunningCap(protocol string, versions []uint) bool {
+	if proto, ok := p.running[protocol]; ok {
+		for _, ver := range versions {
+			if proto.Version == ver {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 // RemoteAddr returns the remote address of the network connection.
 func (p *Peer) RemoteAddr() net.Addr {
 	return p.rw.fd.RemoteAddr()
@@ -304,6 +327,7 @@ func (p *Peer) handle(msg Msg) error {
 		if metrics.Enabled {
 			m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset)
 			metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
+			metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1)
 		}
 		select {
 		case proto.in <- msg:
@@ -365,6 +389,7 @@ func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error)
 		}
 		p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version))
 		go func() {
+			defer p.wg.Done()
 			err := proto.Run(p, rw)
 			if err == nil {
 				p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version))
@@ -373,7 +398,6 @@ func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error)
 				p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err)
 			}
 			p.protoErr <- err
-			p.wg.Done()
 		}()
 	}
 }
@@ -462,7 +486,7 @@ func (p *Peer) Info() *PeerInfo {
 	info := &PeerInfo{
 		Enode:     p.Node().URLv4(),
 		ID:        p.ID().String(),
-		Name:      p.Name(),
+		Name:      p.Fullname(),
 		Caps:      caps,
 		Protocols: make(map[string]interface{}),
 	}
diff --git a/p2p/peer_test.go b/p2p/peer_test.go
index e40deb98f0..4308bbd2eb 100644
--- a/p2p/peer_test.go
+++ b/p2p/peer_test.go
@@ -86,9 +86,15 @@ func newNode(id enode.ID, addr string) *enode.Node {
 }
 
 func testPeer(protos []Protocol) (func(), *conn, *Peer, <-chan error) {
-	fd1, fd2 := net.Pipe()
-	c1 := &conn{fd: fd1, node: newNode(randomID(), ""), transport: newTestTransport(&newkey().PublicKey, fd1)}
-	c2 := &conn{fd: fd2, node: newNode(randomID(), ""), transport: newTestTransport(&newkey().PublicKey, fd2)}
+	var (
+		fd1, fd2   = net.Pipe()
+		key1, key2 = newkey(), newkey()
+		t1         = newTestTransport(&key2.PublicKey, fd1, nil)
+		t2         = newTestTransport(&key1.PublicKey, fd2, &key1.PublicKey)
+	)
+
+	c1 := &conn{fd: fd1, node: newNode(uintID(1), ""), transport: t1}
+	c2 := &conn{fd: fd2, node: newNode(uintID(2), ""), transport: t2}
 	for _, p := range protos {
 		c1.caps = append(c1.caps, p.cap())
 		c2.caps = append(c2.caps, p.cap())
@@ -173,9 +179,12 @@ func TestPeerPing(t *testing.T) {
 	}
 }
 
+// This test checks that a disconnect message sent by a peer is returned
+// as the error from Peer.run.
 func TestPeerDisconnect(t *testing.T) {
 	closer, rw, _, disc := testPeer(nil)
 	defer closer()
+
 	if err := SendItems(rw, discMsg, DiscQuitting); err != nil {
 		t.Fatal(err)
 	}
diff --git a/p2p/rlpx.go b/p2p/rlpx/rlpx.go
similarity index 63%
rename from p2p/rlpx.go
rename to p2p/rlpx/rlpx.go
index c134aec1de..2021bf08be 100644
--- a/p2p/rlpx.go
+++ b/p2p/rlpx/rlpx.go
@@ -14,7 +14,8 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package p2p
+// Package rlpx implements the RLPx transport protocol.
+package rlpx
 
 import (
 	"bytes"
@@ -29,169 +30,312 @@ import (
 	"fmt"
 	"hash"
 	"io"
-	"io/ioutil"
 	mrand "math/rand"
 	"net"
-	"sync"
 	"time"
 
-	"github.com/ethereum/go-ethereum/common/bitutil"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/crypto/ecies"
-	"github.com/ethereum/go-ethereum/metrics"
 	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/golang/snappy"
 	"golang.org/x/crypto/sha3"
 )
 
-const (
-	maxUint24 = ^uint32(0) >> 8
-
-	sskLen = 16                     // ecies.MaxSharedKeyLength(pubKey) / 2
-	sigLen = crypto.SignatureLength // elliptic S256
-	pubLen = 64                     // 512 bit pubkey in uncompressed representation without format byte
-	shaLen = 32                     // hash length (for nonce etc)
-
-	authMsgLen  = sigLen + shaLen + pubLen + shaLen + 1
-	authRespLen = pubLen + shaLen + 1
-
-	eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */
-
-	encAuthMsgLen  = authMsgLen + eciesOverhead  // size of encrypted pre-EIP-8 initiator handshake
-	encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply
+// Conn is an RLPx network connection. It wraps a low-level network connection. The
+// underlying connection should not be used for other activity when it is wrapped by Conn.
+//
+// Before sending messages, a handshake must be performed by calling the Handshake method.
+// This type is not generally safe for concurrent use, but reading and writing of messages
+// may happen concurrently after the handshake.
+type Conn struct {
+	dialDest  *ecdsa.PublicKey
+	conn      net.Conn
+	handshake *handshakeState
+	snappy    bool
+}
 
-	// total timeout for encryption handshake and protocol
-	// handshake in both directions.
-	handshakeTimeout = 5 * time.Second
+type handshakeState struct {
+	enc cipher.Stream
+	dec cipher.Stream
 
-	// This is the timeout for sending the disconnect reason.
-	// This is shorter than the usual timeout because we don't want
-	// to wait if the connection is known to be bad anyway.
-	discWriteTimeout = 1 * time.Second
-)
-
-// errPlainMessageTooLarge is returned if a decompressed message length exceeds
-// the allowed 24 bits (i.e. length >= 16MB).
-var errPlainMessageTooLarge = errors.New("message length >= 16MB")
+	macCipher  cipher.Block
+	egressMAC  hash.Hash
+	ingressMAC hash.Hash
+}
 
-// rlpx is the transport protocol used by actual (non-test) connections.
-// It wraps the frame encoder with locks and read/write deadlines.
-type rlpx struct {
-	fd net.Conn
+// NewConn wraps the given network connection. If dialDest is non-nil, the connection
+// behaves as the initiator during the handshake.
+func NewConn(conn net.Conn, dialDest *ecdsa.PublicKey) *Conn {
+	return &Conn{
+		dialDest: dialDest,
+		conn:     conn,
+	}
+}
 
-	rmu, wmu sync.Mutex
-	rw       *rlpxFrameRW
+// SetSnappy enables or disables snappy compression of messages. This is usually called
+// after the devp2p Hello message exchange when the negotiated version indicates that
+// compression is available on both ends of the connection.
+func (c *Conn) SetSnappy(snappy bool) {
+	c.snappy = snappy
 }
 
-func newRLPX(fd net.Conn) transport {
-	fd.SetDeadline(time.Now().Add(handshakeTimeout))
-	return &rlpx{fd: fd}
+// SetReadDeadline sets the deadline for all future read operations.
+func (c *Conn) SetReadDeadline(time time.Time) error {
+	return c.conn.SetReadDeadline(time)
 }
 
-func (t *rlpx) ReadMsg() (Msg, error) {
-	t.rmu.Lock()
-	defer t.rmu.Unlock()
-	t.fd.SetReadDeadline(time.Now().Add(frameReadTimeout))
-	return t.rw.ReadMsg()
+// SetWriteDeadline sets the deadline for all future write operations.
+func (c *Conn) SetWriteDeadline(time time.Time) error {
+	return c.conn.SetWriteDeadline(time)
 }
 
-func (t *rlpx) WriteMsg(msg Msg) error {
-	t.wmu.Lock()
-	defer t.wmu.Unlock()
-	t.fd.SetWriteDeadline(time.Now().Add(frameWriteTimeout))
-	return t.rw.WriteMsg(msg)
+// SetDeadline sets the deadline for all future read and write operations.
+func (c *Conn) SetDeadline(time time.Time) error {
+	return c.conn.SetDeadline(time)
 }
 
-func (t *rlpx) close(err error) {
-	t.wmu.Lock()
-	defer t.wmu.Unlock()
-	// Tell the remote end why we're disconnecting if possible.
-	if t.rw != nil {
-		if r, ok := err.(DiscReason); ok && r != DiscNetworkError {
-			// rlpx tries to send DiscReason to disconnected peer
-			// if the connection is net.Pipe (in-memory simulation)
-			// it hangs forever, since net.Pipe does not implement
-			// a write deadline. Because of this only try to send
-			// the disconnect reason message if there is no error.
-			if err := t.fd.SetWriteDeadline(time.Now().Add(discWriteTimeout)); err == nil {
-				SendItems(t.rw, discMsg, r)
-			}
+// Read reads a message from the connection.
+func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) {
+	if c.handshake == nil {
+		panic("can't ReadMsg before handshake")
+	}
+
+	frame, err := c.handshake.readFrame(c.conn)
+	if err != nil {
+		return 0, nil, 0, err
+	}
+	code, data, err = rlp.SplitUint64(frame)
+	if err != nil {
+		return 0, nil, 0, fmt.Errorf("invalid message code: %v", err)
+	}
+	wireSize = len(data)
+
+	// If snappy is enabled, verify and decompress message.
+	if c.snappy {
+		var actualSize int
+		actualSize, err = snappy.DecodedLen(data)
+		if err != nil {
+			return code, nil, 0, err
+		}
+		if actualSize > maxUint24 {
+			return code, nil, 0, errPlainMessageTooLarge
 		}
+		data, err = snappy.Decode(nil, data)
 	}
-	t.fd.Close()
+	return code, data, wireSize, err
 }
 
-func (t *rlpx) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
-	// Writing our handshake happens concurrently, we prefer
-	// returning the handshake read error. If the remote side
-	// disconnects us early with a valid reason, we should return it
-	// as the error so it can be tracked elsewhere.
-	werr := make(chan error, 1)
-	go func() { werr <- Send(t.rw, handshakeMsg, our) }()
-	if their, err = readProtocolHandshake(t.rw); err != nil {
-		<-werr // make sure the write terminates too
+func (h *handshakeState) readFrame(conn io.Reader) ([]byte, error) {
+	// read the header
+	headbuf := make([]byte, 32)
+	if _, err := io.ReadFull(conn, headbuf); err != nil {
 		return nil, err
 	}
-	if err := <-werr; err != nil {
-		return nil, fmt.Errorf("write error: %v", err)
+
+	// verify header mac
+	shouldMAC := updateMAC(h.ingressMAC, h.macCipher, headbuf[:16])
+	if !hmac.Equal(shouldMAC, headbuf[16:]) {
+		return nil, errors.New("bad header MAC")
 	}
-	// If the protocol version supports Snappy encoding, upgrade immediately
-	t.rw.snappy = their.Version >= snappyProtocolVersion
+	h.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted
+	fsize := readInt24(headbuf)
+	// ignore protocol type for now
 
-	return their, nil
-}
+	// read the frame content
+	var rsize = fsize // frame size rounded up to 16 byte boundary
+	if padding := fsize % 16; padding > 0 {
+		rsize += 16 - padding
+	}
+	framebuf := make([]byte, rsize)
+	if _, err := io.ReadFull(conn, framebuf); err != nil {
+		return nil, err
+	}
 
-func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) {
-	msg, err := rw.ReadMsg()
-	if err != nil {
+	// read and validate frame MAC. we can re-use headbuf for that.
+	h.ingressMAC.Write(framebuf)
+	fmacseed := h.ingressMAC.Sum(nil)
+	if _, err := io.ReadFull(conn, headbuf[:16]); err != nil {
 		return nil, err
 	}
-	if msg.Size > baseProtocolMaxMsgSize {
-		return nil, fmt.Errorf("message too big")
+	shouldMAC = updateMAC(h.ingressMAC, h.macCipher, fmacseed)
+	if !hmac.Equal(shouldMAC, headbuf[:16]) {
+		return nil, errors.New("bad frame MAC")
+	}
+
+	// decrypt frame content
+	h.dec.XORKeyStream(framebuf, framebuf)
+	return framebuf[:fsize], nil
+}
+
+// Write writes a message to the connection.
+//
+// Write returns the written size of the message data. This may be less than or equal to
+// len(data) depending on whether snappy compression is enabled.
+func (c *Conn) Write(code uint64, data []byte) (uint32, error) {
+	if c.handshake == nil {
+		panic("can't WriteMsg before handshake")
 	}
-	if msg.Code == discMsg {
-		// Disconnect before protocol handshake is valid according to the
-		// spec and we send it ourself if the post-handshake checks fail.
-		// We can't return the reason directly, though, because it is echoed
-		// back otherwise. Wrap it in a string instead.
-		var reason [1]DiscReason
-		rlp.Decode(msg.Payload, &reason)
-		return nil, reason[0]
+	if len(data) > maxUint24 {
+		return 0, errPlainMessageTooLarge
 	}
-	if msg.Code != handshakeMsg {
-		return nil, fmt.Errorf("expected handshake, got %x", msg.Code)
+	if c.snappy {
+		data = snappy.Encode(nil, data)
 	}
-	var hs protoHandshake
-	if err := msg.Decode(&hs); err != nil {
-		return nil, err
+
+	wireSize := uint32(len(data))
+	err := c.handshake.writeFrame(c.conn, code, data)
+	return wireSize, err
+}
+
+func (h *handshakeState) writeFrame(conn io.Writer, code uint64, data []byte) error {
+	ptype, _ := rlp.EncodeToBytes(code)
+
+	// write header
+	headbuf := make([]byte, 32)
+	fsize := len(ptype) + len(data)
+	if fsize > maxUint24 {
+		return errPlainMessageTooLarge
+	}
+	putInt24(uint32(fsize), headbuf)
+	copy(headbuf[3:], zeroHeader)
+	h.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted
+
+	// write header MAC
+	copy(headbuf[16:], updateMAC(h.egressMAC, h.macCipher, headbuf[:16]))
+	if _, err := conn.Write(headbuf); err != nil {
+		return err
+	}
+
+	// write encrypted frame, updating the egress MAC hash with
+	// the data written to conn.
+	tee := cipher.StreamWriter{S: h.enc, W: io.MultiWriter(conn, h.egressMAC)}
+	if _, err := tee.Write(ptype); err != nil {
+		return err
+	}
+	if _, err := tee.Write(data); err != nil {
+		return err
 	}
-	if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) {
-		return nil, DiscInvalidIdentity
+	if padding := fsize % 16; padding > 0 {
+		if _, err := tee.Write(zero16[:16-padding]); err != nil {
+			return err
+		}
 	}
-	return &hs, nil
+
+	// write frame MAC. egress MAC hash is up to date because
+	// frame content was written to it as well.
+	fmacseed := h.egressMAC.Sum(nil)
+	mac := updateMAC(h.egressMAC, h.macCipher, fmacseed)
+	_, err := conn.Write(mac)
+	return err
+}
+
+func readInt24(b []byte) uint32 {
+	return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
 }
 
-// doEncHandshake runs the protocol handshake using authenticated
-// messages. the protocol handshake is the first authenticated message
-// and also verifies whether the encryption handshake 'worked' and the
-// remote side actually provided the right public key.
-func (t *rlpx) doEncHandshake(prv *ecdsa.PrivateKey, dial *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
+func putInt24(v uint32, b []byte) {
+	b[0] = byte(v >> 16)
+	b[1] = byte(v >> 8)
+	b[2] = byte(v)
+}
+
+// updateMAC reseeds the given hash with encrypted seed.
+// it returns the first 16 bytes of the hash sum after seeding.
+func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte {
+	aesbuf := make([]byte, aes.BlockSize)
+	block.Encrypt(aesbuf, mac.Sum(nil))
+	for i := range aesbuf {
+		aesbuf[i] ^= seed[i]
+	}
+	mac.Write(aesbuf)
+	return mac.Sum(nil)[:16]
+}
+
+// Handshake performs the handshake. This must be called before any data is written
+// or read from the connection.
+func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
 	var (
-		sec secrets
+		sec Secrets
 		err error
 	)
-	if dial == nil {
-		sec, err = receiverEncHandshake(t.fd, prv)
+	if c.dialDest != nil {
+		sec, err = initiatorEncHandshake(c.conn, prv, c.dialDest)
 	} else {
-		sec, err = initiatorEncHandshake(t.fd, prv, dial)
+		sec, err = receiverEncHandshake(c.conn, prv)
 	}
 	if err != nil {
 		return nil, err
 	}
-	t.wmu.Lock()
-	t.rw = newRLPXFrameRW(t.fd, sec)
-	t.wmu.Unlock()
-	return sec.Remote.ExportECDSA(), nil
+	c.InitWithSecrets(sec)
+	return sec.remote, err
+}
+
+// InitWithSecrets injects connection secrets as if a handshake had
+// been performed. This cannot be called after the handshake.
+func (c *Conn) InitWithSecrets(sec Secrets) {
+	if c.handshake != nil {
+		panic("can't handshake twice")
+	}
+	macc, err := aes.NewCipher(sec.MAC)
+	if err != nil {
+		panic("invalid MAC secret: " + err.Error())
+	}
+	encc, err := aes.NewCipher(sec.AES)
+	if err != nil {
+		panic("invalid AES secret: " + err.Error())
+	}
+	// we use an all-zeroes IV for AES because the key used
+	// for encryption is ephemeral.
+	iv := make([]byte, encc.BlockSize())
+	c.handshake = &handshakeState{
+		enc:        cipher.NewCTR(encc, iv),
+		dec:        cipher.NewCTR(encc, iv),
+		macCipher:  macc,
+		egressMAC:  sec.EgressMAC,
+		ingressMAC: sec.IngressMAC,
+	}
+}
+
+// Close closes the underlying network connection.
+func (c *Conn) Close() error {
+	return c.conn.Close()
+}
+
+// Constants for the handshake.
+const (
+	maxUint24 = int(^uint32(0) >> 8)
+
+	sskLen = 16                     // ecies.MaxSharedKeyLength(pubKey) / 2
+	sigLen = crypto.SignatureLength // elliptic S256
+	pubLen = 64                     // 512 bit pubkey in uncompressed representation without format byte
+	shaLen = 32                     // hash length (for nonce etc)
+
+	authMsgLen  = sigLen + shaLen + pubLen + shaLen + 1
+	authRespLen = pubLen + shaLen + 1
+
+	eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */
+
+	encAuthMsgLen  = authMsgLen + eciesOverhead  // size of encrypted pre-EIP-8 initiator handshake
+	encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply
+)
+
+var (
+	// this is used in place of actual frame header data.
+	// TODO: replace this when Msg contains the protocol type code.
+	zeroHeader = []byte{0xC2, 0x80, 0x80}
+	// sixteen zero bytes
+	zero16 = make([]byte, 16)
+
+	// errPlainMessageTooLarge is returned if a decompressed message length exceeds
+	// the allowed 24 bits (i.e. length >= 16MB).
+	errPlainMessageTooLarge = errors.New("message length >= 16MB")
+)
+
+// Secrets represents the connection secrets which are negotiated during the handshake.
+type Secrets struct {
+	AES, MAC              []byte
+	EgressMAC, IngressMAC hash.Hash
+	remote                *ecdsa.PublicKey
 }
 
 // encHandshake contains the state of the encryption handshake.
@@ -203,15 +347,6 @@ type encHandshake struct {
 	remoteRandomPub      *ecies.PublicKey  // ecdhe-random-pubk
 }
 
-// secrets represents the connection secrets
-// which are negotiated during the encryption handshake.
-type secrets struct {
-	Remote                *ecies.PublicKey
-	AES, MAC              []byte
-	EgressMAC, IngressMAC hash.Hash
-	Token                 []byte
-}
-
 // RLPx v4 handshake auth (defined in EIP-8).
 type authMsgV4 struct {
 	gotPlain bool // whether read packet had plain format.
@@ -235,19 +370,85 @@ type authRespV4 struct {
 	Rest []rlp.RawValue `rlp:"tail"`
 }
 
+// receiverEncHandshake negotiates a session token on conn.
+// it should be called on the listening side of the connection.
+//
+// prv is the local client's private key.
+func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) {
+	authMsg := new(authMsgV4)
+	authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)
+	if err != nil {
+		return s, err
+	}
+	h := new(encHandshake)
+	if err := h.handleAuthMsg(authMsg, prv); err != nil {
+		return s, err
+	}
+
+	authRespMsg, err := h.makeAuthResp()
+	if err != nil {
+		return s, err
+	}
+	var authRespPacket []byte
+	if authMsg.gotPlain {
+		authRespPacket, err = authRespMsg.sealPlain(h)
+	} else {
+		authRespPacket, err = sealEIP8(authRespMsg, h)
+	}
+	if err != nil {
+		return s, err
+	}
+	if _, err = conn.Write(authRespPacket); err != nil {
+		return s, err
+	}
+	return h.secrets(authPacket, authRespPacket)
+}
+
+func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
+	// Import the remote identity.
+	rpub, err := importPublicKey(msg.InitiatorPubkey[:])
+	if err != nil {
+		return err
+	}
+	h.initNonce = msg.Nonce[:]
+	h.remote = rpub
+
+	// Generate random keypair for ECDH.
+	// If a private key is already set, use it instead of generating one (for testing).
+	if h.randomPrivKey == nil {
+		h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Check the signature.
+	token, err := h.staticSharedSecret(prv)
+	if err != nil {
+		return err
+	}
+	signedMsg := xor(token, h.initNonce)
+	remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:])
+	if err != nil {
+		return err
+	}
+	h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
+	return nil
+}
+
 // secrets is called after the handshake is completed.
 // It extracts the connection secrets from the handshake values.
-func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
+func (h *encHandshake) secrets(auth, authResp []byte) (Secrets, error) {
 	ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
 	if err != nil {
-		return secrets{}, err
+		return Secrets{}, err
 	}
 
 	// derive base secrets from ephemeral key agreement
 	sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce))
 	aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret)
-	s := secrets{
-		Remote: h.remote,
+	s := Secrets{
+		remote: h.remote.ExportECDSA(),
 		AES:    aesSecret,
 		MAC:    crypto.Keccak256(ecdheSecret, aesSecret),
 	}
@@ -278,7 +479,7 @@ func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error)
 // it should be called on the dialing side of the connection.
 //
 // prv is the local client's private key.
-func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s secrets, err error) {
+func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) {
 	h := &encHandshake{initiator: true, remote: ecies.ImportECDSAPublic(remote)}
 	authMsg, err := h.makeAuthMsg(prv)
 	if err != nil {
@@ -288,6 +489,7 @@ func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ec
 	if err != nil {
 		return s, err
 	}
+
 	if _, err = conn.Write(authPacket); err != nil {
 		return s, err
 	}
@@ -342,72 +544,6 @@ func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) {
 	return err
 }
 
-// receiverEncHandshake negotiates a session token on conn.
-// it should be called on the listening side of the connection.
-//
-// prv is the local client's private key.
-func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s secrets, err error) {
-	authMsg := new(authMsgV4)
-	authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)
-	if err != nil {
-		return s, err
-	}
-	h := new(encHandshake)
-	if err := h.handleAuthMsg(authMsg, prv); err != nil {
-		return s, err
-	}
-
-	authRespMsg, err := h.makeAuthResp()
-	if err != nil {
-		return s, err
-	}
-	var authRespPacket []byte
-	if authMsg.gotPlain {
-		authRespPacket, err = authRespMsg.sealPlain(h)
-	} else {
-		authRespPacket, err = sealEIP8(authRespMsg, h)
-	}
-	if err != nil {
-		return s, err
-	}
-	if _, err = conn.Write(authRespPacket); err != nil {
-		return s, err
-	}
-	return h.secrets(authPacket, authRespPacket)
-}
-
-func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
-	// Import the remote identity.
-	rpub, err := importPublicKey(msg.InitiatorPubkey[:])
-	if err != nil {
-		return err
-	}
-	h.initNonce = msg.Nonce[:]
-	h.remote = rpub
-
-	// Generate random keypair for ECDH.
-	// If a private key is already set, use it instead of generating one (for testing).
-	if h.randomPrivKey == nil {
-		h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
-		if err != nil {
-			return err
-		}
-	}
-
-	// Check the signature.
-	token, err := h.staticSharedSecret(prv)
-	if err != nil {
-		return err
-	}
-	signedMsg := xor(token, h.initNonce)
-	remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:])
-	if err != nil {
-		return err
-	}
-	h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
-	return nil
-}
-
 func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) {
 	// Generate random nonce.
 	h.respNonce = make([]byte, shaLen)
@@ -531,200 +667,3 @@ func xor(one, other []byte) (xor []byte) {
 	}
 	return xor
 }
-
-var (
-	// this is used in place of actual frame header data.
-	// TODO: replace this when Msg contains the protocol type code.
-	zeroHeader = []byte{0xC2, 0x80, 0x80}
-	// sixteen zero bytes
-	zero16 = make([]byte, 16)
-)
-
-// rlpxFrameRW implements a simplified version of RLPx framing.
-// chunked messages are not supported and all headers are equal to
-// zeroHeader.
-//
-// rlpxFrameRW is not safe for concurrent use from multiple goroutines.
-type rlpxFrameRW struct {
-	conn io.ReadWriter
-	enc  cipher.Stream
-	dec  cipher.Stream
-
-	macCipher  cipher.Block
-	egressMAC  hash.Hash
-	ingressMAC hash.Hash
-
-	snappy bool
-}
-
-func newRLPXFrameRW(conn io.ReadWriter, s secrets) *rlpxFrameRW {
-	macc, err := aes.NewCipher(s.MAC)
-	if err != nil {
-		panic("invalid MAC secret: " + err.Error())
-	}
-	encc, err := aes.NewCipher(s.AES)
-	if err != nil {
-		panic("invalid AES secret: " + err.Error())
-	}
-	// we use an all-zeroes IV for AES because the key used
-	// for encryption is ephemeral.
-	iv := make([]byte, encc.BlockSize())
-	return &rlpxFrameRW{
-		conn:       conn,
-		enc:        cipher.NewCTR(encc, iv),
-		dec:        cipher.NewCTR(encc, iv),
-		macCipher:  macc,
-		egressMAC:  s.EgressMAC,
-		ingressMAC: s.IngressMAC,
-	}
-}
-
-func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {
-	ptype, _ := rlp.EncodeToBytes(msg.Code)
-
-	// if snappy is enabled, compress message now
-	if rw.snappy {
-		if msg.Size > maxUint24 {
-			return errPlainMessageTooLarge
-		}
-		payload, _ := ioutil.ReadAll(msg.Payload)
-		payload = snappy.Encode(nil, payload)
-
-		msg.Payload = bytes.NewReader(payload)
-		msg.Size = uint32(len(payload))
-	}
-	msg.meterSize = msg.Size
-	if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages
-		m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode)
-		metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
-	}
-	// write header
-	headbuf := make([]byte, 32)
-	fsize := uint32(len(ptype)) + msg.Size
-	if fsize > maxUint24 {
-		return errors.New("message size overflows uint24")
-	}
-	putInt24(fsize, headbuf) // TODO: check overflow
-	copy(headbuf[3:], zeroHeader)
-	rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted
-
-	// write header MAC
-	copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16]))
-	if _, err := rw.conn.Write(headbuf); err != nil {
-		return err
-	}
-
-	// write encrypted frame, updating the egress MAC hash with
-	// the data written to conn.
-	tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)}
-	if _, err := tee.Write(ptype); err != nil {
-		return err
-	}
-	if _, err := io.Copy(tee, msg.Payload); err != nil {
-		return err
-	}
-	if padding := fsize % 16; padding > 0 {
-		if _, err := tee.Write(zero16[:16-padding]); err != nil {
-			return err
-		}
-	}
-
-	// write frame MAC. egress MAC hash is up to date because
-	// frame content was written to it as well.
-	fmacseed := rw.egressMAC.Sum(nil)
-	mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed)
-	_, err := rw.conn.Write(mac)
-	return err
-}
-
-func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {
-	// read the header
-	headbuf := make([]byte, 32)
-	if _, err := io.ReadFull(rw.conn, headbuf); err != nil {
-		return msg, err
-	}
-	// verify header mac
-	shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16])
-	if !hmac.Equal(shouldMAC, headbuf[16:]) {
-		return msg, errors.New("bad header MAC")
-	}
-	rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted
-	fsize := readInt24(headbuf)
-	// ignore protocol type for now
-
-	// read the frame content
-	var rsize = fsize // frame size rounded up to 16 byte boundary
-	if padding := fsize % 16; padding > 0 {
-		rsize += 16 - padding
-	}
-	framebuf := make([]byte, rsize)
-	if _, err := io.ReadFull(rw.conn, framebuf); err != nil {
-		return msg, err
-	}
-
-	// read and validate frame MAC. we can re-use headbuf for that.
-	rw.ingressMAC.Write(framebuf)
-	fmacseed := rw.ingressMAC.Sum(nil)
-	if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil {
-		return msg, err
-	}
-	shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed)
-	if !hmac.Equal(shouldMAC, headbuf[:16]) {
-		return msg, errors.New("bad frame MAC")
-	}
-
-	// decrypt frame content
-	rw.dec.XORKeyStream(framebuf, framebuf)
-
-	// decode message code
-	content := bytes.NewReader(framebuf[:fsize])
-	if err := rlp.Decode(content, &msg.Code); err != nil {
-		return msg, err
-	}
-	msg.Size = uint32(content.Len())
-	msg.meterSize = msg.Size
-	msg.Payload = content
-
-	// if snappy is enabled, verify and decompress message
-	if rw.snappy {
-		payload, err := ioutil.ReadAll(msg.Payload)
-		if err != nil {
-			return msg, err
-		}
-		size, err := snappy.DecodedLen(payload)
-		if err != nil {
-			return msg, err
-		}
-		if size > int(maxUint24) {
-			return msg, errPlainMessageTooLarge
-		}
-		payload, err = snappy.Decode(nil, payload)
-		if err != nil {
-			return msg, err
-		}
-		msg.Size, msg.Payload = uint32(size), bytes.NewReader(payload)
-	}
-	return msg, nil
-}
-
-// updateMAC reseeds the given hash with encrypted seed.
-// it returns the first 16 bytes of the hash sum after seeding.
-func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte {
-	aesbuf := make([]byte, aes.BlockSize)
-	block.Encrypt(aesbuf, mac.Sum(nil))
-	for i := range aesbuf {
-		aesbuf[i] ^= seed[i]
-	}
-	mac.Write(aesbuf)
-	return mac.Sum(nil)[:16]
-}
-
-func readInt24(b []byte) uint32 {
-	return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
-}
-
-func putInt24(v uint32, b []byte) {
-	b[0] = byte(v >> 16)
-	b[1] = byte(v >> 8)
-	b[2] = byte(v)
-}
diff --git a/p2p/rlpx_test.go b/p2p/rlpx/rlpx_test.go
similarity index 58%
rename from p2p/rlpx_test.go
rename to p2p/rlpx/rlpx_test.go
index e62196ff04..127a018164 100644
--- a/p2p/rlpx_test.go
+++ b/p2p/rlpx/rlpx_test.go
@@ -1,4 +1,4 @@
-// Copyright 2015 The go-ethereum Authors
+// 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
@@ -14,298 +14,145 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package p2p
+package rlpx
 
 import (
 	"bytes"
 	"crypto/ecdsa"
-	"crypto/rand"
-	"errors"
+	"encoding/hex"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"reflect"
 	"strings"
-	"sync"
 	"testing"
-	"time"
 
 	"github.com/davecgh/go-spew/spew"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/crypto/ecies"
-	"github.com/ethereum/go-ethereum/p2p/simulations/pipes"
 	"github.com/ethereum/go-ethereum/rlp"
-	"golang.org/x/crypto/sha3"
+	"github.com/stretchr/testify/assert"
 )
 
-func TestSharedSecret(t *testing.T) {
-	prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
-	pub0 := &prv0.PublicKey
-	prv1, _ := crypto.GenerateKey()
-	pub1 := &prv1.PublicKey
-
-	ss0, err := ecies.ImportECDSA(prv0).GenerateShared(ecies.ImportECDSAPublic(pub1), sskLen, sskLen)
-	if err != nil {
-		return
-	}
-	ss1, err := ecies.ImportECDSA(prv1).GenerateShared(ecies.ImportECDSAPublic(pub0), sskLen, sskLen)
-	if err != nil {
-		return
-	}
-	t.Logf("Secret:\n%v %x\n%v %x", len(ss0), ss0, len(ss0), ss1)
-	if !bytes.Equal(ss0, ss1) {
-		t.Errorf("dont match :(")
-	}
+type message struct {
+	code uint64
+	data []byte
+	err  error
 }
 
-func TestEncHandshake(t *testing.T) {
-	for i := 0; i < 10; i++ {
-		start := time.Now()
-		if err := testEncHandshake(nil); err != nil {
-			t.Fatalf("i=%d %v", i, err)
-		}
-		t.Logf("(without token) %d %v\n", i+1, time.Since(start))
-	}
-	for i := 0; i < 10; i++ {
-		tok := make([]byte, shaLen)
-		rand.Reader.Read(tok)
-		start := time.Now()
-		if err := testEncHandshake(tok); err != nil {
-			t.Fatalf("i=%d %v", i, err)
-		}
-		t.Logf("(with token) %d %v\n", i+1, time.Since(start))
-	}
+func TestHandshake(t *testing.T) {
+	p1, p2 := createPeers(t)
+	p1.Close()
+	p2.Close()
 }
 
-func testEncHandshake(token []byte) error {
-	type result struct {
-		side   string
-		pubkey *ecdsa.PublicKey
-		err    error
-	}
-	var (
-		prv0, _  = crypto.GenerateKey()
-		prv1, _  = crypto.GenerateKey()
-		fd0, fd1 = net.Pipe()
-		c0, c1   = newRLPX(fd0).(*rlpx), newRLPX(fd1).(*rlpx)
-		output   = make(chan result)
-	)
-
-	go func() {
-		r := result{side: "initiator"}
-		defer func() { output <- r }()
-		defer fd0.Close()
+// This test checks that messages can be sent and received through WriteMsg/ReadMsg.
+func TestReadWriteMsg(t *testing.T) {
+	peer1, peer2 := createPeers(t)
+	defer peer1.Close()
+	defer peer2.Close()
 
-		r.pubkey, r.err = c0.doEncHandshake(prv0, &prv1.PublicKey)
-		if r.err != nil {
-			return
-		}
-		if !reflect.DeepEqual(r.pubkey, &prv1.PublicKey) {
-			r.err = fmt.Errorf("remote pubkey mismatch: got %v, want: %v", r.pubkey, &prv1.PublicKey)
-		}
-	}()
-	go func() {
-		r := result{side: "receiver"}
-		defer func() { output <- r }()
-		defer fd1.Close()
-
-		r.pubkey, r.err = c1.doEncHandshake(prv1, nil)
-		if r.err != nil {
-			return
-		}
-		if !reflect.DeepEqual(r.pubkey, &prv0.PublicKey) {
-			r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.pubkey, &prv0.PublicKey)
-		}
-	}()
+	testCode := uint64(23)
+	testData := []byte("test")
+	checkMsgReadWrite(t, peer1, peer2, testCode, testData)
 
-	// wait for results from both sides
-	r1, r2 := <-output, <-output
-	if r1.err != nil {
-		return fmt.Errorf("%s side error: %v", r1.side, r1.err)
-	}
-	if r2.err != nil {
-		return fmt.Errorf("%s side error: %v", r2.side, r2.err)
-	}
-
-	// compare derived secrets
-	if !reflect.DeepEqual(c0.rw.egressMAC, c1.rw.ingressMAC) {
-		return fmt.Errorf("egress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.egressMAC, c1.rw.ingressMAC)
-	}
-	if !reflect.DeepEqual(c0.rw.ingressMAC, c1.rw.egressMAC) {
-		return fmt.Errorf("ingress mac mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.ingressMAC, c1.rw.egressMAC)
-	}
-	if !reflect.DeepEqual(c0.rw.enc, c1.rw.enc) {
-		return fmt.Errorf("enc cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.enc, c1.rw.enc)
-	}
-	if !reflect.DeepEqual(c0.rw.dec, c1.rw.dec) {
-		return fmt.Errorf("dec cipher mismatch:\n c0.rw: %#v\n c1.rw: %#v", c0.rw.dec, c1.rw.dec)
-	}
-	return nil
+	t.Log("enabling snappy")
+	peer1.SetSnappy(true)
+	peer2.SetSnappy(true)
+	checkMsgReadWrite(t, peer1, peer2, testCode, testData)
 }
 
-func TestProtocolHandshake(t *testing.T) {
-	var (
-		prv0, _ = crypto.GenerateKey()
-		pub0    = crypto.FromECDSAPub(&prv0.PublicKey)[1:]
-		hs0     = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}}
-
-		prv1, _ = crypto.GenerateKey()
-		pub1    = crypto.FromECDSAPub(&prv1.PublicKey)[1:]
-		hs1     = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}}
-
-		wg sync.WaitGroup
-	)
+func checkMsgReadWrite(t *testing.T, p1, p2 *Conn, msgCode uint64, msgData []byte) {
+	// Set up the reader.
+	ch := make(chan message, 1)
+	go func() {
+		var msg message
+		msg.code, msg.data, _, msg.err = p1.Read()
+		ch <- msg
+	}()
 
-	fd0, fd1, err := pipes.TCPPipe()
+	// Write the message.
+	_, err := p2.Write(msgCode, msgData)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	wg.Add(2)
-	go func() {
-		defer wg.Done()
-		defer fd0.Close()
-		rlpx := newRLPX(fd0)
-		rpubkey, err := rlpx.doEncHandshake(prv0, &prv1.PublicKey)
-		if err != nil {
-			t.Errorf("dial side enc handshake failed: %v", err)
-			return
-		}
-		if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) {
-			t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey)
-			return
-		}
+	// Check it was received correctly.
+	msg := <-ch
+	assert.Equal(t, msgCode, msg.code, "wrong message code returned from ReadMsg")
+	assert.Equal(t, msgData, msg.data, "wrong message data returned from ReadMsg")
+}
 
-		phs, err := rlpx.doProtoHandshake(hs0)
-		if err != nil {
-			t.Errorf("dial side proto handshake error: %v", err)
-			return
-		}
-		phs.Rest = nil
-		if !reflect.DeepEqual(phs, hs1) {
-			t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1))
-			return
-		}
-		rlpx.close(DiscQuitting)
-	}()
-	go func() {
-		defer wg.Done()
-		defer fd1.Close()
-		rlpx := newRLPX(fd1)
-		rpubkey, err := rlpx.doEncHandshake(prv1, nil)
-		if err != nil {
-			t.Errorf("listen side enc handshake failed: %v", err)
-			return
-		}
-		if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) {
-			t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey)
-			return
-		}
+func createPeers(t *testing.T) (peer1, peer2 *Conn) {
+	conn1, conn2 := net.Pipe()
+	key1, key2 := newkey(), newkey()
+	peer1 = NewConn(conn1, &key2.PublicKey) // dialer
+	peer2 = NewConn(conn2, nil)             // listener
+	doHandshake(t, peer1, peer2, key1, key2)
+	return peer1, peer2
+}
 
-		phs, err := rlpx.doProtoHandshake(hs1)
+func doHandshake(t *testing.T, peer1, peer2 *Conn, key1, key2 *ecdsa.PrivateKey) {
+	keyChan := make(chan *ecdsa.PublicKey, 1)
+	go func() {
+		pubKey, err := peer2.Handshake(key2)
 		if err != nil {
-			t.Errorf("listen side proto handshake error: %v", err)
-			return
-		}
-		phs.Rest = nil
-		if !reflect.DeepEqual(phs, hs0) {
-			t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0))
-			return
-		}
-
-		if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil {
-			t.Errorf("error receiving disconnect: %v", err)
+			t.Errorf("peer2 could not do handshake: %v", err)
 		}
+		keyChan <- pubKey
 	}()
-	wg.Wait()
-}
 
-func TestProtocolHandshakeErrors(t *testing.T) {
-	tests := []struct {
-		code uint64
-		msg  interface{}
-		err  error
-	}{
-		{
-			code: discMsg,
-			msg:  []DiscReason{DiscQuitting},
-			err:  DiscQuitting,
-		},
-		{
-			code: 0x989898,
-			msg:  []byte{1},
-			err:  errors.New("expected handshake, got 989898"),
-		},
-		{
-			code: handshakeMsg,
-			msg:  make([]byte, baseProtocolMaxMsgSize+2),
-			err:  errors.New("message too big"),
-		},
-		{
-			code: handshakeMsg,
-			msg:  []byte{1, 2, 3},
-			err:  newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"),
-		},
-		{
-			code: handshakeMsg,
-			msg:  &protoHandshake{Version: 3},
-			err:  DiscInvalidIdentity,
-		},
+	pubKey2, err := peer1.Handshake(key1)
+	if err != nil {
+		t.Errorf("peer1 could not do handshake: %v", err)
 	}
+	pubKey1 := <-keyChan
 
-	for i, test := range tests {
-		p1, p2 := MsgPipe()
-		go Send(p1, test.code, test.msg)
-		_, err := readProtocolHandshake(p2)
-		if !reflect.DeepEqual(err, test.err) {
-			t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err)
-		}
+	// Confirm the handshake was successful.
+	if !reflect.DeepEqual(pubKey1, &key1.PublicKey) || !reflect.DeepEqual(pubKey2, &key2.PublicKey) {
+		t.Fatal("unsuccessful handshake")
 	}
 }
 
-func TestRLPXFrameFake(t *testing.T) {
-	buf := new(bytes.Buffer)
+// This test checks the frame data of written messages.
+func TestFrameReadWrite(t *testing.T) {
+	conn := NewConn(nil, nil)
 	hash := fakeHash([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})
-	rw := newRLPXFrameRW(buf, secrets{
+	conn.InitWithSecrets(Secrets{
 		AES:        crypto.Keccak256(),
 		MAC:        crypto.Keccak256(),
 		IngressMAC: hash,
 		EgressMAC:  hash,
 	})
+	h := conn.handshake
 
 	golden := unhex(`
-00828ddae471818bb0bfa6b551d1cb42
-01010101010101010101010101010101
-ba628a4ba590cb43f7848f41c4382885
-01010101010101010101010101010101
-`)
-
-	// Check WriteMsg. This puts a message into the buffer.
-	if err := Send(rw, 8, []uint{1, 2, 3, 4}); err != nil {
+		00828ddae471818bb0bfa6b551d1cb42
+		01010101010101010101010101010101
+		ba628a4ba590cb43f7848f41c4382885
+		01010101010101010101010101010101
+	`)
+	msgCode := uint64(8)
+	msg := []uint{1, 2, 3, 4}
+	msgEnc, _ := rlp.EncodeToBytes(msg)
+
+	// Check writeFrame. The frame that's written should be equal to the test vector.
+	buf := new(bytes.Buffer)
+	if err := h.writeFrame(buf, msgCode, msgEnc); err != nil {
 		t.Fatalf("WriteMsg error: %v", err)
 	}
-	written := buf.Bytes()
-	if !bytes.Equal(written, golden) {
-		t.Fatalf("output mismatch:\n  got:  %x\n  want: %x", written, golden)
+	if !bytes.Equal(buf.Bytes(), golden) {
+		t.Fatalf("output mismatch:\n  got:  %x\n  want: %x", buf.Bytes(), golden)
 	}
 
-	// Check ReadMsg. It reads the message encoded by WriteMsg, which
-	// is equivalent to the golden message above.
-	msg, err := rw.ReadMsg()
+	// Check readFrame on the test vector.
+	content, err := h.readFrame(bytes.NewReader(golden))
 	if err != nil {
 		t.Fatalf("ReadMsg error: %v", err)
 	}
-	if msg.Size != 5 {
-		t.Errorf("msg size mismatch: got %d, want %d", msg.Size, 5)
-	}
-	if msg.Code != 8 {
-		t.Errorf("msg code mismatch: got %d, want %d", msg.Code, 8)
-	}
-	payload, _ := ioutil.ReadAll(msg.Payload)
-	wantPayload := unhex("C401020304")
-	if !bytes.Equal(payload, wantPayload) {
-		t.Errorf("msg payload mismatch:\ngot  %x\nwant %x", payload, wantPayload)
+	wantContent := unhex("08C401020304")
+	if !bytes.Equal(content, wantContent) {
+		t.Errorf("frame content mismatch:\ngot  %x\nwant %x", content, wantContent)
 	}
 }
 
@@ -314,66 +161,8 @@ type fakeHash []byte
 func (fakeHash) Write(p []byte) (int, error) { return len(p), nil }
 func (fakeHash) Reset()                      {}
 func (fakeHash) BlockSize() int              { return 0 }
-
-func (h fakeHash) Size() int           { return len(h) }
-func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) }
-
-func TestRLPXFrameRW(t *testing.T) {
-	var (
-		aesSecret      = make([]byte, 16)
-		macSecret      = make([]byte, 16)
-		egressMACinit  = make([]byte, 32)
-		ingressMACinit = make([]byte, 32)
-	)
-	for _, s := range [][]byte{aesSecret, macSecret, egressMACinit, ingressMACinit} {
-		rand.Read(s)
-	}
-	conn := new(bytes.Buffer)
-
-	s1 := secrets{
-		AES:        aesSecret,
-		MAC:        macSecret,
-		EgressMAC:  sha3.NewLegacyKeccak256(),
-		IngressMAC: sha3.NewLegacyKeccak256(),
-	}
-	s1.EgressMAC.Write(egressMACinit)
-	s1.IngressMAC.Write(ingressMACinit)
-	rw1 := newRLPXFrameRW(conn, s1)
-
-	s2 := secrets{
-		AES:        aesSecret,
-		MAC:        macSecret,
-		EgressMAC:  sha3.NewLegacyKeccak256(),
-		IngressMAC: sha3.NewLegacyKeccak256(),
-	}
-	s2.EgressMAC.Write(ingressMACinit)
-	s2.IngressMAC.Write(egressMACinit)
-	rw2 := newRLPXFrameRW(conn, s2)
-
-	// send some messages
-	for i := 0; i < 10; i++ {
-		// write message into conn buffer
-		wmsg := []interface{}{"foo", "bar", strings.Repeat("test", i)}
-		err := Send(rw1, uint64(i), wmsg)
-		if err != nil {
-			t.Fatalf("WriteMsg error (i=%d): %v", i, err)
-		}
-
-		// read message that rw1 just wrote
-		msg, err := rw2.ReadMsg()
-		if err != nil {
-			t.Fatalf("ReadMsg error (i=%d): %v", i, err)
-		}
-		if msg.Code != uint64(i) {
-			t.Fatalf("msg code mismatch: got %d, want %d", msg.Code, i)
-		}
-		payload, _ := ioutil.ReadAll(msg.Payload)
-		wantPayload, _ := rlp.EncodeToBytes(wmsg)
-		if !bytes.Equal(payload, wantPayload) {
-			t.Fatalf("msg payload mismatch:\ngot  %x\nwant %x", payload, wantPayload)
-		}
-	}
-}
+func (h fakeHash) Size() int                 { return len(h) }
+func (h fakeHash) Sum(b []byte) []byte       { return append(b, h...) }
 
 type handshakeAuthTest struct {
 	input       string
@@ -598,3 +387,20 @@ func TestHandshakeForwardCompatibility(t *testing.T) {
 		t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash)
 	}
 }
+
+func unhex(str string) []byte {
+	r := strings.NewReplacer("\t", "", " ", "", "\n", "")
+	b, err := hex.DecodeString(r.Replace(str))
+	if err != nil {
+		panic(fmt.Sprintf("invalid hex string: %q", str))
+	}
+	return b
+}
+
+func newkey() *ecdsa.PrivateKey {
+	key, err := crypto.GenerateKey()
+	if err != nil {
+		panic("couldn't generate key: " + err.Error())
+	}
+	return key
+}
diff --git a/p2p/server.go b/p2p/server.go
index 1fe5f39789..f70ebf7216 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -35,7 +35,6 @@ import (
 	"github.com/ethereum/go-ethereum/event"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p/discover"
-	"github.com/ethereum/go-ethereum/p2p/discv5"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
 	"github.com/ethereum/go-ethereum/p2p/nat"
@@ -105,7 +104,7 @@ type Config struct {
 	// BootstrapNodesV5 are used to establish connectivity
 	// with the rest of the network using the V5 discovery
 	// protocol.
-	BootstrapNodesV5 []*discv5.Node `toml:",omitempty"`
+	BootstrapNodesV5 []*enode.Node `toml:",omitempty"`
 
 	// Static nodes are used as pre-configured connections which are always
 	// maintained and re-connected on disconnects.
@@ -166,7 +165,7 @@ type Server struct {
 
 	// Hooks for testing. These are useful because we can inhibit
 	// the whole protocol stack.
-	newTransport func(net.Conn) transport
+	newTransport func(net.Conn, *ecdsa.PublicKey) transport
 	newPeerHook  func(*Peer)
 	listenFunc   func(network, addr string) (net.Listener, error)
 
@@ -182,7 +181,7 @@ type Server struct {
 	nodedb    *enode.DB
 	localnode *enode.LocalNode
 	ntab      *discover.UDPv4
-	DiscV5    *discv5.Network
+	DiscV5    *discover.UDPv5
 	discmix   *enode.FairMix
 	dialsched *dialScheduler
 
@@ -231,7 +230,7 @@ type conn struct {
 
 type transport interface {
 	// The two handshakes.
-	doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error)
+	doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error)
 	doProtoHandshake(our *protoHandshake) (*protoHandshake, error)
 	// The MsgReadWriter can only be used after the encryption
 	// handshake has completed. The code uses conn.id to track this
@@ -413,7 +412,7 @@ type sharedUDPConn struct {
 	unhandled chan discover.ReadPacket
 }
 
-// ReadFromUDP implements discv5.conn
+// ReadFromUDP implements discover.UDPConn
 func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
 	packet, ok := <-s.unhandled
 	if !ok {
@@ -427,7 +426,7 @@ func (s *sharedUDPConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err err
 	return l, packet.Addr, nil
 }
 
-// Close implements discv5.conn
+// Close implements discover.UDPConn
 func (s *sharedUDPConn) Close() error {
 	return nil
 }
@@ -586,7 +585,7 @@ func (srv *Server) setupDiscovery() error {
 			Unhandled:   unhandled,
 			Log:         srv.log,
 		}
-		ntab, err := discover.ListenUDP(conn, srv.localnode, cfg)
+		ntab, err := discover.ListenV4(conn, srv.localnode, cfg)
 		if err != nil {
 			return err
 		}
@@ -596,20 +595,21 @@ func (srv *Server) setupDiscovery() error {
 
 	// Discovery V5
 	if srv.DiscoveryV5 {
-		var ntab *discv5.Network
+		cfg := discover.Config{
+			PrivateKey:  srv.PrivateKey,
+			NetRestrict: srv.NetRestrict,
+			Bootnodes:   srv.BootstrapNodesV5,
+			Log:         srv.log,
+		}
 		var err error
 		if sconn != nil {
-			ntab, err = discv5.ListenUDP(srv.PrivateKey, sconn, "", srv.NetRestrict)
+			srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg)
 		} else {
-			ntab, err = discv5.ListenUDP(srv.PrivateKey, conn, "", srv.NetRestrict)
+			srv.DiscV5, err = discover.ListenV5(conn, srv.localnode, cfg)
 		}
 		if err != nil {
 			return err
 		}
-		if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil {
-			return err
-		}
-		srv.DiscV5 = ntab
 	}
 	return nil
 }
@@ -757,7 +757,7 @@ running:
 				// The handshakes are done and it passed all checks.
 				p := srv.launchPeer(c)
 				peers[c.node.ID()] = p
-				srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", truncateName(c.name))
+				srv.log.Debug("Adding p2p peer", "peercount", len(peers), "id", p.ID(), "conn", c.flags, "addr", p.RemoteAddr(), "name", p.Name())
 				srv.dialsched.peerAdded(c)
 				if p.Inbound() {
 					inboundCount++
@@ -854,13 +854,18 @@ func (srv *Server) listenLoop() {
 		<-slots
 
 		var (
-			fd  net.Conn
-			err error
+			fd      net.Conn
+			err     error
+			lastLog time.Time
 		)
 		for {
 			fd, err = srv.listener.Accept()
 			if netutil.IsTemporaryError(err) {
-				srv.log.Debug("Temporary read error", "err", err)
+				if time.Since(lastLog) > 1*time.Second {
+					srv.log.Debug("Temporary read error", "err", err)
+					lastLog = time.Now()
+				}
+				time.Sleep(time.Millisecond * 200)
 				continue
 			} else if err != nil {
 				srv.log.Debug("Read error", "err", err)
@@ -871,8 +876,8 @@ func (srv *Server) listenLoop() {
 		}
 
 		remoteIP := netutil.AddrIP(fd.RemoteAddr())
-		if err := srv.checkInboundConn(fd, remoteIP); err != nil {
-			srv.log.Debug("Rejected inbound connnection", "addr", fd.RemoteAddr(), "err", err)
+		if err := srv.checkInboundConn(remoteIP); err != nil {
+			srv.log.Debug("Rejected inbound connection", "addr", fd.RemoteAddr(), "err", err)
 			fd.Close()
 			slots <- struct{}{}
 			continue
@@ -892,7 +897,7 @@ func (srv *Server) listenLoop() {
 	}
 }
 
-func (srv *Server) checkInboundConn(fd net.Conn, remoteIP net.IP) error {
+func (srv *Server) checkInboundConn(remoteIP net.IP) error {
 	if remoteIP == nil {
 		return nil
 	}
@@ -914,7 +919,13 @@ func (srv *Server) checkInboundConn(fd net.Conn, remoteIP net.IP) error {
 // as a peer. It returns when the connection has been added as a peer
 // or the handshakes have failed.
 func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) error {
-	c := &conn{fd: fd, transport: srv.newTransport(fd), flags: flags, cont: make(chan error)}
+	c := &conn{fd: fd, flags: flags, cont: make(chan error)}
+	if dialDest == nil {
+		c.transport = srv.newTransport(fd, nil)
+	} else {
+		c.transport = srv.newTransport(fd, dialDest.Pubkey())
+	}
+
 	err := srv.setupConn(c, flags, dialDest)
 	if err != nil {
 		c.close(err)
@@ -943,16 +954,12 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
 	}
 
 	// Run the RLPx handshake.
-	remotePubkey, err := c.doEncHandshake(srv.PrivateKey, dialPubkey)
+	remotePubkey, err := c.doEncHandshake(srv.PrivateKey)
 	if err != nil {
 		srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err)
 		return err
 	}
 	if dialDest != nil {
-		// For dialed connections, check that the remote public key matches.
-		if dialPubkey.X.Cmp(remotePubkey.X) != 0 || dialPubkey.Y.Cmp(remotePubkey.Y) != 0 {
-			return DiscUnexpectedIdentity
-		}
 		c.node = dialDest
 	} else {
 		c.node = nodeFromConn(remotePubkey, c.fd)
@@ -994,13 +1001,6 @@ func nodeFromConn(pubkey *ecdsa.PublicKey, conn net.Conn) *enode.Node {
 	return enode.NewV4(pubkey, ip, port, port)
 }
 
-func truncateName(s string) string {
-	if len(s) > 20 {
-		return s[:20] + "..."
-	}
-	return s
-}
-
 // checkpoint sends the conn to run, which performs the
 // post-handshake checks for the stage (posthandshake, addpeer).
 func (srv *Server) checkpoint(c *conn, stage chan<- *conn) error {
diff --git a/p2p/server_test.go b/p2p/server_test.go
index 7dc344a67d..a5b3190aed 100644
--- a/p2p/server_test.go
+++ b/p2p/server_test.go
@@ -18,6 +18,7 @@ package p2p
 
 import (
 	"crypto/ecdsa"
+	"crypto/sha256"
 	"errors"
 	"io"
 	"math/rand"
@@ -31,28 +32,27 @@ import (
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/enr"
-	"golang.org/x/crypto/sha3"
+	"github.com/ethereum/go-ethereum/p2p/rlpx"
 )
 
 type testTransport struct {
-	rpub *ecdsa.PublicKey
-	*rlpx
-
+	*rlpxTransport
+	rpub     *ecdsa.PublicKey
 	closeErr error
 }
 
-func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn) transport {
-	wrapped := newRLPX(fd).(*rlpx)
-	wrapped.rw = newRLPXFrameRW(fd, secrets{
-		MAC:        zero16,
-		AES:        zero16,
-		IngressMAC: sha3.NewLegacyKeccak256(),
-		EgressMAC:  sha3.NewLegacyKeccak256(),
+func newTestTransport(rpub *ecdsa.PublicKey, fd net.Conn, dialDest *ecdsa.PublicKey) transport {
+	wrapped := newRLPX(fd, dialDest).(*rlpxTransport)
+	wrapped.conn.InitWithSecrets(rlpx.Secrets{
+		AES:        make([]byte, 16),
+		MAC:        make([]byte, 16),
+		EgressMAC:  sha256.New(),
+		IngressMAC: sha256.New(),
 	})
-	return &testTransport{rpub: rpub, rlpx: wrapped}
+	return &testTransport{rpub: rpub, rlpxTransport: wrapped}
 }
 
-func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
+func (c *testTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
 	return c.rpub, nil
 }
 
@@ -62,7 +62,7 @@ func (c *testTransport) doProtoHandshake(our *protoHandshake) (*protoHandshake,
 }
 
 func (c *testTransport) close(err error) {
-	c.rlpx.fd.Close()
+	c.conn.Close()
 	c.closeErr = err
 }
 
@@ -76,9 +76,11 @@ func startTestServer(t *testing.T, remoteKey *ecdsa.PublicKey, pf func(*Peer)) *
 		Logger:      testlog.Logger(t, log.LvlTrace),
 	}
 	server := &Server{
-		Config:       config,
-		newPeerHook:  pf,
-		newTransport: func(fd net.Conn) transport { return newTestTransport(remoteKey, fd) },
+		Config:      config,
+		newPeerHook: pf,
+		newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport {
+			return newTestTransport(remoteKey, fd, dialDest)
+		},
 	}
 	if err := server.Start(); err != nil {
 		t.Fatalf("Could not start server: %v", err)
@@ -253,7 +255,7 @@ func TestServerAtCap(t *testing.T) {
 
 	newconn := func(id enode.ID) *conn {
 		fd, _ := net.Pipe()
-		tx := newTestTransport(&trustedNode.PublicKey, fd)
+		tx := newTestTransport(&trustedNode.PublicKey, fd, nil)
 		node := enode.SignNull(new(enr.Record), id)
 		return &conn{fd: fd, transport: tx, flags: inboundConn, node: node, cont: make(chan error)}
 	}
@@ -321,7 +323,7 @@ func TestServerPeerLimits(t *testing.T) {
 			Protocols:   []Protocol{discard},
 			Logger:      testlog.Logger(t, log.LvlTrace),
 		},
-		newTransport: func(fd net.Conn) transport { return tp },
+		newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return tp },
 	}
 	if err := srv.Start(); err != nil {
 		t.Fatalf("couldn't start server: %v", err)
@@ -390,13 +392,6 @@ func TestServerSetupConn(t *testing.T) {
 			wantCalls:    "doEncHandshake,close,",
 			wantCloseErr: errors.New("read error"),
 		},
-		{
-			tt:           &setupTransport{pubkey: clientpub},
-			dialDest:     enode.NewV4(&newkey().PublicKey, nil, 0, 0),
-			flags:        dynDialedConn,
-			wantCalls:    "doEncHandshake,close,",
-			wantCloseErr: DiscUnexpectedIdentity,
-		},
 		{
 			tt:           &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}},
 			dialDest:     enode.NewV4(clientpub, nil, 0, 0),
@@ -437,7 +432,7 @@ func TestServerSetupConn(t *testing.T) {
 			}
 			srv := &Server{
 				Config:       cfg,
-				newTransport: func(fd net.Conn) transport { return test.tt },
+				newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport { return test.tt },
 				log:          cfg.Logger,
 			}
 			if !test.dontstart {
@@ -468,7 +463,7 @@ type setupTransport struct {
 	closeErr error
 }
 
-func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey, dialDest *ecdsa.PublicKey) (*ecdsa.PublicKey, error) {
+func (c *setupTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
 	c.calls += "doEncHandshake,"
 	return c.pubkey, c.encHandshakeErr
 }
@@ -522,9 +517,9 @@ func TestServerInboundThrottle(t *testing.T) {
 			Protocols:   []Protocol{discard},
 			Logger:      testlog.Logger(t, log.LvlTrace),
 		},
-		newTransport: func(fd net.Conn) transport {
+		newTransport: func(fd net.Conn, dialDest *ecdsa.PublicKey) transport {
 			newTransportCalled <- struct{}{}
-			return newRLPX(fd)
+			return newRLPX(fd, dialDest)
 		},
 		listenFunc: func(network, laddr string) (net.Listener, error) {
 			fakeAddr := &net.TCPAddr{IP: net.IP{95, 33, 21, 2}, Port: 4444}
diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go
index 18ec9c69b8..35ccdfb068 100644
--- a/p2p/simulations/adapters/exec.go
+++ b/p2p/simulations/adapters/exec.go
@@ -75,11 +75,11 @@ func (e *ExecAdapter) Name() string {
 
 // NewNode returns a new ExecNode using the given config
 func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
-	if len(config.Services) == 0 {
-		return nil, errors.New("node must have at least one service")
+	if len(config.Lifecycles) == 0 {
+		return nil, errors.New("node must have at least one service lifecycle")
 	}
-	for _, service := range config.Services {
-		if _, exists := serviceFuncs[service]; !exists {
+	for _, service := range config.Lifecycles {
+		if _, exists := lifecycleConstructorFuncs[service]; !exists {
 			return nil, fmt.Errorf("unknown node service %q", service)
 		}
 	}
@@ -115,7 +115,6 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
 	conf.Stack.P2P.EnableMsgEvents = config.EnableMsgEvents
 	conf.Stack.P2P.NoDiscovery = true
 	conf.Stack.P2P.NAT = nil
-	conf.Stack.NoUSB = true
 
 	// Listen on a localhost port, which we set when we
 	// initialise NodeConfig (usually a random port)
@@ -184,7 +183,19 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
 	if err != nil {
 		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
 	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 	defer cancel()
@@ -263,7 +274,7 @@ func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeSta
 func (n *ExecNode) execCommand() *exec.Cmd {
 	return &exec.Cmd{
 		Path: reexec.Self(),
-		Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()},
+		Args: []string{"p2p-node", strings.Join(n.Config.Node.Lifecycles, ","), n.ID.String()},
 	}
 }
 
@@ -362,13 +373,44 @@ type execNodeConfig struct {
 	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
 // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
 // and the node config from an environment variable.
 func execP2PNode() {
-	glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
-	glogger.Verbosity(log.LvlInfo)
-	log.Root().SetHandler(glogger)
+	initLogging()
+
 	statusURL := os.Getenv(envStatusURL)
 	if statusURL == "" {
 		log.Crit("missing " + envStatusURL)
@@ -380,7 +422,7 @@ func execP2PNode() {
 	if stackErr != nil {
 		status.Err = stackErr.Error()
 	} else {
-		status.WSEndpoint = "ws://" + stack.WSEndpoint()
+		status.WSEndpoint = stack.WSEndpoint()
 		status.NodeInfo = stack.Server().NodeInfo()
 	}
 
@@ -400,7 +442,7 @@ func execP2PNode() {
 		defer signal.Stop(sigc)
 		<-sigc
 		log.Info("Received SIGTERM, shutting down...")
-		stack.Stop()
+		stack.Close()
 	}()
 	stack.Wait() // Wait for the stack to exit.
 }
@@ -434,44 +476,35 @@ func startExecNodeStack() (*node.Node, error) {
 		return nil, fmt.Errorf("error creating node stack: %v", err)
 	}
 
-	// register the services, collecting them into a map so we can wrap
-	// them in a snapshot service
-	services := make(map[string]node.Service, len(serviceNames))
+	// Register the services, collecting them into a map so they can
+	// be accessed by the snapshot API.
+	services := make(map[string]node.Lifecycle, len(serviceNames))
 	for _, name := range serviceNames {
-		serviceFunc, exists := serviceFuncs[name]
+		lifecycleFunc, exists := lifecycleConstructorFuncs[name]
 		if !exists {
 			return nil, fmt.Errorf("unknown node service %q", err)
 		}
-		constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) {
-			ctx := &ServiceContext{
-				RPCDialer:   &wsRPCDialer{addrs: conf.PeerAddrs},
-				NodeContext: nodeCtx,
-				Config:      conf.Node,
-			}
-			if conf.Snapshots != nil {
-				ctx.Snapshot = conf.Snapshots[name]
-			}
-			service, err := serviceFunc(ctx)
-			if err != nil {
-				return nil, err
-			}
-			services[name] = service
-			return service, nil
+		ctx := &ServiceContext{
+			RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs},
+			Config:    conf.Node,
+		}
+		if conf.Snapshots != nil {
+			ctx.Snapshot = conf.Snapshots[name]
 		}
-		if err := stack.Register(constructor); err != nil {
-			return stack, fmt.Errorf("error registering service %q: %v", name, err)
+		service, err := lifecycleFunc(ctx, stack)
+		if err != nil {
+			return nil, err
 		}
+		services[name] = service
 	}
 
-	// register the snapshot service
-	err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
-		return &snapshotService{services}, nil
-	})
-	if err != nil {
-		return stack, fmt.Errorf("error starting snapshot service: %v", err)
-	}
+	// Add the snapshot API.
+	stack.RegisterAPIs([]rpc.API{{
+		Namespace: "simulation",
+		Version:   "1.0",
+		Service:   SnapshotAPI{services},
+	}})
 
-	// start the stack
 	if err = stack.Start(); err != nil {
 		err = fmt.Errorf("error starting stack: %v", err)
 	}
@@ -490,35 +523,9 @@ type nodeStartupJSON struct {
 	NodeInfo   *p2p.NodeInfo
 }
 
-// snapshotService is a node.Service which wraps a list of services and
-// exposes an API to generate a snapshot of those services
-type snapshotService struct {
-	services map[string]node.Service
-}
-
-func (s *snapshotService) APIs() []rpc.API {
-	return []rpc.API{{
-		Namespace: "simulation",
-		Version:   "1.0",
-		Service:   SnapshotAPI{s.services},
-	}}
-}
-
-func (s *snapshotService) Protocols() []p2p.Protocol {
-	return nil
-}
-
-func (s *snapshotService) Start(*p2p.Server) error {
-	return nil
-}
-
-func (s *snapshotService) Stop() error {
-	return nil
-}
-
 // SnapshotAPI provides an RPC method to create snapshots of services
 type SnapshotAPI struct {
-	services map[string]node.Service
+	services map[string]node.Lifecycle
 }
 
 func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go
index 651d9546ae..1cb26a8ea0 100644
--- a/p2p/simulations/adapters/inproc.go
+++ b/p2p/simulations/adapters/inproc.go
@@ -37,29 +37,21 @@ import (
 // SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
 // connects them using net.Pipe
 type SimAdapter struct {
-	pipe     func() (net.Conn, net.Conn, error)
-	mtx      sync.RWMutex
-	nodes    map[enode.ID]*SimNode
-	services map[string]ServiceFunc
+	pipe       func() (net.Conn, net.Conn, error)
+	mtx        sync.RWMutex
+	nodes      map[enode.ID]*SimNode
+	lifecycles LifecycleConstructors
 }
 
 // NewSimAdapter creates a SimAdapter which is capable of running in-memory
 // simulation nodes running any of the given services (the services to run on a
 // particular node are passed to the NewNode function in the NodeConfig)
 // the adapter uses a net.Pipe for in-memory simulated network connections
-func NewSimAdapter(services map[string]ServiceFunc) *SimAdapter {
+func NewSimAdapter(services LifecycleConstructors) *SimAdapter {
 	return &SimAdapter{
-		pipe:     pipes.NetPipe,
-		nodes:    make(map[enode.ID]*SimNode),
-		services: services,
-	}
-}
-
-func NewTCPAdapter(services map[string]ServiceFunc) *SimAdapter {
-	return &SimAdapter{
-		pipe:     pipes.TCPPipe,
-		nodes:    make(map[enode.ID]*SimNode),
-		services: services,
+		pipe:       pipes.NetPipe,
+		nodes:      make(map[enode.ID]*SimNode),
+		lifecycles: services,
 	}
 }
 
@@ -85,11 +77,11 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
 	}
 
 	// check the services are valid
-	if len(config.Services) == 0 {
+	if len(config.Lifecycles) == 0 {
 		return nil, errors.New("node must have at least one service")
 	}
-	for _, service := range config.Services {
-		if _, exists := s.services[service]; !exists {
+	for _, service := range config.Lifecycles {
+		if _, exists := s.lifecycles[service]; !exists {
 			return nil, fmt.Errorf("unknown node service %q", service)
 		}
 	}
@@ -107,8 +99,8 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
 			Dialer:          s,
 			EnableMsgEvents: config.EnableMsgEvents,
 		},
-		NoUSB:  true,
-		Logger: log.New("node.id", id.String()),
+		ExternalSigner: config.ExternalSigner,
+		Logger:         log.New("node.id", id.String()),
 	})
 	if err != nil {
 		return nil, err
@@ -119,7 +111,7 @@ func (s *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
 		config:  config,
 		node:    n,
 		adapter: s,
-		running: make(map[string]node.Service),
+		running: make(map[string]node.Lifecycle),
 	}
 	s.nodes[id] = simNode
 	return simNode, nil
@@ -155,11 +147,7 @@ func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) {
 	if !ok {
 		return nil, fmt.Errorf("unknown node: %s", id)
 	}
-	handler, err := node.node.RPCHandler()
-	if err != nil {
-		return nil, err
-	}
-	return rpc.DialInProc(handler), nil
+	return node.node.Attach()
 }
 
 // GetNode returns the node with the given ID if it exists
@@ -179,7 +167,7 @@ type SimNode struct {
 	config       *NodeConfig
 	adapter      *SimAdapter
 	node         *node.Node
-	running      map[string]node.Service
+	running      map[string]node.Lifecycle
 	client       *rpc.Client
 	registerOnce sync.Once
 }
@@ -227,7 +215,7 @@ func (sn *SimNode) ServeRPC(conn *websocket.Conn) error {
 // simulation_snapshot RPC method
 func (sn *SimNode) Snapshots() (map[string][]byte, error) {
 	sn.lock.RLock()
-	services := make(map[string]node.Service, len(sn.running))
+	services := make(map[string]node.Lifecycle, len(sn.running))
 	for name, service := range sn.running {
 		services[name] = service
 	}
@@ -252,35 +240,29 @@ func (sn *SimNode) Snapshots() (map[string][]byte, error) {
 
 // Start registers the services and starts the underlying devp2p node
 func (sn *SimNode) Start(snapshots map[string][]byte) error {
-	newService := func(name string) func(ctx *node.ServiceContext) (node.Service, error) {
-		return func(nodeCtx *node.ServiceContext) (node.Service, error) {
+	// ensure we only register the services once in the case of the node
+	// being stopped and then started again
+	var regErr error
+	sn.registerOnce.Do(func() {
+		for _, name := range sn.config.Lifecycles {
 			ctx := &ServiceContext{
-				RPCDialer:   sn.adapter,
-				NodeContext: nodeCtx,
-				Config:      sn.config,
+				RPCDialer: sn.adapter,
+				Config:    sn.config,
 			}
 			if snapshots != nil {
 				ctx.Snapshot = snapshots[name]
 			}
-			serviceFunc := sn.adapter.services[name]
-			service, err := serviceFunc(ctx)
+			serviceFunc := sn.adapter.lifecycles[name]
+			service, err := serviceFunc(ctx, sn.node)
 			if err != nil {
-				return nil, err
-			}
-			sn.running[name] = service
-			return service, nil
-		}
-	}
-
-	// ensure we only register the services once in the case of the node
-	// being stopped and then started again
-	var regErr error
-	sn.registerOnce.Do(func() {
-		for _, name := range sn.config.Services {
-			if err := sn.node.Register(newService(name)); err != nil {
 				regErr = err
 				break
 			}
+			// if the service has already been registered, don't register it again.
+			if _, ok := sn.running[name]; ok {
+				continue
+			}
+			sn.running[name] = service
 		}
 	})
 	if regErr != nil {
@@ -292,13 +274,12 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error {
 	}
 
 	// create an in-process RPC client
-	handler, err := sn.node.RPCHandler()
+	client, err := sn.node.Attach()
 	if err != nil {
 		return err
 	}
-
 	sn.lock.Lock()
-	sn.client = rpc.DialInProc(handler)
+	sn.client = client
 	sn.lock.Unlock()
 
 	return nil
@@ -312,21 +293,21 @@ func (sn *SimNode) Stop() error {
 		sn.client = nil
 	}
 	sn.lock.Unlock()
-	return sn.node.Stop()
+	return sn.node.Close()
 }
 
 // Service returns a running service by name
-func (sn *SimNode) Service(name string) node.Service {
+func (sn *SimNode) Service(name string) node.Lifecycle {
 	sn.lock.RLock()
 	defer sn.lock.RUnlock()
 	return sn.running[name]
 }
 
 // Services returns a copy of the underlying services
-func (sn *SimNode) Services() []node.Service {
+func (sn *SimNode) Services() []node.Lifecycle {
 	sn.lock.RLock()
 	defer sn.lock.RUnlock()
-	services := make([]node.Service, 0, len(sn.running))
+	services := make([]node.Lifecycle, 0, len(sn.running))
 	for _, service := range sn.running {
 		services = append(services, service)
 	}
@@ -334,10 +315,10 @@ func (sn *SimNode) Services() []node.Service {
 }
 
 // ServiceMap returns a map by names of the underlying services
-func (sn *SimNode) ServiceMap() map[string]node.Service {
+func (sn *SimNode) ServiceMap() map[string]node.Lifecycle {
 	sn.lock.RLock()
 	defer sn.lock.RUnlock()
-	services := make(map[string]node.Service, len(sn.running))
+	services := make(map[string]node.Lifecycle, len(sn.running))
 	for name, service := range sn.running {
 		services[name] = service
 	}
diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go
index ec22b712c3..1da464a10d 100644
--- a/p2p/simulations/adapters/types.go
+++ b/p2p/simulations/adapters/types.go
@@ -96,17 +96,20 @@ type NodeConfig struct {
 	// Use an existing database instead of a temporary one if non-empty
 	DataDir string
 
-	// Services are the names of the services which should be run when
-	// starting the node (for SimNodes it should be the names of services
-	// contained in SimAdapter.services, for other nodes it should be
-	// services registered by calling the RegisterService function)
-	Services []string
+	// Lifecycles are the names of the service lifecycles which should be run when
+	// starting the node (for SimNodes it should be the names of service lifecycles
+	// contained in SimAdapter.lifecycles, for other nodes it should be
+	// service lifecycles registered by calling the RegisterLifecycle function)
+	Lifecycles []string
 
 	// Properties are the names of the properties this node should hold
 	// within running services (e.g. "bootnode", "lightnode" or any custom values)
 	// These values need to be checked and acted upon by node Services
 	Properties []string
 
+	// ExternalSigner specifies an external URI for a clef-type signer
+	ExternalSigner string
+
 	// Enode
 	node *enode.Node
 
@@ -117,6 +120,17 @@ type NodeConfig struct {
 	Reachable func(id enode.ID) bool
 
 	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
@@ -125,10 +139,12 @@ type nodeConfigJSON struct {
 	ID              string   `json:"id"`
 	PrivateKey      string   `json:"private_key"`
 	Name            string   `json:"name"`
-	Services        []string `json:"services"`
+	Lifecycles      []string `json:"lifecycles"`
 	Properties      []string `json:"properties"`
 	EnableMsgEvents bool     `json:"enable_msg_events"`
 	Port            uint16   `json:"port"`
+	LogFile         string   `json:"logfile"`
+	LogVerbosity    int      `json:"log_verbosity"`
 }
 
 // MarshalJSON implements the json.Marshaler interface by encoding the config
@@ -137,10 +153,12 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) {
 	confJSON := nodeConfigJSON{
 		ID:              n.ID.String(),
 		Name:            n.Name,
-		Services:        n.Services,
+		Lifecycles:      n.Lifecycles,
 		Properties:      n.Properties,
 		Port:            n.Port,
 		EnableMsgEvents: n.EnableMsgEvents,
+		LogFile:         n.LogFile,
+		LogVerbosity:    int(n.LogVerbosity),
 	}
 	if n.PrivateKey != nil {
 		confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey))
@@ -175,10 +193,12 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error {
 	}
 
 	n.Name = confJSON.Name
-	n.Services = confJSON.Services
+	n.Lifecycles = confJSON.Lifecycles
 	n.Properties = confJSON.Properties
 	n.Port = confJSON.Port
 	n.EnableMsgEvents = confJSON.EnableMsgEvents
+	n.LogFile = confJSON.LogFile
+	n.LogVerbosity = log.Lvl(confJSON.LogVerbosity)
 
 	return nil
 }
@@ -208,6 +228,7 @@ func RandomNodeConfig() *NodeConfig {
 		Name:            fmt.Sprintf("node_%s", enodId.String()),
 		Port:            port,
 		EnableMsgEvents: true,
+		LogVerbosity:    log.LvlInfo,
 	}
 }
 
@@ -233,9 +254,8 @@ func assignTCPPort() (uint16, error) {
 type ServiceContext struct {
 	RPCDialer
 
-	NodeContext *node.ServiceContext
-	Config      *NodeConfig
-	Snapshot    []byte
+	Config   *NodeConfig
+	Snapshot []byte
 }
 
 // RPCDialer is used when initialising services which need to connect to
@@ -245,27 +265,29 @@ type RPCDialer interface {
 	DialRPC(id enode.ID) (*rpc.Client, error)
 }
 
-// Services is a collection of services which can be run in a simulation
-type Services map[string]ServiceFunc
+// LifecycleConstructor allows a Lifecycle to be constructed during node start-up.
+// While the service-specific package usually takes care of Lifecycle creation and registration,
+// for testing purposes, it is useful to be able to construct a Lifecycle on spot.
+type LifecycleConstructor func(ctx *ServiceContext, stack *node.Node) (node.Lifecycle, error)
 
-// ServiceFunc returns a node.Service which can be used to boot a devp2p node
-type ServiceFunc func(ctx *ServiceContext) (node.Service, error)
+// LifecycleConstructors stores LifecycleConstructor functions to call during node start-up.
+type LifecycleConstructors map[string]LifecycleConstructor
 
-// serviceFuncs is a map of registered services which are used to boot devp2p
+// lifecycleConstructorFuncs is a map of registered services which are used to boot devp2p
 // nodes
-var serviceFuncs = make(Services)
+var lifecycleConstructorFuncs = make(LifecycleConstructors)
 
-// RegisterServices registers the given Services which can then be used to
+// RegisterLifecycles registers the given Services which can then be used to
 // start devp2p nodes using either the Exec or Docker adapters.
 //
 // It should be called in an init function so that it has the opportunity to
 // execute the services before main() is called.
-func RegisterServices(services Services) {
-	for name, f := range services {
-		if _, exists := serviceFuncs[name]; exists {
+func RegisterLifecycles(lifecycles LifecycleConstructors) {
+	for name, f := range lifecycles {
+		if _, exists := lifecycleConstructorFuncs[name]; exists {
 			panic(fmt.Sprintf("node service already exists: %q", name))
 		}
-		serviceFuncs[name] = f
+		lifecycleConstructorFuncs[name] = f
 	}
 
 	// now we have registered the services, run reexec.Init() which will
@@ -300,5 +322,5 @@ func (n *NodeConfig) initEnode(ip net.IP, tcpport int, udpport int) error {
 }
 
 func (n *NodeConfig) initDummyEnode() error {
-	return n.initEnode(net.IPv4(127, 0, 0, 1), 0, 0)
+	return n.initEnode(net.IPv4(127, 0, 0, 1), int(n.Port), 0)
 }
diff --git a/p2p/simulations/connect_test.go b/p2p/simulations/connect_test.go
index 32d18347d8..0154a18b03 100644
--- a/p2p/simulations/connect_test.go
+++ b/p2p/simulations/connect_test.go
@@ -26,8 +26,8 @@ import (
 
 func newTestNetwork(t *testing.T, nodeCount int) (*Network, []enode.ID) {
 	t.Helper()
-	adapter := adapters.NewSimAdapter(adapters.Services{
-		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
+		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 			return NewNoopService(nil), nil
 		},
 	})
diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go
index cde2f3a677..0cddd9b505 100644
--- a/p2p/simulations/examples/ping-pong.go
+++ b/p2p/simulations/examples/ping-pong.go
@@ -31,7 +31,6 @@ import (
 	"github.com/ethereum/go-ethereum/p2p/enode"
 	"github.com/ethereum/go-ethereum/p2p/simulations"
 	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-	"github.com/ethereum/go-ethereum/rpc"
 )
 
 var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
@@ -45,12 +44,14 @@ func main() {
 	log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
 
 	// register a single ping-pong service
-	services := map[string]adapters.ServiceFunc{
-		"ping-pong": func(ctx *adapters.ServiceContext) (node.Service, error) {
-			return newPingPongService(ctx.Config.ID), nil
+	services := map[string]adapters.LifecycleConstructor{
+		"ping-pong": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
+			pps := newPingPongService(ctx.Config.ID)
+			stack.RegisterProtocols(pps.Protocols())
+			return pps, nil
 		},
 	}
-	adapters.RegisterServices(services)
+	adapters.RegisterLifecycles(services)
 
 	// create the NodeAdapter
 	var adapter adapters.NodeAdapter
@@ -110,11 +111,7 @@ func (p *pingPongService) Protocols() []p2p.Protocol {
 	}}
 }
 
-func (p *pingPongService) APIs() []rpc.API {
-	return nil
-}
-
-func (p *pingPongService) Start(server *p2p.Server) error {
+func (p *pingPongService) Start() error {
 	p.log.Info("ping-pong service starting")
 	return nil
 }
diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go
index 51d45ce8f4..27ed5b75d2 100644
--- a/p2p/simulations/http.go
+++ b/p2p/simulations/http.go
@@ -698,7 +698,7 @@ func (s *Server) JSON(w http.ResponseWriter, status int, data interface{}) {
 	json.NewEncoder(w).Encode(data)
 }
 
-// wrapHandler returns a httprouter.Handle which wraps a http.HandlerFunc by
+// wrapHandler returns an httprouter.Handle which wraps an http.HandlerFunc by
 // populating request.Context with any objects from the URL params
 func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
 	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go
index e88999f48b..6d7f0b6d7a 100644
--- a/p2p/simulations/http_test.go
+++ b/p2p/simulations/http_test.go
@@ -64,12 +64,15 @@ type testService struct {
 	state atomic.Value
 }
 
-func newTestService(ctx *adapters.ServiceContext) (node.Service, error) {
+func newTestService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 	svc := &testService{
 		id:    ctx.Config.ID,
 		peers: make(map[enode.ID]*testPeer),
 	}
 	svc.state.Store(ctx.Snapshot)
+
+	stack.RegisterProtocols(svc.Protocols())
+	stack.RegisterAPIs(svc.APIs())
 	return svc, nil
 }
 
@@ -126,7 +129,7 @@ func (t *testService) APIs() []rpc.API {
 	}}
 }
 
-func (t *testService) Start(server *p2p.Server) error {
+func (t *testService) Start() error {
 	return nil
 }
 
@@ -288,7 +291,7 @@ func (t *TestAPI) Events(ctx context.Context) (*rpc.Subscription, error) {
 	return rpcSub, nil
 }
 
-var testServices = adapters.Services{
+var testServices = adapters.LifecycleConstructors{
 	"test": newTestService,
 }
 
diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go
index ef5451e77e..9b5e2c37f5 100644
--- a/p2p/simulations/network.go
+++ b/p2p/simulations/network.go
@@ -110,8 +110,8 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error)
 	}
 
 	// if no services are configured, use the default service
-	if len(conf.Services) == 0 {
-		conf.Services = []string{net.DefaultService}
+	if len(conf.Lifecycles) == 0 {
+		conf.Lifecycles = []string{net.DefaultService}
 	}
 
 	// use the NodeAdapter to create the node
@@ -454,9 +454,8 @@ func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID {
 	if len(excludeIDs) > 0 {
 		// Return the difference of nodeIDs and excludeIDs
 		return filterIDs(nodeIDs, excludeIDs)
-	} else {
-		return nodeIDs
 	}
+	return nodeIDs
 }
 
 // GetNodes returns the existing nodes.
@@ -472,9 +471,8 @@ func (net *Network) getNodes(excludeIDs []enode.ID) []*Node {
 	if len(excludeIDs) > 0 {
 		nodeIDs := net.getNodeIDs(excludeIDs)
 		return net.getNodesByID(nodeIDs)
-	} else {
-		return net.Nodes
 	}
+	return net.Nodes
 }
 
 // GetNodesByID returns existing nodes with the given enode.IDs.
@@ -913,19 +911,19 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn
 		snap.Nodes[i].Snapshots = snapshots
 		for _, addSvc := range addServices {
 			haveSvc := false
-			for _, svc := range snap.Nodes[i].Node.Config.Services {
+			for _, svc := range snap.Nodes[i].Node.Config.Lifecycles {
 				if svc == addSvc {
 					haveSvc = true
 					break
 				}
 			}
 			if !haveSvc {
-				snap.Nodes[i].Node.Config.Services = append(snap.Nodes[i].Node.Config.Services, addSvc)
+				snap.Nodes[i].Node.Config.Lifecycles = append(snap.Nodes[i].Node.Config.Lifecycles, addSvc)
 			}
 		}
 		if len(removeServices) > 0 {
 			var cleanedServices []string
-			for _, svc := range snap.Nodes[i].Node.Config.Services {
+			for _, svc := range snap.Nodes[i].Node.Config.Lifecycles {
 				haveSvc := false
 				for _, rmSvc := range removeServices {
 					if rmSvc == svc {
@@ -938,7 +936,7 @@ func (net *Network) snapshot(addServices []string, removeServices []string) (*Sn
 				}
 
 			}
-			snap.Nodes[i].Node.Config.Services = cleanedServices
+			snap.Nodes[i].Node.Config.Lifecycles = cleanedServices
 		}
 	}
 	for _, conn := range net.Conns {
@@ -1098,7 +1096,6 @@ func (net *Network) executeNodeEvent(e *Event) error {
 func (net *Network) executeConnEvent(e *Event) error {
 	if e.Conn.Up {
 		return net.Connect(e.Conn.One, e.Conn.Other)
-	} else {
-		return net.Disconnect(e.Conn.One, e.Conn.Other)
 	}
+	return net.Disconnect(e.Conn.One, e.Conn.Other)
 }
diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go
index ac1b06a80c..d5651441a2 100644
--- a/p2p/simulations/network_test.go
+++ b/p2p/simulations/network_test.go
@@ -41,8 +41,8 @@ func TestSnapshot(t *testing.T) {
 	// create snapshot from ring network
 
 	// this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting
-	adapter := adapters.NewSimAdapter(adapters.Services{
-		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
+		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 			return NewNoopService(nil), nil
 		},
 	})
@@ -165,8 +165,8 @@ OUTER:
 	// PART II
 	// load snapshot and verify that exactly same connections are formed
 
-	adapter = adapters.NewSimAdapter(adapters.Services{
-		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
+	adapter = adapters.NewSimAdapter(adapters.LifecycleConstructors{
+		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 			return NewNoopService(nil), nil
 		},
 	})
@@ -256,8 +256,8 @@ OuterTwo:
 	t.Run("conns after load", func(t *testing.T) {
 		// Create new network.
 		n := NewNetwork(
-			adapters.NewSimAdapter(adapters.Services{
-				"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
+			adapters.NewSimAdapter(adapters.LifecycleConstructors{
+				"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 					return NewNoopService(nil), nil
 				},
 			}),
@@ -288,7 +288,7 @@ OuterTwo:
 // with each other and that a snapshot fully represents the desired topology
 func TestNetworkSimulation(t *testing.T) {
 	// create simulation network with 20 testService nodes
-	adapter := adapters.NewSimAdapter(adapters.Services{
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
 		"test": newTestService,
 	})
 	network := NewNetwork(adapter, &NetworkConfig{
@@ -437,7 +437,7 @@ func createTestNodesWithProperty(property string, count int, network *Network) (
 // It then tests again whilst excluding a node ID from being returned.
 // If a node ID is not returned, or more node IDs than expected are returned, the test fails.
 func TestGetNodeIDs(t *testing.T) {
-	adapter := adapters.NewSimAdapter(adapters.Services{
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
 		"test": newTestService,
 	})
 	network := NewNetwork(adapter, &NetworkConfig{
@@ -486,7 +486,7 @@ func TestGetNodeIDs(t *testing.T) {
 // It then tests again whilst excluding a node from being returned.
 // If a node is not returned, or more nodes than expected are returned, the test fails.
 func TestGetNodes(t *testing.T) {
-	adapter := adapters.NewSimAdapter(adapters.Services{
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
 		"test": newTestService,
 	})
 	network := NewNetwork(adapter, &NetworkConfig{
@@ -534,7 +534,7 @@ func TestGetNodes(t *testing.T) {
 // TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID
 // If a node is not returned, or more nodes than expected are returned, the test fails.
 func TestGetNodesByID(t *testing.T) {
-	adapter := adapters.NewSimAdapter(adapters.Services{
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
 		"test": newTestService,
 	})
 	network := NewNetwork(adapter, &NetworkConfig{
@@ -579,7 +579,7 @@ func TestGetNodesByID(t *testing.T) {
 // GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created.
 // If a node with a property is not found, or more nodes than expected are returned, the test fails.
 func TestGetNodesByProperty(t *testing.T) {
-	adapter := adapters.NewSimAdapter(adapters.Services{
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
 		"test": newTestService,
 	})
 	network := NewNetwork(adapter, &NetworkConfig{
@@ -624,7 +624,7 @@ func TestGetNodesByProperty(t *testing.T) {
 // GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created.
 // If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails.
 func TestGetNodeIDsByProperty(t *testing.T) {
-	adapter := adapters.NewSimAdapter(adapters.Services{
+	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
 		"test": newTestService,
 	})
 	network := NewNetwork(adapter, &NetworkConfig{
@@ -705,8 +705,8 @@ func benchmarkMinimalServiceTmp(b *testing.B) {
 		// this is a minimal service, whose protocol will close a channel upon run of protocol
 		// making it possible to bench the time it takes for the service to start and protocol actually to be run
 		protoCMap := make(map[enode.ID]map[enode.ID]chan struct{})
-		adapter := adapters.NewSimAdapter(adapters.Services{
-			"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
+		adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
+			"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
 				protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{})
 				svc := NewNoopService(protoCMap[ctx.Config.ID])
 				return svc, nil
diff --git a/p2p/simulations/test.go b/p2p/simulations/test.go
index 687be6d0b8..0edb07b127 100644
--- a/p2p/simulations/test.go
+++ b/p2p/simulations/test.go
@@ -66,7 +66,7 @@ func (t *NoopService) APIs() []rpc.API {
 	return []rpc.API{}
 }
 
-func (t *NoopService) Start(server *p2p.Server) error {
+func (t *NoopService) Start() error {
 	return nil
 }
 
diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go
deleted file mode 100644
index 91b9704c79..0000000000
--- a/p2p/testing/peerpool.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2018 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 .
-
-package testing
-
-import (
-	"fmt"
-	"sync"
-
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-)
-
-type TestPeer interface {
-	ID() enode.ID
-	Drop()
-}
-
-// TestPeerPool is an example peerPool to demonstrate registration of peer connections
-type TestPeerPool struct {
-	lock  sync.Mutex
-	peers map[enode.ID]TestPeer
-}
-
-func NewTestPeerPool() *TestPeerPool {
-	return &TestPeerPool{peers: make(map[enode.ID]TestPeer)}
-}
-
-func (p *TestPeerPool) Add(peer TestPeer) {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-	log.Trace(fmt.Sprintf("pp add peer  %v", peer.ID()))
-	p.peers[peer.ID()] = peer
-
-}
-
-func (p *TestPeerPool) Remove(peer TestPeer) {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-	delete(p.peers, peer.ID())
-}
-
-func (p *TestPeerPool) Has(id enode.ID) bool {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-	_, ok := p.peers[id]
-	return ok
-}
-
-func (p *TestPeerPool) Get(id enode.ID) TestPeer {
-	p.lock.Lock()
-	defer p.lock.Unlock()
-	return p.peers[id]
-}
diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go
deleted file mode 100644
index 58dac93c36..0000000000
--- a/p2p/testing/protocolsession.go
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright 2018 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 .
-
-package testing
-
-import (
-	"errors"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-)
-
-var errTimedOut = errors.New("timed out")
-
-// ProtocolSession is a quasi simulation of a pivot node running
-// a service and a number of dummy peers that can send (trigger) or
-// receive (expect) messages
-type ProtocolSession struct {
-	Server  *p2p.Server
-	Nodes   []*enode.Node
-	adapter *adapters.SimAdapter
-	events  chan *p2p.PeerEvent
-}
-
-// Exchange is the basic units of protocol tests
-// the triggers and expects in the arrays are run immediately and asynchronously
-// thus one cannot have multiple expects for the SAME peer with DIFFERENT message types
-// because it's unpredictable which expect will receive which message
-// (with expect #1 and #2, messages might be sent #2 and #1, and both expects will complain about wrong message code)
-// an exchange is defined on a session
-type Exchange struct {
-	Label    string
-	Triggers []Trigger
-	Expects  []Expect
-	Timeout  time.Duration
-}
-
-// Trigger is part of the exchange, incoming message for the pivot node
-// sent by a peer
-type Trigger struct {
-	Msg     interface{}   // type of message to be sent
-	Code    uint64        // code of message is given
-	Peer    enode.ID      // the peer to send the message to
-	Timeout time.Duration // timeout duration for the sending
-}
-
-// Expect is part of an exchange, outgoing message from the pivot node
-// received by a peer
-type Expect struct {
-	Msg     interface{}   // type of message to expect
-	Code    uint64        // code of message is now given
-	Peer    enode.ID      // the peer that expects the message
-	Timeout time.Duration // timeout duration for receiving
-}
-
-// Disconnect represents a disconnect event, used and checked by TestDisconnected
-type Disconnect struct {
-	Peer  enode.ID // discconnected peer
-	Error error    // disconnect reason
-}
-
-// trigger sends messages from peers
-func (s *ProtocolSession) trigger(trig Trigger) error {
-	simNode, ok := s.adapter.GetNode(trig.Peer)
-	if !ok {
-		return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.Nodes))
-	}
-	mockNode, ok := simNode.Services()[0].(*mockNode)
-	if !ok {
-		return fmt.Errorf("trigger: peer %v is not a mock", trig.Peer)
-	}
-
-	errc := make(chan error)
-
-	go func() {
-		log.Trace(fmt.Sprintf("trigger %v (%v)....", trig.Msg, trig.Code))
-		errc <- mockNode.Trigger(&trig)
-		log.Trace(fmt.Sprintf("triggered %v (%v)", trig.Msg, trig.Code))
-	}()
-
-	t := trig.Timeout
-	if t == time.Duration(0) {
-		t = 1000 * time.Millisecond
-	}
-	select {
-	case err := <-errc:
-		return err
-	case <-time.After(t):
-		return fmt.Errorf("timout expecting %v to send to peer %v", trig.Msg, trig.Peer)
-	}
-}
-
-// expect checks an expectation of a message sent out by the pivot node
-func (s *ProtocolSession) expect(exps []Expect) error {
-	// construct a map of expectations for each node
-	peerExpects := make(map[enode.ID][]Expect)
-	for _, exp := range exps {
-		if exp.Msg == nil {
-			return errors.New("no message to expect")
-		}
-		peerExpects[exp.Peer] = append(peerExpects[exp.Peer], exp)
-	}
-
-	// construct a map of mockNodes for each node
-	mockNodes := make(map[enode.ID]*mockNode)
-	for nodeID := range peerExpects {
-		simNode, ok := s.adapter.GetNode(nodeID)
-		if !ok {
-			return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.Nodes))
-		}
-		mockNode, ok := simNode.Services()[0].(*mockNode)
-		if !ok {
-			return fmt.Errorf("trigger: peer %v is not a mock", nodeID)
-		}
-		mockNodes[nodeID] = mockNode
-	}
-
-	// done chanell cancels all created goroutines when function returns
-	done := make(chan struct{})
-	defer close(done)
-	// errc catches the first error from
-	errc := make(chan error)
-
-	wg := &sync.WaitGroup{}
-	wg.Add(len(mockNodes))
-	for nodeID, mockNode := range mockNodes {
-		nodeID := nodeID
-		mockNode := mockNode
-		go func() {
-			defer wg.Done()
-
-			// Sum all Expect timeouts to give the maximum
-			// time for all expectations to finish.
-			// mockNode.Expect checks all received messages against
-			// a list of expected messages and timeout for each
-			// of them can not be checked separately.
-			var t time.Duration
-			for _, exp := range peerExpects[nodeID] {
-				if exp.Timeout == time.Duration(0) {
-					t += 2000 * time.Millisecond
-				} else {
-					t += exp.Timeout
-				}
-			}
-			alarm := time.NewTimer(t)
-			defer alarm.Stop()
-
-			// expectErrc is used to check if error returned
-			// from mockNode.Expect is not nil and to send it to
-			// errc only in that case.
-			// done channel will be closed when function
-			expectErrc := make(chan error)
-			go func() {
-				select {
-				case expectErrc <- mockNode.Expect(peerExpects[nodeID]...):
-				case <-done:
-				case <-alarm.C:
-				}
-			}()
-
-			select {
-			case err := <-expectErrc:
-				if err != nil {
-					select {
-					case errc <- err:
-					case <-done:
-					case <-alarm.C:
-						errc <- errTimedOut
-					}
-				}
-			case <-done:
-			case <-alarm.C:
-				errc <- errTimedOut
-			}
-
-		}()
-	}
-
-	go func() {
-		wg.Wait()
-		// close errc when all goroutines finish to return nill err from errc
-		close(errc)
-	}()
-
-	return <-errc
-}
-
-// TestExchanges tests a series of exchanges against the session
-func (s *ProtocolSession) TestExchanges(exchanges ...Exchange) error {
-	for i, e := range exchanges {
-		if err := s.testExchange(e); err != nil {
-			return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err)
-		}
-		log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label))
-	}
-	return nil
-}
-
-// testExchange tests a single Exchange.
-// Default timeout value is 2 seconds.
-func (s *ProtocolSession) testExchange(e Exchange) error {
-	errc := make(chan error)
-	done := make(chan struct{})
-	defer close(done)
-
-	go func() {
-		for _, trig := range e.Triggers {
-			err := s.trigger(trig)
-			if err != nil {
-				errc <- err
-				return
-			}
-		}
-
-		select {
-		case errc <- s.expect(e.Expects):
-		case <-done:
-		}
-	}()
-
-	// time out globally or finish when all expectations satisfied
-	t := e.Timeout
-	if t == 0 {
-		t = 2000 * time.Millisecond
-	}
-	alarm := time.NewTimer(t)
-	defer alarm.Stop()
-	select {
-	case err := <-errc:
-		return err
-	case <-alarm.C:
-		return errTimedOut
-	}
-}
-
-// TestDisconnected tests the disconnections given as arguments
-// the disconnect structs describe what disconnect error is expected on which peer
-func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error {
-	expects := make(map[enode.ID]error)
-	for _, disconnect := range disconnects {
-		expects[disconnect.Peer] = disconnect.Error
-	}
-
-	timeout := time.After(time.Second)
-	for len(expects) > 0 {
-		select {
-		case event := <-s.events:
-			if event.Type != p2p.PeerEventTypeDrop {
-				continue
-			}
-			expectErr, ok := expects[event.Peer]
-			if !ok {
-				continue
-			}
-
-			if !(expectErr == nil && event.Error == "" || expectErr != nil && expectErr.Error() == event.Error) {
-				return fmt.Errorf("unexpected error on peer %v. expected '%v', got '%v'", event.Peer, expectErr, event.Error)
-			}
-			delete(expects, event.Peer)
-		case <-timeout:
-			return fmt.Errorf("timed out waiting for peers to disconnect")
-		}
-	}
-	return nil
-}
diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go
deleted file mode 100644
index b80abcc264..0000000000
--- a/p2p/testing/protocoltester.go
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright 2018 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 .
-
-/*
-the p2p/testing package provides a unit test scheme to check simple
-protocol message exchanges with one pivot node and a number of dummy peers
-The pivot test node runs a node.Service, the dummy peers run a mock node
-that can be used to send and receive messages
-*/
-
-package testing
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"strings"
-	"sync"
-
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/node"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/p2p/simulations"
-	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
-	"github.com/ethereum/go-ethereum/rlp"
-	"github.com/ethereum/go-ethereum/rpc"
-)
-
-// ProtocolTester is the tester environment used for unit testing protocol
-// message exchanges. It uses p2p/simulations framework
-type ProtocolTester struct {
-	*ProtocolSession
-	network *simulations.Network
-}
-
-// NewProtocolTester constructs a new ProtocolTester
-// it takes as argument the pivot node id, the number of dummy peers and the
-// protocol run function called on a peer connection by the p2p server
-func NewProtocolTester(prvkey *ecdsa.PrivateKey, nodeCount int, run func(*p2p.Peer, p2p.MsgReadWriter) error) *ProtocolTester {
-	services := adapters.Services{
-		"test": func(ctx *adapters.ServiceContext) (node.Service, error) {
-			return &testNode{run}, nil
-		},
-		"mock": func(ctx *adapters.ServiceContext) (node.Service, error) {
-			return newMockNode(), nil
-		},
-	}
-	adapter := adapters.NewSimAdapter(services)
-	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{})
-	nodeConfig := &adapters.NodeConfig{
-		PrivateKey:      prvkey,
-		EnableMsgEvents: true,
-		Services:        []string{"test"},
-	}
-	if _, err := net.NewNodeWithConfig(nodeConfig); err != nil {
-		panic(err.Error())
-	}
-	if err := net.Start(nodeConfig.ID); err != nil {
-		panic(err.Error())
-	}
-
-	node := net.GetNode(nodeConfig.ID).Node.(*adapters.SimNode)
-	peers := make([]*adapters.NodeConfig, nodeCount)
-	nodes := make([]*enode.Node, nodeCount)
-	for i := 0; i < nodeCount; i++ {
-		peers[i] = adapters.RandomNodeConfig()
-		peers[i].Services = []string{"mock"}
-		if _, err := net.NewNodeWithConfig(peers[i]); err != nil {
-			panic(fmt.Sprintf("error initializing peer %v: %v", peers[i].ID, err))
-		}
-		if err := net.Start(peers[i].ID); err != nil {
-			panic(fmt.Sprintf("error starting peer %v: %v", peers[i].ID, err))
-		}
-		nodes[i] = peers[i].Node()
-	}
-	events := make(chan *p2p.PeerEvent, 1000)
-	node.SubscribeEvents(events)
-	ps := &ProtocolSession{
-		Server:  node.Server(),
-		Nodes:   nodes,
-		adapter: adapter,
-		events:  events,
-	}
-	self := &ProtocolTester{
-		ProtocolSession: ps,
-		network:         net,
-	}
-
-	self.Connect(nodeConfig.ID, peers...)
-
-	return self
-}
-
-// Stop stops the p2p server
-func (t *ProtocolTester) Stop() {
-	t.Server.Stop()
-	t.network.Shutdown()
-}
-
-// Connect brings up the remote peer node and connects it using the
-// p2p/simulations network connection with the in memory network adapter
-func (t *ProtocolTester) Connect(selfID enode.ID, peers ...*adapters.NodeConfig) {
-	for _, peer := range peers {
-		log.Trace(fmt.Sprintf("connect to %v", peer.ID))
-		if err := t.network.Connect(selfID, peer.ID); err != nil {
-			panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err))
-		}
-	}
-
-}
-
-// testNode wraps a protocol run function and implements the node.Service
-// interface
-type testNode struct {
-	run func(*p2p.Peer, p2p.MsgReadWriter) error
-}
-
-func (t *testNode) Protocols() []p2p.Protocol {
-	return []p2p.Protocol{{
-		Length: 100,
-		Run:    t.run,
-	}}
-}
-
-func (t *testNode) APIs() []rpc.API {
-	return nil
-}
-
-func (t *testNode) Start(server *p2p.Server) error {
-	return nil
-}
-
-func (t *testNode) Stop() error {
-	return nil
-}
-
-// mockNode is a testNode which doesn't actually run a protocol, instead
-// exposing channels so that tests can manually trigger and expect certain
-// messages
-type mockNode struct {
-	testNode
-
-	trigger  chan *Trigger
-	expect   chan []Expect
-	err      chan error
-	stop     chan struct{}
-	stopOnce sync.Once
-}
-
-func newMockNode() *mockNode {
-	mock := &mockNode{
-		trigger: make(chan *Trigger),
-		expect:  make(chan []Expect),
-		err:     make(chan error),
-		stop:    make(chan struct{}),
-	}
-	mock.testNode.run = mock.Run
-	return mock
-}
-
-// Run is a protocol run function which just loops waiting for tests to
-// instruct it to either trigger or expect a message from the peer
-func (m *mockNode) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
-	for {
-		select {
-		case trig := <-m.trigger:
-			wmsg := Wrap(trig.Msg)
-			m.err <- p2p.Send(rw, trig.Code, wmsg)
-		case exps := <-m.expect:
-			m.err <- expectMsgs(rw, exps)
-		case <-m.stop:
-			return nil
-		}
-	}
-}
-
-func (m *mockNode) Trigger(trig *Trigger) error {
-	m.trigger <- trig
-	return <-m.err
-}
-
-func (m *mockNode) Expect(exp ...Expect) error {
-	m.expect <- exp
-	return <-m.err
-}
-
-func (m *mockNode) Stop() error {
-	m.stopOnce.Do(func() { close(m.stop) })
-	return nil
-}
-
-func expectMsgs(rw p2p.MsgReadWriter, exps []Expect) error {
-	matched := make([]bool, len(exps))
-	for {
-		msg, err := rw.ReadMsg()
-		if err != nil {
-			if err == io.EOF {
-				break
-			}
-			return err
-		}
-		actualContent, err := ioutil.ReadAll(msg.Payload)
-		if err != nil {
-			return err
-		}
-		var found bool
-		for i, exp := range exps {
-			if exp.Code == msg.Code && bytes.Equal(actualContent, mustEncodeMsg(Wrap(exp.Msg))) {
-				if matched[i] {
-					return fmt.Errorf("message #%d received two times", i)
-				}
-				matched[i] = true
-				found = true
-				break
-			}
-		}
-		if !found {
-			expected := make([]string, 0)
-			for i, exp := range exps {
-				if matched[i] {
-					continue
-				}
-				expected = append(expected, fmt.Sprintf("code %d payload %x", exp.Code, mustEncodeMsg(Wrap(exp.Msg))))
-			}
-			return fmt.Errorf("unexpected message code %d payload %x, expected %s", msg.Code, actualContent, strings.Join(expected, " or "))
-		}
-		done := true
-		for _, m := range matched {
-			if !m {
-				done = false
-				break
-			}
-		}
-		if done {
-			return nil
-		}
-	}
-	for i, m := range matched {
-		if !m {
-			return fmt.Errorf("expected message #%d not received", i)
-		}
-	}
-	return nil
-}
-
-// mustEncodeMsg uses rlp to encode a message.
-// In case of error it panics.
-func mustEncodeMsg(msg interface{}) []byte {
-	contentEnc, err := rlp.EncodeToBytes(msg)
-	if err != nil {
-		panic("content encode error: " + err.Error())
-	}
-	return contentEnc
-}
-
-type WrappedMsg struct {
-	Context []byte
-	Size    uint32
-	Payload []byte
-}
-
-func Wrap(msg interface{}) interface{} {
-	data, _ := rlp.EncodeToBytes(msg)
-	return &WrappedMsg{
-		Size:    uint32(len(data)),
-		Payload: data,
-	}
-}
diff --git a/p2p/tracker/tracker.go b/p2p/tracker/tracker.go
new file mode 100644
index 0000000000..69a49087e2
--- /dev/null
+++ b/p2p/tracker/tracker.go
@@ -0,0 +1,205 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package tracker
+
+import (
+	"container/list"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/metrics"
+)
+
+const (
+	// trackedGaugeName is the prefix of the per-packet request tracking.
+	trackedGaugeName = "p2p/tracked"
+
+	// lostMeterName is the prefix of the per-packet request expirations.
+	lostMeterName = "p2p/lost"
+
+	// staleMeterName is the prefix of the per-packet stale responses.
+	staleMeterName = "p2p/stale"
+
+	// waitHistName is the prefix of the per-packet (req only) waiting time histograms.
+	waitHistName = "p2p/wait"
+
+	// maxTrackedPackets is a huge number to act as a failsafe on the number of
+	// pending requests the node will track. It should never be hit unless an
+	// attacker figures out a way to spin requests.
+	maxTrackedPackets = 100000
+)
+
+// request tracks sent network requests which have not yet received a response.
+type request struct {
+	peer    string
+	version uint // Protocol version
+
+	reqCode uint64 // Protocol message code of the request
+	resCode uint64 // Protocol message code of the expected response
+
+	time   time.Time     // Timestamp when the request was made
+	expire *list.Element // Expiration marker to untrack it
+}
+
+// Tracker is a pending network request tracker to measure how much time it takes
+// a remote peer to respond.
+type Tracker struct {
+	protocol string        // Protocol capability identifier for the metrics
+	timeout  time.Duration // Global timeout after which to drop a tracked packet
+
+	pending map[uint64]*request // Currently pending requests
+	expire  *list.List          // Linked list tracking the expiration order
+	wake    *time.Timer         // Timer tracking the expiration of the next item
+
+	lock sync.Mutex // Lock protecting from concurrent updates
+}
+
+// New creates a new network request tracker to monitor how much time it takes to
+// fill certain requests and how individual peers perform.
+func New(protocol string, timeout time.Duration) *Tracker {
+	return &Tracker{
+		protocol: protocol,
+		timeout:  timeout,
+		pending:  make(map[uint64]*request),
+		expire:   list.New(),
+	}
+}
+
+// Track adds a network request to the tracker to wait for a response to arrive
+// or until the request it cancelled or times out.
+func (t *Tracker) Track(peer string, version uint, reqCode uint64, resCode uint64, id uint64) {
+	if !metrics.Enabled {
+		return
+	}
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	// If there's a duplicate request, we've just random-collided (or more probably,
+	// we have a bug), report it. We could also add a metric, but we're not really
+	// expecting ourselves to be buggy, so a noisy warning should be enough.
+	if _, ok := t.pending[id]; ok {
+		log.Error("Network request id collision", "protocol", t.protocol, "version", version, "code", reqCode, "id", id)
+		return
+	}
+	// If we have too many pending requests, bail out instead of leaking memory
+	if pending := len(t.pending); pending >= maxTrackedPackets {
+		log.Error("Request tracker exceeded allowance", "pending", pending, "peer", peer, "protocol", t.protocol, "version", version, "code", reqCode)
+		return
+	}
+	// Id doesn't exist yet, start tracking it
+	t.pending[id] = &request{
+		peer:    peer,
+		version: version,
+		reqCode: reqCode,
+		resCode: resCode,
+		time:    time.Now(),
+		expire:  t.expire.PushBack(id),
+	}
+	g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, version, reqCode)
+	metrics.GetOrRegisterGauge(g, nil).Inc(1)
+
+	// If we've just inserted the first item, start the expiration timer
+	if t.wake == nil {
+		t.wake = time.AfterFunc(t.timeout, t.clean)
+	}
+}
+
+// clean is called automatically when a preset time passes without a response
+// being dleivered for the first network request.
+func (t *Tracker) clean() {
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	// Expire anything within a certain threshold (might be no items at all if
+	// we raced with the delivery)
+	for t.expire.Len() > 0 {
+		// Stop iterating if the next pending request is still alive
+		var (
+			head = t.expire.Front()
+			id   = head.Value.(uint64)
+			req  = t.pending[id]
+		)
+		if time.Since(req.time) < t.timeout+5*time.Millisecond {
+			break
+		}
+		// Nope, dead, drop it
+		t.expire.Remove(head)
+		delete(t.pending, id)
+
+		g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode)
+		metrics.GetOrRegisterGauge(g, nil).Dec(1)
+
+		m := fmt.Sprintf("%s/%s/%d/%#02x", lostMeterName, t.protocol, req.version, req.reqCode)
+		metrics.GetOrRegisterMeter(m, nil).Mark(1)
+	}
+	t.schedule()
+}
+
+// schedule starts a timer to trigger on the expiration of the first network
+// packet.
+func (t *Tracker) schedule() {
+	if t.expire.Len() == 0 {
+		t.wake = nil
+		return
+	}
+	t.wake = time.AfterFunc(time.Until(t.pending[t.expire.Front().Value.(uint64)].time.Add(t.timeout)), t.clean)
+}
+
+// Fulfil fills a pending request, if any is available, reporting on various metrics.
+func (t *Tracker) Fulfil(peer string, version uint, code uint64, id uint64) {
+	if !metrics.Enabled {
+		return
+	}
+	t.lock.Lock()
+	defer t.lock.Unlock()
+
+	// If it's a non existing request, track as stale response
+	req, ok := t.pending[id]
+	if !ok {
+		m := fmt.Sprintf("%s/%s/%d/%#02x", staleMeterName, t.protocol, version, code)
+		metrics.GetOrRegisterMeter(m, nil).Mark(1)
+		return
+	}
+	// If the response is funky, it might be some active attack
+	if req.peer != peer || req.version != version || req.resCode != code {
+		log.Warn("Network response id collision",
+			"have", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, version, code),
+			"want", fmt.Sprintf("%s:%s/%d:%d", peer, t.protocol, req.version, req.resCode),
+		)
+		return
+	}
+	// Everything matches, mark the request serviced and meter it
+	t.expire.Remove(req.expire)
+	delete(t.pending, id)
+	if req.expire.Prev() == nil {
+		if t.wake.Stop() {
+			t.schedule()
+		}
+	}
+	g := fmt.Sprintf("%s/%s/%d/%#02x", trackedGaugeName, t.protocol, req.version, req.reqCode)
+	metrics.GetOrRegisterGauge(g, nil).Dec(1)
+
+	h := fmt.Sprintf("%s/%s/%d/%#02x", waitHistName, t.protocol, req.version, req.reqCode)
+	sampler := func() metrics.Sample {
+		return metrics.ResettingSample(
+			metrics.NewExpDecaySample(1028, 0.015),
+		)
+	}
+	metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(time.Since(req.time).Microseconds())
+}
diff --git a/p2p/transport.go b/p2p/transport.go
new file mode 100644
index 0000000000..3f1cd7d64f
--- /dev/null
+++ b/p2p/transport.go
@@ -0,0 +1,177 @@
+// Copyright 2015 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 .
+
+package p2p
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"fmt"
+	"io"
+	"net"
+	"sync"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/bitutil"
+	"github.com/ethereum/go-ethereum/metrics"
+	"github.com/ethereum/go-ethereum/p2p/rlpx"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+const (
+	// total timeout for encryption handshake and protocol
+	// handshake in both directions.
+	handshakeTimeout = 5 * time.Second
+
+	// This is the timeout for sending the disconnect reason.
+	// This is shorter than the usual timeout because we don't want
+	// to wait if the connection is known to be bad anyway.
+	discWriteTimeout = 1 * time.Second
+)
+
+// rlpxTransport is the transport used by actual (non-test) connections.
+// It wraps an RLPx connection with locks and read/write deadlines.
+type rlpxTransport struct {
+	rmu, wmu sync.Mutex
+	wbuf     bytes.Buffer
+	conn     *rlpx.Conn
+}
+
+func newRLPX(conn net.Conn, dialDest *ecdsa.PublicKey) transport {
+	return &rlpxTransport{conn: rlpx.NewConn(conn, dialDest)}
+}
+
+func (t *rlpxTransport) ReadMsg() (Msg, error) {
+	t.rmu.Lock()
+	defer t.rmu.Unlock()
+
+	var msg Msg
+	t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout))
+	code, data, wireSize, err := t.conn.Read()
+	if err == nil {
+		msg = Msg{
+			ReceivedAt: time.Now(),
+			Code:       code,
+			Size:       uint32(len(data)),
+			meterSize:  uint32(wireSize),
+			Payload:    bytes.NewReader(data),
+		}
+	}
+	return msg, err
+}
+
+func (t *rlpxTransport) WriteMsg(msg Msg) error {
+	t.wmu.Lock()
+	defer t.wmu.Unlock()
+
+	// Copy message data to write buffer.
+	t.wbuf.Reset()
+	if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil {
+		return err
+	}
+
+	// Write the message.
+	t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout))
+	size, err := t.conn.Write(msg.Code, t.wbuf.Bytes())
+	if err != nil {
+		return err
+	}
+
+	// Set metrics.
+	msg.meterSize = size
+	if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages
+		m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode)
+		metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
+		metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1)
+	}
+	return nil
+}
+
+func (t *rlpxTransport) close(err error) {
+	t.wmu.Lock()
+	defer t.wmu.Unlock()
+
+	// Tell the remote end why we're disconnecting if possible.
+	// We only bother doing this if the underlying connection supports
+	// setting a timeout tough.
+	if t.conn != nil {
+		if r, ok := err.(DiscReason); ok && r != DiscNetworkError {
+			deadline := time.Now().Add(discWriteTimeout)
+			if err := t.conn.SetWriteDeadline(deadline); err == nil {
+				// Connection supports write deadline.
+				t.wbuf.Reset()
+				rlp.Encode(&t.wbuf, []DiscReason{r})
+				t.conn.Write(discMsg, t.wbuf.Bytes())
+			}
+		}
+	}
+	t.conn.Close()
+}
+
+func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
+	t.conn.SetDeadline(time.Now().Add(handshakeTimeout))
+	return t.conn.Handshake(prv)
+}
+
+func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
+	// Writing our handshake happens concurrently, we prefer
+	// returning the handshake read error. If the remote side
+	// disconnects us early with a valid reason, we should return it
+	// as the error so it can be tracked elsewhere.
+	werr := make(chan error, 1)
+	go func() { werr <- Send(t, handshakeMsg, our) }()
+	if their, err = readProtocolHandshake(t); err != nil {
+		<-werr // make sure the write terminates too
+		return nil, err
+	}
+	if err := <-werr; err != nil {
+		return nil, fmt.Errorf("write error: %v", err)
+	}
+	// If the protocol version supports Snappy encoding, upgrade immediately
+	t.conn.SetSnappy(their.Version >= snappyProtocolVersion)
+
+	return their, nil
+}
+
+func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) {
+	msg, err := rw.ReadMsg()
+	if err != nil {
+		return nil, err
+	}
+	if msg.Size > baseProtocolMaxMsgSize {
+		return nil, fmt.Errorf("message too big")
+	}
+	if msg.Code == discMsg {
+		// Disconnect before protocol handshake is valid according to the
+		// spec and we send it ourself if the post-handshake checks fail.
+		// We can't return the reason directly, though, because it is echoed
+		// back otherwise. Wrap it in a string instead.
+		var reason [1]DiscReason
+		rlp.Decode(msg.Payload, &reason)
+		return nil, reason[0]
+	}
+	if msg.Code != handshakeMsg {
+		return nil, fmt.Errorf("expected handshake, got %x", msg.Code)
+	}
+	var hs protoHandshake
+	if err := msg.Decode(&hs); err != nil {
+		return nil, err
+	}
+	if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) {
+		return nil, DiscInvalidIdentity
+	}
+	return &hs, nil
+}
diff --git a/p2p/transport_test.go b/p2p/transport_test.go
new file mode 100644
index 0000000000..753ea30bf1
--- /dev/null
+++ b/p2p/transport_test.go
@@ -0,0 +1,148 @@
+// Copyright 2015 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 .
+
+package p2p
+
+import (
+	"errors"
+	"reflect"
+	"sync"
+	"testing"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/p2p/simulations/pipes"
+)
+
+func TestProtocolHandshake(t *testing.T) {
+	var (
+		prv0, _ = crypto.GenerateKey()
+		pub0    = crypto.FromECDSAPub(&prv0.PublicKey)[1:]
+		hs0     = &protoHandshake{Version: 3, ID: pub0, Caps: []Cap{{"a", 0}, {"b", 2}}}
+
+		prv1, _ = crypto.GenerateKey()
+		pub1    = crypto.FromECDSAPub(&prv1.PublicKey)[1:]
+		hs1     = &protoHandshake{Version: 3, ID: pub1, Caps: []Cap{{"c", 1}, {"d", 3}}}
+
+		wg sync.WaitGroup
+	)
+
+	fd0, fd1, err := pipes.TCPPipe()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	wg.Add(2)
+	go func() {
+		defer wg.Done()
+		defer fd0.Close()
+		frame := newRLPX(fd0, &prv1.PublicKey)
+		rpubkey, err := frame.doEncHandshake(prv0)
+		if err != nil {
+			t.Errorf("dial side enc handshake failed: %v", err)
+			return
+		}
+		if !reflect.DeepEqual(rpubkey, &prv1.PublicKey) {
+			t.Errorf("dial side remote pubkey mismatch: got %v, want %v", rpubkey, &prv1.PublicKey)
+			return
+		}
+
+		phs, err := frame.doProtoHandshake(hs0)
+		if err != nil {
+			t.Errorf("dial side proto handshake error: %v", err)
+			return
+		}
+		phs.Rest = nil
+		if !reflect.DeepEqual(phs, hs1) {
+			t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1))
+			return
+		}
+		frame.close(DiscQuitting)
+	}()
+	go func() {
+		defer wg.Done()
+		defer fd1.Close()
+		rlpx := newRLPX(fd1, nil)
+		rpubkey, err := rlpx.doEncHandshake(prv1)
+		if err != nil {
+			t.Errorf("listen side enc handshake failed: %v", err)
+			return
+		}
+		if !reflect.DeepEqual(rpubkey, &prv0.PublicKey) {
+			t.Errorf("listen side remote pubkey mismatch: got %v, want %v", rpubkey, &prv0.PublicKey)
+			return
+		}
+
+		phs, err := rlpx.doProtoHandshake(hs1)
+		if err != nil {
+			t.Errorf("listen side proto handshake error: %v", err)
+			return
+		}
+		phs.Rest = nil
+		if !reflect.DeepEqual(phs, hs0) {
+			t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0))
+			return
+		}
+
+		if err := ExpectMsg(rlpx, discMsg, []DiscReason{DiscQuitting}); err != nil {
+			t.Errorf("error receiving disconnect: %v", err)
+		}
+	}()
+	wg.Wait()
+}
+
+func TestProtocolHandshakeErrors(t *testing.T) {
+	tests := []struct {
+		code uint64
+		msg  interface{}
+		err  error
+	}{
+		{
+			code: discMsg,
+			msg:  []DiscReason{DiscQuitting},
+			err:  DiscQuitting,
+		},
+		{
+			code: 0x989898,
+			msg:  []byte{1},
+			err:  errors.New("expected handshake, got 989898"),
+		},
+		{
+			code: handshakeMsg,
+			msg:  make([]byte, baseProtocolMaxMsgSize+2),
+			err:  errors.New("message too big"),
+		},
+		{
+			code: handshakeMsg,
+			msg:  []byte{1, 2, 3},
+			err:  newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"),
+		},
+		{
+			code: handshakeMsg,
+			msg:  &protoHandshake{Version: 3},
+			err:  DiscInvalidIdentity,
+		},
+	}
+
+	for i, test := range tests {
+		p1, p2 := MsgPipe()
+		go Send(p1, test.code, test.msg)
+		_, err := readProtocolHandshake(p2)
+		if !reflect.DeepEqual(err, test.err) {
+			t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err)
+		}
+	}
+}
diff --git a/params/bootnodes.go b/params/bootnodes.go
index 5b4c962530..f36ad61729 100644
--- a/params/bootnodes.go
+++ b/params/bootnodes.go
@@ -56,29 +56,60 @@ var GoerliBootnodes = []string{
 	"enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303",
 	"enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303",
 	"enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313",
-	"enode://c1f8b7c2ac4453271fa07d8e9ecf9a2e8285aa0bd0c07df0131f47153306b0736fd3db8924e7a9bf0bed6b1d8d4f87362a71b033dc7c64547728d953e43e59b2@52.64.155.147:30303",
-	"enode://f4a9c6ee28586009fb5a96c8af13a58ed6d8315a9eee4772212c1d4d9cebe5a8b8a78ea4434f318726317d04a3f531a1ef0420cf9752605a562cfe858c46e263@213.186.16.82:30303",
+	"enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303",
 
 	// Ethereum Foundation bootnode
 	"enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303",
+
+	// Goerli Initiative bootnodes
+	"enode://a869b02cec167211fb4815a82941db2e7ed2936fd90e78619c53eb17753fcf0207463e3419c264e2a1dd8786de0df7e68cf99571ab8aeb7c4e51367ef186b1dd@51.15.116.226:30303",
+	"enode://807b37ee4816ecf407e9112224494b74dd5933625f655962d892f2f0f02d7fbbb3e2a94cf87a96609526f30c998fd71e93e2f53015c558ffc8b03eceaf30ee33@51.15.119.157:30303",
+	"enode://a59e33ccd2b3e52d578f1fbd70c6f9babda2650f0760d6ff3b37742fdcdfdb3defba5d56d315b40c46b70198c7621e63ffa3f987389c7118634b0fefbbdfa7fd@51.15.119.157:40303",
+}
+
+// YoloV3Bootnodes are the enode URLs of the P2P bootstrap nodes running on the
+// YOLOv3 ephemeral test network.
+// TODO: Set Yolov3 bootnodes
+var YoloV3Bootnodes = []string{
+	"enode://9e1096aa59862a6f164994cb5cb16f5124d6c992cdbf4535ff7dea43ea1512afe5448dca9df1b7ab0726129603f1a3336b631e4d7a1a44c94daddd03241587f9@3.9.20.133:30303",
 }
 
-// DiscoveryV5Bootnodes are the enode URLs of the P2P bootstrap nodes for the
-// experimental RLPx v5 topic-discovery network.
-var DiscoveryV5Bootnodes = []string{
-	"enode://06051a5573c81934c9554ef2898eb13b33a34b94cf36b202b69fde139ca17a85051979867720d4bdae4323d4943ddf9aeeb6643633aa656e0be843659795007a@35.177.226.168:30303",
-	"enode://0cc5f5ffb5d9098c8b8c62325f3797f56509bff942704687b6530992ac706e2cb946b90a34f1f19548cd3c7baccbcaea354531e5983c7d1bc0dee16ce4b6440b@40.118.3.223:30304",
-	"enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30306",
-	"enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30307",
+var V5Bootnodes = []string{
+	// Teku team's bootnode
+	"enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA",
+	"enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA",
+	// Prylab team's bootnodes
+	"enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg",
+	"enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA",
+	"enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg",
+	// Lighthouse team's bootnodes
+	"enr:-IS4QLkKqDMy_ExrpOEWa59NiClemOnor-krjp4qoeZwIw2QduPC-q7Kz4u1IOWf3DDbdxqQIgC4fejavBOuUPy-HE4BgmlkgnY0gmlwhCLzAHqJc2VjcDI1NmsxoQLQSJfEAHZApkm5edTCZ_4qps_1k_ub2CxHFxi-gr2JMIN1ZHCCIyg",
+	"enr:-IS4QDAyibHCzYZmIYZCjXwU9BqpotWmv2BsFlIq1V31BwDDMJPFEbox1ijT5c2Ou3kvieOKejxuaCqIcjxBjJ_3j_cBgmlkgnY0gmlwhAMaHiCJc2VjcDI1NmsxoQJIdpj_foZ02MXz4It8xKD7yUHTBx7lVFn3oeRP21KRV4N1ZHCCIyg",
+	// EF bootnodes
+	"enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg",
+	"enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg",
+	"enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg",
+	"enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg",
 }
 
 const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"
 
-// These DNS names provide bootstrap connectivity for public testnets and the mainnet.
-// See https://github.com/ethereum/discv4-dns-lists for more information.
-var KnownDNSNetworks = map[common.Hash]string{
-	MainnetGenesisHash: dnsPrefix + "all.mainnet.ethdisco.net",
-	RopstenGenesisHash: dnsPrefix + "all.ropsten.ethdisco.net",
-	RinkebyGenesisHash: dnsPrefix + "all.rinkeby.ethdisco.net",
-	GoerliGenesisHash:  dnsPrefix + "all.goerli.ethdisco.net",
+// KnownDNSNetwork returns the address of a public DNS-based node list for the given
+// genesis hash and protocol. See https://github.com/ethereum/discv4-dns-lists for more
+// information.
+func KnownDNSNetwork(genesis common.Hash, protocol string) string {
+	var net string
+	switch genesis {
+	case MainnetGenesisHash:
+		net = "mainnet"
+	case RopstenGenesisHash:
+		net = "ropsten"
+	case RinkebyGenesisHash:
+		net = "rinkeby"
+	case GoerliGenesisHash:
+		net = "goerli"
+	default:
+		return ""
+	}
+	return dnsPrefix + protocol + "." + net + ".ethdisco.net"
 }
diff --git a/params/config.go b/params/config.go
index 6c411f2ac4..1a43d835e4 100644
--- a/params/config.go
+++ b/params/config.go
@@ -22,7 +22,7 @@ import (
 	"math/big"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
+	"golang.org/x/crypto/sha3"
 )
 
 // Genesis hashes to enforce below configs on.
@@ -35,6 +35,7 @@ var (
 	BSCGenesisHash    = common.HexToHash("0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b")
 	ChapelGenesisHash = common.HexToHash("0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34")
 	RialtoGenesisHash = common.HexToHash("0x005dc005bddd1967de6187c1c23be801eb7abdd80cebcc24f341b727b70311d6")
+	YoloV3GenesisHash = common.HexToHash("0xf1f2876e8500c77afcc03228757b39477eceffccf645b734967fe3c7e16967b7")
 )
 
 // TrustedCheckpoints associates each known checkpoint with the genesis hash of
@@ -59,27 +60,28 @@ var (
 	// MainnetChainConfig is the chain parameters to run a node on the main network.
 	MainnetChainConfig = &ChainConfig{
 		ChainID:             big.NewInt(1),
-		HomesteadBlock:      big.NewInt(1150000),
-		DAOForkBlock:        big.NewInt(1920000),
+		HomesteadBlock:      big.NewInt(1_150_000),
+		DAOForkBlock:        big.NewInt(1_920_000),
 		DAOForkSupport:      true,
-		EIP150Block:         big.NewInt(2463000),
+		EIP150Block:         big.NewInt(2_463_000),
 		EIP150Hash:          common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
-		EIP155Block:         big.NewInt(2675000),
-		EIP158Block:         big.NewInt(2675000),
-		ByzantiumBlock:      big.NewInt(4370000),
-		ConstantinopleBlock: big.NewInt(7280000),
-		PetersburgBlock:     big.NewInt(7280000),
-		IstanbulBlock:       big.NewInt(9069000),
-		MuirGlacierBlock:    big.NewInt(9200000),
+		EIP155Block:         big.NewInt(2_675_000),
+		EIP158Block:         big.NewInt(2_675_000),
+		ByzantiumBlock:      big.NewInt(4_370_000),
+		ConstantinopleBlock: big.NewInt(7_280_000),
+		PetersburgBlock:     big.NewInt(7_280_000),
+		IstanbulBlock:       big.NewInt(9_069_000),
+		MuirGlacierBlock:    big.NewInt(9_200_000),
+		BerlinBlock:         big.NewInt(12_244_000),
 		Ethash:              new(EthashConfig),
 	}
 
 	// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
 	MainnetTrustedCheckpoint = &TrustedCheckpoint{
-		SectionIndex: 300,
-		SectionHead:  common.HexToHash("0x022d252ffcd289444eed5a4b8c58018aecb2afc9ab0da5fe059a69a7fb618702"),
-		CHTRoot:      common.HexToHash("0xe7044c70ae068969573c7f5abe58ef23d9d82d4ee9152ec88b7c6d0cc8ee2714"),
-		BloomRoot:    common.HexToHash("0xe22600caa25653abaef00d0c112b07b90f4e3395ce0c1f5f7f791cdd6d30a408"),
+		SectionIndex: 371,
+		SectionHead:  common.HexToHash("0x50fd3cec5376ede90ef9129772022690cd1467f22c18abb7faa11e793c51e9c9"),
+		CHTRoot:      common.HexToHash("0xb57b4b22a77b5930847b1ca9f62daa11eae6578948cb7b18997f2c0fe5757025"),
+		BloomRoot:    common.HexToHash("0xa338f8a868a194fa90327d0f5877f656a9f3640c618d2a01a01f2e76ef9ef954"),
 	}
 
 	// MainnetCheckpointOracle contains a set of configs for the main network oracle.
@@ -105,20 +107,21 @@ var (
 		EIP150Hash:          common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
 		EIP155Block:         big.NewInt(10),
 		EIP158Block:         big.NewInt(10),
-		ByzantiumBlock:      big.NewInt(1700000),
-		ConstantinopleBlock: big.NewInt(4230000),
-		PetersburgBlock:     big.NewInt(4939394),
-		IstanbulBlock:       big.NewInt(6485846),
-		MuirGlacierBlock:    big.NewInt(7117117),
+		ByzantiumBlock:      big.NewInt(1_700_000),
+		ConstantinopleBlock: big.NewInt(4_230_000),
+		PetersburgBlock:     big.NewInt(4_939_394),
+		IstanbulBlock:       big.NewInt(6_485_846),
+		MuirGlacierBlock:    big.NewInt(7_117_117),
+		BerlinBlock:         big.NewInt(9_812_189),
 		Ethash:              new(EthashConfig),
 	}
 
 	// RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network.
 	RopstenTrustedCheckpoint = &TrustedCheckpoint{
-		SectionIndex: 234,
-		SectionHead:  common.HexToHash("0x34659b817e99e6de868b0d4c5321bcff7e36c2cf79307386a2f5053361794d95"),
-		CHTRoot:      common.HexToHash("0x249401cd2b07e3f64892729d3f6198514cd11001231a1c001c2e7245659b26e0"),
-		BloomRoot:    common.HexToHash("0x37657aa58a07ac3fa13f421c3e5500a944a76def5a11c6d57f17a85f5b33c129"),
+		SectionIndex: 279,
+		SectionHead:  common.HexToHash("0x4a4912848d4c06090097073357c10015d11c6f4544a0f93cbdd584701c3b7d58"),
+		CHTRoot:      common.HexToHash("0x9053b7867ae921e80a4e2f5a4b15212e4af3d691ca712fb33dc150e9c6ea221c"),
+		BloomRoot:    common.HexToHash("0x3dc04cb1be7ddc271f3f83469b47b76184a79d7209ef51d85b1539ea6d25a645"),
 	}
 
 	// RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle.
@@ -144,10 +147,12 @@ var (
 		EIP150Hash:          common.HexToHash("0x9b095b36c15eaf13044373aef8ee0bd3a382a5abb92e402afa44b8249c3a90e9"),
 		EIP155Block:         big.NewInt(3),
 		EIP158Block:         big.NewInt(3),
-		ByzantiumBlock:      big.NewInt(1035301),
-		ConstantinopleBlock: big.NewInt(3660663),
-		PetersburgBlock:     big.NewInt(4321234),
-		IstanbulBlock:       big.NewInt(5435345),
+		ByzantiumBlock:      big.NewInt(1_035_301),
+		ConstantinopleBlock: big.NewInt(3_660_663),
+		PetersburgBlock:     big.NewInt(4_321_234),
+		IstanbulBlock:       big.NewInt(5_435_345),
+		MuirGlacierBlock:    nil,
+		BerlinBlock:         big.NewInt(8_290_928),
 		Clique: &CliqueConfig{
 			Period: 15,
 			Epoch:  30000,
@@ -156,10 +161,10 @@ var (
 
 	// RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network.
 	RinkebyTrustedCheckpoint = &TrustedCheckpoint{
-		SectionIndex: 191,
-		SectionHead:  common.HexToHash("0xfdf3085848b4126048caf176634fd96a208d8a3b055c643e9e32690420df36d5"),
-		CHTRoot:      common.HexToHash("0x48059ceb7e0bd25708cc736e5603d28a6f173a3bb904e6e1b3511a97fa30ca97"),
-		BloomRoot:    common.HexToHash("0x3566c2b173c0591d5bb4f3ef7e341d82da7577c125fca94e9b51fb7134a676d7"),
+		SectionIndex: 254,
+		SectionHead:  common.HexToHash("0x0cba01dd71baa22ac8fa0b105bc908e94f9ecfbc79b4eb97427fe07b5851dd10"),
+		CHTRoot:      common.HexToHash("0x5673d8fc49c9c7d8729068640e4b392d46952a5a38798973bac1cf1d0d27ad7d"),
+		BloomRoot:    common.HexToHash("0x70e01232b66df9a7778ae3291c9217afb9a2d9f799f32d7b912bd37e7bce83a8"),
 	}
 
 	// RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle.
@@ -186,7 +191,9 @@ var (
 		ByzantiumBlock:      big.NewInt(0),
 		ConstantinopleBlock: big.NewInt(0),
 		PetersburgBlock:     big.NewInt(0),
-		IstanbulBlock:       big.NewInt(1561651),
+		IstanbulBlock:       big.NewInt(1_561_651),
+		MuirGlacierBlock:    nil,
+		BerlinBlock:         big.NewInt(4_460_644),
 		Clique: &CliqueConfig{
 			Period: 15,
 			Epoch:  30000,
@@ -195,10 +202,10 @@ var (
 
 	// GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network.
 	GoerliTrustedCheckpoint = &TrustedCheckpoint{
-		SectionIndex: 76,
-		SectionHead:  common.HexToHash("0xf56ca390d1131767b924d85ee8e039c8a4c4a498cfaf017c1a9abf63ef01ff17"),
-		CHTRoot:      common.HexToHash("0x78ffc5eecf514eed42f61e6f6df1bdcd79f9296c462faf6f33bd600f70a2e8b9"),
-		BloomRoot:    common.HexToHash("0x5186111a2d6c459cc341319398f7d14fa2c973b1ba846b7f2ec678129c7115fd"),
+		SectionIndex: 138,
+		SectionHead:  common.HexToHash("0xb7ea0566abd7d0def5b3c9afa3431debb7bb30b65af35f106ca93a59e6c859a7"),
+		CHTRoot:      common.HexToHash("0x378c7ea9081242beb982e2e39567ba12f2ed3e59e5aba3f9db1d595646d7c9f4"),
+		BloomRoot:    common.HexToHash("0x523c169286cfca52e8a6579d8c35dc8bf093412d8a7478163bfa81ae91c2492d"),
 	}
 
 	// GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle.
@@ -213,7 +220,6 @@ var (
 		},
 		Threshold: 2,
 	}
-
 	BSCChainConfig = &ChainConfig{
 		ChainID:             big.NewInt(56),
 		HomesteadBlock:      big.NewInt(0),
@@ -274,22 +280,45 @@ var (
 		},
 	}
 
+	// YoloV3ChainConfig contains the chain parameters to run a node on the YOLOv3 test network.
+	YoloV3ChainConfig = &ChainConfig{
+		ChainID:             new(big.Int).SetBytes([]byte("yolov3x")),
+		HomesteadBlock:      big.NewInt(0),
+		DAOForkBlock:        nil,
+		DAOForkSupport:      true,
+		EIP150Block:         big.NewInt(0),
+		EIP155Block:         big.NewInt(0),
+		EIP158Block:         big.NewInt(0),
+		ByzantiumBlock:      big.NewInt(0),
+		ConstantinopleBlock: big.NewInt(0),
+		PetersburgBlock:     big.NewInt(0),
+		IstanbulBlock:       big.NewInt(0),
+		MuirGlacierBlock:    nil,
+		BerlinBlock:         nil, // Don't enable Berlin directly, we're YOLOing it
+		YoloV3Block:         big.NewInt(0),
+		Clique: &CliqueConfig{
+			Period: 15,
+			Epoch:  30000,
+		},
+	}
+
 	// AllEthashProtocolChanges contains every protocol change (EIPs) introduced
 	// and accepted by the Ethereum core developers into the Ethash consensus.
 	//
 	// This configuration is intentionally not using keyed fields to force anyone
 	// adding flags to the config to also have to set these fields.
-	AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, new(EthashConfig), nil, nil}
+	AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil,nil, nil, nil, new(EthashConfig), nil, nil}
 
 	// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
 	// and accepted by the Ethereum core developers into the Clique consensus.
 	//
 	// This configuration is intentionally not using keyed fields to force anyone
 	// adding flags to the config to also have to set these fields.
-	AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
+	AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
 
-	TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, new(EthashConfig), nil, nil}
-	TestRules       = TestChainConfig.Rules(new(big.Int))
+	TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil,nil, nil, new(EthashConfig), nil, nil}
+
+	TestRules = TestChainConfig.Rules(new(big.Int))
 )
 
 // TrustedCheckpoint represents a set of post-processed trie roots (CHT and
@@ -313,12 +342,18 @@ func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool {
 
 // Hash returns the hash of checkpoint's four key fields(index, sectionHead, chtRoot and bloomTrieRoot).
 func (c *TrustedCheckpoint) Hash() common.Hash {
-	buf := make([]byte, 8+3*common.HashLength)
-	binary.BigEndian.PutUint64(buf, c.SectionIndex)
-	copy(buf[8:], c.SectionHead.Bytes())
-	copy(buf[8+common.HashLength:], c.CHTRoot.Bytes())
-	copy(buf[8+2*common.HashLength:], c.BloomRoot.Bytes())
-	return crypto.Keccak256Hash(buf)
+	var sectionIndex [8]byte
+	binary.BigEndian.PutUint64(sectionIndex[:], c.SectionIndex)
+
+	w := sha3.NewLegacyKeccak256()
+	w.Write(sectionIndex[:])
+	w.Write(c.SectionHead[:])
+	w.Write(c.CHTRoot[:])
+	w.Write(c.BloomRoot[:])
+
+	var h common.Hash
+	w.Sum(h[:0])
+	return h
 }
 
 // Empty returns an indicator whether the checkpoint is regarded as empty.
@@ -348,21 +383,27 @@ type ChainConfig struct {
 	DAOForkSupport bool     `json:"daoForkSupport,omitempty" toml:",omitempty"` // Whether the nodes supports or opposes the DAO hard-fork
 
 	// EIP150 implements the Gas price changes (https://github.com/ethereum/EIPs/issues/150)
-	EIP150Block *big.Int    `json:"eip150Block,omitempty" toml:",omitempty"` // EIP150 HF block (nil = no fork)
-	EIP150Hash  common.Hash `json:"eip150Hash,omitempty" toml:",omitempty"`  // EIP150 HF hash (needed for header only clients as only gas pricing changed)
-
-	EIP155Block *big.Int `json:"eip155Block,omitempty" toml:",omitempty"` // EIP155 HF block
-	EIP158Block *big.Int `json:"eip158Block,omitempty" toml:",omitempty"` // EIP158 HF block
-
-	ByzantiumBlock      *big.Int `json:"byzantiumBlock,omitempty" toml:",omitempty"`      // Byzantium switch block (nil = no fork, 0 = already on byzantium)
-	ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty" toml:",omitempty"` // Constantinople switch block (nil = no fork, 0 = already activated)
-	PetersburgBlock     *big.Int `json:"petersburgBlock,omitempty" toml:",omitempty"`     // Petersburg switch block (nil = same as Constantinople)
-	IstanbulBlock       *big.Int `json:"istanbulBlock,omitempty" toml:",omitempty"`       // Istanbul switch block (nil = no fork, 0 = already on istanbul)
-	MuirGlacierBlock    *big.Int `json:"muirGlacierBlock,omitempty" toml:",omitempty"`    // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
-	EWASMBlock          *big.Int `json:"ewasmBlock,omitempty" toml:",omitempty"`          // EWASM switch block (nil = no fork, 0 = already activated)
-	RamanujanBlock      *big.Int `json:"ramanujanBlock,omitempty" toml:",omitempty"`      // ramanujanBlock switch block (nil = no fork, 0 = already activated)
-	NielsBlock          *big.Int `json:"nielsBlock,omitempty" toml:",omitempty"`          // nielsBlock switch block (nil = no fork, 0 = already activated)
-	MirrorSyncBlock     *big.Int `json:"mirrorSyncBlock,omitempty" toml:",omitempty"`     // mirrorSyncBlock switch block (nil = no fork, 0 = already activated)
+	EIP150Block *big.Int    `json:"eip150Block,omitempty"` // EIP150 HF block (nil = no fork)
+	EIP150Hash  common.Hash `json:"eip150Hash,omitempty"`  // EIP150 HF hash (needed for header only clients as only gas pricing changed)
+
+	EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF block
+	EIP158Block *big.Int `json:"eip158Block,omitempty"` // EIP158 HF block
+
+	ByzantiumBlock      *big.Int `json:"byzantiumBlock,omitempty"`                    // Byzantium switch block (nil = no fork, 0 = already on byzantium)
+	ConstantinopleBlock *big.Int `json:"constantinopleBlock,omitempty"`               // Constantinople switch block (nil = no fork, 0 = already activated)
+	PetersburgBlock     *big.Int `json:"petersburgBlock,omitempty"`                   // Petersburg switch block (nil = same as Constantinople)
+	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)
+	BerlinBlock         *big.Int `json:"berlinBlock,omitempty"`                       // Berlin switch block (nil = no fork, 0 = already on berlin)
+
+	YoloV3Block         *big.Int `json:"yoloV3Block,omitempty"`                       // YOLO v3: Gas repricings TODO @holiman add EIP references
+	EWASMBlock          *big.Int `json:"ewasmBlock,omitempty"`                        // EWASM switch block (nil = no fork, 0 = already activated)	RamanujanBlock      *big.Int `json:"ramanujanBlock,omitempty" toml:",omitempty"`      // ramanujanBlock switch block (nil = no fork, 0 = already activated)
+	CatalystBlock *big.Int `json:"catalystBlock,omitempty"` // Catalyst switch block (nil = no fork, 0 = already on catalyst)
+
+	RamanujanBlock      *big.Int `json:"ramanujanBlock,omitempty" toml:",omitempty"`  // ramanujanBlock switch block (nil = no fork, 0 = already activated)
+	NielsBlock          *big.Int `json:"nielsBlock,omitempty" toml:",omitempty"`      // nielsBlock switch block (nil = no fork, 0 = already activated)
+	MirrorSyncBlock     *big.Int `json:"mirrorSyncBlock,omitempty" toml:",omitempty"` // mirrorSyncBlock switch block (nil = no fork, 0 = already activated)
+
 
 	// Various consensus engines
 	Ethash *EthashConfig `json:"ethash,omitempty" toml:",omitempty"`
@@ -413,7 +454,7 @@ func (c *ChainConfig) String() string {
 	default:
 		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, Ramanujan: %v, Niels: %v, MirrorSync: %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, Ramanujan: %v, Niels: %v, MirrorSync: %v, Berlin: %v, YOLO v3: %v, Engine: %v}",
 		c.ChainID,
 		c.HomesteadBlock,
 		c.DAOForkBlock,
@@ -429,6 +470,8 @@ func (c *ChainConfig) String() string {
 		c.RamanujanBlock,
 		c.NielsBlock,
 		c.MirrorSyncBlock,
+		c.BerlinBlock,
+		c.YoloV3Block,
 		engine,
 	)
 }
@@ -515,6 +558,16 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool {
 	return isForked(c.IstanbulBlock, num)
 }
 
+// IsBerlin returns whether num is either equal to the Berlin fork block or greater.
+func (c *ChainConfig) IsBerlin(num *big.Int) bool {
+	return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num)
+}
+
+// IsCatalyst returns whether num is either equal to the Merge fork block or greater.
+func (c *ChainConfig) IsCatalyst(num *big.Int) bool {
+	return isForked(c.CatalystBlock, num)
+}
+
 // IsEWASM returns whether num represents a block number after the EWASM fork
 func (c *ChainConfig) IsEWASM(num *big.Int) bool {
 	return isForked(c.EWASMBlock, num)
@@ -542,22 +595,25 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *Confi
 // to guarantee that forks can be implemented in a different order than on official networks
 func (c *ChainConfig) CheckConfigForkOrder() error {
 	type fork struct {
-		name  string
-		block *big.Int
+		name     string
+		block    *big.Int
+		optional bool // if true, the fork may be nil and next fork is still allowed
 	}
 	var lastFork fork
 	for _, cur := range []fork{
-		{"homesteadBlock", c.HomesteadBlock},
-		{"eip150Block", c.EIP150Block},
-		{"eip155Block", c.EIP155Block},
-		{"eip158Block", c.EIP158Block},
-		{"byzantiumBlock", c.ByzantiumBlock},
-		{"constantinopleBlock", c.ConstantinopleBlock},
-		{"petersburgBlock", c.PetersburgBlock},
-		{"istanbulBlock", c.IstanbulBlock},
-		{"muirGlacierBlock", c.MuirGlacierBlock},
-		{"ramanujanBlock", c.RamanujanBlock},
-		{"mirrorSyncBlock", c.MirrorSyncBlock},
+		{name: "homesteadBlock", block: c.HomesteadBlock},
+		{name: "daoForkBlock", block: c.DAOForkBlock, optional: true},
+		{name: "eip150Block", block: c.EIP150Block},
+		{name: "eip155Block", block: c.EIP155Block},
+		{name: "eip158Block", block: c.EIP158Block},
+		{name: "byzantiumBlock", block: c.ByzantiumBlock},
+		{name: "constantinopleBlock", block: c.ConstantinopleBlock},
+		{name: "petersburgBlock", block: c.PetersburgBlock},
+		{name: "istanbulBlock", block: c.IstanbulBlock},
+		{name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true},
+		{name: "ramanujanBlock", block: c.RamanujanBlock},
+		{name: "mirrorSyncBlock", block: c.MirrorSyncBlock},
+		{name: "berlinBlock", block: c.BerlinBlock},
 	} {
 		if lastFork.name != "" {
 			// Next one must be higher number
@@ -572,7 +628,10 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
 				}
 			}
 		}
-		lastFork = cur
+		// If it was optional and not set, then ignore it
+		if !cur.optional || cur.block != nil {
+			lastFork = cur
+		}
 	}
 	return nil
 }
@@ -606,7 +665,11 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
 		return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock)
 	}
 	if isForkIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, head) {
-		return newCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock)
+		// the only case where we allow Petersburg to be set in the past is if it is equal to Constantinople
+		// mainly to satisfy fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set
+		if isForkIncompatible(c.ConstantinopleBlock, newcfg.PetersburgBlock, head) {
+			return newCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock)
+		}
 	}
 	if isForkIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, head) {
 		return newCompatError("Istanbul fork block", c.IstanbulBlock, newcfg.IstanbulBlock)
@@ -614,6 +677,12 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
 	if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) {
 		return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock)
 	}
+	if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) {
+		return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock)
+	}
+	if isForkIncompatible(c.YoloV3Block, newcfg.YoloV3Block, head) {
+		return newCompatError("YOLOv3 fork block", c.YoloV3Block, newcfg.YoloV3Block)
+	}
 	if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
 		return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
 	}
@@ -690,6 +759,7 @@ type Rules struct {
 	ChainID                                                 *big.Int
 	IsHomestead, IsEIP150, IsEIP155, IsEIP158               bool
 	IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
+	IsBerlin, IsCatalyst                                    bool
 }
 
 // Rules ensures c's ChainID is not nil.
@@ -708,5 +778,7 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
 		IsConstantinople: c.IsConstantinople(num),
 		IsPetersburg:     c.IsPetersburg(num),
 		IsIstanbul:       c.IsIstanbul(num),
+		IsBerlin:         c.IsBerlin(num),
+		IsCatalyst:       c.IsCatalyst(num),
 	}
 }
diff --git a/params/config_test.go b/params/config_test.go
index 02c5fe2917..3c8ebaf4a5 100644
--- a/params/config_test.go
+++ b/params/config_test.go
@@ -70,6 +70,23 @@ func TestCheckCompatible(t *testing.T) {
 				RewindTo:     9,
 			},
 		},
+		{
+			stored:  &ChainConfig{ConstantinopleBlock: big.NewInt(30)},
+			new:     &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)},
+			head:    40,
+			wantErr: nil,
+		},
+		{
+			stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)},
+			new:    &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)},
+			head:   40,
+			wantErr: &ConfigCompatError{
+				What:         "Petersburg fork block",
+				StoredConfig: nil,
+				NewConfig:    big.NewInt(31),
+				RewindTo:     30,
+			},
+		},
 	}
 
 	for _, test := range tests {
diff --git a/params/network_params.go b/params/network_params.go
index bba24721c1..9311b5e2d5 100644
--- a/params/network_params.go
+++ b/params/network_params.go
@@ -53,9 +53,15 @@ const (
 	// CheckpointProcessConfirmations is the number before a checkpoint is generated
 	CheckpointProcessConfirmations = 256
 
-	// ImmutabilityThreshold is the number of blocks after which a chain segment is
+	// FullImmutabilityThreshold is the number of blocks after which a chain segment is
 	// considered immutable (i.e. soft finality). It is used by the downloader as a
 	// hard limit against deep ancestors, by the blockchain against deep reorgs, by
-	// the freezer as the cutoff treshold and by clique as the snapshot trust limit.
-	ImmutabilityThreshold = 90000
+	// the freezer as the cutoff threshold and by clique as the snapshot trust limit.
+	FullImmutabilityThreshold = 90000
+
+	// LightImmutabilityThreshold is the number of blocks after which a header chain
+	// segment is considered immutable for light client(i.e. soft finality). It is used by
+	// the downloader as a hard limit against deep ancestors, by the blockchain against deep
+	// reorgs, by the light pruner as the pruning validity guarantee.
+	LightImmutabilityThreshold = 30000
 )
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 155ef86dde..857bb9a582 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -54,32 +54,31 @@ const (
 	NetSstoreResetRefund      uint64 = 4800  // Once per SSTORE operation for resetting to the original non-zero value
 	NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value
 
-	SstoreSentryGasEIP2200   uint64 = 2300  // Minimum gas required to be present for an SSTORE call, not consumed
-	SstoreNoopGasEIP2200     uint64 = 800   // Once per SSTORE operation if the value doesn't change.
-	SstoreDirtyGasEIP2200    uint64 = 800   // Once per SSTORE operation if a dirty value is changed.
-	SstoreInitGasEIP2200     uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero
-	SstoreInitRefundEIP2200  uint64 = 19200 // Once per SSTORE operation for resetting to the original zero value
-	SstoreCleanGasEIP2200    uint64 = 5000  // Once per SSTORE operation from clean non-zero to something else
-	SstoreCleanRefundEIP2200 uint64 = 4200  // Once per SSTORE operation for resetting to the original non-zero value
-	SstoreClearRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot
+	SstoreSentryGasEIP2200            uint64 = 2300  // Minimum gas required to be present for an SSTORE call, not consumed
+	SstoreSetGasEIP2200               uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero
+	SstoreResetGasEIP2200             uint64 = 5000  // Once per SSTORE operation from clean non-zero to something else
+	SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot
 
 	JumpdestGas   uint64 = 1     // Once per JUMPDEST operation.
 	EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
 
-	CreateDataGas            uint64 = 200   //
-	CallCreateDepth          uint64 = 1024  // Maximum depth of call/create stack.
-	ExpGas                   uint64 = 10    // Once per EXP instruction
-	LogGas                   uint64 = 375   // Per LOG* operation.
-	CopyGas                  uint64 = 3     //
-	StackLimit               uint64 = 1024  // Maximum size of VM stack allowed.
-	TierStepGas              uint64 = 0     // Once per operation, for a selection of them.
-	LogTopicGas              uint64 = 375   // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
-	CreateGas                uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
-	Create2Gas               uint64 = 32000 // Once per CREATE2 operation
-	SelfdestructRefundGas    uint64 = 24000 // Refunded following a selfdestruct operation.
-	MemoryGas                uint64 = 3     // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
-	TxDataNonZeroGasFrontier uint64 = 68    // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions.
-	TxDataNonZeroGasEIP2028  uint64 = 16    // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)
+	CreateDataGas         uint64 = 200   //
+	CallCreateDepth       uint64 = 1024  // Maximum depth of call/create stack.
+	ExpGas                uint64 = 10    // Once per EXP instruction
+	LogGas                uint64 = 375   // Per LOG* operation.
+	CopyGas               uint64 = 3     //
+	StackLimit            uint64 = 1024  // Maximum size of VM stack allowed.
+	TierStepGas           uint64 = 0     // Once per operation, for a selection of them.
+	LogTopicGas           uint64 = 375   // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
+	CreateGas             uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
+	Create2Gas            uint64 = 32000 // Once per CREATE2 operation
+	SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
+	MemoryGas             uint64 = 3     // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
+
+	TxDataNonZeroGasFrontier  uint64 = 68   // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions.
+	TxDataNonZeroGasEIP2028   uint64 = 16   // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)
+	TxAccessListAddressGas    uint64 = 2400 // Per address specified in EIP 2930 access list
+	TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
 
 	// These have been changed during the course of the chain
 	CallGasFrontier              uint64 = 40  // Once per CALL operation & message call transaction.
@@ -126,7 +125,6 @@ const (
 	Ripemd160PerWordGas uint64 = 120  // Per-word price for a RIPEMD160 operation
 	IdentityBaseGas     uint64 = 15   // Base price for a data copy operation
 	IdentityPerWordGas  uint64 = 3    // Per-work price for a data copy operation
-	ModExpQuadCoeffDiv  uint64 = 20   // Divisor for the quadratic particle of the big int modular exponentiation
 
 	Bn256AddGasByzantium             uint64 = 500    // Byzantium gas needed for an elliptic curve addition
 	Bn256AddGasIstanbul              uint64 = 150    // Gas needed for an elliptic curve addition
@@ -136,8 +134,20 @@ const (
 	Bn256PairingBaseGasIstanbul      uint64 = 45000  // Base price for an elliptic curve pairing check
 	Bn256PairingPerPointGasByzantium uint64 = 80000  // Byzantium per-point price for an elliptic curve pairing check
 	Bn256PairingPerPointGasIstanbul  uint64 = 34000  // Per-point price for an elliptic curve pairing check
+
+	Bls12381G1AddGas          uint64 = 600    // Price for BLS12-381 elliptic curve G1 point addition
+	Bls12381G1MulGas          uint64 = 12000  // Price for BLS12-381 elliptic curve G1 point scalar multiplication
+	Bls12381G2AddGas          uint64 = 4500   // Price for BLS12-381 elliptic curve G2 point addition
+	Bls12381G2MulGas          uint64 = 55000  // Price for BLS12-381 elliptic curve G2 point scalar multiplication
+	Bls12381PairingBaseGas    uint64 = 115000 // Base gas price for BLS12-381 elliptic curve pairing check
+	Bls12381PairingPerPairGas uint64 = 23000  // Per-point pair gas price for BLS12-381 elliptic curve pairing check
+	Bls12381MapG1Gas          uint64 = 5500   // Gas price for BLS12-381 mapping field element to G1 operation
+	Bls12381MapG2Gas          uint64 = 110000 // Gas price for BLS12-381 mapping field element to G2 operation
 )
 
+// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
+var Bls12381MultiExpDiscountTable = [128]uint64{1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174}
+
 var (
 	DifficultyBoundDivisor = big.NewInt(2048)   // The bound divisor of the difficulty, used in the update calculations.
 	GenesisDifficulty      = big.NewInt(131072) // Difficulty of the Genesis block.
diff --git a/params/version.go b/params/version.go
index d5bdbb4687..87dea87679 100644
--- a/params/version.go
+++ b/params/version.go
@@ -22,9 +22,9 @@ import (
 
 const (
 	VersionMajor = 1      // Major version component of the current release
-	VersionMinor = 0      // Minor version component of the current release
-	VersionPatch = 7      // Patch version component of the current release
-	VersionMeta  = "hf.2" // Version metadata to append to the version string
+	VersionMinor = 1      // Minor version component of the current release
+	VersionPatch = 0      // Patch version component of the current release
+	VersionMeta  = "beta" // Version metadata to append to the version string
 )
 
 // Version holds the textual version string.
diff --git a/rlp/decode.go b/rlp/decode.go
index 5f3f5eedfd..79b7ef0626 100644
--- a/rlp/decode.go
+++ b/rlp/decode.go
@@ -952,7 +952,13 @@ func (s *Stream) readFull(buf []byte) (err error) {
 		n += nn
 	}
 	if err == io.EOF {
-		err = io.ErrUnexpectedEOF
+		if n < len(buf) {
+			err = io.ErrUnexpectedEOF
+		} else {
+			// Readers are allowed to give EOF even though the read succeeded.
+			// In such cases, we discard the EOF, like io.ReadFull() does.
+			err = nil
+		}
 	}
 	return err
 }
diff --git a/rlp/decode_test.go b/rlp/decode_test.go
index 167e9974b9..d94c3969b2 100644
--- a/rlp/decode_test.go
+++ b/rlp/decode_test.go
@@ -665,6 +665,26 @@ func TestDecodeWithByteReader(t *testing.T) {
 	})
 }
 
+func testDecodeWithEncReader(t *testing.T, n int) {
+	s := strings.Repeat("0", n)
+	_, r, _ := EncodeToReader(s)
+	var decoded string
+	err := Decode(r, &decoded)
+	if err != nil {
+		t.Errorf("Unexpected decode error with n=%v: %v", n, err)
+	}
+	if decoded != s {
+		t.Errorf("Decode mismatch with n=%v", n)
+	}
+}
+
+// This is a regression test checking that decoding from encReader
+// works for RLP values of size 8192 bytes or more.
+func TestDecodeWithEncReader(t *testing.T) {
+	testDecodeWithEncReader(t, 8188) // length with header is 8191
+	testDecodeWithEncReader(t, 8189) // length with header is 8192
+}
+
 // plainReader reads from a byte slice but does not
 // implement ReadByte. It is also not recognized by the
 // size validation. This is useful to test how the decoder
diff --git a/rlp/encode.go b/rlp/encode.go
index 9c9e8d706d..77b591045d 100644
--- a/rlp/encode.go
+++ b/rlp/encode.go
@@ -91,13 +91,6 @@ func EncodeToReader(val interface{}) (size int, r io.Reader, err error) {
 	return eb.size(), &encReader{buf: eb}, nil
 }
 
-type encbuf struct {
-	str     []byte      // string data, contains everything except list headers
-	lheads  []*listhead // all list headers
-	lhsize  int         // sum of sizes of all encoded list headers
-	sizebuf []byte      // 9-byte auxiliary buffer for uint encoding
-}
-
 type listhead struct {
 	offset int // index of this header in string data
 	size   int // total size of encoded data (including list headers)
@@ -130,19 +123,26 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int {
 	return sizesize + 1
 }
 
+type encbuf struct {
+	str      []byte        // string data, contains everything except list headers
+	lheads   []listhead    // all list headers
+	lhsize   int           // sum of sizes of all encoded list headers
+	sizebuf  [9]byte       // auxiliary buffer for uint encoding
+	bufvalue reflect.Value // used in writeByteArrayCopy
+}
+
 // encbufs are pooled.
 var encbufPool = sync.Pool{
-	New: func() interface{} { return &encbuf{sizebuf: make([]byte, 9)} },
+	New: func() interface{} {
+		var bytes []byte
+		return &encbuf{bufvalue: reflect.ValueOf(&bytes).Elem()}
+	},
 }
 
 func (w *encbuf) reset() {
 	w.lhsize = 0
-	if w.str != nil {
-		w.str = w.str[:0]
-	}
-	if w.lheads != nil {
-		w.lheads = w.lheads[:0]
-	}
+	w.str = w.str[:0]
+	w.lheads = w.lheads[:0]
 }
 
 // encbuf implements io.Writer so it can be passed it into EncodeRLP.
@@ -164,7 +164,6 @@ func (w *encbuf) encodeStringHeader(size int) {
 	if size < 56 {
 		w.str = append(w.str, 0x80+byte(size))
 	} else {
-		// TODO: encode to w.str directly
 		sizesize := putint(w.sizebuf[1:], uint64(size))
 		w.sizebuf[0] = 0xB7 + byte(sizesize)
 		w.str = append(w.str, w.sizebuf[:sizesize+1]...)
@@ -181,13 +180,29 @@ func (w *encbuf) encodeString(b []byte) {
 	}
 }
 
-func (w *encbuf) list() *listhead {
-	lh := &listhead{offset: len(w.str), size: w.lhsize}
-	w.lheads = append(w.lheads, lh)
-	return lh
+func (w *encbuf) encodeUint(i uint64) {
+	if i == 0 {
+		w.str = append(w.str, 0x80)
+	} else if i < 128 {
+		// fits single byte
+		w.str = append(w.str, byte(i))
+	} else {
+		s := putint(w.sizebuf[1:], i)
+		w.sizebuf[0] = 0x80 + byte(s)
+		w.str = append(w.str, w.sizebuf[:s+1]...)
+	}
 }
 
-func (w *encbuf) listEnd(lh *listhead) {
+// list adds a new list header to the header stack. It returns the index
+// of the header. The caller must call listEnd with this index after encoding
+// the content of the list.
+func (w *encbuf) list() int {
+	w.lheads = append(w.lheads, listhead{offset: len(w.str), size: w.lhsize})
+	return len(w.lheads) - 1
+}
+
+func (w *encbuf) listEnd(index int) {
+	lh := &w.lheads[index]
 	lh.size = w.size() - lh.offset - lh.size
 	if lh.size < 56 {
 		w.lhsize++ // length encoded into kind tag
@@ -230,7 +245,7 @@ func (w *encbuf) toWriter(out io.Writer) (err error) {
 			}
 		}
 		// write the header
-		enc := head.encode(w.sizebuf)
+		enc := head.encode(w.sizebuf[:])
 		if _, err = out.Write(enc); err != nil {
 			return err
 		}
@@ -296,7 +311,7 @@ func (r *encReader) next() []byte {
 			return p
 		}
 		r.lhpos++
-		return head.encode(r.buf.sizebuf)
+		return head.encode(r.buf.sizebuf[:])
 
 	case r.strpos < len(r.buf.str):
 		// String data at the end, after all list headers.
@@ -309,10 +324,7 @@ func (r *encReader) next() []byte {
 	}
 }
 
-var (
-	encoderInterface = reflect.TypeOf(new(Encoder)).Elem()
-	big0             = big.NewInt(0)
-)
+var encoderInterface = reflect.TypeOf(new(Encoder)).Elem()
 
 // makeWriter creates a writer function for the given type.
 func makeWriter(typ reflect.Type, ts tags) (writer, error) {
@@ -337,7 +349,7 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) {
 	case kind == reflect.Slice && isByte(typ.Elem()):
 		return writeBytes, nil
 	case kind == reflect.Array && isByte(typ.Elem()):
-		return writeByteArray, nil
+		return makeByteArrayWriter(typ), nil
 	case kind == reflect.Slice || kind == reflect.Array:
 		return makeSliceWriter(typ, ts)
 	case kind == reflect.Struct:
@@ -349,28 +361,13 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) {
 	}
 }
 
-func isByte(typ reflect.Type) bool {
-	return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface)
-}
-
 func writeRawValue(val reflect.Value, w *encbuf) error {
 	w.str = append(w.str, val.Bytes()...)
 	return nil
 }
 
 func writeUint(val reflect.Value, w *encbuf) error {
-	i := val.Uint()
-	if i == 0 {
-		w.str = append(w.str, 0x80)
-	} else if i < 128 {
-		// fits single byte
-		w.str = append(w.str, byte(i))
-	} else {
-		// TODO: encode int to w.str directly
-		s := putint(w.sizebuf[1:], i)
-		w.sizebuf[0] = 0x80 + byte(s)
-		w.str = append(w.str, w.sizebuf[:s+1]...)
-	}
+	w.encodeUint(val.Uint())
 	return nil
 }
 
@@ -397,13 +394,32 @@ func writeBigIntNoPtr(val reflect.Value, w *encbuf) error {
 	return writeBigInt(&i, w)
 }
 
+// wordBytes is the number of bytes in a big.Word
+const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8
+
 func writeBigInt(i *big.Int, w *encbuf) error {
-	if cmp := i.Cmp(big0); cmp == -1 {
+	if i.Sign() == -1 {
 		return fmt.Errorf("rlp: cannot encode negative *big.Int")
-	} else if cmp == 0 {
-		w.str = append(w.str, 0x80)
-	} else {
-		w.encodeString(i.Bytes())
+	}
+	bitlen := i.BitLen()
+	if bitlen <= 64 {
+		w.encodeUint(i.Uint64())
+		return nil
+	}
+	// Integer is larger than 64 bits, encode from i.Bits().
+	// The minimal byte length is bitlen rounded up to the next
+	// multiple of 8, divided by 8.
+	length := ((bitlen + 7) & -8) >> 3
+	w.encodeStringHeader(length)
+	w.str = append(w.str, make([]byte, length)...)
+	index := length
+	buf := w.str[len(w.str)-length:]
+	for _, d := range i.Bits() {
+		for j := 0; j < wordBytes && index > 0; j++ {
+			index--
+			buf[index] = byte(d)
+			d >>= 8
+		}
 	}
 	return nil
 }
@@ -413,7 +429,52 @@ func writeBytes(val reflect.Value, w *encbuf) error {
 	return nil
 }
 
-func writeByteArray(val reflect.Value, w *encbuf) error {
+var byteType = reflect.TypeOf(byte(0))
+
+func makeByteArrayWriter(typ reflect.Type) writer {
+	length := typ.Len()
+	if length == 0 {
+		return writeLengthZeroByteArray
+	} else if length == 1 {
+		return writeLengthOneByteArray
+	}
+	if typ.Elem() != byteType {
+		return writeNamedByteArray
+	}
+	return func(val reflect.Value, w *encbuf) error {
+		writeByteArrayCopy(length, val, w)
+		return nil
+	}
+}
+
+func writeLengthZeroByteArray(val reflect.Value, w *encbuf) error {
+	w.str = append(w.str, 0x80)
+	return nil
+}
+
+func writeLengthOneByteArray(val reflect.Value, w *encbuf) error {
+	b := byte(val.Index(0).Uint())
+	if b <= 0x7f {
+		w.str = append(w.str, b)
+	} else {
+		w.str = append(w.str, 0x81, b)
+	}
+	return nil
+}
+
+// writeByteArrayCopy encodes byte arrays using reflect.Copy. This is
+// the fast path for [N]byte where N > 1.
+func writeByteArrayCopy(length int, val reflect.Value, w *encbuf) {
+	w.encodeStringHeader(length)
+	offset := len(w.str)
+	w.str = append(w.str, make([]byte, length)...)
+	w.bufvalue.SetBytes(w.str[offset:])
+	reflect.Copy(w.bufvalue, val)
+}
+
+// writeNamedByteArray encodes byte arrays with named element type.
+// This exists because reflect.Copy can't be used with such types.
+func writeNamedByteArray(val reflect.Value, w *encbuf) error {
 	if !val.CanAddr() {
 		// Slice requires the value to be addressable.
 		// Make it addressable by copying.
diff --git a/rlp/encode_test.go b/rlp/encode_test.go
index b4b9e51287..418ee10a35 100644
--- a/rlp/encode_test.go
+++ b/rlp/encode_test.go
@@ -25,6 +25,8 @@ import (
 	"math/big"
 	"sync"
 	"testing"
+
+	"github.com/ethereum/go-ethereum/common/math"
 )
 
 type testEncoder struct {
@@ -37,9 +39,8 @@ func (e *testEncoder) EncodeRLP(w io.Writer) error {
 	}
 	if e.err != nil {
 		return e.err
-	} else {
-		w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1})
 	}
+	w.Write([]byte{0, 1, 0, 1, 0, 1, 0, 1, 0, 1})
 	return nil
 }
 
@@ -137,16 +138,43 @@ var encTests = []encTest{
 	// negative ints are not supported
 	{val: big.NewInt(-1), error: "rlp: cannot encode negative *big.Int"},
 
-	// byte slices, strings
+	// byte arrays
+	{val: [0]byte{}, output: "80"},
+	{val: [1]byte{0}, output: "00"},
+	{val: [1]byte{1}, output: "01"},
+	{val: [1]byte{0x7F}, output: "7F"},
+	{val: [1]byte{0x80}, output: "8180"},
+	{val: [1]byte{0xFF}, output: "81FF"},
+	{val: [3]byte{1, 2, 3}, output: "83010203"},
+	{val: [57]byte{1, 2, 3}, output: "B839010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
+
+	// named byte type arrays
+	{val: [0]namedByteType{}, output: "80"},
+	{val: [1]namedByteType{0}, output: "00"},
+	{val: [1]namedByteType{1}, output: "01"},
+	{val: [1]namedByteType{0x7F}, output: "7F"},
+	{val: [1]namedByteType{0x80}, output: "8180"},
+	{val: [1]namedByteType{0xFF}, output: "81FF"},
+	{val: [3]namedByteType{1, 2, 3}, output: "83010203"},
+	{val: [57]namedByteType{1, 2, 3}, output: "B839010203000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},
+
+	// byte slices
 	{val: []byte{}, output: "80"},
+	{val: []byte{0}, output: "00"},
 	{val: []byte{0x7E}, output: "7E"},
 	{val: []byte{0x7F}, output: "7F"},
 	{val: []byte{0x80}, output: "8180"},
 	{val: []byte{1, 2, 3}, output: "83010203"},
 
+	// named byte type slices
+	{val: []namedByteType{}, output: "80"},
+	{val: []namedByteType{0}, output: "00"},
+	{val: []namedByteType{0x7E}, output: "7E"},
+	{val: []namedByteType{0x7F}, output: "7F"},
+	{val: []namedByteType{0x80}, output: "8180"},
 	{val: []namedByteType{1, 2, 3}, output: "83010203"},
-	{val: [...]namedByteType{1, 2, 3}, output: "83010203"},
 
+	// strings
 	{val: "", output: "80"},
 	{val: "\x7E", output: "7E"},
 	{val: "\x7F", output: "7F"},
@@ -401,3 +429,36 @@ func TestEncodeToReaderReturnToPool(t *testing.T) {
 	}
 	wg.Wait()
 }
+
+var sink interface{}
+
+func BenchmarkIntsize(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		sink = intsize(0x12345678)
+	}
+}
+
+func BenchmarkPutint(b *testing.B) {
+	buf := make([]byte, 8)
+	for i := 0; i < b.N; i++ {
+		putint(buf, 0x12345678)
+		sink = buf
+	}
+}
+
+func BenchmarkEncodeBigInts(b *testing.B) {
+	ints := make([]*big.Int, 200)
+	for i := range ints {
+		ints[i] = math.BigPow(2, int64(i))
+	}
+	out := bytes.NewBuffer(make([]byte, 0, 4096))
+	b.ResetTimer()
+	b.ReportAllocs()
+
+	for i := 0; i < b.N; i++ {
+		out.Reset()
+		if err := Encode(out, ints); err != nil {
+			b.Fatal(err)
+		}
+	}
+}
diff --git a/rlp/iterator.go b/rlp/iterator.go
new file mode 100644
index 0000000000..559e03a868
--- /dev/null
+++ b/rlp/iterator.go
@@ -0,0 +1,61 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package rlp
+
+type listIterator struct {
+	data []byte
+	next []byte
+	err  error
+}
+
+// NewListIterator creates an iterator for the (list) represented by data
+// TODO: Consider removing this implementation, as it is no longer used.
+func NewListIterator(data RawValue) (*listIterator, error) {
+	k, t, c, err := readKind(data)
+	if err != nil {
+		return nil, err
+	}
+	if k != List {
+		return nil, ErrExpectedList
+	}
+	it := &listIterator{
+		data: data[t : t+c],
+	}
+	return it, nil
+
+}
+
+// Next forwards the iterator one step, returns true if it was not at end yet
+func (it *listIterator) Next() bool {
+	if len(it.data) == 0 {
+		return false
+	}
+	_, t, c, err := readKind(it.data)
+	it.next = it.data[:t+c]
+	it.data = it.data[t+c:]
+	it.err = err
+	return true
+}
+
+// Value returns the current value
+func (it *listIterator) Value() []byte {
+	return it.next
+}
+
+func (it *listIterator) Err() error {
+	return it.err
+}
diff --git a/rlp/iterator_test.go b/rlp/iterator_test.go
new file mode 100644
index 0000000000..53c3819186
--- /dev/null
+++ b/rlp/iterator_test.go
@@ -0,0 +1,59 @@
+// Copyright 2019 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package rlp
+
+import (
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+// TestIterator tests some basic things about the ListIterator. A more
+// comprehensive test can be found in core/rlp_test.go, where we can
+// use both types and rlp without dependency cycles
+func TestIterator(t *testing.T) {
+	bodyRlpHex := "0xf902cbf8d6f869800182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ba01025c66fad28b4ce3370222624d952c35529e602af7cbe04f667371f61b0e3b3a00ab8813514d1217059748fd903288ace1b4001a4bc5fbde2790debdc8167de2ff869010182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ca05ac4cf1d19be06f3742c21df6c49a7e929ceb3dbaf6a09f3cfb56ff6828bd9a7a06875970133a35e63ac06d360aa166d228cc013e9b96e0a2cae7f55b22e1ee2e8f901f0f901eda0c75448377c0e426b8017b23c5f77379ecf69abc1d5c224284ad3ba1c46c59adaa00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"
+	bodyRlp := hexutil.MustDecode(bodyRlpHex)
+
+	it, err := NewListIterator(bodyRlp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Check that txs exist
+	if !it.Next() {
+		t.Fatal("expected two elems, got zero")
+	}
+	txs := it.Value()
+	// Check that uncles exist
+	if !it.Next() {
+		t.Fatal("expected two elems, got one")
+	}
+	txit, err := NewListIterator(txs)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var i = 0
+	for txit.Next() {
+		if txit.err != nil {
+			t.Fatal(txit.err)
+		}
+		i++
+	}
+	if exp := 2; i != exp {
+		t.Errorf("count wrong, expected %d got %d", i, exp)
+	}
+}
diff --git a/rlp/raw.go b/rlp/raw.go
index 2b3f328f66..3071e99cab 100644
--- a/rlp/raw.go
+++ b/rlp/raw.go
@@ -57,6 +57,32 @@ func SplitString(b []byte) (content, rest []byte, err error) {
 	return content, rest, nil
 }
 
+// SplitUint64 decodes an integer at the beginning of b.
+// It also returns the remaining data after the integer in 'rest'.
+func SplitUint64(b []byte) (x uint64, rest []byte, err error) {
+	content, rest, err := SplitString(b)
+	if err != nil {
+		return 0, b, err
+	}
+	switch {
+	case len(content) == 0:
+		return 0, rest, nil
+	case len(content) == 1:
+		if content[0] == 0 {
+			return 0, b, ErrCanonInt
+		}
+		return uint64(content[0]), rest, nil
+	case len(content) > 8:
+		return 0, b, errUintOverflow
+	default:
+		x, err = readSize(content, byte(len(content)))
+		if err != nil {
+			return 0, b, ErrCanonInt
+		}
+		return x, rest, nil
+	}
+}
+
 // SplitList splits b into the content of a list and any remaining
 // bytes after the list.
 func SplitList(b []byte) (content, rest []byte, err error) {
@@ -154,3 +180,74 @@ func readSize(b []byte, slen byte) (uint64, error) {
 	}
 	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),
+		)
+	}
+}
diff --git a/rlp/raw_test.go b/rlp/raw_test.go
index 2aad042100..c976c4f734 100644
--- a/rlp/raw_test.go
+++ b/rlp/raw_test.go
@@ -21,6 +21,7 @@ import (
 	"io"
 	"reflect"
 	"testing"
+	"testing/quick"
 )
 
 func TestCountValues(t *testing.T) {
@@ -71,6 +72,49 @@ func TestSplitTypes(t *testing.T) {
 	}
 }
 
+func TestSplitUint64(t *testing.T) {
+	tests := []struct {
+		input string
+		val   uint64
+		rest  string
+		err   error
+	}{
+		{"01", 1, "", nil},
+		{"7FFF", 0x7F, "FF", nil},
+		{"80FF", 0, "FF", nil},
+		{"81FAFF", 0xFA, "FF", nil},
+		{"82FAFAFF", 0xFAFA, "FF", nil},
+		{"83FAFAFAFF", 0xFAFAFA, "FF", nil},
+		{"84FAFAFAFAFF", 0xFAFAFAFA, "FF", nil},
+		{"85FAFAFAFAFAFF", 0xFAFAFAFAFA, "FF", nil},
+		{"86FAFAFAFAFAFAFF", 0xFAFAFAFAFAFA, "FF", nil},
+		{"87FAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFA, "FF", nil},
+		{"88FAFAFAFAFAFAFAFAFF", 0xFAFAFAFAFAFAFAFA, "FF", nil},
+
+		// errors
+		{"", 0, "", io.ErrUnexpectedEOF},
+		{"00", 0, "00", ErrCanonInt},
+		{"81", 0, "81", ErrValueTooLarge},
+		{"8100", 0, "8100", ErrCanonSize},
+		{"8200FF", 0, "8200FF", ErrCanonInt},
+		{"8103FF", 0, "8103FF", ErrCanonSize},
+		{"89FAFAFAFAFAFAFAFAFAFF", 0, "89FAFAFAFAFAFAFAFAFAFF", errUintOverflow},
+	}
+
+	for i, test := range tests {
+		val, rest, err := SplitUint64(unhex(test.input))
+		if val != test.val {
+			t.Errorf("test %d: val mismatch: got %x, want %x (input %q)", i, val, test.val, test.input)
+		}
+		if !bytes.Equal(rest, unhex(test.rest)) {
+			t.Errorf("test %d: rest mismatch: got %x, want %s (input %q)", i, rest, test.rest, test.input)
+		}
+		if err != test.err {
+			t.Errorf("test %d: error mismatch: got %q, want %q", i, err, test.err)
+		}
+	}
+}
+
 func TestSplit(t *testing.T) {
 	tests := []struct {
 		input     string
@@ -78,7 +122,9 @@ func TestSplit(t *testing.T) {
 		val, rest string
 		err       error
 	}{
+		{input: "00FFFF", kind: Byte, val: "00", rest: "FFFF"},
 		{input: "01FFFF", kind: Byte, val: "01", rest: "FFFF"},
+		{input: "7FFFFF", kind: Byte, val: "7F", rest: "FFFF"},
 		{input: "80FFFF", kind: String, val: "", rest: "FFFF"},
 		{input: "C3010203", kind: List, val: "010203"},
 
@@ -194,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)
+	}
+}
diff --git a/rlp/typecache.go b/rlp/typecache.go
index e9a1e3f9e2..6026e1a649 100644
--- a/rlp/typecache.go
+++ b/rlp/typecache.go
@@ -210,6 +210,10 @@ func isUint(k reflect.Kind) bool {
 	return k >= reflect.Uint && k <= reflect.Uintptr
 }
 
+func isByte(typ reflect.Type) bool {
+	return typ.Kind() == reflect.Uint8 && !typ.Implements(encoderInterface)
+}
+
 func isByteArray(typ reflect.Type) bool {
 	return (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) && isByte(typ.Elem())
 }
diff --git a/rpc/client.go b/rpc/client.go
index 4b436a5c4e..c2ffa532cc 100644
--- a/rpc/client.go
+++ b/rpc/client.go
@@ -85,7 +85,7 @@ type Client struct {
 
 	// writeConn is used for writing to the connection on the caller's goroutine. It should
 	// only be accessed outside of dispatch, with the write lock held. The write lock is
-	// taken by sending on requestOp and released by sending on sendDone.
+	// taken by sending on reqInit and released by sending on reqSent.
 	writeConn jsonWriter
 
 	// for dispatch
@@ -260,6 +260,19 @@ func (c *Client) Close() {
 	}
 }
 
+// SetHeader adds a custom HTTP header to the client's requests.
+// This method only works for clients using HTTP, it doesn't have
+// any effect for clients using another transport.
+func (c *Client) SetHeader(key, value string) {
+	if !c.isHTTP {
+		return
+	}
+	conn := c.writeConn.(*httpConn)
+	conn.mu.Lock()
+	conn.headers.Set(key, value)
+	conn.mu.Unlock()
+}
+
 // Call performs a JSON-RPC call with the given arguments and unmarshals into
 // result if no error occurred.
 //
@@ -391,9 +404,8 @@ func (c *Client) Notify(ctx context.Context, method string, args ...interface{})
 
 	if c.isHTTP {
 		return c.sendHTTP(ctx, op, msg)
-	} else {
-		return c.send(ctx, op, msg)
 	}
+	return c.send(ctx, op, msg)
 }
 
 // EthSubscribe registers a subscripion under the "eth" namespace.
@@ -402,6 +414,7 @@ func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ...
 }
 
 // ShhSubscribe registers a subscripion under the "shh" namespace.
+// Deprecated: use Subscribe(ctx, "shh", ...).
 func (c *Client) ShhSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) {
 	return c.Subscribe(ctx, "shh", channel, args...)
 }
diff --git a/rpc/client_example_test.go b/rpc/client_example_test.go
index 3bb8717b80..044b57a9c4 100644
--- a/rpc/client_example_test.go
+++ b/rpc/client_example_test.go
@@ -19,9 +19,9 @@ package rpc_test
 import (
 	"context"
 	"fmt"
-	"math/big"
 	"time"
 
+	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/rpc"
 )
 
@@ -31,16 +31,16 @@ import (
 // eth_getBlockByNumber("latest", {})
 //    returns the latest block object.
 //
-// eth_subscribe("newBlocks")
+// eth_subscribe("newHeads")
 //    creates a subscription which fires block objects when new blocks arrive.
 
 type Block struct {
-	Number *big.Int
+	Number *hexutil.Big
 }
 
 func ExampleClientSubscription() {
 	// Connect the client.
-	client, _ := rpc.Dial("ws://127.0.0.1:8485")
+	client, _ := rpc.Dial("ws://127.0.0.1:8545")
 	subch := make(chan Block)
 
 	// Ensure that subch receives the latest block.
@@ -75,7 +75,8 @@ func subscribeBlocks(client *rpc.Client, subch chan Block) {
 	// The connection is established now.
 	// Update the channel with the current block.
 	var lastBlock Block
-	if err := client.CallContext(ctx, &lastBlock, "eth_getBlockByNumber", "latest"); err != nil {
+	err = client.CallContext(ctx, &lastBlock, "eth_getBlockByNumber", "latest", false)
+	if err != nil {
 		fmt.Println("can't get latest block:", err)
 		return
 	}
diff --git a/rpc/client_test.go b/rpc/client_test.go
index 8a996aeda5..224eb0c5c8 100644
--- a/rpc/client_test.go
+++ b/rpc/client_test.go
@@ -18,6 +18,7 @@ package rpc
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"math/rand"
 	"net"
@@ -26,6 +27,7 @@ import (
 	"os"
 	"reflect"
 	"runtime"
+	"strings"
 	"sync"
 	"testing"
 	"time"
@@ -66,6 +68,33 @@ func TestClientResponseType(t *testing.T) {
 	}
 }
 
+// This test checks that server-returned errors with code and data come out of Client.Call.
+func TestClientErrorData(t *testing.T) {
+	server := newTestServer()
+	defer server.Stop()
+	client := DialInProc(server)
+	defer client.Close()
+
+	var resp interface{}
+	err := client.Call(&resp, "test_returnError")
+	if err == nil {
+		t.Fatal("expected error")
+	}
+
+	// Check code.
+	if e, ok := err.(Error); !ok {
+		t.Fatalf("client did not return rpc.Error, got %#v", e)
+	} else if e.ErrorCode() != (testError{}.ErrorCode()) {
+		t.Fatalf("wrong error code %d, want %d", e.ErrorCode(), testError{}.ErrorCode())
+	}
+	// Check data.
+	if e, ok := err.(DataError); !ok {
+		t.Fatalf("client did not return rpc.DataError, got %#v", e)
+	} else if e.ErrorData() != (testError{}.ErrorData()) {
+		t.Fatalf("wrong error data %#v, want %#v", e.ErrorData(), testError{}.ErrorData())
+	}
+}
+
 func TestClientBatchRequest(t *testing.T) {
 	server := newTestServer()
 	defer server.Stop()
@@ -348,6 +377,93 @@ func TestClientCloseUnsubscribeRace(t *testing.T) {
 	}
 }
 
+// unsubscribeRecorder collects the subscription IDs of *_unsubscribe calls.
+type unsubscribeRecorder struct {
+	ServerCodec
+	unsubscribes map[string]bool
+}
+
+func (r *unsubscribeRecorder) readBatch() ([]*jsonrpcMessage, bool, error) {
+	if r.unsubscribes == nil {
+		r.unsubscribes = make(map[string]bool)
+	}
+
+	msgs, batch, err := r.ServerCodec.readBatch()
+	for _, msg := range msgs {
+		if msg.isUnsubscribe() {
+			var params []string
+			if err := json.Unmarshal(msg.Params, ¶ms); err != nil {
+				panic("unsubscribe decode error: " + err.Error())
+			}
+			r.unsubscribes[params[0]] = true
+		}
+	}
+	return msgs, batch, err
+}
+
+// This checks that Client calls the _unsubscribe method on the server when Unsubscribe is
+// called on a subscription.
+func TestClientSubscriptionUnsubscribeServer(t *testing.T) {
+	t.Parallel()
+
+	// Create the server.
+	srv := NewServer()
+	srv.RegisterName("nftest", new(notificationTestService))
+	p1, p2 := net.Pipe()
+	recorder := &unsubscribeRecorder{ServerCodec: NewCodec(p1)}
+	go srv.ServeCodec(recorder, OptionMethodInvocation|OptionSubscriptions)
+	defer srv.Stop()
+
+	// Create the client on the other end of the pipe.
+	client, _ := newClient(context.Background(), func(context.Context) (ServerCodec, error) {
+		return NewCodec(p2), nil
+	})
+	defer client.Close()
+
+	// Create the subscription.
+	ch := make(chan int)
+	sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 1, 1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Unsubscribe and check that unsubscribe was called.
+	sub.Unsubscribe()
+	if !recorder.unsubscribes[sub.subid] {
+		t.Fatal("client did not call unsubscribe method")
+	}
+	if _, open := <-sub.Err(); open {
+		t.Fatal("subscription error channel not closed after unsubscribe")
+	}
+}
+
+// This checks that the subscribed channel can be closed after Unsubscribe.
+// It is the reproducer for https://github.com/ethereum/go-ethereum/issues/22322
+func TestClientSubscriptionChannelClose(t *testing.T) {
+	t.Parallel()
+
+	var (
+		srv     = NewServer()
+		httpsrv = httptest.NewServer(srv.WebsocketHandler(nil))
+		wsURL   = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
+	)
+	defer srv.Stop()
+	defer httpsrv.Close()
+
+	srv.RegisterName("nftest", new(notificationTestService))
+	client, _ := Dial(wsURL)
+
+	for i := 0; i < 100; i++ {
+		ch := make(chan int, 100)
+		sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1)
+		if err != nil {
+			t.Fatal(err)
+		}
+		sub.Unsubscribe()
+		close(ch)
+	}
+}
+
 // This test checks that Client doesn't lock up when a single subscriber
 // doesn't read subscription events.
 func TestClientNotificationStorm(t *testing.T) {
@@ -399,7 +515,43 @@ func TestClientNotificationStorm(t *testing.T) {
 	}
 
 	doTest(8000, false)
-	doTest(23000, true)
+	doTest(24000, true)
+}
+
+func TestClientSetHeader(t *testing.T) {
+	var gotHeader bool
+	srv := newTestServer()
+	httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Header.Get("test") == "ok" {
+			gotHeader = true
+		}
+		srv.ServeHTTP(w, r)
+	}))
+	defer httpsrv.Close()
+	defer srv.Stop()
+
+	client, err := Dial(httpsrv.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer client.Close()
+
+	client.SetHeader("test", "ok")
+	if _, err := client.SupportedModules(); err != nil {
+		t.Fatal(err)
+	}
+	if !gotHeader {
+		t.Fatal("client did not set custom header")
+	}
+
+	// Check that Content-Type can be replaced.
+	client.SetHeader("content-type", "application/x-garbage")
+	_, err = client.SupportedModules()
+	if err == nil {
+		t.Fatal("no error for invalid content-type header")
+	} else if !strings.Contains(err.Error(), "Unsupported Media Type") {
+		t.Fatalf("error is not related to content-type: %q", err)
+	}
 }
 
 func TestClientHTTP(t *testing.T) {
diff --git a/rpc/endpoints.go b/rpc/endpoints.go
index 9fc0705172..d78ebe2858 100644
--- a/rpc/endpoints.go
+++ b/rpc/endpoints.go
@@ -18,6 +18,7 @@ package rpc
 
 import (
 	"net"
+	"strings"
 
 	"github.com/ethereum/go-ethereum/log"
 )
@@ -25,13 +26,22 @@ import (
 // StartIPCEndpoint starts an IPC endpoint.
 func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) {
 	// Register all the APIs exposed by the services.
-	handler := NewServer()
+	var (
+		handler    = NewServer()
+		regMap     = make(map[string]struct{})
+		registered []string
+	)
 	for _, api := range apis {
 		if err := handler.RegisterName(api.Namespace, api.Service); err != nil {
+			log.Info("IPC registration failed", "namespace", api.Namespace, "error", err)
 			return nil, nil, err
 		}
-		log.Debug("IPC registered", "namespace", api.Namespace)
+		if _, ok := regMap[api.Namespace]; !ok {
+			registered = append(registered, api.Namespace)
+			regMap[api.Namespace] = struct{}{}
+		}
 	}
+	log.Debug("IPCs registered", "namespaces", strings.Join(registered, ","))
 	// All APIs registered, start the IPC listener.
 	listener, err := ipcListen(ipcEndpoint)
 	if err != nil {
diff --git a/rpc/errors.go b/rpc/errors.go
index c3aa826cc8..4c06a745fb 100644
--- a/rpc/errors.go
+++ b/rpc/errors.go
@@ -18,6 +18,44 @@ package rpc
 
 import "fmt"
 
+// HTTPError is returned by client operations when the HTTP status code of the
+// response is not a 2xx status.
+type HTTPError struct {
+	StatusCode int
+	Status     string
+	Body       []byte
+}
+
+func (err HTTPError) Error() string {
+	if len(err.Body) == 0 {
+		return err.Status
+	}
+	return fmt.Sprintf("%v: %s", err.Status, err.Body)
+}
+
+// Error wraps RPC errors, which contain an error code in addition to the message.
+type Error interface {
+	Error() string  // returns the message
+	ErrorCode() int // returns the code
+}
+
+// A DataError contains some data in addition to the error message.
+type DataError interface {
+	Error() string          // returns the message
+	ErrorData() interface{} // returns the error data
+}
+
+// Error types defined below are the built-in JSON-RPC errors.
+
+var (
+	_ Error = new(methodNotFoundError)
+	_ Error = new(subscriptionNotFoundError)
+	_ Error = new(parseError)
+	_ Error = new(invalidRequestError)
+	_ Error = new(invalidMessageError)
+	_ Error = new(invalidParamsError)
+)
+
 const defaultErrorCode = -32000
 
 type methodNotFoundError struct{ method string }
diff --git a/rpc/handler.go b/rpc/handler.go
index 67d7eb477a..2ca6480ec2 100644
--- a/rpc/handler.go
+++ b/rpc/handler.go
@@ -189,7 +189,7 @@ func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) {
 	}
 	for id, sub := range h.clientSubs {
 		delete(h.clientSubs, id)
-		sub.quitWithError(false, err)
+		sub.close(err)
 	}
 }
 
@@ -281,7 +281,7 @@ func (h *handler) handleResponse(msg *jsonrpcMessage) {
 		return
 	}
 	if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil {
-		go op.sub.start()
+		go op.sub.run()
 		h.clientSubs[op.sub.subid] = op.sub
 	}
 }
@@ -296,11 +296,19 @@ func (h *handler) handleCallMsg(ctx *callProc, reqCtx context.Context, msg *json
 		return nil
 	case msg.isCall():
 		resp := h.handleCall(ctx, msg)
+		var ctx []interface{}
+		ctx = append(ctx, "reqid", idForLog{msg.ID}, "t", time.Since(start))
 		if resp.Error != nil {
 			xForward := reqCtx.Value("X-Forwarded-For")
 			h.log.Warn("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start), "err", resp.Error.Message, "X-Forwarded-For", xForward)
+
+			ctx = append(ctx, "err", resp.Error.Message)
+			if resp.Error.Data != nil {
+				ctx = append(ctx, "errdata", resp.Error.Data)
+			}
+			h.log.Warn("Served "+msg.Method, ctx...)
 		} else {
-			h.log.Debug("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start))
+			h.log.Debug("Served "+msg.Method, ctx...)
 		}
 		return resp
 	case msg.hasValidID():
diff --git a/rpc/http.go b/rpc/http.go
index 3b6cf75f86..9b64495fb8 100644
--- a/rpc/http.go
+++ b/rpc/http.go
@@ -26,6 +26,7 @@ import (
 	"io/ioutil"
 	"mime"
 	"net/http"
+	"net/url"
 	"sync"
 	"time"
 )
@@ -40,9 +41,11 @@ var acceptedContentTypes = []string{contentType, "application/json-rpc", "applic
 
 type httpConn struct {
 	client    *http.Client
-	req       *http.Request
+	url       string
 	closeOnce sync.Once
 	closeCh   chan interface{}
+	mu        sync.Mutex // protects headers
+	headers   http.Header
 }
 
 // httpConn is treated specially by Client.
@@ -51,7 +54,7 @@ func (hc *httpConn) writeJSON(context.Context, interface{}) error {
 }
 
 func (hc *httpConn) remoteAddr() string {
-	return hc.req.URL.String()
+	return hc.url
 }
 
 func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) {
@@ -102,16 +105,24 @@ var DefaultHTTPTimeouts = HTTPTimeouts{
 // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
 // using the provided HTTP Client.
 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
-	req, err := http.NewRequest(http.MethodPost, endpoint, nil)
+	// Sanity check URL so we don't end up with a client that will fail every request.
+	_, err := url.Parse(endpoint)
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("Content-Type", contentType)
-	req.Header.Set("Accept", contentType)
 
 	initctx := context.Background()
+	headers := make(http.Header, 2)
+	headers.Set("accept", contentType)
+	headers.Set("content-type", contentType)
 	return newClient(initctx, func(context.Context) (ServerCodec, error) {
-		return &httpConn{client: client, req: req, closeCh: make(chan interface{})}, nil
+		hc := &httpConn{
+			client:  client,
+			headers: headers,
+			url:     endpoint,
+			closeCh: make(chan interface{}),
+		}
+		return hc, nil
 	})
 }
 
@@ -123,19 +134,11 @@ func DialHTTP(endpoint string) (*Client, error) {
 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
 	hc := c.writeConn.(*httpConn)
 	respBody, err := hc.doRequest(ctx, msg)
-	if respBody != nil {
-		defer respBody.Close()
-	}
-
 	if err != nil {
-		if respBody != nil {
-			buf := new(bytes.Buffer)
-			if _, err2 := buf.ReadFrom(respBody); err2 == nil {
-				return fmt.Errorf("%v %v", err, buf.String())
-			}
-		}
 		return err
 	}
+	defer respBody.Close()
+
 	var respmsg jsonrpcMessage
 	if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
 		return err
@@ -166,16 +169,34 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos
 	if err != nil {
 		return nil, err
 	}
-	req := hc.req.WithContext(ctx)
-	req.Body = ioutil.NopCloser(bytes.NewReader(body))
+	req, err := http.NewRequestWithContext(ctx, "POST", hc.url, ioutil.NopCloser(bytes.NewReader(body)))
+	if err != nil {
+		return nil, err
+	}
 	req.ContentLength = int64(len(body))
 
+	// set headers
+	hc.mu.Lock()
+	req.Header = hc.headers.Clone()
+	hc.mu.Unlock()
+
+	// do request
 	resp, err := hc.client.Do(req)
 	if err != nil {
 		return nil, err
 	}
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
-		return resp.Body, errors.New(resp.Status)
+		var buf bytes.Buffer
+		var body []byte
+		if _, err := buf.ReadFrom(resp.Body); err == nil {
+			body = buf.Bytes()
+		}
+
+		return nil, HTTPError{
+			Status:     resp.Status,
+			StatusCode: resp.StatusCode,
+			Body:       body,
+		}
 	}
 	return resp.Body, nil
 }
@@ -208,6 +229,7 @@ func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil }
 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// Permit dumb empty requests for remote health-checks (AWS)
 	if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" {
+		w.WriteHeader(http.StatusOK)
 		return
 	}
 	if code, err := validateRequest(r); err != nil {
diff --git a/rpc/http_test.go b/rpc/http_test.go
index b3f694d8af..97f8d44c39 100644
--- a/rpc/http_test.go
+++ b/rpc/http_test.go
@@ -23,32 +23,142 @@ import (
 	"testing"
 )
 
+func confirmStatusCode(t *testing.T, got, want int) {
+	t.Helper()
+	if got == want {
+		return
+	}
+	if gotName := http.StatusText(got); len(gotName) > 0 {
+		if wantName := http.StatusText(want); len(wantName) > 0 {
+			t.Fatalf("response status code: got %d (%s), want %d (%s)", got, gotName, want, wantName)
+		}
+	}
+	t.Fatalf("response status code: got %d, want %d", got, want)
+}
+
+func confirmRequestValidationCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
+	t.Helper()
+	request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
+	if len(contentType) > 0 {
+		request.Header.Set("Content-Type", contentType)
+	}
+	code, err := validateRequest(request)
+	if code == 0 {
+		if err != nil {
+			t.Errorf("validation: got error %v, expected nil", err)
+		}
+	} else if err == nil {
+		t.Errorf("validation: code %d: got nil, expected error", code)
+	}
+	confirmStatusCode(t, code, expectedStatusCode)
+}
+
 func TestHTTPErrorResponseWithDelete(t *testing.T) {
-	testHTTPErrorResponse(t, http.MethodDelete, contentType, "", http.StatusMethodNotAllowed)
+	confirmRequestValidationCode(t, http.MethodDelete, contentType, "", http.StatusMethodNotAllowed)
 }
 
 func TestHTTPErrorResponseWithPut(t *testing.T) {
-	testHTTPErrorResponse(t, http.MethodPut, contentType, "", http.StatusMethodNotAllowed)
+	confirmRequestValidationCode(t, http.MethodPut, contentType, "", http.StatusMethodNotAllowed)
 }
 
 func TestHTTPErrorResponseWithMaxContentLength(t *testing.T) {
 	body := make([]rune, maxRequestContentLength+1)
-	testHTTPErrorResponse(t,
+	confirmRequestValidationCode(t,
 		http.MethodPost, contentType, string(body), http.StatusRequestEntityTooLarge)
 }
 
 func TestHTTPErrorResponseWithEmptyContentType(t *testing.T) {
-	testHTTPErrorResponse(t, http.MethodPost, "", "", http.StatusUnsupportedMediaType)
+	confirmRequestValidationCode(t, http.MethodPost, "", "", http.StatusUnsupportedMediaType)
 }
 
 func TestHTTPErrorResponseWithValidRequest(t *testing.T) {
-	testHTTPErrorResponse(t, http.MethodPost, contentType, "", 0)
+	confirmRequestValidationCode(t, http.MethodPost, contentType, "", 0)
 }
 
-func testHTTPErrorResponse(t *testing.T, method, contentType, body string, expected int) {
-	request := httptest.NewRequest(method, "http://url.com", strings.NewReader(body))
-	request.Header.Set("content-type", contentType)
-	if code, _ := validateRequest(request); code != expected {
-		t.Fatalf("response code should be %d not %d", expected, code)
+func confirmHTTPRequestYieldsStatusCode(t *testing.T, method, contentType, body string, expectedStatusCode int) {
+	t.Helper()
+	s := Server{}
+	ts := httptest.NewServer(&s)
+	defer ts.Close()
+
+	request, err := http.NewRequest(method, ts.URL, strings.NewReader(body))
+	if err != nil {
+		t.Fatalf("failed to create a valid HTTP request: %v", err)
+	}
+	if len(contentType) > 0 {
+		request.Header.Set("Content-Type", contentType)
+	}
+	resp, err := http.DefaultClient.Do(request)
+	if err != nil {
+		t.Fatalf("request failed: %v", err)
+	}
+	confirmStatusCode(t, resp.StatusCode, expectedStatusCode)
+}
+
+func TestHTTPResponseWithEmptyGet(t *testing.T) {
+	confirmHTTPRequestYieldsStatusCode(t, http.MethodGet, "", "", http.StatusOK)
+}
+
+// This checks that maxRequestContentLength is not applied to the response of a request.
+func TestHTTPRespBodyUnlimited(t *testing.T) {
+	const respLength = maxRequestContentLength * 3
+
+	s := NewServer()
+	defer s.Stop()
+	s.RegisterName("test", largeRespService{respLength})
+	ts := httptest.NewServer(s)
+	defer ts.Close()
+
+	c, err := DialHTTP(ts.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer c.Close()
+
+	var r string
+	if err := c.Call(&r, "test_largeResp"); err != nil {
+		t.Fatal(err)
+	}
+	if len(r) != respLength {
+		t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
+	}
+}
+
+// Tests that an HTTP error results in an HTTPError instance
+// being returned with the expected attributes.
+func TestHTTPErrorResponse(t *testing.T) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		http.Error(w, "error has occurred!", http.StatusTeapot)
+	}))
+	defer ts.Close()
+
+	c, err := DialHTTP(ts.URL)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var r string
+	err = c.Call(&r, "test_method")
+	if err == nil {
+		t.Fatal("error was expected")
+	}
+
+	httpErr, ok := err.(HTTPError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+
+	if httpErr.StatusCode != http.StatusTeapot {
+		t.Error("unexpected status code", httpErr.StatusCode)
+	}
+	if httpErr.Status != "418 I'm a teapot" {
+		t.Error("unexpected status text", httpErr.Status)
+	}
+	if body := string(httpErr.Body); body != "error has occurred!\n" {
+		t.Error("unexpected body", body)
+	}
+
+	if errMsg := httpErr.Error(); errMsg != "418 I'm a teapot: error has occurred!\n" {
+		t.Error("unexpected error message", errMsg)
 	}
 }
diff --git a/rpc/json.go b/rpc/json.go
index f0250fa4a5..1daee3db82 100644
--- a/rpc/json.go
+++ b/rpc/json.go
@@ -115,6 +115,10 @@ func errorMessage(err error) *jsonrpcMessage {
 	if ok {
 		msg.Error.Code = ec.ErrorCode()
 	}
+	de, ok := err.(DataError)
+	if ok {
+		msg.Error.Data = de.ErrorData()
+	}
 	return msg
 }
 
@@ -135,6 +139,10 @@ func (err *jsonError) ErrorCode() int {
 	return err.Code
 }
 
+func (err *jsonError) ErrorData() interface{} {
+	return err.Data
+}
+
 // Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec.
 type Conn interface {
 	io.ReadWriteCloser
diff --git a/rpc/server.go b/rpc/server.go
index eedcbf7957..5e037b4b33 100644
--- a/rpc/server.go
+++ b/rpc/server.go
@@ -36,7 +36,7 @@ const (
 	// OptionMethodInvocation is an indication that the codec supports RPC method calls
 	OptionMethodInvocation CodecOption = 1 << iota
 
-	// OptionSubscriptions is an indication that the codec suports RPC notifications
+	// OptionSubscriptions is an indication that the codec supports RPC notifications
 	OptionSubscriptions = 1 << iota // support pub sub
 )
 
diff --git a/rpc/server_test.go b/rpc/server_test.go
index 99cca26ddf..6a2b09e449 100644
--- a/rpc/server_test.go
+++ b/rpc/server_test.go
@@ -45,7 +45,7 @@ func TestServerRegisterName(t *testing.T) {
 		t.Fatalf("Expected service calc to be registered")
 	}
 
-	wantCallbacks := 8
+	wantCallbacks := 9
 	if len(svc.callbacks) != wantCallbacks {
 		t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks))
 	}
diff --git a/rpc/subscription.go b/rpc/subscription.go
index f9d60dcb98..942e764e5d 100644
--- a/rpc/subscription.go
+++ b/rpc/subscription.go
@@ -17,7 +17,6 @@
 package rpc
 
 import (
-	"bufio"
 	"container/list"
 	"context"
 	crand "crypto/rand"
@@ -51,10 +50,14 @@ func NewID() ID {
 
 // randomIDGenerator returns a function generates a random IDs.
 func randomIDGenerator() func() ID {
-	seed, err := binary.ReadVarint(bufio.NewReader(crand.Reader))
-	if err != nil {
+	var buf = make([]byte, 8)
+	var seed int64
+	if _, err := crand.Read(buf); err == nil {
+		seed = int64(binary.BigEndian.Uint64(buf))
+	} else {
 		seed = int64(time.Now().Nanosecond())
 	}
+
 	var (
 		mu  sync.Mutex
 		rng = rand.New(rand.NewSource(seed))
@@ -205,23 +208,37 @@ type ClientSubscription struct {
 	channel   reflect.Value
 	namespace string
 	subid     string
-	in        chan json.RawMessage
 
-	quitOnce sync.Once     // ensures quit is closed once
-	quit     chan struct{} // quit is closed when the subscription exits
-	errOnce  sync.Once     // ensures err is closed once
-	err      chan error
+	// The in channel receives notification values from client dispatcher.
+	in chan json.RawMessage
+
+	// The error channel receives the error from the forwarding loop.
+	// It is closed by Unsubscribe.
+	err     chan error
+	errOnce sync.Once
+
+	// Closing of the subscription is requested by sending on 'quit'. This is handled by
+	// the forwarding loop, which closes 'forwardDone' when it has stopped sending to
+	// sub.channel. Finally, 'unsubDone' is closed after unsubscribing on the server side.
+	quit        chan error
+	forwardDone chan struct{}
+	unsubDone   chan struct{}
 }
 
+// This is the sentinel value sent on sub.quit when Unsubscribe is called.
+var errUnsubscribed = errors.New("unsubscribed")
+
 func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription {
 	sub := &ClientSubscription{
-		client:    c,
-		namespace: namespace,
-		etype:     channel.Type().Elem(),
-		channel:   channel,
-		quit:      make(chan struct{}),
-		err:       make(chan error, 1),
-		in:        make(chan json.RawMessage),
+		client:      c,
+		namespace:   namespace,
+		etype:       channel.Type().Elem(),
+		channel:     channel,
+		in:          make(chan json.RawMessage),
+		quit:        make(chan error),
+		forwardDone: make(chan struct{}),
+		unsubDone:   make(chan struct{}),
+		err:         make(chan error, 1),
 	}
 	return sub
 }
@@ -229,9 +246,9 @@ func newClientSubscription(c *Client, namespace string, channel reflect.Value) *
 // Err returns the subscription error channel. The intended use of Err is to schedule
 // resubscription when the client connection is closed unexpectedly.
 //
-// The error channel receives a value when the subscription has ended due
-// to an error. The received error is nil if Close has been called
-// on the underlying client and no other error has occurred.
+// The error channel receives a value when the subscription has ended due to an error. The
+// received error is nil if Close has been called on the underlying client and no other
+// error has occurred.
 //
 // The error channel is closed when Unsubscribe is called on the subscription.
 func (sub *ClientSubscription) Err() <-chan error {
@@ -241,41 +258,63 @@ func (sub *ClientSubscription) Err() <-chan error {
 // Unsubscribe unsubscribes the notification and closes the error channel.
 // It can safely be called more than once.
 func (sub *ClientSubscription) Unsubscribe() {
-	sub.quitWithError(true, nil)
-	sub.errOnce.Do(func() { close(sub.err) })
-}
-
-func (sub *ClientSubscription) quitWithError(unsubscribeServer bool, err error) {
-	sub.quitOnce.Do(func() {
-		// The dispatch loop won't be able to execute the unsubscribe call
-		// if it is blocked on deliver. Close sub.quit first because it
-		// unblocks deliver.
-		close(sub.quit)
-		if unsubscribeServer {
-			sub.requestUnsubscribe()
-		}
-		if err != nil {
-			if err == ErrClientQuit {
-				err = nil // Adhere to subscription semantics.
-			}
-			sub.err <- err
+	sub.errOnce.Do(func() {
+		select {
+		case sub.quit <- errUnsubscribed:
+			<-sub.unsubDone
+		case <-sub.unsubDone:
 		}
+		close(sub.err)
 	})
 }
 
+// deliver is called by the client's message dispatcher to send a notification value.
 func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) {
 	select {
 	case sub.in <- result:
 		return true
-	case <-sub.quit:
+	case <-sub.forwardDone:
 		return false
 	}
 }
 
-func (sub *ClientSubscription) start() {
-	sub.quitWithError(sub.forward())
+// close is called by the client's message dispatcher when the connection is closed.
+func (sub *ClientSubscription) close(err error) {
+	select {
+	case sub.quit <- err:
+	case <-sub.forwardDone:
+	}
+}
+
+// run is the forwarding loop of the subscription. It runs in its own goroutine and
+// is launched by the client's handler after the subscription has been created.
+func (sub *ClientSubscription) run() {
+	defer close(sub.unsubDone)
+
+	unsubscribe, err := sub.forward()
+
+	// The client's dispatch loop won't be able to execute the unsubscribe call if it is
+	// blocked in sub.deliver() or sub.close(). Closing forwardDone unblocks them.
+	close(sub.forwardDone)
+
+	// Call the unsubscribe method on the server.
+	if unsubscribe {
+		sub.requestUnsubscribe()
+	}
+
+	// Send the error.
+	if err != nil {
+		if err == ErrClientQuit {
+			// ErrClientQuit gets here when Client.Close is called. This is reported as a
+			// nil error because it's not an error, but we can't close sub.err here.
+			err = nil
+		}
+		sub.err <- err
+	}
 }
 
+// forward is the forwarding loop. It takes in RPC notifications and sends them
+// on the subscription channel.
 func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
 	cases := []reflect.SelectCase{
 		{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
@@ -283,7 +322,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
 		{Dir: reflect.SelectSend, Chan: sub.channel},
 	}
 	buffer := list.New()
-	defer buffer.Init()
+
 	for {
 		var chosen int
 		var recv reflect.Value
@@ -298,7 +337,15 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
 
 		switch chosen {
 		case 0: // <-sub.quit
-			return false, nil
+			if !recv.IsNil() {
+				err = recv.Interface().(error)
+			}
+			if err == errUnsubscribed {
+				// Exiting because Unsubscribe was called, unsubscribe on server.
+				return true, nil
+			}
+			return false, err
+
 		case 1: // <-sub.in
 			val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
 			if err != nil {
@@ -308,6 +355,7 @@ func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
 				return true, ErrSubscriptionQueueOverflow
 			}
 			buffer.PushBack(val)
+
 		case 2: // sub.channel<-
 			cases[2].Send = reflect.Value{} // Don't hold onto the value.
 			buffer.Remove(buffer.Front())
diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go
index 3094c639e6..62afc1df44 100644
--- a/rpc/testservice_test.go
+++ b/rpc/testservice_test.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"encoding/binary"
 	"errors"
+	"strings"
 	"sync"
 	"time"
 )
@@ -63,6 +64,12 @@ type echoResult struct {
 	Args   *echoArgs
 }
 
+type testError struct{}
+
+func (testError) Error() string          { return "testError" }
+func (testError) ErrorCode() int         { return 444 }
+func (testError) ErrorData() interface{} { return "testError data" }
+
 func (s *testService) NoArgsRets() {}
 
 func (s *testService) Echo(str string, i int, args *echoArgs) echoResult {
@@ -99,6 +106,10 @@ func (s *testService) InvalidRets3() (string, string, error) {
 	return "", "", nil
 }
 
+func (s *testService) ReturnError() error {
+	return testError{}
+}
+
 func (s *testService) CallMeBack(ctx context.Context, method string, args []interface{}) (interface{}, error) {
 	c, ok := ClientFromContext(ctx)
 	if !ok {
@@ -184,3 +195,12 @@ func (s *notificationTestService) HangSubscription(ctx context.Context, val int)
 	}()
 	return subscription, nil
 }
+
+// largeRespService generates arbitrary-size JSON responses.
+type largeRespService struct {
+	length int
+}
+
+func (x largeRespService) LargeResp() string {
+	return strings.Repeat("x", x.length)
+}
diff --git a/rpc/types.go b/rpc/types.go
index dc9248d0fe..d1b878c785 100644
--- a/rpc/types.go
+++ b/rpc/types.go
@@ -35,12 +35,6 @@ type API struct {
 	Public    bool        // indication if the methods must be considered safe for public use
 }
 
-// Error wraps RPC errors, which contain an error code in addition to the message.
-type Error interface {
-	Error() string  // returns the message
-	ErrorCode() int // returns the code
-}
-
 // ServerCodec implements reading, parsing and writing RPC messages for the server side of
 // a RPC session. Implementations must be go-routine safe since the codec can be called in
 // multiple go-routines concurrently.
diff --git a/rpc/websocket.go b/rpc/websocket.go
index 6e37b8522d..ab55ae69c1 100644
--- a/rpc/websocket.go
+++ b/rpc/websocket.go
@@ -25,6 +25,7 @@ import (
 	"os"
 	"strings"
 	"sync"
+	"time"
 
 	mapset "github.com/deckarep/golang-set"
 	"github.com/ethereum/go-ethereum/log"
@@ -32,8 +33,11 @@ import (
 )
 
 const (
-	wsReadBuffer  = 1024
-	wsWriteBuffer = 1024
+	wsReadBuffer       = 1024
+	wsWriteBuffer      = 1024
+	wsPingInterval     = 60 * time.Second
+	wsPingWriteTimeout = 5 * time.Second
+	wsMessageSizeLimit = 15 * 1024 * 1024
 )
 
 var wsBufferPool = new(sync.Pool)
@@ -72,14 +76,14 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool {
 			allowAllOrigins = true
 		}
 		if origin != "" {
-			origins.Add(strings.ToLower(origin))
+			origins.Add(origin)
 		}
 	}
 	// allow localhost if no allowedOrigins are specified.
 	if len(origins.ToSlice()) == 0 {
 		origins.Add("http://localhost")
 		if hostname, err := os.Hostname(); err == nil {
-			origins.Add("http://" + strings.ToLower(hostname))
+			origins.Add("http://" + hostname)
 		}
 	}
 	log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice()))
@@ -94,7 +98,7 @@ func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool {
 		}
 		// Verify origin against whitelist.
 		origin := strings.ToLower(req.Header.Get("Origin"))
-		if allowAllOrigins || origins.Contains(origin) {
+		if allowAllOrigins || originIsAllowed(origins, origin) {
 			return true
 		}
 		log.Warn("Rejected WebSocket connection", "origin", origin)
@@ -117,6 +121,65 @@ func (e wsHandshakeError) Error() string {
 	return s
 }
 
+func originIsAllowed(allowedOrigins mapset.Set, browserOrigin string) bool {
+	it := allowedOrigins.Iterator()
+	for origin := range it.C {
+		if ruleAllowsOrigin(origin.(string), browserOrigin) {
+			return true
+		}
+	}
+	return false
+}
+
+func ruleAllowsOrigin(allowedOrigin string, browserOrigin string) bool {
+	var (
+		allowedScheme, allowedHostname, allowedPort string
+		browserScheme, browserHostname, browserPort string
+		err                                         error
+	)
+	allowedScheme, allowedHostname, allowedPort, err = parseOriginURL(allowedOrigin)
+	if err != nil {
+		log.Warn("Error parsing allowed origin specification", "spec", allowedOrigin, "error", err)
+		return false
+	}
+	browserScheme, browserHostname, browserPort, err = parseOriginURL(browserOrigin)
+	if err != nil {
+		log.Warn("Error parsing browser 'Origin' field", "Origin", browserOrigin, "error", err)
+		return false
+	}
+	if allowedScheme != "" && allowedScheme != browserScheme {
+		return false
+	}
+	if allowedHostname != "" && allowedHostname != browserHostname {
+		return false
+	}
+	if allowedPort != "" && allowedPort != browserPort {
+		return false
+	}
+	return true
+}
+
+func parseOriginURL(origin string) (string, string, string, error) {
+	parsedURL, err := url.Parse(strings.ToLower(origin))
+	if err != nil {
+		return "", "", "", err
+	}
+	var scheme, hostname, port string
+	if strings.Contains(origin, "://") {
+		scheme = parsedURL.Scheme
+		hostname = parsedURL.Hostname()
+		port = parsedURL.Port()
+	} else {
+		scheme = ""
+		hostname = parsedURL.Scheme
+		port = parsedURL.Opaque
+		if hostname == "" {
+			hostname = origin
+		}
+	}
+	return scheme, hostname, port, nil
+}
+
 // DialWebsocketWithDialer creates a new RPC client that communicates with a JSON-RPC server
 // that is listening on the given endpoint using the provided dialer.
 func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, dialer websocket.Dialer) (*Client, error) {
@@ -168,7 +231,64 @@ func wsClientHeaders(endpoint, origin string) (string, http.Header, error) {
 	return endpointURL.String(), header, nil
 }
 
+type websocketCodec struct {
+	*jsonCodec
+	conn *websocket.Conn
+
+	wg        sync.WaitGroup
+	pingReset chan struct{}
+}
+
 func newWebsocketCodec(conn *websocket.Conn) ServerCodec {
-	conn.SetReadLimit(maxRequestContentLength)
-	return NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON)
+	conn.SetReadLimit(wsMessageSizeLimit)
+	wc := &websocketCodec{
+		jsonCodec: NewFuncCodec(conn, conn.WriteJSON, conn.ReadJSON).(*jsonCodec),
+		conn:      conn,
+		pingReset: make(chan struct{}, 1),
+	}
+	wc.wg.Add(1)
+	go wc.pingLoop()
+	return wc
+}
+
+func (wc *websocketCodec) close() {
+	wc.jsonCodec.close()
+	wc.wg.Wait()
+}
+
+func (wc *websocketCodec) writeJSON(ctx context.Context, v interface{}) error {
+	err := wc.jsonCodec.writeJSON(ctx, v)
+	if err == nil {
+		// Notify pingLoop to delay the next idle ping.
+		select {
+		case wc.pingReset <- struct{}{}:
+		default:
+		}
+	}
+	return err
+}
+
+// pingLoop sends periodic ping frames when the connection is idle.
+func (wc *websocketCodec) pingLoop() {
+	var timer = time.NewTimer(wsPingInterval)
+	defer wc.wg.Done()
+	defer timer.Stop()
+
+	for {
+		select {
+		case <-wc.closed():
+			return
+		case <-wc.pingReset:
+			if !timer.Stop() {
+				<-timer.C
+			}
+			timer.Reset(wsPingInterval)
+		case <-timer.C:
+			wc.jsonCodec.encMu.Lock()
+			wc.conn.SetWriteDeadline(time.Now().Add(wsPingWriteTimeout))
+			wc.conn.WriteMessage(websocket.PingMessage, nil)
+			wc.jsonCodec.encMu.Unlock()
+			timer.Reset(wsPingInterval)
+		}
+	}
 }
diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go
index f54fc3cd54..4976853baf 100644
--- a/rpc/websocket_test.go
+++ b/rpc/websocket_test.go
@@ -129,11 +129,15 @@ func TestClientWebsocketPing(t *testing.T) {
 	if err != nil {
 		t.Fatalf("client dial error: %v", err)
 	}
+	defer client.Close()
+
 	resultChan := make(chan int)
 	sub, err := client.EthSubscribe(ctx, resultChan, "foo")
 	if err != nil {
 		t.Fatalf("client subscribe error: %v", err)
 	}
+	// Note: Unsubscribe is not called on this subscription because the mockup
+	// server can't handle the request.
 
 	// Wait for the context's deadline to be reached before proceeding.
 	// This is important for reproducing https://github.com/ethereum/go-ethereum/issues/19798
@@ -157,6 +161,33 @@ func TestClientWebsocketPing(t *testing.T) {
 	}
 }
 
+// This checks that the websocket transport can deal with large messages.
+func TestClientWebsocketLargeMessage(t *testing.T) {
+	var (
+		srv     = NewServer()
+		httpsrv = httptest.NewServer(srv.WebsocketHandler(nil))
+		wsURL   = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
+	)
+	defer srv.Stop()
+	defer httpsrv.Close()
+
+	respLength := wsMessageSizeLimit - 50
+	srv.RegisterName("test", largeRespService{respLength})
+
+	c, err := DialWebsocket(context.Background(), wsURL, "")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var r string
+	if err := c.Call(&r, "test_largeResp"); err != nil {
+		t.Fatal("call failed:", err)
+	}
+	if len(r) != respLength {
+		t.Fatalf("response has wrong length %d, want %d", len(r), respLength)
+	}
+}
+
 // wsPingTestServer runs a WebSocket server which accepts a single subscription request.
 // When a value arrives on sendPing, the server sends a ping frame, waits for a matching
 // pong and finally delivers a single subscription result.
diff --git a/signer/core/api.go b/signer/core/api.go
index 7e6ece997f..3811162f8f 100644
--- a/signer/core/api.go
+++ b/signer/core/api.go
@@ -33,7 +33,6 @@ import (
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/internal/ethapi"
 	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/rlp"
 	"github.com/ethereum/go-ethereum/signer/storage"
 )
 
@@ -41,7 +40,7 @@ const (
 	// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
 	numberOfAccountsToDerive = 10
 	// ExternalAPIVersion -- see extapi_changelog.md
-	ExternalAPIVersion = "6.0.0"
+	ExternalAPIVersion = "6.1.0"
 	// InternalAPIVersion -- see intapi_changelog.md
 	InternalAPIVersion = "7.0.1"
 )
@@ -62,6 +61,8 @@ type ExternalAPI interface {
 	EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
 	// Version info about the APIs
 	Version(ctx context.Context) (string, error)
+	// SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction
+	SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error)
 }
 
 // UIClientAPI specifies what method a UI needs to implement to be able to be used as a
@@ -234,6 +235,7 @@ type (
 		Address     common.MixedcaseAddress `json:"address"`
 		Rawdata     []byte                  `json:"raw_data"`
 		Messages    []*NameValueType        `json:"messages"`
+		Callinfo    []ValidationInfo        `json:"call_info"`
 		Hash        hexutil.Bytes           `json:"hash"`
 		Meta        Metadata                `json:"meta"`
 	}
@@ -319,59 +321,73 @@ func (api *SignerAPI) openTrezor(url accounts.URL) {
 
 // startUSBListener starts a listener for USB events, for hardware wallet interaction
 func (api *SignerAPI) startUSBListener() {
-	events := make(chan accounts.WalletEvent, 16)
+	eventCh := make(chan accounts.WalletEvent, 16)
 	am := api.am
-	am.Subscribe(events)
-	go func() {
+	am.Subscribe(eventCh)
+	// Open any wallets already attached
+	for _, wallet := range am.Wallets() {
+		if err := wallet.Open(""); err != nil {
+			log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
+			if err == usbwallet.ErrTrezorPINNeeded {
+				go api.openTrezor(wallet.URL())
+			}
+		}
+	}
+	go api.derivationLoop(eventCh)
+}
 
-		// Open any wallets already attached
-		for _, wallet := range am.Wallets() {
-			if err := wallet.Open(""); err != nil {
-				log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
+// derivationLoop listens for wallet events
+func (api *SignerAPI) derivationLoop(events chan accounts.WalletEvent) {
+	// Listen for wallet event till termination
+	for event := range events {
+		switch event.Kind {
+		case accounts.WalletArrived:
+			if err := event.Wallet.Open(""); err != nil {
+				log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
 				if err == usbwallet.ErrTrezorPINNeeded {
-					go api.openTrezor(wallet.URL())
+					go api.openTrezor(event.Wallet.URL())
 				}
 			}
-		}
-		// Listen for wallet event till termination
-		for event := range events {
-			switch event.Kind {
-			case accounts.WalletArrived:
-				if err := event.Wallet.Open(""); err != nil {
-					log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
-					if err == usbwallet.ErrTrezorPINNeeded {
-						go api.openTrezor(event.Wallet.URL())
-					}
-				}
-			case accounts.WalletOpened:
-				status, _ := event.Wallet.Status()
-				log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
-
+		case accounts.WalletOpened:
+			status, _ := event.Wallet.Status()
+			log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
+			var derive = func(limit int, next func() accounts.DerivationPath) {
 				// Derive first N accounts, hardcoded for now
-				var nextPath = make(accounts.DerivationPath, len(accounts.DefaultBaseDerivationPath))
-				copy(nextPath[:], accounts.DefaultBaseDerivationPath[:])
-
-				for i := 0; i < numberOfAccountsToDerive; i++ {
-					acc, err := event.Wallet.Derive(nextPath, true)
-					if err != nil {
-						log.Warn("account derivation failed", "error", err)
+				for i := 0; i < limit; i++ {
+					path := next()
+					if acc, err := event.Wallet.Derive(path, true); err != nil {
+						log.Warn("Account derivation failed", "error", err)
 					} else {
-						log.Info("derived account", "address", acc.Address)
+						log.Info("Derived account", "address", acc.Address, "path", path)
 					}
-					nextPath[len(nextPath)-1]++
 				}
-			case accounts.WalletDropped:
-				log.Info("Old wallet dropped", "url", event.Wallet.URL())
-				event.Wallet.Close()
 			}
+			log.Info("Deriving default paths")
+			derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.DefaultBaseDerivationPath))
+			if event.Wallet.URL().Scheme == "ledger" {
+				log.Info("Deriving ledger legacy paths")
+				derive(numberOfAccountsToDerive, accounts.DefaultIterator(accounts.LegacyLedgerBaseDerivationPath))
+				log.Info("Deriving ledger live paths")
+				// For ledger live, since it's based off the same (DefaultBaseDerivationPath)
+				// as one we've already used, we need to step it forward one step to avoid
+				// hitting the same path again
+				nextFn := accounts.LedgerLiveIterator(accounts.DefaultBaseDerivationPath)
+				nextFn()
+				derive(numberOfAccountsToDerive, nextFn)
+			}
+		case accounts.WalletDropped:
+			log.Info("Old wallet dropped", "url", event.Wallet.URL())
+			event.Wallet.Close()
 		}
-	}()
+	}
 }
 
 // List returns the set of wallet this signer manages. Each wallet can contain
 // multiple accounts.
 func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) {
-	var accs []accounts.Account
+	var accs = make([]accounts.Account, 0)
+	// accs is initialized as empty list, not nil. We use 'nil' to signal
+	// rejection, as opposed to an empty list.
 	for _, wallet := range api.am.Wallets() {
 		accs = append(accs, wallet.Accounts()...)
 	}
@@ -381,13 +397,11 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) {
 	}
 	if result.Accounts == nil {
 		return nil, ErrRequestDenied
-
 	}
 	addresses := make([]common.Address, 0)
 	for _, acc := range result.Accounts {
 		addresses = append(addresses, acc.Address)
 	}
-
 	return addresses, nil
 }
 
@@ -424,7 +438,7 @@ func (api *SignerAPI) newAccount() (common.Address, error) {
 			continue
 		}
 		if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil {
-			api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
+			api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", i+1, pwErr))
 		} else {
 			// No error
 			acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text)
@@ -520,6 +534,14 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
 			return nil, err
 		}
 	}
+	if args.ChainID != nil {
+		requestedChainId := (*big.Int)(args.ChainID)
+		if api.chainID.Cmp(requestedChainId) != 0 {
+			log.Error("Signing request with wrong chain id", "requested", requestedChainId, "configured", api.chainID)
+			return nil, fmt.Errorf("requested chainid %d does not match the configuration of the signer",
+				requestedChainId)
+		}
+	}
 	req := SignTxRequest{
 		Transaction: args,
 		Meta:        MetadataFromContext(ctx),
@@ -559,11 +581,11 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
 		return nil, err
 	}
 
-	rlpdata, err := rlp.EncodeToBytes(signedTx)
+	data, err := signedTx.MarshalBinary()
 	if err != nil {
 		return nil, err
 	}
-	response := ethapi.SignTransactionResult{Raw: rlpdata, Tx: signedTx}
+	response := ethapi.SignTransactionResult{Raw: data, Tx: signedTx}
 
 	// Finally, send the signed tx to the UI
 	api.UI.OnApprovedTx(response)
@@ -572,6 +594,33 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
 
 }
 
+func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) {
+	// Do the usual validations, but on the last-stage transaction
+	args := gnosisTx.ArgsForValidation()
+	msgs, err := api.validator.ValidateTransaction(methodSelector, args)
+	if err != nil {
+		return nil, err
+	}
+	// If we are in 'rejectMode', then reject rather than show the user warnings
+	if api.rejectMode {
+		if err := msgs.getWarnings(); err != nil {
+			return nil, err
+		}
+	}
+	typedData := gnosisTx.ToTypedData()
+	signature, preimage, err := api.signTypedData(ctx, signerAddress, typedData, msgs)
+	if err != nil {
+		return nil, err
+	}
+	checkSummedSender, _ := common.NewMixedcaseAddressFromString(signerAddress.Address().Hex())
+
+	gnosisTx.Signature = signature
+	gnosisTx.SafeTxHash = common.BytesToHash(preimage)
+	gnosisTx.Sender = *checkSummedSender // Must be checksumed to be accepted by relay
+
+	return &gnosisTx, nil
+}
+
 // Returns the external api version. This method does not require user acceptance. Available methods are
 // available via enumeration anyway, and this info does not contain user-specific data
 func (api *SignerAPI) Version(ctx context.Context) (string, error) {
diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go
index 1092e7a923..bda88a8b2e 100644
--- a/signer/core/auditlog.go
+++ b/signer/core/auditlog.go
@@ -18,6 +18,7 @@ package core
 
 import (
 	"context"
+	"encoding/json"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
@@ -61,13 +62,32 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth
 }
 
 func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) {
+	marshalledData, _ := json.Marshal(data) // can ignore error, marshalling what we just unmarshalled
 	l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
-		"addr", addr.String(), "data", data, "content-type", contentType)
+		"addr", addr.String(), "data", marshalledData, "content-type", contentType)
 	b, e := l.api.SignData(ctx, contentType, addr, data)
 	l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
 	return b, e
 }
 
+func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) {
+	sel := ""
+	if methodSelector != nil {
+		sel = *methodSelector
+	}
+	data, _ := json.Marshal(gnosisTx) // can ignore error, marshalling what we just unmarshalled
+	l.log.Info("SignGnosisSafeTx", "type", "request", "metadata", MetadataFromContext(ctx).String(),
+		"addr", addr.String(), "data", string(data), "selector", sel)
+	res, e := l.api.SignGnosisSafeTx(ctx, addr, gnosisTx, methodSelector)
+	if res != nil {
+		data, _ := json.Marshal(res) // can ignore error, marshalling what we just unmarshalled
+		l.log.Info("SignGnosisSafeTx", "type", "response", "data", string(data), "error", e)
+	} else {
+		l.log.Info("SignGnosisSafeTx", "type", "response", "data", res, "error", e)
+	}
+	return res, e
+}
+
 func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) {
 	l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
 		"addr", addr.String(), "data", data)
diff --git a/signer/core/cliui.go b/signer/core/cliui.go
index 1e033299af..e0375483c3 100644
--- a/signer/core/cliui.go
+++ b/signer/core/cliui.go
@@ -25,9 +25,9 @@ import (
 	"sync"
 
 	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/console/prompt"
 	"github.com/ethereum/go-ethereum/internal/ethapi"
 	"github.com/ethereum/go-ethereum/log"
-	"golang.org/x/crypto/ssh/terminal"
 )
 
 type CommandlineUI struct {
@@ -61,17 +61,16 @@ func (ui *CommandlineUI) readString() string {
 func (ui *CommandlineUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
 
 	fmt.Printf("## %s\n\n%s\n", info.Title, info.Prompt)
+	defer fmt.Println("-----------------------")
 	if info.IsPassword {
-		fmt.Printf("> ")
-		text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
+		text, err := prompt.Stdin.PromptPassword("> ")
 		if err != nil {
-			log.Error("Failed to read password", "err", err)
+			log.Error("Failed to read password", "error", err)
+			return UserInputResponse{}, err
 		}
-		fmt.Println("-----------------------")
-		return UserInputResponse{string(text)}, err
+		return UserInputResponse{text}, nil
 	}
 	text := ui.readString()
-	fmt.Println("-----------------------")
 	return UserInputResponse{text}, nil
 }
 
@@ -85,10 +84,19 @@ func (ui *CommandlineUI) confirm() bool {
 	return false
 }
 
+// sanitize quotes and truncates 'txt' if longer than 'limit'. If truncated,
+// and ellipsis is added after the quoted string
+func sanitize(txt string, limit int) string {
+	if len(txt) > limit {
+		return fmt.Sprintf("%q...", txt[:limit])
+	}
+	return fmt.Sprintf("%q", txt)
+}
+
 func showMetadata(metadata Metadata) {
 	fmt.Printf("Request context:\n\t%v -> %v -> %v\n", metadata.Remote, metadata.Scheme, metadata.Local)
 	fmt.Printf("\nAdditional HTTP header data, provided by the external caller:\n")
-	fmt.Printf("\tUser-Agent: %v\n\tOrigin: %v\n", metadata.UserAgent, metadata.Origin)
+	fmt.Printf("\tUser-Agent: %v\n\tOrigin: %v\n", sanitize(metadata.UserAgent, 200), sanitize(metadata.Origin, 100))
 }
 
 // ApproveTx prompt the user for confirmation to request to sign Transaction
@@ -110,10 +118,21 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
 	fmt.Printf("gas:      %v (%v)\n", request.Transaction.Gas, uint64(request.Transaction.Gas))
 	fmt.Printf("gasprice: %v wei\n", request.Transaction.GasPrice.ToInt())
 	fmt.Printf("nonce:    %v (%v)\n", request.Transaction.Nonce, uint64(request.Transaction.Nonce))
+	if chainId := request.Transaction.ChainID; chainId != nil {
+		fmt.Printf("chainid:  %v\n", chainId)
+	}
+	if list := request.Transaction.AccessList; list != nil {
+		fmt.Printf("Accesslist\n")
+		for i, el := range *list {
+			fmt.Printf(" %d. %v\n", i, el.Address)
+			for j, slot := range el.StorageKeys {
+				fmt.Printf("   %d. %v\n", j, slot)
+			}
+		}
+	}
 	if request.Transaction.Data != nil {
 		d := *request.Transaction.Data
 		if len(d) > 0 {
-
 			fmt.Printf("data:     %v\n", hexutil.Encode(d))
 		}
 	}
@@ -141,11 +160,18 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
 
 	fmt.Printf("-------- Sign data request--------------\n")
 	fmt.Printf("Account:  %s\n", request.Address.String())
+	if len(request.Callinfo) != 0 {
+		fmt.Printf("\nValidation messages:\n")
+		for _, m := range request.Callinfo {
+			fmt.Printf("  * %s : %s\n", m.Typ, m.Message)
+		}
+		fmt.Println()
+	}
 	fmt.Printf("messages:\n")
 	for _, nvt := range request.Messages {
 		fmt.Printf("\u00a0\u00a0%v\n", strings.TrimSpace(nvt.Pprint(1)))
 	}
-	fmt.Printf("raw data:  \n%q\n", request.Rawdata)
+	fmt.Printf("raw data:  \n\t%q\n", request.Rawdata)
 	fmt.Printf("data hash:  %v\n", request.Hash)
 	fmt.Printf("-------------------------------------------\n")
 	showMetadata(request.Meta)
diff --git a/signer/core/gnosis_safe.go b/signer/core/gnosis_safe.go
new file mode 100644
index 0000000000..e4385f9dc3
--- /dev/null
+++ b/signer/core/gnosis_safe.go
@@ -0,0 +1,91 @@
+package core
+
+import (
+	"fmt"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/ethereum/go-ethereum/common/math"
+)
+
+// GnosisSafeTx is a type to parse the safe-tx returned by the relayer,
+// it also conforms to the API required by the Gnosis Safe tx relay service.
+// See 'SafeMultisigTransaction' on https://safe-transaction.mainnet.gnosis.io/
+type GnosisSafeTx struct {
+	// These fields are only used on output
+	Signature  hexutil.Bytes           `json:"signature"`
+	SafeTxHash common.Hash             `json:"contractTransactionHash"`
+	Sender     common.MixedcaseAddress `json:"sender"`
+	// These fields are used both on input and output
+	Safe           common.MixedcaseAddress `json:"safe"`
+	To             common.MixedcaseAddress `json:"to"`
+	Value          math.Decimal256         `json:"value"`
+	GasPrice       math.Decimal256         `json:"gasPrice"`
+	Data           *hexutil.Bytes          `json:"data"`
+	Operation      uint8                   `json:"operation"`
+	GasToken       common.Address          `json:"gasToken"`
+	RefundReceiver common.Address          `json:"refundReceiver"`
+	BaseGas        big.Int                 `json:"baseGas"`
+	SafeTxGas      big.Int                 `json:"safeTxGas"`
+	Nonce          big.Int                 `json:"nonce"`
+	InputExpHash   common.Hash             `json:"safeTxHash"`
+}
+
+// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing
+func (tx *GnosisSafeTx) ToTypedData() TypedData {
+	var data hexutil.Bytes
+	if tx.Data != nil {
+		data = *tx.Data
+	}
+	gnosisTypedData := TypedData{
+		Types: Types{
+			"EIP712Domain": []Type{{Name: "verifyingContract", Type: "address"}},
+			"SafeTx": []Type{
+				{Name: "to", Type: "address"},
+				{Name: "value", Type: "uint256"},
+				{Name: "data", Type: "bytes"},
+				{Name: "operation", Type: "uint8"},
+				{Name: "safeTxGas", Type: "uint256"},
+				{Name: "baseGas", Type: "uint256"},
+				{Name: "gasPrice", Type: "uint256"},
+				{Name: "gasToken", Type: "address"},
+				{Name: "refundReceiver", Type: "address"},
+				{Name: "nonce", Type: "uint256"},
+			},
+		},
+		Domain: TypedDataDomain{
+			VerifyingContract: tx.Safe.Address().Hex(),
+		},
+		PrimaryType: "SafeTx",
+		Message: TypedDataMessage{
+			"to":             tx.To.Address().Hex(),
+			"value":          tx.Value.String(),
+			"data":           data,
+			"operation":      fmt.Sprintf("%d", tx.Operation),
+			"safeTxGas":      fmt.Sprintf("%#d", &tx.SafeTxGas),
+			"baseGas":        fmt.Sprintf("%#d", &tx.BaseGas),
+			"gasPrice":       tx.GasPrice.String(),
+			"gasToken":       tx.GasToken.Hex(),
+			"refundReceiver": tx.RefundReceiver.Hex(),
+			"nonce":          fmt.Sprintf("%d", tx.Nonce.Uint64()),
+		},
+	}
+	return gnosisTypedData
+}
+
+// ArgsForValidation returns a SendTxArgs struct, which can be used for the
+// common validations, e.g. look up 4byte destinations
+func (tx *GnosisSafeTx) ArgsForValidation() *SendTxArgs {
+	args := &SendTxArgs{
+		From:     tx.Safe,
+		To:       &tx.To,
+		Gas:      hexutil.Uint64(tx.SafeTxGas.Uint64()),
+		GasPrice: hexutil.Big(tx.GasPrice),
+		Value:    hexutil.Big(tx.Value),
+		Nonce:    hexutil.Uint64(tx.Nonce.Uint64()),
+		Data:     tx.Data,
+		Input:    nil,
+	}
+	return args
+}
diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go
index 2031716b26..8fb39a43b8 100644
--- a/signer/core/signed_data.go
+++ b/signer/core/signed_data.go
@@ -31,7 +31,6 @@ import (
 	"unicode"
 
 	"github.com/ethereum/go-ethereum/accounts"
-	"github.com/ethereum/go-ethereum/accounts/abi"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
@@ -104,7 +103,7 @@ func (t *Type) isReferenceType() bool {
 	if len(t.Type) == 0 {
 		return false
 	}
-	// Reference types must have a leading uppercase characer
+	// Reference types must have a leading uppercase character
 	return unicode.IsUpper([]rune(t.Type)[0])
 }
 
@@ -131,7 +130,7 @@ var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`)
 //
 // Note, the produced signature conforms to the secp256k1 curve R, S and V values,
 // where the V value will be 27 or 28 for legacy reasons, if legacyV==true.
-func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) {
+func (api *SignerAPI) sign(req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) {
 	// We make the request prior to looking up if we actually have the account, to prevent
 	// account-enumeration via the API
 	res, err := api.UI.ApproveSignData(req)
@@ -142,7 +141,7 @@ func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, l
 		return nil, ErrRequestDenied
 	}
 	// Look up the wallet containing the requested signer
-	account := accounts.Account{Address: addr.Address()}
+	account := accounts.Account{Address: req.Address.Address()}
 	wallet, err := api.am.Find(account)
 	if err != nil {
 		return nil, err
@@ -173,7 +172,7 @@ func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr com
 	if err != nil {
 		return nil, err
 	}
-	signature, err := api.sign(addr, req, transformV)
+	signature, err := api.sign(req, transformV)
 	if err != nil {
 		api.UI.ShowError(err.Error())
 		return nil, err
@@ -363,28 +362,47 @@ func parliaHeaderHashAndRlp(header *types.Header, chainId *big.Int) (hash, rlp [
 
 // SignTypedData signs EIP-712 conformant typed data
 // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
+// It returns
+// - the signature,
+// - and/or any error
 func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) {
+	signature, _, err := api.signTypedData(ctx, addr, typedData, nil)
+	return signature, err
+}
+
+// signTypedData is identical to the capitalized version, except that it also returns the hash (preimage)
+// - the signature preimage (hash)
+func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAddress,
+	typedData TypedData, validationMessages *ValidationMessages) (hexutil.Bytes, hexutil.Bytes, error) {
 	domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 	rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
 	sighash := crypto.Keccak256(rawData)
 	messages, err := typedData.Format()
 	if err != nil {
-		return nil, err
-	}
-	req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Messages: messages, Hash: sighash}
-	signature, err := api.sign(addr, req, true)
+		return nil, nil, err
+	}
+	req := &SignDataRequest{
+		ContentType: DataTyped.Mime,
+		Rawdata:     rawData,
+		Messages:    messages,
+		Hash:        sighash,
+		Address:     addr}
+	if validationMessages != nil {
+		req.Callinfo = validationMessages.Messages
+	}
+	signature, err := api.sign(req, true)
 	if err != nil {
 		api.UI.ShowError(err.Error())
-		return nil, err
+		return nil, nil, err
 	}
-	return signature, nil
+	return signature, sighash, nil
 }
 
 // HashStruct generates a keccak256 hash of the encoding of the provided data
@@ -471,8 +489,8 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter
 	buffer := bytes.Buffer{}
 
 	// Verify extra data
-	if len(typedData.Types[primaryType]) < len(data) {
-		return nil, errors.New("there is extra data provided in the message")
+	if exp, got := len(typedData.Types[primaryType]), len(data); exp < got {
+		return nil, fmt.Errorf("there is extra data provided in the message (%d < %d)", exp, got)
 	}
 
 	// Add typehash
@@ -532,6 +550,24 @@ func (typedData *TypedData) EncodeData(primaryType string, data map[string]inter
 	return buffer.Bytes(), nil
 }
 
+// Attempt to parse bytes in different formats: byte array, hex string, hexutil.Bytes.
+func parseBytes(encType interface{}) ([]byte, bool) {
+	switch v := encType.(type) {
+	case []byte:
+		return v, true
+	case hexutil.Bytes:
+		return v, true
+	case string:
+		bytes, err := hexutil.Decode(v)
+		if err != nil {
+			return nil, false
+		}
+		return bytes, true
+	default:
+		return nil, false
+	}
+}
+
 func parseInteger(encType string, encValue interface{}) (*big.Int, error) {
 	var (
 		length int
@@ -611,7 +647,7 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf
 		}
 		return crypto.Keccak256([]byte(strVal)), nil
 	case "bytes":
-		bytesValue, ok := encValue.([]byte)
+		bytesValue, ok := parseBytes(encValue)
 		if !ok {
 			return nil, dataMismatchError(encType, encValue)
 		}
@@ -626,10 +662,13 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf
 		if length < 0 || length > 32 {
 			return nil, fmt.Errorf("invalid size on bytes: %d", length)
 		}
-		if byteValue, ok := encValue.(hexutil.Bytes); !ok {
+		if byteValue, ok := parseBytes(encValue); !ok || len(byteValue) != length {
 			return nil, dataMismatchError(encType, encValue)
 		} else {
-			return math.PaddedBigBytes(new(big.Int).SetBytes(byteValue), 32), nil
+			// Right-pad the bits
+			dst := make([]byte, 32)
+			copy(dst, byteValue)
+			return dst, nil
 		}
 	}
 	if strings.HasPrefix(encType, "int") || strings.HasPrefix(encType, "uint") {
@@ -637,7 +676,7 @@ func (typedData *TypedData) EncodePrimitiveValue(encType string, encValue interf
 		if err != nil {
 			return nil, err
 		}
-		return abi.U256(b), nil
+		return math.U256Bytes(b), nil
 	}
 	return nil, fmt.Errorf("unrecognized type '%s'", encType)
 
@@ -864,7 +903,11 @@ func (nvt *NameValueType) Pprint(depth int) string {
 			output.WriteString(sublevel)
 		}
 	} else {
-		output.WriteString(fmt.Sprintf("%q\n", nvt.Value))
+		if nvt.Value != nil {
+			output.WriteString(fmt.Sprintf("%q\n", nvt.Value))
+		} else {
+			output.WriteString("\n")
+		}
 	}
 	return output.String()
 }
@@ -877,23 +920,23 @@ func (t Types) validate() error {
 		}
 		for i, typeObj := range typeArr {
 			if len(typeObj.Type) == 0 {
-				return fmt.Errorf("type %v:%d: empty Type", typeKey, i)
+				return fmt.Errorf("type %q:%d: empty Type", typeKey, i)
 			}
 			if len(typeObj.Name) == 0 {
-				return fmt.Errorf("type %v:%d: empty Name", typeKey, i)
+				return fmt.Errorf("type %q:%d: empty Name", typeKey, i)
 			}
 			if typeKey == typeObj.Type {
-				return fmt.Errorf("type '%s' cannot reference itself", typeObj.Type)
+				return fmt.Errorf("type %q cannot reference itself", typeObj.Type)
 			}
 			if typeObj.isReferenceType() {
 				if _, exist := t[typeObj.typeName()]; !exist {
-					return fmt.Errorf("reference type '%s' is undefined", typeObj.Type)
+					return fmt.Errorf("reference type %q is undefined", typeObj.Type)
 				}
 				if !typedDataReferenceTypeRegexp.MatchString(typeObj.Type) {
-					return fmt.Errorf("unknown reference type '%s", typeObj.Type)
+					return fmt.Errorf("unknown reference type %q", typeObj.Type)
 				}
 			} else if !isPrimitiveTypeValid(typeObj.Type) {
-				return fmt.Errorf("unknown type '%s'", typeObj.Type)
+				return fmt.Errorf("unknown type %q", typeObj.Type)
 			}
 		}
 	}
@@ -1016,11 +1059,7 @@ func isPrimitiveTypeValid(primitiveType string) bool {
 // validate checks if the given domain is valid, i.e. contains at least
 // the minimum viable keys and values
 func (domain *TypedDataDomain) validate() error {
-	if domain.ChainId == nil {
-		return errors.New("chainId must be specified according to EIP-155")
-	}
-
-	if len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 {
+	if domain.ChainId == nil && len(domain.Name) == 0 && len(domain.Version) == 0 && len(domain.VerifyingContract) == 0 && len(domain.Salt) == 0 {
 		return errors.New("domain is undefined")
 	}
 
diff --git a/signer/core/signed_data_internal_test.go b/signer/core/signed_data_internal_test.go
index 0d59fcfca8..9768ee0b3f 100644
--- a/signer/core/signed_data_internal_test.go
+++ b/signer/core/signed_data_internal_test.go
@@ -17,10 +17,104 @@
 package core
 
 import (
+	"bytes"
 	"math/big"
 	"testing"
+
+	"github.com/ethereum/go-ethereum/common/hexutil"
 )
 
+func TestBytesPadding(t *testing.T) {
+	tests := []struct {
+		Type   string
+		Input  []byte
+		Output []byte // nil => error
+	}{
+		{
+			// Fail on wrong length
+			Type:   "bytes20",
+			Input:  []byte{},
+			Output: nil,
+		},
+		{
+			Type:   "bytes1",
+			Input:  []byte{1},
+			Output: []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+		},
+		{
+			Type:   "bytes1",
+			Input:  []byte{1, 2},
+			Output: nil,
+		},
+		{
+			Type:   "bytes7",
+			Input:  []byte{1, 2, 3, 4, 5, 6, 7},
+			Output: []byte{1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+		},
+		{
+			Type:   "bytes32",
+			Input:  []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32},
+			Output: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32},
+		},
+		{
+			Type:   "bytes32",
+			Input:  []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33},
+			Output: nil,
+		},
+	}
+
+	d := TypedData{}
+	for i, test := range tests {
+		val, err := d.EncodePrimitiveValue(test.Type, test.Input, 1)
+		if test.Output == nil {
+			if err == nil {
+				t.Errorf("test %d: expected error, got no error (result %x)", i, val)
+			}
+		} else {
+			if err != nil {
+				t.Errorf("test %d: expected no error, got %v", i, err)
+			}
+			if len(val) != 32 {
+				t.Errorf("test %d: expected len 32, got %d", i, len(val))
+			}
+			if !bytes.Equal(val, test.Output) {
+				t.Errorf("test %d: expected %x, got %x", i, test.Output, val)
+			}
+		}
+	}
+}
+
+func TestParseBytes(t *testing.T) {
+	for i, tt := range []struct {
+		v   interface{}
+		exp []byte
+	}{
+		{"0x", []byte{}},
+		{"0x1234", []byte{0x12, 0x34}},
+		{[]byte{12, 34}, []byte{12, 34}},
+		{hexutil.Bytes([]byte{12, 34}), []byte{12, 34}},
+		{"1234", nil},    // not a proper hex-string
+		{"0x01233", nil}, // nibbles should be rejected
+		{"not a hex string", nil},
+		{15, nil},
+		{nil, nil},
+	} {
+		out, ok := parseBytes(tt.v)
+		if tt.exp == nil {
+			if ok || out != nil {
+				t.Errorf("test %d: expected !ok, got ok = %v with out = %x", i, ok, out)
+			}
+			continue
+		}
+		if !ok {
+			t.Errorf("test %d: expected ok got !ok", i)
+		}
+		if !bytes.Equal(out, tt.exp) {
+			t.Errorf("test %d: expected %x got %x", i, tt.exp, out)
+		}
+	}
+}
+
 func TestParseInteger(t *testing.T) {
 	for i, tt := range []struct {
 		t   string
diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go
index e5f4780890..23b7b9897b 100644
--- a/signer/core/signed_data_test.go
+++ b/signer/core/signed_data_test.go
@@ -17,6 +17,7 @@
 package core_test
 
 import (
+	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
@@ -245,6 +246,10 @@ func TestDomainChainId(t *testing.T) {
 	if _, ok := withoutChainID.Domain.Map()["chainId"]; ok {
 		t.Errorf("Expected the chainId key to not be present in the domain map")
 	}
+	// should encode successfully
+	if _, err := withoutChainID.HashStruct("EIP712Domain", withoutChainID.Domain.Map()); err != nil {
+		t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
+	}
 	withChainID := core.TypedData{
 		Types: core.Types{
 			"EIP712Domain": []core.Type{
@@ -261,6 +266,10 @@ func TestDomainChainId(t *testing.T) {
 	if _, ok := withChainID.Domain.Map()["chainId"]; !ok {
 		t.Errorf("Expected the chainId key be present in the domain map")
 	}
+	// should encode successfully
+	if _, err := withChainID.HashStruct("EIP712Domain", withChainID.Domain.Map()); err != nil {
+		t.Errorf("Expected the typedData to encode the domain successfully, got %v", err)
+	}
 }
 
 func TestHashStruct(t *testing.T) {
@@ -406,3 +415,119 @@ func TestFuzzerFiles(t *testing.T) {
 		typedData.Format()
 	}
 }
+
+var gnosisTypedData = `
+{
+	"types": {
+		"EIP712Domain": [
+			{ "type": "address", "name": "verifyingContract" }
+		],
+		"SafeTx": [
+			{ "type": "address", "name": "to" },
+			{ "type": "uint256", "name": "value" },
+			{ "type": "bytes", "name": "data" },
+			{ "type": "uint8", "name": "operation" },
+			{ "type": "uint256", "name": "safeTxGas" },
+			{ "type": "uint256", "name": "baseGas" },
+			{ "type": "uint256", "name": "gasPrice" },
+			{ "type": "address", "name": "gasToken" },
+			{ "type": "address", "name": "refundReceiver" },
+			{ "type": "uint256", "name": "nonce" }
+		]
+	},
+	"domain": {
+		"verifyingContract": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3"
+	},
+	"primaryType": "SafeTx",
+	"message": {
+		"to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D",
+		"value": "20000000000000000",
+		"data": "0x",
+		"operation": 0,
+		"safeTxGas": 27845,
+		"baseGas": 0,
+		"gasPrice": "0",
+		"gasToken": "0x0000000000000000000000000000000000000000",
+		"refundReceiver": "0x0000000000000000000000000000000000000000",
+		"nonce": 3
+	}
+}`
+
+var gnosisTx = `
+{
+      "safe": "0x25a6c4BBd32B2424A9c99aEB0584Ad12045382B3",
+      "to": "0x9eE457023bB3De16D51A003a247BaEaD7fce313D",
+      "value": "20000000000000000",
+      "data": null,
+      "operation": 0,
+      "gasToken": "0x0000000000000000000000000000000000000000",
+      "safeTxGas": 27845,
+      "baseGas": 0,
+      "gasPrice": "0",
+      "refundReceiver": "0x0000000000000000000000000000000000000000",
+      "nonce": 3,
+      "executionDate": null,
+      "submissionDate": "2020-09-15T21:59:23.815748Z",
+      "modified": "2020-09-15T21:59:23.815748Z",
+      "blockNumber": null,
+      "transactionHash": null,
+      "safeTxHash": "0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f",
+      "executor": null,
+      "isExecuted": false,
+      "isSuccessful": null,
+      "ethGasPrice": null,
+      "gasUsed": null,
+      "fee": null,
+      "origin": null,
+      "dataDecoded": null,
+      "confirmationsRequired": null,
+      "confirmations": [
+        {
+          "owner": "0xAd2e180019FCa9e55CADe76E4487F126Fd08DA34",
+          "submissionDate": "2020-09-15T21:59:28.281243Z",
+          "transactionHash": null,
+          "confirmationType": "CONFIRMATION",
+          "signature": "0x5e562065a0cb15d766dac0cd49eb6d196a41183af302c4ecad45f1a81958d7797753f04424a9b0aa1cb0448e4ec8e189540fbcdda7530ef9b9d95dfc2d36cb521b",
+          "signatureType": "EOA"
+        }
+      ],
+      "signatures": null
+    }
+`
+
+// TestGnosisTypedData tests the scenario where a user submits a full EIP-712
+// struct without using the gnosis-specific endpoint
+func TestGnosisTypedData(t *testing.T) {
+	var td core.TypedData
+	err := json.Unmarshal([]byte(gnosisTypedData), &td)
+	if err != nil {
+		t.Fatalf("unmarshalling failed '%v'", err)
+	}
+	_, sighash, err := sign(td)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f")
+	if !bytes.Equal(expSigHash, sighash) {
+		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
+	}
+}
+
+// TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe
+// specific data, and we fill the TypedData struct on our side
+func TestGnosisCustomData(t *testing.T) {
+	var tx core.GnosisSafeTx
+	err := json.Unmarshal([]byte(gnosisTx), &tx)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var td = tx.ToTypedData()
+	_, sighash, err := sign(td)
+	if err != nil {
+		t.Fatal(err)
+	}
+	expSigHash := common.FromHex("0x28bae2bd58d894a1d9b69e5e9fde3570c4b98a6fc5499aefb54fb830137e831f")
+	if !bytes.Equal(expSigHash, sighash) {
+		t.Fatalf("Error, got %x, wanted %x", sighash, expSigHash)
+	}
+}
diff --git a/signer/core/types.go b/signer/core/types.go
index 58b377c8d8..e952a21209 100644
--- a/signer/core/types.go
+++ b/signer/core/types.go
@@ -76,6 +76,10 @@ type SendTxArgs struct {
 	// We accept "data" and "input" for backwards-compatibility reasons.
 	Data  *hexutil.Bytes `json:"data"`
 	Input *hexutil.Bytes `json:"input,omitempty"`
+
+	// For non-legacy transactions
+	AccessList *types.AccessList `json:"accessList,omitempty"`
+	ChainID    *hexutil.Big      `json:"chainId,omitempty"`
 }
 
 func (args SendTxArgs) String() string {
@@ -93,8 +97,32 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
 	} else if args.Input != nil {
 		input = *args.Input
 	}
-	if args.To == nil {
-		return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input)
+	var to *common.Address
+	if args.To != nil {
+		_to := args.To.Address()
+		to = &_to
+	}
+	var data types.TxData
+	if args.AccessList == nil {
+		data = &types.LegacyTx{
+			To:       to,
+			Nonce:    uint64(args.Nonce),
+			Gas:      uint64(args.Gas),
+			GasPrice: (*big.Int)(&args.GasPrice),
+			Value:    (*big.Int)(&args.Value),
+			Data:     input,
+		}
+	} else {
+		data = &types.AccessListTx{
+			To:         to,
+			ChainID:    (*big.Int)(args.ChainID),
+			Nonce:      uint64(args.Nonce),
+			Gas:        uint64(args.Gas),
+			GasPrice:   (*big.Int)(&args.GasPrice),
+			Value:      (*big.Int)(&args.Value),
+			Data:       input,
+			AccessList: *args.AccessList,
+		}
 	}
-	return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input)
+	return types.NewTx(data)
 }
diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go
index 25a587de56..3a0327d869 100644
--- a/signer/core/uiapi.go
+++ b/signer/core/uiapi.go
@@ -109,7 +109,7 @@ func (s *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (account
 	return wallet.Derive(derivPath, *pin)
 }
 
-// fetchKeystore retrives the encrypted keystore from the account manager.
+// fetchKeystore retrieves the encrypted keystore from the account manager.
 func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
 	return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
 }
diff --git a/signer/fourbyte/abi.go b/signer/fourbyte/abi.go
index 796086d415..d8fbabd3b1 100644
--- a/signer/fourbyte/abi.go
+++ b/signer/fourbyte/abi.go
@@ -85,7 +85,7 @@ var selectorRegexp = regexp.MustCompile(`^([^\)]+)\(([A-Za-z0-9,\[\]]*)\)`)
 
 // parseSelector converts a method selector into an ABI JSON spec. The returned
 // data is a valid JSON string which can be consumed by the standard abi package.
-func parseSelector(selector string) ([]byte, error) {
+func parseSelector(unescapedSelector string) ([]byte, error) {
 	// Define a tiny fake ABI struct for JSON marshalling
 	type fakeArg struct {
 		Type string `json:"type"`
@@ -95,10 +95,10 @@ func parseSelector(selector string) ([]byte, error) {
 		Type   string    `json:"type"`
 		Inputs []fakeArg `json:"inputs"`
 	}
-	// Validate the selector and extract it's components
-	groups := selectorRegexp.FindStringSubmatch(selector)
+	// Validate the unescapedSelector and extract it's components
+	groups := selectorRegexp.FindStringSubmatch(unescapedSelector)
 	if len(groups) != 3 {
-		return nil, fmt.Errorf("invalid selector %s (%v matches)", selector, len(groups))
+		return nil, fmt.Errorf("invalid selector %q (%v matches)", unescapedSelector, len(groups))
 	}
 	name := groups[1]
 	args := groups[2]
@@ -115,7 +115,7 @@ func parseSelector(selector string) ([]byte, error) {
 
 // parseCallData matches the provided call data against the ABI definition and
 // returns a struct containing the actual go-typed values.
-func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
+func parseCallData(calldata []byte, unescapedAbidata string) (*decodedCallData, error) {
 	// Validate the call data that it has the 4byte prefix and the rest divisible by 32 bytes
 	if len(calldata) < 4 {
 		return nil, fmt.Errorf("invalid call data, incomplete method signature (%d bytes < 4)", len(calldata))
@@ -127,9 +127,9 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
 		return nil, fmt.Errorf("invalid call data; length should be a multiple of 32 bytes (was %d)", len(argdata))
 	}
 	// Validate the called method and upack the call data accordingly
-	abispec, err := abi.JSON(strings.NewReader(abidata))
+	abispec, err := abi.JSON(strings.NewReader(unescapedAbidata))
 	if err != nil {
-		return nil, fmt.Errorf("invalid method signature (%s): %v", abidata, err)
+		return nil, fmt.Errorf("invalid method signature (%q): %v", unescapedAbidata, err)
 	}
 	method, err := abispec.MethodById(sigdata)
 	if err != nil {
@@ -140,7 +140,7 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
 		return nil, fmt.Errorf("signature %q matches, but arguments mismatch: %v", method.String(), err)
 	}
 	// Everything valid, assemble the call infos for the signer
-	decoded := decodedCallData{signature: method.Sig(), name: method.RawName}
+	decoded := decodedCallData{signature: method.Sig, name: method.RawName}
 	for i := 0; i < len(method.Inputs); i++ {
 		decoded.inputs = append(decoded.inputs, decodedArgument{
 			soltype: method.Inputs[i],
@@ -158,7 +158,7 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
 	if !bytes.Equal(encoded, argdata) {
 		was := common.Bytes2Hex(encoded)
 		exp := common.Bytes2Hex(argdata)
-		return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig())
+		return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig)
 	}
 	return &decoded, nil
 }
diff --git a/signer/fourbyte/abi_test.go b/signer/fourbyte/abi_test.go
index 314c12735b..68c027ecea 100644
--- a/signer/fourbyte/abi_test.go
+++ b/signer/fourbyte/abi_test.go
@@ -68,7 +68,7 @@ func TestNewUnpacker(t *testing.T) {
 				[10]byte{49, 50, 51, 52, 53, 54, 55, 56, 57, 48},
 				common.Hex2Bytes("48656c6c6f2c20776f726c6421"),
 			},
-		}, { // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples
+		}, { // https://docs.soliditylang.org/en/develop/abi-spec.html#examples
 			`[{"type":"function","name":"sam","inputs":[{"type":"bytes"},{"type":"bool"},{"type":"uint256[]"}]}]`,
 			//  "dave", true and [1,2,3]
 			"a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
@@ -124,7 +124,7 @@ func TestCalldataDecoding(t *testing.T) {
 		"42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
 		// Too short compareAndApprove
 		"a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042",
-		// From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
+		// From https://docs.soliditylang.org/en/develop/abi-spec.html
 		// contains a bool with illegal values
 		"a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
 	} {
@@ -135,7 +135,7 @@ func TestCalldataDecoding(t *testing.T) {
 	}
 	// Expected success
 	for i, hexdata := range []string{
-		// From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
+		// From https://docs.soliditylang.org/en/develop/abi-spec.html
 		"a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
 		"a52c101e0000000000000000000000000000000000000000000000000000000000000012",
 		"a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
diff --git a/signer/fourbyte/fourbyte_test.go b/signer/fourbyte/fourbyte_test.go
index cdbd7ef73d..cf54c9b9c2 100644
--- a/signer/fourbyte/fourbyte_test.go
+++ b/signer/fourbyte/fourbyte_test.go
@@ -48,8 +48,8 @@ func TestEmbeddedDatabase(t *testing.T) {
 			t.Errorf("Failed to get method by id (%s): %v", id, err)
 			continue
 		}
-		if m.Sig() != selector {
-			t.Errorf("Selector mismatch: have %v, want %v", m.Sig(), selector)
+		if m.Sig != selector {
+			t.Errorf("Selector mismatch: have %v, want %v", m.Sig, selector)
 		}
 	}
 }
diff --git a/signer/fourbyte/validation.go b/signer/fourbyte/validation.go
index fd13e0a630..baec32f72c 100644
--- a/signer/fourbyte/validation.go
+++ b/signer/fourbyte/validation.go
@@ -98,7 +98,7 @@ func (db *Database) ValidateCallData(selector *string, data []byte, messages *co
 		if info, err := verifySelector(*selector, data); err != nil {
 			messages.Warn(fmt.Sprintf("Transaction contains data, but provided ABI signature could not be matched: %v", err))
 		} else {
-			messages.Info(info.String())
+			messages.Info(fmt.Sprintf("Transaction invokes the following method: %q", info.String()))
 			db.AddSelector(*selector, data[:4])
 		}
 		return
@@ -112,6 +112,6 @@ func (db *Database) ValidateCallData(selector *string, data []byte, messages *co
 	if info, err := verifySelector(embedded, data); err != nil {
 		messages.Warn(fmt.Sprintf("Transaction contains data, but provided ABI signature could not be verified: %v", err))
 	} else {
-		messages.Info(info.String())
+		messages.Info(fmt.Sprintf("Transaction invokes the following method: %q", info.String()))
 	}
 }
diff --git a/tests/block_test.go b/tests/block_test.go
index 8fa90e3e39..4820ba733f 100644
--- a/tests/block_test.go
+++ b/tests/block_test.go
@@ -25,7 +25,11 @@ func TestBlockchain(t *testing.T) {
 
 	bt := new(testMatcher)
 	// General state tests are 'exported' as blockchain tests, but we can run them natively.
+	// For speedier CI-runs, the line below can be uncommented, so those are skipped.
+	// For now, in hardfork-times (Berlin), we run the tests both as StateTests and
+	// as blockchain tests, since the latter also covers things like receipt root
 	bt.skipLoad(`^GeneralStateTests/`)
+
 	// Skip random failures due to selfish mining test
 	bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
 
@@ -43,7 +47,6 @@ func TestBlockchain(t *testing.T) {
 	// test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range,
 	// using 4.6 TGas
 	bt.skipLoad(`.*randomStatetest94.json.*`)
-
 	bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) {
 		if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil {
 			t.Errorf("test without snapshotter failed: %v", err)
diff --git a/tests/block_test_util.go b/tests/block_test_util.go
index 37f63f538a..745208b5b8 100644
--- a/tests/block_test_util.go
+++ b/tests/block_test_util.go
@@ -32,7 +32,6 @@ import (
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
-	"github.com/ethereum/go-ethereum/core/state/snapshot"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/params"
@@ -124,7 +123,7 @@ func (t *BlockTest) Run(snapshotter bool) error {
 		cache.SnapshotLimit = 1
 		cache.SnapshotWait = true
 	}
-	chain, err := core.NewBlockChain(db, cache, config, engine, vm.Config{}, nil)
+	chain, err := core.NewBlockChain(db, cache, config, engine, vm.Config{}, nil, nil)
 	if err != nil {
 		return err
 	}
@@ -147,15 +146,8 @@ func (t *BlockTest) Run(snapshotter bool) error {
 	}
 	// Cross-check the snapshot-to-hash against the trie hash
 	if snapshotter {
-		snapTree := chain.Snapshot()
-		root := chain.CurrentBlock().Root()
-		it, err := snapTree.AccountIterator(root, common.Hash{})
-		if err != nil {
-			return fmt.Errorf("Could not create iterator for root %x: %v", root, err)
-		}
-		generatedRoot := snapshot.GenerateTrieRoot(it)
-		if generatedRoot != root {
-			return fmt.Errorf("Snapshot corruption, got %d exp %d", generatedRoot, root)
+		if err := chain.Snapshots().Verify(chain.CurrentBlock().Root()); err != nil {
+			return err
 		}
 	}
 	return t.validateImportedHeaders(chain, validBlocks)
diff --git a/tests/fuzzers/README.md b/tests/fuzzers/README.md
index fd8c4ec57f..7611c53698 100644
--- a/tests/fuzzers/README.md
+++ b/tests/fuzzers/README.md
@@ -34,7 +34,7 @@ go-fuzz -bin ./rlp/rlp-fuzz.zip
 Once a 'crasher' is found, the fuzzer tries to avoid reporting the same vector twice, so stores the fault in the `suppressions` folder. Thus, if you 
 e.g. make changes to fix a bug, you should _remove_ all data from the `suppressions`-folder, to verify that the issue is indeed resolved. 
 
-Also, if you have only one and the same exit-point for multiple different types of test, the suppression can make the fuzzer hide differnent types of errors. So make 
+Also, if you have only one and the same exit-point for multiple different types of test, the suppression can make the fuzzer hide different types of errors. So make
 sure that each type of failure is unique (for an example, see the rlp fuzzer, where a counter `i` is used to differentiate between failures: 
 
 ```golang
diff --git a/tests/fuzzers/abi/abifuzzer.go b/tests/fuzzers/abi/abifuzzer.go
new file mode 100644
index 0000000000..60233d158a
--- /dev/null
+++ b/tests/fuzzers/abi/abifuzzer.go
@@ -0,0 +1,170 @@
+// 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 .
+
+package abi
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/accounts/abi"
+	fuzz "github.com/google/gofuzz"
+)
+
+var (
+	names            = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"}
+	stateMut         = []string{"", "pure", "view", "payable"}
+	stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]}
+	pays             = []string{"", "true", "false"}
+	payables         = []*string{&pays[0], &pays[1]}
+	vNames           = []string{"a", "b", "c", "d", "e", "f", "g"}
+	varNames         = append(vNames, names...)
+	varTypes         = []string{"bool", "address", "bytes", "string",
+		"uint8", "int8", "uint8", "int8", "uint16", "int16",
+		"uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56",
+		"uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96",
+		"uint104", "int104", "uint112", "int112", "uint120", "int120", "uint128", "int128", "uint136", "int136",
+		"uint144", "int144", "uint152", "int152", "uint160", "int160", "uint168", "int168", "uint176", "int176",
+		"uint184", "int184", "uint192", "int192", "uint200", "int200", "uint208", "int208", "uint216", "int216",
+		"uint224", "int224", "uint232", "int232", "uint240", "int240", "uint248", "int248", "uint256", "int256",
+		"bytes1", "bytes2", "bytes3", "bytes4", "bytes5", "bytes6", "bytes7", "bytes8", "bytes9", "bytes10", "bytes11",
+		"bytes12", "bytes13", "bytes14", "bytes15", "bytes16", "bytes17", "bytes18", "bytes19", "bytes20", "bytes21",
+		"bytes22", "bytes23", "bytes24", "bytes25", "bytes26", "bytes27", "bytes28", "bytes29", "bytes30", "bytes31",
+		"bytes32", "bytes"}
+)
+
+func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) {
+	if out, err := abi.Unpack(method, input); err == nil {
+		_, err := abi.Pack(method, out...)
+		if err != nil {
+			// We have some false positives as we can unpack these type successfully, but not pack them
+			if err.Error() == "abi: cannot use []uint8 as type [0]int8 as argument" ||
+				err.Error() == "abi: cannot use uint8 as type int8 as argument" {
+				return out, false
+			}
+			panic(err)
+		}
+		return out, true
+	}
+	return nil, false
+}
+
+func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool {
+	if packed, err := abi.Pack(method, input); err == nil {
+		outptr := reflect.New(reflect.TypeOf(input))
+		err := abi.UnpackIntoInterface(outptr.Interface(), method, packed)
+		if err != nil {
+			panic(err)
+		}
+		out := outptr.Elem().Interface()
+		if !reflect.DeepEqual(input, out) {
+			panic(fmt.Sprintf("unpackPack is not equal, \ninput : %x\noutput: %x", input, out))
+		}
+		return true
+	}
+	return false
+}
+
+type args struct {
+	name string
+	typ  string
+}
+
+func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) {
+	sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name)
+	if stateMutability != nil {
+		sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability)
+	}
+	if payable != nil {
+		sig += fmt.Sprintf(`, "payable": %v `, *payable)
+	}
+	if len(inputs) > 0 {
+		sig += `, "inputs" : [ {`
+		for i, inp := range inputs {
+			sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ)
+			if i+1 < len(inputs) {
+				sig += ","
+			}
+		}
+		sig += "} ]"
+		sig += `, "outputs" : [ {`
+		for i, inp := range inputs {
+			sig += fmt.Sprintf(`"name" : "%v", "type" : "%v" `, inp.name, inp.typ)
+			if i+1 < len(inputs) {
+				sig += ","
+			}
+		}
+		sig += "} ]"
+	}
+	sig += `}]`
+
+	return abi.JSON(strings.NewReader(sig))
+}
+
+func runFuzzer(input []byte) int {
+	good := false
+	fuzzer := fuzz.NewFromGoFuzz(input)
+
+	name := names[getUInt(fuzzer)%len(names)]
+	stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)]
+	payable := payables[getUInt(fuzzer)%len(payables)]
+	maxLen := 5
+	for k := 1; k < maxLen; k++ {
+		var arg []args
+		for i := k; i > 0; i-- {
+			argName := varNames[i]
+			argTyp := varTypes[getUInt(fuzzer)%len(varTypes)]
+			if getUInt(fuzzer)%10 == 0 {
+				argTyp += "[]"
+			} else if getUInt(fuzzer)%10 == 0 {
+				arrayArgs := getUInt(fuzzer)%30 + 1
+				argTyp += fmt.Sprintf("[%d]", arrayArgs)
+			}
+			arg = append(arg, args{
+				name: argName,
+				typ:  argTyp,
+			})
+		}
+		abi, err := createABI(name, stateM, payable, arg)
+		if err != nil {
+			continue
+		}
+		structs, b := unpackPack(abi, name, input)
+		c := packUnpack(abi, name, &structs)
+		good = good || b || c
+	}
+	if good {
+		return 1
+	}
+	return 0
+}
+
+func Fuzz(input []byte) int {
+	return runFuzzer(input)
+}
+
+func getUInt(fuzzer *fuzz.Fuzzer) int {
+	var i int
+	fuzzer.Fuzz(&i)
+	if i < 0 {
+		i = -i
+		if i < 0 {
+			return 0
+		}
+	}
+	return i
+}
diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go
new file mode 100644
index 0000000000..423a3cd232
--- /dev/null
+++ b/tests/fuzzers/abi/abifuzzer_test.go
@@ -0,0 +1,43 @@
+// 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 .
+
+package abi
+
+import (
+	"testing"
+)
+
+// TestReplicate can be used to replicate crashers from the fuzzing tests.
+// Just replace testString with the data in .quoted
+func TestReplicate(t *testing.T) {
+	testString := "\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00"
+	data := []byte(testString)
+	runFuzzer(data)
+}
+
+// TestGenerateCorpus can be used to add corpus for the fuzzer.
+// Just replace corpusHex with the hexEncoded output you want to add to the fuzzer.
+func TestGenerateCorpus(t *testing.T) {
+	/*
+		corpusHex := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+		data := common.FromHex(corpusHex)
+		checksum := sha1.Sum(data)
+		outf := fmt.Sprintf("corpus/%x", checksum)
+		if err := ioutil.WriteFile(outf, data, 0777); err != nil {
+			panic(err)
+		}
+	*/
+}
diff --git a/common/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go
similarity index 68%
rename from common/bitutil/compress_fuzz.go
rename to tests/fuzzers/bitutil/compress_fuzz.go
index 1b87f50edc..5903cf2f93 100644
--- a/common/bitutil/compress_fuzz.go
+++ b/tests/fuzzers/bitutil/compress_fuzz.go
@@ -14,17 +14,19 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// +build gofuzz
-
 package bitutil
 
-import "bytes"
+import (
+	"bytes"
+
+	"github.com/ethereum/go-ethereum/common/bitutil"
+)
 
 // Fuzz implements a go-fuzz fuzzer method to test various encoding method
 // invocations.
 func Fuzz(data []byte) int {
 	if len(data) == 0 {
-		return -1
+		return 0
 	}
 	if data[0]%2 == 0 {
 		return fuzzEncode(data[1:])
@@ -35,22 +37,34 @@ func Fuzz(data []byte) int {
 // fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and
 // decoding algorithm.
 func fuzzEncode(data []byte) int {
-	proc, _ := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
+	proc, _ := bitutil.DecompressBytes(bitutil.CompressBytes(data), len(data))
 	if !bytes.Equal(data, proc) {
 		panic("content mismatch")
 	}
-	return 0
+	return 1
 }
 
 // fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
 // reencoding algorithm.
 func fuzzDecode(data []byte) int {
-	blob, err := bitsetDecodeBytes(data, 1024)
+	blob, err := bitutil.DecompressBytes(data, 1024)
 	if err != nil {
 		return 0
 	}
-	if comp := bitsetEncodeBytes(blob); !bytes.Equal(comp, data) {
+	// re-compress it (it's OK if the re-compressed differs from the
+	// original - the first input may not have been compressed at all)
+	comp := bitutil.CompressBytes(blob)
+	if len(comp) > len(blob) {
+		// After compression, it must be smaller or equal
+		panic("bad compression")
+	}
+	// But decompressing it once again should work
+	decomp, err := bitutil.DecompressBytes(data, 1024)
+	if err != nil {
+		panic(err)
+	}
+	if !bytes.Equal(decomp, blob) {
 		panic("content mismatch")
 	}
-	return 0
+	return 1
 }
diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go
new file mode 100644
index 0000000000..298050ad36
--- /dev/null
+++ b/tests/fuzzers/bls12381/bls12381_fuzz.go
@@ -0,0 +1,244 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// +build gofuzz
+
+package bls
+
+import (
+	"bytes"
+	"crypto/rand"
+	"fmt"
+	"io"
+	"math/big"
+
+	gnark "github.com/consensys/gnark-crypto/ecc/bls12-381"
+	"github.com/consensys/gnark-crypto/ecc/bls12-381/fp"
+	"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
+	"github.com/ethereum/go-ethereum/crypto/bls12381"
+)
+
+func FuzzCrossPairing(data []byte) int {
+	input := bytes.NewReader(data)
+
+	// get random G1 points
+	kpG1, cpG1, err := getG1Points(input)
+	if err != nil {
+		return 0
+	}
+
+	// get random G2 points
+	kpG2, cpG2, err := getG2Points(input)
+	if err != nil {
+		return 0
+	}
+
+	// compute pairing using geth
+	engine := bls12381.NewPairingEngine()
+	engine.AddPair(kpG1, kpG2)
+	kResult := engine.Result()
+
+	// compute pairing using gnark
+	cResult, err := gnark.Pair([]gnark.G1Affine{*cpG1}, []gnark.G2Affine{*cpG2})
+	if err != nil {
+		panic(fmt.Sprintf("gnark/bls12381 encountered error: %v", err))
+	}
+
+	// compare result
+	if !(bytes.Equal(cResult.Marshal(), bls12381.NewGT().ToBytes(kResult))) {
+		panic("pairing mismatch gnark / geth ")
+	}
+
+	return 1
+}
+
+func FuzzCrossG1Add(data []byte) int {
+	input := bytes.NewReader(data)
+
+	// get random G1 points
+	kp1, cp1, err := getG1Points(input)
+	if err != nil {
+		return 0
+	}
+
+	// get random G1 points
+	kp2, cp2, err := getG1Points(input)
+	if err != nil {
+		return 0
+	}
+
+	// compute kp = kp1 + kp2
+	g1 := bls12381.NewG1()
+	kp := bls12381.PointG1{}
+	g1.Add(&kp, kp1, kp2)
+
+	// compute cp = cp1 + cp2
+	_cp1 := new(gnark.G1Jac).FromAffine(cp1)
+	_cp2 := new(gnark.G1Jac).FromAffine(cp2)
+	cp := new(gnark.G1Affine).FromJacobian(_cp1.AddAssign(_cp2))
+
+	// compare result
+	if !(bytes.Equal(cp.Marshal(), g1.ToBytes(&kp))) {
+		panic("G1 point addition mismatch gnark / geth ")
+	}
+
+	return 1
+}
+
+func FuzzCrossG2Add(data []byte) int {
+	input := bytes.NewReader(data)
+
+	// get random G2 points
+	kp1, cp1, err := getG2Points(input)
+	if err != nil {
+		return 0
+	}
+
+	// get random G2 points
+	kp2, cp2, err := getG2Points(input)
+	if err != nil {
+		return 0
+	}
+
+	// compute kp = kp1 + kp2
+	g2 := bls12381.NewG2()
+	kp := bls12381.PointG2{}
+	g2.Add(&kp, kp1, kp2)
+
+	// compute cp = cp1 + cp2
+	_cp1 := new(gnark.G2Jac).FromAffine(cp1)
+	_cp2 := new(gnark.G2Jac).FromAffine(cp2)
+	cp := new(gnark.G2Affine).FromJacobian(_cp1.AddAssign(_cp2))
+
+	// compare result
+	if !(bytes.Equal(cp.Marshal(), g2.ToBytes(&kp))) {
+		panic("G2 point addition mismatch gnark / geth ")
+	}
+
+	return 1
+}
+
+func FuzzCrossG1MultiExp(data []byte) int {
+	var (
+		input        = bytes.NewReader(data)
+		gethScalars  []*big.Int
+		gnarkScalars []fr.Element
+		gethPoints   []*bls12381.PointG1
+		gnarkPoints  []gnark.G1Affine
+	)
+	// n random scalars (max 17)
+	for i := 0; i < 17; i++ {
+		// note that geth/crypto/bls12381 works only with scalars <= 32bytes
+		s, err := randomScalar(input, fr.Modulus())
+		if err != nil {
+			break
+		}
+		// get a random G1 point as basis
+		kp1, cp1, err := getG1Points(input)
+		if err != nil {
+			break
+		}
+		gethScalars = append(gethScalars, s)
+		var gnarkScalar = &fr.Element{}
+		gnarkScalar = gnarkScalar.SetBigInt(s).FromMont()
+		gnarkScalars = append(gnarkScalars, *gnarkScalar)
+
+		gethPoints = append(gethPoints, new(bls12381.PointG1).Set(kp1))
+		gnarkPoints = append(gnarkPoints, *cp1)
+	}
+	if len(gethScalars) == 0{
+		return 0
+	}
+	// compute multi exponentiation
+	g1 := bls12381.NewG1()
+	kp := bls12381.PointG1{}
+	if _, err := g1.MultiExp(&kp, gethPoints, gethScalars); err != nil {
+		panic(fmt.Sprintf("G1 multi exponentiation errored (geth): %v", err))
+	}
+	// note that geth/crypto/bls12381.MultiExp mutates the scalars slice (and sets all the scalars to zero)
+
+	// gnark multi exp
+	cp := new(gnark.G1Affine)
+	cp.MultiExp(gnarkPoints, gnarkScalars)
+
+	// compare result
+	if !(bytes.Equal(cp.Marshal(), g1.ToBytes(&kp))) {
+		panic("G1 multi exponentiation mismatch gnark / geth ")
+	}
+
+	return 1
+}
+
+func getG1Points(input io.Reader) (*bls12381.PointG1, *gnark.G1Affine, error) {
+	// sample a random scalar
+	s, err := randomScalar(input, fp.Modulus())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// compute a random point
+	cp := new(gnark.G1Affine)
+	_, _, g1Gen, _ := gnark.Generators()
+	cp.ScalarMultiplication(&g1Gen, s)
+	cpBytes := cp.Marshal()
+
+	// marshal gnark point -> geth point
+	g1 := bls12381.NewG1()
+	kp, err := g1.FromBytes(cpBytes)
+	if err != nil {
+		panic(fmt.Sprintf("Could not marshal gnark.G1 -> geth.G1: %v", err))
+	}
+	if !bytes.Equal(g1.ToBytes(kp), cpBytes) {
+		panic("bytes(gnark.G1) != bytes(geth.G1)")
+	}
+
+	return kp, cp, nil
+}
+
+func getG2Points(input io.Reader) (*bls12381.PointG2, *gnark.G2Affine, error) {
+	// sample a random scalar
+	s, err := randomScalar(input, fp.Modulus())
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// compute a random point
+	cp := new(gnark.G2Affine)
+	_, _, _, g2Gen := gnark.Generators()
+	cp.ScalarMultiplication(&g2Gen, s)
+	cpBytes := cp.Marshal()
+
+	// marshal gnark point -> geth point
+	g2 := bls12381.NewG2()
+	kp, err := g2.FromBytes(cpBytes)
+	if err != nil {
+		panic(fmt.Sprintf("Could not marshal gnark.G2 -> geth.G2: %v", err))
+	}
+	if !bytes.Equal(g2.ToBytes(kp), cpBytes) {
+		panic("bytes(gnark.G2) != bytes(geth.G2)")
+	}
+
+	return kp, cp, nil
+}
+
+func randomScalar(r io.Reader, max *big.Int) (k *big.Int, err error) {
+	for {
+		k, err = rand.Int(r, max)
+		if err != nil || k.Sign() > 0 {
+			return
+		}
+	}
+}
diff --git a/tests/fuzzers/bls12381/precompile_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go
new file mode 100644
index 0000000000..bc3c456526
--- /dev/null
+++ b/tests/fuzzers/bls12381/precompile_fuzzer.go
@@ -0,0 +1,101 @@
+// 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 .
+
+package bls
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/vm"
+)
+
+const (
+	blsG1Add      = byte(10)
+	blsG1Mul      = byte(11)
+	blsG1MultiExp = byte(12)
+	blsG2Add      = byte(13)
+	blsG2Mul      = byte(14)
+	blsG2MultiExp = byte(15)
+	blsPairing    = byte(16)
+	blsMapG1      = byte(17)
+	blsMapG2      = byte(18)
+)
+
+func FuzzG1Add(data []byte) int      { return fuzz(blsG1Add, data) }
+func FuzzG1Mul(data []byte) int      { return fuzz(blsG1Mul, data) }
+func FuzzG1MultiExp(data []byte) int { return fuzz(blsG1MultiExp, data) }
+func FuzzG2Add(data []byte) int      { return fuzz(blsG2Add, data) }
+func FuzzG2Mul(data []byte) int      { return fuzz(blsG2Mul, data) }
+func FuzzG2MultiExp(data []byte) int { return fuzz(blsG2MultiExp, data) }
+func FuzzPairing(data []byte) int    { return fuzz(blsPairing, data) }
+func FuzzMapG1(data []byte) int      { return fuzz(blsMapG1, data) }
+func FuzzMapG2(data []byte) int      { return fuzz(blsMapG2, data) }
+
+func checkInput(id byte, inputLen int) bool {
+	switch id {
+	case blsG1Add:
+		return inputLen == 256
+	case blsG1Mul:
+		return inputLen == 160
+	case blsG1MultiExp:
+		return inputLen%160 == 0
+	case blsG2Add:
+		return inputLen == 512
+	case blsG2Mul:
+		return inputLen == 288
+	case blsG2MultiExp:
+		return inputLen%288 == 0
+	case blsPairing:
+		return inputLen%384 == 0
+	case blsMapG1:
+		return inputLen == 64
+	case blsMapG2:
+		return inputLen == 128
+	}
+	panic("programmer error")
+}
+
+// The fuzzer functions 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(id byte, data []byte) int {
+	// Even on bad input, it should not crash, so we still test the gas calc
+	precompile := vm.PrecompiledContractsBLS[common.BytesToAddress([]byte{id})]
+	gas := precompile.RequiredGas(data)
+	if !checkInput(id, len(data)) {
+		return 0
+	}
+	// If the gas cost is too large (25M), bail out
+	if gas > 25*1000*1000 {
+		return 0
+	}
+	cpy := make([]byte, len(data))
+	copy(cpy, data)
+	_, err := precompile.Run(cpy)
+	if !bytes.Equal(cpy, data) {
+		panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy))
+	}
+	if err != nil {
+		return 0
+	}
+	return 1
+}
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip
new file mode 100644
index 0000000000..16498c1cba
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_g1_add_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip
new file mode 100644
index 0000000000..57f9d6696d
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_g1_mul_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g1_multiexp_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g1_multiexp_seed_corpus.zip
new file mode 100644
index 0000000000..7271f040f3
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_g1_multiexp_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip
new file mode 100644
index 0000000000..cd5206ca0b
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_g2_add_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_mul_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_mul_seed_corpus.zip
new file mode 100644
index 0000000000..f784a5a3d7
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_g2_mul_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip
new file mode 100644
index 0000000000..c205117a46
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_g2_multiexp_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip
new file mode 100644
index 0000000000..70382fbe53
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_map_g1_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip
new file mode 100644
index 0000000000..67adc5b5e8
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_map_g2_seed_corpus.zip differ
diff --git a/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip b/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip
new file mode 100644
index 0000000000..e24d2b0a52
Binary files /dev/null and b/tests/fuzzers/bls12381/testdata/fuzz_pairing_seed_corpus.zip differ
diff --git a/tests/fuzzers/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go
new file mode 100644
index 0000000000..030ac19b3f
--- /dev/null
+++ b/tests/fuzzers/bn256/bn256_fuzz.go
@@ -0,0 +1,156 @@
+// Copyright 2018 Péter Szilágyi. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be found
+// in the LICENSE file.
+
+// +build gofuzz
+
+package bn256
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math/big"
+
+	"github.com/consensys/gnark-crypto/ecc/bn254"
+	cloudflare "github.com/ethereum/go-ethereum/crypto/bn256/cloudflare"
+	google "github.com/ethereum/go-ethereum/crypto/bn256/google"
+)
+
+func getG1Points(input io.Reader) (*cloudflare.G1, *google.G1, *bn254.G1Affine) {
+	_, xc, err := cloudflare.RandomG1(input)
+	if err != nil {
+		// insufficient input
+		return nil, nil, nil
+	}
+	xg := new(google.G1)
+	if _, err := xg.Unmarshal(xc.Marshal()); err != nil {
+		panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err))
+	}
+	xs := new(bn254.G1Affine)
+	if err := xs.Unmarshal(xc.Marshal()); err != nil {
+		panic(fmt.Sprintf("Could not marshal cloudflare -> gnark: %v", err))
+	}
+	return xc, xg, xs
+}
+
+func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *bn254.G2Affine) {
+	_, xc, err := cloudflare.RandomG2(input)
+	if err != nil {
+		// insufficient input
+		return nil, nil, nil
+	}
+	xg := new(google.G2)
+	if _, err := xg.Unmarshal(xc.Marshal()); err != nil {
+		panic(fmt.Sprintf("Could not marshal cloudflare -> google: %v", err))
+	}
+	xs := new(bn254.G2Affine)
+	if err := xs.Unmarshal(xc.Marshal()); err != nil {
+		panic(fmt.Sprintf("Could not marshal cloudflare -> gnark: %v", err))
+	}
+	return xc, xg, xs
+}
+
+// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries.
+func FuzzAdd(data []byte) int {
+	input := bytes.NewReader(data)
+	xc, xg, xs := getG1Points(input)
+	if xc == nil {
+		return 0
+	}
+	yc, yg, ys := getG1Points(input)
+	if yc == nil {
+		return 0
+	}
+	// Ensure both libs can parse the second curve point
+	// Add the two points and ensure they result in the same output
+	rc := new(cloudflare.G1)
+	rc.Add(xc, yc)
+
+	rg := new(google.G1)
+	rg.Add(xg, yg)
+
+	tmpX := new(bn254.G1Jac).FromAffine(xs)
+	tmpY := new(bn254.G1Jac).FromAffine(ys)
+	rs := new(bn254.G1Affine).FromJacobian(tmpX.AddAssign(tmpY))
+
+	if !bytes.Equal(rc.Marshal(), rg.Marshal()) {
+		panic("add mismatch: cloudflare/google")
+	}
+
+	if !bytes.Equal(rc.Marshal(), rs.Marshal()) {
+		panic("add mismatch: cloudflare/gnark")
+	}
+	return 1
+}
+
+// FuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare
+// libraries.
+func FuzzMul(data []byte) int {
+	input := bytes.NewReader(data)
+	pc, pg, ps := getG1Points(input)
+	if pc == nil {
+		return 0
+	}
+	// Add the two points and ensure they result in the same output
+	remaining := input.Len()
+	if remaining == 0 {
+		return 0
+	}
+	if remaining > 128 {
+		// The evm only ever uses 32 byte integers, we need to cap this otherwise
+		// we run into slow exec. A 236Kb byte integer cause oss-fuzz to report it as slow.
+		// 128 bytes should be fine though
+		return 0
+	}
+	buf := make([]byte, remaining)
+	input.Read(buf)
+
+	rc := new(cloudflare.G1)
+	rc.ScalarMult(pc, new(big.Int).SetBytes(buf))
+
+	rg := new(google.G1)
+	rg.ScalarMult(pg, new(big.Int).SetBytes(buf))
+
+	rs := new(bn254.G1Jac)
+	psJac := new(bn254.G1Jac).FromAffine(ps)
+	rs.ScalarMultiplication(psJac, new(big.Int).SetBytes(buf))
+	rsAffine := new(bn254.G1Affine).FromJacobian(rs)
+
+	if !bytes.Equal(rc.Marshal(), rg.Marshal()) {
+		panic("scalar mul mismatch: cloudflare/google")
+	}
+	if !bytes.Equal(rc.Marshal(), rsAffine.Marshal()) {
+		panic("scalar mul mismatch: cloudflare/gnark")
+	}
+	return 1
+}
+
+func FuzzPair(data []byte) int {
+	input := bytes.NewReader(data)
+	pc, pg, ps := getG1Points(input)
+	if pc == nil {
+		return 0
+	}
+	tc, tg, ts := getG2Points(input)
+	if tc == nil {
+		return 0
+	}
+
+	// Pair the two points and ensure they result in the same output
+	clPair := cloudflare.Pair(pc, tc).Marshal()
+	gPair := google.Pair(pg, tg).Marshal()
+	if !bytes.Equal(clPair, gPair) {
+		panic("pairing mismatch: cloudflare/google")
+	}
+
+	cPair, err := bn254.Pair([]bn254.G1Affine{*ps}, []bn254.G2Affine{*ts})
+	if err != nil {
+		panic(fmt.Sprintf("gnark/bn254 encountered error: %v", err))
+	}
+	if !bytes.Equal(clPair, cPair.Marshal()) {
+		panic("pairing mismatch: cloudflare/gnark")
+	}
+
+	return 1
+}
diff --git a/tests/fuzzers/difficulty/debug/main.go b/tests/fuzzers/difficulty/debug/main.go
new file mode 100644
index 0000000000..23516b3a0d
--- /dev/null
+++ b/tests/fuzzers/difficulty/debug/main.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/ethereum/go-ethereum/tests/fuzzers/difficulty"
+)
+
+func main() {
+	if len(os.Args) != 2 {
+		fmt.Fprintf(os.Stderr, "Usage: debug ")
+		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)
+	}
+	difficulty.Fuzz(data)
+}
diff --git a/tests/fuzzers/difficulty/difficulty-fuzz.go b/tests/fuzzers/difficulty/difficulty-fuzz.go
new file mode 100644
index 0000000000..e4c5dcf57c
--- /dev/null
+++ b/tests/fuzzers/difficulty/difficulty-fuzz.go
@@ -0,0 +1,145 @@
+// 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 .
+
+package difficulty
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+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
+}
+
+func (f *fuzzer) readUint64(min, max uint64) uint64 {
+	if min == max {
+		return min
+	}
+	var a uint64
+	if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
+		f.exhausted = true
+	}
+	a = min + a%(max-min)
+	return a
+}
+func (f *fuzzer) readBool() bool {
+	return f.read(1)[0]&0x1 == 0
+}
+
+// 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()
+}
+
+var minDifficulty = big.NewInt(0x2000)
+
+type calculator func(time uint64, parent *types.Header) *big.Int
+
+func (f *fuzzer) fuzz() int {
+	// A parent header
+	header := &types.Header{}
+	if f.readBool() {
+		header.UncleHash = types.EmptyUncleHash
+	}
+	// Difficulty can range between 0x2000 (2 bytes) and up to 32 bytes
+	{
+		diff := new(big.Int).SetBytes(f.readSlice(2, 32))
+		if diff.Cmp(minDifficulty) < 0 {
+			diff.Set(minDifficulty)
+		}
+		header.Difficulty = diff
+	}
+	// Number can range between 0 and up to 32 bytes (but not so that the child exceeds it)
+	{
+		// However, if we use astronomic numbers, then the bomb exp karatsuba calculation
+		// in the legacy methods)
+		// times out, so we limit it to fit within reasonable bounds
+		number := new(big.Int).SetBytes(f.readSlice(0, 4)) // 4 bytes: 32 bits: block num max 4 billion
+		header.Number = number
+	}
+	// Both parent and child time must fit within uint64
+	var time uint64
+	{
+		childTime := f.readUint64(1, 0xFFFFFFFFFFFFFFFF)
+		//fmt.Printf("childTime: %x\n",childTime)
+		delta := f.readUint64(1, childTime)
+		//fmt.Printf("delta: %v\n", delta)
+		pTime := childTime - delta
+		header.Time = pTime
+		time = childTime
+	}
+	// Bomb delay will never exceed uint64
+	bombDelay := new(big.Int).SetUint64(f.readUint64(1, 0xFFFFFFFFFFFFFFFe))
+
+	if f.exhausted {
+		return 0
+	}
+
+	for i, pair := range []struct {
+		bigFn  calculator
+		u256Fn calculator
+	}{
+		{ethash.FrontierDifficultyCalulator, ethash.CalcDifficultyFrontierU256},
+		{ethash.HomesteadDifficultyCalulator, ethash.CalcDifficultyHomesteadU256},
+		{ethash.DynamicDifficultyCalculator(bombDelay), ethash.MakeDifficultyCalculatorU256(bombDelay)},
+	} {
+		want := pair.bigFn(time, header)
+		have := pair.u256Fn(time, header)
+		if want.Cmp(have) != 0 {
+			panic(fmt.Sprintf("pair %d: want %x have %x\nparent.Number: %x\np.Time: %x\nc.Time: %x\nBombdelay: %v\n", i, want, have,
+				header.Number, header.Time, time, bombDelay))
+		}
+	}
+	return 1
+}
diff --git a/tests/fuzzers/keystore/keystore-fuzzer.go b/tests/fuzzers/keystore/keystore-fuzzer.go
index 704f29dc48..e3bcae92e1 100644
--- a/tests/fuzzers/keystore/keystore-fuzzer.go
+++ b/tests/fuzzers/keystore/keystore-fuzzer.go
@@ -33,5 +33,5 @@ func Fuzz(input []byte) int {
 		panic(err)
 	}
 	os.Remove(a.URL.Path)
-	return 0
+	return 1
 }
diff --git a/core/types/derive_sha.go b/tests/fuzzers/les/debug/main.go
similarity index 58%
rename from core/types/derive_sha.go
rename to tests/fuzzers/les/debug/main.go
index 00c42c5bc6..09e087d4c8 100644
--- a/core/types/derive_sha.go
+++ b/tests/fuzzers/les/debug/main.go
@@ -1,4 +1,4 @@
-// Copyright 2014 The go-ethereum Authors
+// 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
@@ -14,28 +14,28 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-package types
+package main
 
 import (
-	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
 
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/rlp"
-	"github.com/ethereum/go-ethereum/trie"
+	"github.com/ethereum/go-ethereum/tests/fuzzers/les"
 )
 
-type DerivableList interface {
-	Len() int
-	GetRlp(i int) []byte
-}
-
-func DeriveSha(list DerivableList) common.Hash {
-	keybuf := new(bytes.Buffer)
-	trie := new(trie.Trie)
-	for i := 0; i < list.Len(); i++ {
-		keybuf.Reset()
-		rlp.Encode(keybuf, uint(i))
-		trie.Update(keybuf.Bytes(), list.GetRlp(i))
+func main() {
+	if len(os.Args) != 2 {
+		fmt.Fprintf(os.Stderr, "Usage: debug \n")
+		fmt.Fprintf(os.Stderr, "Example\n")
+		fmt.Fprintf(os.Stderr, "	$ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n")
+		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)
 	}
-	return trie.Hash()
+	les.Fuzz(data)
 }
diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go
new file mode 100644
index 0000000000..3e10171873
--- /dev/null
+++ b/tests/fuzzers/les/les-fuzzer.go
@@ -0,0 +1,407 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package les
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+	"math/big"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/consensus/ethash"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/rawdb"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	l "github.com/ethereum/go-ethereum/les"
+	"github.com/ethereum/go-ethereum/params"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+var (
+	bankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+	bankAddr   = crypto.PubkeyToAddress(bankKey.PublicKey)
+	bankFunds  = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether))
+
+	testChainLen     = 256
+	testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
+
+	chain      *core.BlockChain
+	addrHashes []common.Hash
+	txHashes   []common.Hash
+
+	chtTrie   *trie.Trie
+	bloomTrie *trie.Trie
+	chtKeys   [][]byte
+	bloomKeys [][]byte
+)
+
+func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) {
+	db := rawdb.NewMemoryDatabase()
+	gspec := core.Genesis{
+		Config:   params.TestChainConfig,
+		Alloc:    core.GenesisAlloc{bankAddr: {Balance: bankFunds}},
+		GasLimit: 100000000,
+	}
+	genesis := gspec.MustCommit(db)
+	signer := types.HomesteadSigner{}
+	blocks, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, testChainLen,
+		func(i int, gen *core.BlockGen) {
+			var (
+				tx   *types.Transaction
+				addr common.Address
+			)
+			nonce := uint64(i)
+			if i%4 == 0 {
+				tx, _ = types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, bankKey)
+				addr = crypto.CreateAddress(bankAddr, nonce)
+			} else {
+				addr = common.BigToAddress(big.NewInt(int64(i)))
+				tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey)
+			}
+			gen.AddTx(tx)
+			addrHashes = append(addrHashes, crypto.Keccak256Hash(addr[:]))
+			txHashes = append(txHashes, tx.Hash())
+		})
+	bc, _ = core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
+	if _, err := bc.InsertChain(blocks); err != nil {
+		panic(err)
+	}
+	return
+}
+
+func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [][]byte) {
+	chtTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase()))
+	bloomTrie, _ = trie.New(common.Hash{}, trie.NewDatabase(rawdb.NewMemoryDatabase()))
+	for i := 0; i < testChainLen; i++ {
+		// The element in CHT is  -> 
+		key := make([]byte, 8)
+		binary.BigEndian.PutUint64(key, uint64(i+1))
+		chtTrie.Update(key, []byte{0x1, 0xf})
+		chtKeys = append(chtKeys, key)
+
+		// The element in Bloom trie is <2 byte bit index> +  -> bloom
+		key2 := make([]byte, 10)
+		binary.BigEndian.PutUint64(key2[2:], uint64(i+1))
+		bloomTrie.Update(key2, []byte{0x2, 0xe})
+		bloomKeys = append(bloomKeys, key2)
+	}
+	return
+}
+
+func init() {
+	chain, addrHashes, txHashes = makechain()
+	chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries()
+}
+
+type fuzzer struct {
+	chain *core.BlockChain
+	pool  *core.TxPool
+
+	chainLen  int
+	addr, txs []common.Hash
+	nonce     uint64
+
+	chtKeys   [][]byte
+	bloomKeys [][]byte
+	chtTrie   *trie.Trie
+	bloomTrie *trie.Trie
+
+	input     io.Reader
+	exhausted bool
+}
+
+func newFuzzer(input []byte) *fuzzer {
+	return &fuzzer{
+		chain:     chain,
+		chainLen:  testChainLen,
+		addr:      addrHashes,
+		txs:       txHashes,
+		chtTrie:   chtTrie,
+		bloomTrie: bloomTrie,
+		chtKeys:   chtKeys,
+		bloomKeys: bloomKeys,
+		nonce:     uint64(len(txHashes)),
+		pool:      core.NewTxPool(core.DefaultTxPoolConfig, params.TestChainConfig, chain),
+		input:     bytes.NewReader(input),
+	}
+}
+
+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) randomByte() byte {
+	d := f.read(1)
+	return d[0]
+}
+
+func (f *fuzzer) randomBool() bool {
+	d := f.read(1)
+	return d[0]&1 == 1
+}
+
+func (f *fuzzer) randomInt(max int) int {
+	if max == 0 {
+		return 0
+	}
+	if max <= 256 {
+		return int(f.randomByte()) % max
+	}
+	var a uint16
+	if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
+		f.exhausted = true
+	}
+	return int(a % uint16(max))
+}
+
+func (f *fuzzer) randomX(max int) uint64 {
+	var a uint16
+	if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
+		f.exhausted = true
+	}
+	if a < 0x8000 {
+		return uint64(a%uint16(max+1)) - 1
+	}
+	return (uint64(1)<<(a%64+1) - 1) & (uint64(a) * 343897772345826595)
+}
+
+func (f *fuzzer) randomBlockHash() common.Hash {
+	h := f.chain.GetCanonicalHash(uint64(f.randomInt(3 * f.chainLen)))
+	if h != (common.Hash{}) {
+		return h
+	}
+	return common.BytesToHash(f.read(common.HashLength))
+}
+
+func (f *fuzzer) randomAddrHash() []byte {
+	i := f.randomInt(3 * len(f.addr))
+	if i < len(f.addr) {
+		return f.addr[i].Bytes()
+	}
+	return f.read(common.HashLength)
+}
+
+func (f *fuzzer) randomCHTTrieKey() []byte {
+	i := f.randomInt(3 * len(f.chtKeys))
+	if i < len(f.chtKeys) {
+		return f.chtKeys[i]
+	}
+	return f.read(8)
+}
+
+func (f *fuzzer) randomBloomTrieKey() []byte {
+	i := f.randomInt(3 * len(f.bloomKeys))
+	if i < len(f.bloomKeys) {
+		return f.bloomKeys[i]
+	}
+	return f.read(10)
+}
+
+func (f *fuzzer) randomTxHash() common.Hash {
+	i := f.randomInt(3 * len(f.txs))
+	if i < len(f.txs) {
+		return f.txs[i]
+	}
+	return common.BytesToHash(f.read(common.HashLength))
+}
+
+func (f *fuzzer) BlockChain() *core.BlockChain {
+	return f.chain
+}
+
+func (f *fuzzer) TxPool() *core.TxPool {
+	return f.pool
+}
+
+func (f *fuzzer) ArchiveMode() bool {
+	return false
+}
+
+func (f *fuzzer) AddTxsSync() bool {
+	return false
+}
+
+func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie {
+	if typ == 0 {
+		return f.chtTrie
+	} else if typ == 1 {
+		return f.bloomTrie
+	}
+	return nil
+}
+
+type dummyMsg struct {
+	data []byte
+}
+
+func (d dummyMsg) Decode(val interface{}) error {
+	return rlp.DecodeBytes(d.data, val)
+}
+
+func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) {
+	enc, err := rlp.EncodeToBytes(packet)
+	if err != nil {
+		panic(err)
+	}
+	version := f.randomInt(3) + 2 // [LES2, LES3, LES4]
+	peer, closeFn := l.NewFuzzerPeer(version)
+	defer closeFn()
+	fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc})
+	if err != nil {
+		panic(err)
+	}
+	fn(f, peer, func() bool { return true })
+}
+
+func Fuzz(input []byte) int {
+	// We expect some large inputs
+	if len(input) < 100 {
+		return -1
+	}
+	f := newFuzzer(input)
+	if f.exhausted {
+		return -1
+	}
+	for !f.exhausted {
+		switch f.randomInt(8) {
+		case 0:
+			req := &l.GetBlockHeadersPacket{
+				Query: l.GetBlockHeadersData{
+					Amount:  f.randomX(l.MaxHeaderFetch + 1),
+					Skip:    f.randomX(10),
+					Reverse: f.randomBool(),
+				},
+			}
+			if f.randomBool() {
+				req.Query.Origin.Hash = f.randomBlockHash()
+			} else {
+				req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2))
+			}
+			f.doFuzz(l.GetBlockHeadersMsg, req)
+
+		case 1:
+			req := &l.GetBlockBodiesPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxBodyFetch+1))}
+			for i := range req.Hashes {
+				req.Hashes[i] = f.randomBlockHash()
+			}
+			f.doFuzz(l.GetBlockBodiesMsg, req)
+
+		case 2:
+			req := &l.GetCodePacket{Reqs: make([]l.CodeReq, f.randomInt(l.MaxCodeFetch+1))}
+			for i := range req.Reqs {
+				req.Reqs[i] = l.CodeReq{
+					BHash:  f.randomBlockHash(),
+					AccKey: f.randomAddrHash(),
+				}
+			}
+			f.doFuzz(l.GetCodeMsg, req)
+
+		case 3:
+			req := &l.GetReceiptsPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxReceiptFetch+1))}
+			for i := range req.Hashes {
+				req.Hashes[i] = f.randomBlockHash()
+			}
+			f.doFuzz(l.GetReceiptsMsg, req)
+
+		case 4:
+			req := &l.GetProofsPacket{Reqs: make([]l.ProofReq, f.randomInt(l.MaxProofsFetch+1))}
+			for i := range req.Reqs {
+				if f.randomBool() {
+					req.Reqs[i] = l.ProofReq{
+						BHash:     f.randomBlockHash(),
+						AccKey:    f.randomAddrHash(),
+						Key:       f.randomAddrHash(),
+						FromLevel: uint(f.randomX(3)),
+					}
+				} else {
+					req.Reqs[i] = l.ProofReq{
+						BHash:     f.randomBlockHash(),
+						Key:       f.randomAddrHash(),
+						FromLevel: uint(f.randomX(3)),
+					}
+				}
+			}
+			f.doFuzz(l.GetProofsV2Msg, req)
+
+		case 5:
+			req := &l.GetHelperTrieProofsPacket{Reqs: make([]l.HelperTrieReq, f.randomInt(l.MaxHelperTrieProofsFetch+1))}
+			for i := range req.Reqs {
+				switch f.randomInt(3) {
+				case 0:
+					// Canonical hash trie
+					req.Reqs[i] = l.HelperTrieReq{
+						Type:      0,
+						TrieIdx:   f.randomX(3),
+						Key:       f.randomCHTTrieKey(),
+						FromLevel: uint(f.randomX(3)),
+						AuxReq:    uint(2),
+					}
+				case 1:
+					// Bloom trie
+					req.Reqs[i] = l.HelperTrieReq{
+						Type:      1,
+						TrieIdx:   f.randomX(3),
+						Key:       f.randomBloomTrieKey(),
+						FromLevel: uint(f.randomX(3)),
+						AuxReq:    0,
+					}
+				default:
+					// Random trie
+					req.Reqs[i] = l.HelperTrieReq{
+						Type:      2,
+						TrieIdx:   f.randomX(3),
+						Key:       f.randomCHTTrieKey(),
+						FromLevel: uint(f.randomX(3)),
+						AuxReq:    0,
+					}
+				}
+			}
+			f.doFuzz(l.GetHelperTrieProofsMsg, req)
+
+		case 6:
+			req := &l.SendTxPacket{Txs: make([]*types.Transaction, f.randomInt(l.MaxTxSend+1))}
+			signer := types.HomesteadSigner{}
+			for i := range req.Txs {
+				var nonce uint64
+				if f.randomBool() {
+					nonce = uint64(f.randomByte())
+				} else {
+					nonce = f.nonce
+					f.nonce += 1
+				}
+				req.Txs[i], _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(10000), params.TxGas, big.NewInt(1000000000*int64(f.randomByte())), nil), signer, bankKey)
+			}
+			f.doFuzz(l.SendTxV2Msg, req)
+
+		case 7:
+			req := &l.GetTxStatusPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxTxStatus+1))}
+			for i := range req.Hashes {
+				req.Hashes[i] = f.randomTxHash()
+			}
+			f.doFuzz(l.GetTxStatusMsg, req)
+		}
+	}
+	return 0
+}
diff --git a/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 b/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1
new file mode 100644
index 0000000000..31c08bafaf
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/1c14030f26872e57bf1481084f151d71eed8161c-1 differ
diff --git a/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 b/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2
new file mode 100644
index 0000000000..7bce13ef80
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/27e54254422543060a13ea8a4bc913d768e4adb6-2 differ
diff --git a/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 b/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5
new file mode 100644
index 0000000000..613e76a020
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/6bfc2cbe2d7a43361e240118439785445a0fdfb7-5 differ
diff --git a/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 b/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1
new file mode 100644
index 0000000000..805ad8df77
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/a67e63bc0c0004bd009944a6061297cb7d4ac238-1 differ
diff --git a/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 b/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4
new file mode 100644
index 0000000000..605acf81c1
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/ae892bbae0a843950bc8316496e595b1a194c009-4 differ
diff --git a/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 b/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2
new file mode 100644
index 0000000000..8f32dd775a
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/ee05d0d813f6261b3dba16506f9ea03d9c5e993d-2 differ
diff --git a/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 b/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2
new file mode 100644
index 0000000000..af96210f20
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/f50a6d57a46d30184aa294af5b252ab9701af7c9-2 differ
diff --git a/tests/fuzzers/rangeproof/corpus/random.dat b/tests/fuzzers/rangeproof/corpus/random.dat
new file mode 100644
index 0000000000..2c998ad812
Binary files /dev/null and b/tests/fuzzers/rangeproof/corpus/random.dat differ
diff --git a/tests/fuzzers/rangeproof/debug/main.go b/tests/fuzzers/rangeproof/debug/main.go
new file mode 100644
index 0000000000..a81c69fea5
--- /dev/null
+++ b/tests/fuzzers/rangeproof/debug/main.go
@@ -0,0 +1,41 @@
+// 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 .
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/ethereum/go-ethereum/tests/fuzzers/rangeproof"
+)
+
+func main() {
+	if len(os.Args) != 2 {
+		fmt.Fprintf(os.Stderr, "Usage: debug \n")
+		fmt.Fprintf(os.Stderr, "Example\n")
+		fmt.Fprintf(os.Stderr, "	$ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n")
+		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)
+	}
+	rangeproof.Fuzz(data)
+}
diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go
new file mode 100644
index 0000000000..09ee6bb9c7
--- /dev/null
+++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go
@@ -0,0 +1,199 @@
+// 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 .
+
+package rangeproof
+
+import (
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"sort"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/trie"
+)
+
+type kv struct {
+	k, v []byte
+	t    bool
+}
+
+type entrySlice []*kv
+
+func (p entrySlice) Len() int           { return len(p) }
+func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 }
+func (p entrySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+type fuzzer struct {
+	input     io.Reader
+	exhausted bool
+}
+
+func (f *fuzzer) randBytes(n int) []byte {
+	r := make([]byte, n)
+	if _, err := f.input.Read(r); err != nil {
+		f.exhausted = true
+	}
+	return r
+}
+
+func (f *fuzzer) readInt() uint64 {
+	var x uint64
+	if err := binary.Read(f.input, binary.LittleEndian, &x); err != nil {
+		f.exhausted = true
+	}
+	return x
+}
+
+func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) {
+
+	trie := new(trie.Trie)
+	vals := make(map[string]*kv)
+	size := f.readInt()
+	// Fill it with some fluff
+	for i := byte(0); i < byte(size); i++ {
+		value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
+		value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false}
+		trie.Update(value.k, value.v)
+		trie.Update(value2.k, value2.v)
+		vals[string(value.k)] = value
+		vals[string(value2.k)] = value2
+	}
+	if f.exhausted {
+		return nil, nil
+	}
+	// And now fill with some random
+	for i := 0; i < n; i++ {
+		k := f.randBytes(32)
+		v := f.randBytes(20)
+		value := &kv{k, v, false}
+		trie.Update(k, v)
+		vals[string(k)] = value
+		if f.exhausted {
+			return nil, nil
+		}
+	}
+	return trie, vals
+}
+
+func (f *fuzzer) fuzz() int {
+	maxSize := 200
+	tr, vals := f.randomTrie(1 + int(f.readInt())%maxSize)
+	if f.exhausted {
+		return 0 // input too short
+	}
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	if len(entries) <= 1 {
+		return 0
+	}
+	sort.Sort(entries)
+
+	var ok = 0
+	for {
+		start := int(f.readInt() % uint64(len(entries)))
+		end := 1 + int(f.readInt()%uint64(len(entries)-1))
+		testcase := int(f.readInt() % uint64(6))
+		index := int(f.readInt() & 0xFFFFFFFF)
+		index2 := int(f.readInt() & 0xFFFFFFFF)
+		if f.exhausted {
+			break
+		}
+		proof := memorydb.New()
+		if err := tr.Prove(entries[start].k, 0, proof); err != nil {
+			panic(fmt.Sprintf("Failed to prove the first node %v", err))
+		}
+		if err := tr.Prove(entries[end-1].k, 0, proof); err != nil {
+			panic(fmt.Sprintf("Failed to prove the last node %v", err))
+		}
+		var keys [][]byte
+		var vals [][]byte
+		for i := start; i < end; i++ {
+			keys = append(keys, entries[i].k)
+			vals = append(vals, entries[i].v)
+		}
+		if len(keys) == 0 {
+			return 0
+		}
+		var first, last = keys[0], keys[len(keys)-1]
+		testcase %= 6
+		switch testcase {
+		case 0:
+			// Modified key
+			keys[index%len(keys)] = f.randBytes(32) // In theory it can't be same
+		case 1:
+			// Modified val
+			vals[index%len(vals)] = f.randBytes(20) // In theory it can't be same
+		case 2:
+			// Gapped entry slice
+			index = index % len(keys)
+			keys = append(keys[:index], keys[index+1:]...)
+			vals = append(vals[:index], vals[index+1:]...)
+		case 3:
+			// Out of order
+			index1 := index % len(keys)
+			index2 := index2 % len(keys)
+			keys[index1], keys[index2] = keys[index2], keys[index1]
+			vals[index1], vals[index2] = vals[index2], vals[index1]
+		case 4:
+			// Set random key to nil, do nothing
+			keys[index%len(keys)] = nil
+		case 5:
+			// Set random value to nil, deletion
+			vals[index%len(vals)] = nil
+
+			// Other cases:
+			// Modify something in the proof db
+			// add stuff to proof db
+			// drop stuff from proof db
+
+		}
+		if f.exhausted {
+			break
+		}
+		ok = 1
+		//nodes, subtrie
+		hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof)
+		if err != nil {
+			if hasMore {
+				panic("err != nil && hasMore == true")
+			}
+		}
+	}
+	return ok
+}
+
+// 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 {
+	if len(input) < 100 {
+		return 0
+	}
+	r := bytes.NewReader(input)
+	f := fuzzer{
+		input:     r,
+		exhausted: false,
+	}
+	return f.fuzz()
+}
diff --git a/tests/fuzzers/rlp/rlp_fuzzer.go b/tests/fuzzers/rlp/rlp_fuzzer.go
index 534540476c..18b36287b5 100644
--- a/tests/fuzzers/rlp/rlp_fuzzer.go
+++ b/tests/fuzzers/rlp/rlp_fuzzer.go
@@ -37,17 +37,17 @@ func decodeEncode(input []byte, val interface{}, i int) {
 }
 
 func Fuzz(input []byte) int {
+	if len(input) == 0 {
+		return 0
+	}
+
 	var i int
 	{
-		if len(input) > 0 {
-			rlp.Split(input)
-		}
+		rlp.Split(input)
 	}
 	{
-		if len(input) > 0 {
-			if elems, _, err := rlp.SplitList(input); err == nil {
-				rlp.CountValues(elems)
-			}
+		if elems, _, err := rlp.SplitList(input); err == nil {
+			rlp.CountValues(elems)
 		}
 	}
 
@@ -123,5 +123,5 @@ func Fuzz(input []byte) int {
 		var rs types.Receipts
 		decodeEncode(input, &rs, i)
 	}
-	return 0
+	return 1
 }
diff --git a/core/vm/runtime/fuzz.go b/tests/fuzzers/runtime/runtime_fuzz.go
similarity index 82%
rename from core/vm/runtime/fuzz.go
rename to tests/fuzzers/runtime/runtime_fuzz.go
index cb9ff08b5b..9b96045752 100644
--- a/core/vm/runtime/fuzz.go
+++ b/tests/fuzzers/runtime/runtime_fuzz.go
@@ -14,23 +14,23 @@
 // You should have received a copy of the GNU Lesser General Public License
 // along with the go-ethereum library. If not, see .
 
-// +build gofuzz
-
 package runtime
 
+import (
+	"github.com/ethereum/go-ethereum/core/vm/runtime"
+)
+
 // Fuzz is the basic entry point for the go-fuzz tool
 //
 // This returns 1 for valid parsable/runable code, 0
 // for invalid opcode.
 func Fuzz(input []byte) int {
-	_, _, err := Execute(input, input, &Config{
-		GasLimit: 3000000,
+	_, _, err := runtime.Execute(input, input, &runtime.Config{
+		GasLimit: 12000000,
 	})
-
 	// invalid opcode
-	if err != nil && len(err.Error()) > 6 && string(err.Error()[:7]) == "invalid" {
+	if err != nil && len(err.Error()) > 6 && err.Error()[:7] == "invalid" {
 		return 0
 	}
-
 	return 1
 }
diff --git a/tests/fuzzers/stacktrie/debug/main.go b/tests/fuzzers/stacktrie/debug/main.go
new file mode 100644
index 0000000000..1ec28a8ef1
--- /dev/null
+++ b/tests/fuzzers/stacktrie/debug/main.go
@@ -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 ")
+		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)
+}
diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go
new file mode 100644
index 0000000000..5cea7769c2
--- /dev/null
+++ b/tests/fuzzers/stacktrie/trie_fuzzer.go
@@ -0,0 +1,204 @@
+// 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 .
+
+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
+		// operate on unique keys only
+		keys = make(map[string]struct{})
+	)
+	// 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
+		}
+		if _, present := keys[string(k)]; present {
+			// This key is a duplicate, ignore it
+			continue
+		}
+		keys[string(k)] = struct{}{}
+		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
+}
diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go
index 9818838053..762ab5f347 100644
--- a/tests/fuzzers/trie/trie-fuzzer.go
+++ b/tests/fuzzers/trie/trie-fuzzer.go
@@ -122,15 +122,22 @@ func Generate(input []byte) randTest {
 	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 {
 	program := Generate(input)
 	if len(program) == 0 {
-		return -1
+		return 0
 	}
 	if err := runRandTest(program); err != nil {
 		panic(err)
 	}
-	return 0
+	return 1
 }
 
 func runRandTest(rt randTest) error {
diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go
index 10c7eb9424..d1d6fdc665 100644
--- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go
+++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go
@@ -51,8 +51,9 @@ func init() {
 func Fuzz(input []byte) int {
 	// Don't generate insanely large test cases, not much value in them
 	if len(input) > 16*1024 {
-		return -1
+		return 0
 	}
+	verbose := false
 	r := bytes.NewReader(input)
 
 	// Reduce the problem space for certain fuzz runs. Small tx space is better
@@ -124,7 +125,9 @@ func Fuzz(input []byte) int {
 				announceIdxs[i] = (int(annBuf[0])*256 + int(annBuf[1])) % len(txs)
 				announces[i] = txs[announceIdxs[i]].Hash()
 			}
-			fmt.Println("Notify", peer, announceIdxs)
+			if verbose {
+				fmt.Println("Notify", peer, announceIdxs)
+			}
 			if err := f.Notify(peer, announces); err != nil {
 				panic(err)
 			}
@@ -163,8 +166,9 @@ func Fuzz(input []byte) int {
 				return 0
 			}
 			direct := (directFlag % 2) == 0
-
-			fmt.Println("Enqueue", peer, deliverIdxs, direct)
+			if verbose {
+				fmt.Println("Enqueue", peer, deliverIdxs, direct)
+			}
 			if err := f.Enqueue(peer, deliveries, direct); err != nil {
 				panic(err)
 			}
@@ -177,8 +181,9 @@ func Fuzz(input []byte) int {
 				return 0
 			}
 			peer := peers[int(peerIdx)%len(peers)]
-
-			fmt.Println("Drop", peer)
+			if verbose {
+				fmt.Println("Drop", peer)
+			}
 			if err := f.Drop(peer); err != nil {
 				panic(err)
 			}
@@ -191,8 +196,9 @@ func Fuzz(input []byte) int {
 				return 0
 			}
 			tick := time.Duration(tickCnt) * 100 * time.Millisecond
-
-			fmt.Println("Sleep", tick)
+			if verbose {
+				fmt.Println("Sleep", tick)
+			}
 			clock.Run(tick)
 		}
 	}
diff --git a/tests/fuzzers/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go
new file mode 100644
index 0000000000..0414c001ec
--- /dev/null
+++ b/tests/fuzzers/vflux/clientpool-fuzzer.go
@@ -0,0 +1,335 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package vflux
+
+import (
+	"bytes"
+	"encoding/binary"
+	"io"
+	"math"
+	"math/big"
+	"time"
+
+	"github.com/ethereum/go-ethereum/common/mclock"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+	"github.com/ethereum/go-ethereum/les/vflux"
+	vfs "github.com/ethereum/go-ethereum/les/vflux/server"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/p2p/enode"
+	"github.com/ethereum/go-ethereum/p2p/enr"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+var (
+	debugMode = false
+	doLog     = func(msg string, ctx ...interface{}) {
+		if !debugMode {
+			return
+		}
+		log.Info(msg, ctx...)
+	}
+)
+
+type fuzzer struct {
+	peers                  [256]*clientPeer
+	disconnectList         []*clientPeer
+	input                  io.Reader
+	exhausted              bool
+	activeCount, activeCap uint64
+	maxCount, maxCap       uint64
+}
+
+type clientPeer struct {
+	fuzzer  *fuzzer
+	node    *enode.Node
+	freeID  string
+	timeout time.Duration
+
+	balance  vfs.ConnectedBalance
+	capacity uint64
+}
+
+func (p *clientPeer) Node() *enode.Node {
+	return p.node
+}
+
+func (p *clientPeer) FreeClientId() string {
+	return p.freeID
+}
+
+func (p *clientPeer) InactiveAllowance() time.Duration {
+	return p.timeout
+}
+
+func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) {
+	origin, originTotal := p.capacity, p.fuzzer.activeCap
+	p.fuzzer.activeCap -= p.capacity
+	if p.capacity != 0 {
+		p.fuzzer.activeCount--
+	}
+	p.capacity = newCap
+	p.fuzzer.activeCap += p.capacity
+	if p.capacity != 0 {
+		p.fuzzer.activeCount++
+	}
+	doLog("Update capacity", "peer", p.node.ID(), "origin", origin, "cap", newCap, "origintotal", originTotal, "total", p.fuzzer.activeCap, "requested", requested)
+}
+
+func (p *clientPeer) Disconnect() {
+	origin, originTotal := p.capacity, p.fuzzer.activeCap
+	p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p)
+	p.fuzzer.activeCap -= p.capacity
+	if p.capacity != 0 {
+		p.fuzzer.activeCount--
+	}
+	p.capacity = 0
+	p.balance = nil
+	doLog("Disconnect", "peer", p.node.ID(), "origin", origin, "origintotal", originTotal, "total", p.fuzzer.activeCap)
+}
+
+func newFuzzer(input []byte) *fuzzer {
+	f := &fuzzer{
+		input: bytes.NewReader(input),
+	}
+	for i := range f.peers {
+		f.peers[i] = &clientPeer{
+			fuzzer:  f,
+			node:    enode.SignNull(new(enr.Record), enode.ID{byte(i)}),
+			freeID:  string([]byte{byte(i)}),
+			timeout: f.randomDelay(),
+		}
+	}
+	return f
+}
+
+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) randomByte() byte {
+	d := f.read(1)
+	return d[0]
+}
+
+func (f *fuzzer) randomBool() bool {
+	d := f.read(1)
+	return d[0]&1 == 1
+}
+
+func (f *fuzzer) randomInt(max int) int {
+	if max == 0 {
+		return 0
+	}
+	if max <= 256 {
+		return int(f.randomByte()) % max
+	}
+	var a uint16
+	if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil {
+		f.exhausted = true
+	}
+	return int(a % uint16(max))
+}
+
+func (f *fuzzer) randomTokenAmount(signed bool) int64 {
+	x := uint64(f.randomInt(65000))
+	x = x * x * x * x
+
+	if signed && (x&1) == 1 {
+		if x <= math.MaxInt64 {
+			return -int64(x)
+		}
+		return math.MinInt64
+	}
+	if x <= math.MaxInt64 {
+		return int64(x)
+	}
+	return math.MaxInt64
+}
+
+func (f *fuzzer) randomDelay() time.Duration {
+	delay := f.randomByte()
+	if delay < 128 {
+		return time.Duration(delay) * time.Second
+	}
+	return 0
+}
+
+func (f *fuzzer) randomFactors() vfs.PriceFactors {
+	return vfs.PriceFactors{
+		TimeFactor:     float64(f.randomByte()) / 25500,
+		CapacityFactor: float64(f.randomByte()) / 255,
+		RequestFactor:  float64(f.randomByte()) / 255,
+	}
+}
+
+func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance, id enode.ID) {
+	switch f.randomInt(3) {
+	case 0:
+		cost := uint64(f.randomTokenAmount(false))
+		balance.RequestServed(cost)
+		doLog("Serve request cost", "id", id, "amount", cost)
+	case 1:
+		posFactor, negFactor := f.randomFactors(), f.randomFactors()
+		balance.SetPriceFactors(posFactor, negFactor)
+		doLog("Set price factor", "pos", posFactor, "neg", negFactor)
+	case 2:
+		balance.GetBalance()
+		balance.GetRawBalance()
+		balance.GetPriceFactors()
+	}
+}
+
+func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator, id enode.ID) {
+	switch f.randomInt(3) {
+	case 0:
+		amount := f.randomTokenAmount(true)
+		balance.AddBalance(amount)
+		doLog("Add balance", "id", id, "amount", amount)
+	case 1:
+		pos, neg := uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false))
+		balance.SetBalance(pos, neg)
+		doLog("Set balance", "id", id, "pos", pos, "neg", neg)
+	case 2:
+		balance.GetBalance()
+		balance.GetRawBalance()
+		balance.GetPriceFactors()
+	}
+}
+
+func FuzzClientPool(input []byte) int {
+	if len(input) > 10000 {
+		return -1
+	}
+	f := newFuzzer(input)
+	if f.exhausted {
+		return 0
+	}
+	clock := &mclock.Simulated{}
+	db := memorydb.New()
+	pool := vfs.NewClientPool(db, 10, f.randomDelay(), clock, func() bool { return true })
+	pool.Start()
+	defer pool.Stop()
+
+	count := 0
+	for !f.exhausted && count < 1000 {
+		count++
+		switch f.randomInt(11) {
+		case 0:
+			i := int(f.randomByte())
+			f.peers[i].balance = pool.Register(f.peers[i])
+			doLog("Register peer", "id", f.peers[i].node.ID())
+		case 1:
+			i := int(f.randomByte())
+			f.peers[i].Disconnect()
+			doLog("Disconnect peer", "id", f.peers[i].node.ID())
+		case 2:
+			f.maxCount = uint64(f.randomByte())
+			f.maxCap = uint64(f.randomByte())
+			f.maxCap *= f.maxCap
+
+			count, cap := pool.Limits()
+			pool.SetLimits(f.maxCount, f.maxCap)
+			doLog("Set limits", "maxcount", f.maxCount, "maxcap", f.maxCap, "origincount", count, "oricap", cap)
+		case 3:
+			bias := f.randomDelay()
+			pool.SetConnectedBias(f.randomDelay())
+			doLog("Set connection bias", "bias", bias)
+		case 4:
+			pos, neg := f.randomFactors(), f.randomFactors()
+			pool.SetDefaultFactors(pos, neg)
+			doLog("Set default factors", "pos", pos, "neg", neg)
+		case 5:
+			pos, neg := uint64(f.randomInt(50000)), uint64(f.randomInt(50000))
+			pool.SetExpirationTCs(pos, neg)
+			doLog("Set expiration constants", "pos", pos, "neg", neg)
+		case 6:
+			var (
+				index     = f.randomByte()
+				reqCap    = uint64(f.randomByte())
+				bias      = f.randomDelay()
+				requested = f.randomBool()
+			)
+			if _, err := pool.SetCapacity(f.peers[index].node, reqCap, bias, requested); err == vfs.ErrCantFindMaximum {
+				panic(nil)
+			}
+			doLog("Set capacity", "id", f.peers[index].node.ID(), "reqcap", reqCap, "bias", bias, "requested", requested)
+		case 7:
+			index := f.randomByte()
+			if balance := f.peers[index].balance; balance != nil {
+				f.connectedBalanceOp(balance, f.peers[index].node.ID())
+			}
+		case 8:
+			index := f.randomByte()
+			pool.BalanceOperation(f.peers[index].node.ID(), f.peers[index].freeID, func(balance vfs.AtomicBalanceOperator) {
+				count := f.randomInt(4)
+				for i := 0; i < count; i++ {
+					f.atomicBalanceOp(balance, f.peers[index].node.ID())
+				}
+			})
+		case 9:
+			pool.TotalTokenAmount()
+			pool.GetExpirationTCs()
+			pool.Active()
+			pool.Limits()
+			pool.GetPosBalanceIDs(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].node.ID(), f.randomInt(100))
+		case 10:
+			req := vflux.CapacityQueryReq{
+				Bias:      uint64(f.randomByte()),
+				AddTokens: make([]vflux.IntOrInf, f.randomInt(vflux.CapacityQueryMaxLen+1)),
+			}
+			for i := range req.AddTokens {
+				v := vflux.IntOrInf{Type: uint8(f.randomInt(4))}
+				if v.Type < 2 {
+					v.Value = *big.NewInt(f.randomTokenAmount(false))
+				}
+				req.AddTokens[i] = v
+			}
+			reqEnc, err := rlp.EncodeToBytes(&req)
+			if err != nil {
+				panic(err)
+			}
+			p := int(f.randomByte())
+			if p < len(reqEnc) {
+				reqEnc[p] = f.randomByte()
+			}
+			pool.Handle(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, vflux.CapacityQueryName, reqEnc)
+		}
+
+		for _, peer := range f.disconnectList {
+			pool.Unregister(peer)
+			doLog("Unregister peer", "id", peer.node.ID())
+		}
+		f.disconnectList = nil
+		if d := f.randomDelay(); d > 0 {
+			clock.Run(d)
+		}
+		doLog("Clientpool stats in fuzzer", "count", f.activeCap, "maxcount", f.maxCount, "cap", f.activeCap, "maxcap", f.maxCap)
+		activeCount, activeCap := pool.Active()
+		doLog("Clientpool stats in pool", "count", activeCount, "cap", activeCap)
+		if activeCount != f.activeCount || activeCap != f.activeCap {
+			panic(nil)
+		}
+		if f.activeCount > f.maxCount || f.activeCap > f.maxCap {
+			panic(nil)
+		}
+	}
+	return 0
+}
diff --git a/tests/fuzzers/vflux/debug/main.go b/tests/fuzzers/vflux/debug/main.go
new file mode 100644
index 0000000000..1d4a5ff19c
--- /dev/null
+++ b/tests/fuzzers/vflux/debug/main.go
@@ -0,0 +1,44 @@
+// 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 .
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/tests/fuzzers/vflux"
+)
+
+func main() {
+	log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
+
+	if len(os.Args) != 2 {
+		fmt.Fprintf(os.Stderr, "Usage: debug \n")
+		fmt.Fprintf(os.Stderr, "Example\n")
+		fmt.Fprintf(os.Stderr, "	$ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n")
+		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)
+	}
+	vflux.FuzzClientPool(data)
+}
diff --git a/tests/fuzzers/whisperv6/corpus/009c5adfa4fd685caef58e1ce932fa7fb209730a b/tests/fuzzers/whisperv6/corpus/009c5adfa4fd685caef58e1ce932fa7fb209730a
deleted file mode 100644
index af2f082673..0000000000
Binary files a/tests/fuzzers/whisperv6/corpus/009c5adfa4fd685caef58e1ce932fa7fb209730a and /dev/null differ
diff --git a/tests/fuzzers/whisperv6/whisper-fuzzer.go b/tests/fuzzers/whisperv6/whisper-fuzzer.go
deleted file mode 100644
index 379e4224fd..0000000000
--- a/tests/fuzzers/whisperv6/whisper-fuzzer.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2019 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package whisperv6
-
-import (
-	"bytes"
-
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/rlp"
-	"github.com/ethereum/go-ethereum/whisper/whisperv6"
-)
-
-type MessageParams struct {
-	Topic    whisperv6.TopicType
-	WorkTime uint32
-	TTL      uint32
-	KeySym   []byte
-	Payload  []byte
-}
-
-//export fuzzer_entry
-func Fuzz(input []byte) int {
-
-	var paramsDecoded MessageParams
-	err := rlp.DecodeBytes(input, ¶msDecoded)
-	if err != nil {
-		return 0
-	}
-	var params whisperv6.MessageParams
-	params.KeySym = make([]byte, 32)
-	if len(paramsDecoded.KeySym) <= 32 {
-		copy(params.KeySym, paramsDecoded.KeySym)
-	}
-	if input[0] == 255 {
-		params.PoW = 0.01
-		params.WorkTime = 1
-	} else {
-		params.PoW = 0
-		params.WorkTime = 0
-	}
-	params.TTL = paramsDecoded.TTL
-	params.Payload = paramsDecoded.Payload
-	text := make([]byte, 0, 512)
-	text = append(text, params.Payload...)
-	params.Topic = paramsDecoded.Topic
-	params.Src, err = crypto.GenerateKey()
-	if err != nil {
-		return 0
-	}
-	msg, err := whisperv6.NewSentMessage(¶ms)
-	if err != nil {
-		panic(err)
-		//return
-	}
-	env, err := msg.Wrap(¶ms)
-	if err != nil {
-		panic(err)
-	}
-	decrypted, err := env.OpenSymmetric(params.KeySym)
-	if err != nil {
-		panic(err)
-	}
-	if !decrypted.ValidateAndParse() {
-		panic("ValidateAndParse failed")
-	}
-	if !bytes.Equal(text, decrypted.Payload) {
-		panic("text != decrypted.Payload")
-	}
-	if len(decrypted.Signature) != 65 {
-		panic("Unexpected signature length")
-	}
-	if !whisperv6.IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) {
-		panic("Unexpected public key")
-	}
-	return 0
-}
diff --git a/tests/gen_stenv.go b/tests/gen_stenv.go
index 1d4baf2fd7..bfecc145b4 100644
--- a/tests/gen_stenv.go
+++ b/tests/gen_stenv.go
@@ -13,6 +13,7 @@ import (
 
 var _ = (*stEnvMarshaling)(nil)
 
+// MarshalJSON marshals as JSON.
 func (s stEnv) MarshalJSON() ([]byte, error) {
 	type stEnv struct {
 		Coinbase   common.UnprefixedAddress `json:"currentCoinbase"   gencodec:"required"`
@@ -30,6 +31,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&enc)
 }
 
+// UnmarshalJSON unmarshals from JSON.
 func (s *stEnv) UnmarshalJSON(input []byte) error {
 	type stEnv struct {
 		Coinbase   *common.UnprefixedAddress `json:"currentCoinbase"   gencodec:"required"`
diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go
index 451ffcbf43..2670f4f9c8 100644
--- a/tests/gen_sttransaction.go
+++ b/tests/gen_sttransaction.go
@@ -8,25 +8,29 @@ import (
 
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/core/types"
 )
 
 var _ = (*stTransactionMarshaling)(nil)
 
+// MarshalJSON marshals as JSON.
 func (s stTransaction) MarshalJSON() ([]byte, error) {
 	type stTransaction struct {
-		GasPrice   *math.HexOrDecimal256 `json:"gasPrice"`
-		Nonce      math.HexOrDecimal64   `json:"nonce"`
-		To         string                `json:"to"`
-		Data       []string              `json:"data"`
-		GasLimit   []math.HexOrDecimal64 `json:"gasLimit"`
-		Value      []string              `json:"value"`
-		PrivateKey hexutil.Bytes         `json:"secretKey"`
+		GasPrice    *math.HexOrDecimal256 `json:"gasPrice"`
+		Nonce       math.HexOrDecimal64   `json:"nonce"`
+		To          string                `json:"to"`
+		Data        []string              `json:"data"`
+		AccessLists []*types.AccessList   `json:"accessLists,omitempty"`
+		GasLimit    []math.HexOrDecimal64 `json:"gasLimit"`
+		Value       []string              `json:"value"`
+		PrivateKey  hexutil.Bytes         `json:"secretKey"`
 	}
 	var enc stTransaction
 	enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
 	enc.Nonce = math.HexOrDecimal64(s.Nonce)
 	enc.To = s.To
 	enc.Data = s.Data
+	enc.AccessLists = s.AccessLists
 	if s.GasLimit != nil {
 		enc.GasLimit = make([]math.HexOrDecimal64, len(s.GasLimit))
 		for k, v := range s.GasLimit {
@@ -38,15 +42,17 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&enc)
 }
 
+// UnmarshalJSON unmarshals from JSON.
 func (s *stTransaction) UnmarshalJSON(input []byte) error {
 	type stTransaction struct {
-		GasPrice   *math.HexOrDecimal256 `json:"gasPrice"`
-		Nonce      *math.HexOrDecimal64  `json:"nonce"`
-		To         *string               `json:"to"`
-		Data       []string              `json:"data"`
-		GasLimit   []math.HexOrDecimal64 `json:"gasLimit"`
-		Value      []string              `json:"value"`
-		PrivateKey *hexutil.Bytes        `json:"secretKey"`
+		GasPrice    *math.HexOrDecimal256 `json:"gasPrice"`
+		Nonce       *math.HexOrDecimal64  `json:"nonce"`
+		To          *string               `json:"to"`
+		Data        []string              `json:"data"`
+		AccessLists []*types.AccessList   `json:"accessLists,omitempty"`
+		GasLimit    []math.HexOrDecimal64 `json:"gasLimit"`
+		Value       []string              `json:"value"`
+		PrivateKey  *hexutil.Bytes        `json:"secretKey"`
 	}
 	var dec stTransaction
 	if err := json.Unmarshal(input, &dec); err != nil {
@@ -64,6 +70,9 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
 	if dec.Data != nil {
 		s.Data = dec.Data
 	}
+	if dec.AccessLists != nil {
+		s.AccessLists = dec.AccessLists
+	}
 	if dec.GasLimit != nil {
 		s.GasLimit = make([]uint64, len(dec.GasLimit))
 		for k, v := range dec.GasLimit {
diff --git a/tests/gen_vmexec.go b/tests/gen_vmexec.go
index a5f01cf456..2fe155152d 100644
--- a/tests/gen_vmexec.go
+++ b/tests/gen_vmexec.go
@@ -14,6 +14,7 @@ import (
 
 var _ = (*vmExecMarshaling)(nil)
 
+// MarshalJSON marshals as JSON.
 func (v vmExec) MarshalJSON() ([]byte, error) {
 	type vmExec struct {
 		Address  common.UnprefixedAddress `json:"address"  gencodec:"required"`
@@ -37,6 +38,7 @@ func (v vmExec) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&enc)
 }
 
+// UnmarshalJSON unmarshals from JSON.
 func (v *vmExec) UnmarshalJSON(input []byte) error {
 	type vmExec struct {
 		Address  *common.UnprefixedAddress `json:"address"  gencodec:"required"`
diff --git a/tests/init.go b/tests/init.go
index a18e12bdd2..67f706eb50 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -19,6 +19,7 @@ package tests
 import (
 	"fmt"
 	"math/big"
+	"sort"
 
 	"github.com/ethereum/go-ethereum/params"
 )
@@ -140,6 +141,42 @@ var Forks = map[string]*params.ChainConfig{
 		PetersburgBlock:     big.NewInt(0),
 		IstanbulBlock:       big.NewInt(5),
 	},
+	"YOLOv3": {
+		ChainID:             big.NewInt(1),
+		HomesteadBlock:      big.NewInt(0),
+		EIP150Block:         big.NewInt(0),
+		EIP155Block:         big.NewInt(0),
+		EIP158Block:         big.NewInt(0),
+		ByzantiumBlock:      big.NewInt(0),
+		ConstantinopleBlock: big.NewInt(0),
+		PetersburgBlock:     big.NewInt(0),
+		IstanbulBlock:       big.NewInt(0),
+		YoloV3Block:         big.NewInt(0),
+	},
+	// This specification is subject to change, but is for now identical to YOLOv3
+	// for cross-client testing purposes
+	"Berlin": {
+		ChainID:             big.NewInt(1),
+		HomesteadBlock:      big.NewInt(0),
+		EIP150Block:         big.NewInt(0),
+		EIP155Block:         big.NewInt(0),
+		EIP158Block:         big.NewInt(0),
+		ByzantiumBlock:      big.NewInt(0),
+		ConstantinopleBlock: big.NewInt(0),
+		PetersburgBlock:     big.NewInt(0),
+		IstanbulBlock:       big.NewInt(0),
+		BerlinBlock:         big.NewInt(0),
+	},
+}
+
+// Returns the set of defined fork names
+func AvailableForks() []string {
+	var availableForks []string
+	for k := range Forks {
+		availableForks = append(availableForks, k)
+	}
+	sort.Strings(availableForks)
+	return availableForks
 }
 
 // UnsupportedForkError is returned when a test requests a fork that isn't implemented.
diff --git a/tests/state_test.go b/tests/state_test.go
index c0a90b3a42..b77a898c21 100644
--- a/tests/state_test.go
+++ b/tests/state_test.go
@@ -37,13 +37,15 @@ func TestState(t *testing.T) {
 	st.slow(`^stQuadraticComplexityTest/`)
 	st.slow(`^stStaticCall/static_Call50000`)
 	st.slow(`^stStaticCall/static_Return50000`)
-	st.slow(`^stStaticCall/static_Call1MB`)
 	st.slow(`^stSystemOperationsTest/CallRecursiveBomb`)
 	st.slow(`^stTransactionTest/Opcodes_TransactionInit`)
 
 	// Very time consuming
 	st.skipLoad(`^stTimeConsuming/`)
 
+	// Uses 1GB RAM per tested fork
+	st.skipLoad(`^stStaticCall/static_Call1MB`)
+
 	// Broken tests:
 	// Expected failures:
 	//st.fails(`^stRevertTest/RevertPrecompiledTouch(_storage)?\.json/Byzantium/0`, "bug in test")
@@ -66,13 +68,16 @@ func TestState(t *testing.T) {
 
 				t.Run(key+"/trie", func(t *testing.T) {
 					withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error {
-						_, err := test.Run(subtest, vmconfig, false)
+						_, _, err := test.Run(subtest, vmconfig, false)
 						return st.checkFailure(t, name+"/trie", err)
 					})
 				})
 				t.Run(key+"/snap", func(t *testing.T) {
 					withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error {
-						_, err := test.Run(subtest, vmconfig, true)
+						snaps, statedb, err := test.Run(subtest, vmconfig, true)
+						if _, err := snaps.Journal(statedb.IntermediateRoot(false)); err != nil {
+							return err
+						}
 						return st.checkFailure(t, name+"/snap", err)
 					})
 				})
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 5e5b96d527..46834de6da 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -24,14 +24,13 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/ethereum/go-ethereum/core/state/snapshot"
-
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/state/snapshot"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -96,13 +95,14 @@ type stEnvMarshaling struct {
 //go:generate gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go
 
 type stTransaction struct {
-	GasPrice   *big.Int `json:"gasPrice"`
-	Nonce      uint64   `json:"nonce"`
-	To         string   `json:"to"`
-	Data       []string `json:"data"`
-	GasLimit   []uint64 `json:"gasLimit"`
-	Value      []string `json:"value"`
-	PrivateKey []byte   `json:"secretKey"`
+	GasPrice    *big.Int            `json:"gasPrice"`
+	Nonce       uint64              `json:"nonce"`
+	To          string              `json:"to"`
+	Data        []string            `json:"data"`
+	AccessLists []*types.AccessList `json:"accessLists,omitempty"`
+	GasLimit    []uint64            `json:"gasLimit"`
+	Value       []string            `json:"value"`
+	PrivateKey  []byte              `json:"secretKey"`
 }
 
 type stTransactionMarshaling struct {
@@ -112,11 +112,11 @@ type stTransactionMarshaling struct {
 	PrivateKey hexutil.Bytes
 }
 
-// getVMConfig takes a fork definition and returns a chain config.
+// GetChainConfig takes a fork definition and returns a chain config.
 // The fork definition can be
 // - a plain forkname, e.g. `Byzantium`,
 // - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`.
-func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) {
+func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) {
 	var (
 		splitForks            = strings.Split(forkString, "+")
 		ok                    bool
@@ -129,6 +129,9 @@ func getVMConfig(forkString string) (baseConfig *params.ChainConfig, eips []int,
 		if eipNum, err := strconv.Atoi(eip); err != nil {
 			return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum)
 		} else {
+			if !vm.ValidEip(eipNum) {
+				return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum)
+			}
 			eips = append(eips, eipNum)
 		}
 	}
@@ -147,48 +150,53 @@ func (t *StateTest) Subtests() []StateSubtest {
 }
 
 // Run executes a specific subtest and verifies the post-state and logs
-func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*state.StateDB, error) {
-	statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter)
+func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, error) {
+	snaps, statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter)
 	if err != nil {
-		return statedb, err
+		return snaps, statedb, err
 	}
 	post := t.json.Post[subtest.Fork][subtest.Index]
 	// N.B: We need to do this in a two-step process, because the first Commit takes care
 	// of suicides, and we need to touch the coinbase _after_ it has potentially suicided.
 	if root != common.Hash(post.Root) {
-		return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
+		return snaps, statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
 	}
 	if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) {
-		return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
+		return snaps, statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
 	}
-	return statedb, nil
+	return snaps, statedb, nil
 }
 
 // RunNoVerify runs a specific subtest and returns the statedb and post-state root
-func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*state.StateDB, common.Hash, error) {
-	config, eips, err := getVMConfig(subtest.Fork)
+func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, common.Hash, error) {
+	config, eips, err := GetChainConfig(subtest.Fork)
 	if err != nil {
-		return nil, common.Hash{}, UnsupportedForkError{subtest.Fork}
+		return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork}
 	}
 	vmconfig.ExtraEips = eips
 	block := t.genesis(config).ToBlock(nil)
-	statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter)
+	snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter)
 
 	post := t.json.Post[subtest.Fork][subtest.Index]
 	msg, err := t.json.Tx.toMessage(post)
 	if err != nil {
-		return nil, common.Hash{}, err
+		return nil, nil, common.Hash{}, err
 	}
-	context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase)
+
+	// Prepare the EVM.
+	txContext := core.NewEVMTxContext(msg)
+	context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
 	context.GetHash = vmTestBlockHash
-	evm := vm.NewEVM(context, statedb, config, vmconfig)
+	evm := vm.NewEVM(context, txContext, statedb, config, vmconfig)
 
+	// Execute the message.
+	snapshot := statedb.Snapshot()
 	gaspool := new(core.GasPool)
 	gaspool.AddGas(block.GasLimit())
-	snapshot := statedb.Snapshot()
-	if _, _, _, err := core.ApplyMessage(evm, msg, gaspool); err != nil {
+	if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil {
 		statedb.RevertToSnapshot(snapshot)
 	}
+
 	// Commit block
 	statedb.Commit(config.IsEIP158(block.Number()))
 	// Add 0-value mining reward. This only makes a difference in the cases
@@ -199,14 +207,14 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
 	statedb.AddBalance(block.Coinbase(), new(big.Int))
 	// And _now_ get the state root
 	root := statedb.IntermediateRoot(config.IsEIP158(block.Number()))
-	return statedb, root, nil
+	return snaps, statedb, root, nil
 }
 
 func (t *StateTest) gasLimit(subtest StateSubtest) uint64 {
 	return t.json.Tx.GasLimit[t.json.Post[subtest.Fork][subtest.Index].Indexes.Gas]
 }
 
-func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) *state.StateDB {
+func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) (*snapshot.Tree, *state.StateDB) {
 	sdb := state.NewDatabase(db)
 	statedb, _ := state.New(common.Hash{}, sdb, nil)
 	for addr, a := range accounts {
@@ -222,10 +230,10 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
 
 	var snaps *snapshot.Tree
 	if snapshotter {
-		snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false)
+		snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false)
 	}
 	statedb, _ = state.New(root, sdb, snaps)
-	return statedb
+	return snaps, statedb
 }
 
 func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis {
@@ -285,8 +293,11 @@ func (tx *stTransaction) toMessage(ps stPostState) (core.Message, error) {
 	if err != nil {
 		return nil, fmt.Errorf("invalid tx data %q", dataHex)
 	}
-
-	msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, true)
+	var accessList types.AccessList
+	if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil {
+		accessList = *tx.AccessLists[ps.Indexes.Data]
+	}
+	msg := types.NewMessage(from, to, tx.Nonce, value, gasLimit, tx.GasPrice, data, accessList, true)
 	return msg, nil
 }
 
diff --git a/tests/testdata b/tests/testdata
index 7497b116a0..c600d7795a 160000
--- a/tests/testdata
+++ b/tests/testdata
@@ -1 +1 @@
-Subproject commit 7497b116a019beb26215cbea4028df068dea06be
+Subproject commit c600d7795aa2ea57a9c856fc79f72fc05b542124
diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go
index aea90535c3..82ee01de15 100644
--- a/tests/transaction_test_util.go
+++ b/tests/transaction_test_util.go
@@ -55,7 +55,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error {
 			return nil, nil, err
 		}
 		// Intrinsic gas
-		requiredGas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, isHomestead, isIstanbul)
+		requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul)
 		if err != nil {
 			return nil, nil, err
 		}
diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go
index 9acbe59f44..418cc67168 100644
--- a/tests/vm_test_util.go
+++ b/tests/vm_test_util.go
@@ -79,7 +79,15 @@ type vmExecMarshaling struct {
 }
 
 func (t *VMTest) Run(vmconfig vm.Config, snapshotter bool) error {
-	statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter)
+	snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter)
+	if snapshotter {
+		preRoot := statedb.IntermediateRoot(false)
+		defer func() {
+			if _, err := snaps.Journal(preRoot); err != nil {
+				panic(err)
+			}
+		}()
+	}
 	ret, gasRemaining, err := t.exec(statedb, vmconfig)
 
 	if t.json.GasRemaining == nil {
@@ -130,20 +138,22 @@ func (t *VMTest) newEVM(statedb *state.StateDB, vmconfig vm.Config) *vm.EVM {
 		return core.CanTransfer(db, address, amount)
 	}
 	transfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {}
-	context := vm.Context{
+	txContext := vm.TxContext{
+		Origin:   t.json.Exec.Origin,
+		GasPrice: t.json.Exec.GasPrice,
+	}
+	context := vm.BlockContext{
 		CanTransfer: canTransfer,
 		Transfer:    transfer,
 		GetHash:     vmTestBlockHash,
-		Origin:      t.json.Exec.Origin,
 		Coinbase:    t.json.Env.Coinbase,
 		BlockNumber: new(big.Int).SetUint64(t.json.Env.Number),
 		Time:        new(big.Int).SetUint64(t.json.Env.Timestamp),
 		GasLimit:    t.json.Env.GasLimit,
 		Difficulty:  t.json.Env.Difficulty,
-		GasPrice:    t.json.Exec.GasPrice,
 	}
 	vmconfig.NoRecursion = true
-	return vm.NewEVM(context, statedb, params.MainnetChainConfig, vmconfig)
+	return vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vmconfig)
 }
 
 func vmTestBlockHash(n uint64) common.Hash {
diff --git a/trie/committer.go b/trie/committer.go
index eacefdff11..ce4065f5fd 100644
--- a/trie/committer.go
+++ b/trie/committer.go
@@ -22,31 +22,30 @@ import (
 	"sync"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/crypto"
 	"golang.org/x/crypto/sha3"
 )
 
 // leafChanSize is the size of the leafCh. It's a pretty arbitrary number, to allow
-// some paralellism but not incur too much memory overhead.
+// some parallelism but not incur too much memory overhead.
 const leafChanSize = 200
 
 // leaf represents a trie leaf value
 type leaf struct {
-	size   int         // size of the rlp data (estimate)
-	hash   common.Hash // hash of rlp data
-	node   node        // the node to commit
-	vnodes bool        // set to true if the node (possibly) contains a valueNode
+	size int         // size of the rlp data (estimate)
+	hash common.Hash // hash of rlp data
+	node node        // the node to commit
 }
 
 // committer is a type used for the trie Commit operation. A committer has some
 // internal preallocated temp space, and also a callback that is invoked when
 // leaves are committed. The leafs are passed through the `leafCh`,  to allow
-// some level of paralellism.
+// some level of parallelism.
 // By 'some level' of parallelism, it's still the case that all leaves will be
 // processed sequentially - onleaf will never be called in parallel or out of order.
 type committer struct {
 	tmp sliceBuffer
-	sha keccakState
+	sha crypto.KeccakState
 
 	onleaf LeafCallback
 	leafCh chan *leaf
@@ -57,7 +56,7 @@ var committerPool = sync.Pool{
 	New: func() interface{} {
 		return &committer{
 			tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
-			sha: sha3.NewLegacyKeccak256().(keccakState),
+			sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
 		}
 	},
 }
@@ -73,18 +72,12 @@ func returnCommitterToPool(h *committer) {
 	committerPool.Put(h)
 }
 
-// commitNeeded returns 'false' if the given node is already in sync with db
-func (c *committer) commitNeeded(n node) bool {
-	hash, dirty := n.cache()
-	return hash == nil || dirty
-}
-
 // commit collapses a node down into a hash node and inserts it into the database
 func (c *committer) Commit(n node, db *Database) (hashNode, error) {
 	if db == nil {
 		return nil, errors.New("no db provided")
 	}
-	h, err := c.commit(n, db, true)
+	h, err := c.commit(n, db)
 	if err != nil {
 		return nil, err
 	}
@@ -92,7 +85,7 @@ func (c *committer) Commit(n node, db *Database) (hashNode, error) {
 }
 
 // commit collapses a node down into a hash node and inserts it into the database
-func (c *committer) commit(n node, db *Database, force bool) (node, error) {
+func (c *committer) commit(n node, db *Database) (node, error) {
 	// if this path is clean, use available cached data
 	hash, dirty := n.cache()
 	if hash != nil && !dirty {
@@ -103,89 +96,90 @@ func (c *committer) commit(n node, db *Database, force bool) (node, error) {
 	case *shortNode:
 		// Commit child
 		collapsed := cn.copy()
-		if _, ok := cn.Val.(valueNode); !ok {
-			if childV, err := c.commit(cn.Val, db, false); err != nil {
+
+		// If the child is fullnode, recursively commit.
+		// Otherwise it can only be hashNode or valueNode.
+		if _, ok := cn.Val.(*fullNode); ok {
+			childV, err := c.commit(cn.Val, db)
+			if err != nil {
 				return nil, err
-			} else {
-				collapsed.Val = childV
 			}
+			collapsed.Val = childV
 		}
 		// The key needs to be copied, since we're delivering it to database
 		collapsed.Key = hexToCompact(cn.Key)
-		hashedNode := c.store(collapsed, db, force, true)
+		hashedNode := c.store(collapsed, db)
 		if hn, ok := hashedNode.(hashNode); ok {
 			return hn, nil
-		} else {
-			return collapsed, nil
 		}
+		return collapsed, nil
 	case *fullNode:
-		hashedKids, hasVnodes, err := c.commitChildren(cn, db, force)
+		hashedKids, err := c.commitChildren(cn, db)
 		if err != nil {
 			return nil, err
 		}
 		collapsed := cn.copy()
 		collapsed.Children = hashedKids
 
-		hashedNode := c.store(collapsed, db, force, hasVnodes)
+		hashedNode := c.store(collapsed, db)
 		if hn, ok := hashedNode.(hashNode); ok {
 			return hn, nil
-		} else {
-			return collapsed, nil
 		}
-	case valueNode:
-		return c.store(cn, db, force, false), nil
-	// hashnodes aren't stored
+		return collapsed, nil
 	case hashNode:
 		return cn, nil
+	default:
+		// nil, valuenode shouldn't be committed
+		panic(fmt.Sprintf("%T: invalid node: %v", n, n))
 	}
-	return hash, nil
 }
 
 // commitChildren commits the children of the given fullnode
-func (c *committer) commitChildren(n *fullNode, db *Database, force bool) ([17]node, bool, error) {
+func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, error) {
 	var children [17]node
-	var hasValueNodeChildren = false
-	for i, child := range n.Children {
+	for i := 0; i < 16; i++ {
+		child := n.Children[i]
 		if child == nil {
 			continue
 		}
-		hnode, err := c.commit(child, db, false)
-		if err != nil {
-			return children, false, err
+		// If it's the hashed child, save the hash value directly.
+		// Note: it's impossible that the child in range [0, 15]
+		// is a valuenode.
+		if hn, ok := child.(hashNode); ok {
+			children[i] = hn
+			continue
 		}
-		children[i] = hnode
-		if _, ok := hnode.(valueNode); ok {
-			hasValueNodeChildren = true
+		// Commit the child recursively and store the "hashed" value.
+		// Note the returned node can be some embedded nodes, so it's
+		// possible the type is not hashnode.
+		hashed, err := c.commit(child, db)
+		if err != nil {
+			return children, err
 		}
+		children[i] = hashed
 	}
-	return children, hasValueNodeChildren, nil
+	// For the 17th child, it's possible the type is valuenode.
+	if n.Children[16] != nil {
+		children[16] = n.Children[16]
+	}
+	return children, nil
 }
 
 // store hashes the node n and if we have a storage layer specified, it writes
 // the key/value pair to it and tracks any node->child references as well as any
 // node->external trie references.
-func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren bool) node {
+func (c *committer) store(n node, db *Database) node {
 	// Larger nodes are replaced by their hash and stored in the database.
 	var (
 		hash, _ = n.cache()
 		size    int
 	)
 	if hash == nil {
-		if vn, ok := n.(valueNode); ok {
-			c.tmp.Reset()
-			if err := rlp.Encode(&c.tmp, vn); err != nil {
-				panic("encode error: " + err.Error())
-			}
-			size = len(c.tmp)
-			if size < 32 && !force {
-				return n // Nodes smaller than 32 bytes are stored inside their parent
-			}
-			hash = c.makeHashNode(c.tmp)
-		} else {
-			// This was not generated - must be a small node stored in the parent
-			// No need to do anything here
-			return n
-		}
+		// This was not generated - must be a small node stored in the parent.
+		// In theory we should apply the leafCall here if it's not nil(embedded
+		// node usually contains value). But small value(less than 32bytes) is
+		// not our target.
+		return n
 	} else {
 		// We have the hash already, estimate the RLP encoding-size of the node.
 		// The size is used for mem tracking, does not need to be exact
@@ -195,10 +189,9 @@ func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren boo
 	// The leaf channel will be active only when there an active leaf-callback
 	if c.leafCh != nil {
 		c.leafCh <- &leaf{
-			size:   size,
-			hash:   common.BytesToHash(hash),
-			node:   n,
-			vnodes: hasVnodeChildren,
+			size: size,
+			hash: common.BytesToHash(hash),
+			node: n,
 		}
 	} else if db != nil {
 		// No leaf-callback used, but there's still a database. Do serial
@@ -210,30 +203,30 @@ func (c *committer) store(n node, db *Database, force bool, hasVnodeChildren boo
 	return hash
 }
 
-// commitLoop does the actual insert + leaf callback for nodes
+// commitLoop does the actual insert + leaf callback for nodes.
 func (c *committer) commitLoop(db *Database) {
 	for item := range c.leafCh {
 		var (
-			hash      = item.hash
-			size      = item.size
-			n         = item.node
-			hasVnodes = item.vnodes
+			hash = item.hash
+			size = item.size
+			n    = item.node
 		)
 		// We are pooling the trie nodes into an intermediate memory cache
 		db.lock.Lock()
 		db.insert(hash, size, n)
 		db.lock.Unlock()
-		if c.onleaf != nil && hasVnodes {
+
+		if c.onleaf != nil {
 			switch n := n.(type) {
 			case *shortNode:
 				if child, ok := n.Val.(valueNode); ok {
-					c.onleaf(child, hash)
+					c.onleaf(nil, nil, child, hash)
 				}
 			case *fullNode:
-				for i := 0; i < 16; i++ {
-					if child, ok := n.Children[i].(valueNode); ok {
-						c.onleaf(child, hash)
-					}
+				// For children in range [0, 15], it's impossible
+				// to contain valuenode. Only check the 17th child.
+				if n.Children[16] != nil {
+					c.onleaf(nil, nil, n.Children[16].(valueNode), hash)
 				}
 			}
 		}
@@ -264,7 +257,7 @@ func estimateSize(n node) int {
 			if child := n.Children[i]; child != nil {
 				s += estimateSize(child)
 			} else {
-				s += 1
+				s++
 			}
 		}
 		return s
@@ -274,6 +267,5 @@ func estimateSize(n node) int {
 		return 1 + len(n)
 	default:
 		panic(fmt.Sprintf("node type %T", n))
-
 	}
 }
diff --git a/trie/database.go b/trie/database.go
index 5b673938f0..b18665770e 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -21,11 +21,13 @@ import (
 	"fmt"
 	"io"
 	"reflect"
+	"runtime"
 	"sync"
 	"time"
 
 	"github.com/VictoriaMetrics/fastcache"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
@@ -56,12 +58,6 @@ var (
 	memcacheCommitSizeMeter  = metrics.NewRegisteredMeter("trie/memcache/commit/size", nil)
 )
 
-// secureKeyPrefix is the database key prefix used to store trie node preimages.
-var secureKeyPrefix = []byte("secure-key-")
-
-// secureKeyLength is the length of the above prefix + 32byte hash.
-const secureKeyLength = 11 + 32
-
 // Database is an intermediate write layer between the trie data structures and
 // the disk database. The aim is to accumulate trie writes in-memory and only
 // periodically flush a couple tries to disk, garbage collecting the remainder.
@@ -74,12 +70,11 @@ type Database struct {
 	diskdb ethdb.KeyValueStore // Persistent storage for matured trie nodes
 
 	cleans  *fastcache.Cache            // GC friendly memory cache of clean node RLPs
-	dirties map[common.Hash]*cachedNode // Data and references relationships of dirty nodes
+	dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes
 	oldest  common.Hash                 // Oldest tracked node, flush-list head
 	newest  common.Hash                 // Newest tracked node, flush-list tail
 
 	preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
-	seckeybuf [secureKeyLength]byte  // Ephemeral buffer for calculating preimage keys
 
 	gctime  time.Duration      // Time spent on garbage collection since last commit
 	gcnodes uint64             // Nodes garbage collected since last commit
@@ -104,6 +99,11 @@ type rawNode []byte
 func (n rawNode) cache() (hashNode, bool)   { panic("this should never end up in a live trie") }
 func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") }
 
+func (n rawNode) EncodeRLP(w io.Writer) error {
+	_, err := w.Write(n)
+	return err
+}
+
 // rawFullNode represents only the useful data content of a full node, with the
 // caches and flags stripped out to minimize its data storage. This type honors
 // the same RLP encoding as the original parent.
@@ -136,8 +136,8 @@ type rawShortNode struct {
 func (n rawShortNode) cache() (hashNode, bool)   { panic("this should never end up in a live trie") }
 func (n rawShortNode) fstring(ind string) string { panic("this should never end up in a live trie") }
 
-// cachedNode is all the information we know about a single cached node in the
-// memory database write layer.
+// cachedNode is all the information we know about a single cached trie node
+// in the memory database write layer.
 type cachedNode struct {
 	node node   // Cached collapsed trie node, or raw rlp data
 	size uint16 // Byte size of the useful cached data
@@ -158,8 +158,8 @@ var cachedNodeSize = int(reflect.TypeOf(cachedNode{}).Size())
 // reference map.
 const cachedNodeChildrenSize = 48
 
-// rlp returns the raw rlp encoded blob of the cached node, either directly from
-// the cache, or by regenerating it from the collapsed node.
+// rlp returns the raw rlp encoded blob of the cached trie node, either directly
+// from the cache, or by regenerating it from the collapsed node.
 func (n *cachedNode) rlp() []byte {
 	if node, ok := n.node.(rawNode); ok {
 		return node
@@ -180,9 +180,9 @@ func (n *cachedNode) obj(hash common.Hash) node {
 	return expandNode(hash[:], n.node)
 }
 
-// forChilds invokes the callback for  all the tracked children of this node,
-// both the implicit ones  from inside the node as well as the explicit ones
-//from outside the node.
+// forChilds invokes the callback for all the tracked children of this node,
+// both the implicit ones from inside the node as well as the explicit ones
+// from outside the node.
 func (n *cachedNode) forChilds(onChild func(hash common.Hash)) {
 	for child := range n.children {
 		onChild(child)
@@ -204,7 +204,7 @@ func forGatherChildren(n node, onChild func(hash common.Hash)) {
 		}
 	case hashNode:
 		onChild(common.BytesToHash(n))
-	case valueNode, nil:
+	case valueNode, nil, rawNode:
 	default:
 		panic(fmt.Sprintf("unknown node type: %T", n))
 	}
@@ -272,51 +272,54 @@ func expandNode(hash hashNode, n node) node {
 	}
 }
 
+// Config defines all necessary options for database.
+type Config struct {
+	Cache     int    // Memory allowance (MB) to use for caching trie nodes in memory
+	Journal   string // Journal of clean cache to survive node restarts
+	Preimages bool   // Flag whether the preimage of trie key is recorded
+}
+
 // NewDatabase creates a new trie database to store ephemeral trie content before
 // its written out to disk or garbage collected. No read cache is created, so all
 // data retrievals will hit the underlying disk database.
 func NewDatabase(diskdb ethdb.KeyValueStore) *Database {
-	return NewDatabaseWithCache(diskdb, 0)
+	return NewDatabaseWithConfig(diskdb, nil)
 }
 
-// NewDatabaseWithCache creates a new trie database to store ephemeral trie content
+// NewDatabaseWithConfig creates a new trie database to store ephemeral trie content
 // before its written out to disk or garbage collected. It also acts as a read cache
 // for nodes loaded from disk.
-func NewDatabaseWithCache(diskdb ethdb.KeyValueStore, cache int) *Database {
+func NewDatabaseWithConfig(diskdb ethdb.KeyValueStore, config *Config) *Database {
 	var cleans *fastcache.Cache
-	if cache > 0 {
-		cleans = fastcache.New(cache * 1024 * 1024)
+	if config != nil && config.Cache > 0 {
+		if config.Journal == "" {
+			cleans = fastcache.New(config.Cache * 1024 * 1024)
+		} else {
+			cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024)
+		}
 	}
-	return &Database{
+	db := &Database{
 		diskdb: diskdb,
 		cleans: cleans,
 		dirties: map[common.Hash]*cachedNode{{}: {
 			children: make(map[common.Hash]uint16),
 		}},
-		preimages: make(map[common.Hash][]byte),
 	}
+	if config == nil || config.Preimages { // TODO(karalabe): Flip to default off in the future
+		db.preimages = make(map[common.Hash][]byte)
+	}
+	return db
 }
 
 // DiskDB retrieves the persistent storage backing the trie database.
-func (db *Database) DiskDB() ethdb.KeyValueReader {
+func (db *Database) DiskDB() ethdb.KeyValueStore {
 	return db.diskdb
 }
 
-// InsertBlob writes a new reference tracked blob to the memory database if it's
-// yet unknown. This method should only be used for non-trie nodes that require
-// reference counting, since trie nodes are garbage collected directly through
-// their embedded children.
-func (db *Database) InsertBlob(hash common.Hash, blob []byte) {
-	db.lock.Lock()
-	defer db.lock.Unlock()
-
-	db.insert(hash, len(blob), rawNode(blob))
-}
-
-// insert inserts a collapsed trie node into the memory database. This method is
-// a more generic version of InsertBlob, supporting both raw blob insertions as
-// well ex trie node insertions. The blob size must be specified to allow proper
-// size tracking.
+// insert inserts a collapsed trie node into the memory database.
+// The blob size must be specified to allow proper size tracking.
+// All nodes inserted by this function will be reference tracked
+// and in theory should only used for **trie nodes** insertion.
 func (db *Database) insert(hash common.Hash, size int, node node) {
 	// If the node's already cached, skip
 	if _, ok := db.dirties[hash]; ok {
@@ -347,14 +350,20 @@ func (db *Database) insert(hash common.Hash, size int, node node) {
 }
 
 // insertPreimage writes a new trie node pre-image to the memory database if it's
-// yet unknown. The method will make a copy of the slice.
+// yet unknown. The method will NOT make a copy of the slice,
+// only use if the preimage will NOT be changed later on.
 //
 // Note, this method assumes that the database's lock is held!
 func (db *Database) insertPreimage(hash common.Hash, preimage []byte) {
+	// Short circuit if preimage collection is disabled
+	if db.preimages == nil {
+		return
+	}
+	// Track the preimage if a yet unknown one
 	if _, ok := db.preimages[hash]; ok {
 		return
 	}
-	db.preimages[hash] = common.CopyBytes(preimage)
+	db.preimages[hash] = preimage
 	db.preimagesSize += common.StorageSize(common.HashLength + len(preimage))
 }
 
@@ -397,7 +406,7 @@ func (db *Database) node(hash common.Hash) node {
 // Node retrieves an encoded cached trie node from memory. If it cannot be found
 // cached, the method queries the persistent database for the content.
 func (db *Database) Node(hash common.Hash) ([]byte, error) {
-	// It doens't make sense to retrieve the metaroot
+	// It doesn't make sense to retrieve the metaroot
 	if hash == (common.Hash{}) {
 		return nil, errors.New("not found")
 	}
@@ -422,39 +431,34 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) {
 	memcacheDirtyMissMeter.Mark(1)
 
 	// Content unavailable in memory, attempt to retrieve from disk
-	enc, err := db.diskdb.Get(hash[:])
-	if err == nil && enc != nil {
+	enc := rawdb.ReadTrieNode(db.diskdb, hash)
+	if len(enc) != 0 {
 		if db.cleans != nil {
 			db.cleans.Set(hash[:], enc)
 			memcacheCleanMissMeter.Mark(1)
 			memcacheCleanWriteMeter.Mark(int64(len(enc)))
 		}
+		return enc, nil
 	}
-	return enc, err
+	return nil, errors.New("not found")
 }
 
 // preimage retrieves a cached trie node pre-image from memory. If it cannot be
 // found cached, the method queries the persistent database for the content.
-func (db *Database) preimage(hash common.Hash) ([]byte, error) {
+func (db *Database) preimage(hash common.Hash) []byte {
+	// Short circuit if preimage collection is disabled
+	if db.preimages == nil {
+		return nil
+	}
 	// Retrieve the node from cache if available
 	db.lock.RLock()
 	preimage := db.preimages[hash]
 	db.lock.RUnlock()
 
 	if preimage != nil {
-		return preimage, nil
+		return preimage
 	}
-	// Content unavailable in memory, attempt to retrieve from disk
-	return db.diskdb.Get(db.secureKey(hash[:]))
-}
-
-// secureKey returns the database key for the preimage of key, as an ephemeral
-// buffer. The caller must not hold onto the return value because it will become
-// invalid on the next call.
-func (db *Database) secureKey(key []byte) []byte {
-	buf := append(db.seckeybuf[:0], secureKeyPrefix...)
-	buf = append(buf, key...)
-	return buf
+	return rawdb.ReadPreimage(db.diskdb, hash)
 }
 
 // Nodes retrieves the hashes of all the nodes cached within the memory database.
@@ -474,6 +478,9 @@ func (db *Database) Nodes() []common.Hash {
 }
 
 // Reference adds a new reference from a parent node to a child node.
+// This function is used to add reference between internal trie node
+// and external node(e.g. storage trie root), all internal trie nodes
+// are referenced together by database itself.
 func (db *Database) Reference(child common.Hash, parent common.Hash) {
 	db.lock.Lock()
 	defer db.lock.Unlock()
@@ -600,11 +607,10 @@ func (db *Database) Cap(limit common.StorageSize) error {
 	// leave for later to deduplicate writes.
 	flushPreimages := db.preimagesSize > 4*1024*1024
 	if flushPreimages {
-		for hash, preimage := range db.preimages {
-			if err := batch.Put(db.secureKey(hash[:]), preimage); err != nil {
-				log.Error("Failed to commit preimage from trie database", "err", err)
-				return err
-			}
+		if db.preimages == nil {
+			log.Error("Attempted to write preimages whilst disabled")
+		} else {
+			rawdb.WritePreimages(batch, db.preimages)
 			if batch.ValueSize() > ethdb.IdealBatchSize {
 				if err := batch.Write(); err != nil {
 					return err
@@ -618,9 +624,8 @@ func (db *Database) Cap(limit common.StorageSize) error {
 	for size > limit && oldest != (common.Hash{}) {
 		// Fetch the oldest referenced node and push into the batch
 		node := db.dirties[oldest]
-		if err := batch.Put(oldest[:], node.rlp()); err != nil {
-			return err
-		}
+		rawdb.WriteTrieNode(batch, oldest, node.rlp())
+
 		// If we exceeded the ideal batch size, commit and reset
 		if batch.ValueSize() >= ethdb.IdealBatchSize {
 			if err := batch.Write(); err != nil {
@@ -648,8 +653,11 @@ func (db *Database) Cap(limit common.StorageSize) error {
 	defer db.lock.Unlock()
 
 	if flushPreimages {
-		db.preimages = make(map[common.Hash][]byte)
-		db.preimagesSize = 0
+		if db.preimages == nil {
+			log.Error("Attempted to reset preimage cache whilst disabled")
+		} else {
+			db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0
+		}
 	}
 	for db.oldest != oldest {
 		node := db.dirties[db.oldest]
@@ -684,7 +692,7 @@ func (db *Database) Cap(limit common.StorageSize) error {
 //
 // Note, this method is a non-synchronized mutator. It is unsafe to call this
 // concurrently with other mutators.
-func (db *Database) Commit(node common.Hash, report bool) error {
+func (db *Database) Commit(node common.Hash, report bool, callback func(common.Hash)) error {
 	// Create a database batch to flush persistent data out. It is important that
 	// outside code doesn't see an inconsistent state (referenced data removed from
 	// memory cache during commit but not yet in persistent storage). This is ensured
@@ -693,31 +701,26 @@ func (db *Database) Commit(node common.Hash, report bool) error {
 	batch := db.diskdb.NewBatch()
 
 	// Move all of the accumulated preimages into a write batch
-	for hash, preimage := range db.preimages {
-		if err := batch.Put(db.secureKey(hash[:]), preimage); err != nil {
-			log.Error("Failed to commit preimage from trie database", "err", err)
-			return err
-		}
-		// If the batch is too large, flush to disk
+	if db.preimages != nil {
+		rawdb.WritePreimages(batch, db.preimages)
 		if batch.ValueSize() > ethdb.IdealBatchSize {
 			if err := batch.Write(); err != nil {
 				return err
 			}
 			batch.Reset()
 		}
+		// Since we're going to replay trie node writes into the clean cache, flush out
+		// any batched pre-images before continuing.
+		if err := batch.Write(); err != nil {
+			return err
+		}
+		batch.Reset()
 	}
-	// Since we're going to replay trie node writes into the clean cache, flush out
-	// any batched pre-images before continuing.
-	if err := batch.Write(); err != nil {
-		return err
-	}
-	batch.Reset()
-
 	// Move the trie itself into the batch, flushing if enough data is accumulated
 	nodes, storage := len(db.dirties), db.dirtiesSize
 
 	uncacher := &cleaner{db}
-	if err := db.commit(node, batch, uncacher); err != nil {
+	if err := db.commit(node, batch, uncacher, callback); err != nil {
 		log.Error("Failed to commit trie from trie database", "err", err)
 		return err
 	}
@@ -733,10 +736,10 @@ func (db *Database) Commit(node common.Hash, report bool) error {
 	batch.Replay(uncacher)
 	batch.Reset()
 
-	// Reset the storage counters and bumpd metrics
-	db.preimages = make(map[common.Hash][]byte)
-	db.preimagesSize = 0
-
+	// Reset the storage counters and bumped metrics
+	if db.preimages != nil {
+		db.preimages, db.preimagesSize = make(map[common.Hash][]byte), 0
+	}
 	memcacheCommitTimeTimer.Update(time.Since(start))
 	memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize))
 	memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties)))
@@ -756,7 +759,7 @@ func (db *Database) Commit(node common.Hash, report bool) error {
 }
 
 // commit is the private locked version of Commit.
-func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error {
+func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner, callback func(common.Hash)) error {
 	// If the node does not exist, it's a previously committed node
 	node, ok := db.dirties[hash]
 	if !ok {
@@ -765,16 +768,17 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane
 	var err error
 	node.forChilds(func(child common.Hash) {
 		if err == nil {
-			err = db.commit(child, batch, uncacher)
+			err = db.commit(child, batch, uncacher, callback)
 		}
 	})
 	if err != nil {
 		return err
 	}
-	if err := batch.Put(hash[:], node.rlp()); err != nil {
-		return err
-	}
 	// If we've reached an optimal batch size, commit and start over
+	rawdb.WriteTrieNode(batch, hash, node.rlp())
+	if callback != nil {
+		callback(hash)
+	}
 	if batch.ValueSize() >= ethdb.IdealBatchSize {
 		if err := batch.Write(); err != nil {
 			return err
@@ -849,3 +853,43 @@ func (db *Database) Size() (common.StorageSize, common.StorageSize) {
 	var metarootRefs = common.StorageSize(len(db.dirties[common.Hash{}].children) * (common.HashLength + 2))
 	return db.dirtiesSize + db.childrenSize + metadataSize - metarootRefs, db.preimagesSize
 }
+
+// saveCache saves clean state cache to given directory path
+// using specified CPU cores.
+func (db *Database) saveCache(dir string, threads int) error {
+	if db.cleans == nil {
+		return nil
+	}
+	log.Info("Writing clean trie cache to disk", "path", dir, "threads", threads)
+
+	start := time.Now()
+	err := db.cleans.SaveToFileConcurrent(dir, threads)
+	if err != nil {
+		log.Error("Failed to persist clean trie cache", "error", err)
+		return err
+	}
+	log.Info("Persisted the clean trie cache", "path", dir, "elapsed", common.PrettyDuration(time.Since(start)))
+	return nil
+}
+
+// SaveCache atomically saves fast cache data to the given dir using all
+// available CPU cores.
+func (db *Database) SaveCache(dir string) error {
+	return db.saveCache(dir, runtime.GOMAXPROCS(0))
+}
+
+// SaveCachePeriodically atomically saves fast cache data to the given dir with
+// the specified interval. All dump operation will only use a single CPU core.
+func (db *Database) SaveCachePeriodically(dir string, interval time.Duration, stopCh <-chan struct{}) {
+	ticker := time.NewTicker(interval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ticker.C:
+			db.saveCache(dir, 1)
+		case <-stopCh:
+			return
+		}
+	}
+}
diff --git a/trie/encoding.go b/trie/encoding.go
index 1955a3e664..8ee0022ef3 100644
--- a/trie/encoding.go
+++ b/trie/encoding.go
@@ -51,6 +51,35 @@ func hexToCompact(hex []byte) []byte {
 	return buf
 }
 
+// hexToCompactInPlace places the compact key in input buffer, returning the length
+// needed for the representation
+func hexToCompactInPlace(hex []byte) int {
+	var (
+		hexLen    = len(hex) // length of the hex input
+		firstByte = byte(0)
+	)
+	// Check if we have a terminator there
+	if hexLen > 0 && hex[hexLen-1] == 16 {
+		firstByte = 1 << 5
+		hexLen-- // last part was the terminator, ignore that
+	}
+	var (
+		binLen = hexLen/2 + 1
+		ni     = 0 // index in hex
+		bi     = 1 // index in bin (compact)
+	)
+	if hexLen&1 == 1 {
+		firstByte |= 1 << 4 // odd flag
+		firstByte |= hex[0] // first nibble is contained in the first byte
+		ni++
+	}
+	for ; ni < hexLen; bi, ni = bi+1, ni+2 {
+		hex[bi] = hex[ni]<<4 | hex[ni+1]
+	}
+	hex[0] = firstByte
+	return binLen
+}
+
 func compactToHex(compact []byte) []byte {
 	if len(compact) == 0 {
 		return compact
diff --git a/trie/encoding_test.go b/trie/encoding_test.go
index 97d8da1361..16393313f7 100644
--- a/trie/encoding_test.go
+++ b/trie/encoding_test.go
@@ -18,6 +18,8 @@ package trie
 
 import (
 	"bytes"
+	"encoding/hex"
+	"math/rand"
 	"testing"
 )
 
@@ -75,6 +77,40 @@ func TestHexKeybytes(t *testing.T) {
 	}
 }
 
+func TestHexToCompactInPlace(t *testing.T) {
+	for i, keyS := range []string{
+		"00",
+		"060a040c0f000a090b040803010801010900080d090a0a0d0903000b10",
+		"10",
+	} {
+		hexBytes, _ := hex.DecodeString(keyS)
+		exp := hexToCompact(hexBytes)
+		sz := hexToCompactInPlace(hexBytes)
+		got := hexBytes[:sz]
+		if !bytes.Equal(exp, got) {
+			t.Fatalf("test %d: encoding err\ninp %v\ngot %x\nexp %x\n", i, keyS, got, exp)
+		}
+	}
+}
+
+func TestHexToCompactInPlaceRandom(t *testing.T) {
+	for i := 0; i < 10000; i++ {
+		l := rand.Intn(128)
+		key := make([]byte, l)
+		rand.Read(key)
+		hexBytes := keybytesToHex(key)
+		hexOrig := []byte(string(hexBytes))
+		exp := hexToCompact(hexBytes)
+		sz := hexToCompactInPlace(hexBytes)
+		got := hexBytes[:sz]
+
+		if !bytes.Equal(exp, got) {
+			t.Fatalf("encoding err \ncpt %x\nhex %x\ngot %x\nexp %x\n",
+				key, hexOrig, got, exp)
+		}
+	}
+}
+
 func BenchmarkHexToCompact(b *testing.B) {
 	testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}
 	for i := 0; i < b.N; i++ {
diff --git a/trie/hasher.go b/trie/hasher.go
index 8e8eec9f61..3a62a2f119 100644
--- a/trie/hasher.go
+++ b/trie/hasher.go
@@ -17,21 +17,13 @@
 package trie
 
 import (
-	"hash"
 	"sync"
 
+	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/rlp"
 	"golang.org/x/crypto/sha3"
 )
 
-// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
-// Read to get a variable amount of data from the hash state. Read is faster than Sum
-// because it doesn't copy the internal state, but also modifies the internal state.
-type keccakState interface {
-	hash.Hash
-	Read([]byte) (int, error)
-}
-
 type sliceBuffer []byte
 
 func (b *sliceBuffer) Write(data []byte) (n int, err error) {
@@ -46,7 +38,7 @@ func (b *sliceBuffer) Reset() {
 // hasher is a type used for the trie Hash operation. A hasher has some
 // internal preallocated temp space
 type hasher struct {
-	sha      keccakState
+	sha      crypto.KeccakState
 	tmp      sliceBuffer
 	parallel bool // Whether to use paralallel threads when hashing
 }
@@ -56,7 +48,7 @@ var hasherPool = sync.Pool{
 	New: func() interface{} {
 		return &hasher{
 			tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
-			sha: sha3.NewLegacyKeccak256().(keccakState),
+			sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
 		}
 	},
 }
@@ -74,11 +66,11 @@ func returnHasherToPool(h *hasher) {
 // hash collapses a node down into a hash node, also returning a copy of the
 // original node initialized with the computed hash to replace the original one.
 func (h *hasher) hash(n node, force bool) (hashed node, cached node) {
-	// We're not storing the node, just hashing, use available cached data
+	// Return the cached hash if it's available
 	if hash, _ := n.cache(); hash != nil {
 		return hash, n
 	}
-	// Trie not processed yet or needs storage, walk the children
+	// Trie not processed yet, walk the children
 	switch n := n.(type) {
 	case *shortNode:
 		collapsed, cached := h.hashShortNodeChildren(n)
diff --git a/trie/iterator.go b/trie/iterator.go
index bb4025d8f3..406f216c22 100644
--- a/trie/iterator.go
+++ b/trie/iterator.go
@@ -22,6 +22,7 @@ import (
 	"errors"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/rlp"
 )
 
@@ -102,6 +103,19 @@ type NodeIterator interface {
 	// iterator is not positioned at a leaf. Callers must not retain references
 	// to the value after calling Next.
 	LeafProof() [][]byte
+
+	// AddResolver sets an intermediate database to use for looking up trie nodes
+	// before reaching into the real persistent layer.
+	//
+	// This is not required for normal operation, rather is an optimization for
+	// cases where trie nodes can be recovered from some external mechanism without
+	// reading from disk. In those cases, this resolver allows short circuiting
+	// accesses and returning them from memory.
+	//
+	// Before adding a similar mechanism to any other place in Geth, consider
+	// making trie.Database an interface and wrapping at that level. It's a huge
+	// refactor, but it could be worth it if another occurrence arises.
+	AddResolver(ethdb.KeyValueStore)
 }
 
 // nodeIteratorState represents the iteration state at one particular node of the
@@ -119,6 +133,8 @@ type nodeIterator struct {
 	stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
 	path  []byte               // Path to the current node
 	err   error                // Failure set in case of an internal error in the iterator
+
+	resolver ethdb.KeyValueStore // Optional intermediate resolver above the disk layer
 }
 
 // errIteratorEnd is stored in nodeIterator.err when iteration is done.
@@ -143,6 +159,10 @@ func newNodeIterator(trie *Trie, start []byte) NodeIterator {
 	return it
 }
 
+func (it *nodeIterator) AddResolver(resolver ethdb.KeyValueStore) {
+	it.resolver = resolver
+}
+
 func (it *nodeIterator) Hash() common.Hash {
 	if len(it.stack) == 0 {
 		return common.Hash{}
@@ -173,7 +193,7 @@ func (it *nodeIterator) LeafKey() []byte {
 func (it *nodeIterator) LeafBlob() []byte {
 	if len(it.stack) > 0 {
 		if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok {
-			return []byte(node)
+			return node
 		}
 	}
 	panic("not at leaf")
@@ -243,7 +263,7 @@ func (it *nodeIterator) seek(prefix []byte) error {
 	key = key[:len(key)-1]
 	// Move forward until we're just before the closest match to key.
 	for {
-		state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path))
+		state, parentIndex, path, err := it.peekSeek(key)
 		if err == errIteratorEnd {
 			return errIteratorEnd
 		} else if err != nil {
@@ -255,16 +275,21 @@ func (it *nodeIterator) seek(prefix []byte) error {
 	}
 }
 
+// init initializes the the iterator.
+func (it *nodeIterator) init() (*nodeIteratorState, error) {
+	root := it.trie.Hash()
+	state := &nodeIteratorState{node: it.trie.root, index: -1}
+	if root != emptyRoot {
+		state.hash = root
+	}
+	return state, state.resolve(it, nil)
+}
+
 // peek creates the next state of the iterator.
 func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) {
+	// Initialize the iterator if we've just started.
 	if len(it.stack) == 0 {
-		// Initialize the iterator if we've just started.
-		root := it.trie.Hash()
-		state := &nodeIteratorState{node: it.trie.root, index: -1}
-		if root != emptyRoot {
-			state.hash = root
-		}
-		err := state.resolve(it.trie, nil)
+		state, err := it.init()
 		return state, nil, nil, err
 	}
 	if !descend {
@@ -281,7 +306,40 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er
 		}
 		state, path, ok := it.nextChild(parent, ancestor)
 		if ok {
-			if err := state.resolve(it.trie, path); err != nil {
+			if err := state.resolve(it, path); err != nil {
+				return parent, &parent.index, path, err
+			}
+			return state, &parent.index, path, nil
+		}
+		// No more child nodes, move back up.
+		it.pop()
+	}
+	return nil, nil, nil, errIteratorEnd
+}
+
+// peekSeek is like peek, but it also tries to skip resolving hashes by skipping
+// over the siblings that do not lead towards the desired seek position.
+func (it *nodeIterator) peekSeek(seekKey []byte) (*nodeIteratorState, *int, []byte, error) {
+	// Initialize the iterator if we've just started.
+	if len(it.stack) == 0 {
+		state, err := it.init()
+		return state, nil, nil, err
+	}
+	if !bytes.HasPrefix(seekKey, it.path) {
+		// If we're skipping children, pop the current node first
+		it.pop()
+	}
+
+	// Continue iteration to the next child
+	for len(it.stack) > 0 {
+		parent := it.stack[len(it.stack)-1]
+		ancestor := parent.hash
+		if (ancestor == common.Hash{}) {
+			ancestor = parent.parent
+		}
+		state, path, ok := it.nextChildAt(parent, ancestor, seekKey)
+		if ok {
+			if err := state.resolve(it, path); err != nil {
 				return parent, &parent.index, path, err
 			}
 			return state, &parent.index, path, nil
@@ -292,9 +350,21 @@ func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, er
 	return nil, nil, nil, errIteratorEnd
 }
 
-func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error {
+func (it *nodeIterator) resolveHash(hash hashNode, path []byte) (node, error) {
+	if it.resolver != nil {
+		if blob, err := it.resolver.Get(hash); err == nil && len(blob) > 0 {
+			if resolved, err := decodeNode(hash, blob); err == nil {
+				return resolved, nil
+			}
+		}
+	}
+	resolved, err := it.trie.resolveHash(hash, path)
+	return resolved, err
+}
+
+func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error {
 	if hash, ok := st.node.(hashNode); ok {
-		resolved, err := tr.resolveHash(hash, path)
+		resolved, err := it.resolveHash(hash, path)
 		if err != nil {
 			return err
 		}
@@ -304,25 +374,38 @@ func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error {
 	return nil
 }
 
+func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) {
+	var (
+		child     node
+		state     *nodeIteratorState
+		childPath []byte
+	)
+	for ; index < len(n.Children); index++ {
+		if n.Children[index] != nil {
+			child = n.Children[index]
+			hash, _ := child.cache()
+			state = &nodeIteratorState{
+				hash:    common.BytesToHash(hash),
+				node:    child,
+				parent:  ancestor,
+				index:   -1,
+				pathlen: len(path),
+			}
+			childPath = append(childPath, path...)
+			childPath = append(childPath, byte(index))
+			return child, state, childPath, index
+		}
+	}
+	return nil, nil, nil, 0
+}
+
 func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Hash) (*nodeIteratorState, []byte, bool) {
 	switch node := parent.node.(type) {
 	case *fullNode:
-		// Full node, move to the first non-nil child.
-		for i := parent.index + 1; i < len(node.Children); i++ {
-			child := node.Children[i]
-			if child != nil {
-				hash, _ := child.cache()
-				state := &nodeIteratorState{
-					hash:    common.BytesToHash(hash),
-					node:    child,
-					parent:  ancestor,
-					index:   -1,
-					pathlen: len(it.path),
-				}
-				path := append(it.path, byte(i))
-				parent.index = i - 1
-				return state, path, true
-			}
+		//Full node, move to the first non-nil child.
+		if child, state, path, index := findChild(node, parent.index+1, it.path, ancestor); child != nil {
+			parent.index = index - 1
+			return state, path, true
 		}
 	case *shortNode:
 		// Short node, return the pointer singleton child
@@ -342,6 +425,52 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has
 	return parent, it.path, false
 }
 
+// nextChildAt is similar to nextChild, except that it targets a child as close to the
+// target key as possible, thus skipping siblings.
+func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.Hash, key []byte) (*nodeIteratorState, []byte, bool) {
+	switch n := parent.node.(type) {
+	case *fullNode:
+		// Full node, move to the first non-nil child before the desired key position
+		child, state, path, index := findChild(n, parent.index+1, it.path, ancestor)
+		if child == nil {
+			// No more children in this fullnode
+			return parent, it.path, false
+		}
+		// If the child we found is already past the seek position, just return it.
+		if bytes.Compare(path, key) >= 0 {
+			parent.index = index - 1
+			return state, path, true
+		}
+		// The child is before the seek position. Try advancing
+		for {
+			nextChild, nextState, nextPath, nextIndex := findChild(n, index+1, it.path, ancestor)
+			// If we run out of children, or skipped past the target, return the
+			// previous one
+			if nextChild == nil || bytes.Compare(nextPath, key) >= 0 {
+				parent.index = index - 1
+				return state, path, true
+			}
+			// We found a better child closer to the target
+			state, path, index = nextState, nextPath, nextIndex
+		}
+	case *shortNode:
+		// Short node, return the pointer singleton child
+		if parent.index < 0 {
+			hash, _ := n.Val.cache()
+			state := &nodeIteratorState{
+				hash:    common.BytesToHash(hash),
+				node:    n.Val,
+				parent:  ancestor,
+				index:   -1,
+				pathlen: len(it.path),
+			}
+			path := append(it.path, n.Key...)
+			return state, path, true
+		}
+	}
+	return parent, it.path, false
+}
+
 func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) {
 	it.path = path
 	it.stack = append(it.stack, state)
@@ -420,6 +549,10 @@ func (it *differenceIterator) Path() []byte {
 	return it.b.Path()
 }
 
+func (it *differenceIterator) AddResolver(resolver ethdb.KeyValueStore) {
+	panic("not implemented")
+}
+
 func (it *differenceIterator) Next(bool) bool {
 	// Invariants:
 	// - We always advance at least one element in b.
@@ -527,6 +660,10 @@ func (it *unionIterator) Path() []byte {
 	return (*it.items)[0].Path()
 }
 
+func (it *unionIterator) AddResolver(resolver ethdb.KeyValueStore) {
+	panic("not implemented")
+}
+
 // Next returns the next node in the union of tries being iterated over.
 //
 // It does this by maintaining a heap of iterators, sorted by the iteration
diff --git a/trie/iterator_test.go b/trie/iterator_test.go
index 54dd3069f9..2518f7bac8 100644
--- a/trie/iterator_test.go
+++ b/trie/iterator_test.go
@@ -18,11 +18,14 @@ package trie
 
 import (
 	"bytes"
+	"encoding/binary"
 	"fmt"
 	"math/rand"
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/ethdb/memorydb"
 )
 
@@ -301,7 +304,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) {
 	}
 	tr.Commit(nil)
 	if !memonly {
-		triedb.Commit(tr.Hash(), true)
+		triedb.Commit(tr.Hash(), true, nil)
 	}
 	wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil)
 
@@ -392,7 +395,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) {
 	}
 	root, _ := ctr.Commit(nil)
 	if !memonly {
-		triedb.Commit(root, true)
+		triedb.Commit(root, true, nil)
 	}
 	barNodeHash := common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e")
 	var (
@@ -440,3 +443,81 @@ func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) in
 	}
 	return len(seen)
 }
+
+type loggingDb struct {
+	getCount uint64
+	backend  ethdb.KeyValueStore
+}
+
+func (l *loggingDb) Has(key []byte) (bool, error) {
+	return l.backend.Has(key)
+}
+
+func (l *loggingDb) Get(key []byte) ([]byte, error) {
+	l.getCount++
+	return l.backend.Get(key)
+}
+
+func (l *loggingDb) Put(key []byte, value []byte) error {
+	return l.backend.Put(key, value)
+}
+
+func (l *loggingDb) Delete(key []byte) error {
+	return l.backend.Delete(key)
+}
+
+func (l *loggingDb) NewBatch() ethdb.Batch {
+	return l.backend.NewBatch()
+}
+
+func (l *loggingDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
+	fmt.Printf("NewIterator\n")
+	return l.backend.NewIterator(prefix, start)
+}
+func (l *loggingDb) Stat(property string) (string, error) {
+	return l.backend.Stat(property)
+}
+
+func (l *loggingDb) Compact(start []byte, limit []byte) error {
+	return l.backend.Compact(start, limit)
+}
+
+func (l *loggingDb) Close() error {
+	return l.backend.Close()
+}
+
+// makeLargeTestTrie create a sample test trie
+func makeLargeTestTrie() (*Database, *SecureTrie, *loggingDb) {
+	// Create an empty trie
+	logDb := &loggingDb{0, memorydb.New()}
+	triedb := NewDatabase(logDb)
+	trie, _ := NewSecure(common.Hash{}, triedb)
+
+	// Fill it with some arbitrary data
+	for i := 0; i < 10000; i++ {
+		key := make([]byte, 32)
+		val := make([]byte, 32)
+		binary.BigEndian.PutUint64(key, uint64(i))
+		binary.BigEndian.PutUint64(val, uint64(i))
+		key = crypto.Keccak256(key)
+		val = crypto.Keccak256(val)
+		trie.Update(key, val)
+	}
+	trie.Commit(nil)
+	// Return the generated trie
+	return triedb, trie, logDb
+}
+
+// Tests that the node iterator indeed walks over the entire database contents.
+func TestNodeIteratorLargeTrie(t *testing.T) {
+	// Create some arbitrary test trie to iterate
+	db, trie, logDb := makeLargeTestTrie()
+	db.Cap(0) // flush everything
+	// Do a seek operation
+	trie.NodeIterator(common.FromHex("0x77667766776677766778855885885885"))
+	// master: 24 get operations
+	// this pr: 5 get operations
+	if have, want := logDb.getCount, uint64(5); have != want {
+		t.Fatalf("Too many lookups during seek, have %d want %d", have, want)
+	}
+}
diff --git a/trie/proof.go b/trie/proof.go
index 58ca69c680..08a9e40422 100644
--- a/trie/proof.go
+++ b/trie/proof.go
@@ -18,10 +18,12 @@ package trie
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/rlp"
 )
@@ -101,33 +103,470 @@ func (t *SecureTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWri
 // VerifyProof checks merkle proofs. The given proof must contain the value for
 // key in a trie with the given root hash. VerifyProof returns an error if the
 // proof contains invalid trie nodes or the wrong value.
-func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) (value []byte, nodes int, err error) {
+func VerifyProof(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueReader) (value []byte, err error) {
 	key = keybytesToHex(key)
 	wantHash := rootHash
 	for i := 0; ; i++ {
 		buf, _ := proofDb.Get(wantHash[:])
 		if buf == nil {
-			return nil, i, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash)
+			return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash)
 		}
 		n, err := decodeNode(wantHash[:], buf)
 		if err != nil {
-			return nil, i, fmt.Errorf("bad proof node %d: %v", i, err)
+			return nil, fmt.Errorf("bad proof node %d: %v", i, err)
 		}
-		keyrest, cld := get(n, key)
+		keyrest, cld := get(n, key, true)
 		switch cld := cld.(type) {
 		case nil:
 			// The trie doesn't contain the key.
-			return nil, i, nil
+			return nil, nil
 		case hashNode:
 			key = keyrest
 			copy(wantHash[:], cld)
 		case valueNode:
-			return cld, i + 1, nil
+			return cld, nil
 		}
 	}
 }
 
-func get(tn node, key []byte) ([]byte, node) {
+// proofToPath converts a merkle proof to trie node path. The main purpose of
+// this function is recovering a node path from the merkle proof stream. All
+// necessary nodes will be resolved and leave the remaining as hashnode.
+//
+// The given edge proof is allowed to be an existent or non-existent proof.
+func proofToPath(rootHash common.Hash, root node, key []byte, proofDb ethdb.KeyValueReader, allowNonExistent bool) (node, []byte, error) {
+	// resolveNode retrieves and resolves trie node from merkle proof stream
+	resolveNode := func(hash common.Hash) (node, error) {
+		buf, _ := proofDb.Get(hash[:])
+		if buf == nil {
+			return nil, fmt.Errorf("proof node (hash %064x) missing", hash)
+		}
+		n, err := decodeNode(hash[:], buf)
+		if err != nil {
+			return nil, fmt.Errorf("bad proof node %v", err)
+		}
+		return n, err
+	}
+	// If the root node is empty, resolve it first.
+	// Root node must be included in the proof.
+	if root == nil {
+		n, err := resolveNode(rootHash)
+		if err != nil {
+			return nil, nil, err
+		}
+		root = n
+	}
+	var (
+		err           error
+		child, parent node
+		keyrest       []byte
+		valnode       []byte
+	)
+	key, parent = keybytesToHex(key), root
+	for {
+		keyrest, child = get(parent, key, false)
+		switch cld := child.(type) {
+		case nil:
+			// The trie doesn't contain the key. It's possible
+			// the proof is a non-existing proof, but at least
+			// we can prove all resolved nodes are correct, it's
+			// enough for us to prove range.
+			if allowNonExistent {
+				return root, nil, nil
+			}
+			return nil, nil, errors.New("the node is not contained in trie")
+		case *shortNode:
+			key, parent = keyrest, child // Already resolved
+			continue
+		case *fullNode:
+			key, parent = keyrest, child // Already resolved
+			continue
+		case hashNode:
+			child, err = resolveNode(common.BytesToHash(cld))
+			if err != nil {
+				return nil, nil, err
+			}
+		case valueNode:
+			valnode = cld
+		}
+		// Link the parent and child.
+		switch pnode := parent.(type) {
+		case *shortNode:
+			pnode.Val = child
+		case *fullNode:
+			pnode.Children[key[0]] = child
+		default:
+			panic(fmt.Sprintf("%T: invalid node: %v", pnode, pnode))
+		}
+		if len(valnode) > 0 {
+			return root, valnode, nil // The whole path is resolved
+		}
+		key, parent = keyrest, child
+	}
+}
+
+// unsetInternal removes all internal node references(hashnode, embedded node).
+// It should be called after a trie is constructed with two edge paths. Also
+// the given boundary keys must be the one used to construct the edge paths.
+//
+// It's the key step for range proof. All visited nodes should be marked dirty
+// since the node content might be modified. Besides it can happen that some
+// fullnodes only have one child which is disallowed. But if the proof is valid,
+// the missing children will be filled, otherwise it will be thrown anyway.
+//
+// Note we have the assumption here the given boundary keys are different
+// and right is larger than left.
+func unsetInternal(n node, left []byte, right []byte) (bool, error) {
+	left, right = keybytesToHex(left), keybytesToHex(right)
+
+	// Step down to the fork point. There are two scenarios can happen:
+	// - the fork point is a shortnode: either the key of left proof or
+	//   right proof doesn't match with shortnode's key.
+	// - the fork point is a fullnode: both two edge proofs are allowed
+	//   to point to a non-existent key.
+	var (
+		pos    = 0
+		parent node
+
+		// fork indicator, 0 means no fork, -1 means proof is less, 1 means proof is greater
+		shortForkLeft, shortForkRight int
+	)
+findFork:
+	for {
+		switch rn := (n).(type) {
+		case *shortNode:
+			rn.flags = nodeFlag{dirty: true}
+
+			// If either the key of left proof or right proof doesn't match with
+			// shortnode, stop here and the forkpoint is the shortnode.
+			if len(left)-pos < len(rn.Key) {
+				shortForkLeft = bytes.Compare(left[pos:], rn.Key)
+			} else {
+				shortForkLeft = bytes.Compare(left[pos:pos+len(rn.Key)], rn.Key)
+			}
+			if len(right)-pos < len(rn.Key) {
+				shortForkRight = bytes.Compare(right[pos:], rn.Key)
+			} else {
+				shortForkRight = bytes.Compare(right[pos:pos+len(rn.Key)], rn.Key)
+			}
+			if shortForkLeft != 0 || shortForkRight != 0 {
+				break findFork
+			}
+			parent = n
+			n, pos = rn.Val, pos+len(rn.Key)
+		case *fullNode:
+			rn.flags = nodeFlag{dirty: true}
+
+			// If either the node pointed by left proof or right proof is nil,
+			// stop here and the forkpoint is the fullnode.
+			leftnode, rightnode := rn.Children[left[pos]], rn.Children[right[pos]]
+			if leftnode == nil || rightnode == nil || leftnode != rightnode {
+				break findFork
+			}
+			parent = n
+			n, pos = rn.Children[left[pos]], pos+1
+		default:
+			panic(fmt.Sprintf("%T: invalid node: %v", n, n))
+		}
+	}
+	switch rn := n.(type) {
+	case *shortNode:
+		// There can have these five scenarios:
+		// - both proofs are less than the trie path => no valid range
+		// - both proofs are greater than the trie path => no valid range
+		// - left proof is less and right proof is greater => valid range, unset the shortnode entirely
+		// - left proof points to the shortnode, but right proof is greater
+		// - right proof points to the shortnode, but left proof is less
+		if shortForkLeft == -1 && shortForkRight == -1 {
+			return false, errors.New("empty range")
+		}
+		if shortForkLeft == 1 && shortForkRight == 1 {
+			return false, errors.New("empty range")
+		}
+		if shortForkLeft != 0 && shortForkRight != 0 {
+			// The fork point is root node, unset the entire trie
+			if parent == nil {
+				return true, nil
+			}
+			parent.(*fullNode).Children[left[pos-1]] = nil
+			return false, nil
+		}
+		// Only one proof points to non-existent key.
+		if shortForkRight != 0 {
+			if _, ok := rn.Val.(valueNode); ok {
+				// The fork point is root node, unset the entire trie
+				if parent == nil {
+					return true, nil
+				}
+				parent.(*fullNode).Children[left[pos-1]] = nil
+				return false, nil
+			}
+			return false, unset(rn, rn.Val, left[pos:], len(rn.Key), false)
+		}
+		if shortForkLeft != 0 {
+			if _, ok := rn.Val.(valueNode); ok {
+				// The fork point is root node, unset the entire trie
+				if parent == nil {
+					return true, nil
+				}
+				parent.(*fullNode).Children[right[pos-1]] = nil
+				return false, nil
+			}
+			return false, unset(rn, rn.Val, right[pos:], len(rn.Key), true)
+		}
+		return false, nil
+	case *fullNode:
+		// unset all internal nodes in the forkpoint
+		for i := left[pos] + 1; i < right[pos]; i++ {
+			rn.Children[i] = nil
+		}
+		if err := unset(rn, rn.Children[left[pos]], left[pos:], 1, false); err != nil {
+			return false, err
+		}
+		if err := unset(rn, rn.Children[right[pos]], right[pos:], 1, true); err != nil {
+			return false, err
+		}
+		return false, nil
+	default:
+		panic(fmt.Sprintf("%T: invalid node: %v", n, n))
+	}
+}
+
+// unset removes all internal node references either the left most or right most.
+// It can meet these scenarios:
+//
+// - The given path is existent in the trie, unset the associated nodes with the
+//   specific direction
+// - The given path is non-existent in the trie
+//   - the fork point is a fullnode, the corresponding child pointed by path
+//     is nil, return
+//   - the fork point is a shortnode, the shortnode is included in the range,
+//     keep the entire branch and return.
+//   - the fork point is a shortnode, the shortnode is excluded in the range,
+//     unset the entire branch.
+func unset(parent node, child node, key []byte, pos int, removeLeft bool) error {
+	switch cld := child.(type) {
+	case *fullNode:
+		if removeLeft {
+			for i := 0; i < int(key[pos]); i++ {
+				cld.Children[i] = nil
+			}
+			cld.flags = nodeFlag{dirty: true}
+		} else {
+			for i := key[pos] + 1; i < 16; i++ {
+				cld.Children[i] = nil
+			}
+			cld.flags = nodeFlag{dirty: true}
+		}
+		return unset(cld, cld.Children[key[pos]], key, pos+1, removeLeft)
+	case *shortNode:
+		if len(key[pos:]) < len(cld.Key) || !bytes.Equal(cld.Key, key[pos:pos+len(cld.Key)]) {
+			// Find the fork point, it's an non-existent branch.
+			if removeLeft {
+				if bytes.Compare(cld.Key, key[pos:]) < 0 {
+					// The key of fork shortnode is less than the path
+					// (it belongs to the range), unset the entrie
+					// branch. The parent must be a fullnode.
+					fn := parent.(*fullNode)
+					fn.Children[key[pos-1]] = nil
+				} else {
+					// The key of fork shortnode is greater than the
+					// path(it doesn't belong to the range), keep
+					// it with the cached hash available.
+				}
+			} else {
+				if bytes.Compare(cld.Key, key[pos:]) > 0 {
+					// The key of fork shortnode is greater than the
+					// path(it belongs to the range), unset the entrie
+					// branch. The parent must be a fullnode.
+					fn := parent.(*fullNode)
+					fn.Children[key[pos-1]] = nil
+				} else {
+					// The key of fork shortnode is less than the
+					// path(it doesn't belong to the range), keep
+					// it with the cached hash available.
+				}
+			}
+			return nil
+		}
+		if _, ok := cld.Val.(valueNode); ok {
+			fn := parent.(*fullNode)
+			fn.Children[key[pos-1]] = nil
+			return nil
+		}
+		cld.flags = nodeFlag{dirty: true}
+		return unset(cld, cld.Val, key, pos+len(cld.Key), removeLeft)
+	case nil:
+		// If the node is nil, then it's a child of the fork point
+		// fullnode(it's a non-existent branch).
+		return nil
+	default:
+		panic("it shouldn't happen") // hashNode, valueNode
+	}
+}
+
+// hasRightElement returns the indicator whether there exists more elements
+// in the right side of the given path. The given path can point to an existent
+// key or a non-existent one. This function has the assumption that the whole
+// path should already be resolved.
+func hasRightElement(node node, key []byte) bool {
+	pos, key := 0, keybytesToHex(key)
+	for node != nil {
+		switch rn := node.(type) {
+		case *fullNode:
+			for i := key[pos] + 1; i < 16; i++ {
+				if rn.Children[i] != nil {
+					return true
+				}
+			}
+			node, pos = rn.Children[key[pos]], pos+1
+		case *shortNode:
+			if len(key)-pos < len(rn.Key) || !bytes.Equal(rn.Key, key[pos:pos+len(rn.Key)]) {
+				return bytes.Compare(rn.Key, key[pos:]) > 0
+			}
+			node, pos = rn.Val, pos+len(rn.Key)
+		case valueNode:
+			return false // We have resolved the whole path
+		default:
+			panic(fmt.Sprintf("%T: invalid node: %v", node, node)) // hashnode
+		}
+	}
+	return false
+}
+
+// VerifyRangeProof checks whether the given leaf nodes and edge proof
+// can prove the given trie leaves range is matched with the specific root.
+// Besides, the range should be consecutive (no gap inside) and monotonic
+// increasing.
+//
+// Note the given proof actually contains two edge proofs. Both of them can
+// be non-existent proofs. For example the first proof is for a non-existent
+// key 0x03, the last proof is for a non-existent key 0x10. The given batch
+// leaves are [0x04, 0x05, .. 0x09]. It's still feasible to prove the given
+// batch is valid.
+//
+// The firstKey is paired with firstProof, not necessarily the same as keys[0]
+// (unless firstProof is an existent proof). Similarly, lastKey and lastProof
+// are paired.
+//
+// Expect the normal case, this function can also be used to verify the following
+// range proofs:
+//
+// - All elements proof. In this case the proof can be nil, but the range should
+//   be all the leaves in the trie.
+//
+// - One element proof. In this case no matter the edge proof is a non-existent
+//   proof or not, we can always verify the correctness of the proof.
+//
+// - Zero element proof. In this case a single non-existent proof is enough to prove.
+//   Besides, if there are still some other leaves available on the right side, then
+//   an error will be returned.
+//
+// Except returning the error to indicate the proof is valid or not, the function will
+// also return a flag to indicate whether there exists more accounts/slots in the trie.
+//
+// Note: This method does not verify that the proof is of minimal form. If the input
+// proofs are 'bloated' with neighbour leaves or random data, aside from the 'useful'
+// data, then the proof will still be accepted.
+func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) {
+	if len(keys) != len(values) {
+		return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
+	}
+	// Ensure the received batch is monotonic increasing.
+	for i := 0; i < len(keys)-1; i++ {
+		if bytes.Compare(keys[i], keys[i+1]) >= 0 {
+			return false, errors.New("range is not monotonically increasing")
+		}
+	}
+	// Special case, there is no edge proof at all. The given range is expected
+	// to be the whole leaf-set in the trie.
+	if proof == nil {
+		tr := NewStackTrie(nil)
+		for index, key := range keys {
+			tr.TryUpdate(key, values[index])
+		}
+		if have, want := tr.Hash(), rootHash; have != want {
+			return false, fmt.Errorf("invalid proof, want hash %x, got %x", want, have)
+		}
+		return false, nil // No more elements
+	}
+	// Special case, there is a provided edge proof but zero key/value
+	// pairs, ensure there are no more accounts / slots in the trie.
+	if len(keys) == 0 {
+		root, val, err := proofToPath(rootHash, nil, firstKey, proof, true)
+		if err != nil {
+			return false, err
+		}
+		if val != nil || hasRightElement(root, firstKey) {
+			return false, errors.New("more entries available")
+		}
+		return hasRightElement(root, firstKey), nil
+	}
+	// Special case, there is only one element and two edge keys are same.
+	// In this case, we can't construct two edge paths. So handle it here.
+	if len(keys) == 1 && bytes.Equal(firstKey, lastKey) {
+		root, val, err := proofToPath(rootHash, nil, firstKey, proof, false)
+		if err != nil {
+			return false, err
+		}
+		if !bytes.Equal(firstKey, keys[0]) {
+			return false, errors.New("correct proof but invalid key")
+		}
+		if !bytes.Equal(val, values[0]) {
+			return false, errors.New("correct proof but invalid data")
+		}
+		return hasRightElement(root, firstKey), nil
+	}
+	// Ok, in all other cases, we require two edge paths available.
+	// First check the validity of edge keys.
+	if bytes.Compare(firstKey, lastKey) >= 0 {
+		return false, errors.New("invalid edge keys")
+	}
+	// todo(rjl493456442) different length edge keys should be supported
+	if len(firstKey) != len(lastKey) {
+		return false, errors.New("inconsistent edge keys")
+	}
+	// Convert the edge proofs to edge trie paths. Then we can
+	// have the same tree architecture with the original one.
+	// For the first edge proof, non-existent proof is allowed.
+	root, _, err := proofToPath(rootHash, nil, firstKey, proof, true)
+	if err != nil {
+		return false, err
+	}
+	// Pass the root node here, the second path will be merged
+	// with the first one. For the last edge proof, non-existent
+	// proof is also allowed.
+	root, _, err = proofToPath(rootHash, root, lastKey, proof, true)
+	if err != nil {
+		return false, err
+	}
+	// Remove all internal references. All the removed parts should
+	// be re-filled(or re-constructed) by the given leaves range.
+	empty, err := unsetInternal(root, firstKey, lastKey)
+	if err != nil {
+		return false, err
+	}
+	// Rebuild the trie with the leaf stream, the shape of trie
+	// should be same with the original one.
+	tr := &Trie{root: root, db: NewDatabase(memorydb.New())}
+	if empty {
+		tr.root = nil
+	}
+	for index, key := range keys {
+		tr.TryUpdate(key, values[index])
+	}
+	if tr.Hash() != rootHash {
+		return false, fmt.Errorf("invalid proof, want hash %x, got %x", rootHash, tr.Hash())
+	}
+	return hasRightElement(root, keys[len(keys)-1]), nil
+}
+
+// get returns the child of the given node. Return nil if the
+// node with specified key doesn't exist at all.
+//
+// There is an additional flag `skipResolved`. If it's set then
+// all resolved nodes won't be returned.
+func get(tn node, key []byte, skipResolved bool) ([]byte, node) {
 	for {
 		switch n := tn.(type) {
 		case *shortNode:
@@ -136,9 +575,15 @@ func get(tn node, key []byte) ([]byte, node) {
 			}
 			tn = n.Val
 			key = key[len(n.Key):]
+			if !skipResolved {
+				return key, tn
+			}
 		case *fullNode:
 			tn = n.Children[key[0]]
 			key = key[1:]
+			if !skipResolved {
+				return key, tn
+			}
 		case hashNode:
 			return key, n
 		case nil:
diff --git a/trie/proof_test.go b/trie/proof_test.go
index 4caae73381..a35b7144c0 100644
--- a/trie/proof_test.go
+++ b/trie/proof_test.go
@@ -19,7 +19,9 @@ package trie
 import (
 	"bytes"
 	crand "crypto/rand"
+	"encoding/binary"
 	mrand "math/rand"
+	"sort"
 	"testing"
 	"time"
 
@@ -65,7 +67,7 @@ func TestProof(t *testing.T) {
 			if proof == nil {
 				t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k)
 			}
-			val, _, err := VerifyProof(root, kv.k, proof)
+			val, err := VerifyProof(root, kv.k, proof)
 			if err != nil {
 				t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof)
 			}
@@ -87,7 +89,7 @@ func TestOneElementProof(t *testing.T) {
 		if proof.Len() != 1 {
 			t.Errorf("prover %d: proof should have one element", i)
 		}
-		val, _, err := VerifyProof(trie.Hash(), []byte("k"), proof)
+		val, err := VerifyProof(trie.Hash(), []byte("k"), proof)
 		if err != nil {
 			t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
 		}
@@ -118,7 +120,7 @@ func TestBadProof(t *testing.T) {
 			mutateByte(val)
 			proof.Put(crypto.Keccak256(val), val)
 
-			if _, _, err := VerifyProof(root, kv.k, proof); err == nil {
+			if _, err := VerifyProof(root, kv.k, proof); err == nil {
 				t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k)
 			}
 		}
@@ -138,7 +140,7 @@ func TestMissingKeyProof(t *testing.T) {
 		if proof.Len() != 1 {
 			t.Errorf("test %d: proof should have one element", i)
 		}
-		val, _, err := VerifyProof(trie.Hash(), []byte(key), proof)
+		val, err := VerifyProof(trie.Hash(), []byte(key), proof)
 		if err != nil {
 			t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof)
 		}
@@ -148,6 +150,669 @@ func TestMissingKeyProof(t *testing.T) {
 	}
 }
 
+type entrySlice []*kv
+
+func (p entrySlice) Len() int           { return len(p) }
+func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 }
+func (p entrySlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
+
+// TestRangeProof tests normal range proof with both edge proofs
+// as the existent proof. The test cases are generated randomly.
+func TestRangeProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+	for i := 0; i < 500; i++ {
+		start := mrand.Intn(len(entries))
+		end := mrand.Intn(len(entries)-start) + start + 1
+
+		proof := memorydb.New()
+		if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the first node %v", err)
+		}
+		if err := trie.Prove(entries[end-1].k, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the last node %v", err)
+		}
+		var keys [][]byte
+		var vals [][]byte
+		for i := start; i < end; i++ {
+			keys = append(keys, entries[i].k)
+			vals = append(vals, entries[i].v)
+		}
+		_, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
+		if err != nil {
+			t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
+		}
+	}
+}
+
+// TestRangeProof tests normal range proof with two non-existent proofs.
+// The test cases are generated randomly.
+func TestRangeProofWithNonExistentProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+	for i := 0; i < 500; i++ {
+		start := mrand.Intn(len(entries))
+		end := mrand.Intn(len(entries)-start) + start + 1
+		proof := memorydb.New()
+
+		// Short circuit if the decreased key is same with the previous key
+		first := decreseKey(common.CopyBytes(entries[start].k))
+		if start != 0 && bytes.Equal(first, entries[start-1].k) {
+			continue
+		}
+		// Short circuit if the decreased key is underflow
+		if bytes.Compare(first, entries[start].k) > 0 {
+			continue
+		}
+		// Short circuit if the increased key is same with the next key
+		last := increseKey(common.CopyBytes(entries[end-1].k))
+		if end != len(entries) && bytes.Equal(last, entries[end].k) {
+			continue
+		}
+		// Short circuit if the increased key is overflow
+		if bytes.Compare(last, entries[end-1].k) < 0 {
+			continue
+		}
+		if err := trie.Prove(first, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the first node %v", err)
+		}
+		if err := trie.Prove(last, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the last node %v", err)
+		}
+		var keys [][]byte
+		var vals [][]byte
+		for i := start; i < end; i++ {
+			keys = append(keys, entries[i].k)
+			vals = append(vals, entries[i].v)
+		}
+		_, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof)
+		if err != nil {
+			t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
+		}
+	}
+	// Special case, two edge proofs for two edge key.
+	proof := memorydb.New()
+	first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes()
+	last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	var k [][]byte
+	var v [][]byte
+	for i := 0; i < len(entries); i++ {
+		k = append(k, entries[i].k)
+		v = append(v, entries[i].v)
+	}
+	_, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof)
+	if err != nil {
+		t.Fatal("Failed to verify whole rang with non-existent edges")
+	}
+}
+
+// TestRangeProofWithInvalidNonExistentProof tests such scenarios:
+// - There exists a gap between the first element and the left edge proof
+// - There exists a gap between the last element and the right edge proof
+func TestRangeProofWithInvalidNonExistentProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	// Case 1
+	start, end := 100, 200
+	first := decreseKey(common.CopyBytes(entries[start].k))
+
+	proof := memorydb.New()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(entries[end-1].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	start = 105 // Gap created
+	k := make([][]byte, 0)
+	v := make([][]byte, 0)
+	for i := start; i < end; i++ {
+		k = append(k, entries[i].k)
+		v = append(v, entries[i].v)
+	}
+	_, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof)
+	if err == nil {
+		t.Fatalf("Expected to detect the error, got nil")
+	}
+
+	// Case 2
+	start, end = 100, 200
+	last := increseKey(common.CopyBytes(entries[end-1].k))
+	proof = memorydb.New()
+	if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	end = 195 // Capped slice
+	k = make([][]byte, 0)
+	v = make([][]byte, 0)
+	for i := start; i < end; i++ {
+		k = append(k, entries[i].k)
+		v = append(v, entries[i].v)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof)
+	if err == nil {
+		t.Fatalf("Expected to detect the error, got nil")
+	}
+}
+
+// TestOneElementRangeProof tests the proof with only one
+// element. The first edge proof can be existent one or
+// non-existent one.
+func TestOneElementRangeProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	// One element with existent edge proof, both edge proofs
+	// point to the SAME key.
+	start := 1000
+	proof := memorydb.New()
+	if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	_, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+
+	// One element with left non-existent edge proof
+	start = 1000
+	first := decreseKey(common.CopyBytes(entries[start].k))
+	proof = memorydb.New()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+
+	// One element with right non-existent edge proof
+	start = 1000
+	last := increseKey(common.CopyBytes(entries[start].k))
+	proof = memorydb.New()
+	if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+
+	// One element with two non-existent edge proofs
+	start = 1000
+	first, last = decreseKey(common.CopyBytes(entries[start].k)), increseKey(common.CopyBytes(entries[start].k))
+	proof = memorydb.New()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+
+	// Test the mini trie with only a single element.
+	tinyTrie := new(Trie)
+	entry := &kv{randBytes(32), randBytes(20), false}
+	tinyTrie.Update(entry.k, entry.v)
+
+	first = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes()
+	last = entry.k
+	proof = memorydb.New()
+	if err := tinyTrie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := tinyTrie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+}
+
+// TestAllElementsProof tests the range proof with all elements.
+// The edge proofs can be nil.
+func TestAllElementsProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	var k [][]byte
+	var v [][]byte
+	for i := 0; i < len(entries); i++ {
+		k = append(k, entries[i].k)
+		v = append(v, entries[i].v)
+	}
+	_, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+
+	// With edge proofs, it should still work.
+	proof := memorydb.New()
+	if err := trie.Prove(entries[0].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+
+	// Even with non-existent edge proofs, it should still work.
+	proof = memorydb.New()
+	first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes()
+	last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof)
+	if err != nil {
+		t.Fatalf("Expected no error, got %v", err)
+	}
+}
+
+// TestSingleSideRangeProof tests the range starts from zero.
+func TestSingleSideRangeProof(t *testing.T) {
+	for i := 0; i < 64; i++ {
+		trie := new(Trie)
+		var entries entrySlice
+		for i := 0; i < 4096; i++ {
+			value := &kv{randBytes(32), randBytes(20), false}
+			trie.Update(value.k, value.v)
+			entries = append(entries, value)
+		}
+		sort.Sort(entries)
+
+		var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1}
+		for _, pos := range cases {
+			proof := memorydb.New()
+			if err := trie.Prove(common.Hash{}.Bytes(), 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+			if err := trie.Prove(entries[pos].k, 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+			k := make([][]byte, 0)
+			v := make([][]byte, 0)
+			for i := 0; i <= pos; i++ {
+				k = append(k, entries[i].k)
+				v = append(v, entries[i].v)
+			}
+			_, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof)
+			if err != nil {
+				t.Fatalf("Expected no error, got %v", err)
+			}
+		}
+	}
+}
+
+// TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff.
+func TestReverseSingleSideRangeProof(t *testing.T) {
+	for i := 0; i < 64; i++ {
+		trie := new(Trie)
+		var entries entrySlice
+		for i := 0; i < 4096; i++ {
+			value := &kv{randBytes(32), randBytes(20), false}
+			trie.Update(value.k, value.v)
+			entries = append(entries, value)
+		}
+		sort.Sort(entries)
+
+		var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1}
+		for _, pos := range cases {
+			proof := memorydb.New()
+			if err := trie.Prove(entries[pos].k, 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+			last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
+			if err := trie.Prove(last.Bytes(), 0, proof); err != nil {
+				t.Fatalf("Failed to prove the last node %v", err)
+			}
+			k := make([][]byte, 0)
+			v := make([][]byte, 0)
+			for i := pos; i < len(entries); i++ {
+				k = append(k, entries[i].k)
+				v = append(v, entries[i].v)
+			}
+			_, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof)
+			if err != nil {
+				t.Fatalf("Expected no error, got %v", err)
+			}
+		}
+	}
+}
+
+// TestBadRangeProof tests a few cases which the proof is wrong.
+// The prover is expected to detect the error.
+func TestBadRangeProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	for i := 0; i < 500; i++ {
+		start := mrand.Intn(len(entries))
+		end := mrand.Intn(len(entries)-start) + start + 1
+		proof := memorydb.New()
+		if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the first node %v", err)
+		}
+		if err := trie.Prove(entries[end-1].k, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the last node %v", err)
+		}
+		var keys [][]byte
+		var vals [][]byte
+		for i := start; i < end; i++ {
+			keys = append(keys, entries[i].k)
+			vals = append(vals, entries[i].v)
+		}
+		var first, last = keys[0], keys[len(keys)-1]
+		testcase := mrand.Intn(6)
+		var index int
+		switch testcase {
+		case 0:
+			// Modified key
+			index = mrand.Intn(end - start)
+			keys[index] = randBytes(32) // In theory it can't be same
+		case 1:
+			// Modified val
+			index = mrand.Intn(end - start)
+			vals[index] = randBytes(20) // In theory it can't be same
+		case 2:
+			// Gapped entry slice
+			index = mrand.Intn(end - start)
+			if (index == 0 && start < 100) || (index == end-start-1 && end <= 100) {
+				continue
+			}
+			keys = append(keys[:index], keys[index+1:]...)
+			vals = append(vals[:index], vals[index+1:]...)
+		case 3:
+			// Out of order
+			index1 := mrand.Intn(end - start)
+			index2 := mrand.Intn(end - start)
+			if index1 == index2 {
+				continue
+			}
+			keys[index1], keys[index2] = keys[index2], keys[index1]
+			vals[index1], vals[index2] = vals[index2], vals[index1]
+		case 4:
+			// Set random key to nil, do nothing
+			index = mrand.Intn(end - start)
+			keys[index] = nil
+		case 5:
+			// Set random value to nil, deletion
+			index = mrand.Intn(end - start)
+			vals[index] = nil
+		}
+		_, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof)
+		if err == nil {
+			t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1)
+		}
+	}
+}
+
+// TestGappedRangeProof focuses on the small trie with embedded nodes.
+// If the gapped node is embedded in the trie, it should be detected too.
+func TestGappedRangeProof(t *testing.T) {
+	trie := new(Trie)
+	var entries []*kv // Sorted entries
+	for i := byte(0); i < 10; i++ {
+		value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
+		trie.Update(value.k, value.v)
+		entries = append(entries, value)
+	}
+	first, last := 2, 8
+	proof := memorydb.New()
+	if err := trie.Prove(entries[first].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(entries[last-1].k, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	var keys [][]byte
+	var vals [][]byte
+	for i := first; i < last; i++ {
+		if i == (first+last)/2 {
+			continue
+		}
+		keys = append(keys, entries[i].k)
+		vals = append(vals, entries[i].v)
+	}
+	_, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof)
+	if err == nil {
+		t.Fatal("expect error, got nil")
+	}
+}
+
+// TestSameSideProofs tests the element is not in the range covered by proofs
+func TestSameSideProofs(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	pos := 1000
+	first := decreseKey(common.CopyBytes(entries[pos].k))
+	first = decreseKey(first)
+	last := decreseKey(common.CopyBytes(entries[pos].k))
+
+	proof := memorydb.New()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
+	if err == nil {
+		t.Fatalf("Expected error, got nil")
+	}
+
+	first = increseKey(common.CopyBytes(entries[pos].k))
+	last = increseKey(common.CopyBytes(entries[pos].k))
+	last = increseKey(last)
+
+	proof = memorydb.New()
+	if err := trie.Prove(first, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(last, 0, proof); err != nil {
+		t.Fatalf("Failed to prove the last node %v", err)
+	}
+	_, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof)
+	if err == nil {
+		t.Fatalf("Expected error, got nil")
+	}
+}
+
+func TestHasRightElement(t *testing.T) {
+	trie := new(Trie)
+	var entries entrySlice
+	for i := 0; i < 4096; i++ {
+		value := &kv{randBytes(32), randBytes(20), false}
+		trie.Update(value.k, value.v)
+		entries = append(entries, value)
+	}
+	sort.Sort(entries)
+
+	var cases = []struct {
+		start   int
+		end     int
+		hasMore bool
+	}{
+		{-1, 1, true}, // single element with non-existent left proof
+		{0, 1, true},  // single element with existent left proof
+		{0, 10, true},
+		{50, 100, true},
+		{50, len(entries), false},               // No more element expected
+		{len(entries) - 1, len(entries), false}, // Single last element with two existent proofs(point to same key)
+		{len(entries) - 1, -1, false},           // Single last element with non-existent right proof
+		{0, len(entries), false},                // The whole set with existent left proof
+		{-1, len(entries), false},               // The whole set with non-existent left proof
+		{-1, -1, false},                         // The whole set with non-existent left/right proof
+	}
+	for _, c := range cases {
+		var (
+			firstKey []byte
+			lastKey  []byte
+			start    = c.start
+			end      = c.end
+			proof    = memorydb.New()
+		)
+		if c.start == -1 {
+			firstKey, start = common.Hash{}.Bytes(), 0
+			if err := trie.Prove(firstKey, 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+		} else {
+			firstKey = entries[c.start].k
+			if err := trie.Prove(entries[c.start].k, 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+		}
+		if c.end == -1 {
+			lastKey, end = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes(), len(entries)
+			if err := trie.Prove(lastKey, 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+		} else {
+			lastKey = entries[c.end-1].k
+			if err := trie.Prove(entries[c.end-1].k, 0, proof); err != nil {
+				t.Fatalf("Failed to prove the first node %v", err)
+			}
+		}
+		k := make([][]byte, 0)
+		v := make([][]byte, 0)
+		for i := start; i < end; i++ {
+			k = append(k, entries[i].k)
+			v = append(v, entries[i].v)
+		}
+		hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof)
+		if err != nil {
+			t.Fatalf("Expected no error, got %v", err)
+		}
+		if hasMore != c.hasMore {
+			t.Fatalf("Wrong hasMore indicator, want %t, got %t", c.hasMore, hasMore)
+		}
+	}
+}
+
+// TestEmptyRangeProof tests the range proof with "no" element.
+// The first edge proof must be a non-existent proof.
+func TestEmptyRangeProof(t *testing.T) {
+	trie, vals := randomTrie(4096)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	var cases = []struct {
+		pos int
+		err bool
+	}{
+		{len(entries) - 1, false},
+		{500, true},
+	}
+	for _, c := range cases {
+		proof := memorydb.New()
+		first := increseKey(common.CopyBytes(entries[c.pos].k))
+		if err := trie.Prove(first, 0, proof); err != nil {
+			t.Fatalf("Failed to prove the first node %v", err)
+		}
+		_, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof)
+		if c.err && err == nil {
+			t.Fatalf("Expected error, got nil")
+		}
+		if !c.err && err != nil {
+			t.Fatalf("Expected no error, got %v", err)
+		}
+	}
+}
+
+// TestBloatedProof tests a malicious proof, where the proof is more or less the
+// whole trie. Previously we didn't accept such packets, but the new APIs do, so
+// lets leave this test as a bit weird, but present.
+func TestBloatedProof(t *testing.T) {
+	// Use a small trie
+	trie, kvs := nonRandomTrie(100)
+	var entries entrySlice
+	for _, kv := range kvs {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+	var keys [][]byte
+	var vals [][]byte
+
+	proof := memorydb.New()
+	// In the 'malicious' case, we add proofs for every single item
+	// (but only one key/value pair used as leaf)
+	for i, entry := range entries {
+		trie.Prove(entry.k, 0, proof)
+		if i == 50 {
+			keys = append(keys, entry.k)
+			vals = append(vals, entry.v)
+		}
+	}
+	// For reference, we use the same function, but _only_ prove the first
+	// and last element
+	want := memorydb.New()
+	trie.Prove(keys[0], 0, want)
+	trie.Prove(keys[len(keys)-1], 0, want)
+
+	if _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof); err != nil {
+		t.Fatalf("expected bloated proof to succeed, got %v", err)
+	}
+}
+
 // mutateByte changes one byte in b.
 func mutateByte(b []byte) {
 	for r := mrand.Intn(len(b)); ; {
@@ -159,6 +824,26 @@ func mutateByte(b []byte) {
 	}
 }
 
+func increseKey(key []byte) []byte {
+	for i := len(key) - 1; i >= 0; i-- {
+		key[i]++
+		if key[i] != 0x0 {
+			break
+		}
+	}
+	return key
+}
+
+func decreseKey(key []byte) []byte {
+	for i := len(key) - 1; i >= 0; i-- {
+		key[i]--
+		if key[i] != 0xff {
+			break
+		}
+	}
+	return key
+}
+
 func BenchmarkProve(b *testing.B) {
 	trie, vals := randomTrie(100)
 	var keys []string
@@ -191,12 +876,77 @@ func BenchmarkVerifyProof(b *testing.B) {
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
 		im := i % len(keys)
-		if _, _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil {
+		if _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil {
 			b.Fatalf("key %x: %v", keys[im], err)
 		}
 	}
 }
 
+func BenchmarkVerifyRangeProof10(b *testing.B)   { benchmarkVerifyRangeProof(b, 10) }
+func BenchmarkVerifyRangeProof100(b *testing.B)  { benchmarkVerifyRangeProof(b, 100) }
+func BenchmarkVerifyRangeProof1000(b *testing.B) { benchmarkVerifyRangeProof(b, 1000) }
+func BenchmarkVerifyRangeProof5000(b *testing.B) { benchmarkVerifyRangeProof(b, 5000) }
+
+func benchmarkVerifyRangeProof(b *testing.B, size int) {
+	trie, vals := randomTrie(8192)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	start := 2
+	end := start + size
+	proof := memorydb.New()
+	if err := trie.Prove(entries[start].k, 0, proof); err != nil {
+		b.Fatalf("Failed to prove the first node %v", err)
+	}
+	if err := trie.Prove(entries[end-1].k, 0, proof); err != nil {
+		b.Fatalf("Failed to prove the last node %v", err)
+	}
+	var keys [][]byte
+	var values [][]byte
+	for i := start; i < end; i++ {
+		keys = append(keys, entries[i].k)
+		values = append(values, entries[i].v)
+	}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof)
+		if err != nil {
+			b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err)
+		}
+	}
+}
+
+func BenchmarkVerifyRangeNoProof10(b *testing.B)   { benchmarkVerifyRangeNoProof(b, 100) }
+func BenchmarkVerifyRangeNoProof500(b *testing.B)  { benchmarkVerifyRangeNoProof(b, 500) }
+func BenchmarkVerifyRangeNoProof1000(b *testing.B) { benchmarkVerifyRangeNoProof(b, 1000) }
+
+func benchmarkVerifyRangeNoProof(b *testing.B, size int) {
+	trie, vals := randomTrie(size)
+	var entries entrySlice
+	for _, kv := range vals {
+		entries = append(entries, kv)
+	}
+	sort.Sort(entries)
+
+	var keys [][]byte
+	var values [][]byte
+	for _, entry := range entries {
+		keys = append(keys, entry.k)
+		values = append(values, entry.v)
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, nil)
+		if err != nil {
+			b.Fatalf("Expected no error, got %v", err)
+		}
+	}
+}
+
 func randomTrie(n int) (*Trie, map[string]*kv) {
 	trie := new(Trie)
 	vals := make(map[string]*kv)
@@ -221,3 +971,20 @@ func randBytes(n int) []byte {
 	crand.Read(r)
 	return r
 }
+
+func nonRandomTrie(n int) (*Trie, map[string]*kv) {
+	trie := new(Trie)
+	vals := make(map[string]*kv)
+	max := uint64(0xffffffffffffffff)
+	for i := uint64(0); i < uint64(n); i++ {
+		value := make([]byte, 32)
+		key := make([]byte, 32)
+		binary.LittleEndian.PutUint64(key, i)
+		binary.LittleEndian.PutUint64(value, i-max)
+		//value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
+		elem := &kv{key, value, false}
+		trie.Update(elem.k, elem.v)
+		vals[string(elem.k)] = elem
+	}
+	return trie, vals
+}
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index 955771495b..e38471c1b7 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -79,6 +79,12 @@ func (t *SecureTrie) TryGet(key []byte) ([]byte, error) {
 	return t.trie.TryGet(t.hashKey(key))
 }
 
+// TryGetNode attempts to retrieve a trie node by compact-encoded path. It is not
+// possible to use keybyte-encoding as the path might contain odd nibbles.
+func (t *SecureTrie) TryGetNode(path []byte) ([]byte, int, error) {
+	return t.trie.TryGetNode(path)
+}
+
 // Update associates key with value in the trie. Subsequent calls to
 // Get will return value. If value has length zero, any existing value
 // is deleted from the trie and calls to Get will return nil.
@@ -130,8 +136,7 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
 	if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
 		return key
 	}
-	key, _ := t.trie.db.preimage(common.BytesToHash(shaKey))
-	return key
+	return t.trie.db.preimage(common.BytesToHash(shaKey))
 }
 
 // Commit writes all nodes and the secure hash pre-images to the trie's database.
@@ -142,12 +147,13 @@ func (t *SecureTrie) GetKey(shaKey []byte) []byte {
 func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
 	// Write all the pre-images to the actual disk database
 	if len(t.getSecKeyCache()) > 0 {
-		t.trie.db.lock.Lock()
-		for hk, key := range t.secKeyCache {
-			t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key)
+		if t.trie.db.preimages != nil { // Ugly direct check but avoids the below write lock
+			t.trie.db.lock.Lock()
+			for hk, key := range t.secKeyCache {
+				t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key)
+			}
+			t.trie.db.lock.Unlock()
 		}
-		t.trie.db.lock.Unlock()
-
 		t.secKeyCache = make(map[string][]byte)
 	}
 	// Commit the trie to its intermediate node database
@@ -179,9 +185,9 @@ func (t *SecureTrie) hashKey(key []byte) []byte {
 	h := newHasher(false)
 	h.sha.Reset()
 	h.sha.Write(key)
-	buf := h.sha.Sum(t.hashKeyBuf[:0])
+	h.sha.Read(t.hashKeyBuf[:])
 	returnHasherToPool(h)
-	return buf
+	return t.hashKeyBuf[:]
 }
 
 // getSecKeyCache returns the current secure key cache, creating a new one if
diff --git a/trie/stacktrie.go b/trie/stacktrie.go
new file mode 100644
index 0000000000..f9ff10b62d
--- /dev/null
+++ b/trie/stacktrie.go
@@ -0,0 +1,513 @@
+// 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 .
+
+package trie
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/gob"
+	"errors"
+	"fmt"
+	"io"
+	"sync"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/ethdb"
+	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/rlp"
+)
+
+var ErrCommitDisabled = errors.New("no database for committing")
+
+var stPool = sync.Pool{
+	New: func() interface{} {
+		return NewStackTrie(nil)
+	},
+}
+
+func stackTrieFromPool(db ethdb.KeyValueWriter) *StackTrie {
+	st := stPool.Get().(*StackTrie)
+	st.db = db
+	return st
+}
+
+func returnToPool(st *StackTrie) {
+	st.Reset()
+	stPool.Put(st)
+}
+
+// StackTrie is a trie implementation that expects keys to be inserted
+// in order. Once it determines that a subtree will no longer be inserted
+// into, it will hash it and free up the memory it uses.
+type StackTrie struct {
+	nodeType  uint8                // node type (as in branch, ext, leaf)
+	val       []byte               // value contained by this node if it's a leaf
+	key       []byte               // key chunk covered by this (full|ext) node
+	keyOffset int                  // offset of the key chunk inside a full key
+	children  [16]*StackTrie       // list of children (for fullnodes and exts)
+	db        ethdb.KeyValueWriter // Pointer to the commit db, can be nil
+}
+
+// NewStackTrie allocates and initializes an empty trie.
+func NewStackTrie(db ethdb.KeyValueWriter) *StackTrie {
+	return &StackTrie{
+		nodeType: emptyNode,
+		db:       db,
+	}
+}
+
+// NewFromBinary initialises a serialized stacktrie with the given db.
+func NewFromBinary(data []byte, db ethdb.KeyValueWriter) (*StackTrie, error) {
+	var st StackTrie
+	if err := st.UnmarshalBinary(data); err != nil {
+		return nil, err
+	}
+	// If a database is used, we need to recursively add it to every child
+	if db != nil {
+		st.setDb(db)
+	}
+	return &st, nil
+}
+
+// MarshalBinary implements encoding.BinaryMarshaler
+func (st *StackTrie) MarshalBinary() (data []byte, err error) {
+	var (
+		b bytes.Buffer
+		w = bufio.NewWriter(&b)
+	)
+	if err := gob.NewEncoder(w).Encode(struct {
+		Nodetype  uint8
+		Val       []byte
+		Key       []byte
+		KeyOffset uint8
+	}{
+		st.nodeType,
+		st.val,
+		st.key,
+		uint8(st.keyOffset),
+	}); err != nil {
+		return nil, err
+	}
+	for _, child := range st.children {
+		if child == nil {
+			w.WriteByte(0)
+			continue
+		}
+		w.WriteByte(1)
+		if childData, err := child.MarshalBinary(); err != nil {
+			return nil, err
+		} else {
+			w.Write(childData)
+		}
+	}
+	w.Flush()
+	return b.Bytes(), nil
+}
+
+// UnmarshalBinary implements encoding.BinaryUnmarshaler
+func (st *StackTrie) UnmarshalBinary(data []byte) error {
+	r := bytes.NewReader(data)
+	return st.unmarshalBinary(r)
+}
+
+func (st *StackTrie) unmarshalBinary(r io.Reader) error {
+	var dec struct {
+		Nodetype  uint8
+		Val       []byte
+		Key       []byte
+		KeyOffset uint8
+	}
+	gob.NewDecoder(r).Decode(&dec)
+	st.nodeType = dec.Nodetype
+	st.val = dec.Val
+	st.key = dec.Key
+	st.keyOffset = int(dec.KeyOffset)
+
+	var hasChild = make([]byte, 1)
+	for i := range st.children {
+		if _, err := r.Read(hasChild); err != nil {
+			return err
+		} else if hasChild[0] == 0 {
+			continue
+		}
+		var child StackTrie
+		child.unmarshalBinary(r)
+		st.children[i] = &child
+	}
+	return nil
+}
+
+func (st *StackTrie) setDb(db ethdb.KeyValueWriter) {
+	st.db = db
+	for _, child := range st.children {
+		if child != nil {
+			child.setDb(db)
+		}
+	}
+}
+
+func newLeaf(ko int, key, val []byte, db ethdb.KeyValueWriter) *StackTrie {
+	st := stackTrieFromPool(db)
+	st.nodeType = leafNode
+	st.keyOffset = ko
+	st.key = append(st.key, key[ko:]...)
+	st.val = val
+	return st
+}
+
+func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie {
+	st := stackTrieFromPool(db)
+	st.nodeType = extNode
+	st.keyOffset = ko
+	st.key = append(st.key, key[ko:]...)
+	st.children[0] = child
+	return st
+}
+
+// List all values that StackTrie#nodeType can hold
+const (
+	emptyNode = iota
+	branchNode
+	extNode
+	leafNode
+	hashedNode
+)
+
+// TryUpdate inserts a (key, value) pair into the stack trie
+func (st *StackTrie) TryUpdate(key, value []byte) error {
+	k := keybytesToHex(key)
+	if len(value) == 0 {
+		panic("deletion not supported")
+	}
+	st.insert(k[:len(k)-1], value)
+	return nil
+}
+
+func (st *StackTrie) Update(key, value []byte) {
+	if err := st.TryUpdate(key, value); err != nil {
+		log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
+	}
+}
+
+func (st *StackTrie) Reset() {
+	st.db = nil
+	st.key = st.key[:0]
+	st.val = nil
+	for i := range st.children {
+		st.children[i] = nil
+	}
+	st.nodeType = emptyNode
+	st.keyOffset = 0
+}
+
+// Helper function that, given a full key, determines the index
+// at which the chunk pointed by st.keyOffset is different from
+// the same chunk in the full key.
+func (st *StackTrie) getDiffIndex(key []byte) int {
+	diffindex := 0
+	for ; diffindex < len(st.key) && st.key[diffindex] == key[st.keyOffset+diffindex]; diffindex++ {
+	}
+	return diffindex
+}
+
+// Helper function to that inserts a (key, value) pair into
+// the trie.
+func (st *StackTrie) insert(key, value []byte) {
+	switch st.nodeType {
+	case branchNode: /* Branch */
+		idx := int(key[st.keyOffset])
+		// Unresolve elder siblings
+		for i := idx - 1; i >= 0; i-- {
+			if st.children[i] != nil {
+				if st.children[i].nodeType != hashedNode {
+					st.children[i].hash()
+				}
+				break
+			}
+		}
+		// Add new child
+		if st.children[idx] == nil {
+			st.children[idx] = stackTrieFromPool(st.db)
+			st.children[idx].keyOffset = st.keyOffset + 1
+		}
+		st.children[idx].insert(key, value)
+	case extNode: /* Ext */
+		// Compare both key chunks and see where they differ
+		diffidx := st.getDiffIndex(key)
+
+		// Check if chunks are identical. If so, recurse into
+		// the child node. Otherwise, the key has to be split
+		// into 1) an optional common prefix, 2) the fullnode
+		// representing the two differing path, and 3) a leaf
+		// for each of the differentiated subtrees.
+		if diffidx == len(st.key) {
+			// Ext key and key segment are identical, recurse into
+			// the child node.
+			st.children[0].insert(key, value)
+			return
+		}
+		// Save the original part. Depending if the break is
+		// at the extension's last byte or not, create an
+		// intermediate extension or use the extension's child
+		// node directly.
+		var n *StackTrie
+		if diffidx < len(st.key)-1 {
+			n = newExt(diffidx+1, st.key, st.children[0], st.db)
+		} else {
+			// Break on the last byte, no need to insert
+			// an extension node: reuse the current node
+			n = st.children[0]
+		}
+		// Convert to hash
+		n.hash()
+		var p *StackTrie
+		if diffidx == 0 {
+			// the break is on the first byte, so
+			// the current node is converted into
+			// a branch node.
+			st.children[0] = nil
+			p = st
+			st.nodeType = branchNode
+		} else {
+			// the common prefix is at least one byte
+			// long, insert a new intermediate branch
+			// node.
+			st.children[0] = stackTrieFromPool(st.db)
+			st.children[0].nodeType = branchNode
+			st.children[0].keyOffset = st.keyOffset + diffidx
+			p = st.children[0]
+		}
+		// Create a leaf for the inserted part
+		o := newLeaf(st.keyOffset+diffidx+1, key, value, st.db)
+
+		// Insert both child leaves where they belong:
+		origIdx := st.key[diffidx]
+		newIdx := key[diffidx+st.keyOffset]
+		p.children[origIdx] = n
+		p.children[newIdx] = o
+		st.key = st.key[:diffidx]
+
+	case leafNode: /* Leaf */
+		// Compare both key chunks and see where they differ
+		diffidx := st.getDiffIndex(key)
+
+		// Overwriting a key isn't supported, which means that
+		// the current leaf is expected to be split into 1) an
+		// optional extension for the common prefix of these 2
+		// keys, 2) a fullnode selecting the path on which the
+		// keys differ, and 3) one leaf for the differentiated
+		// component of each key.
+		if diffidx >= len(st.key) {
+			panic("Trying to insert into existing key")
+		}
+
+		// Check if the split occurs at the first nibble of the
+		// chunk. In that case, no prefix extnode is necessary.
+		// Otherwise, create that
+		var p *StackTrie
+		if diffidx == 0 {
+			// Convert current leaf into a branch
+			st.nodeType = branchNode
+			p = st
+			st.children[0] = nil
+		} else {
+			// Convert current node into an ext,
+			// and insert a child branch node.
+			st.nodeType = extNode
+			st.children[0] = NewStackTrie(st.db)
+			st.children[0].nodeType = branchNode
+			st.children[0].keyOffset = st.keyOffset + diffidx
+			p = st.children[0]
+		}
+
+		// Create the two child leaves: the one containing the
+		// original value and the one containing the new value
+		// The child leave will be hashed directly in order to
+		// free up some memory.
+		origIdx := st.key[diffidx]
+		p.children[origIdx] = newLeaf(diffidx+1, st.key, st.val, st.db)
+		p.children[origIdx].hash()
+
+		newIdx := key[diffidx+st.keyOffset]
+		p.children[newIdx] = newLeaf(p.keyOffset+1, key, value, st.db)
+
+		// Finally, cut off the key part that has been passed
+		// over to the children.
+		st.key = st.key[:diffidx]
+		st.val = nil
+	case emptyNode: /* Empty */
+		st.nodeType = leafNode
+		st.key = key[st.keyOffset:]
+		st.val = value
+	case hashedNode:
+		panic("trying to insert into hash")
+	default:
+		panic("invalid type")
+	}
+}
+
+// hash() hashes the node 'st' and converts it into 'hashedNode', if possible.
+// Possible outcomes:
+// 1. The rlp-encoded value was >= 32 bytes:
+//  - Then the 32-byte `hash` will be accessible in `st.val`.
+//  - And the 'st.type' will be 'hashedNode'
+// 2. The rlp-encoded value was < 32 bytes
+//  - Then the <32 byte rlp-encoded value will be accessible in 'st.val'.
+//  - And the 'st.type' will be 'hashedNode' AGAIN
+//
+// This method will also:
+// set 'st.type' to hashedNode
+// clear 'st.key'
+func (st *StackTrie) hash() {
+	/* Shortcut if node is already hashed */
+	if st.nodeType == hashedNode {
+		return
+	}
+	// The 'hasher' is taken from a pool, but we don't actually
+	// claim an instance until all children are done with their hashing,
+	// and we actually need one
+	var h *hasher
+
+	switch st.nodeType {
+	case branchNode:
+		var nodes [17]node
+		for i, child := range st.children {
+			if child == nil {
+				nodes[i] = nilValueNode
+				continue
+			}
+			child.hash()
+			if len(child.val) < 32 {
+				nodes[i] = rawNode(child.val)
+			} else {
+				nodes[i] = hashNode(child.val)
+			}
+			st.children[i] = nil // Reclaim mem from subtree
+			returnToPool(child)
+		}
+		nodes[16] = nilValueNode
+		h = newHasher(false)
+		defer returnHasherToPool(h)
+		h.tmp.Reset()
+		if err := rlp.Encode(&h.tmp, nodes); err != nil {
+			panic(err)
+		}
+	case extNode:
+		st.children[0].hash()
+		h = newHasher(false)
+		defer returnHasherToPool(h)
+		h.tmp.Reset()
+		var valuenode node
+		if len(st.children[0].val) < 32 {
+			valuenode = rawNode(st.children[0].val)
+		} else {
+			valuenode = hashNode(st.children[0].val)
+		}
+		n := struct {
+			Key []byte
+			Val node
+		}{
+			Key: hexToCompact(st.key),
+			Val: valuenode,
+		}
+		if err := rlp.Encode(&h.tmp, n); err != nil {
+			panic(err)
+		}
+		returnToPool(st.children[0])
+		st.children[0] = nil // Reclaim mem from subtree
+	case leafNode:
+		h = newHasher(false)
+		defer returnHasherToPool(h)
+		h.tmp.Reset()
+		st.key = append(st.key, byte(16))
+		sz := hexToCompactInPlace(st.key)
+		n := [][]byte{st.key[:sz], st.val}
+		if err := rlp.Encode(&h.tmp, n); err != nil {
+			panic(err)
+		}
+	case emptyNode:
+		st.val = emptyRoot.Bytes()
+		st.key = st.key[:0]
+		st.nodeType = hashedNode
+		return
+	default:
+		panic("Invalid node type")
+	}
+	st.key = st.key[:0]
+	st.nodeType = hashedNode
+	if len(h.tmp) < 32 {
+		st.val = common.CopyBytes(h.tmp)
+		return
+	}
+	// Write the hash to the 'val'. We allocate a new val here to not mutate
+	// input values
+	st.val = make([]byte, 32)
+	h.sha.Reset()
+	h.sha.Write(h.tmp)
+	h.sha.Read(st.val)
+	if st.db != nil {
+		// TODO! Is it safe to Put the slice here?
+		// Do all db implementations copy the value provided?
+		st.db.Put(st.val, h.tmp)
+	}
+}
+
+// Hash returns the hash of the current node
+func (st *StackTrie) Hash() (h common.Hash) {
+	st.hash()
+	if len(st.val) != 32 {
+		// If the node's RLP isn't 32 bytes long, the node will not
+		// be hashed, and instead contain the  rlp-encoding of the
+		// node. For the top level node, we need to force the hashing.
+		ret := make([]byte, 32)
+		h := newHasher(false)
+		defer returnHasherToPool(h)
+		h.sha.Reset()
+		h.sha.Write(st.val)
+		h.sha.Read(ret)
+		return common.BytesToHash(ret)
+	}
+	return common.BytesToHash(st.val)
+}
+
+// Commit will firstly hash the entrie trie if it's still not hashed
+// and then commit all nodes to the associated database. Actually most
+// of the trie nodes MAY have been committed already. The main purpose
+// here is to commit the root node.
+//
+// The associated database is expected, otherwise the whole commit
+// functionality should be disabled.
+func (st *StackTrie) Commit() (common.Hash, error) {
+	if st.db == nil {
+		return common.Hash{}, ErrCommitDisabled
+	}
+	st.hash()
+	if len(st.val) != 32 {
+		// 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
+}
diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go
new file mode 100644
index 0000000000..ccdf389d52
--- /dev/null
+++ b/trie/stacktrie_test.go
@@ -0,0 +1,217 @@
+package trie
+
+import (
+	"bytes"
+	"math/big"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
+)
+
+func TestSizeBug(t *testing.T) {
+	st := NewStackTrie(nil)
+	nt, _ := New(common.Hash{}, NewDatabase(memorydb.New()))
+
+	leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
+	value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
+
+	nt.TryUpdate(leaf, value)
+	st.TryUpdate(leaf, value)
+
+	if nt.Hash() != st.Hash() {
+		t.Fatalf("error %x != %x", st.Hash(), nt.Hash())
+	}
+}
+
+func TestEmptyBug(t *testing.T) {
+	st := NewStackTrie(nil)
+	nt, _ := New(common.Hash{}, NewDatabase(memorydb.New()))
+
+	//leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
+	//value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
+	kvs := []struct {
+		K string
+		V string
+	}{
+		{K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "9496f4ec2bf9dab484cac6be589e8417d84781be08"},
+		{K: "40edb63a35fcf86c08022722aa3287cdd36440d671b4918131b2514795fefa9c", V: "01"},
+		{K: "b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", V: "947a30f7736e48d6599356464ba4c150d8da0302ff"},
+		{K: "c2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b", V: "02"},
+	}
+
+	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())
+	}
+}
+
+func TestValLength56(t *testing.T) {
+	st := NewStackTrie(nil)
+	nt, _ := New(common.Hash{}, NewDatabase(memorydb.New()))
+
+	//leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")
+	//value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3")
+	kvs := []struct {
+		K string
+		V string
+	}{
+		{K: "405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace", V: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"},
+	}
+
+	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())
+	}
+}
+
+// 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())
+	}
+}
+
+// TestStacktrieNotModifyValues checks that inserting blobs of data into the
+// stacktrie does not mutate the blobs
+func TestStacktrieNotModifyValues(t *testing.T) {
+	st := NewStackTrie(nil)
+	{ // Test a very small trie
+		// Give it the value as a slice with large backing alloc,
+		// so if the stacktrie tries to append, it won't have to realloc
+		value := make([]byte, 1, 100)
+		value[0] = 0x2
+		want := common.CopyBytes(value)
+		st.TryUpdate([]byte{0x01}, value)
+		st.Hash()
+		if have := value; !bytes.Equal(have, want) {
+			t.Fatalf("tiny trie: have %#x want %#x", have, want)
+		}
+		st = NewStackTrie(nil)
+	}
+	// Test with a larger trie
+	keyB := big.NewInt(1)
+	keyDelta := big.NewInt(1)
+	var vals [][]byte
+	getValue := func(i int) []byte {
+		if i%2 == 0 { // large
+			return crypto.Keccak256(big.NewInt(int64(i)).Bytes())
+		} else { //small
+			return big.NewInt(int64(i)).Bytes()
+		}
+	}
+	for i := 0; i < 1000; i++ {
+		key := common.BigToHash(keyB)
+		value := getValue(i)
+		st.TryUpdate(key.Bytes(), value)
+		vals = append(vals, value)
+		keyB = keyB.Add(keyB, keyDelta)
+		keyDelta.Add(keyDelta, common.Big1)
+	}
+	st.Hash()
+	for i := 0; i < 1000; i++ {
+		want := getValue(i)
+
+		have := vals[i]
+		if !bytes.Equal(have, want) {
+			t.Fatalf("item %d, have %#x want %#x", i, have, want)
+		}
+
+	}
+}
+
+// TestStacktrieSerialization tests that the stacktrie works well if we
+// serialize/unserialize it a lot
+func TestStacktrieSerialization(t *testing.T) {
+	var (
+		st       = NewStackTrie(nil)
+		nt, _    = New(common.Hash{}, NewDatabase(memorydb.New()))
+		keyB     = big.NewInt(1)
+		keyDelta = big.NewInt(1)
+		vals     [][]byte
+		keys     [][]byte
+	)
+	getValue := func(i int) []byte {
+		if i%2 == 0 { // large
+			return crypto.Keccak256(big.NewInt(int64(i)).Bytes())
+		} else { //small
+			return big.NewInt(int64(i)).Bytes()
+		}
+	}
+	for i := 0; i < 10; i++ {
+		vals = append(vals, getValue(i))
+		keys = append(keys, common.BigToHash(keyB).Bytes())
+		keyB = keyB.Add(keyB, keyDelta)
+		keyDelta.Add(keyDelta, common.Big1)
+	}
+	for i, k := range keys {
+		nt.TryUpdate(k, common.CopyBytes(vals[i]))
+	}
+
+	for i, k := range keys {
+		blob, err := st.MarshalBinary()
+		if err != nil {
+			t.Fatal(err)
+		}
+		newSt, err := NewFromBinary(blob, nil)
+		if err != nil {
+			t.Fatal(err)
+		}
+		st = newSt
+		st.TryUpdate(k, common.CopyBytes(vals[i]))
+	}
+	if have, want := st.Hash(), nt.Hash(); have != want {
+		t.Fatalf("have %#x want %#x", have, want)
+	}
+}
diff --git a/trie/sync.go b/trie/sync.go
index e5a0c17493..3a6076ff8f 100644
--- a/trie/sync.go
+++ b/trie/sync.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/prque"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/ethdb"
 )
 
@@ -33,21 +34,58 @@ var ErrNotRequested = errors.New("not requested")
 // node it already processed previously.
 var ErrAlreadyProcessed = errors.New("already processed")
 
+// maxFetchesPerDepth is the maximum number of pending trie nodes per depth. The
+// role of this value is to limit the number of trie nodes that get expanded in
+// memory if the node was configured with a significant number of peers.
+const maxFetchesPerDepth = 16384
+
 // request represents a scheduled or already in-flight state retrieval request.
 type request struct {
+	path []byte      // Merkle path leading to this node for prioritization
 	hash common.Hash // Hash of the node data content to retrieve
 	data []byte      // Data content of the node, cached until all subtrees complete
-	raw  bool        // Whether this is a raw entry (code) or a trie node
+	code bool        // Whether this is a code entry
 
 	parents []*request // Parent state nodes referencing this entry (notify all upon completion)
-	depth   int        // Depth level within the trie the node is located to prioritise DFS
 	deps    int        // Number of dependencies before allowed to commit this node
 
 	callback LeafCallback // Callback to invoke if a leaf node it reached on this branch
 }
 
-// SyncResult is a simple list to return missing nodes along with their request
-// hashes.
+// SyncPath is a path tuple identifying a particular trie node either in a single
+// trie (account) or a layered trie (account -> storage).
+//
+// Content wise the tuple either has 1 element if it addresses a node in a single
+// trie or 2 elements if it addresses a node in a stacked trie.
+//
+// To support aiming arbitrary trie nodes, the path needs to support odd nibble
+// lengths. To avoid transferring expanded hex form over the network, the last
+// part of the tuple (which needs to index into the middle of a trie) is compact
+// encoded. In case of a 2-tuple, the first item is always 32 bytes so that is
+// simple binary encoded.
+//
+// Examples:
+//   - Path 0x9  -> {0x19}
+//   - Path 0x99 -> {0x0099}
+//   - Path 0x01234567890123456789012345678901012345678901234567890123456789019  -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x19}
+//   - Path 0x012345678901234567890123456789010123456789012345678901234567890199 -> {0x0123456789012345678901234567890101234567890123456789012345678901, 0x0099}
+type SyncPath [][]byte
+
+// newSyncPath converts an expanded trie path from nibble form into a compact
+// version that can be sent over the network.
+func newSyncPath(path []byte) SyncPath {
+	// If the hash is from the account trie, append a single item, if it
+	// is from the a storage trie, append a tuple. Note, the length 64 is
+	// clashing between account leaf and storage root. It's fine though
+	// because having a trie node at 64 depth means a hash collision was
+	// found and we're long dead.
+	if len(path) < 64 {
+		return SyncPath{hexToCompact(path)}
+	}
+	return SyncPath{hexToKeybytes(path[:64]), hexToCompact(path[64:])}
+}
+
+// SyncResult is a response with requested data along with it's hash.
 type SyncResult struct {
 	Hash common.Hash // Hash of the originally unknown trie node
 	Data []byte      // Data content of the retrieved node
@@ -56,25 +94,41 @@ type SyncResult struct {
 // syncMemBatch is an in-memory buffer of successfully downloaded but not yet
 // persisted data items.
 type syncMemBatch struct {
-	batch map[common.Hash][]byte // In-memory membatch of recently completed items
+	nodes map[common.Hash][]byte // In-memory membatch of recently completed nodes
+	codes map[common.Hash][]byte // In-memory membatch of recently completed codes
 }
 
 // newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes.
 func newSyncMemBatch() *syncMemBatch {
 	return &syncMemBatch{
-		batch: make(map[common.Hash][]byte),
+		nodes: make(map[common.Hash][]byte),
+		codes: make(map[common.Hash][]byte),
 	}
 }
 
+// hasNode reports the trie node with specific hash is already cached.
+func (batch *syncMemBatch) hasNode(hash common.Hash) bool {
+	_, ok := batch.nodes[hash]
+	return ok
+}
+
+// hasCode reports the contract code with specific hash is already cached.
+func (batch *syncMemBatch) hasCode(hash common.Hash) bool {
+	_, ok := batch.codes[hash]
+	return ok
+}
+
 // Sync is the main state trie synchronisation scheduler, which provides yet
 // unknown trie hashes to retrieve, accepts node data associated with said hashes
 // and reconstructs the trie step by step until all is done.
 type Sync struct {
 	database ethdb.KeyValueReader     // Persistent database to check for existing entries
 	membatch *syncMemBatch            // Memory buffer to avoid frequent database writes
-	requests map[common.Hash]*request // Pending requests pertaining to a key hash
+	nodeReqs map[common.Hash]*request // Pending requests pertaining to a trie node hash
+	codeReqs map[common.Hash]*request // Pending requests pertaining to a code hash
 	queue    *prque.Prque             // Priority queue with the pending requests
-	bloom    *SyncBloom               // Bloom filter for fast node existence checks
+	fetches  map[int]int              // Number of active fetches per trie node depth
+	bloom    *SyncBloom               // Bloom filter for fast state existence checks
 }
 
 // NewSync creates a new trie data download scheduler.
@@ -82,27 +136,31 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb
 	ts := &Sync{
 		database: database,
 		membatch: newSyncMemBatch(),
-		requests: make(map[common.Hash]*request),
+		nodeReqs: make(map[common.Hash]*request),
+		codeReqs: make(map[common.Hash]*request),
 		queue:    prque.New(nil),
+		fetches:  make(map[int]int),
 		bloom:    bloom,
 	}
-	ts.AddSubTrie(root, 0, common.Hash{}, callback)
+	ts.AddSubTrie(root, nil, common.Hash{}, callback)
 	return ts
 }
 
 // AddSubTrie registers a new trie to the sync code, rooted at the designated parent.
-func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback LeafCallback) {
+func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, callback LeafCallback) {
 	// Short circuit if the trie is empty or already known
 	if root == emptyRoot {
 		return
 	}
-	if _, ok := s.membatch.batch[root]; ok {
+	if s.membatch.hasNode(root) {
 		return
 	}
-	if s.bloom.Contains(root[:]) {
-		// Bloom filter says this might be a duplicate, double check
-		blob, _ := s.database.Get(root[:])
-		if local, err := decodeNode(root[:], blob); local != nil && err == nil {
+	if s.bloom == nil || s.bloom.Contains(root[:]) {
+		// Bloom filter says this might be a duplicate, double check.
+		// If database says yes, then at least the trie node is present
+		// and we hold the assumption that it's NOT legacy contract code.
+		blob := rawdb.ReadTrieNode(s.database, root)
+		if len(blob) > 0 {
 			return
 		}
 		// False positive, bump fault meter
@@ -110,13 +168,13 @@ func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callb
 	}
 	// Assemble the new sub-trie sync request
 	req := &request{
+		path:     path,
 		hash:     root,
-		depth:    depth,
 		callback: callback,
 	}
 	// If this sub-trie has a designated parent, link them together
 	if parent != (common.Hash{}) {
-		ancestor := s.requests[parent]
+		ancestor := s.nodeReqs[parent]
 		if ancestor == nil {
 			panic(fmt.Sprintf("sub-trie ancestor not found: %x", parent))
 		}
@@ -126,21 +184,25 @@ func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callb
 	s.schedule(req)
 }
 
-// AddRawEntry schedules the direct retrieval of a state entry that should not be
-// interpreted as a trie node, but rather accepted and stored into the database
-// as is. This method's goal is to support misc state metadata retrievals (e.g.
-// contract code).
-func (s *Sync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) {
+// AddCodeEntry schedules the direct retrieval of a contract code that should not
+// be interpreted as a trie node, but rather accepted and stored into the database
+// as is.
+func (s *Sync) AddCodeEntry(hash common.Hash, path []byte, parent common.Hash) {
 	// Short circuit if the entry is empty or already known
 	if hash == emptyState {
 		return
 	}
-	if _, ok := s.membatch.batch[hash]; ok {
+	if s.membatch.hasCode(hash) {
 		return
 	}
-	if s.bloom.Contains(hash[:]) {
-		// Bloom filter says this might be a duplicate, double check
-		if ok, _ := s.database.Has(hash[:]); ok {
+	if s.bloom == nil || s.bloom.Contains(hash[:]) {
+		// Bloom filter says this might be a duplicate, double check.
+		// If database says yes, the blob is present for sure.
+		// Note we only check the existence with new code scheme, fast
+		// sync is expected to run with a fresh new node. Even there
+		// exists the code with legacy format, fetch and store with
+		// new scheme anyway.
+		if blob := rawdb.ReadCodeWithPrefix(s.database, hash); len(blob) > 0 {
 			return
 		}
 		// False positive, bump fault meter
@@ -148,13 +210,13 @@ func (s *Sync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) {
 	}
 	// Assemble the new sub-trie sync request
 	req := &request{
-		hash:  hash,
-		raw:   true,
-		depth: depth,
+		path: path,
+		hash: hash,
+		code: true,
 	}
 	// If this sub-trie has a designated parent, link them together
 	if parent != (common.Hash{}) {
-		ancestor := s.requests[parent]
+		ancestor := s.nodeReqs[parent] // the parent of codereq can ONLY be nodereq
 		if ancestor == nil {
 			panic(fmt.Sprintf("raw-entry ancestor not found: %x", parent))
 		}
@@ -164,71 +226,102 @@ func (s *Sync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) {
 	s.schedule(req)
 }
 
-// Missing retrieves the known missing nodes from the trie for retrieval.
-func (s *Sync) Missing(max int) []common.Hash {
-	var requests []common.Hash
-	for !s.queue.Empty() && (max == 0 || len(requests) < max) {
-		requests = append(requests, s.queue.PopItem().(common.Hash))
-	}
-	return requests
-}
+// Missing retrieves the known missing nodes from the trie for retrieval. To aid
+// both eth/6x style fast sync and snap/1x style state sync, the paths of trie
+// nodes are returned too, as well as separate hash list for codes.
+func (s *Sync) Missing(max int) (nodes []common.Hash, paths []SyncPath, codes []common.Hash) {
+	var (
+		nodeHashes []common.Hash
+		nodePaths  []SyncPath
+		codeHashes []common.Hash
+	)
+	for !s.queue.Empty() && (max == 0 || len(nodeHashes)+len(codeHashes) < max) {
+		// Retrieve th enext item in line
+		item, prio := s.queue.Peek()
 
-// Process injects a batch of retrieved trie nodes data, returning if something
-// was committed to the database and also the index of an entry if processing of
-// it failed.
-func (s *Sync) Process(results []SyncResult) (bool, int, error) {
-	committed := false
-
-	for i, item := range results {
-		// If the item was not requested, bail out
-		request := s.requests[item.Hash]
-		if request == nil {
-			return committed, i, ErrNotRequested
-		}
-		if request.data != nil {
-			return committed, i, ErrAlreadyProcessed
+		// If we have too many already-pending tasks for this depth, throttle
+		depth := int(prio >> 56)
+		if s.fetches[depth] > maxFetchesPerDepth {
+			break
 		}
-		// If the item is a raw entry request, commit directly
-		if request.raw {
-			request.data = item.Data
-			s.commit(request)
-			committed = true
-			continue
+		// Item is allowed to be scheduled, add it to the task list
+		s.queue.Pop()
+		s.fetches[depth]++
+
+		hash := item.(common.Hash)
+		if req, ok := s.nodeReqs[hash]; ok {
+			nodeHashes = append(nodeHashes, hash)
+			nodePaths = append(nodePaths, newSyncPath(req.path))
+		} else {
+			codeHashes = append(codeHashes, hash)
 		}
+	}
+	return nodeHashes, nodePaths, codeHashes
+}
+
+// Process injects the received data for requested item. Note it can
+// happpen that the single response commits two pending requests(e.g.
+// there are two requests one for code and one for node but the hash
+// is same). In this case the second response for the same hash will
+// be treated as "non-requested" item or "already-processed" item but
+// there is no downside.
+func (s *Sync) Process(result SyncResult) error {
+	// If the item was not requested either for code or node, bail out
+	if s.nodeReqs[result.Hash] == nil && s.codeReqs[result.Hash] == nil {
+		return ErrNotRequested
+	}
+	// There is an pending code request for this data, commit directly
+	var filled bool
+	if req := s.codeReqs[result.Hash]; req != nil && req.data == nil {
+		filled = true
+		req.data = result.Data
+		s.commit(req)
+	}
+	// There is an pending node request for this data, fill it.
+	if req := s.nodeReqs[result.Hash]; req != nil && req.data == nil {
+		filled = true
 		// Decode the node data content and update the request
-		node, err := decodeNode(item.Hash[:], item.Data)
+		node, err := decodeNode(result.Hash[:], result.Data)
 		if err != nil {
-			return committed, i, err
+			return err
 		}
-		request.data = item.Data
+		req.data = result.Data
 
 		// Create and schedule a request for all the children nodes
-		requests, err := s.children(request, node)
+		requests, err := s.children(req, node)
 		if err != nil {
-			return committed, i, err
-		}
-		if len(requests) == 0 && request.deps == 0 {
-			s.commit(request)
-			committed = true
-			continue
+			return err
 		}
-		request.deps += len(requests)
-		for _, child := range requests {
-			s.schedule(child)
+		if len(requests) == 0 && req.deps == 0 {
+			s.commit(req)
+		} else {
+			req.deps += len(requests)
+			for _, child := range requests {
+				s.schedule(child)
+			}
 		}
 	}
-	return committed, 0, nil
+	if !filled {
+		return ErrAlreadyProcessed
+	}
+	return nil
 }
 
 // Commit flushes the data stored in the internal membatch out to persistent
 // storage, returning any occurred error.
 func (s *Sync) Commit(dbw ethdb.Batch) error {
 	// Dump the membatch into a database dbw
-	for key, value := range s.membatch.batch {
-		if err := dbw.Put(key[:], value); err != nil {
-			return err
+	for key, value := range s.membatch.nodes {
+		rawdb.WriteTrieNode(dbw, key, value)
+		if s.bloom != nil {
+			s.bloom.Add(key[:])
+		}
+	}
+	for key, value := range s.membatch.codes {
+		rawdb.WriteCode(dbw, key, value)
+		if s.bloom != nil {
+			s.bloom.Add(key[:])
 		}
-		s.bloom.Add(key[:])
 	}
 	// Drop the membatch data and return
 	s.membatch = newSyncMemBatch()
@@ -237,21 +330,34 @@ func (s *Sync) Commit(dbw ethdb.Batch) error {
 
 // Pending returns the number of state entries currently pending for download.
 func (s *Sync) Pending() int {
-	return len(s.requests)
+	return len(s.nodeReqs) + len(s.codeReqs)
 }
 
 // schedule inserts a new state retrieval request into the fetch queue. If there
 // is already a pending request for this node, the new request will be discarded
 // and only a parent reference added to the old one.
 func (s *Sync) schedule(req *request) {
+	var reqset = s.nodeReqs
+	if req.code {
+		reqset = s.codeReqs
+	}
 	// If we're already requesting this node, add a new reference and stop
-	if old, ok := s.requests[req.hash]; ok {
+	if old, ok := reqset[req.hash]; ok {
 		old.parents = append(old.parents, req.parents...)
 		return
 	}
-	// Schedule the request for future retrieval
-	s.queue.Push(req.hash, int64(req.depth))
-	s.requests[req.hash] = req
+	reqset[req.hash] = req
+
+	// Schedule the request for future retrieval. This queue is shared
+	// by both node requests and code requests. It can happen that there
+	// is a trie node and code has same hash. In this case two elements
+	// with same hash and same or different depth will be pushed. But it's
+	// ok the worst case is the second response will be treated as duplicated.
+	prio := int64(len(req.path)) << 56 // depth >= 128 will never happen, storage leaves will be included in their parents
+	for i := 0; i < 14 && i < len(req.path); i++ {
+		prio |= int64(15-req.path[i]) << (52 - i*4) // 15-nibble => lexicographic order
+	}
+	s.queue.Push(req.hash, prio)
 }
 
 // children retrieves all the missing children of a state trie entry for future
@@ -259,23 +365,27 @@ func (s *Sync) schedule(req *request) {
 func (s *Sync) children(req *request, object node) ([]*request, error) {
 	// Gather all the children of the node, irrelevant whether known or not
 	type child struct {
-		node  node
-		depth int
+		path []byte
+		node node
 	}
 	var children []child
 
 	switch node := (object).(type) {
 	case *shortNode:
+		key := node.Key
+		if hasTerm(key) {
+			key = key[:len(key)-1]
+		}
 		children = []child{{
-			node:  node.Val,
-			depth: req.depth + len(node.Key),
+			node: node.Val,
+			path: append(append([]byte(nil), req.path...), key...),
 		}}
 	case *fullNode:
 		for i := 0; i < 17; i++ {
 			if node.Children[i] != nil {
 				children = append(children, child{
-					node:  node.Children[i],
-					depth: req.depth + 1,
+					node: node.Children[i],
+					path: append(append([]byte(nil), req.path...), byte(i)),
 				})
 			}
 		}
@@ -288,7 +398,14 @@ func (s *Sync) children(req *request, object node) ([]*request, error) {
 		// Notify any external watcher of a new key/value node
 		if req.callback != nil {
 			if node, ok := (child.node).(valueNode); ok {
-				if err := req.callback(node, req.hash); err != nil {
+				var paths [][]byte
+				if len(child.path) == 2*common.HashLength {
+					paths = append(paths, hexToKeybytes(child.path))
+				} else if len(child.path) == 4*common.HashLength {
+					paths = append(paths, hexToKeybytes(child.path[:2*common.HashLength]))
+					paths = append(paths, hexToKeybytes(child.path[2*common.HashLength:]))
+				}
+				if err := req.callback(paths, child.path, node, req.hash); err != nil {
 					return nil, err
 				}
 			}
@@ -297,12 +414,14 @@ func (s *Sync) children(req *request, object node) ([]*request, error) {
 		if node, ok := (child.node).(hashNode); ok {
 			// Try to resolve the node from the local database
 			hash := common.BytesToHash(node)
-			if _, ok := s.membatch.batch[hash]; ok {
+			if s.membatch.hasNode(hash) {
 				continue
 			}
-			if s.bloom.Contains(node) {
-				// Bloom filter says this might be a duplicate, double check
-				if ok, _ := s.database.Has(node); ok {
+			if s.bloom == nil || s.bloom.Contains(node) {
+				// Bloom filter says this might be a duplicate, double check.
+				// If database says yes, then at least the trie node is present
+				// and we hold the assumption that it's NOT legacy contract code.
+				if blob := rawdb.ReadTrieNode(s.database, hash); len(blob) > 0 {
 					continue
 				}
 				// False positive, bump fault meter
@@ -310,9 +429,9 @@ func (s *Sync) children(req *request, object node) ([]*request, error) {
 			}
 			// Locally unknown node, schedule for retrieval
 			requests = append(requests, &request{
+				path:     child.path,
 				hash:     hash,
 				parents:  []*request{req},
-				depth:    child.depth,
 				callback: req.callback,
 			})
 		}
@@ -325,10 +444,15 @@ func (s *Sync) children(req *request, object node) ([]*request, error) {
 // committed themselves.
 func (s *Sync) commit(req *request) (err error) {
 	// Write the node content to the membatch
-	s.membatch.batch[req.hash] = req.data
-
-	delete(s.requests, req.hash)
-
+	if req.code {
+		s.membatch.codes[req.hash] = req.data
+		delete(s.codeReqs, req.hash)
+		s.fetches[len(req.path)]--
+	} else {
+		s.membatch.nodes[req.hash] = req.data
+		delete(s.nodeReqs, req.hash)
+		s.fetches[len(req.path)]--
+	}
 	// Check all parents for completion
 	for _, parent := range req.parents {
 		parent.deps--
diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go
index 3108b05935..1afcce21da 100644
--- a/trie/sync_bloom.go
+++ b/trie/sync_bloom.go
@@ -19,16 +19,16 @@ package trie
 import (
 	"encoding/binary"
 	"fmt"
-	"math"
 	"sync"
 	"sync/atomic"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/core/rawdb"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/metrics"
-	"github.com/steakknife/bloomfilter"
+	bloomfilter "github.com/holiman/bloomfilter/v2"
 )
 
 var (
@@ -40,22 +40,10 @@ var (
 	bloomErrorGauge = metrics.NewRegisteredGauge("trie/bloom/error", nil)
 )
 
-// syncBloomHasher is a wrapper around a byte blob to satisfy the interface API
-// requirements of the bloom library used. It's used to convert a trie hash into
-// a 64 bit mini hash.
-type syncBloomHasher []byte
-
-func (f syncBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") }
-func (f syncBloomHasher) Sum(b []byte) []byte               { panic("not implemented") }
-func (f syncBloomHasher) Reset()                            { panic("not implemented") }
-func (f syncBloomHasher) BlockSize() int                    { panic("not implemented") }
-func (f syncBloomHasher) Size() int                         { return 8 }
-func (f syncBloomHasher) Sum64() uint64                     { return binary.BigEndian.Uint64(f) }
-
 // SyncBloom is a bloom filter used during fast sync to quickly decide if a trie
-// node already exists on disk or not. It self populates from the provided disk
-// database on creation in a background thread and will only start returning live
-// results once that's finished.
+// node or contract code already exists on disk or not. It self populates from the
+// provided disk database on creation in a background thread and will only start
+// returning live results once that's finished.
 type SyncBloom struct {
 	bloom  *bloomfilter.Filter
 	inited uint32
@@ -68,7 +56,7 @@ type SyncBloom struct {
 // initializes it from the database. The bloom is hard coded to use 3 filters.
 func NewSyncBloom(memory uint64, database ethdb.Iteratee) *SyncBloom {
 	// Create the bloom filter to track known trie nodes
-	bloom, err := bloomfilter.New(memory*1024*1024*8, 3)
+	bloom, err := bloomfilter.New(memory*1024*1024*8, 4)
 	if err != nil {
 		panic(fmt.Sprintf("failed to create bloom: %v", err))
 	}
@@ -107,8 +95,13 @@ func (b *SyncBloom) init(database ethdb.Iteratee) {
 	)
 	for it.Next() && atomic.LoadUint32(&b.closed) == 0 {
 		// If the database entry is a trie node, add it to the bloom
-		if key := it.Key(); len(key) == common.HashLength {
-			b.bloom.Add(syncBloomHasher(key))
+		key := it.Key()
+		if len(key) == common.HashLength {
+			b.bloom.AddHash(binary.BigEndian.Uint64(key))
+			bloomLoadMeter.Mark(1)
+		} else if ok, hash := rawdb.IsCodeKey(key); ok {
+			// If the database entry is a contract code, add it to the bloom
+			b.bloom.AddHash(binary.BigEndian.Uint64(hash))
 			bloomLoadMeter.Mark(1)
 		}
 		// If enough time elapsed since the last iterator swap, restart
@@ -118,14 +111,14 @@ func (b *SyncBloom) init(database ethdb.Iteratee) {
 			it.Release()
 			it = database.NewIterator(nil, key)
 
-			log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
+			log.Info("Initializing state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability(), "elapsed", common.PrettyDuration(time.Since(start)))
 			swap = time.Now()
 		}
 	}
 	it.Release()
 
 	// Mark the bloom filter inited and return
-	log.Info("Initialized fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start)))
+	log.Info("Initialized state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability(), "elapsed", common.PrettyDuration(time.Since(start)))
 	atomic.StoreUint32(&b.inited, 1)
 }
 
@@ -134,7 +127,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) {
 func (b *SyncBloom) meter() {
 	for {
 		// Report the current error ration. No floats, lame, scale it up.
-		bloomErrorGauge.Update(int64(b.errorRate() * 100000))
+		bloomErrorGauge.Update(int64(b.bloom.FalsePosititveProbability() * 100000))
 
 		// Wait one second, but check termination more frequently
 		for i := 0; i < 10; i++ {
@@ -155,7 +148,7 @@ func (b *SyncBloom) Close() error {
 		b.pend.Wait()
 
 		// Wipe the bloom, but mark it "uninited" just in case someone attempts an access
-		log.Info("Deallocated fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate())
+		log.Info("Deallocated state bloom", "items", b.bloom.N(), "errorrate", b.bloom.FalsePosititveProbability())
 
 		atomic.StoreUint32(&b.inited, 0)
 		b.bloom = nil
@@ -168,7 +161,7 @@ func (b *SyncBloom) Add(hash []byte) {
 	if atomic.LoadUint32(&b.closed) == 1 {
 		return
 	}
-	b.bloom.Add(syncBloomHasher(hash))
+	b.bloom.AddHash(binary.BigEndian.Uint64(hash))
 	bloomAddMeter.Mark(1)
 }
 
@@ -186,22 +179,9 @@ func (b *SyncBloom) Contains(hash []byte) bool {
 		return true
 	}
 	// Bloom initialized, check the real one and report any successful misses
-	maybe := b.bloom.Contains(syncBloomHasher(hash))
+	maybe := b.bloom.ContainsHash(binary.BigEndian.Uint64(hash))
 	if !maybe {
 		bloomMissMeter.Mark(1)
 	}
 	return maybe
 }
-
-// errorRate calculates the probability of a random containment test returning a
-// false positive.
-//
-// We're calculating it ourselves because the bloom library we used missed a
-// parentheses in the formula and calculates it wrong. And it's discontinued...
-func (b *SyncBloom) errorRate() float64 {
-	k := float64(b.bloom.K())
-	n := float64(b.bloom.N())
-	m := float64(b.bloom.M())
-
-	return math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k)
-}
diff --git a/trie/sync_test.go b/trie/sync_test.go
index 6025b87fcc..cb3283875d 100644
--- a/trie/sync_test.go
+++ b/trie/sync_test.go
@@ -21,14 +21,15 @@ import (
 	"testing"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/ethdb/memorydb"
 )
 
 // makeTestTrie create a sample test trie to test node-wise reconstruction.
-func makeTestTrie() (*Database, *Trie, map[string][]byte) {
+func makeTestTrie() (*Database, *SecureTrie, map[string][]byte) {
 	// Create an empty trie
 	triedb := NewDatabase(memorydb.New())
-	trie, _ := New(common.Hash{}, triedb)
+	trie, _ := NewSecure(common.Hash{}, triedb)
 
 	// Fill it with some arbitrary data
 	content := make(map[string][]byte)
@@ -59,7 +60,7 @@ func makeTestTrie() (*Database, *Trie, map[string][]byte) {
 // content map.
 func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) {
 	// Check root availability and trie contents
-	trie, err := New(common.BytesToHash(root), db)
+	trie, err := NewSecure(common.BytesToHash(root), db)
 	if err != nil {
 		t.Fatalf("failed to create trie at %x: %v", root, err)
 	}
@@ -76,7 +77,7 @@ func checkTrieContents(t *testing.T, db *Database, root []byte, content map[stri
 // checkTrieConsistency checks that all nodes in a trie are indeed present.
 func checkTrieConsistency(db *Database, root common.Hash) error {
 	// Create and iterate a trie rooted in a subnode
-	trie, err := New(root, db)
+	trie, err := NewSecure(root, db)
 	if err != nil {
 		return nil // Consider a non existent state consistent
 	}
@@ -94,18 +95,21 @@ func TestEmptySync(t *testing.T) {
 	emptyB, _ := New(emptyRoot, dbB)
 
 	for i, trie := range []*Trie{emptyA, emptyB} {
-		if req := NewSync(trie.Hash(), memorydb.New(), nil, NewSyncBloom(1, memorydb.New())).Missing(1); len(req) != 0 {
-			t.Errorf("test %d: content requested for empty trie: %v", i, req)
+		sync := NewSync(trie.Hash(), memorydb.New(), nil, NewSyncBloom(1, memorydb.New()))
+		if nodes, paths, codes := sync.Missing(1); len(nodes) != 0 || len(paths) != 0 || len(codes) != 0 {
+			t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, nodes, paths, codes)
 		}
 	}
 }
 
 // Tests that given a root hash, a trie can sync iteratively on a single thread,
 // requesting retrieval tasks and returning all of them in one go.
-func TestIterativeSyncIndividual(t *testing.T) { testIterativeSync(t, 1) }
-func TestIterativeSyncBatched(t *testing.T)    { testIterativeSync(t, 100) }
+func TestIterativeSyncIndividual(t *testing.T)       { testIterativeSync(t, 1, false) }
+func TestIterativeSyncBatched(t *testing.T)          { testIterativeSync(t, 100, false) }
+func TestIterativeSyncIndividualByPath(t *testing.T) { testIterativeSync(t, 1, true) }
+func TestIterativeSyncBatchedByPath(t *testing.T)    { testIterativeSync(t, 100, true) }
 
-func testIterativeSync(t *testing.T, count int) {
+func testIterativeSync(t *testing.T, count int, bypath bool) {
 	// Create a random trie to copy
 	srcDb, srcTrie, srcData := makeTestTrie()
 
@@ -114,25 +118,51 @@ func testIterativeSync(t *testing.T, count int) {
 	triedb := NewDatabase(diskdb)
 	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
 
-	queue := append([]common.Hash{}, sched.Missing(count)...)
-	for len(queue) > 0 {
-		results := make([]SyncResult, len(queue))
-		for i, hash := range queue {
+	nodes, paths, codes := sched.Missing(count)
+	var (
+		hashQueue []common.Hash
+		pathQueue []SyncPath
+	)
+	if !bypath {
+		hashQueue = append(append(hashQueue[:0], nodes...), codes...)
+	} else {
+		hashQueue = append(hashQueue[:0], codes...)
+		pathQueue = append(pathQueue[:0], paths...)
+	}
+	for len(hashQueue)+len(pathQueue) > 0 {
+		results := make([]SyncResult, len(hashQueue)+len(pathQueue))
+		for i, hash := range hashQueue {
 			data, err := srcDb.Node(hash)
 			if err != nil {
-				t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
+				t.Fatalf("failed to retrieve node data for hash %x: %v", hash, err)
 			}
 			results[i] = SyncResult{hash, data}
 		}
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for i, path := range pathQueue {
+			data, _, err := srcTrie.TryGetNode(path[0])
+			if err != nil {
+				t.Fatalf("failed to retrieve node data for path %x: %v", path, err)
+			}
+			results[len(hashQueue)+i] = SyncResult{crypto.Keccak256Hash(data), data}
+		}
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := diskdb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
-		queue = append(queue[:0], sched.Missing(count)...)
+
+		nodes, paths, codes = sched.Missing(count)
+		if !bypath {
+			hashQueue = append(append(hashQueue[:0], nodes...), codes...)
+		} else {
+			hashQueue = append(hashQueue[:0], codes...)
+			pathQueue = append(pathQueue[:0], paths...)
+		}
 	}
 	// Cross check that the two tries are in sync
 	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
@@ -149,7 +179,9 @@ func TestIterativeDelayedSync(t *testing.T) {
 	triedb := NewDatabase(diskdb)
 	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
 
-	queue := append([]common.Hash{}, sched.Missing(10000)...)
+	nodes, _, codes := sched.Missing(10000)
+	queue := append(append([]common.Hash{}, nodes...), codes...)
+
 	for len(queue) > 0 {
 		// Sync only half of the scheduled nodes
 		results := make([]SyncResult, len(queue)/2+1)
@@ -160,15 +192,19 @@ func TestIterativeDelayedSync(t *testing.T) {
 			}
 			results[i] = SyncResult{hash, data}
 		}
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := diskdb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
-		queue = append(queue[len(results):], sched.Missing(10000)...)
+
+		nodes, _, codes = sched.Missing(10000)
+		queue = append(append(queue[len(results):], nodes...), codes...)
 	}
 	// Cross check that the two tries are in sync
 	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
@@ -190,7 +226,8 @@ func testIterativeRandomSync(t *testing.T, count int) {
 	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
 
 	queue := make(map[common.Hash]struct{})
-	for _, hash := range sched.Missing(count) {
+	nodes, _, codes := sched.Missing(count)
+	for _, hash := range append(nodes, codes...) {
 		queue[hash] = struct{}{}
 	}
 	for len(queue) > 0 {
@@ -204,16 +241,20 @@ func testIterativeRandomSync(t *testing.T, count int) {
 			results = append(results, SyncResult{hash, data})
 		}
 		// Feed the retrieved results back and queue new tasks
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := diskdb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
+
 		queue = make(map[common.Hash]struct{})
-		for _, hash := range sched.Missing(count) {
+		nodes, _, codes = sched.Missing(count)
+		for _, hash := range append(nodes, codes...) {
 			queue[hash] = struct{}{}
 		}
 	}
@@ -233,7 +274,8 @@ func TestIterativeRandomDelayedSync(t *testing.T) {
 	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
 
 	queue := make(map[common.Hash]struct{})
-	for _, hash := range sched.Missing(10000) {
+	nodes, _, codes := sched.Missing(10000)
+	for _, hash := range append(nodes, codes...) {
 		queue[hash] = struct{}{}
 	}
 	for len(queue) > 0 {
@@ -251,8 +293,10 @@ func TestIterativeRandomDelayedSync(t *testing.T) {
 			}
 		}
 		// Feed the retrieved results back and queue new tasks
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := diskdb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
@@ -262,7 +306,8 @@ func TestIterativeRandomDelayedSync(t *testing.T) {
 		for _, result := range results {
 			delete(queue, result.Hash)
 		}
-		for _, hash := range sched.Missing(10000) {
+		nodes, _, codes = sched.Missing(10000)
+		for _, hash := range append(nodes, codes...) {
 			queue[hash] = struct{}{}
 		}
 	}
@@ -281,7 +326,8 @@ func TestDuplicateAvoidanceSync(t *testing.T) {
 	triedb := NewDatabase(diskdb)
 	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
 
-	queue := append([]common.Hash{}, sched.Missing(0)...)
+	nodes, _, codes := sched.Missing(0)
+	queue := append(append([]common.Hash{}, nodes...), codes...)
 	requested := make(map[common.Hash]struct{})
 
 	for len(queue) > 0 {
@@ -298,15 +344,19 @@ func TestDuplicateAvoidanceSync(t *testing.T) {
 
 			results[i] = SyncResult{hash, data}
 		}
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := diskdb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
 			t.Fatalf("failed to commit data: %v", err)
 		}
 		batch.Write()
-		queue = append(queue[:0], sched.Missing(0)...)
+
+		nodes, _, codes = sched.Missing(0)
+		queue = append(append(queue[:0], nodes...), codes...)
 	}
 	// Cross check that the two tries are in sync
 	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
@@ -324,7 +374,9 @@ func TestIncompleteSync(t *testing.T) {
 	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
 
 	var added []common.Hash
-	queue := append([]common.Hash{}, sched.Missing(1)...)
+
+	nodes, _, codes := sched.Missing(1)
+	queue := append(append([]common.Hash{}, nodes...), codes...)
 	for len(queue) > 0 {
 		// Fetch a batch of trie nodes
 		results := make([]SyncResult, len(queue))
@@ -336,8 +388,10 @@ func TestIncompleteSync(t *testing.T) {
 			results[i] = SyncResult{hash, data}
 		}
 		// Process each of the trie nodes
-		if _, index, err := sched.Process(results); err != nil {
-			t.Fatalf("failed to process result #%d: %v", index, err)
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
 		}
 		batch := diskdb.NewBatch()
 		if err := sched.Commit(batch); err != nil {
@@ -346,15 +400,14 @@ func TestIncompleteSync(t *testing.T) {
 		batch.Write()
 		for _, result := range results {
 			added = append(added, result.Hash)
-		}
-		// Check that all known sub-tries in the synced trie are complete
-		for _, root := range added {
-			if err := checkTrieConsistency(triedb, root); err != nil {
+			// Check that all known sub-tries in the synced trie are complete
+			if err := checkTrieConsistency(triedb, result.Hash); err != nil {
 				t.Fatalf("trie inconsistent: %v", err)
 			}
 		}
 		// Fetch the next batch to retrieve
-		queue = append(queue[:0], sched.Missing(1)...)
+		nodes, _, codes = sched.Missing(1)
+		queue = append(append(queue[:0], nodes...), codes...)
 	}
 	// Sanity check that removing any node from the database is detected
 	for _, node := range added[1:] {
@@ -368,3 +421,58 @@ func TestIncompleteSync(t *testing.T) {
 		diskdb.Put(key, value)
 	}
 }
+
+// Tests that trie nodes get scheduled lexicographically when having the same
+// depth.
+func TestSyncOrdering(t *testing.T) {
+	// Create a random trie to copy
+	srcDb, srcTrie, srcData := makeTestTrie()
+
+	// Create a destination trie and sync with the scheduler, tracking the requests
+	diskdb := memorydb.New()
+	triedb := NewDatabase(diskdb)
+	sched := NewSync(srcTrie.Hash(), diskdb, nil, NewSyncBloom(1, diskdb))
+
+	nodes, paths, _ := sched.Missing(1)
+	queue := append([]common.Hash{}, nodes...)
+	reqs := append([]SyncPath{}, paths...)
+
+	for len(queue) > 0 {
+		results := make([]SyncResult, len(queue))
+		for i, hash := range queue {
+			data, err := srcDb.Node(hash)
+			if err != nil {
+				t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
+			}
+			results[i] = SyncResult{hash, data}
+		}
+		for _, result := range results {
+			if err := sched.Process(result); err != nil {
+				t.Fatalf("failed to process result %v", err)
+			}
+		}
+		batch := diskdb.NewBatch()
+		if err := sched.Commit(batch); err != nil {
+			t.Fatalf("failed to commit data: %v", err)
+		}
+		batch.Write()
+
+		nodes, paths, _ = sched.Missing(1)
+		queue = append(queue[:0], nodes...)
+		reqs = append(reqs, paths...)
+	}
+	// Cross check that the two tries are in sync
+	checkTrieContents(t, triedb, srcTrie.Hash().Bytes(), srcData)
+
+	// Check that the trie nodes have been requested path-ordered
+	for i := 0; i < len(reqs)-1; i++ {
+		if len(reqs[i]) > 1 || len(reqs[i+1]) > 1 {
+			// In the case of the trie tests, there's no storage so the tuples
+			// must always be single items. 2-tuples should be tested in state.
+			t.Errorf("Invalid request tuples: len(%v) or len(%v) > 1", reqs[i], reqs[i+1])
+		}
+		if bytes.Compare(compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0])) > 0 {
+			t.Errorf("Invalid request order: %v before %v", compactToHex(reqs[i][0]), compactToHex(reqs[i+1][0]))
+		}
+	}
+}
diff --git a/trie/trie.go b/trie/trie.go
index 78e2eff534..7ed235fa8a 100644
--- a/trie/trie.go
+++ b/trie/trie.go
@@ -19,6 +19,7 @@ package trie
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"sync"
 
@@ -36,9 +37,20 @@ var (
 )
 
 // LeafCallback is a callback type invoked when a trie operation reaches a leaf
-// node. It's used by state sync and commit to allow handling external references
-// between account and storage tries.
-type LeafCallback func(leaf []byte, parent common.Hash) error
+// node.
+//
+// The paths is a path tuple identifying a particular trie node either in a single
+// trie (account) or a layered trie (account -> storage). Each path in the tuple
+// is in the raw format(32 bytes).
+//
+// The hexpath is a composite hexary path identifying the trie node. All the key
+// bytes are converted to the hexary nibbles and composited with the parent path
+// if the trie node is in a layered trie.
+//
+// It's used by state sync and commit to allow handling external references
+// between account and storage tries. And also it's used in the state healing
+// for extracting the raw states(leaf nodes) with corresponding paths.
+type LeafCallback func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error
 
 // Trie is a Merkle Patricia Trie.
 // The zero value is an empty trie with no database.
@@ -102,8 +114,7 @@ func (t *Trie) Get(key []byte) []byte {
 // The value bytes must not be modified by the caller.
 // If a node was not found in the database, a MissingNodeError is returned.
 func (t *Trie) TryGet(key []byte) ([]byte, error) {
-	key = keybytesToHex(key)
-	value, newroot, didResolve, err := t.tryGet(t.root, key, 0)
+	value, newroot, didResolve, err := t.tryGet(t.root, keybytesToHex(key), 0)
 	if err == nil && didResolve {
 		t.root = newroot
 	}
@@ -146,6 +157,83 @@ func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode
 	}
 }
 
+// TryGetNode attempts to retrieve a trie node by compact-encoded path. It is not
+// possible to use keybyte-encoding as the path might contain odd nibbles.
+func (t *Trie) TryGetNode(path []byte) ([]byte, int, error) {
+	item, newroot, resolved, err := t.tryGetNode(t.root, compactToHex(path), 0)
+	if err != nil {
+		return nil, resolved, err
+	}
+	if resolved > 0 {
+		t.root = newroot
+	}
+	if item == nil {
+		return nil, resolved, nil
+	}
+	return item, resolved, err
+}
+
+func (t *Trie) tryGetNode(origNode node, path []byte, pos int) (item []byte, newnode node, resolved int, err error) {
+	// If we reached the requested path, return the current node
+	if pos >= len(path) {
+		// Although we most probably have the original node expanded, encoding
+		// that into consensus form can be nasty (needs to cascade down) and
+		// time consuming. Instead, just pull the hash up from disk directly.
+		var hash hashNode
+		if node, ok := origNode.(hashNode); ok {
+			hash = node
+		} else {
+			hash, _ = origNode.cache()
+		}
+		if hash == nil {
+			return nil, origNode, 0, errors.New("non-consensus node")
+		}
+		blob, err := t.db.Node(common.BytesToHash(hash))
+		return blob, origNode, 1, err
+	}
+	// Path still needs to be traversed, descend into children
+	switch n := (origNode).(type) {
+	case nil:
+		// Non-existent path requested, abort
+		return nil, nil, 0, nil
+
+	case valueNode:
+		// Path prematurely ended, abort
+		return nil, nil, 0, nil
+
+	case *shortNode:
+		if len(path)-pos < len(n.Key) || !bytes.Equal(n.Key, path[pos:pos+len(n.Key)]) {
+			// Path branches off from short node
+			return nil, n, 0, nil
+		}
+		item, newnode, resolved, err = t.tryGetNode(n.Val, path, pos+len(n.Key))
+		if err == nil && resolved > 0 {
+			n = n.copy()
+			n.Val = newnode
+		}
+		return item, n, resolved, err
+
+	case *fullNode:
+		item, newnode, resolved, err = t.tryGetNode(n.Children[path[pos]], path, pos+1)
+		if err == nil && resolved > 0 {
+			n = n.copy()
+			n.Children[path[pos]] = newnode
+		}
+		return item, n, resolved, err
+
+	case hashNode:
+		child, err := t.resolveHash(n, path[:pos])
+		if err != nil {
+			return nil, n, 1, err
+		}
+		item, newnode, resolved, err := t.tryGetNode(child, path, pos)
+		return item, newnode, resolved + 1, err
+
+	default:
+		panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode))
+	}
+}
+
 // Update associates key with value in the trie. Subsequent calls to
 // Get will return value. If value has length zero, any existing value
 // is deleted from the trie and calls to Get will return nil.
@@ -411,7 +499,7 @@ func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
 // Hash returns the root hash of the trie. It does not write to the
 // database and can be used even if the trie doesn't have one.
 func (t *Trie) Hash() common.Hash {
-	hash, cached, _ := t.hashRoot(nil)
+	hash, cached, _ := t.hashRoot()
 	t.root = cached
 	return common.BytesToHash(hash.(hashNode))
 }
@@ -425,13 +513,16 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
 	if t.root == nil {
 		return emptyRoot, nil
 	}
+	// Derive the hash for all dirty nodes first. We hold the assumption
+	// in the following procedure that all nodes are hashed.
 	rootHash := t.Hash()
 	h := newCommitter()
 	defer returnCommitterToPool(h)
+
 	// Do a quick check if we really need to commit, before we spin
 	// up goroutines. This can happen e.g. if we load a trie for reading storage
 	// values, but don't write to it.
-	if !h.commitNeeded(t.root) {
+	if _, dirty := t.root.cache(); !dirty {
 		return rootHash, nil
 	}
 	var wg sync.WaitGroup
@@ -462,7 +553,7 @@ func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) {
 }
 
 // hashRoot calculates the root hash of the given trie
-func (t *Trie) hashRoot(db *Database) (node, node, error) {
+func (t *Trie) hashRoot() (node, node, error) {
 	if t.root == nil {
 		return hashNode(emptyRoot.Bytes()), nil, nil
 	}
@@ -473,3 +564,9 @@ func (t *Trie) hashRoot(db *Database) (node, node, error) {
 	t.unhashed = 0
 	return hashed, cached, nil
 }
+
+// Reset drops the referenced root node and cleans all internal state.
+func (t *Trie) Reset() {
+	t.root = nil
+	t.unhashed = 0
+}
diff --git a/trie/trie_test.go b/trie/trie_test.go
index 172572dddc..492b423c2f 100644
--- a/trie/trie_test.go
+++ b/trie/trie_test.go
@@ -19,7 +19,9 @@ package trie
 import (
 	"bytes"
 	"encoding/binary"
+	"errors"
 	"fmt"
+	"hash"
 	"io/ioutil"
 	"math/big"
 	"math/rand"
@@ -31,9 +33,11 @@ import (
 	"github.com/davecgh/go-spew/spew"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/ethdb/leveldb"
 	"github.com/ethereum/go-ethereum/ethdb/memorydb"
 	"github.com/ethereum/go-ethereum/rlp"
+	"golang.org/x/crypto/sha3"
 )
 
 func init() {
@@ -88,7 +92,7 @@ func testMissingNode(t *testing.T, memonly bool) {
 	updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf")
 	root, _ := trie.Commit(nil)
 	if !memonly {
-		triedb.Commit(root, true)
+		triedb.Commit(root, true, nil)
 	}
 
 	trie, _ = New(root, triedb)
@@ -318,7 +322,7 @@ func TestLargeValue(t *testing.T) {
 
 // TestRandomCases tests som cases that were found via random fuzzing
 func TestRandomCases(t *testing.T) {
-	var rt []randTestStep = []randTestStep{
+	var rt = []randTestStep{
 		{op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")},                                                                                                 // step 0
 		{op: 6, key: common.Hex2Bytes(""), value: common.Hex2Bytes("")},                                                                                                 // step 1
 		{op: 0, key: common.Hex2Bytes("d51b182b95d677e5f1c82508c0228de96b73092d78ce78b2230cd948674f66fd1483bd"), value: common.Hex2Bytes("0000000000000002")},           // step 2
@@ -565,7 +569,7 @@ func BenchmarkCommitAfterHash(b *testing.B) {
 		benchmarkCommitAfterHash(b, nil)
 	})
 	var a account
-	onleaf := func(leaf []byte, parent common.Hash) error {
+	onleaf := func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error {
 		rlp.DecodeBytes(leaf, &a)
 		return nil
 	}
@@ -590,21 +594,20 @@ func benchmarkCommitAfterHash(b *testing.B, onleaf LeafCallback) {
 
 func TestTinyTrie(t *testing.T) {
 	// Create a realistic account trie to hash
-	_, accounts := makeAccounts(10000)
+	_, accounts := makeAccounts(5)
 	trie := newEmpty()
 	trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3])
-	if exp, root := common.HexToHash("4fa6efd292cffa2db0083b8bedd23add2798ae73802442f52486e95c3df7111c"), trie.Hash(); exp != root {
-		t.Fatalf("1: got %x, exp %x", root, exp)
+	if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root {
+		t.Errorf("1: got %x, exp %x", root, exp)
 	}
 	trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4])
-	if exp, root := common.HexToHash("cb5fb1213826dad9e604f095f8ceb5258fe6b5c01805ce6ef019a50699d2d479"), trie.Hash(); exp != root {
-		t.Fatalf("2: got %x, exp %x", root, exp)
+	if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root {
+		t.Errorf("2: got %x, exp %x", root, exp)
 	}
 	trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4])
-	if exp, root := common.HexToHash("ed7e06b4010057d8703e7b9a160a6d42cf4021f9020da3c8891030349a646987"), trie.Hash(); exp != root {
-		t.Fatalf("3: got %x, exp %x", root, exp)
+	if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root {
+		t.Errorf("3: got %x, exp %x", root, exp)
 	}
-
 	checktr, _ := New(common.Hash{}, trie.db)
 	it := NewIterator(trie.NodeIterator(nil))
 	for it.Next() {
@@ -626,7 +629,7 @@ func TestCommitAfterHash(t *testing.T) {
 	trie.Hash()
 	trie.Commit(nil)
 	root := trie.Hash()
-	exp := common.HexToHash("e5e9c29bb50446a4081e6d1d748d2892c6101c1e883a1f77cf21d4094b697822")
+	exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6")
 	if exp != root {
 		t.Errorf("got %x, exp %x", root, exp)
 	}
@@ -642,23 +645,257 @@ func makeAccounts(size int) (addresses [][20]byte, accounts [][]byte) {
 	// Create a realistic account trie to hash
 	addresses = make([][20]byte, size)
 	for i := 0; i < len(addresses); i++ {
-		for j := 0; j < len(addresses[i]); j++ {
-			addresses[i][j] = byte(random.Intn(256))
-		}
+		data := make([]byte, 20)
+		random.Read(data)
+		copy(addresses[i][:], data)
 	}
 	accounts = make([][]byte, len(addresses))
 	for i := 0; i < len(accounts); i++ {
 		var (
-			nonce   = uint64(random.Int63())
-			balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil))
-			root    = emptyRoot
-			code    = crypto.Keccak256(nil)
+			nonce = uint64(random.Int63())
+			root  = emptyRoot
+			code  = crypto.Keccak256(nil)
 		)
-		accounts[i], _ = rlp.EncodeToBytes(&account{nonce, balance, root, code})
+		// The big.Rand function is not deterministic with regards to 64 vs 32 bit systems,
+		// and will consume different amount of data from the rand source.
+		//balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil))
+		// Therefore, we instead just read via byte buffer
+		numBytes := random.Uint32() % 33 // [0, 32] bytes
+		balanceBytes := make([]byte, numBytes)
+		random.Read(balanceBytes)
+		balance := new(big.Int).SetBytes(balanceBytes)
+		data, _ := rlp.EncodeToBytes(&account{nonce, balance, root, code})
+		accounts[i] = data
 	}
 	return addresses, accounts
 }
 
+// spongeDb is a dummy db backend which accumulates writes in a sponge
+type spongeDb struct {
+	sponge  hash.Hash
+	id      string
+	journal []string
+}
+
+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 {
+	valbrief := value
+	if len(valbrief) > 8 {
+		valbrief = valbrief[:8]
+	}
+	s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, key[:8], len(value), valbrief))
+	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 }
+
+// TestCommitSequence tests that the trie.Commit operation writes the elements of the trie
+// in the expected order, and calls the callbacks in the expected order.
+// The test data was based on the 'master' code, and is basically random. It can be used
+// to check whether changes to the trie modifies the write order or data in any way.
+func TestCommitSequence(t *testing.T) {
+	for i, tc := range []struct {
+		count              int
+		expWriteSeqHash    []byte
+		expCallbackSeqHash []byte
+	}{
+		{20, common.FromHex("873c78df73d60e59d4a2bcf3716e8bfe14554549fea2fc147cb54129382a8066"),
+			common.FromHex("ff00f91ac05df53b82d7f178d77ada54fd0dca64526f537034a5dbe41b17df2a")},
+		{200, common.FromHex("ba03d891bb15408c940eea5ee3d54d419595102648d02774a0268d892add9c8e"),
+			common.FromHex("f3cd509064c8d319bbdd1c68f511850a902ad275e6ed5bea11547e23d492a926")},
+		{2000, common.FromHex("f7a184f20df01c94f09537401d11e68d97ad0c00115233107f51b9c287ce60c7"),
+			common.FromHex("ff795ea898ba1e4cfed4a33b4cf5535a347a02cf931f88d88719faf810f9a1c9")},
+	} {
+		addresses, accounts := makeAccounts(tc.count)
+		// This spongeDb is used to check the sequence of disk-db-writes
+		s := &spongeDb{sponge: sha3.NewLegacyKeccak256()}
+		db := NewDatabase(s)
+		trie, _ := New(common.Hash{}, db)
+		// Another sponge is used to check the callback-sequence
+		callbackSponge := sha3.NewLegacyKeccak256()
+		// Fill the trie with elements
+		for i := 0; i < tc.count; i++ {
+			trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
+		}
+		// Flush trie -> database
+		root, _ := trie.Commit(nil)
+		// Flush memdb -> disk (sponge)
+		db.Commit(root, false, func(c common.Hash) {
+			// And spongify the callback-order
+			callbackSponge.Write(c[:])
+		})
+		if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
+			t.Errorf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp)
+		}
+		if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) {
+			t.Errorf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp)
+		}
+	}
+}
+
+// TestCommitSequenceRandomBlobs is identical to TestCommitSequence
+// but uses random blobs instead of 'accounts'
+func TestCommitSequenceRandomBlobs(t *testing.T) {
+	for i, tc := range []struct {
+		count              int
+		expWriteSeqHash    []byte
+		expCallbackSeqHash []byte
+	}{
+		{20, common.FromHex("8e4a01548551d139fa9e833ebc4e66fc1ba40a4b9b7259d80db32cff7b64ebbc"),
+			common.FromHex("450238d73bc36dc6cc6f926987e5428535e64be403877c4560e238a52749ba24")},
+		{200, common.FromHex("6869b4e7b95f3097a19ddb30ff735f922b915314047e041614df06958fc50554"),
+			common.FromHex("0ace0b03d6cb8c0b82f6289ef5b1a1838306b455a62dafc63cada8e2924f2550")},
+		{2000, common.FromHex("444200e6f4e2df49f77752f629a96ccf7445d4698c164f962bbd85a0526ef424"),
+			common.FromHex("117d30dafaa62a1eed498c3dfd70982b377ba2b46dd3e725ed6120c80829e518")},
+	} {
+		prng := rand.New(rand.NewSource(int64(i)))
+		// This spongeDb is used to check the sequence of disk-db-writes
+		s := &spongeDb{sponge: sha3.NewLegacyKeccak256()}
+		db := NewDatabase(s)
+		trie, _ := New(common.Hash{}, db)
+		// Another sponge is used to check the callback-sequence
+		callbackSponge := sha3.NewLegacyKeccak256()
+		// Fill the trie with elements
+		for i := 0; i < tc.count; i++ {
+			key := make([]byte, 32)
+			var val []byte
+			// 50% short elements, 50% large elements
+			if prng.Intn(2) == 0 {
+				val = make([]byte, 1+prng.Intn(32))
+			} else {
+				val = make([]byte, 1+prng.Intn(4096))
+			}
+			prng.Read(key)
+			prng.Read(val)
+			trie.Update(key, val)
+		}
+		// Flush trie -> database
+		root, _ := trie.Commit(nil)
+		// Flush memdb -> disk (sponge)
+		db.Commit(root, false, func(c common.Hash) {
+			// And spongify the callback-order
+			callbackSponge.Write(c[:])
+		})
+		if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) {
+			t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", i, got, exp)
+		}
+		if got, exp := callbackSponge.Sum(nil), tc.expCallbackSeqHash; !bytes.Equal(got, exp) {
+			t.Fatalf("test %d, call back sequence wrong:\ngot: %x exp %x\n", i, got, exp)
+		}
+	}
+}
+
+func TestCommitSequenceStackTrie(t *testing.T) {
+	for count := 1; count < 200; count++ {
+		prng := rand.New(rand.NewSource(int64(count)))
+		// This spongeDb is used to check the sequence of disk-db-writes
+		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)
+		// Fill the trie with elements
+		for i := 1; i < count; i++ {
+			// For the stack trie, we need to do inserts in proper order
+			key := make([]byte, 32)
+			binary.BigEndian.PutUint64(key, uint64(i))
+			var val []byte
+			// 50% short elements, 50% large elements
+			if prng.Intn(2) == 0 {
+				val = make([]byte, 1+prng.Intn(32))
+			} else {
+				val = make([]byte, 1+prng.Intn(1024))
+			}
+			prng.Read(val)
+			trie.TryUpdate(key, val)
+			stTrie.TryUpdate(key, val)
+		}
+		// 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)
+		}
+		if got, exp := stackTrieSponge.sponge.Sum(nil), s.sponge.Sum(nil); !bytes.Equal(got, exp) {
+			// Show the journal
+			t.Logf("Expected:")
+			for i, v := range s.journal {
+				t.Logf("op %d: %v", i, v)
+			}
+			t.Logf("Stacktrie:")
+			for i, v := range stackTrieSponge.journal {
+				t.Logf("op %d: %v", i, v)
+			}
+			t.Fatalf("test %d, disk write sequence wrong:\ngot %x exp %x\n", count, got, exp)
+		}
+	}
+}
+
+// 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.
 // 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
@@ -823,7 +1060,7 @@ func tempDB() (string, *Database) {
 	if err != nil {
 		panic(fmt.Sprintf("can't create temporary directory: %v", err))
 	}
-	diskdb, err := leveldb.New(dir, 256, 0, "")
+	diskdb, err := leveldb.New(dir, 256, 0, "", false)
 	if err != nil {
 		panic(fmt.Sprintf("can't create temporary database: %v", err))
 	}
diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go
deleted file mode 100644
index 7312bbe23d..0000000000
--- a/whisper/mailserver/mailserver.go
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2017 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 .
-
-// Package mailserver provides a naive, example mailserver implementation
-package mailserver
-
-import (
-	"encoding/binary"
-	"fmt"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/rlp"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
-	"github.com/syndtr/goleveldb/leveldb"
-	"github.com/syndtr/goleveldb/leveldb/errors"
-	"github.com/syndtr/goleveldb/leveldb/opt"
-	"github.com/syndtr/goleveldb/leveldb/util"
-)
-
-// WMailServer represents the state data of the mailserver.
-type WMailServer struct {
-	db  *leveldb.DB
-	w   *whisper.Whisper
-	pow float64
-	key []byte
-}
-
-type DBKey struct {
-	timestamp uint32
-	hash      common.Hash
-	raw       []byte
-}
-
-// NewDbKey is a helper function that creates a levelDB
-// key from a hash and an integer.
-func NewDbKey(t uint32, h common.Hash) *DBKey {
-	const sz = common.HashLength + 4
-	var k DBKey
-	k.timestamp = t
-	k.hash = h
-	k.raw = make([]byte, sz)
-	binary.BigEndian.PutUint32(k.raw, k.timestamp)
-	copy(k.raw[4:], k.hash[:])
-	return &k
-}
-
-// Init initializes the mail server.
-func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) error {
-	var err error
-	if len(path) == 0 {
-		return fmt.Errorf("DB file is not specified")
-	}
-
-	if len(password) == 0 {
-		return fmt.Errorf("password is not specified")
-	}
-
-	s.db, err = leveldb.OpenFile(path, &opt.Options{OpenFilesCacheCapacity: 32})
-	if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
-		s.db, err = leveldb.RecoverFile(path, nil)
-	}
-	if err != nil {
-		return fmt.Errorf("open DB file: %s", err)
-	}
-
-	s.w = shh
-	s.pow = pow
-
-	MailServerKeyID, err := s.w.AddSymKeyFromPassword(password)
-	if err != nil {
-		return fmt.Errorf("create symmetric key: %s", err)
-	}
-	s.key, err = s.w.GetSymKey(MailServerKeyID)
-	if err != nil {
-		return fmt.Errorf("save symmetric key: %s", err)
-	}
-	return nil
-}
-
-// Close cleans up before shutdown.
-func (s *WMailServer) Close() {
-	if s.db != nil {
-		s.db.Close()
-	}
-}
-
-// Archive stores the
-func (s *WMailServer) Archive(env *whisper.Envelope) {
-	key := NewDbKey(env.Expiry-env.TTL, env.Hash())
-	rawEnvelope, err := rlp.EncodeToBytes(env)
-	if err != nil {
-		log.Error(fmt.Sprintf("rlp.EncodeToBytes failed: %s", err))
-	} else {
-		err = s.db.Put(key.raw, rawEnvelope, nil)
-		if err != nil {
-			log.Error(fmt.Sprintf("Writing to DB failed: %s", err))
-		}
-	}
-}
-
-// DeliverMail responds with saved messages upon request by the
-// messages' owner.
-func (s *WMailServer) DeliverMail(peer *whisper.Peer, request *whisper.Envelope) {
-	if peer == nil {
-		log.Error("Whisper peer is nil")
-		return
-	}
-
-	ok, lower, upper, bloom := s.validateRequest(peer.ID(), request)
-	if ok {
-		s.processRequest(peer, lower, upper, bloom)
-	}
-}
-
-func (s *WMailServer) processRequest(peer *whisper.Peer, lower, upper uint32, bloom []byte) []*whisper.Envelope {
-	ret := make([]*whisper.Envelope, 0)
-	var err error
-	var zero common.Hash
-	kl := NewDbKey(lower, zero)
-	ku := NewDbKey(upper+1, zero) // LevelDB is exclusive, while the Whisper API is inclusive
-	i := s.db.NewIterator(&util.Range{Start: kl.raw, Limit: ku.raw}, nil)
-	defer i.Release()
-
-	for i.Next() {
-		var envelope whisper.Envelope
-		err = rlp.DecodeBytes(i.Value(), &envelope)
-		if err != nil {
-			log.Error(fmt.Sprintf("RLP decoding failed: %s", err))
-		}
-
-		if whisper.BloomFilterMatch(bloom, envelope.Bloom()) {
-			if peer == nil {
-				// used for test purposes
-				ret = append(ret, &envelope)
-			} else {
-				err = s.w.SendP2PDirect(peer, &envelope)
-				if err != nil {
-					log.Error(fmt.Sprintf("Failed to send direct message to peer: %s", err))
-					return nil
-				}
-			}
-		}
-	}
-
-	err = i.Error()
-	if err != nil {
-		log.Error(fmt.Sprintf("Level DB iterator error: %s", err))
-	}
-
-	return ret
-}
-
-func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) (bool, uint32, uint32, []byte) {
-	if s.pow > 0.0 && request.PoW() < s.pow {
-		return false, 0, 0, nil
-	}
-
-	f := whisper.Filter{KeySym: s.key}
-	decrypted := request.Open(&f)
-	if decrypted == nil {
-		log.Warn("Failed to decrypt p2p request")
-		return false, 0, 0, nil
-	}
-
-	src := crypto.FromECDSAPub(decrypted.Src)
-	if len(src)-len(peerID) == 1 {
-		src = src[1:]
-	}
-
-	// if you want to check the signature, you can do it here. e.g.:
-	// if !bytes.Equal(peerID, src) {
-	if src == nil {
-		log.Warn("Wrong signature of p2p request")
-		return false, 0, 0, nil
-	}
-
-	var bloom []byte
-	payloadSize := len(decrypted.Payload)
-	if payloadSize < 8 {
-		log.Warn("Undersized p2p request")
-		return false, 0, 0, nil
-	} else if payloadSize == 8 {
-		bloom = whisper.MakeFullNodeBloom()
-	} else if payloadSize < 8+whisper.BloomFilterSize {
-		log.Warn("Undersized bloom filter in p2p request")
-		return false, 0, 0, nil
-	} else {
-		bloom = decrypted.Payload[8 : 8+whisper.BloomFilterSize]
-	}
-
-	lower := binary.BigEndian.Uint32(decrypted.Payload[:4])
-	upper := binary.BigEndian.Uint32(decrypted.Payload[4:8])
-	return true, lower, upper, bloom
-}
diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go
deleted file mode 100644
index 4f80e550aa..0000000000
--- a/whisper/mailserver/server_test.go
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright 2017 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 .
-
-package mailserver
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"encoding/binary"
-	"io/ioutil"
-	"math/rand"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
-)
-
-const powRequirement = 0.00001
-
-var keyID string
-var shh *whisper.Whisper
-var seed = time.Now().Unix()
-
-type ServerTestParams struct {
-	topic whisper.TopicType
-	low   uint32
-	upp   uint32
-	key   *ecdsa.PrivateKey
-}
-
-func assert(statement bool, text string, t *testing.T) {
-	if !statement {
-		t.Fatal(text)
-	}
-}
-
-func TestDBKey(t *testing.T) {
-	var h common.Hash
-	i := uint32(time.Now().Unix())
-	k := NewDbKey(i, h)
-	assert(len(k.raw) == common.HashLength+4, "wrong DB key length", t)
-	assert(byte(i%0x100) == k.raw[3], "raw representation should be big endian", t)
-	assert(byte(i/0x1000000) == k.raw[0], "big endian expected", t)
-}
-
-func generateEnvelope(t *testing.T) *whisper.Envelope {
-	h := crypto.Keccak256Hash([]byte("test sample data"))
-	params := &whisper.MessageParams{
-		KeySym:   h[:],
-		Topic:    whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
-		Payload:  []byte("test payload"),
-		PoW:      powRequirement,
-		WorkTime: 2,
-	}
-
-	msg, err := whisper.NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed to wrap with seed %d: %s.", seed, err)
-	}
-	return env
-}
-
-func TestMailServer(t *testing.T) {
-	const password = "password_for_this_test"
-	const dbPath = "whisper-server-test"
-
-	dir, err := ioutil.TempDir("", dbPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	var server WMailServer
-	shh = whisper.New(&whisper.DefaultConfig)
-	shh.RegisterServer(&server)
-
-	err = server.Init(shh, dir, password, powRequirement)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer server.Close()
-
-	keyID, err = shh.AddSymKeyFromPassword(password)
-	if err != nil {
-		t.Fatalf("Failed to create symmetric key for mail request: %s", err)
-	}
-
-	rand.Seed(seed)
-	env := generateEnvelope(t)
-	server.Archive(env)
-	deliverTest(t, &server, env)
-}
-
-func deliverTest(t *testing.T, server *WMailServer, env *whisper.Envelope) {
-	id, err := shh.NewKeyPair()
-	if err != nil {
-		t.Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
-	}
-	testPeerID, err := shh.GetPrivateKey(id)
-	if err != nil {
-		t.Fatalf("failed to retrieve new key pair with seed %d: %s.", seed, err)
-	}
-	birth := env.Expiry - env.TTL
-	p := &ServerTestParams{
-		topic: env.Topic,
-		low:   birth - 1,
-		upp:   birth + 1,
-		key:   testPeerID,
-	}
-
-	singleRequest(t, server, env, p, true)
-
-	p.low, p.upp = birth+1, 0xffffffff
-	singleRequest(t, server, env, p, false)
-
-	p.low, p.upp = 0, birth-1
-	singleRequest(t, server, env, p, false)
-
-	p.low = birth - 1
-	p.upp = birth + 1
-	p.topic[0] = 0xFF
-	singleRequest(t, server, env, p, false)
-}
-
-func singleRequest(t *testing.T, server *WMailServer, env *whisper.Envelope, p *ServerTestParams, expect bool) {
-	request := createRequest(t, p)
-	src := crypto.FromECDSAPub(&p.key.PublicKey)
-	ok, lower, upper, bloom := server.validateRequest(src, request)
-	if !ok {
-		t.Fatalf("request validation failed, seed: %d.", seed)
-	}
-	if lower != p.low {
-		t.Fatalf("request validation failed (lower bound), seed: %d.", seed)
-	}
-	if upper != p.upp {
-		t.Fatalf("request validation failed (upper bound), seed: %d.", seed)
-	}
-	expectedBloom := whisper.TopicToBloom(p.topic)
-	if !bytes.Equal(bloom, expectedBloom) {
-		t.Fatalf("request validation failed (topic), seed: %d.", seed)
-	}
-
-	var exist bool
-	mail := server.processRequest(nil, p.low, p.upp, bloom)
-	for _, msg := range mail {
-		if msg.Hash() == env.Hash() {
-			exist = true
-			break
-		}
-	}
-
-	if exist != expect {
-		t.Fatalf("error: exist = %v, seed: %d.", exist, seed)
-	}
-
-	src[0]++
-	ok, lower, upper, _ = server.validateRequest(src, request)
-	if !ok {
-		// request should be valid regardless of signature
-		t.Fatalf("request validation false negative, seed: %d (lower: %d, upper: %d).", seed, lower, upper)
-	}
-}
-
-func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope {
-	bloom := whisper.TopicToBloom(p.topic)
-	data := make([]byte, 8)
-	binary.BigEndian.PutUint32(data, p.low)
-	binary.BigEndian.PutUint32(data[4:], p.upp)
-	data = append(data, bloom...)
-
-	key, err := shh.GetSymKey(keyID)
-	if err != nil {
-		t.Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
-	}
-
-	params := &whisper.MessageParams{
-		KeySym:   key,
-		Topic:    p.topic,
-		Payload:  data,
-		PoW:      powRequirement * 2,
-		WorkTime: 2,
-		Src:      p.key,
-	}
-
-	msg, err := whisper.NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed to wrap with seed %d: %s.", seed, err)
-	}
-	return env
-}
diff --git a/whisper/shhclient/client.go b/whisper/shhclient/client.go
deleted file mode 100644
index a814154e47..0000000000
--- a/whisper/shhclient/client.go
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2017 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 .
-
-package shhclient
-
-import (
-	"context"
-
-	"github.com/ethereum/go-ethereum"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	"github.com/ethereum/go-ethereum/rpc"
-	whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
-)
-
-// Client defines typed wrappers for the Whisper v6 RPC API.
-type Client struct {
-	c *rpc.Client
-}
-
-// Dial connects a client to the given URL.
-func Dial(rawurl string) (*Client, error) {
-	c, err := rpc.Dial(rawurl)
-	if err != nil {
-		return nil, err
-	}
-	return NewClient(c), nil
-}
-
-// NewClient creates a client that uses the given RPC client.
-func NewClient(c *rpc.Client) *Client {
-	return &Client{c}
-}
-
-// Version returns the Whisper sub-protocol version.
-func (sc *Client) Version(ctx context.Context) (string, error) {
-	var result string
-	err := sc.c.CallContext(ctx, &result, "shh_version")
-	return result, err
-}
-
-// Info returns diagnostic information about the whisper node.
-func (sc *Client) Info(ctx context.Context) (whisper.Info, error) {
-	var info whisper.Info
-	err := sc.c.CallContext(ctx, &info, "shh_info")
-	return info, err
-}
-
-// SetMaxMessageSize sets the maximal message size allowed by this node. Incoming
-// and outgoing messages with a larger size will be rejected. Whisper message size
-// can never exceed the limit imposed by the underlying P2P protocol (10 Mb).
-func (sc *Client) SetMaxMessageSize(ctx context.Context, size uint32) error {
-	var ignored bool
-	return sc.c.CallContext(ctx, &ignored, "shh_setMaxMessageSize", size)
-}
-
-// SetMinimumPoW (experimental) sets the minimal PoW required by this node.
-// This experimental function was introduced for the future dynamic adjustment of
-// PoW requirement. If the node is overwhelmed with messages, it should raise the
-// PoW requirement and notify the peers. The new value should be set relative to
-// the old value (e.g. double). The old value could be obtained via shh_info call.
-func (sc *Client) SetMinimumPoW(ctx context.Context, pow float64) error {
-	var ignored bool
-	return sc.c.CallContext(ctx, &ignored, "shh_setMinPoW", pow)
-}
-
-// MarkTrustedPeer marks specific peer trusted, which will allow it to send historic (expired) messages.
-// Note This function is not adding new nodes, the node needs to exists as a peer.
-func (sc *Client) MarkTrustedPeer(ctx context.Context, enode string) error {
-	var ignored bool
-	return sc.c.CallContext(ctx, &ignored, "shh_markTrustedPeer", enode)
-}
-
-// NewKeyPair generates a new public and private key pair for message decryption and encryption.
-// It returns an identifier that can be used to refer to the key.
-func (sc *Client) NewKeyPair(ctx context.Context) (string, error) {
-	var id string
-	return id, sc.c.CallContext(ctx, &id, "shh_newKeyPair")
-}
-
-// AddPrivateKey stored the key pair, and returns its ID.
-func (sc *Client) AddPrivateKey(ctx context.Context, key []byte) (string, error) {
-	var id string
-	return id, sc.c.CallContext(ctx, &id, "shh_addPrivateKey", hexutil.Bytes(key))
-}
-
-// DeleteKeyPair delete the specifies key.
-func (sc *Client) DeleteKeyPair(ctx context.Context, id string) (string, error) {
-	var ignored bool
-	return id, sc.c.CallContext(ctx, &ignored, "shh_deleteKeyPair", id)
-}
-
-// HasKeyPair returns an indication if the node has a private key or
-// key pair matching the given ID.
-func (sc *Client) HasKeyPair(ctx context.Context, id string) (bool, error) {
-	var has bool
-	return has, sc.c.CallContext(ctx, &has, "shh_hasKeyPair", id)
-}
-
-// PublicKey return the public key for a key ID.
-func (sc *Client) PublicKey(ctx context.Context, id string) ([]byte, error) {
-	var key hexutil.Bytes
-	return []byte(key), sc.c.CallContext(ctx, &key, "shh_getPublicKey", id)
-}
-
-// PrivateKey return the private key for a key ID.
-func (sc *Client) PrivateKey(ctx context.Context, id string) ([]byte, error) {
-	var key hexutil.Bytes
-	return []byte(key), sc.c.CallContext(ctx, &key, "shh_getPrivateKey", id)
-}
-
-// NewSymmetricKey generates a random symmetric key and returns its identifier.
-// Can be used encrypting and decrypting messages where the key is known to both parties.
-func (sc *Client) NewSymmetricKey(ctx context.Context) (string, error) {
-	var id string
-	return id, sc.c.CallContext(ctx, &id, "shh_newSymKey")
-}
-
-// AddSymmetricKey stores the key, and returns its identifier.
-func (sc *Client) AddSymmetricKey(ctx context.Context, key []byte) (string, error) {
-	var id string
-	return id, sc.c.CallContext(ctx, &id, "shh_addSymKey", hexutil.Bytes(key))
-}
-
-// GenerateSymmetricKeyFromPassword generates the key from password, stores it, and returns its identifier.
-func (sc *Client) GenerateSymmetricKeyFromPassword(ctx context.Context, passwd string) (string, error) {
-	var id string
-	return id, sc.c.CallContext(ctx, &id, "shh_generateSymKeyFromPassword", passwd)
-}
-
-// HasSymmetricKey returns an indication if the key associated with the given id is stored in the node.
-func (sc *Client) HasSymmetricKey(ctx context.Context, id string) (bool, error) {
-	var found bool
-	return found, sc.c.CallContext(ctx, &found, "shh_hasSymKey", id)
-}
-
-// GetSymmetricKey returns the symmetric key associated with the given identifier.
-func (sc *Client) GetSymmetricKey(ctx context.Context, id string) ([]byte, error) {
-	var key hexutil.Bytes
-	return []byte(key), sc.c.CallContext(ctx, &key, "shh_getSymKey", id)
-}
-
-// DeleteSymmetricKey deletes the symmetric key associated with the given identifier.
-func (sc *Client) DeleteSymmetricKey(ctx context.Context, id string) error {
-	var ignored bool
-	return sc.c.CallContext(ctx, &ignored, "shh_deleteSymKey", id)
-}
-
-// Post a message onto the network.
-func (sc *Client) Post(ctx context.Context, message whisper.NewMessage) (string, error) {
-	var hash string
-	return hash, sc.c.CallContext(ctx, &hash, "shh_post", message)
-}
-
-// SubscribeMessages subscribes to messages that match the given criteria. This method
-// is only supported on bi-directional connections such as websockets and IPC.
-// NewMessageFilter uses polling and is supported over HTTP.
-func (sc *Client) SubscribeMessages(ctx context.Context, criteria whisper.Criteria, ch chan<- *whisper.Message) (ethereum.Subscription, error) {
-	return sc.c.ShhSubscribe(ctx, ch, "messages", criteria)
-}
-
-// NewMessageFilter creates a filter within the node. This filter can be used to poll
-// for new messages (see FilterMessages) that satisfy the given criteria. A filter can
-// timeout when it was polled for in whisper.filterTimeout.
-func (sc *Client) NewMessageFilter(ctx context.Context, criteria whisper.Criteria) (string, error) {
-	var id string
-	return id, sc.c.CallContext(ctx, &id, "shh_newMessageFilter", criteria)
-}
-
-// DeleteMessageFilter removes the filter associated with the given id.
-func (sc *Client) DeleteMessageFilter(ctx context.Context, id string) error {
-	var ignored bool
-	return sc.c.CallContext(ctx, &ignored, "shh_deleteMessageFilter", id)
-}
-
-// FilterMessages retrieves all messages that are received between the last call to
-// this function and match the criteria that where given when the filter was created.
-func (sc *Client) FilterMessages(ctx context.Context, id string) ([]*whisper.Message, error) {
-	var messages []*whisper.Message
-	return messages, sc.c.CallContext(ctx, &messages, "shh_getFilterMessages", id)
-}
diff --git a/whisper/whisperv6/api.go b/whisper/whisperv6/api.go
deleted file mode 100644
index d6d4c8d3de..0000000000
--- a/whisper/whisperv6/api.go
+++ /dev/null
@@ -1,593 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"context"
-	"crypto/ecdsa"
-	"errors"
-	"fmt"
-	"sync"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p/enode"
-	"github.com/ethereum/go-ethereum/rpc"
-)
-
-// List of errors
-var (
-	ErrSymAsym              = errors.New("specify either a symmetric or an asymmetric key")
-	ErrInvalidSymmetricKey  = errors.New("invalid symmetric key")
-	ErrInvalidPublicKey     = errors.New("invalid public key")
-	ErrInvalidSigningPubKey = errors.New("invalid signing public key")
-	ErrTooLowPoW            = errors.New("message rejected, PoW too low")
-	ErrNoTopics             = errors.New("missing topic(s)")
-)
-
-// PublicWhisperAPI provides the whisper RPC service that can be
-// use publicly without security implications.
-type PublicWhisperAPI struct {
-	w *Whisper
-
-	mu       sync.Mutex
-	lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
-}
-
-// NewPublicWhisperAPI create a new RPC whisper service.
-func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI {
-	api := &PublicWhisperAPI{
-		w:        w,
-		lastUsed: make(map[string]time.Time),
-	}
-	return api
-}
-
-// Version returns the Whisper sub-protocol version.
-func (api *PublicWhisperAPI) Version(ctx context.Context) string {
-	return ProtocolVersionStr
-}
-
-// Info contains diagnostic information.
-type Info struct {
-	Memory         int     `json:"memory"`         // Memory size of the floating messages in bytes.
-	Messages       int     `json:"messages"`       // Number of floating messages.
-	MinPow         float64 `json:"minPow"`         // Minimal accepted PoW
-	MaxMessageSize uint32  `json:"maxMessageSize"` // Maximum accepted message size
-}
-
-// Info returns diagnostic information about the whisper node.
-func (api *PublicWhisperAPI) Info(ctx context.Context) Info {
-	stats := api.w.Stats()
-	return Info{
-		Memory:         stats.memoryUsed,
-		Messages:       len(api.w.messageQueue) + len(api.w.p2pMsgQueue),
-		MinPow:         api.w.MinPow(),
-		MaxMessageSize: api.w.MaxMessageSize(),
-	}
-}
-
-// SetMaxMessageSize sets the maximum message size that is accepted.
-// Upper limit is defined by MaxMessageSize.
-func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) (bool, error) {
-	return true, api.w.SetMaxMessageSize(size)
-}
-
-// SetMinPoW sets the minimum PoW, and notifies the peers.
-func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) {
-	return true, api.w.SetMinimumPoW(pow)
-}
-
-// SetBloomFilter sets the new value of bloom filter, and notifies the peers.
-func (api *PublicWhisperAPI) SetBloomFilter(ctx context.Context, bloom hexutil.Bytes) (bool, error) {
-	return true, api.w.SetBloomFilter(bloom)
-}
-
-// MarkTrustedPeer marks a peer trusted, which will allow it to send historic (expired) messages.
-// Note: This function is not adding new nodes, the node needs to exists as a peer.
-func (api *PublicWhisperAPI) MarkTrustedPeer(ctx context.Context, url string) (bool, error) {
-	n, err := enode.Parse(enode.ValidSchemes, url)
-	if err != nil {
-		return false, err
-	}
-	return true, api.w.AllowP2PMessagesFromPeer(n.ID().Bytes())
-}
-
-// NewKeyPair generates a new public and private key pair for message decryption and encryption.
-// It returns an ID that can be used to refer to the keypair.
-func (api *PublicWhisperAPI) NewKeyPair(ctx context.Context) (string, error) {
-	return api.w.NewKeyPair()
-}
-
-// AddPrivateKey imports the given private key.
-func (api *PublicWhisperAPI) AddPrivateKey(ctx context.Context, privateKey hexutil.Bytes) (string, error) {
-	key, err := crypto.ToECDSA(privateKey)
-	if err != nil {
-		return "", err
-	}
-	return api.w.AddKeyPair(key)
-}
-
-// DeleteKeyPair removes the key with the given key if it exists.
-func (api *PublicWhisperAPI) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
-	if ok := api.w.DeleteKeyPair(key); ok {
-		return true, nil
-	}
-	return false, fmt.Errorf("key pair %s not found", key)
-}
-
-// HasKeyPair returns an indication if the node has a key pair that is associated with the given id.
-func (api *PublicWhisperAPI) HasKeyPair(ctx context.Context, id string) bool {
-	return api.w.HasKeyPair(id)
-}
-
-// GetPublicKey returns the public key associated with the given key. The key is the hex
-// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
-func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexutil.Bytes, error) {
-	key, err := api.w.GetPrivateKey(id)
-	if err != nil {
-		return hexutil.Bytes{}, err
-	}
-	return crypto.FromECDSAPub(&key.PublicKey), nil
-}
-
-// GetPrivateKey returns the private key associated with the given key. The key is the hex
-// encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62.
-func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) {
-	key, err := api.w.GetPrivateKey(id)
-	if err != nil {
-		return hexutil.Bytes{}, err
-	}
-	return crypto.FromECDSA(key), nil
-}
-
-// NewSymKey generate a random symmetric key.
-// It returns an ID that can be used to refer to the key.
-// Can be used encrypting and decrypting messages where the key is known to both parties.
-func (api *PublicWhisperAPI) NewSymKey(ctx context.Context) (string, error) {
-	return api.w.GenerateSymKey()
-}
-
-// AddSymKey import a symmetric key.
-// It returns an ID that can be used to refer to the key.
-// Can be used encrypting and decrypting messages where the key is known to both parties.
-func (api *PublicWhisperAPI) AddSymKey(ctx context.Context, key hexutil.Bytes) (string, error) {
-	return api.w.AddSymKeyDirect([]byte(key))
-}
-
-// GenerateSymKeyFromPassword derive a key from the given password, stores it, and returns its ID.
-func (api *PublicWhisperAPI) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
-	return api.w.AddSymKeyFromPassword(passwd)
-}
-
-// HasSymKey returns an indication if the node has a symmetric key associated with the given key.
-func (api *PublicWhisperAPI) HasSymKey(ctx context.Context, id string) bool {
-	return api.w.HasSymKey(id)
-}
-
-// GetSymKey returns the symmetric key associated with the given id.
-func (api *PublicWhisperAPI) GetSymKey(ctx context.Context, id string) (hexutil.Bytes, error) {
-	return api.w.GetSymKey(id)
-}
-
-// DeleteSymKey deletes the symmetric key that is associated with the given id.
-func (api *PublicWhisperAPI) DeleteSymKey(ctx context.Context, id string) bool {
-	return api.w.DeleteSymKey(id)
-}
-
-// MakeLightClient turns the node into light client, which does not forward
-// any incoming messages, and sends only messages originated in this node.
-func (api *PublicWhisperAPI) MakeLightClient(ctx context.Context) bool {
-	api.w.SetLightClientMode(true)
-	return api.w.LightClientMode()
-}
-
-// CancelLightClient cancels light client mode.
-func (api *PublicWhisperAPI) CancelLightClient(ctx context.Context) bool {
-	api.w.SetLightClientMode(false)
-	return !api.w.LightClientMode()
-}
-
-//go:generate gencodec -type NewMessage -field-override newMessageOverride -out gen_newmessage_json.go
-
-// NewMessage represents a new whisper message that is posted through the RPC.
-type NewMessage struct {
-	SymKeyID   string    `json:"symKeyID"`
-	PublicKey  []byte    `json:"pubKey"`
-	Sig        string    `json:"sig"`
-	TTL        uint32    `json:"ttl"`
-	Topic      TopicType `json:"topic"`
-	Payload    []byte    `json:"payload"`
-	Padding    []byte    `json:"padding"`
-	PowTime    uint32    `json:"powTime"`
-	PowTarget  float64   `json:"powTarget"`
-	TargetPeer string    `json:"targetPeer"`
-}
-
-type newMessageOverride struct {
-	PublicKey hexutil.Bytes
-	Payload   hexutil.Bytes
-	Padding   hexutil.Bytes
-}
-
-// Post posts a message on the Whisper network.
-// returns the hash of the message in case of success.
-func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) {
-	var (
-		symKeyGiven = len(req.SymKeyID) > 0
-		pubKeyGiven = len(req.PublicKey) > 0
-		err         error
-	)
-
-	// user must specify either a symmetric or an asymmetric key
-	if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
-		return nil, ErrSymAsym
-	}
-
-	params := &MessageParams{
-		TTL:      req.TTL,
-		Payload:  req.Payload,
-		Padding:  req.Padding,
-		WorkTime: req.PowTime,
-		PoW:      req.PowTarget,
-		Topic:    req.Topic,
-	}
-
-	// Set key that is used to sign the message
-	if len(req.Sig) > 0 {
-		if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil {
-			return nil, err
-		}
-	}
-
-	// Set symmetric key that is used to encrypt the message
-	if symKeyGiven {
-		if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption
-			return nil, ErrNoTopics
-		}
-		if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
-			return nil, err
-		}
-		if !validateDataIntegrity(params.KeySym, aesKeyLength) {
-			return nil, ErrInvalidSymmetricKey
-		}
-	}
-
-	// Set asymmetric key that is used to encrypt the message
-	if pubKeyGiven {
-		if params.Dst, err = crypto.UnmarshalPubkey(req.PublicKey); err != nil {
-			return nil, ErrInvalidPublicKey
-		}
-	}
-
-	// encrypt and sent message
-	whisperMsg, err := NewSentMessage(params)
-	if err != nil {
-		return nil, err
-	}
-
-	var result []byte
-	env, err := whisperMsg.Wrap(params)
-	if err != nil {
-		return nil, err
-	}
-
-	// send to specific node (skip PoW check)
-	if len(req.TargetPeer) > 0 {
-		n, err := enode.Parse(enode.ValidSchemes, req.TargetPeer)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse target peer: %s", err)
-		}
-		err = api.w.SendP2PMessage(n.ID().Bytes(), env)
-		if err == nil {
-			hash := env.Hash()
-			result = hash[:]
-		}
-		return result, err
-	}
-
-	// ensure that the message PoW meets the node's minimum accepted PoW
-	if req.PowTarget < api.w.MinPow() {
-		return nil, ErrTooLowPoW
-	}
-
-	err = api.w.Send(env)
-	if err == nil {
-		hash := env.Hash()
-		result = hash[:]
-	}
-	return result, err
-}
-
-//go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go
-
-// Criteria holds various filter options for inbound messages.
-type Criteria struct {
-	SymKeyID     string      `json:"symKeyID"`
-	PrivateKeyID string      `json:"privateKeyID"`
-	Sig          []byte      `json:"sig"`
-	MinPow       float64     `json:"minPow"`
-	Topics       []TopicType `json:"topics"`
-	AllowP2P     bool        `json:"allowP2P"`
-}
-
-type criteriaOverride struct {
-	Sig hexutil.Bytes
-}
-
-// Messages set up a subscription that fires events when messages arrive that match
-// the given set of criteria.
-func (api *PublicWhisperAPI) Messages(ctx context.Context, crit Criteria) (*rpc.Subscription, error) {
-	var (
-		symKeyGiven = len(crit.SymKeyID) > 0
-		pubKeyGiven = len(crit.PrivateKeyID) > 0
-		err         error
-	)
-
-	// ensure that the RPC connection supports subscriptions
-	notifier, supported := rpc.NotifierFromContext(ctx)
-	if !supported {
-		return nil, rpc.ErrNotificationsUnsupported
-	}
-
-	// user must specify either a symmetric or an asymmetric key
-	if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) {
-		return nil, ErrSymAsym
-	}
-
-	filter := Filter{
-		PoW:      crit.MinPow,
-		Messages: make(map[common.Hash]*ReceivedMessage),
-		AllowP2P: crit.AllowP2P,
-	}
-
-	if len(crit.Sig) > 0 {
-		if filter.Src, err = crypto.UnmarshalPubkey(crit.Sig); err != nil {
-			return nil, ErrInvalidSigningPubKey
-		}
-	}
-
-	for i, bt := range crit.Topics {
-		if len(bt) == 0 || len(bt) > 4 {
-			return nil, fmt.Errorf("subscribe: topic %d has wrong size: %d", i, len(bt))
-		}
-		filter.Topics = append(filter.Topics, bt[:])
-	}
-
-	// listen for message that are encrypted with the given symmetric key
-	if symKeyGiven {
-		if len(filter.Topics) == 0 {
-			return nil, ErrNoTopics
-		}
-		key, err := api.w.GetSymKey(crit.SymKeyID)
-		if err != nil {
-			return nil, err
-		}
-		if !validateDataIntegrity(key, aesKeyLength) {
-			return nil, ErrInvalidSymmetricKey
-		}
-		filter.KeySym = key
-		filter.SymKeyHash = crypto.Keccak256Hash(filter.KeySym)
-	}
-
-	// listen for messages that are encrypted with the given public key
-	if pubKeyGiven {
-		filter.KeyAsym, err = api.w.GetPrivateKey(crit.PrivateKeyID)
-		if err != nil || filter.KeyAsym == nil {
-			return nil, ErrInvalidPublicKey
-		}
-	}
-
-	id, err := api.w.Subscribe(&filter)
-	if err != nil {
-		return nil, err
-	}
-
-	// create subscription and start waiting for message events
-	rpcSub := notifier.CreateSubscription()
-	go func() {
-		// for now poll internally, refactor whisper internal for channel support
-		ticker := time.NewTicker(250 * time.Millisecond)
-		defer ticker.Stop()
-
-		for {
-			select {
-			case <-ticker.C:
-				if filter := api.w.GetFilter(id); filter != nil {
-					for _, rpcMessage := range toMessage(filter.Retrieve()) {
-						if err := notifier.Notify(rpcSub.ID, rpcMessage); err != nil {
-							log.Error("Failed to send notification", "err", err)
-						}
-					}
-				}
-			case <-rpcSub.Err():
-				api.w.Unsubscribe(id)
-				return
-			case <-notifier.Closed():
-				api.w.Unsubscribe(id)
-				return
-			}
-		}
-	}()
-
-	return rpcSub, nil
-}
-
-//go:generate gencodec -type Message -field-override messageOverride -out gen_message_json.go
-
-// Message is the RPC representation of a whisper message.
-type Message struct {
-	Sig       []byte    `json:"sig,omitempty"`
-	TTL       uint32    `json:"ttl"`
-	Timestamp uint32    `json:"timestamp"`
-	Topic     TopicType `json:"topic"`
-	Payload   []byte    `json:"payload"`
-	Padding   []byte    `json:"padding"`
-	PoW       float64   `json:"pow"`
-	Hash      []byte    `json:"hash"`
-	Dst       []byte    `json:"recipientPublicKey,omitempty"`
-}
-
-type messageOverride struct {
-	Sig     hexutil.Bytes
-	Payload hexutil.Bytes
-	Padding hexutil.Bytes
-	Hash    hexutil.Bytes
-	Dst     hexutil.Bytes
-}
-
-// ToWhisperMessage converts an internal message into an API version.
-func ToWhisperMessage(message *ReceivedMessage) *Message {
-	msg := Message{
-		Payload:   message.Payload,
-		Padding:   message.Padding,
-		Timestamp: message.Sent,
-		TTL:       message.TTL,
-		PoW:       message.PoW,
-		Hash:      message.EnvelopeHash.Bytes(),
-		Topic:     message.Topic,
-	}
-
-	if message.Dst != nil {
-		b := crypto.FromECDSAPub(message.Dst)
-		if b != nil {
-			msg.Dst = b
-		}
-	}
-
-	if isMessageSigned(message.Raw[0]) {
-		b := crypto.FromECDSAPub(message.SigToPubKey())
-		if b != nil {
-			msg.Sig = b
-		}
-	}
-
-	return &msg
-}
-
-// toMessage converts a set of messages to its RPC representation.
-func toMessage(messages []*ReceivedMessage) []*Message {
-	msgs := make([]*Message, len(messages))
-	for i, msg := range messages {
-		msgs[i] = ToWhisperMessage(msg)
-	}
-	return msgs
-}
-
-// GetFilterMessages returns the messages that match the filter criteria and
-// are received between the last poll and now.
-func (api *PublicWhisperAPI) GetFilterMessages(id string) ([]*Message, error) {
-	api.mu.Lock()
-	f := api.w.GetFilter(id)
-	if f == nil {
-		api.mu.Unlock()
-		return nil, fmt.Errorf("filter not found")
-	}
-	api.lastUsed[id] = time.Now()
-	api.mu.Unlock()
-
-	receivedMessages := f.Retrieve()
-	messages := make([]*Message, 0, len(receivedMessages))
-	for _, msg := range receivedMessages {
-		messages = append(messages, ToWhisperMessage(msg))
-	}
-
-	return messages, nil
-}
-
-// DeleteMessageFilter deletes a filter.
-func (api *PublicWhisperAPI) DeleteMessageFilter(id string) (bool, error) {
-	api.mu.Lock()
-	defer api.mu.Unlock()
-
-	delete(api.lastUsed, id)
-	return true, api.w.Unsubscribe(id)
-}
-
-// NewMessageFilter creates a new filter that can be used to poll for
-// (new) messages that satisfy the given criteria.
-func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) {
-	var (
-		src     *ecdsa.PublicKey
-		keySym  []byte
-		keyAsym *ecdsa.PrivateKey
-		topics  [][]byte
-
-		symKeyGiven  = len(req.SymKeyID) > 0
-		asymKeyGiven = len(req.PrivateKeyID) > 0
-
-		err error
-	)
-
-	// user must specify either a symmetric or an asymmetric key
-	if (symKeyGiven && asymKeyGiven) || (!symKeyGiven && !asymKeyGiven) {
-		return "", ErrSymAsym
-	}
-
-	if len(req.Sig) > 0 {
-		if src, err = crypto.UnmarshalPubkey(req.Sig); err != nil {
-			return "", ErrInvalidSigningPubKey
-		}
-	}
-
-	if symKeyGiven {
-		if keySym, err = api.w.GetSymKey(req.SymKeyID); err != nil {
-			return "", err
-		}
-		if !validateDataIntegrity(keySym, aesKeyLength) {
-			return "", ErrInvalidSymmetricKey
-		}
-	}
-
-	if asymKeyGiven {
-		if keyAsym, err = api.w.GetPrivateKey(req.PrivateKeyID); err != nil {
-			return "", err
-		}
-	}
-
-	if len(req.Topics) > 0 {
-		topics = make([][]byte, len(req.Topics))
-		for i, topic := range req.Topics {
-			topics[i] = make([]byte, TopicLength)
-			copy(topics[i], topic[:])
-		}
-	}
-
-	f := &Filter{
-		Src:      src,
-		KeySym:   keySym,
-		KeyAsym:  keyAsym,
-		PoW:      req.MinPow,
-		AllowP2P: req.AllowP2P,
-		Topics:   topics,
-		Messages: make(map[common.Hash]*ReceivedMessage),
-	}
-
-	id, err := api.w.Subscribe(f)
-	if err != nil {
-		return "", err
-	}
-
-	api.mu.Lock()
-	api.lastUsed[id] = time.Now()
-	api.mu.Unlock()
-
-	return id, nil
-}
diff --git a/whisper/whisperv6/api_test.go b/whisper/whisperv6/api_test.go
deleted file mode 100644
index 6d7157f57f..0000000000
--- a/whisper/whisperv6/api_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 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 .
-
-package whisperv6
-
-import (
-	"bytes"
-	"testing"
-	"time"
-)
-
-func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) {
-	w := New(nil)
-
-	keyID, err := w.GenerateSymKey()
-	if err != nil {
-		t.Fatalf("Error generating symmetric key: %v", err)
-	}
-	api := PublicWhisperAPI{
-		w:        w,
-		lastUsed: make(map[string]time.Time),
-	}
-
-	t1 := [4]byte{0xde, 0xea, 0xbe, 0xef}
-	t2 := [4]byte{0xca, 0xfe, 0xde, 0xca}
-
-	crit := Criteria{
-		SymKeyID: keyID,
-		Topics:   []TopicType{TopicType(t1), TopicType(t2)},
-	}
-
-	_, err = api.NewMessageFilter(crit)
-	if err != nil {
-		t.Fatalf("Error creating the filter: %v", err)
-	}
-
-	found := false
-	candidates := w.filters.getWatchersByTopic(TopicType(t1))
-	for _, f := range candidates {
-		if len(f.Topics) == 2 {
-			if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) {
-				found = true
-			}
-		}
-	}
-
-	if !found {
-		t.Fatalf("Could not find filter with both topics")
-	}
-}
diff --git a/whisper/whisperv6/benchmarks_test.go b/whisper/whisperv6/benchmarks_test.go
deleted file mode 100644
index 0473179da5..0000000000
--- a/whisper/whisperv6/benchmarks_test.go
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"crypto/sha256"
-	"testing"
-
-	"github.com/ethereum/go-ethereum/crypto"
-	"golang.org/x/crypto/pbkdf2"
-)
-
-func BenchmarkDeriveKeyMaterial(b *testing.B) {
-	for i := 0; i < b.N; i++ {
-		pbkdf2.Key([]byte("test"), nil, 65356, aesKeyLength, sha256.New)
-	}
-}
-
-func BenchmarkEncryptionSym(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	for i := 0; i < b.N; i++ {
-		msg, _ := NewSentMessage(params)
-		_, err := msg.Wrap(params)
-		if err != nil {
-			b.Errorf("failed Wrap with seed %d: %s.", seed, err)
-			b.Errorf("i = %d, len(msg.Raw) = %d, params.Payload = %d.", i, len(msg.Raw), len(params.Payload))
-			return
-		}
-	}
-}
-
-func BenchmarkEncryptionAsym(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-	params.KeySym = nil
-	params.Dst = &key.PublicKey
-
-	for i := 0; i < b.N; i++ {
-		msg, _ := NewSentMessage(params)
-		_, err := msg.Wrap(params)
-		if err != nil {
-			b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-		}
-	}
-}
-
-func BenchmarkDecryptionSymValid(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, _ := NewSentMessage(params)
-	env, err := msg.Wrap(params)
-	if err != nil {
-		b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-	f := Filter{KeySym: params.KeySym}
-
-	for i := 0; i < b.N; i++ {
-		msg := env.Open(&f)
-		if msg == nil {
-			b.Fatalf("failed to open with seed %d.", seed)
-		}
-	}
-}
-
-func BenchmarkDecryptionSymInvalid(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, _ := NewSentMessage(params)
-	env, err := msg.Wrap(params)
-	if err != nil {
-		b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-	f := Filter{KeySym: []byte("arbitrary stuff here")}
-
-	for i := 0; i < b.N; i++ {
-		msg := env.Open(&f)
-		if msg != nil {
-			b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
-		}
-	}
-}
-
-func BenchmarkDecryptionAsymValid(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-	f := Filter{KeyAsym: key}
-	params.KeySym = nil
-	params.Dst = &key.PublicKey
-	msg, _ := NewSentMessage(params)
-	env, err := msg.Wrap(params)
-	if err != nil {
-		b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	for i := 0; i < b.N; i++ {
-		msg := env.Open(&f)
-		if msg == nil {
-			b.Fatalf("fail to open, seed: %d.", seed)
-		}
-	}
-}
-
-func BenchmarkDecryptionAsymInvalid(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-	params.KeySym = nil
-	params.Dst = &key.PublicKey
-	msg, _ := NewSentMessage(params)
-	env, err := msg.Wrap(params)
-	if err != nil {
-		b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	key, err = crypto.GenerateKey()
-	if err != nil {
-		b.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-	f := Filter{KeyAsym: key}
-
-	for i := 0; i < b.N; i++ {
-		msg := env.Open(&f)
-		if msg != nil {
-			b.Fatalf("opened envelope with invalid key, seed: %d.", seed)
-		}
-	}
-}
-
-func increment(x []byte) {
-	for i := 0; i < len(x); i++ {
-		x[i]++
-		if x[i] != 0 {
-			break
-		}
-	}
-}
-
-func BenchmarkPoW(b *testing.B) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	params.Payload = make([]byte, 32)
-	params.PoW = 10.0
-	params.TTL = 1
-
-	for i := 0; i < b.N; i++ {
-		increment(params.Payload)
-		msg, _ := NewSentMessage(params)
-		_, err := msg.Wrap(params)
-		if err != nil {
-			b.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-		}
-	}
-}
diff --git a/whisper/whisperv6/config.go b/whisper/whisperv6/config.go
deleted file mode 100644
index 38eb9551cc..0000000000
--- a/whisper/whisperv6/config.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2017 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 .
-
-package whisperv6
-
-// Config represents the configuration state of a whisper node.
-type Config struct {
-	MaxMessageSize                        uint32  `toml:",omitempty"`
-	MinimumAcceptedPOW                    float64 `toml:",omitempty"`
-	RestrictConnectionBetweenLightClients bool    `toml:",omitempty"`
-}
-
-// DefaultConfig represents (shocker!) the default configuration.
-var DefaultConfig = Config{
-	MaxMessageSize:                        DefaultMaxMessageSize,
-	MinimumAcceptedPOW:                    DefaultMinimumPoW,
-	RestrictConnectionBetweenLightClients: true,
-}
diff --git a/whisper/whisperv6/doc.go b/whisper/whisperv6/doc.go
deleted file mode 100644
index 44c0c3271c..0000000000
--- a/whisper/whisperv6/doc.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2016 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 .
-
-/*
-Package whisper implements the Whisper protocol (version 6).
-
-Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP).
-As such it may be likened and compared to both, not dissimilar to the
-matter/energy duality (apologies to physicists for the blatant abuse of a
-fundamental and beautiful natural principle).
-
-Whisper is a pure identity-based messaging system. Whisper provides a low-level
-(non-application-specific) but easily-accessible API without being based upon
-or prejudiced by the low-level hardware attributes and characteristics,
-particularly the notion of singular endpoints.
-*/
-
-// Contains the Whisper protocol constant definitions
-
-package whisperv6
-
-import (
-	"time"
-
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-// Whisper protocol parameters
-const (
-	ProtocolVersion    = uint64(6) // Protocol version number
-	ProtocolVersionStr = "6.0"     // The same, as a string
-	ProtocolName       = "shh"     // Nickname of the protocol in geth
-
-	// whisper protocol message codes, according to EIP-627
-	statusCode           = 0   // used by whisper protocol
-	messagesCode         = 1   // normal whisper message
-	powRequirementCode   = 2   // PoW requirement
-	bloomFilterExCode    = 3   // bloom filter exchange
-	p2pRequestCode       = 126 // peer-to-peer message, used by Dapp protocol
-	p2pMessageCode       = 127 // peer-to-peer message (to be consumed by the peer, but not forwarded any further)
-	NumberOfMessageCodes = 128
-
-	SizeMask      = byte(3) // mask used to extract the size of payload size field from the flags
-	signatureFlag = byte(4)
-
-	TopicLength     = 4                      // in bytes
-	signatureLength = crypto.SignatureLength // in bytes
-	aesKeyLength    = 32                     // in bytes
-	aesNonceLength  = 12                     // in bytes; for more info please see cipher.gcmStandardNonceSize & aesgcm.NonceSize()
-	keyIDSize       = 32                     // in bytes
-	BloomFilterSize = 64                     // in bytes
-	flagsLength     = 1
-
-	EnvelopeHeaderLength = 20
-
-	MaxMessageSize        = uint32(10 * 1024 * 1024) // maximum accepted size of a message.
-	DefaultMaxMessageSize = uint32(1024 * 1024)
-	DefaultMinimumPoW     = 0.2
-
-	padSizeLimit      = 256 // just an arbitrary number, could be changed without breaking the protocol
-	messageQueueLimit = 1024
-
-	expirationCycle   = time.Second
-	transmissionCycle = 300 * time.Millisecond
-
-	DefaultTTL           = 50 // seconds
-	DefaultSyncAllowance = 10 // seconds
-)
-
-// MailServer represents a mail server, capable of
-// archiving the old messages for subsequent delivery
-// to the peers. Any implementation must ensure that both
-// functions are thread-safe. Also, they must return ASAP.
-// DeliverMail should use directMessagesCode for delivery,
-// in order to bypass the expiry checks.
-type MailServer interface {
-	Archive(env *Envelope)
-	DeliverMail(whisperPeer *Peer, request *Envelope)
-}
diff --git a/whisper/whisperv6/envelope.go b/whisper/whisperv6/envelope.go
deleted file mode 100644
index 5b6925edb3..0000000000
--- a/whisper/whisperv6/envelope.go
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright 2016 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 .
-
-// Contains the Whisper protocol Envelope element.
-
-package whisperv6
-
-import (
-	"crypto/ecdsa"
-	"encoding/binary"
-	"fmt"
-	gmath "math"
-	"math/big"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/crypto/ecies"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-// Envelope represents a clear-text data packet to transmit through the Whisper
-// network. Its contents may or may not be encrypted and signed.
-type Envelope struct {
-	Expiry uint32
-	TTL    uint32
-	Topic  TopicType
-	Data   []byte
-	Nonce  uint64
-
-	pow float64 // Message-specific PoW as described in the Whisper specification.
-
-	// the following variables should not be accessed directly, use the corresponding function instead: Hash(), Bloom()
-	hash  common.Hash // Cached hash of the envelope to avoid rehashing every time.
-	bloom []byte
-}
-
-// size returns the size of envelope as it is sent (i.e. public fields only)
-func (e *Envelope) size() int {
-	return EnvelopeHeaderLength + len(e.Data)
-}
-
-// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce.
-func (e *Envelope) rlpWithoutNonce() []byte {
-	res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Data})
-	return res
-}
-
-// NewEnvelope wraps a Whisper message with expiration and destination data
-// included into an envelope for network forwarding.
-func NewEnvelope(ttl uint32, topic TopicType, msg *sentMessage) *Envelope {
-	env := Envelope{
-		Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()),
-		TTL:    ttl,
-		Topic:  topic,
-		Data:   msg.Raw,
-		Nonce:  0,
-	}
-
-	return &env
-}
-
-// Seal closes the envelope by spending the requested amount of time as a proof
-// of work on hashing the data.
-func (e *Envelope) Seal(options *MessageParams) error {
-	if options.PoW == 0 {
-		// PoW is not required
-		return nil
-	}
-
-	var target, bestLeadingZeros int
-	if options.PoW < 0 {
-		// target is not set - the function should run for a period
-		// of time specified in WorkTime param. Since we can predict
-		// the execution time, we can also adjust Expiry.
-		e.Expiry += options.WorkTime
-	} else {
-		target = e.powToFirstBit(options.PoW)
-	}
-
-	rlp := e.rlpWithoutNonce()
-	buf := make([]byte, len(rlp)+8)
-	copy(buf, rlp)
-	asAnInt := new(big.Int)
-
-	finish := time.Now().Add(time.Duration(options.WorkTime) * time.Second).UnixNano()
-	for nonce := uint64(0); time.Now().UnixNano() < finish; {
-		for i := 0; i < 1024; i++ {
-			binary.BigEndian.PutUint64(buf[len(rlp):], nonce)
-			h := crypto.Keccak256(buf)
-			asAnInt.SetBytes(h)
-			leadingZeros := 256 - asAnInt.BitLen()
-			if leadingZeros > bestLeadingZeros {
-				e.Nonce, bestLeadingZeros = nonce, leadingZeros
-				if target > 0 && bestLeadingZeros >= target {
-					return nil
-				}
-			}
-			nonce++
-		}
-	}
-
-	if target > 0 && bestLeadingZeros < target {
-		return fmt.Errorf("failed to reach the PoW target, specified pow time (%d seconds) was insufficient", options.WorkTime)
-	}
-
-	return nil
-}
-
-// PoW computes (if necessary) and returns the proof of work target
-// of the envelope.
-func (e *Envelope) PoW() float64 {
-	if e.pow == 0 {
-		e.calculatePoW(0)
-	}
-	return e.pow
-}
-
-func (e *Envelope) calculatePoW(diff uint32) {
-	rlp := e.rlpWithoutNonce()
-	buf := make([]byte, len(rlp)+8)
-	copy(buf, rlp)
-	binary.BigEndian.PutUint64(buf[len(rlp):], e.Nonce)
-	powHash := new(big.Int).SetBytes(crypto.Keccak256(buf))
-	leadingZeroes := 256 - powHash.BitLen()
-	x := gmath.Pow(2, float64(leadingZeroes))
-	x /= float64(len(rlp))
-	x /= float64(e.TTL + diff)
-	e.pow = x
-}
-
-func (e *Envelope) powToFirstBit(pow float64) int {
-	x := pow
-	x *= float64(e.size())
-	x *= float64(e.TTL)
-	bits := gmath.Log2(x)
-	bits = gmath.Ceil(bits)
-	res := int(bits)
-	if res < 1 {
-		res = 1
-	}
-	return res
-}
-
-// Hash returns the SHA3 hash of the envelope, calculating it if not yet done.
-func (e *Envelope) Hash() common.Hash {
-	if (e.hash == common.Hash{}) {
-		encoded, _ := rlp.EncodeToBytes(e)
-		e.hash = crypto.Keccak256Hash(encoded)
-	}
-	return e.hash
-}
-
-// DecodeRLP decodes an Envelope from an RLP data stream.
-func (e *Envelope) DecodeRLP(s *rlp.Stream) error {
-	raw, err := s.Raw()
-	if err != nil {
-		return err
-	}
-	// The decoding of Envelope uses the struct fields but also needs
-	// to compute the hash of the whole RLP-encoded envelope. This
-	// type has the same structure as Envelope but is not an
-	// rlp.Decoder (does not implement DecodeRLP function).
-	// Only public members will be encoded.
-	type rlpenv Envelope
-	if err := rlp.DecodeBytes(raw, (*rlpenv)(e)); err != nil {
-		return err
-	}
-	e.hash = crypto.Keccak256Hash(raw)
-	return nil
-}
-
-// OpenAsymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
-func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, error) {
-	message := &ReceivedMessage{Raw: e.Data}
-	err := message.decryptAsymmetric(key)
-	switch err {
-	case nil:
-		return message, nil
-	case ecies.ErrInvalidPublicKey: // addressed to somebody else
-		return nil, err
-	default:
-		return nil, fmt.Errorf("unable to open envelope, decrypt failed: %v", err)
-	}
-}
-
-// OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key.
-func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) {
-	msg = &ReceivedMessage{Raw: e.Data}
-	err = msg.decryptSymmetric(key)
-	if err != nil {
-		msg = nil
-	}
-	return msg, err
-}
-
-// Open tries to decrypt an envelope, and populates the message fields in case of success.
-func (e *Envelope) Open(watcher *Filter) (msg *ReceivedMessage) {
-	if watcher == nil {
-		return nil
-	}
-
-	// The API interface forbids filters doing both symmetric and asymmetric encryption.
-	if watcher.expectsAsymmetricEncryption() && watcher.expectsSymmetricEncryption() {
-		return nil
-	}
-
-	if watcher.expectsAsymmetricEncryption() {
-		msg, _ = e.OpenAsymmetric(watcher.KeyAsym)
-		if msg != nil {
-			msg.Dst = &watcher.KeyAsym.PublicKey
-		}
-	} else if watcher.expectsSymmetricEncryption() {
-		msg, _ = e.OpenSymmetric(watcher.KeySym)
-		if msg != nil {
-			msg.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
-		}
-	}
-
-	if msg != nil {
-		ok := msg.ValidateAndParse()
-		if !ok {
-			return nil
-		}
-		msg.Topic = e.Topic
-		msg.PoW = e.PoW()
-		msg.TTL = e.TTL
-		msg.Sent = e.Expiry - e.TTL
-		msg.EnvelopeHash = e.Hash()
-	}
-	return msg
-}
-
-// Bloom maps 4-bytes Topic into 64-byte bloom filter with 3 bits set (at most).
-func (e *Envelope) Bloom() []byte {
-	if e.bloom == nil {
-		e.bloom = TopicToBloom(e.Topic)
-	}
-	return e.bloom
-}
-
-// TopicToBloom converts the topic (4 bytes) to the bloom filter (64 bytes)
-func TopicToBloom(topic TopicType) []byte {
-	b := make([]byte, BloomFilterSize)
-	var index [3]int
-	for j := 0; j < 3; j++ {
-		index[j] = int(topic[j])
-		if (topic[3] & (1 << uint(j))) != 0 {
-			index[j] += 256
-		}
-	}
-
-	for j := 0; j < 3; j++ {
-		byteIndex := index[j] / 8
-		bitIndex := index[j] % 8
-		b[byteIndex] = (1 << uint(bitIndex))
-	}
-	return b
-}
-
-// GetEnvelope retrieves an envelope from the message queue by its hash.
-// It returns nil if the envelope can not be found.
-func (w *Whisper) GetEnvelope(hash common.Hash) *Envelope {
-	w.poolMu.RLock()
-	defer w.poolMu.RUnlock()
-	return w.envelopes[hash]
-}
diff --git a/whisper/whisperv6/envelope_test.go b/whisper/whisperv6/envelope_test.go
deleted file mode 100644
index c0bb4373b8..0000000000
--- a/whisper/whisperv6/envelope_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2017 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 .
-
-// Contains the tests associated with the Whisper protocol Envelope object.
-
-package whisperv6
-
-import (
-	mrand "math/rand"
-	"testing"
-
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-func TestPoWCalculationsWithNoLeadingZeros(t *testing.T) {
-	e := Envelope{
-		TTL:   1,
-		Data:  []byte{0xde, 0xad, 0xbe, 0xef},
-		Nonce: 100000,
-	}
-
-	e.calculatePoW(0)
-
-	if e.pow != 0.07692307692307693 {
-		t.Fatalf("invalid PoW calculation. Expected 0.07692307692307693, got %v", e.pow)
-	}
-}
-
-func TestPoWCalculationsWith8LeadingZeros(t *testing.T) {
-	e := Envelope{
-		TTL:   1,
-		Data:  []byte{0xde, 0xad, 0xbe, 0xef},
-		Nonce: 276,
-	}
-	e.calculatePoW(0)
-
-	if e.pow != 19.692307692307693 {
-		t.Fatalf("invalid PoW calculation. Expected 19.692307692307693, got %v", e.pow)
-	}
-}
-
-func TestEnvelopeOpenAcceptsOnlyOneKeyTypeInFilter(t *testing.T) {
-	symKey := make([]byte, aesKeyLength)
-	mrand.Read(symKey)
-
-	asymKey, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-
-	params := MessageParams{
-		PoW:      0.01,
-		WorkTime: 1,
-		TTL:      uint32(mrand.Intn(1024)),
-		Payload:  make([]byte, 50),
-		KeySym:   symKey,
-		Dst:      nil,
-	}
-
-	mrand.Read(params.Payload)
-
-	msg, err := NewSentMessage(¶ms)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-
-	e, err := msg.Wrap(¶ms)
-	if err != nil {
-		t.Fatalf("Failed to Wrap the message in an envelope with seed %d: %s", seed, err)
-	}
-
-	f := Filter{KeySym: symKey, KeyAsym: asymKey}
-
-	decrypted := e.Open(&f)
-	if decrypted != nil {
-		t.Fatalf("Managed to decrypt a message with an invalid filter, seed %d", seed)
-	}
-}
diff --git a/whisper/whisperv6/filter.go b/whisper/whisperv6/filter.go
deleted file mode 100644
index 6a5b79674b..0000000000
--- a/whisper/whisperv6/filter.go
+++ /dev/null
@@ -1,262 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"crypto/ecdsa"
-	"fmt"
-	"sync"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-)
-
-// Filter represents a Whisper message filter
-type Filter struct {
-	Src        *ecdsa.PublicKey  // Sender of the message
-	KeyAsym    *ecdsa.PrivateKey // Private Key of recipient
-	KeySym     []byte            // Key associated with the Topic
-	Topics     [][]byte          // Topics to filter messages with
-	PoW        float64           // Proof of work as described in the Whisper spec
-	AllowP2P   bool              // Indicates whether this filter is interested in direct peer-to-peer messages
-	SymKeyHash common.Hash       // The Keccak256Hash of the symmetric key, needed for optimization
-	id         string            // unique identifier
-
-	Messages map[common.Hash]*ReceivedMessage
-	mutex    sync.RWMutex
-}
-
-// Filters represents a collection of filters
-type Filters struct {
-	watchers map[string]*Filter
-
-	topicMatcher     map[TopicType]map[*Filter]struct{} // map a topic to the filters that are interested in being notified when a message matches that topic
-	allTopicsMatcher map[*Filter]struct{}               // list all the filters that will be notified of a new message, no matter what its topic is
-
-	whisper *Whisper
-	mutex   sync.RWMutex
-}
-
-// NewFilters returns a newly created filter collection
-func NewFilters(w *Whisper) *Filters {
-	return &Filters{
-		watchers:         make(map[string]*Filter),
-		topicMatcher:     make(map[TopicType]map[*Filter]struct{}),
-		allTopicsMatcher: make(map[*Filter]struct{}),
-		whisper:          w,
-	}
-}
-
-// Install will add a new filter to the filter collection
-func (fs *Filters) Install(watcher *Filter) (string, error) {
-	if watcher.KeySym != nil && watcher.KeyAsym != nil {
-		return "", fmt.Errorf("filters must choose between symmetric and asymmetric keys")
-	}
-
-	if watcher.Messages == nil {
-		watcher.Messages = make(map[common.Hash]*ReceivedMessage)
-	}
-
-	id, err := GenerateRandomID()
-	if err != nil {
-		return "", err
-	}
-
-	fs.mutex.Lock()
-	defer fs.mutex.Unlock()
-
-	if fs.watchers[id] != nil {
-		return "", fmt.Errorf("failed to generate unique ID")
-	}
-
-	if watcher.expectsSymmetricEncryption() {
-		watcher.SymKeyHash = crypto.Keccak256Hash(watcher.KeySym)
-	}
-
-	watcher.id = id
-	fs.watchers[id] = watcher
-	fs.addTopicMatcher(watcher)
-	return id, err
-}
-
-// Uninstall will remove a filter whose id has been specified from
-// the filter collection
-func (fs *Filters) Uninstall(id string) bool {
-	fs.mutex.Lock()
-	defer fs.mutex.Unlock()
-	if fs.watchers[id] != nil {
-		fs.removeFromTopicMatchers(fs.watchers[id])
-		delete(fs.watchers, id)
-		return true
-	}
-	return false
-}
-
-// addTopicMatcher adds a filter to the topic matchers.
-// If the filter's Topics array is empty, it will be tried on every topic.
-// Otherwise, it will be tried on the topics specified.
-func (fs *Filters) addTopicMatcher(watcher *Filter) {
-	if len(watcher.Topics) == 0 {
-		fs.allTopicsMatcher[watcher] = struct{}{}
-	} else {
-		for _, t := range watcher.Topics {
-			topic := BytesToTopic(t)
-			if fs.topicMatcher[topic] == nil {
-				fs.topicMatcher[topic] = make(map[*Filter]struct{})
-			}
-			fs.topicMatcher[topic][watcher] = struct{}{}
-		}
-	}
-}
-
-// removeFromTopicMatchers removes a filter from the topic matchers
-func (fs *Filters) removeFromTopicMatchers(watcher *Filter) {
-	delete(fs.allTopicsMatcher, watcher)
-	for _, topic := range watcher.Topics {
-		delete(fs.topicMatcher[BytesToTopic(topic)], watcher)
-	}
-}
-
-// getWatchersByTopic returns a slice containing the filters that
-// match a specific topic
-func (fs *Filters) getWatchersByTopic(topic TopicType) []*Filter {
-	res := make([]*Filter, 0, len(fs.allTopicsMatcher))
-	for watcher := range fs.allTopicsMatcher {
-		res = append(res, watcher)
-	}
-	for watcher := range fs.topicMatcher[topic] {
-		res = append(res, watcher)
-	}
-	return res
-}
-
-// Get returns a filter from the collection with a specific ID
-func (fs *Filters) Get(id string) *Filter {
-	fs.mutex.RLock()
-	defer fs.mutex.RUnlock()
-	return fs.watchers[id]
-}
-
-// NotifyWatchers notifies any filter that has declared interest
-// for the envelope's topic.
-func (fs *Filters) NotifyWatchers(env *Envelope, p2pMessage bool) {
-	var msg *ReceivedMessage
-
-	fs.mutex.RLock()
-	defer fs.mutex.RUnlock()
-
-	candidates := fs.getWatchersByTopic(env.Topic)
-	for _, watcher := range candidates {
-		if p2pMessage && !watcher.AllowP2P {
-			log.Trace(fmt.Sprintf("msg [%x], filter [%s]: p2p messages are not allowed", env.Hash(), watcher.id))
-			continue
-		}
-
-		var match bool
-		if msg != nil {
-			match = watcher.MatchMessage(msg)
-		} else {
-			match = watcher.MatchEnvelope(env)
-			if match {
-				msg = env.Open(watcher)
-				if msg == nil {
-					log.Trace("processing message: failed to open", "message", env.Hash().Hex(), "filter", watcher.id)
-				}
-			} else {
-				log.Trace("processing message: does not match", "message", env.Hash().Hex(), "filter", watcher.id)
-			}
-		}
-
-		if match && msg != nil {
-			log.Trace("processing message: decrypted", "hash", env.Hash().Hex())
-			if watcher.Src == nil || IsPubKeyEqual(msg.Src, watcher.Src) {
-				watcher.Trigger(msg)
-			}
-		}
-	}
-}
-
-func (f *Filter) expectsAsymmetricEncryption() bool {
-	return f.KeyAsym != nil
-}
-
-func (f *Filter) expectsSymmetricEncryption() bool {
-	return f.KeySym != nil
-}
-
-// Trigger adds a yet-unknown message to the filter's list of
-// received messages.
-func (f *Filter) Trigger(msg *ReceivedMessage) {
-	f.mutex.Lock()
-	defer f.mutex.Unlock()
-
-	if _, exist := f.Messages[msg.EnvelopeHash]; !exist {
-		f.Messages[msg.EnvelopeHash] = msg
-	}
-}
-
-// Retrieve will return the list of all received messages associated
-// to a filter.
-func (f *Filter) Retrieve() (all []*ReceivedMessage) {
-	f.mutex.Lock()
-	defer f.mutex.Unlock()
-
-	all = make([]*ReceivedMessage, 0, len(f.Messages))
-	for _, msg := range f.Messages {
-		all = append(all, msg)
-	}
-
-	f.Messages = make(map[common.Hash]*ReceivedMessage) // delete old messages
-	return all
-}
-
-// MatchMessage checks if the filter matches an already decrypted
-// message (i.e. a Message that has already been handled by
-// MatchEnvelope when checked by a previous filter).
-// Topics are not checked here, since this is done by topic matchers.
-func (f *Filter) MatchMessage(msg *ReceivedMessage) bool {
-	if f.PoW > 0 && msg.PoW < f.PoW {
-		return false
-	}
-
-	if f.expectsAsymmetricEncryption() && msg.isAsymmetricEncryption() {
-		return IsPubKeyEqual(&f.KeyAsym.PublicKey, msg.Dst)
-	} else if f.expectsSymmetricEncryption() && msg.isSymmetricEncryption() {
-		return f.SymKeyHash == msg.SymKeyHash
-	}
-	return false
-}
-
-// MatchEnvelope checks if it's worth decrypting the message. If
-// it returns `true`, client code is expected to attempt decrypting
-// the message and subsequently call MatchMessage.
-// Topics are not checked here, since this is done by topic matchers.
-func (f *Filter) MatchEnvelope(envelope *Envelope) bool {
-	return f.PoW <= 0 || envelope.pow >= f.PoW
-}
-
-// IsPubKeyEqual checks that two public keys are equal
-func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
-	if !ValidatePublicKey(a) {
-		return false
-	} else if !ValidatePublicKey(b) {
-		return false
-	}
-	// the curve is always the same, just compare the points
-	return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
-}
diff --git a/whisper/whisperv6/filter_test.go b/whisper/whisperv6/filter_test.go
deleted file mode 100644
index 55f5052919..0000000000
--- a/whisper/whisperv6/filter_test.go
+++ /dev/null
@@ -1,825 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"math/big"
-	mrand "math/rand"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-var seed int64
-
-// InitSingleTest should be called in the beginning of every
-// test, which uses RNG, in order to make the tests
-// reproduciblity independent of their sequence.
-func InitSingleTest() {
-	seed = time.Now().Unix()
-	mrand.Seed(seed)
-}
-
-type FilterTestCase struct {
-	f      *Filter
-	id     string
-	alive  bool
-	msgCnt int
-}
-
-func generateFilter(t *testing.T, symmetric bool) (*Filter, error) {
-	var f Filter
-	f.Messages = make(map[common.Hash]*ReceivedMessage)
-
-	const topicNum = 8
-	f.Topics = make([][]byte, topicNum)
-	for i := 0; i < topicNum; i++ {
-		f.Topics[i] = make([]byte, 4)
-		mrand.Read(f.Topics[i])
-		f.Topics[i][0] = 0x01
-	}
-
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("generateFilter 1 failed with seed %d.", seed)
-		return nil, err
-	}
-	f.Src = &key.PublicKey
-
-	if symmetric {
-		f.KeySym = make([]byte, aesKeyLength)
-		mrand.Read(f.KeySym)
-		f.SymKeyHash = crypto.Keccak256Hash(f.KeySym)
-	} else {
-		f.KeyAsym, err = crypto.GenerateKey()
-		if err != nil {
-			t.Fatalf("generateFilter 2 failed with seed %d.", seed)
-			return nil, err
-		}
-	}
-
-	// AcceptP2P & PoW are not set
-	return &f, nil
-}
-
-func generateTestCases(t *testing.T, SizeTestFilters int) []FilterTestCase {
-	cases := make([]FilterTestCase, SizeTestFilters)
-	for i := 0; i < SizeTestFilters; i++ {
-		f, _ := generateFilter(t, true)
-		cases[i].f = f
-		cases[i].alive = mrand.Int()&int(1) == 0
-	}
-	return cases
-}
-
-func TestInstallFilters(t *testing.T) {
-	InitSingleTest()
-
-	const SizeTestFilters = 256
-	w := New(&Config{})
-	filters := NewFilters(w)
-	tst := generateTestCases(t, SizeTestFilters)
-
-	var err error
-	var j string
-	for i := 0; i < SizeTestFilters; i++ {
-		j, err = filters.Install(tst[i].f)
-		if err != nil {
-			t.Fatalf("seed %d: failed to install filter: %s", seed, err)
-		}
-		tst[i].id = j
-		if len(j) != keyIDSize*2 {
-			t.Fatalf("seed %d: wrong filter id size [%d]", seed, len(j))
-		}
-	}
-
-	for _, testCase := range tst {
-		if !testCase.alive {
-			filters.Uninstall(testCase.id)
-		}
-	}
-
-	for i, testCase := range tst {
-		fil := filters.Get(testCase.id)
-		exist := fil != nil
-		if exist != testCase.alive {
-			t.Fatalf("seed %d: failed alive: %d, %v, %v", seed, i, exist, testCase.alive)
-		}
-		if exist && fil.PoW != testCase.f.PoW {
-			t.Fatalf("seed %d: failed Get: %d, %v, %v", seed, i, exist, testCase.alive)
-		}
-	}
-}
-
-func TestInstallSymKeyGeneratesHash(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&Config{})
-	filters := NewFilters(w)
-	filter, _ := generateFilter(t, true)
-
-	// save the current SymKeyHash for comparison
-	initialSymKeyHash := filter.SymKeyHash
-
-	// ensure the SymKeyHash is invalid, for Install to recreate it
-	var invalid common.Hash
-	filter.SymKeyHash = invalid
-
-	_, err := filters.Install(filter)
-
-	if err != nil {
-		t.Fatalf("Error installing the filter: %s", err)
-	}
-
-	for i, b := range filter.SymKeyHash {
-		if b != initialSymKeyHash[i] {
-			t.Fatalf("The filter's symmetric key hash was not properly generated by Install")
-		}
-	}
-}
-
-func TestInstallIdenticalFilters(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&Config{})
-	filters := NewFilters(w)
-	filter1, _ := generateFilter(t, true)
-
-	// Copy the first filter since some of its fields
-	// are randomly gnerated.
-	filter2 := &Filter{
-		KeySym:   filter1.KeySym,
-		Topics:   filter1.Topics,
-		PoW:      filter1.PoW,
-		AllowP2P: filter1.AllowP2P,
-		Messages: make(map[common.Hash]*ReceivedMessage),
-	}
-
-	_, err := filters.Install(filter1)
-
-	if err != nil {
-		t.Fatalf("Error installing the first filter with seed %d: %s", seed, err)
-	}
-
-	_, err = filters.Install(filter2)
-
-	if err != nil {
-		t.Fatalf("Error installing the second filter with seed %d: %s", seed, err)
-	}
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("Error generating message parameters with seed %d: %s", seed, err)
-	}
-
-	params.KeySym = filter1.KeySym
-	params.Topic = BytesToTopic(filter1.Topics[0])
-
-	filter1.Src = ¶ms.Src.PublicKey
-	filter2.Src = ¶ms.Src.PublicKey
-
-	sentMessage, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := sentMessage.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-	msg := env.Open(filter1)
-	if msg == nil {
-		t.Fatalf("failed to Open with filter1")
-	}
-
-	if !filter1.MatchEnvelope(env) {
-		t.Fatalf("failed matching with the first filter")
-	}
-
-	if !filter2.MatchEnvelope(env) {
-		t.Fatalf("failed matching with the first filter")
-	}
-
-	if !filter1.MatchMessage(msg) {
-		t.Fatalf("failed matching with the second filter")
-	}
-
-	if !filter2.MatchMessage(msg) {
-		t.Fatalf("failed matching with the second filter")
-	}
-}
-
-func TestInstallFilterWithSymAndAsymKeys(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&Config{})
-	filters := NewFilters(w)
-	filter1, _ := generateFilter(t, true)
-
-	asymKey, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("Unable to create asymetric keys: %v", err)
-	}
-
-	// Copy the first filter since some of its fields
-	// are randomly gnerated.
-	filter := &Filter{
-		KeySym:   filter1.KeySym,
-		KeyAsym:  asymKey,
-		Topics:   filter1.Topics,
-		PoW:      filter1.PoW,
-		AllowP2P: filter1.AllowP2P,
-		Messages: make(map[common.Hash]*ReceivedMessage),
-	}
-
-	_, err = filters.Install(filter)
-
-	if err == nil {
-		t.Fatalf("Error detecting that a filter had both an asymmetric and symmetric key, with seed %d", seed)
-	}
-}
-
-func TestComparePubKey(t *testing.T) {
-	InitSingleTest()
-
-	key1, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to generate first key with seed %d: %s.", seed, err)
-	}
-	key2, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to generate second key with seed %d: %s.", seed, err)
-	}
-	if IsPubKeyEqual(&key1.PublicKey, &key2.PublicKey) {
-		t.Fatalf("public keys are equal, seed %d.", seed)
-	}
-
-	// generate key3 == key1
-	mrand.Seed(seed)
-	key3, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed to generate third key with seed %d: %s.", seed, err)
-	}
-	if IsPubKeyEqual(&key1.PublicKey, &key3.PublicKey) {
-		t.Fatalf("key1 == key3, seed %d.", seed)
-	}
-}
-
-func TestMatchEnvelope(t *testing.T) {
-	InitSingleTest()
-
-	fsym, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
-	}
-
-	fasym, err := generateFilter(t, false)
-	if err != nil {
-		t.Fatalf("failed generateFilter() with seed %d: %s.", seed, err)
-	}
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	params.Topic[0] = 0xFF // topic mismatch
-
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	if _, err = msg.Wrap(params); err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	// encrypt symmetrically
-	i := mrand.Int() % 4
-	fsym.Topics[i] = params.Topic[:]
-	fasym.Topics[i] = params.Topic[:]
-	msg, err = NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap() with seed %d: %s.", seed, err)
-	}
-
-	// symmetric + matching topic: match
-	match := fsym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope() symmetric with seed %d.", seed)
-	}
-
-	// symmetric + matching topic + insufficient PoW: mismatch
-	fsym.PoW = env.PoW() + 1.0
-	match = fsym.MatchEnvelope(env)
-	if match {
-		t.Fatalf("failed MatchEnvelope(symmetric + matching topic + insufficient PoW) asymmetric with seed %d.", seed)
-	}
-
-	// symmetric + matching topic + sufficient PoW: match
-	fsym.PoW = env.PoW() / 2
-	match = fsym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(symmetric + matching topic + sufficient PoW) with seed %d.", seed)
-	}
-
-	// symmetric + topics are nil (wildcard): match
-	prevTopics := fsym.Topics
-	fsym.Topics = nil
-	match = fsym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(symmetric + topics are nil) with seed %d.", seed)
-	}
-	fsym.Topics = prevTopics
-
-	// encrypt asymmetrically
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-	params.KeySym = nil
-	params.Dst = &key.PublicKey
-	msg, err = NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err = msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap() with seed %d: %s.", seed, err)
-	}
-
-	// encryption method mismatch
-	match = fsym.MatchEnvelope(env)
-	if match {
-		t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
-	}
-
-	// asymmetric + mismatching topic: mismatch
-	match = fasym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(asymmetric + mismatching topic) with seed %d.", seed)
-	}
-
-	// asymmetric + matching topic: match
-	fasym.Topics[i] = fasym.Topics[i+1]
-	match = fasym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(asymmetric + matching topic) with seed %d.", seed)
-	}
-
-	// asymmetric + filter without topic (wildcard): match
-	fasym.Topics = nil
-	match = fasym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(asymmetric + filter without topic) with seed %d.", seed)
-	}
-
-	// asymmetric + insufficient PoW: mismatch
-	fasym.PoW = env.PoW() + 1.0
-	match = fasym.MatchEnvelope(env)
-	if match {
-		t.Fatalf("failed MatchEnvelope(asymmetric + insufficient PoW) with seed %d.", seed)
-	}
-
-	// asymmetric + sufficient PoW: match
-	fasym.PoW = env.PoW() / 2
-	match = fasym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(asymmetric + sufficient PoW) with seed %d.", seed)
-	}
-
-	// filter without topic + envelope without topic: match
-	env.Topic = TopicType{}
-	match = fasym.MatchEnvelope(env)
-	if !match {
-		t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed)
-	}
-
-	// filter with topic + envelope without topic: mismatch
-	fasym.Topics = fsym.Topics
-	match = fasym.MatchEnvelope(env)
-	if !match {
-		// topic mismatch should have no affect, as topics are handled by topic matchers
-		t.Fatalf("failed MatchEnvelope(filter without topic + envelope without topic) with seed %d.", seed)
-	}
-}
-
-func TestMatchMessageSym(t *testing.T) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	f, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
-	}
-
-	const index = 1
-	params.KeySym = f.KeySym
-	params.Topic = BytesToTopic(f.Topics[index])
-
-	sentMessage, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := sentMessage.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-	msg := env.Open(f)
-	if msg == nil {
-		t.Fatalf("failed Open with seed %d.", seed)
-	}
-
-	// Src: match
-	*f.Src.X = *params.Src.PublicKey.X
-	*f.Src.Y = *params.Src.PublicKey.Y
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(src match) with seed %d.", seed)
-	}
-
-	// insufficient PoW: mismatch
-	f.PoW = msg.PoW + 1.0
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed)
-	}
-
-	// sufficient PoW: match
-	f.PoW = msg.PoW / 2
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed)
-	}
-
-	// topic mismatch
-	f.Topics[index][0]++
-	if !f.MatchMessage(msg) {
-		// topic mismatch should have no affect, as topics are handled by topic matchers
-		t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed)
-	}
-	f.Topics[index][0]--
-
-	// key mismatch
-	f.SymKeyHash[0]++
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed)
-	}
-	f.SymKeyHash[0]--
-
-	// Src absent: match
-	f.Src = nil
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed)
-	}
-
-	// key hash mismatch
-	h := f.SymKeyHash
-	f.SymKeyHash = common.Hash{}
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(key hash mismatch) with seed %d.", seed)
-	}
-	f.SymKeyHash = h
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(key hash match) with seed %d.", seed)
-	}
-
-	// encryption method mismatch
-	f.KeySym = nil
-	f.KeyAsym, err = crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
-	}
-}
-
-func TestMatchMessageAsym(t *testing.T) {
-	InitSingleTest()
-
-	f, err := generateFilter(t, false)
-	if err != nil {
-		t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
-	}
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	const index = 1
-	params.Topic = BytesToTopic(f.Topics[index])
-	params.Dst = &f.KeyAsym.PublicKey
-	keySymOrig := params.KeySym
-	params.KeySym = nil
-
-	sentMessage, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := sentMessage.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-	msg := env.Open(f)
-	if msg == nil {
-		t.Fatalf("failed to open with seed %d.", seed)
-	}
-
-	// Src: match
-	*f.Src.X = *params.Src.PublicKey.X
-	*f.Src.Y = *params.Src.PublicKey.Y
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchMessage(src match) with seed %d.", seed)
-	}
-
-	// insufficient PoW: mismatch
-	f.PoW = msg.PoW + 1.0
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(insufficient PoW) with seed %d.", seed)
-	}
-
-	// sufficient PoW: match
-	f.PoW = msg.PoW / 2
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(sufficient PoW) with seed %d.", seed)
-	}
-
-	// topic mismatch
-	f.Topics[index][0]++
-	if !f.MatchMessage(msg) {
-		// topic mismatch should have no affect, as topics are handled by topic matchers
-		t.Fatalf("failed MatchEnvelope(topic mismatch) with seed %d.", seed)
-	}
-	f.Topics[index][0]--
-
-	// key mismatch
-	prev := *f.KeyAsym.PublicKey.X
-	zero := *big.NewInt(0)
-	*f.KeyAsym.PublicKey.X = zero
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(key mismatch) with seed %d.", seed)
-	}
-	*f.KeyAsym.PublicKey.X = prev
-
-	// Src absent: match
-	f.Src = nil
-	if !f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(src absent) with seed %d.", seed)
-	}
-
-	// encryption method mismatch
-	f.KeySym = keySymOrig
-	f.KeyAsym = nil
-	if f.MatchMessage(msg) {
-		t.Fatalf("failed MatchEnvelope(encryption method mismatch) with seed %d.", seed)
-	}
-}
-
-func cloneFilter(orig *Filter) *Filter {
-	var clone Filter
-	clone.Messages = make(map[common.Hash]*ReceivedMessage)
-	clone.Src = orig.Src
-	clone.KeyAsym = orig.KeyAsym
-	clone.KeySym = orig.KeySym
-	clone.Topics = orig.Topics
-	clone.PoW = orig.PoW
-	clone.AllowP2P = orig.AllowP2P
-	clone.SymKeyHash = orig.SymKeyHash
-	return &clone
-}
-
-func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope {
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-		return nil
-	}
-
-	params.KeySym = f.KeySym
-	params.Topic = BytesToTopic(f.Topics[2])
-	sentMessage, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := sentMessage.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-		return nil
-	}
-	return env
-}
-
-func TestWatchers(t *testing.T) {
-	InitSingleTest()
-
-	const NumFilters = 16
-	const NumMessages = 256
-	var i int
-	var j uint32
-	var e *Envelope
-	var x, firstID string
-	var err error
-
-	w := New(&Config{})
-	filters := NewFilters(w)
-	tst := generateTestCases(t, NumFilters)
-	for i = 0; i < NumFilters; i++ {
-		tst[i].f.Src = nil
-		x, err = filters.Install(tst[i].f)
-		if err != nil {
-			t.Fatalf("failed to install filter with seed %d: %s.", seed, err)
-		}
-		tst[i].id = x
-		if len(firstID) == 0 {
-			firstID = x
-		}
-	}
-
-	lastID := x
-
-	var envelopes [NumMessages]*Envelope
-	for i = 0; i < NumMessages; i++ {
-		j = mrand.Uint32() % NumFilters
-		e = generateCompatibeEnvelope(t, tst[j].f)
-		envelopes[i] = e
-		tst[j].msgCnt++
-	}
-
-	for i = 0; i < NumMessages; i++ {
-		filters.NotifyWatchers(envelopes[i], false)
-	}
-
-	var total int
-	var mail []*ReceivedMessage
-	var count [NumFilters]int
-
-	for i = 0; i < NumFilters; i++ {
-		mail = tst[i].f.Retrieve()
-		count[i] = len(mail)
-		total += len(mail)
-	}
-
-	if total != NumMessages {
-		t.Fatalf("failed with seed %d: total = %d, want: %d.", seed, total, NumMessages)
-	}
-
-	for i = 0; i < NumFilters; i++ {
-		mail = tst[i].f.Retrieve()
-		if len(mail) != 0 {
-			t.Fatalf("failed with seed %d: i = %d.", seed, i)
-		}
-
-		if tst[i].msgCnt != count[i] {
-			t.Fatalf("failed with seed %d: count[%d]: get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
-		}
-	}
-
-	// another round with a cloned filter
-
-	clone := cloneFilter(tst[0].f)
-	filters.Uninstall(lastID)
-	total = 0
-	last := NumFilters - 1
-	tst[last].f = clone
-	filters.Install(clone)
-	for i = 0; i < NumFilters; i++ {
-		tst[i].msgCnt = 0
-		count[i] = 0
-	}
-
-	// make sure that the first watcher receives at least one message
-	e = generateCompatibeEnvelope(t, tst[0].f)
-	envelopes[0] = e
-	tst[0].msgCnt++
-	for i = 1; i < NumMessages; i++ {
-		j = mrand.Uint32() % NumFilters
-		e = generateCompatibeEnvelope(t, tst[j].f)
-		envelopes[i] = e
-		tst[j].msgCnt++
-	}
-
-	for i = 0; i < NumMessages; i++ {
-		filters.NotifyWatchers(envelopes[i], false)
-	}
-
-	for i = 0; i < NumFilters; i++ {
-		mail = tst[i].f.Retrieve()
-		count[i] = len(mail)
-		total += len(mail)
-	}
-
-	combined := tst[0].msgCnt + tst[last].msgCnt
-	if total != NumMessages+count[0] {
-		t.Fatalf("failed with seed %d: total = %d, count[0] = %d.", seed, total, count[0])
-	}
-
-	if combined != count[0] {
-		t.Fatalf("failed with seed %d: combined = %d, count[0] = %d.", seed, combined, count[0])
-	}
-
-	if combined != count[last] {
-		t.Fatalf("failed with seed %d: combined = %d, count[last] = %d.", seed, combined, count[last])
-	}
-
-	for i = 1; i < NumFilters-1; i++ {
-		mail = tst[i].f.Retrieve()
-		if len(mail) != 0 {
-			t.Fatalf("failed with seed %d: i = %d.", seed, i)
-		}
-
-		if tst[i].msgCnt != count[i] {
-			t.Fatalf("failed with seed %d: i = %d, get %d, want %d.", seed, i, tst[i].msgCnt, count[i])
-		}
-	}
-
-	// test AcceptP2P
-
-	total = 0
-	filters.NotifyWatchers(envelopes[0], true)
-
-	for i = 0; i < NumFilters; i++ {
-		mail = tst[i].f.Retrieve()
-		total += len(mail)
-	}
-
-	if total != 0 {
-		t.Fatalf("failed with seed %d: total: got %d, want 0.", seed, total)
-	}
-
-	f := filters.Get(firstID)
-	if f == nil {
-		t.Fatalf("failed to get the filter with seed %d.", seed)
-	}
-	f.AllowP2P = true
-	total = 0
-	filters.NotifyWatchers(envelopes[0], true)
-
-	for i = 0; i < NumFilters; i++ {
-		mail = tst[i].f.Retrieve()
-		total += len(mail)
-	}
-
-	if total != 1 {
-		t.Fatalf("failed with seed %d: total: got %d, want 1.", seed, total)
-	}
-}
-
-func TestVariableTopics(t *testing.T) {
-	InitSingleTest()
-
-	const lastTopicByte = 3
-	var match bool
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	f, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateFilter with seed %d: %s.", seed, err)
-	}
-
-	for i := 0; i < 4; i++ {
-		env.Topic = BytesToTopic(f.Topics[i])
-		match = f.MatchEnvelope(env)
-		if !match {
-			t.Fatalf("failed MatchEnvelope symmetric with seed %d, step %d.", seed, i)
-		}
-
-		f.Topics[i][lastTopicByte]++
-		match = f.MatchEnvelope(env)
-		if !match {
-			// topic mismatch should have no affect, as topics are handled by topic matchers
-			t.Fatalf("MatchEnvelope symmetric with seed %d, step %d.", seed, i)
-		}
-	}
-}
diff --git a/whisper/whisperv6/gen_criteria_json.go b/whisper/whisperv6/gen_criteria_json.go
deleted file mode 100644
index 1a428d6df7..0000000000
--- a/whisper/whisperv6/gen_criteria_json.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
-
-package whisperv6
-
-import (
-	"encoding/json"
-
-	"github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-var _ = (*criteriaOverride)(nil)
-
-// MarshalJSON marshals type Criteria to a json string
-func (c Criteria) MarshalJSON() ([]byte, error) {
-	type Criteria struct {
-		SymKeyID     string        `json:"symKeyID"`
-		PrivateKeyID string        `json:"privateKeyID"`
-		Sig          hexutil.Bytes `json:"sig"`
-		MinPow       float64       `json:"minPow"`
-		Topics       []TopicType   `json:"topics"`
-		AllowP2P     bool          `json:"allowP2P"`
-	}
-	var enc Criteria
-	enc.SymKeyID = c.SymKeyID
-	enc.PrivateKeyID = c.PrivateKeyID
-	enc.Sig = c.Sig
-	enc.MinPow = c.MinPow
-	enc.Topics = c.Topics
-	enc.AllowP2P = c.AllowP2P
-	return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals type Criteria to a json string
-func (c *Criteria) UnmarshalJSON(input []byte) error {
-	type Criteria struct {
-		SymKeyID     *string        `json:"symKeyID"`
-		PrivateKeyID *string        `json:"privateKeyID"`
-		Sig          *hexutil.Bytes `json:"sig"`
-		MinPow       *float64       `json:"minPow"`
-		Topics       []TopicType    `json:"topics"`
-		AllowP2P     *bool          `json:"allowP2P"`
-	}
-	var dec Criteria
-	if err := json.Unmarshal(input, &dec); err != nil {
-		return err
-	}
-	if dec.SymKeyID != nil {
-		c.SymKeyID = *dec.SymKeyID
-	}
-	if dec.PrivateKeyID != nil {
-		c.PrivateKeyID = *dec.PrivateKeyID
-	}
-	if dec.Sig != nil {
-		c.Sig = *dec.Sig
-	}
-	if dec.MinPow != nil {
-		c.MinPow = *dec.MinPow
-	}
-	if dec.Topics != nil {
-		c.Topics = dec.Topics
-	}
-	if dec.AllowP2P != nil {
-		c.AllowP2P = *dec.AllowP2P
-	}
-	return nil
-}
diff --git a/whisper/whisperv6/gen_message_json.go b/whisper/whisperv6/gen_message_json.go
deleted file mode 100644
index 6218f5df6e..0000000000
--- a/whisper/whisperv6/gen_message_json.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
-
-package whisperv6
-
-import (
-	"encoding/json"
-
-	"github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-var _ = (*messageOverride)(nil)
-
-// MarshalJSON marshals type Message to a json string
-func (m Message) MarshalJSON() ([]byte, error) {
-	type Message struct {
-		Sig       hexutil.Bytes `json:"sig,omitempty"`
-		TTL       uint32        `json:"ttl"`
-		Timestamp uint32        `json:"timestamp"`
-		Topic     TopicType     `json:"topic"`
-		Payload   hexutil.Bytes `json:"payload"`
-		Padding   hexutil.Bytes `json:"padding"`
-		PoW       float64       `json:"pow"`
-		Hash      hexutil.Bytes `json:"hash"`
-		Dst       hexutil.Bytes `json:"recipientPublicKey,omitempty"`
-	}
-	var enc Message
-	enc.Sig = m.Sig
-	enc.TTL = m.TTL
-	enc.Timestamp = m.Timestamp
-	enc.Topic = m.Topic
-	enc.Payload = m.Payload
-	enc.Padding = m.Padding
-	enc.PoW = m.PoW
-	enc.Hash = m.Hash
-	enc.Dst = m.Dst
-	return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals type Message to a json string
-func (m *Message) UnmarshalJSON(input []byte) error {
-	type Message struct {
-		Sig       *hexutil.Bytes `json:"sig,omitempty"`
-		TTL       *uint32        `json:"ttl"`
-		Timestamp *uint32        `json:"timestamp"`
-		Topic     *TopicType     `json:"topic"`
-		Payload   *hexutil.Bytes `json:"payload"`
-		Padding   *hexutil.Bytes `json:"padding"`
-		PoW       *float64       `json:"pow"`
-		Hash      *hexutil.Bytes `json:"hash"`
-		Dst       *hexutil.Bytes `json:"recipientPublicKey,omitempty"`
-	}
-	var dec Message
-	if err := json.Unmarshal(input, &dec); err != nil {
-		return err
-	}
-	if dec.Sig != nil {
-		m.Sig = *dec.Sig
-	}
-	if dec.TTL != nil {
-		m.TTL = *dec.TTL
-	}
-	if dec.Timestamp != nil {
-		m.Timestamp = *dec.Timestamp
-	}
-	if dec.Topic != nil {
-		m.Topic = *dec.Topic
-	}
-	if dec.Payload != nil {
-		m.Payload = *dec.Payload
-	}
-	if dec.Padding != nil {
-		m.Padding = *dec.Padding
-	}
-	if dec.PoW != nil {
-		m.PoW = *dec.PoW
-	}
-	if dec.Hash != nil {
-		m.Hash = *dec.Hash
-	}
-	if dec.Dst != nil {
-		m.Dst = *dec.Dst
-	}
-	return nil
-}
diff --git a/whisper/whisperv6/gen_newmessage_json.go b/whisper/whisperv6/gen_newmessage_json.go
deleted file mode 100644
index 75a1279ae3..0000000000
--- a/whisper/whisperv6/gen_newmessage_json.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
-
-package whisperv6
-
-import (
-	"encoding/json"
-
-	"github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-var _ = (*newMessageOverride)(nil)
-
-// MarshalJSON marshals type NewMessage to a json string
-func (n NewMessage) MarshalJSON() ([]byte, error) {
-	type NewMessage struct {
-		SymKeyID   string        `json:"symKeyID"`
-		PublicKey  hexutil.Bytes `json:"pubKey"`
-		Sig        string        `json:"sig"`
-		TTL        uint32        `json:"ttl"`
-		Topic      TopicType     `json:"topic"`
-		Payload    hexutil.Bytes `json:"payload"`
-		Padding    hexutil.Bytes `json:"padding"`
-		PowTime    uint32        `json:"powTime"`
-		PowTarget  float64       `json:"powTarget"`
-		TargetPeer string        `json:"targetPeer"`
-	}
-	var enc NewMessage
-	enc.SymKeyID = n.SymKeyID
-	enc.PublicKey = n.PublicKey
-	enc.Sig = n.Sig
-	enc.TTL = n.TTL
-	enc.Topic = n.Topic
-	enc.Payload = n.Payload
-	enc.Padding = n.Padding
-	enc.PowTime = n.PowTime
-	enc.PowTarget = n.PowTarget
-	enc.TargetPeer = n.TargetPeer
-	return json.Marshal(&enc)
-}
-
-// UnmarshalJSON unmarshals type NewMessage to a json string
-func (n *NewMessage) UnmarshalJSON(input []byte) error {
-	type NewMessage struct {
-		SymKeyID   *string        `json:"symKeyID"`
-		PublicKey  *hexutil.Bytes `json:"pubKey"`
-		Sig        *string        `json:"sig"`
-		TTL        *uint32        `json:"ttl"`
-		Topic      *TopicType     `json:"topic"`
-		Payload    *hexutil.Bytes `json:"payload"`
-		Padding    *hexutil.Bytes `json:"padding"`
-		PowTime    *uint32        `json:"powTime"`
-		PowTarget  *float64       `json:"powTarget"`
-		TargetPeer *string        `json:"targetPeer"`
-	}
-	var dec NewMessage
-	if err := json.Unmarshal(input, &dec); err != nil {
-		return err
-	}
-	if dec.SymKeyID != nil {
-		n.SymKeyID = *dec.SymKeyID
-	}
-	if dec.PublicKey != nil {
-		n.PublicKey = *dec.PublicKey
-	}
-	if dec.Sig != nil {
-		n.Sig = *dec.Sig
-	}
-	if dec.TTL != nil {
-		n.TTL = *dec.TTL
-	}
-	if dec.Topic != nil {
-		n.Topic = *dec.Topic
-	}
-	if dec.Payload != nil {
-		n.Payload = *dec.Payload
-	}
-	if dec.Padding != nil {
-		n.Padding = *dec.Padding
-	}
-	if dec.PowTime != nil {
-		n.PowTime = *dec.PowTime
-	}
-	if dec.PowTarget != nil {
-		n.PowTarget = *dec.PowTarget
-	}
-	if dec.TargetPeer != nil {
-		n.TargetPeer = *dec.TargetPeer
-	}
-	return nil
-}
diff --git a/whisper/whisperv6/message.go b/whisper/whisperv6/message.go
deleted file mode 100644
index 2d4e862441..0000000000
--- a/whisper/whisperv6/message.go
+++ /dev/null
@@ -1,355 +0,0 @@
-// Copyright 2016 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 .
-
-// Contains the Whisper protocol Message element.
-
-package whisperv6
-
-import (
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/ecdsa"
-	crand "crypto/rand"
-	"encoding/binary"
-	"errors"
-	mrand "math/rand"
-	"strconv"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/crypto/ecies"
-	"github.com/ethereum/go-ethereum/log"
-)
-
-// MessageParams specifies the exact way a message should be wrapped
-// into an Envelope.
-type MessageParams struct {
-	TTL      uint32
-	Src      *ecdsa.PrivateKey
-	Dst      *ecdsa.PublicKey
-	KeySym   []byte
-	Topic    TopicType
-	WorkTime uint32
-	PoW      float64
-	Payload  []byte
-	Padding  []byte
-}
-
-// SentMessage represents an end-user data packet to transmit through the
-// Whisper protocol. These are wrapped into Envelopes that need not be
-// understood by intermediate nodes, just forwarded.
-type sentMessage struct {
-	Raw []byte
-}
-
-// ReceivedMessage represents a data packet to be received through the
-// Whisper protocol and successfully decrypted.
-type ReceivedMessage struct {
-	Raw []byte
-
-	Payload   []byte
-	Padding   []byte
-	Signature []byte
-	Salt      []byte
-
-	PoW   float64          // Proof of work as described in the Whisper spec
-	Sent  uint32           // Time when the message was posted into the network
-	TTL   uint32           // Maximum time to live allowed for the message
-	Src   *ecdsa.PublicKey // Message recipient (identity used to decode the message)
-	Dst   *ecdsa.PublicKey // Message recipient (identity used to decode the message)
-	Topic TopicType
-
-	SymKeyHash   common.Hash // The Keccak256Hash of the key
-	EnvelopeHash common.Hash // Message envelope hash to act as a unique id
-}
-
-func isMessageSigned(flags byte) bool {
-	return (flags & signatureFlag) != 0
-}
-
-func (msg *ReceivedMessage) isSymmetricEncryption() bool {
-	return msg.SymKeyHash != common.Hash{}
-}
-
-func (msg *ReceivedMessage) isAsymmetricEncryption() bool {
-	return msg.Dst != nil
-}
-
-// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message.
-func NewSentMessage(params *MessageParams) (*sentMessage, error) {
-	const payloadSizeFieldMaxSize = 4
-	msg := sentMessage{}
-	msg.Raw = make([]byte, 1,
-		flagsLength+payloadSizeFieldMaxSize+len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit)
-	msg.Raw[0] = 0 // set all the flags to zero
-	msg.addPayloadSizeField(params.Payload)
-	msg.Raw = append(msg.Raw, params.Payload...)
-	err := msg.appendPadding(params)
-	return &msg, err
-}
-
-// addPayloadSizeField appends the auxiliary field containing the size of payload
-func (msg *sentMessage) addPayloadSizeField(payload []byte) {
-	fieldSize := getSizeOfPayloadSizeField(payload)
-	field := make([]byte, 4)
-	binary.LittleEndian.PutUint32(field, uint32(len(payload)))
-	field = field[:fieldSize]
-	msg.Raw = append(msg.Raw, field...)
-	msg.Raw[0] |= byte(fieldSize)
-}
-
-// getSizeOfPayloadSizeField returns the number of bytes necessary to encode the size of payload
-func getSizeOfPayloadSizeField(payload []byte) int {
-	s := 1
-	for i := len(payload); i >= 256; i /= 256 {
-		s++
-	}
-	return s
-}
-
-// appendPadding appends the padding specified in params.
-// If no padding is provided in params, then random padding is generated.
-func (msg *sentMessage) appendPadding(params *MessageParams) error {
-	if len(params.Padding) != 0 {
-		// padding data was provided by the Dapp, just use it as is
-		msg.Raw = append(msg.Raw, params.Padding...)
-		return nil
-	}
-
-	rawSize := flagsLength + getSizeOfPayloadSizeField(params.Payload) + len(params.Payload)
-	if params.Src != nil {
-		rawSize += signatureLength
-	}
-	odd := rawSize % padSizeLimit
-	paddingSize := padSizeLimit - odd
-	pad := make([]byte, paddingSize)
-	_, err := crand.Read(pad)
-	if err != nil {
-		return err
-	}
-	if !validateDataIntegrity(pad, paddingSize) {
-		return errors.New("failed to generate random padding of size " + strconv.Itoa(paddingSize))
-	}
-	msg.Raw = append(msg.Raw, pad...)
-	return nil
-}
-
-// sign calculates and sets the cryptographic signature for the message,
-// also setting the sign flag.
-func (msg *sentMessage) sign(key *ecdsa.PrivateKey) error {
-	if isMessageSigned(msg.Raw[0]) {
-		// this should not happen, but no reason to panic
-		log.Error("failed to sign the message: already signed")
-		return nil
-	}
-
-	msg.Raw[0] |= signatureFlag // it is important to set this flag before signing
-	hash := crypto.Keccak256(msg.Raw)
-	signature, err := crypto.Sign(hash, key)
-	if err != nil {
-		msg.Raw[0] &= (0xFF ^ signatureFlag) // clear the flag
-		return err
-	}
-	msg.Raw = append(msg.Raw, signature...)
-	return nil
-}
-
-// encryptAsymmetric encrypts a message with a public key.
-func (msg *sentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error {
-	if !ValidatePublicKey(key) {
-		return errors.New("invalid public key provided for asymmetric encryption")
-	}
-	encrypted, err := ecies.Encrypt(crand.Reader, ecies.ImportECDSAPublic(key), msg.Raw, nil, nil)
-	if err == nil {
-		msg.Raw = encrypted
-	}
-	return err
-}
-
-// encryptSymmetric encrypts a message with a topic key, using AES-GCM-256.
-// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
-func (msg *sentMessage) encryptSymmetric(key []byte) (err error) {
-	if !validateDataIntegrity(key, aesKeyLength) {
-		return errors.New("invalid key provided for symmetric encryption, size: " + strconv.Itoa(len(key)))
-	}
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return err
-	}
-	aesgcm, err := cipher.NewGCM(block)
-	if err != nil {
-		return err
-	}
-	salt, err := generateSecureRandomData(aesNonceLength) // never use more than 2^32 random nonces with a given key
-	if err != nil {
-		return err
-	}
-	encrypted := aesgcm.Seal(nil, salt, msg.Raw, nil)
-	msg.Raw = append(encrypted, salt...)
-	return nil
-}
-
-// generateSecureRandomData generates random data where extra security is required.
-// The purpose of this function is to prevent some bugs in software or in hardware
-// from delivering not-very-random data. This is especially useful for AES nonce,
-// where true randomness does not really matter, but it is very important to have
-// a unique nonce for every message.
-func generateSecureRandomData(length int) ([]byte, error) {
-	x := make([]byte, length)
-	y := make([]byte, length)
-	res := make([]byte, length)
-
-	_, err := crand.Read(x)
-	if err != nil {
-		return nil, err
-	} else if !validateDataIntegrity(x, length) {
-		return nil, errors.New("crypto/rand failed to generate secure random data")
-	}
-	_, err = mrand.Read(y)
-	if err != nil {
-		return nil, err
-	} else if !validateDataIntegrity(y, length) {
-		return nil, errors.New("math/rand failed to generate secure random data")
-	}
-	for i := 0; i < length; i++ {
-		res[i] = x[i] ^ y[i]
-	}
-	if !validateDataIntegrity(res, length) {
-		return nil, errors.New("failed to generate secure random data")
-	}
-	return res, nil
-}
-
-// Wrap bundles the message into an Envelope to transmit over the network.
-func (msg *sentMessage) Wrap(options *MessageParams) (envelope *Envelope, err error) {
-	if options.TTL == 0 {
-		options.TTL = DefaultTTL
-	}
-	if options.Src != nil {
-		if err = msg.sign(options.Src); err != nil {
-			return nil, err
-		}
-	}
-	if options.Dst != nil {
-		err = msg.encryptAsymmetric(options.Dst)
-	} else if options.KeySym != nil {
-		err = msg.encryptSymmetric(options.KeySym)
-	} else {
-		err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided")
-	}
-	if err != nil {
-		return nil, err
-	}
-
-	envelope = NewEnvelope(options.TTL, options.Topic, msg)
-	if err = envelope.Seal(options); err != nil {
-		return nil, err
-	}
-	return envelope, nil
-}
-
-// decryptSymmetric decrypts a message with a topic key, using AES-GCM-256.
-// nonce size should be 12 bytes (see cipher.gcmStandardNonceSize).
-func (msg *ReceivedMessage) decryptSymmetric(key []byte) error {
-	// symmetric messages are expected to contain the 12-byte nonce at the end of the payload
-	if len(msg.Raw) < aesNonceLength {
-		return errors.New("missing salt or invalid payload in symmetric message")
-	}
-	salt := msg.Raw[len(msg.Raw)-aesNonceLength:]
-
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return err
-	}
-	aesgcm, err := cipher.NewGCM(block)
-	if err != nil {
-		return err
-	}
-	decrypted, err := aesgcm.Open(nil, salt, msg.Raw[:len(msg.Raw)-aesNonceLength], nil)
-	if err != nil {
-		return err
-	}
-	msg.Raw = decrypted
-	msg.Salt = salt
-	return nil
-}
-
-// decryptAsymmetric decrypts an encrypted payload with a private key.
-func (msg *ReceivedMessage) decryptAsymmetric(key *ecdsa.PrivateKey) error {
-	decrypted, err := ecies.ImportECDSA(key).Decrypt(msg.Raw, nil, nil)
-	if err == nil {
-		msg.Raw = decrypted
-	}
-	return err
-}
-
-// ValidateAndParse checks the message validity and extracts the fields in case of success.
-func (msg *ReceivedMessage) ValidateAndParse() bool {
-	end := len(msg.Raw)
-	if end < 1 {
-		return false
-	}
-
-	if isMessageSigned(msg.Raw[0]) {
-		end -= signatureLength
-		if end <= 1 {
-			return false
-		}
-		msg.Signature = msg.Raw[end : end+signatureLength]
-		msg.Src = msg.SigToPubKey()
-		if msg.Src == nil {
-			return false
-		}
-	}
-
-	beg := 1
-	payloadSize := 0
-	sizeOfPayloadSizeField := int(msg.Raw[0] & SizeMask) // number of bytes indicating the size of payload
-	if sizeOfPayloadSizeField != 0 {
-		payloadSize = int(bytesToUintLittleEndian(msg.Raw[beg : beg+sizeOfPayloadSizeField]))
-		if payloadSize+1 > end {
-			return false
-		}
-		beg += sizeOfPayloadSizeField
-		msg.Payload = msg.Raw[beg : beg+payloadSize]
-	}
-
-	beg += payloadSize
-	msg.Padding = msg.Raw[beg:end]
-	return true
-}
-
-// SigToPubKey returns the public key associated to the message's
-// signature.
-func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey {
-	defer func() { recover() }() // in case of invalid signature
-
-	pub, err := crypto.SigToPub(msg.hash(), msg.Signature)
-	if err != nil {
-		log.Error("failed to recover public key from signature", "err", err)
-		return nil
-	}
-	return pub
-}
-
-// hash calculates the SHA3 checksum of the message flags, payload size field, payload and padding.
-func (msg *ReceivedMessage) hash() []byte {
-	if isMessageSigned(msg.Raw[0]) {
-		sz := len(msg.Raw) - signatureLength
-		return crypto.Keccak256(msg.Raw[:sz])
-	}
-	return crypto.Keccak256(msg.Raw)
-}
diff --git a/whisper/whisperv6/message_test.go b/whisper/whisperv6/message_test.go
deleted file mode 100644
index ece6d732cc..0000000000
--- a/whisper/whisperv6/message_test.go
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"bytes"
-	"crypto/aes"
-	"crypto/cipher"
-	mrand "math/rand"
-	"testing"
-
-	"github.com/ethereum/go-ethereum/common/hexutil"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-func generateMessageParams() (*MessageParams, error) {
-	// set all the parameters except p.Dst and p.Padding
-
-	buf := make([]byte, 4)
-	mrand.Read(buf)
-	sz := mrand.Intn(400)
-
-	var p MessageParams
-	p.PoW = 0.001
-	p.WorkTime = 1
-	p.TTL = uint32(mrand.Intn(1024))
-	p.Payload = make([]byte, sz)
-	p.KeySym = make([]byte, aesKeyLength)
-	mrand.Read(p.Payload)
-	mrand.Read(p.KeySym)
-	p.Topic = BytesToTopic(buf)
-
-	var err error
-	p.Src, err = crypto.GenerateKey()
-	if err != nil {
-		return nil, err
-	}
-
-	return &p, nil
-}
-
-func singleMessageTest(t *testing.T, symmetric bool) {
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-
-	if !symmetric {
-		params.KeySym = nil
-		params.Dst = &key.PublicKey
-	}
-
-	text := make([]byte, 0, 512)
-	text = append(text, params.Payload...)
-
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	var decrypted *ReceivedMessage
-	if symmetric {
-		decrypted, err = env.OpenSymmetric(params.KeySym)
-	} else {
-		decrypted, err = env.OpenAsymmetric(key)
-	}
-
-	if err != nil {
-		t.Fatalf("failed to encrypt with seed %d: %s.", seed, err)
-	}
-
-	if !decrypted.ValidateAndParse() {
-		t.Fatalf("failed to validate with seed %d, symmetric = %v.", seed, symmetric)
-	}
-
-	if !bytes.Equal(text, decrypted.Payload) {
-		t.Fatalf("failed with seed %d: compare payload.", seed)
-	}
-	if !isMessageSigned(decrypted.Raw[0]) {
-		t.Fatalf("failed with seed %d: unsigned.", seed)
-	}
-	if len(decrypted.Signature) != signatureLength {
-		t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
-	}
-	if !IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) {
-		t.Fatalf("failed with seed %d: signature mismatch.", seed)
-	}
-}
-
-func TestMessageEncryption(t *testing.T) {
-	InitSingleTest()
-
-	var symmetric bool
-	for i := 0; i < 256; i++ {
-		singleMessageTest(t, symmetric)
-		symmetric = !symmetric
-	}
-}
-
-func TestMessageWrap(t *testing.T) {
-	seed = int64(1777444222)
-	mrand.Seed(seed)
-	target := 128.0
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	params.TTL = 1
-	params.WorkTime = 12
-	params.PoW = target
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	pow := env.PoW()
-	if pow < target {
-		t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
-	}
-
-	// set PoW target too high, expect error
-	msg2, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	params.TTL = 1000000
-	params.WorkTime = 1
-	params.PoW = 10000000.0
-	_, err = msg2.Wrap(params)
-	if err == nil {
-		t.Fatalf("unexpectedly reached the PoW target with seed %d.", seed)
-	}
-}
-
-func TestMessageSeal(t *testing.T) {
-	// this test depends on deterministic choice of seed (1976726903)
-	seed = int64(1976726903)
-	mrand.Seed(seed)
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	params.TTL = 1
-
-	env := NewEnvelope(params.TTL, params.Topic, msg)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	env.Expiry = uint32(seed) // make it deterministic
-	target := 32.0
-	params.WorkTime = 4
-	params.PoW = target
-	env.Seal(params)
-
-	env.calculatePoW(0)
-	pow := env.PoW()
-	if pow < target {
-		t.Fatalf("failed Wrap with seed %d: pow < target (%f vs. %f).", seed, pow, target)
-	}
-
-	params.WorkTime = 1
-	params.PoW = 1000000000.0
-	env.Seal(params)
-	env.calculatePoW(0)
-	pow = env.PoW()
-	if pow < 2*target {
-		t.Fatalf("failed Wrap with seed %d: pow too small %f.", seed, pow)
-	}
-}
-
-func TestEnvelopeOpen(t *testing.T) {
-	InitSingleTest()
-
-	var symmetric bool
-	for i := 0; i < 32; i++ {
-		singleEnvelopeOpenTest(t, symmetric)
-		symmetric = !symmetric
-	}
-}
-
-func singleEnvelopeOpenTest(t *testing.T, symmetric bool) {
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	key, err := crypto.GenerateKey()
-	if err != nil {
-		t.Fatalf("failed GenerateKey with seed %d: %s.", seed, err)
-	}
-
-	if !symmetric {
-		params.KeySym = nil
-		params.Dst = &key.PublicKey
-	}
-
-	text := make([]byte, 0, 512)
-	text = append(text, params.Payload...)
-
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	var f Filter
-	if symmetric {
-		f = Filter{KeySym: params.KeySym}
-	} else {
-		f = Filter{KeyAsym: key}
-	}
-	decrypted := env.Open(&f)
-	if decrypted == nil {
-		t.Fatalf("failed to open with seed %d.", seed)
-	}
-
-	if !bytes.Equal(text, decrypted.Payload) {
-		t.Fatalf("failed with seed %d: compare payload.", seed)
-	}
-	if !isMessageSigned(decrypted.Raw[0]) {
-		t.Fatalf("failed with seed %d: unsigned.", seed)
-	}
-	if len(decrypted.Signature) != signatureLength {
-		t.Fatalf("failed with seed %d: signature len %d.", seed, len(decrypted.Signature))
-	}
-	if !IsPubKeyEqual(decrypted.Src, ¶ms.Src.PublicKey) {
-		t.Fatalf("failed with seed %d: signature mismatch.", seed)
-	}
-	if decrypted.isAsymmetricEncryption() == symmetric {
-		t.Fatalf("failed with seed %d: asymmetric %v vs. %v.", seed, decrypted.isAsymmetricEncryption(), symmetric)
-	}
-	if decrypted.isSymmetricEncryption() != symmetric {
-		t.Fatalf("failed with seed %d: symmetric %v vs. %v.", seed, decrypted.isSymmetricEncryption(), symmetric)
-	}
-	if !symmetric {
-		if decrypted.Dst == nil {
-			t.Fatalf("failed with seed %d: dst is nil.", seed)
-		}
-		if !IsPubKeyEqual(decrypted.Dst, &key.PublicKey) {
-			t.Fatalf("failed with seed %d: Dst.", seed)
-		}
-	}
-}
-
-func TestEncryptWithZeroKey(t *testing.T) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	params.KeySym = make([]byte, aesKeyLength)
-	_, err = msg.Wrap(params)
-	if err == nil {
-		t.Fatalf("wrapped with zero key, seed: %d.", seed)
-	}
-
-	params, err = generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, err = NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	params.KeySym = make([]byte, 0)
-	_, err = msg.Wrap(params)
-	if err == nil {
-		t.Fatalf("wrapped with empty key, seed: %d.", seed)
-	}
-
-	params, err = generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, err = NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	params.KeySym = nil
-	_, err = msg.Wrap(params)
-	if err == nil {
-		t.Fatalf("wrapped with nil key, seed: %d.", seed)
-	}
-}
-
-func TestRlpEncode(t *testing.T) {
-	InitSingleTest()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("wrapped with zero key, seed: %d.", seed)
-	}
-
-	raw, err := rlp.EncodeToBytes(env)
-	if err != nil {
-		t.Fatalf("RLP encode failed: %s.", err)
-	}
-
-	var decoded Envelope
-	rlp.DecodeBytes(raw, &decoded)
-	if err != nil {
-		t.Fatalf("RLP decode failed: %s.", err)
-	}
-
-	he := env.Hash()
-	hd := decoded.Hash()
-
-	if he != hd {
-		t.Fatalf("Hashes are not equal: %x vs. %x", he, hd)
-	}
-}
-
-func singlePaddingTest(t *testing.T, padSize int) {
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err)
-	}
-	params.Padding = make([]byte, padSize)
-	params.PoW = 0.0000000001
-	pad := make([]byte, padSize)
-	_, err = mrand.Read(pad)
-	if err != nil {
-		t.Fatalf("padding is not generated (seed %d): %s", seed, err)
-	}
-	n := copy(params.Padding, pad)
-	if n != padSize {
-		t.Fatalf("padding is not copied (seed %d): %s", seed, err)
-	}
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed to wrap, seed: %d and sz=%d.", seed, padSize)
-	}
-	f := Filter{KeySym: params.KeySym}
-	decrypted := env.Open(&f)
-	if decrypted == nil {
-		t.Fatalf("failed to open, seed and sz=%d: %d.", seed, padSize)
-	}
-	if !bytes.Equal(pad, decrypted.Padding) {
-		t.Fatalf("padding is not retireved as expected with seed %d and sz=%d:\n[%x]\n[%x].", seed, padSize, pad, decrypted.Padding)
-	}
-}
-
-func TestPadding(t *testing.T) {
-	InitSingleTest()
-
-	for i := 1; i < 260; i++ {
-		singlePaddingTest(t, i)
-	}
-
-	lim := 256 * 256
-	for i := lim - 5; i < lim+2; i++ {
-		singlePaddingTest(t, i)
-	}
-
-	for i := 0; i < 256; i++ {
-		n := mrand.Intn(256*254) + 256
-		singlePaddingTest(t, n)
-	}
-
-	for i := 0; i < 256; i++ {
-		n := mrand.Intn(256*1024) + 256*256
-		singlePaddingTest(t, n)
-	}
-}
-
-func TestPaddingAppendedToSymMessagesWithSignature(t *testing.T) {
-	params := &MessageParams{
-		Payload: make([]byte, 246),
-		KeySym:  make([]byte, aesKeyLength),
-	}
-
-	pSrc, err := crypto.GenerateKey()
-
-	if err != nil {
-		t.Fatalf("Error creating the signature key %v", err)
-		return
-	}
-	params.Src = pSrc
-
-	// Simulate a message with a payload just under 256 so that
-	// payload + flag + signature > 256. Check that the result
-	// is padded on the next 256 boundary.
-	msg := sentMessage{}
-	const payloadSizeFieldMinSize = 1
-	msg.Raw = make([]byte, flagsLength+payloadSizeFieldMinSize+len(params.Payload))
-
-	err = msg.appendPadding(params)
-
-	if err != nil {
-		t.Fatalf("Error appending padding to message %v", err)
-		return
-	}
-
-	if len(msg.Raw) != 512-signatureLength {
-		t.Errorf("Invalid size %d != 512", len(msg.Raw))
-	}
-}
-
-func TestAesNonce(t *testing.T) {
-	key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31")
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		t.Fatalf("NewCipher failed: %s", err)
-	}
-	aesgcm, err := cipher.NewGCM(block)
-	if err != nil {
-		t.Fatalf("NewGCM failed: %s", err)
-	}
-	// This is the most important single test in this package.
-	// If it fails, whisper will not be working.
-	if aesgcm.NonceSize() != aesNonceLength {
-		t.Fatalf("Nonce size is wrong. This is a critical error. Apparently AES nonce size have changed in the new version of AES GCM package. Whisper will not be working until this problem is resolved.")
-	}
-}
diff --git a/whisper/whisperv6/peer.go b/whisper/whisperv6/peer.go
deleted file mode 100644
index 68fa7c8cba..0000000000
--- a/whisper/whisperv6/peer.go
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"fmt"
-	"math"
-	"sync"
-	"time"
-
-	mapset "github.com/deckarep/golang-set"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rlp"
-)
-
-// Peer represents a whisper protocol peer connection.
-type Peer struct {
-	host *Whisper
-	peer *p2p.Peer
-	ws   p2p.MsgReadWriter
-
-	trusted        bool
-	powRequirement float64
-	bloomMu        sync.Mutex
-	bloomFilter    []byte
-	fullNode       bool
-
-	known mapset.Set // Messages already known by the peer to avoid wasting bandwidth
-
-	quit chan struct{}
-
-	wg sync.WaitGroup
-}
-
-// newPeer creates a new whisper peer object, but does not run the handshake itself.
-func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
-	return &Peer{
-		host:           host,
-		peer:           remote,
-		ws:             rw,
-		trusted:        false,
-		powRequirement: 0.0,
-		known:          mapset.NewSet(),
-		quit:           make(chan struct{}),
-		bloomFilter:    MakeFullNodeBloom(),
-		fullNode:       true,
-	}
-}
-
-// start initiates the peer updater, periodically broadcasting the whisper packets
-// into the network.
-func (peer *Peer) start() {
-	peer.wg.Add(1)
-	go peer.update()
-	log.Trace("start", "peer", peer.ID())
-}
-
-// stop terminates the peer updater, stopping message forwarding to it.
-func (peer *Peer) stop() {
-	close(peer.quit)
-	peer.wg.Wait()
-	log.Trace("stop", "peer", peer.ID())
-}
-
-// handshake sends the protocol initiation status message to the remote peer and
-// verifies the remote status too.
-func (peer *Peer) handshake() error {
-	// Send the handshake status message asynchronously
-	errc := make(chan error, 1)
-	isLightNode := peer.host.LightClientMode()
-	isRestrictedLightNodeConnection := peer.host.LightClientModeConnectionRestricted()
-	peer.wg.Add(1)
-	go func() {
-		defer peer.wg.Done()
-		pow := peer.host.MinPow()
-		powConverted := math.Float64bits(pow)
-		bloom := peer.host.BloomFilter()
-
-		errc <- p2p.SendItems(peer.ws, statusCode, ProtocolVersion, powConverted, bloom, isLightNode)
-	}()
-
-	// Fetch the remote status packet and verify protocol match
-	packet, err := peer.ws.ReadMsg()
-	if err != nil {
-		return err
-	}
-	if packet.Code != statusCode {
-		return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code)
-	}
-	s := rlp.NewStream(packet.Payload, uint64(packet.Size))
-	_, err = s.List()
-	if err != nil {
-		return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err)
-	}
-	peerVersion, err := s.Uint()
-	if err != nil {
-		return fmt.Errorf("peer [%x] sent bad status message (unable to decode version): %v", peer.ID(), err)
-	}
-	if peerVersion != ProtocolVersion {
-		return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion)
-	}
-
-	// only version is mandatory, subsequent parameters are optional
-	powRaw, err := s.Uint()
-	if err == nil {
-		pow := math.Float64frombits(powRaw)
-		if math.IsInf(pow, 0) || math.IsNaN(pow) || pow < 0.0 {
-			return fmt.Errorf("peer [%x] sent bad status message: invalid pow", peer.ID())
-		}
-		peer.powRequirement = pow
-
-		var bloom []byte
-		err = s.Decode(&bloom)
-		if err == nil {
-			sz := len(bloom)
-			if sz != BloomFilterSize && sz != 0 {
-				return fmt.Errorf("peer [%x] sent bad status message: wrong bloom filter size %d", peer.ID(), sz)
-			}
-			peer.setBloomFilter(bloom)
-		}
-	}
-
-	isRemotePeerLightNode, _ := s.Bool()
-	if isRemotePeerLightNode && isLightNode && isRestrictedLightNodeConnection {
-		return fmt.Errorf("peer [%x] is useless: two light client communication restricted", peer.ID())
-	}
-
-	if err := <-errc; err != nil {
-		return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err)
-	}
-	return nil
-}
-
-// update executes periodic operations on the peer, including message transmission
-// and expiration.
-func (peer *Peer) update() {
-	defer peer.wg.Done()
-	// Start the tickers for the updates
-	expire := time.NewTicker(expirationCycle)
-	defer expire.Stop()
-	transmit := time.NewTicker(transmissionCycle)
-	defer transmit.Stop()
-
-	// Loop and transmit until termination is requested
-	for {
-		select {
-		case <-expire.C:
-			peer.expire()
-
-		case <-transmit.C:
-			if err := peer.broadcast(); err != nil {
-				log.Trace("broadcast failed", "reason", err, "peer", peer.ID())
-				return
-			}
-
-		case <-peer.quit:
-			return
-		}
-	}
-}
-
-// mark marks an envelope known to the peer so that it won't be sent back.
-func (peer *Peer) mark(envelope *Envelope) {
-	peer.known.Add(envelope.Hash())
-}
-
-// marked checks if an envelope is already known to the remote peer.
-func (peer *Peer) marked(envelope *Envelope) bool {
-	return peer.known.Contains(envelope.Hash())
-}
-
-// expire iterates over all the known envelopes in the host and removes all
-// expired (unknown) ones from the known list.
-func (peer *Peer) expire() {
-	unmark := make(map[common.Hash]struct{})
-	peer.known.Each(func(v interface{}) bool {
-		if !peer.host.isEnvelopeCached(v.(common.Hash)) {
-			unmark[v.(common.Hash)] = struct{}{}
-		}
-		return true
-	})
-	// Dump all known but no longer cached
-	for hash := range unmark {
-		peer.known.Remove(hash)
-	}
-}
-
-// broadcast iterates over the collection of envelopes and transmits yet unknown
-// ones over the network.
-func (peer *Peer) broadcast() error {
-	envelopes := peer.host.Envelopes()
-	bundle := make([]*Envelope, 0, len(envelopes))
-	for _, envelope := range envelopes {
-		if !peer.marked(envelope) && envelope.PoW() >= peer.powRequirement && peer.bloomMatch(envelope) {
-			bundle = append(bundle, envelope)
-		}
-	}
-
-	if len(bundle) > 0 {
-		// transmit the batch of envelopes
-		if err := p2p.Send(peer.ws, messagesCode, bundle); err != nil {
-			return err
-		}
-
-		// mark envelopes only if they were successfully sent
-		for _, e := range bundle {
-			peer.mark(e)
-		}
-
-		log.Trace("broadcast", "num. messages", len(bundle))
-	}
-	return nil
-}
-
-// ID returns a peer's id
-func (peer *Peer) ID() []byte {
-	id := peer.peer.ID()
-	return id[:]
-}
-
-func (peer *Peer) notifyAboutPowRequirementChange(pow float64) error {
-	i := math.Float64bits(pow)
-	return p2p.Send(peer.ws, powRequirementCode, i)
-}
-
-func (peer *Peer) notifyAboutBloomFilterChange(bloom []byte) error {
-	return p2p.Send(peer.ws, bloomFilterExCode, bloom)
-}
-
-func (peer *Peer) bloomMatch(env *Envelope) bool {
-	peer.bloomMu.Lock()
-	defer peer.bloomMu.Unlock()
-	return peer.fullNode || BloomFilterMatch(peer.bloomFilter, env.Bloom())
-}
-
-func (peer *Peer) setBloomFilter(bloom []byte) {
-	peer.bloomMu.Lock()
-	defer peer.bloomMu.Unlock()
-	peer.bloomFilter = bloom
-	peer.fullNode = isFullNode(bloom)
-	if peer.fullNode && peer.bloomFilter == nil {
-		peer.bloomFilter = MakeFullNodeBloom()
-	}
-}
-
-func MakeFullNodeBloom() []byte {
-	bloom := make([]byte, BloomFilterSize)
-	for i := 0; i < BloomFilterSize; i++ {
-		bloom[i] = 0xFF
-	}
-	return bloom
-}
diff --git a/whisper/whisperv6/topic.go b/whisper/whisperv6/topic.go
deleted file mode 100644
index ee255f785d..0000000000
--- a/whisper/whisperv6/topic.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2016 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 .
-
-// Contains the Whisper protocol Topic element.
-
-package whisperv6
-
-import (
-	"github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-// TopicType represents a cryptographically secure, probabilistic partial
-// classifications of a message, determined as the first (left) 4 bytes of the
-// SHA3 hash of some arbitrary data given by the original author of the message.
-type TopicType [TopicLength]byte
-
-// BytesToTopic converts from the byte array representation of a topic
-// into the TopicType type.
-func BytesToTopic(b []byte) (t TopicType) {
-	sz := TopicLength
-	if x := len(b); x < TopicLength {
-		sz = x
-	}
-	for i := 0; i < sz; i++ {
-		t[i] = b[i]
-	}
-	return t
-}
-
-// String converts a topic byte array to a string representation.
-func (t *TopicType) String() string {
-	return hexutil.Encode(t[:])
-}
-
-// MarshalText returns the hex representation of t.
-func (t TopicType) MarshalText() ([]byte, error) {
-	return hexutil.Bytes(t[:]).MarshalText()
-}
-
-// UnmarshalText parses a hex representation to a topic.
-func (t *TopicType) UnmarshalText(input []byte) error {
-	return hexutil.UnmarshalFixedText("Topic", input, t[:])
-}
diff --git a/whisper/whisperv6/topic_test.go b/whisper/whisperv6/topic_test.go
deleted file mode 100644
index 454afe0de1..0000000000
--- a/whisper/whisperv6/topic_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"encoding/json"
-	"testing"
-)
-
-var topicStringTests = []struct {
-	topic TopicType
-	str   string
-}{
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, str: "0x00000000"},
-	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, str: "0x007f80ff"},
-	{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, str: "0xff807f00"},
-	{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, str: "0xf26e7779"},
-}
-
-func TestTopicString(t *testing.T) {
-	for i, tst := range topicStringTests {
-		s := tst.topic.String()
-		if s != tst.str {
-			t.Fatalf("failed test %d: have %s, want %s.", i, s, tst.str)
-		}
-	}
-}
-
-var bytesToTopicTests = []struct {
-	data  []byte
-	topic TopicType
-}{
-	{topic: TopicType{0x8f, 0x9a, 0x2b, 0x7d}, data: []byte{0x8f, 0x9a, 0x2b, 0x7d}},
-	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte{0x00, 0x7f, 0x80, 0xff}},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00, 0x00}},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{0x00, 0x00, 0x00}},
-	{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte{0x01}},
-	{topic: TopicType{0x00, 0xfe, 0x00, 0x00}, data: []byte{0x00, 0xfe}},
-	{topic: TopicType{0xea, 0x1d, 0x43, 0x00}, data: []byte{0xea, 0x1d, 0x43}},
-	{topic: TopicType{0x6f, 0x3c, 0xb0, 0xdd}, data: []byte{0x6f, 0x3c, 0xb0, 0xdd, 0x0f, 0x00, 0x90}},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte{}},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: nil},
-}
-
-var unmarshalTestsGood = []struct {
-	topic TopicType
-	data  []byte
-}{
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x00000000"`)},
-	{topic: TopicType{0x00, 0x7f, 0x80, 0xff}, data: []byte(`"0x007f80ff"`)},
-	{topic: TopicType{0xff, 0x80, 0x7f, 0x00}, data: []byte(`"0xff807f00"`)},
-	{topic: TopicType{0xf2, 0x6e, 0x77, 0x79}, data: []byte(`"0xf26e7779"`)},
-}
-
-var unmarshalTestsBad = []struct {
-	topic TopicType
-	data  []byte
-}{
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x000000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0x0000000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"000000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"0000000000"`)},
-	{topic: TopicType{0x00, 0x00, 0x00, 0x00}, data: []byte(`"abcdefg0"`)},
-}
-
-var unmarshalTestsUgly = []struct {
-	topic TopicType
-	data  []byte
-}{
-	{topic: TopicType{0x01, 0x00, 0x00, 0x00}, data: []byte(`"0x00000001"`)},
-}
-
-func TestBytesToTopic(t *testing.T) {
-	for i, tst := range bytesToTopicTests {
-		top := BytesToTopic(tst.data)
-		if top != tst.topic {
-			t.Fatalf("failed test %d: have %v, want %v.", i, t, tst.topic)
-		}
-	}
-}
-
-func TestUnmarshalTestsGood(t *testing.T) {
-	for i, tst := range unmarshalTestsGood {
-		var top TopicType
-		err := json.Unmarshal(tst.data, &top)
-		if err != nil {
-			t.Errorf("failed test %d. input: %v. err: %v", i, tst.data, err)
-		} else if top != tst.topic {
-			t.Errorf("failed test %d: have %v, want %v.", i, t, tst.topic)
-		}
-	}
-}
-
-func TestUnmarshalTestsBad(t *testing.T) {
-	// in this test UnmarshalJSON() is supposed to fail
-	for i, tst := range unmarshalTestsBad {
-		var top TopicType
-		err := json.Unmarshal(tst.data, &top)
-		if err == nil {
-			t.Fatalf("failed test %d. input: %v.", i, tst.data)
-		}
-	}
-}
-
-func TestUnmarshalTestsUgly(t *testing.T) {
-	// in this test UnmarshalJSON() is NOT supposed to fail, but result should be wrong
-	for i, tst := range unmarshalTestsUgly {
-		var top TopicType
-		err := json.Unmarshal(tst.data, &top)
-		if err != nil {
-			t.Errorf("failed test %d. input: %v.", i, tst.data)
-		} else if top == tst.topic {
-			t.Errorf("failed test %d: have %v, want %v.", i, top, tst.topic)
-		}
-	}
-}
diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go
deleted file mode 100644
index 377406b360..0000000000
--- a/whisper/whisperv6/whisper.go
+++ /dev/null
@@ -1,1088 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"crypto/sha256"
-	"fmt"
-	"math"
-	"runtime"
-	"sync"
-	"time"
-
-	mapset "github.com/deckarep/golang-set"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/log"
-	"github.com/ethereum/go-ethereum/p2p"
-	"github.com/ethereum/go-ethereum/rlp"
-	"github.com/ethereum/go-ethereum/rpc"
-	"github.com/syndtr/goleveldb/leveldb/errors"
-	"golang.org/x/crypto/pbkdf2"
-	"golang.org/x/sync/syncmap"
-)
-
-// Statistics holds several message-related counter for analytics
-// purposes.
-type Statistics struct {
-	messagesCleared      int
-	memoryCleared        int
-	memoryUsed           int
-	cycles               int
-	totalMessagesCleared int
-}
-
-const (
-	maxMsgSizeIdx                            = iota // Maximal message length allowed by the whisper node
-	overflowIdx                                     // Indicator of message queue overflow
-	minPowIdx                                       // Minimal PoW required by the whisper node
-	minPowToleranceIdx                              // Minimal PoW tolerated by the whisper node for a limited time
-	bloomFilterIdx                                  // Bloom filter for topics of interest for this node
-	bloomFilterToleranceIdx                         // Bloom filter tolerated by the whisper node for a limited time
-	lightClientModeIdx                              // Light client mode. (does not forward any messages)
-	restrictConnectionBetweenLightClientsIdx        // Restrict connection between two light clients
-)
-
-// Whisper represents a dark communication interface through the Ethereum
-// network, using its very own P2P communication layer.
-type Whisper struct {
-	protocol p2p.Protocol // Protocol description and parameters
-	filters  *Filters     // Message filters installed with Subscribe function
-
-	privateKeys map[string]*ecdsa.PrivateKey // Private key storage
-	symKeys     map[string][]byte            // Symmetric key storage
-	keyMu       sync.RWMutex                 // Mutex associated with key storages
-
-	poolMu      sync.RWMutex              // Mutex to sync the message and expiration pools
-	envelopes   map[common.Hash]*Envelope // Pool of envelopes currently tracked by this node
-	expirations map[uint32]mapset.Set     // Message expiration pool
-
-	peerMu sync.RWMutex       // Mutex to sync the active peer set
-	peers  map[*Peer]struct{} // Set of currently active peers
-
-	messageQueue chan *Envelope // Message queue for normal whisper messages
-	p2pMsgQueue  chan *Envelope // Message queue for peer-to-peer messages (not to be forwarded any further)
-	quit         chan struct{}  // Channel used for graceful exit
-
-	settings syncmap.Map // holds configuration settings that can be dynamically changed
-
-	syncAllowance int // maximum time in seconds allowed to process the whisper-related messages
-
-	statsMu sync.Mutex // guard stats
-	stats   Statistics // Statistics of whisper node
-
-	mailServer MailServer // MailServer interface
-
-	wg sync.WaitGroup
-}
-
-// New creates a Whisper client ready to communicate through the Ethereum P2P network.
-func New(cfg *Config) *Whisper {
-	if cfg == nil {
-		cfg = &DefaultConfig
-	}
-
-	whisper := &Whisper{
-		privateKeys:   make(map[string]*ecdsa.PrivateKey),
-		symKeys:       make(map[string][]byte),
-		envelopes:     make(map[common.Hash]*Envelope),
-		expirations:   make(map[uint32]mapset.Set),
-		peers:         make(map[*Peer]struct{}),
-		messageQueue:  make(chan *Envelope, messageQueueLimit),
-		p2pMsgQueue:   make(chan *Envelope, messageQueueLimit),
-		quit:          make(chan struct{}),
-		syncAllowance: DefaultSyncAllowance,
-	}
-
-	whisper.filters = NewFilters(whisper)
-
-	whisper.settings.Store(minPowIdx, cfg.MinimumAcceptedPOW)
-	whisper.settings.Store(maxMsgSizeIdx, cfg.MaxMessageSize)
-	whisper.settings.Store(overflowIdx, false)
-	whisper.settings.Store(restrictConnectionBetweenLightClientsIdx, cfg.RestrictConnectionBetweenLightClients)
-
-	// p2p whisper sub protocol handler
-	whisper.protocol = p2p.Protocol{
-		Name:    ProtocolName,
-		Version: uint(ProtocolVersion),
-		Length:  NumberOfMessageCodes,
-		Run:     whisper.HandlePeer,
-		NodeInfo: func() interface{} {
-			return map[string]interface{}{
-				"version":        ProtocolVersionStr,
-				"maxMessageSize": whisper.MaxMessageSize(),
-				"minimumPoW":     whisper.MinPow(),
-			}
-		},
-	}
-
-	return whisper
-}
-
-// MinPow returns the PoW value required by this node.
-func (whisper *Whisper) MinPow() float64 {
-	val, exist := whisper.settings.Load(minPowIdx)
-	if !exist || val == nil {
-		return DefaultMinimumPoW
-	}
-	v, ok := val.(float64)
-	if !ok {
-		log.Error("Error loading minPowIdx, using default")
-		return DefaultMinimumPoW
-	}
-	return v
-}
-
-// MinPowTolerance returns the value of minimum PoW which is tolerated for a limited
-// time after PoW was changed. If sufficient time have elapsed or no change of PoW
-// have ever occurred, the return value will be the same as return value of MinPow().
-func (whisper *Whisper) MinPowTolerance() float64 {
-	val, exist := whisper.settings.Load(minPowToleranceIdx)
-	if !exist || val == nil {
-		return DefaultMinimumPoW
-	}
-	return val.(float64)
-}
-
-// BloomFilter returns the aggregated bloom filter for all the topics of interest.
-// The nodes are required to send only messages that match the advertised bloom filter.
-// If a message does not match the bloom, it will tantamount to spam, and the peer will
-// be disconnected.
-func (whisper *Whisper) BloomFilter() []byte {
-	val, exist := whisper.settings.Load(bloomFilterIdx)
-	if !exist || val == nil {
-		return nil
-	}
-	return val.([]byte)
-}
-
-// BloomFilterTolerance returns the bloom filter which is tolerated for a limited
-// time after new bloom was advertised to the peers. If sufficient time have elapsed
-// or no change of bloom filter have ever occurred, the return value will be the same
-// as return value of BloomFilter().
-func (whisper *Whisper) BloomFilterTolerance() []byte {
-	val, exist := whisper.settings.Load(bloomFilterToleranceIdx)
-	if !exist || val == nil {
-		return nil
-	}
-	return val.([]byte)
-}
-
-// MaxMessageSize returns the maximum accepted message size.
-func (whisper *Whisper) MaxMessageSize() uint32 {
-	val, _ := whisper.settings.Load(maxMsgSizeIdx)
-	return val.(uint32)
-}
-
-// Overflow returns an indication if the message queue is full.
-func (whisper *Whisper) Overflow() bool {
-	val, _ := whisper.settings.Load(overflowIdx)
-	return val.(bool)
-}
-
-// APIs returns the RPC descriptors the Whisper implementation offers
-func (whisper *Whisper) APIs() []rpc.API {
-	return []rpc.API{
-		{
-			Namespace: ProtocolName,
-			Version:   ProtocolVersionStr,
-			Service:   NewPublicWhisperAPI(whisper),
-			Public:    true,
-		},
-	}
-}
-
-// RegisterServer registers MailServer interface.
-// MailServer will process all the incoming messages with p2pRequestCode.
-func (whisper *Whisper) RegisterServer(server MailServer) {
-	whisper.mailServer = server
-}
-
-// Protocols returns the whisper sub-protocols ran by this particular client.
-func (whisper *Whisper) Protocols() []p2p.Protocol {
-	return []p2p.Protocol{whisper.protocol}
-}
-
-// Version returns the whisper sub-protocols version number.
-func (whisper *Whisper) Version() uint {
-	return whisper.protocol.Version
-}
-
-// SetMaxMessageSize sets the maximal message size allowed by this node
-func (whisper *Whisper) SetMaxMessageSize(size uint32) error {
-	if size > MaxMessageSize {
-		return fmt.Errorf("message size too large [%d>%d]", size, MaxMessageSize)
-	}
-	whisper.settings.Store(maxMsgSizeIdx, size)
-	return nil
-}
-
-// SetBloomFilter sets the new bloom filter
-func (whisper *Whisper) SetBloomFilter(bloom []byte) error {
-	if len(bloom) != BloomFilterSize {
-		return fmt.Errorf("invalid bloom filter size: %d", len(bloom))
-	}
-
-	b := make([]byte, BloomFilterSize)
-	copy(b, bloom)
-
-	whisper.settings.Store(bloomFilterIdx, b)
-	whisper.notifyPeersAboutBloomFilterChange(b)
-
-	whisper.wg.Add(1)
-	go func() {
-		// allow some time before all the peers have processed the notification
-		defer whisper.wg.Done()
-		time.Sleep(time.Duration(whisper.syncAllowance) * time.Second)
-		whisper.settings.Store(bloomFilterToleranceIdx, b)
-	}()
-
-	return nil
-}
-
-// SetMinimumPoW sets the minimal PoW required by this node
-func (whisper *Whisper) SetMinimumPoW(val float64) error {
-	if val < 0.0 {
-		return fmt.Errorf("invalid PoW: %f", val)
-	}
-
-	whisper.settings.Store(minPowIdx, val)
-	whisper.notifyPeersAboutPowRequirementChange(val)
-
-	whisper.wg.Add(1)
-	go func() {
-		defer whisper.wg.Done()
-		// allow some time before all the peers have processed the notification
-		time.Sleep(time.Duration(whisper.syncAllowance) * time.Second)
-		whisper.settings.Store(minPowToleranceIdx, val)
-	}()
-
-	return nil
-}
-
-// SetMinimumPowTest sets the minimal PoW in test environment
-func (whisper *Whisper) SetMinimumPowTest(val float64) {
-	whisper.settings.Store(minPowIdx, val)
-	whisper.notifyPeersAboutPowRequirementChange(val)
-	whisper.settings.Store(minPowToleranceIdx, val)
-}
-
-//SetLightClientMode makes node light client (does not forward any messages)
-func (whisper *Whisper) SetLightClientMode(v bool) {
-	whisper.settings.Store(lightClientModeIdx, v)
-}
-
-//LightClientMode indicates is this node is light client (does not forward any messages)
-func (whisper *Whisper) LightClientMode() bool {
-	val, exist := whisper.settings.Load(lightClientModeIdx)
-	if !exist || val == nil {
-		return false
-	}
-	v, ok := val.(bool)
-	return v && ok
-}
-
-//LightClientModeConnectionRestricted indicates that connection to light client in light client mode not allowed
-func (whisper *Whisper) LightClientModeConnectionRestricted() bool {
-	val, exist := whisper.settings.Load(restrictConnectionBetweenLightClientsIdx)
-	if !exist || val == nil {
-		return false
-	}
-	v, ok := val.(bool)
-	return v && ok
-}
-
-func (whisper *Whisper) notifyPeersAboutPowRequirementChange(pow float64) {
-	arr := whisper.getPeers()
-	for _, p := range arr {
-		err := p.notifyAboutPowRequirementChange(pow)
-		if err != nil {
-			// allow one retry
-			err = p.notifyAboutPowRequirementChange(pow)
-		}
-		if err != nil {
-			log.Warn("failed to notify peer about new pow requirement", "peer", p.ID(), "error", err)
-		}
-	}
-}
-
-func (whisper *Whisper) notifyPeersAboutBloomFilterChange(bloom []byte) {
-	arr := whisper.getPeers()
-	for _, p := range arr {
-		err := p.notifyAboutBloomFilterChange(bloom)
-		if err != nil {
-			// allow one retry
-			err = p.notifyAboutBloomFilterChange(bloom)
-		}
-		if err != nil {
-			log.Warn("failed to notify peer about new bloom filter", "peer", p.ID(), "error", err)
-		}
-	}
-}
-
-func (whisper *Whisper) getPeers() []*Peer {
-	arr := make([]*Peer, len(whisper.peers))
-	i := 0
-	whisper.peerMu.Lock()
-	for p := range whisper.peers {
-		arr[i] = p
-		i++
-	}
-	whisper.peerMu.Unlock()
-	return arr
-}
-
-// getPeer retrieves peer by ID
-func (whisper *Whisper) getPeer(peerID []byte) (*Peer, error) {
-	whisper.peerMu.Lock()
-	defer whisper.peerMu.Unlock()
-	for p := range whisper.peers {
-		id := p.peer.ID()
-		if bytes.Equal(peerID, id[:]) {
-			return p, nil
-		}
-	}
-	return nil, fmt.Errorf("could not find peer with ID: %x", peerID)
-}
-
-// AllowP2PMessagesFromPeer marks specific peer trusted,
-// which will allow it to send historic (expired) messages.
-func (whisper *Whisper) AllowP2PMessagesFromPeer(peerID []byte) error {
-	p, err := whisper.getPeer(peerID)
-	if err != nil {
-		return err
-	}
-	p.trusted = true
-	return nil
-}
-
-// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer,
-// which is known to implement MailServer interface, and is supposed to process this
-// request and respond with a number of peer-to-peer messages (possibly expired),
-// which are not supposed to be forwarded any further.
-// The whisper protocol is agnostic of the format and contents of envelope.
-func (whisper *Whisper) RequestHistoricMessages(peerID []byte, envelope *Envelope) error {
-	p, err := whisper.getPeer(peerID)
-	if err != nil {
-		return err
-	}
-	p.trusted = true
-	return p2p.Send(p.ws, p2pRequestCode, envelope)
-}
-
-// SendP2PMessage sends a peer-to-peer message to a specific peer.
-func (whisper *Whisper) SendP2PMessage(peerID []byte, envelope *Envelope) error {
-	p, err := whisper.getPeer(peerID)
-	if err != nil {
-		return err
-	}
-	return whisper.SendP2PDirect(p, envelope)
-}
-
-// SendP2PDirect sends a peer-to-peer message to a specific peer.
-func (whisper *Whisper) SendP2PDirect(peer *Peer, envelope *Envelope) error {
-	return p2p.Send(peer.ws, p2pMessageCode, envelope)
-}
-
-// NewKeyPair generates a new cryptographic identity for the client, and injects
-// it into the known identities for message decryption. Returns ID of the new key pair.
-func (whisper *Whisper) NewKeyPair() (string, error) {
-	key, err := crypto.GenerateKey()
-	if err != nil || !validatePrivateKey(key) {
-		key, err = crypto.GenerateKey() // retry once
-	}
-	if err != nil {
-		return "", err
-	}
-	if !validatePrivateKey(key) {
-		return "", fmt.Errorf("failed to generate valid key")
-	}
-
-	id, err := GenerateRandomID()
-	if err != nil {
-		return "", fmt.Errorf("failed to generate ID: %s", err)
-	}
-
-	whisper.keyMu.Lock()
-	defer whisper.keyMu.Unlock()
-
-	if whisper.privateKeys[id] != nil {
-		return "", fmt.Errorf("failed to generate unique ID")
-	}
-	whisper.privateKeys[id] = key
-	return id, nil
-}
-
-// DeleteKeyPair deletes the specified key if it exists.
-func (whisper *Whisper) DeleteKeyPair(key string) bool {
-	whisper.keyMu.Lock()
-	defer whisper.keyMu.Unlock()
-
-	if whisper.privateKeys[key] != nil {
-		delete(whisper.privateKeys, key)
-		return true
-	}
-	return false
-}
-
-// AddKeyPair imports a asymmetric private key and returns it identifier.
-func (whisper *Whisper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) {
-	id, err := GenerateRandomID()
-	if err != nil {
-		return "", fmt.Errorf("failed to generate ID: %s", err)
-	}
-
-	whisper.keyMu.Lock()
-	whisper.privateKeys[id] = key
-	whisper.keyMu.Unlock()
-
-	return id, nil
-}
-
-// HasKeyPair checks if the whisper node is configured with the private key
-// of the specified public pair.
-func (whisper *Whisper) HasKeyPair(id string) bool {
-	whisper.keyMu.RLock()
-	defer whisper.keyMu.RUnlock()
-	return whisper.privateKeys[id] != nil
-}
-
-// GetPrivateKey retrieves the private key of the specified identity.
-func (whisper *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) {
-	whisper.keyMu.RLock()
-	defer whisper.keyMu.RUnlock()
-	key := whisper.privateKeys[id]
-	if key == nil {
-		return nil, fmt.Errorf("invalid id")
-	}
-	return key, nil
-}
-
-// GenerateSymKey generates a random symmetric key and stores it under id,
-// which is then returned. Will be used in the future for session key exchange.
-func (whisper *Whisper) GenerateSymKey() (string, error) {
-	key, err := generateSecureRandomData(aesKeyLength)
-	if err != nil {
-		return "", err
-	} else if !validateDataIntegrity(key, aesKeyLength) {
-		return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data")
-	}
-
-	id, err := GenerateRandomID()
-	if err != nil {
-		return "", fmt.Errorf("failed to generate ID: %s", err)
-	}
-
-	whisper.keyMu.Lock()
-	defer whisper.keyMu.Unlock()
-
-	if whisper.symKeys[id] != nil {
-		return "", fmt.Errorf("failed to generate unique ID")
-	}
-	whisper.symKeys[id] = key
-	return id, nil
-}
-
-// AddSymKeyDirect stores the key, and returns its id.
-func (whisper *Whisper) AddSymKeyDirect(key []byte) (string, error) {
-	if len(key) != aesKeyLength {
-		return "", fmt.Errorf("wrong key size: %d", len(key))
-	}
-
-	id, err := GenerateRandomID()
-	if err != nil {
-		return "", fmt.Errorf("failed to generate ID: %s", err)
-	}
-
-	whisper.keyMu.Lock()
-	defer whisper.keyMu.Unlock()
-
-	if whisper.symKeys[id] != nil {
-		return "", fmt.Errorf("failed to generate unique ID")
-	}
-	whisper.symKeys[id] = key
-	return id, nil
-}
-
-// AddSymKeyFromPassword generates the key from password, stores it, and returns its id.
-func (whisper *Whisper) AddSymKeyFromPassword(password string) (string, error) {
-	id, err := GenerateRandomID()
-	if err != nil {
-		return "", fmt.Errorf("failed to generate ID: %s", err)
-	}
-	if whisper.HasSymKey(id) {
-		return "", fmt.Errorf("failed to generate unique ID")
-	}
-
-	// kdf should run no less than 0.1 seconds on an average computer,
-	// because it's an once in a session experience
-	derived := pbkdf2.Key([]byte(password), nil, 65356, aesKeyLength, sha256.New)
-	if err != nil {
-		return "", err
-	}
-
-	whisper.keyMu.Lock()
-	defer whisper.keyMu.Unlock()
-
-	// double check is necessary, because deriveKeyMaterial() is very slow
-	if whisper.symKeys[id] != nil {
-		return "", fmt.Errorf("critical error: failed to generate unique ID")
-	}
-	whisper.symKeys[id] = derived
-	return id, nil
-}
-
-// HasSymKey returns true if there is a key associated with the given id.
-// Otherwise returns false.
-func (whisper *Whisper) HasSymKey(id string) bool {
-	whisper.keyMu.RLock()
-	defer whisper.keyMu.RUnlock()
-	return whisper.symKeys[id] != nil
-}
-
-// DeleteSymKey deletes the key associated with the name string if it exists.
-func (whisper *Whisper) DeleteSymKey(id string) bool {
-	whisper.keyMu.Lock()
-	defer whisper.keyMu.Unlock()
-	if whisper.symKeys[id] != nil {
-		delete(whisper.symKeys, id)
-		return true
-	}
-	return false
-}
-
-// GetSymKey returns the symmetric key associated with the given id.
-func (whisper *Whisper) GetSymKey(id string) ([]byte, error) {
-	whisper.keyMu.RLock()
-	defer whisper.keyMu.RUnlock()
-	if whisper.symKeys[id] != nil {
-		return whisper.symKeys[id], nil
-	}
-	return nil, fmt.Errorf("non-existent key ID")
-}
-
-// Subscribe installs a new message handler used for filtering, decrypting
-// and subsequent storing of incoming messages.
-func (whisper *Whisper) Subscribe(f *Filter) (string, error) {
-	s, err := whisper.filters.Install(f)
-	if err == nil {
-		whisper.updateBloomFilter(f)
-	}
-	return s, err
-}
-
-// updateBloomFilter recalculates the new value of bloom filter,
-// and informs the peers if necessary.
-func (whisper *Whisper) updateBloomFilter(f *Filter) {
-	aggregate := make([]byte, BloomFilterSize)
-	for _, t := range f.Topics {
-		top := BytesToTopic(t)
-		b := TopicToBloom(top)
-		aggregate = addBloom(aggregate, b)
-	}
-
-	if !BloomFilterMatch(whisper.BloomFilter(), aggregate) {
-		// existing bloom filter must be updated
-		aggregate = addBloom(whisper.BloomFilter(), aggregate)
-		whisper.SetBloomFilter(aggregate)
-	}
-}
-
-// GetFilter returns the filter by id.
-func (whisper *Whisper) GetFilter(id string) *Filter {
-	return whisper.filters.Get(id)
-}
-
-// Unsubscribe removes an installed message handler.
-func (whisper *Whisper) Unsubscribe(id string) error {
-	ok := whisper.filters.Uninstall(id)
-	if !ok {
-		return fmt.Errorf("Unsubscribe: Invalid ID")
-	}
-	return nil
-}
-
-// Send injects a message into the whisper send queue, to be distributed in the
-// network in the coming cycles.
-func (whisper *Whisper) Send(envelope *Envelope) error {
-	ok, err := whisper.add(envelope, false)
-	if err == nil && !ok {
-		return fmt.Errorf("failed to add envelope")
-	}
-	return err
-}
-
-// Start implements node.Service, starting the background data propagation thread
-// of the Whisper protocol.
-func (whisper *Whisper) Start(*p2p.Server) error {
-	log.Info("started whisper v." + ProtocolVersionStr)
-	whisper.wg.Add(1)
-	go whisper.update()
-
-	numCPU := runtime.NumCPU()
-	for i := 0; i < numCPU; i++ {
-		whisper.wg.Add(1)
-		go whisper.processQueue()
-	}
-
-	return nil
-}
-
-// Stop implements node.Service, stopping the background data propagation thread
-// of the Whisper protocol.
-func (whisper *Whisper) Stop() error {
-	close(whisper.quit)
-	whisper.wg.Wait()
-	log.Info("whisper stopped")
-	return nil
-}
-
-// HandlePeer is called by the underlying P2P layer when the whisper sub-protocol
-// connection is negotiated.
-func (whisper *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
-	// Create the new peer and start tracking it
-	whisperPeer := newPeer(whisper, peer, rw)
-
-	whisper.peerMu.Lock()
-	whisper.peers[whisperPeer] = struct{}{}
-	whisper.peerMu.Unlock()
-
-	defer func() {
-		whisper.peerMu.Lock()
-		delete(whisper.peers, whisperPeer)
-		whisper.peerMu.Unlock()
-	}()
-
-	// Run the peer handshake and state updates
-	if err := whisperPeer.handshake(); err != nil {
-		return err
-	}
-	whisperPeer.start()
-	defer whisperPeer.stop()
-
-	return whisper.runMessageLoop(whisperPeer, rw)
-}
-
-// runMessageLoop reads and processes inbound messages directly to merge into client-global state.
-func (whisper *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error {
-	for {
-		// fetch the next packet
-		packet, err := rw.ReadMsg()
-		if err != nil {
-			log.Info("message loop", "peer", p.peer.ID(), "err", err)
-			return err
-		}
-		if packet.Size > whisper.MaxMessageSize() {
-			log.Warn("oversized message received", "peer", p.peer.ID())
-			return errors.New("oversized message received")
-		}
-
-		switch packet.Code {
-		case statusCode:
-			// this should not happen, but no need to panic; just ignore this message.
-			log.Warn("unxepected status message received", "peer", p.peer.ID())
-		case messagesCode:
-			// decode the contained envelopes
-			var envelopes []*Envelope
-			if err := packet.Decode(&envelopes); err != nil {
-				log.Warn("failed to decode envelopes, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-				return errors.New("invalid envelopes")
-			}
-
-			trouble := false
-			for _, env := range envelopes {
-				cached, err := whisper.add(env, whisper.LightClientMode())
-				if err != nil {
-					trouble = true
-					log.Error("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-				}
-				if cached {
-					p.mark(env)
-				}
-			}
-
-			if trouble {
-				return errors.New("invalid envelope")
-			}
-		case powRequirementCode:
-			s := rlp.NewStream(packet.Payload, uint64(packet.Size))
-			i, err := s.Uint()
-			if err != nil {
-				log.Warn("failed to decode powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-				return errors.New("invalid powRequirementCode message")
-			}
-			f := math.Float64frombits(i)
-			if math.IsInf(f, 0) || math.IsNaN(f) || f < 0.0 {
-				log.Warn("invalid value in powRequirementCode message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-				return errors.New("invalid value in powRequirementCode message")
-			}
-			p.powRequirement = f
-		case bloomFilterExCode:
-			var bloom []byte
-			err := packet.Decode(&bloom)
-			if err == nil && len(bloom) != BloomFilterSize {
-				err = fmt.Errorf("wrong bloom filter size %d", len(bloom))
-			}
-
-			if err != nil {
-				log.Warn("failed to decode bloom filter exchange message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-				return errors.New("invalid bloom filter exchange message")
-			}
-			p.setBloomFilter(bloom)
-		case p2pMessageCode:
-			// peer-to-peer message, sent directly to peer bypassing PoW checks, etc.
-			// this message is not supposed to be forwarded to other peers, and
-			// therefore might not satisfy the PoW, expiry and other requirements.
-			// these messages are only accepted from the trusted peer.
-			if p.trusted {
-				var envelope Envelope
-				if err := packet.Decode(&envelope); err != nil {
-					log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-					return errors.New("invalid direct message")
-				}
-				whisper.postEvent(&envelope, true)
-			}
-		case p2pRequestCode:
-			// Must be processed if mail server is implemented. Otherwise ignore.
-			if whisper.mailServer != nil {
-				var request Envelope
-				if err := packet.Decode(&request); err != nil {
-					log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err)
-					return errors.New("invalid p2p request")
-				}
-				whisper.mailServer.DeliverMail(p, &request)
-			}
-		default:
-			// New message types might be implemented in the future versions of Whisper.
-			// For forward compatibility, just ignore.
-		}
-
-		packet.Discard()
-	}
-}
-
-// add inserts a new envelope into the message pool to be distributed within the
-// whisper network. It also inserts the envelope into the expiration pool at the
-// appropriate time-stamp. In case of error, connection should be dropped.
-// param isP2P indicates whether the message is peer-to-peer (should not be forwarded).
-func (whisper *Whisper) add(envelope *Envelope, isP2P bool) (bool, error) {
-	now := uint32(time.Now().Unix())
-	sent := envelope.Expiry - envelope.TTL
-
-	if sent > now {
-		if sent-DefaultSyncAllowance > now {
-			return false, fmt.Errorf("envelope created in the future [%x]", envelope.Hash())
-		}
-		// recalculate PoW, adjusted for the time difference, plus one second for latency
-		envelope.calculatePoW(sent - now + 1)
-	}
-
-	if envelope.Expiry < now {
-		if envelope.Expiry+DefaultSyncAllowance*2 < now {
-			return false, fmt.Errorf("very old message")
-		}
-		log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex())
-		return false, nil // drop envelope without error
-	}
-
-	if uint32(envelope.size()) > whisper.MaxMessageSize() {
-		return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash())
-	}
-
-	if envelope.PoW() < whisper.MinPow() {
-		// maybe the value was recently changed, and the peers did not adjust yet.
-		// in this case the previous value is retrieved by MinPowTolerance()
-		// for a short period of peer synchronization.
-		if envelope.PoW() < whisper.MinPowTolerance() {
-			return false, fmt.Errorf("envelope with low PoW received: PoW=%f, hash=[%v]", envelope.PoW(), envelope.Hash().Hex())
-		}
-	}
-
-	if !BloomFilterMatch(whisper.BloomFilter(), envelope.Bloom()) {
-		// maybe the value was recently changed, and the peers did not adjust yet.
-		// in this case the previous value is retrieved by BloomFilterTolerance()
-		// for a short period of peer synchronization.
-		if !BloomFilterMatch(whisper.BloomFilterTolerance(), envelope.Bloom()) {
-			return false, fmt.Errorf("envelope does not match bloom filter, hash=[%v], bloom: \n%x \n%x \n%x",
-				envelope.Hash().Hex(), whisper.BloomFilter(), envelope.Bloom(), envelope.Topic)
-		}
-	}
-
-	hash := envelope.Hash()
-
-	whisper.poolMu.Lock()
-	_, alreadyCached := whisper.envelopes[hash]
-	if !alreadyCached {
-		whisper.envelopes[hash] = envelope
-		if whisper.expirations[envelope.Expiry] == nil {
-			whisper.expirations[envelope.Expiry] = mapset.NewThreadUnsafeSet()
-		}
-		if !whisper.expirations[envelope.Expiry].Contains(hash) {
-			whisper.expirations[envelope.Expiry].Add(hash)
-		}
-	}
-	whisper.poolMu.Unlock()
-
-	if alreadyCached {
-		log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex())
-	} else {
-		log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex())
-		whisper.statsMu.Lock()
-		whisper.stats.memoryUsed += envelope.size()
-		whisper.statsMu.Unlock()
-		whisper.postEvent(envelope, isP2P) // notify the local node about the new message
-		if whisper.mailServer != nil {
-			whisper.mailServer.Archive(envelope)
-		}
-	}
-	return true, nil
-}
-
-// postEvent queues the message for further processing.
-func (whisper *Whisper) postEvent(envelope *Envelope, isP2P bool) {
-	if isP2P {
-		whisper.p2pMsgQueue <- envelope
-	} else {
-		whisper.checkOverflow()
-		whisper.messageQueue <- envelope
-	}
-}
-
-// checkOverflow checks if message queue overflow occurs and reports it if necessary.
-func (whisper *Whisper) checkOverflow() {
-	queueSize := len(whisper.messageQueue)
-
-	if queueSize == messageQueueLimit {
-		if !whisper.Overflow() {
-			whisper.settings.Store(overflowIdx, true)
-			log.Warn("message queue overflow")
-		}
-	} else if queueSize <= messageQueueLimit/2 {
-		if whisper.Overflow() {
-			whisper.settings.Store(overflowIdx, false)
-			log.Warn("message queue overflow fixed (back to normal)")
-		}
-	}
-}
-
-// processQueue delivers the messages to the watchers during the lifetime of the whisper node.
-func (whisper *Whisper) processQueue() {
-	defer whisper.wg.Done()
-	var e *Envelope
-	for {
-		select {
-		case <-whisper.quit:
-			return
-
-		case e = <-whisper.messageQueue:
-			whisper.filters.NotifyWatchers(e, false)
-
-		case e = <-whisper.p2pMsgQueue:
-			whisper.filters.NotifyWatchers(e, true)
-		}
-	}
-}
-
-// update loops until the lifetime of the whisper node, updating its internal
-// state by expiring stale messages from the pool.
-func (whisper *Whisper) update() {
-	defer whisper.wg.Done()
-	// Start a ticker to check for expirations
-	expire := time.NewTicker(expirationCycle)
-	defer expire.Stop()
-
-	// Repeat updates until termination is requested
-	for {
-		select {
-		case <-expire.C:
-			whisper.expire()
-
-		case <-whisper.quit:
-			return
-		}
-	}
-}
-
-// expire iterates over all the expiration timestamps, removing all stale
-// messages from the pools.
-func (whisper *Whisper) expire() {
-	whisper.poolMu.Lock()
-	defer whisper.poolMu.Unlock()
-
-	whisper.statsMu.Lock()
-	defer whisper.statsMu.Unlock()
-	whisper.stats.reset()
-	now := uint32(time.Now().Unix())
-	for expiry, hashSet := range whisper.expirations {
-		if expiry < now {
-			// Dump all expired messages and remove timestamp
-			hashSet.Each(func(v interface{}) bool {
-				sz := whisper.envelopes[v.(common.Hash)].size()
-				delete(whisper.envelopes, v.(common.Hash))
-				whisper.stats.messagesCleared++
-				whisper.stats.memoryCleared += sz
-				whisper.stats.memoryUsed -= sz
-				return false
-			})
-			whisper.expirations[expiry].Clear()
-			delete(whisper.expirations, expiry)
-		}
-	}
-}
-
-// Stats returns the whisper node statistics.
-func (whisper *Whisper) Stats() Statistics {
-	whisper.statsMu.Lock()
-	defer whisper.statsMu.Unlock()
-
-	return whisper.stats
-}
-
-// Envelopes retrieves all the messages currently pooled by the node.
-func (whisper *Whisper) Envelopes() []*Envelope {
-	whisper.poolMu.RLock()
-	defer whisper.poolMu.RUnlock()
-
-	all := make([]*Envelope, 0, len(whisper.envelopes))
-	for _, envelope := range whisper.envelopes {
-		all = append(all, envelope)
-	}
-	return all
-}
-
-// isEnvelopeCached checks if envelope with specific hash has already been received and cached.
-func (whisper *Whisper) isEnvelopeCached(hash common.Hash) bool {
-	whisper.poolMu.Lock()
-	defer whisper.poolMu.Unlock()
-
-	_, exist := whisper.envelopes[hash]
-	return exist
-}
-
-// reset resets the node's statistics after each expiry cycle.
-func (s *Statistics) reset() {
-	s.cycles++
-	s.totalMessagesCleared += s.messagesCleared
-
-	s.memoryCleared = 0
-	s.messagesCleared = 0
-}
-
-// ValidatePublicKey checks the format of the given public key.
-func ValidatePublicKey(k *ecdsa.PublicKey) bool {
-	return k != nil && k.X != nil && k.Y != nil && k.X.Sign() != 0 && k.Y.Sign() != 0
-}
-
-// validatePrivateKey checks the format of the given private key.
-func validatePrivateKey(k *ecdsa.PrivateKey) bool {
-	if k == nil || k.D == nil || k.D.Sign() == 0 {
-		return false
-	}
-	return ValidatePublicKey(&k.PublicKey)
-}
-
-// validateDataIntegrity returns false if the data have the wrong or contains all zeros,
-// which is the simplest and the most common bug.
-func validateDataIntegrity(k []byte, expectedSize int) bool {
-	if len(k) != expectedSize {
-		return false
-	}
-	if expectedSize > 3 && containsOnlyZeros(k) {
-		return false
-	}
-	return true
-}
-
-// containsOnlyZeros checks if the data contain only zeros.
-func containsOnlyZeros(data []byte) bool {
-	for _, b := range data {
-		if b != 0 {
-			return false
-		}
-	}
-	return true
-}
-
-// bytesToUintLittleEndian converts the slice to 64-bit unsigned integer.
-func bytesToUintLittleEndian(b []byte) (res uint64) {
-	mul := uint64(1)
-	for i := 0; i < len(b); i++ {
-		res += uint64(b[i]) * mul
-		mul *= 256
-	}
-	return res
-}
-
-// BytesToUintBigEndian converts the slice to 64-bit unsigned integer.
-func BytesToUintBigEndian(b []byte) (res uint64) {
-	for i := 0; i < len(b); i++ {
-		res *= 256
-		res += uint64(b[i])
-	}
-	return res
-}
-
-// GenerateRandomID generates a random string, which is then returned to be used as a key id
-func GenerateRandomID() (id string, err error) {
-	buf, err := generateSecureRandomData(keyIDSize)
-	if err != nil {
-		return "", err
-	}
-	if !validateDataIntegrity(buf, keyIDSize) {
-		return "", fmt.Errorf("error in generateRandomID: crypto/rand failed to generate random data")
-	}
-	id = common.Bytes2Hex(buf)
-	return id, err
-}
-
-func isFullNode(bloom []byte) bool {
-	if bloom == nil {
-		return true
-	}
-	for _, b := range bloom {
-		if b != 255 {
-			return false
-		}
-	}
-	return true
-}
-
-func BloomFilterMatch(filter, sample []byte) bool {
-	if filter == nil {
-		return true
-	}
-
-	for i := 0; i < BloomFilterSize; i++ {
-		f := filter[i]
-		s := sample[i]
-		if (f | s) != f {
-			return false
-		}
-	}
-
-	return true
-}
-
-func addBloom(a, b []byte) []byte {
-	c := make([]byte, BloomFilterSize)
-	for i := 0; i < BloomFilterSize; i++ {
-		c[i] = a[i] | b[i]
-	}
-	return c
-}
diff --git a/whisper/whisperv6/whisper_test.go b/whisper/whisperv6/whisper_test.go
deleted file mode 100644
index 012e217710..0000000000
--- a/whisper/whisperv6/whisper_test.go
+++ /dev/null
@@ -1,891 +0,0 @@
-// Copyright 2016 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 .
-
-package whisperv6
-
-import (
-	"bytes"
-	"crypto/ecdsa"
-	"crypto/sha256"
-	mrand "math/rand"
-	"testing"
-	"time"
-
-	"github.com/ethereum/go-ethereum/common"
-	"golang.org/x/crypto/pbkdf2"
-)
-
-func TestWhisperBasic(t *testing.T) {
-	w := New(&DefaultConfig)
-	p := w.Protocols()
-	shh := p[0]
-	if shh.Name != ProtocolName {
-		t.Fatalf("failed Protocol Name: %v.", shh.Name)
-	}
-	if uint64(shh.Version) != ProtocolVersion {
-		t.Fatalf("failed Protocol Version: %v.", shh.Version)
-	}
-	if shh.Length != NumberOfMessageCodes {
-		t.Fatalf("failed Protocol Length: %v.", shh.Length)
-	}
-	if shh.Run == nil {
-		t.Fatalf("failed shh.Run.")
-	}
-	if uint64(w.Version()) != ProtocolVersion {
-		t.Fatalf("failed whisper Version: %v.", shh.Version)
-	}
-	if w.GetFilter("non-existent") != nil {
-		t.Fatalf("failed GetFilter.")
-	}
-
-	peerID := make([]byte, 64)
-	mrand.Read(peerID)
-	peer, _ := w.getPeer(peerID)
-	if peer != nil {
-		t.Fatal("found peer for random key.")
-	}
-	if err := w.AllowP2PMessagesFromPeer(peerID); err == nil {
-		t.Fatalf("failed MarkPeerTrusted.")
-	}
-	exist := w.HasSymKey("non-existing")
-	if exist {
-		t.Fatalf("failed HasSymKey.")
-	}
-	key, err := w.GetSymKey("non-existing")
-	if err == nil {
-		t.Fatalf("failed GetSymKey(non-existing): false positive.")
-	}
-	if key != nil {
-		t.Fatalf("failed GetSymKey: false positive.")
-	}
-	mail := w.Envelopes()
-	if len(mail) != 0 {
-		t.Fatalf("failed w.Envelopes().")
-	}
-
-	derived := pbkdf2.Key(peerID, nil, 65356, aesKeyLength, sha256.New)
-	if !validateDataIntegrity(derived, aesKeyLength) {
-		t.Fatalf("failed validateSymmetricKey with param = %v.", derived)
-	}
-	if containsOnlyZeros(derived) {
-		t.Fatalf("failed containsOnlyZeros with param = %v.", derived)
-	}
-
-	buf := []byte{0xFF, 0xE5, 0x80, 0x2, 0}
-	le := bytesToUintLittleEndian(buf)
-	be := BytesToUintBigEndian(buf)
-	if le != uint64(0x280e5ff) {
-		t.Fatalf("failed bytesToIntLittleEndian: %d.", le)
-	}
-	if be != uint64(0xffe5800200) {
-		t.Fatalf("failed BytesToIntBigEndian: %d.", be)
-	}
-
-	id, err := w.NewKeyPair()
-	if err != nil {
-		t.Fatalf("failed to generate new key pair: %s.", err)
-	}
-	pk, err := w.GetPrivateKey(id)
-	if err != nil {
-		t.Fatalf("failed to retrieve new key pair: %s.", err)
-	}
-	if !validatePrivateKey(pk) {
-		t.Fatalf("failed validatePrivateKey: %v.", pk)
-	}
-	if !ValidatePublicKey(&pk.PublicKey) {
-		t.Fatalf("failed ValidatePublicKey: %v.", pk)
-	}
-}
-
-func TestWhisperAsymmetricKeyImport(t *testing.T) {
-	var (
-		w           = New(&DefaultConfig)
-		privateKeys []*ecdsa.PrivateKey
-	)
-
-	for i := 0; i < 50; i++ {
-		id, err := w.NewKeyPair()
-		if err != nil {
-			t.Fatalf("could not generate key: %v", err)
-		}
-
-		pk, err := w.GetPrivateKey(id)
-		if err != nil {
-			t.Fatalf("could not export private key: %v", err)
-		}
-
-		privateKeys = append(privateKeys, pk)
-
-		if !w.DeleteKeyPair(id) {
-			t.Fatalf("could not delete private key")
-		}
-	}
-
-	for _, pk := range privateKeys {
-		if _, err := w.AddKeyPair(pk); err != nil {
-			t.Fatalf("could not import private key: %v", err)
-		}
-	}
-}
-
-func TestWhisperIdentityManagement(t *testing.T) {
-	w := New(&DefaultConfig)
-	id1, err := w.NewKeyPair()
-	if err != nil {
-		t.Fatalf("failed to generate new key pair: %s.", err)
-	}
-	id2, err := w.NewKeyPair()
-	if err != nil {
-		t.Fatalf("failed to generate new key pair: %s.", err)
-	}
-	pk1, err := w.GetPrivateKey(id1)
-	if err != nil {
-		t.Fatalf("failed to retrieve the key pair: %s.", err)
-	}
-	pk2, err := w.GetPrivateKey(id2)
-	if err != nil {
-		t.Fatalf("failed to retrieve the key pair: %s.", err)
-	}
-
-	if !w.HasKeyPair(id1) {
-		t.Fatalf("failed HasIdentity(pk1).")
-	}
-	if !w.HasKeyPair(id2) {
-		t.Fatalf("failed HasIdentity(pk2).")
-	}
-	if pk1 == nil {
-		t.Fatalf("failed GetIdentity(pk1).")
-	}
-	if pk2 == nil {
-		t.Fatalf("failed GetIdentity(pk2).")
-	}
-
-	if !validatePrivateKey(pk1) {
-		t.Fatalf("pk1 is invalid.")
-	}
-	if !validatePrivateKey(pk2) {
-		t.Fatalf("pk2 is invalid.")
-	}
-
-	// Delete one identity
-	done := w.DeleteKeyPair(id1)
-	if !done {
-		t.Fatalf("failed to delete id1.")
-	}
-	pk1, err = w.GetPrivateKey(id1)
-	if err == nil {
-		t.Fatalf("retrieve the key pair: false positive.")
-	}
-	pk2, err = w.GetPrivateKey(id2)
-	if err != nil {
-		t.Fatalf("failed to retrieve the key pair: %s.", err)
-	}
-	if w.HasKeyPair(id1) {
-		t.Fatalf("failed DeleteIdentity(pub1): still exist.")
-	}
-	if !w.HasKeyPair(id2) {
-		t.Fatalf("failed DeleteIdentity(pub1): pub2 does not exist.")
-	}
-	if pk1 != nil {
-		t.Fatalf("failed DeleteIdentity(pub1): first key still exist.")
-	}
-	if pk2 == nil {
-		t.Fatalf("failed DeleteIdentity(pub1): second key does not exist.")
-	}
-
-	// Delete again non-existing identity
-	done = w.DeleteKeyPair(id1)
-	if done {
-		t.Fatalf("delete id1: false positive.")
-	}
-	pk1, err = w.GetPrivateKey(id1)
-	if err == nil {
-		t.Fatalf("retrieve the key pair: false positive.")
-	}
-	pk2, err = w.GetPrivateKey(id2)
-	if err != nil {
-		t.Fatalf("failed to retrieve the key pair: %s.", err)
-	}
-	if w.HasKeyPair(id1) {
-		t.Fatalf("failed delete non-existing identity: exist.")
-	}
-	if !w.HasKeyPair(id2) {
-		t.Fatalf("failed delete non-existing identity: pub2 does not exist.")
-	}
-	if pk1 != nil {
-		t.Fatalf("failed delete non-existing identity: first key exist.")
-	}
-	if pk2 == nil {
-		t.Fatalf("failed delete non-existing identity: second key does not exist.")
-	}
-
-	// Delete second identity
-	done = w.DeleteKeyPair(id2)
-	if !done {
-		t.Fatalf("failed to delete id2.")
-	}
-	pk1, err = w.GetPrivateKey(id1)
-	if err == nil {
-		t.Fatalf("retrieve the key pair: false positive.")
-	}
-	pk2, err = w.GetPrivateKey(id2)
-	if err == nil {
-		t.Fatalf("retrieve the key pair: false positive.")
-	}
-	if w.HasKeyPair(id1) {
-		t.Fatalf("failed delete second identity: first identity exist.")
-	}
-	if w.HasKeyPair(id2) {
-		t.Fatalf("failed delete second identity: still exist.")
-	}
-	if pk1 != nil {
-		t.Fatalf("failed delete second identity: first key exist.")
-	}
-	if pk2 != nil {
-		t.Fatalf("failed delete second identity: second key exist.")
-	}
-}
-
-func TestWhisperSymKeyManagement(t *testing.T) {
-	InitSingleTest()
-
-	var (
-		k1, k2 []byte
-		w      = New(&DefaultConfig)
-		id2    = string("arbitrary-string-2")
-	)
-	id1, err := w.GenerateSymKey()
-	if err != nil {
-		t.Fatalf("failed GenerateSymKey with seed %d: %s.", seed, err)
-	}
-
-	k1, err = w.GetSymKey(id1)
-	if err != nil {
-		t.Fatalf("failed GetSymKey(id1).")
-	}
-	k2, err = w.GetSymKey(id2)
-	if err == nil {
-		t.Fatalf("failed GetSymKey(id2): false positive.")
-	}
-	if !w.HasSymKey(id1) {
-		t.Fatalf("failed HasSymKey(id1).")
-	}
-	if w.HasSymKey(id2) {
-		t.Fatalf("failed HasSymKey(id2): false positive.")
-	}
-	if k1 == nil {
-		t.Fatalf("first key does not exist.")
-	}
-	if k2 != nil {
-		t.Fatalf("second key still exist.")
-	}
-
-	// add existing id, nothing should change
-	randomKey := make([]byte, aesKeyLength)
-	mrand.Read(randomKey)
-	id1, err = w.AddSymKeyDirect(randomKey)
-	if err != nil {
-		t.Fatalf("failed AddSymKey with seed %d: %s.", seed, err)
-	}
-
-	k1, err = w.GetSymKey(id1)
-	if err != nil {
-		t.Fatalf("failed w.GetSymKey(id1).")
-	}
-	k2, err = w.GetSymKey(id2)
-	if err == nil {
-		t.Fatalf("failed w.GetSymKey(id2): false positive.")
-	}
-	if !w.HasSymKey(id1) {
-		t.Fatalf("failed w.HasSymKey(id1).")
-	}
-	if w.HasSymKey(id2) {
-		t.Fatalf("failed w.HasSymKey(id2): false positive.")
-	}
-	if k1 == nil {
-		t.Fatalf("first key does not exist.")
-	}
-	if !bytes.Equal(k1, randomKey) {
-		t.Fatalf("k1 != randomKey.")
-	}
-	if k2 != nil {
-		t.Fatalf("second key already exist.")
-	}
-
-	id2, err = w.AddSymKeyDirect(randomKey)
-	if err != nil {
-		t.Fatalf("failed AddSymKey(id2) with seed %d: %s.", seed, err)
-	}
-	k1, err = w.GetSymKey(id1)
-	if err != nil {
-		t.Fatalf("failed w.GetSymKey(id1).")
-	}
-	k2, err = w.GetSymKey(id2)
-	if err != nil {
-		t.Fatalf("failed w.GetSymKey(id2).")
-	}
-	if !w.HasSymKey(id1) {
-		t.Fatalf("HasSymKey(id1) failed.")
-	}
-	if !w.HasSymKey(id2) {
-		t.Fatalf("HasSymKey(id2) failed.")
-	}
-	if k1 == nil {
-		t.Fatalf("k1 does not exist.")
-	}
-	if k2 == nil {
-		t.Fatalf("k2 does not exist.")
-	}
-	if !bytes.Equal(k1, k2) {
-		t.Fatalf("k1 != k2.")
-	}
-	if !bytes.Equal(k1, randomKey) {
-		t.Fatalf("k1 != randomKey.")
-	}
-	if len(k1) != aesKeyLength {
-		t.Fatalf("wrong length of k1.")
-	}
-	if len(k2) != aesKeyLength {
-		t.Fatalf("wrong length of k2.")
-	}
-
-	w.DeleteSymKey(id1)
-	k1, err = w.GetSymKey(id1)
-	if err == nil {
-		t.Fatalf("failed w.GetSymKey(id1): false positive.")
-	}
-	if k1 != nil {
-		t.Fatalf("failed GetSymKey(id1): false positive.")
-	}
-	k2, err = w.GetSymKey(id2)
-	if err != nil {
-		t.Fatalf("failed w.GetSymKey(id2).")
-	}
-	if w.HasSymKey(id1) {
-		t.Fatalf("failed to delete first key: still exist.")
-	}
-	if !w.HasSymKey(id2) {
-		t.Fatalf("failed to delete first key: second key does not exist.")
-	}
-	if k1 != nil {
-		t.Fatalf("failed to delete first key.")
-	}
-	if k2 == nil {
-		t.Fatalf("failed to delete first key: second key is nil.")
-	}
-
-	w.DeleteSymKey(id1)
-	w.DeleteSymKey(id2)
-	k1, err = w.GetSymKey(id1)
-	if err == nil {
-		t.Fatalf("failed w.GetSymKey(id1): false positive.")
-	}
-	k2, err = w.GetSymKey(id2)
-	if err == nil {
-		t.Fatalf("failed w.GetSymKey(id2): false positive.")
-	}
-	if k1 != nil || k2 != nil {
-		t.Fatalf("k1 or k2 is not nil")
-	}
-	if w.HasSymKey(id1) {
-		t.Fatalf("failed to delete second key: first key exist.")
-	}
-	if w.HasSymKey(id2) {
-		t.Fatalf("failed to delete second key: still exist.")
-	}
-	if k1 != nil {
-		t.Fatalf("failed to delete second key: first key is not nil.")
-	}
-	if k2 != nil {
-		t.Fatalf("failed to delete second key: second key is not nil.")
-	}
-
-	randomKey = make([]byte, aesKeyLength+1)
-	mrand.Read(randomKey)
-	_, err = w.AddSymKeyDirect(randomKey)
-	if err == nil {
-		t.Fatalf("added the key with wrong size, seed %d.", seed)
-	}
-
-	const password = "arbitrary data here"
-	id1, err = w.AddSymKeyFromPassword(password)
-	if err != nil {
-		t.Fatalf("failed AddSymKeyFromPassword(id1) with seed %d: %s.", seed, err)
-	}
-	id2, err = w.AddSymKeyFromPassword(password)
-	if err != nil {
-		t.Fatalf("failed AddSymKeyFromPassword(id2) with seed %d: %s.", seed, err)
-	}
-	k1, err = w.GetSymKey(id1)
-	if err != nil {
-		t.Fatalf("failed w.GetSymKey(id1).")
-	}
-	k2, err = w.GetSymKey(id2)
-	if err != nil {
-		t.Fatalf("failed w.GetSymKey(id2).")
-	}
-	if !w.HasSymKey(id1) {
-		t.Fatalf("HasSymKey(id1) failed.")
-	}
-	if !w.HasSymKey(id2) {
-		t.Fatalf("HasSymKey(id2) failed.")
-	}
-	if !validateDataIntegrity(k2, aesKeyLength) {
-		t.Fatalf("key validation failed.")
-	}
-	if !bytes.Equal(k1, k2) {
-		t.Fatalf("k1 != k2.")
-	}
-}
-
-func TestExpiry(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&DefaultConfig)
-	w.SetMinimumPowTest(0.0000001)
-	defer w.SetMinimumPowTest(DefaultMinimumPoW)
-	w.Start(nil)
-	defer w.Stop()
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	params.TTL = 1
-
-	messagesCount := 5
-
-	// Send a few messages one after another. Due to low PoW and expiration buckets
-	// with one second resolution, it covers a case when there are multiple items
-	// in a single expiration bucket.
-	for i := 0; i < messagesCount; i++ {
-		msg, err := NewSentMessage(params)
-		if err != nil {
-			t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-		}
-		env, err := msg.Wrap(params)
-		if err != nil {
-			t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-		}
-
-		err = w.Send(env)
-		if err != nil {
-			t.Fatalf("failed to send envelope with seed %d: %s.", seed, err)
-		}
-	}
-
-	// wait till received or timeout
-	var received, expired bool
-	for j := 0; j < 20; j++ {
-		time.Sleep(100 * time.Millisecond)
-		if len(w.Envelopes()) == messagesCount {
-			received = true
-			break
-		}
-	}
-
-	if !received {
-		t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
-	}
-
-	// wait till expired or timeout
-	for j := 0; j < 20; j++ {
-		time.Sleep(100 * time.Millisecond)
-		if len(w.Envelopes()) == 0 {
-			expired = true
-			break
-		}
-	}
-
-	if !expired {
-		t.Fatalf("expire failed, seed: %d.", seed)
-	}
-}
-
-func TestCustomization(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&DefaultConfig)
-	defer w.SetMinimumPowTest(DefaultMinimumPoW)
-	defer w.SetMaxMessageSize(DefaultMaxMessageSize)
-	w.Start(nil)
-	defer w.Stop()
-
-	const smallPoW = 0.00001
-
-	f, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	params.KeySym = f.KeySym
-	params.Topic = BytesToTopic(f.Topics[2])
-	params.PoW = smallPoW
-	params.TTL = 3600 * 24 // one day
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	err = w.Send(env)
-	if err == nil {
-		t.Fatalf("successfully sent envelope with PoW %.06f, false positive (seed %d).", env.PoW(), seed)
-	}
-
-	w.SetMinimumPowTest(smallPoW / 2)
-	err = w.Send(env)
-	if err != nil {
-		t.Fatalf("failed to send envelope with seed %d: %s.", seed, err)
-	}
-
-	params.TTL++
-	msg, err = NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err = msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-	w.SetMaxMessageSize(uint32(env.size() - 1))
-	err = w.Send(env)
-	if err == nil {
-		t.Fatalf("successfully sent oversized envelope (seed %d): false positive.", seed)
-	}
-
-	w.SetMaxMessageSize(DefaultMaxMessageSize)
-	err = w.Send(env)
-	if err != nil {
-		t.Fatalf("failed to send second envelope with seed %d: %s.", seed, err)
-	}
-
-	// wait till received or timeout
-	var received bool
-	for j := 0; j < 20; j++ {
-		time.Sleep(100 * time.Millisecond)
-		if len(w.Envelopes()) > 1 {
-			received = true
-			break
-		}
-	}
-
-	if !received {
-		t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
-	}
-
-	// check w.messages()
-	_, err = w.Subscribe(f)
-	if err != nil {
-		t.Fatalf("failed subscribe with seed %d: %s.", seed, err)
-	}
-	time.Sleep(5 * time.Millisecond)
-	mail := f.Retrieve()
-	if len(mail) > 0 {
-		t.Fatalf("received premature mail")
-	}
-}
-
-func TestSymmetricSendCycle(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&DefaultConfig)
-	defer w.SetMinimumPowTest(DefaultMinimumPoW)
-	defer w.SetMaxMessageSize(DefaultMaxMessageSize)
-	w.Start(nil)
-	defer w.Stop()
-
-	filter1, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	filter1.PoW = DefaultMinimumPoW
-
-	// Copy the first filter since some of its fields
-	// are randomly gnerated.
-	filter2 := &Filter{
-		KeySym:   filter1.KeySym,
-		Topics:   filter1.Topics,
-		PoW:      filter1.PoW,
-		AllowP2P: filter1.AllowP2P,
-		Messages: make(map[common.Hash]*ReceivedMessage),
-	}
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	filter1.Src = ¶ms.Src.PublicKey
-	filter2.Src = ¶ms.Src.PublicKey
-
-	params.KeySym = filter1.KeySym
-	params.Topic = BytesToTopic(filter1.Topics[2])
-	params.PoW = filter1.PoW
-	params.WorkTime = 10
-	params.TTL = 50
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	_, err = w.Subscribe(filter1)
-	if err != nil {
-		t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err)
-	}
-
-	_, err = w.Subscribe(filter2)
-	if err != nil {
-		t.Fatalf("failed subscribe 2 with seed %d: %s.", seed, err)
-	}
-
-	err = w.Send(env)
-	if err != nil {
-		t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err)
-	}
-
-	// wait till received or timeout
-	var received bool
-	for j := 0; j < 200; j++ {
-		time.Sleep(10 * time.Millisecond)
-		if len(w.Envelopes()) > 0 {
-			received = true
-			break
-		}
-	}
-
-	if !received {
-		t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
-	}
-
-	// check w.messages()
-	time.Sleep(5 * time.Millisecond)
-	mail1 := filter1.Retrieve()
-	mail2 := filter2.Retrieve()
-	if len(mail2) == 0 {
-		t.Fatalf("did not receive any email for filter 2")
-	}
-	if len(mail1) == 0 {
-		t.Fatalf("did not receive any email for filter 1")
-	}
-
-}
-
-func TestSymmetricSendWithoutAKey(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&DefaultConfig)
-	defer w.SetMinimumPowTest(DefaultMinimumPoW)
-	defer w.SetMaxMessageSize(DefaultMaxMessageSize)
-	w.Start(nil)
-	defer w.Stop()
-
-	filter, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	filter.PoW = DefaultMinimumPoW
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	filter.Src = nil
-
-	params.KeySym = filter.KeySym
-	params.Topic = BytesToTopic(filter.Topics[2])
-	params.PoW = filter.PoW
-	params.WorkTime = 10
-	params.TTL = 50
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	_, err = w.Subscribe(filter)
-	if err != nil {
-		t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err)
-	}
-
-	err = w.Send(env)
-	if err != nil {
-		t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err)
-	}
-
-	// wait till received or timeout
-	var received bool
-	for j := 0; j < 200; j++ {
-		time.Sleep(10 * time.Millisecond)
-		if len(w.Envelopes()) > 0 {
-			received = true
-			break
-		}
-	}
-
-	if !received {
-		t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
-	}
-
-	// check w.messages()
-	time.Sleep(5 * time.Millisecond)
-	mail := filter.Retrieve()
-	if len(mail) == 0 {
-		t.Fatalf("did not receive message in spite of not setting a public key")
-	}
-}
-
-func TestSymmetricSendKeyMismatch(t *testing.T) {
-	InitSingleTest()
-
-	w := New(&DefaultConfig)
-	defer w.SetMinimumPowTest(DefaultMinimumPoW)
-	defer w.SetMaxMessageSize(DefaultMaxMessageSize)
-	w.Start(nil)
-	defer w.Stop()
-
-	filter, err := generateFilter(t, true)
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-	filter.PoW = DefaultMinimumPoW
-
-	params, err := generateMessageParams()
-	if err != nil {
-		t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err)
-	}
-
-	params.KeySym = filter.KeySym
-	params.Topic = BytesToTopic(filter.Topics[2])
-	params.PoW = filter.PoW
-	params.WorkTime = 10
-	params.TTL = 50
-	msg, err := NewSentMessage(params)
-	if err != nil {
-		t.Fatalf("failed to create new message with seed %d: %s.", seed, err)
-	}
-	env, err := msg.Wrap(params)
-	if err != nil {
-		t.Fatalf("failed Wrap with seed %d: %s.", seed, err)
-	}
-
-	_, err = w.Subscribe(filter)
-	if err != nil {
-		t.Fatalf("failed subscribe 1 with seed %d: %s.", seed, err)
-	}
-
-	err = w.Send(env)
-	if err != nil {
-		t.Fatalf("Failed sending envelope with PoW %.06f (seed %d): %s", env.PoW(), seed, err)
-	}
-
-	// wait till received or timeout
-	var received bool
-	for j := 0; j < 200; j++ {
-		time.Sleep(10 * time.Millisecond)
-		if len(w.Envelopes()) > 0 {
-			received = true
-			break
-		}
-	}
-
-	if !received {
-		t.Fatalf("did not receive the sent envelope, seed: %d.", seed)
-	}
-
-	// check w.messages()
-	time.Sleep(5 * time.Millisecond)
-	mail := filter.Retrieve()
-	if len(mail) > 0 {
-		t.Fatalf("received a message when keys weren't matching")
-	}
-}
-
-func TestBloom(t *testing.T) {
-	topic := TopicType{0, 0, 255, 6}
-	b := TopicToBloom(topic)
-	x := make([]byte, BloomFilterSize)
-	x[0] = byte(1)
-	x[32] = byte(1)
-	x[BloomFilterSize-1] = byte(128)
-	if !BloomFilterMatch(x, b) || !BloomFilterMatch(b, x) {
-		t.Fatalf("bloom filter does not match the mask")
-	}
-
-	_, err := mrand.Read(b)
-	if err != nil {
-		t.Fatalf("math rand error")
-	}
-	_, err = mrand.Read(x)
-	if err != nil {
-		t.Fatalf("math rand error")
-	}
-	if !BloomFilterMatch(b, b) {
-		t.Fatalf("bloom filter does not match self")
-	}
-	x = addBloom(x, b)
-	if !BloomFilterMatch(x, b) {
-		t.Fatalf("bloom filter does not match combined bloom")
-	}
-	if !isFullNode(nil) {
-		t.Fatalf("isFullNode did not recognize nil as full node")
-	}
-	x[17] = 254
-	if isFullNode(x) {
-		t.Fatalf("isFullNode false positive")
-	}
-	for i := 0; i < BloomFilterSize; i++ {
-		b[i] = byte(255)
-	}
-	if !isFullNode(b) {
-		t.Fatalf("isFullNode false negative")
-	}
-	if BloomFilterMatch(x, b) {
-		t.Fatalf("bloomFilterMatch false positive")
-	}
-	if !BloomFilterMatch(b, x) {
-		t.Fatalf("bloomFilterMatch false negative")
-	}
-
-	w := New(&DefaultConfig)
-	f := w.BloomFilter()
-	if f != nil {
-		t.Fatalf("wrong bloom on creation")
-	}
-	err = w.SetBloomFilter(x)
-	if err != nil {
-		t.Fatalf("failed to set bloom filter: %s", err)
-	}
-	f = w.BloomFilter()
-	if !BloomFilterMatch(f, x) || !BloomFilterMatch(x, f) {
-		t.Fatalf("retireved wrong bloom filter")
-	}
-}